Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2021-10-20 02:16:36 +0300
committerCampbell Barton <ideasman42@gmail.com>2021-10-20 02:31:30 +0300
commitef9269bd62f31e39d39cc59cd34354b53eae6661 (patch)
tree5ff2fb2f3b03678b7e58a23397d4d53bb83f35f8 /source/blender/blendthumb/src/blendthumb_extract.cc
parentbca2701236ab60dce5e32a8731f3bba43783b5b2 (diff)
Thumbnails: refactor extraction to use one code-path for all platforms
Thumbnail extraction now shares code between Linux/Windows, allowing thumbnails from Zstd compressed blend files to be extracted. The main logic is placed in blendthumb_extract.cc and is built as static library. For windows there is DLL which is registered during blender install and which then reads and generates thumbnails. For other platforms there is blender-thumbnailer executable file which takes blend file as an input and generates PNG file. As a result Python script blender-thumbnailer.py is no longer needed. The thumbnail extractor shares the same code-path as Blenders file reading, so there is no need to duplicate any file reading logic. This means reading compressed blend files is supported (broken since the recent move Zstd compression - D5799). This resolves T63736. Contributors: - @alausic original patch. - @LazyDodo windows fixes/support. - @campbellbarton general fixes/update. - @lukasstockner97 Zstd support. Reviewed By: sybren, mont29, LazyDodo, campbellbarton Ref D6408
Diffstat (limited to 'source/blender/blendthumb/src/blendthumb_extract.cc')
-rw-r--r--source/blender/blendthumb/src/blendthumb_extract.cc257
1 files changed, 257 insertions, 0 deletions
diff --git a/source/blender/blendthumb/src/blendthumb_extract.cc b/source/blender/blendthumb/src/blendthumb_extract.cc
new file mode 100644
index 00000000000..99b13c89994
--- /dev/null
+++ b/source/blender/blendthumb/src/blendthumb_extract.cc
@@ -0,0 +1,257 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup blendthumb
+ *
+ * Expose #blendthumb_create_thumb_from_file that creates the PNG data
+ * but does not write it to a file.
+ */
+
+#include <cstring>
+
+#include "BLI_alloca.h"
+#include "BLI_endian_defines.h"
+#include "BLI_endian_switch.h"
+#include "BLI_fileops.h"
+#include "BLI_filereader.h"
+#include "BLI_string.h"
+
+#include "blendthumb.hh"
+
+static bool blend_header_check_magic(const char header[12])
+{
+ /* Check magic string at start of file. */
+ if (!BLI_str_startswith(header, "BLENDER")) {
+ return false;
+ }
+ /* Check pointer size and endianness indicators. */
+ if (((header[7] != '_') && (header[7] != '-')) || ((header[8] != 'v') && (header[8] != 'V'))) {
+ return false;
+ }
+ /* Check version number. */
+ if (!isdigit(header[9]) || !isdigit(header[10]) || !isdigit(header[11])) {
+ return false;
+ }
+ return true;
+}
+
+static bool blend_header_is_version_valid(const char header[12])
+{
+ /* Thumbnails are only in files with version >= 2.50 */
+ char num[4];
+ memcpy(num, header + 9, 3);
+ num[3] = 0;
+ return atoi(num) >= 250;
+}
+
+static int blend_header_pointer_size(const char header[12])
+{
+ return header[7] == '_' ? 4 : 8;
+}
+
+static bool blend_header_is_endian_switch_needed(const char header[12])
+{
+ return (((header[8] == 'v') ? L_ENDIAN : B_ENDIAN) != ENDIAN_ORDER);
+}
+
+static void thumb_data_vertical_flip(Thumbnail *thumb)
+{
+ uint32_t *rect = (uint32_t *)thumb->data.data();
+ int x = thumb->width, y = thumb->height;
+ uint32_t *top = rect;
+ uint32_t *bottom = top + ((y - 1) * x);
+ uint32_t *line = (uint32_t *)malloc(x * sizeof(uint32_t));
+
+ y >>= 1;
+ for (; y > 0; y--) {
+ memcpy(line, top, x * sizeof(uint32_t));
+ memcpy(top, bottom, x * sizeof(uint32_t));
+ memcpy(bottom, line, x * sizeof(uint32_t));
+ bottom -= x;
+ top += x;
+ }
+ free(line);
+}
+
+static int32_t bytes_to_native_i32(const uint8_t bytes[4], bool endian_switch)
+{
+ int32_t data;
+ memcpy(&data, bytes, 4);
+ if (endian_switch) {
+ BLI_endian_switch_int32(&data);
+ }
+ return data;
+}
+
+static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
+{
+ return (file->read(file, buf, buf_len) == buf_len);
+}
+
+static bool file_seek(FileReader *file, size_t len)
+{
+ if (file->seek != nullptr) {
+ if (file->seek(file, len, SEEK_CUR) == -1) {
+ return false;
+ }
+ return true;
+ }
+
+ /* File doesn't support seeking (e.g. gzip), so read and discard in chunks. */
+ constexpr size_t dummy_data_size = 4096;
+ blender::Array<char> dummy_data(dummy_data_size);
+ while (len > 0) {
+ const size_t len_chunk = std::min(len, dummy_data_size);
+ if ((size_t)file->read(file, dummy_data.data(), len_chunk) != len_chunk) {
+ return false;
+ }
+ len -= len_chunk;
+ }
+ return true;
+}
+
+static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file,
+ Thumbnail *thumb,
+ const size_t bhead_size,
+ const bool endian_switch)
+{
+ /* Iterate over file blocks until we find the thumbnail or run out of data. */
+ uint8_t *bhead_data = (uint8_t *)BLI_array_alloca(bhead_data, bhead_size);
+ while (file_read(file, bhead_data, bhead_size)) {
+ /* Parse type and size from `BHead`. */
+ const int32_t block_size = bytes_to_native_i32(&bhead_data[4], endian_switch);
+
+ /* We're looking for the thumbnail, so skip any other block. */
+ switch (*((int32_t *)bhead_data)) {
+ case MAKE_ID('T', 'E', 'S', 'T'): {
+ uint8_t shape[8];
+ if (!file_read(file, shape, sizeof(shape))) {
+ return BT_INVALID_THUMB;
+ }
+ thumb->width = bytes_to_native_i32(&shape[0], endian_switch);
+ thumb->height = bytes_to_native_i32(&shape[4], endian_switch);
+
+ /* Verify that image dimensions and data size make sense. */
+ size_t data_size = block_size - 8;
+ const size_t expected_size = thumb->width * thumb->height * 4;
+ if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
+ return BT_INVALID_THUMB;
+ }
+
+ thumb->data = blender::Array<uint8_t>(data_size);
+ if (!file_read(file, thumb->data.data(), data_size)) {
+ return BT_INVALID_THUMB;
+ }
+ return BT_OK;
+ }
+ case MAKE_ID('R', 'E', 'N', 'D'): {
+ if (!file_seek(file, block_size)) {
+ return BT_INVALID_THUMB;
+ }
+ /* Check the next block. */
+ break;
+ }
+ default: {
+ /* Early exit if there are no `TEST` or `REND` blocks.
+ * This saves scanning the entire blend file which could be slow. */
+ return BT_INVALID_THUMB;
+ }
+ }
+ }
+
+ return BT_INVALID_THUMB;
+}
+
+/**
+ * This function extracts the thumbnail from the .blend file into thumb.
+ * Returns #BT_OK for success and the relevant error code otherwise.
+ */
+eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *thumb)
+{
+ /* Read header in order to identify file type. */
+ char header[12];
+ if (rawfile->read(rawfile, header, sizeof(header)) != sizeof(header)) {
+ rawfile->close(rawfile);
+ return BT_ERROR;
+ }
+
+ /* Rewind the file after reading the header. */
+ rawfile->seek(rawfile, 0, SEEK_SET);
+
+ /* Try to identify the file type from the header. */
+ FileReader *file = nullptr;
+ if (BLI_str_startswith(header, "BLENDER")) {
+ file = rawfile;
+ rawfile = nullptr;
+ }
+ else if (BLI_file_magic_is_gzip(header)) {
+ file = BLI_filereader_new_gzip(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The Gzip #FileReader takes ownership of raw-file. */
+ }
+ }
+ else if (BLI_file_magic_is_zstd(header)) {
+ file = BLI_filereader_new_zstd(rawfile);
+ if (file != nullptr) {
+ rawfile = nullptr; /* The Zstd #FileReader takes ownership of raw-file. */
+ }
+ }
+
+ /* Clean up rawfile if it wasn't taken over. */
+ if (rawfile != nullptr) {
+ rawfile->close(rawfile);
+ }
+
+ if (file == nullptr) {
+ return BT_ERROR;
+ }
+
+ /* Re-read header in case we had compression. */
+ if (file->read(file, header, sizeof(header)) != sizeof(header)) {
+ file->close(file);
+ return BT_ERROR;
+ }
+
+ /* Check if the header format is valid for a .blend file. */
+ if (!blend_header_check_magic(header)) {
+ file->close(file);
+ return BT_INVALID_FILE;
+ }
+
+ /* Check if the file is new enough to contain a thumbnail. */
+ if (!blend_header_is_version_valid(header)) {
+ file->close(file);
+ return BT_EARLY_VERSION;
+ }
+
+ /* Depending on where it was saved, the file can use different pointer size or endianness. */
+ int bhead_size = 16 + blend_header_pointer_size(header);
+ const bool endian_switch = blend_header_is_endian_switch_needed(header);
+
+ /* Read the thumbnail. */
+ eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, bhead_size, endian_switch);
+ file->close(file);
+ if (err != BT_OK) {
+ return err;
+ }
+
+ thumb_data_vertical_flip(thumb);
+ return BT_OK;
+}