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:
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;
+}