Blender V4.3
blendthumb_extract.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12#include <cctype>
13#include <cstring>
14
15#include "BLI_alloca.h"
16#include "BLI_endian_defines.h"
17#include "BLI_endian_switch.h"
18#include "BLI_fileops.h"
19#include "BLI_filereader.h"
20#include "BLI_string.h"
21
22#include "blendthumb.hh"
23
24static bool blend_header_check_magic(const char header[12])
25{
26 /* Check magic string at start of file. */
27 if (!BLI_str_startswith(header, "BLENDER")) {
28 return false;
29 }
30 /* Check pointer size and endianness indicators. */
31 if (!ELEM(header[7], '_', '-') || !ELEM(header[8], 'v', 'V')) {
32 return false;
33 }
34 /* Check version number. */
35 if (!isdigit(header[9]) || !isdigit(header[10]) || !isdigit(header[11])) {
36 return false;
37 }
38 return true;
39}
40
41static bool blend_header_is_version_valid(const char header[12])
42{
43 /* Thumbnails are only in files with version >= 2.50 */
44 char num[4];
45 memcpy(num, header + 9, 3);
46 num[3] = 0;
47 return atoi(num) >= 250;
48}
49
50static int blend_header_pointer_size(const char header[12])
51{
52 return header[7] == '_' ? 4 : 8;
53}
54
55static bool blend_header_is_endian_switch_needed(const char header[12])
56{
57 return (((header[8] == 'v') ? L_ENDIAN : B_ENDIAN) != ENDIAN_ORDER);
58}
59
61{
62 uint32_t *rect = (uint32_t *)thumb->data.data();
63 int x = thumb->width, y = thumb->height;
64 uint32_t *top = rect;
65 uint32_t *bottom = top + ((y - 1) * x);
66 uint32_t *line = (uint32_t *)malloc(x * sizeof(uint32_t));
67
68 y >>= 1;
69 for (; y > 0; y--) {
70 memcpy(line, top, x * sizeof(uint32_t));
71 memcpy(top, bottom, x * sizeof(uint32_t));
72 memcpy(bottom, line, x * sizeof(uint32_t));
73 bottom -= x;
74 top += x;
75 }
76 free(line);
77}
78
80{
82 memcpy(&data, bytes, 4);
83 if (endian_switch) {
85 }
86 return data;
87}
88
89static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
90{
91 return (file->read(file, buf, buf_len) == buf_len);
92}
93
94static bool file_seek(FileReader *file, size_t len)
95{
96 if (file->seek != nullptr) {
97 if (file->seek(file, len, SEEK_CUR) == -1) {
98 return false;
99 }
100 return true;
101 }
102
103 /* File doesn't support seeking (e.g. gzip), so read and discard in chunks. */
104 constexpr size_t dummy_data_size = 4096;
105 blender::Array<char> dummy_data(dummy_data_size);
106 while (len > 0) {
107 const size_t len_chunk = std::min(len, dummy_data_size);
108 if (size_t(file->read(file, dummy_data.data(), len_chunk)) != len_chunk) {
109 return false;
110 }
111 len -= len_chunk;
112 }
113 return true;
114}
115
117 Thumbnail *thumb,
118 const size_t bhead_size,
119 const bool endian_switch)
120{
121 /* Iterate over file blocks until we find the thumbnail or run out of data. */
122 uint8_t *bhead_data = (uint8_t *)BLI_array_alloca(bhead_data, bhead_size);
123 while (file_read(file, bhead_data, bhead_size)) {
124 /* Parse type and size from `BHead`. */
125 const int32_t block_size = bytes_to_native_i32(&bhead_data[4], endian_switch);
126 if (UNLIKELY(block_size < 0)) {
127 return BT_INVALID_THUMB;
128 }
129
130 /* We're looking for the thumbnail, so skip any other block. */
131 switch (*((int32_t *)bhead_data)) {
132 case MAKE_ID('T', 'E', 'S', 'T'): {
133 uint8_t shape[8];
134 if (!file_read(file, shape, sizeof(shape))) {
135 return BT_INVALID_THUMB;
136 }
137 thumb->width = bytes_to_native_i32(&shape[0], endian_switch);
138 thumb->height = bytes_to_native_i32(&shape[4], endian_switch);
139
140 /* Verify that image dimensions and data size make sense. */
141 size_t data_size = block_size - sizeof(shape);
142 const uint64_t expected_size = uint64_t(thumb->width) * uint64_t(thumb->height) * 4;
143 if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
144 return BT_INVALID_THUMB;
145 }
146
147 thumb->data = blender::Array<uint8_t>(data_size);
148 if (!file_read(file, thumb->data.data(), data_size)) {
149 return BT_INVALID_THUMB;
150 }
151 return BT_OK;
152 }
153 case MAKE_ID('R', 'E', 'N', 'D'): {
154 if (!file_seek(file, block_size)) {
155 return BT_INVALID_THUMB;
156 }
157 /* Check the next block. */
158 break;
159 }
160 default: {
161 /* Early exit if there are no `TEST` or `REND` blocks.
162 * This saves scanning the entire blend file which could be slow. */
163 return BT_INVALID_THUMB;
164 }
165 }
166 }
167
168 return BT_INVALID_THUMB;
169}
170
172{
173 /* Read header in order to identify file type. */
174 char header[12];
175 if (rawfile->read(rawfile, header, sizeof(header)) != sizeof(header)) {
176 rawfile->close(rawfile);
177 return BT_ERROR;
178 }
179
180 /* Rewind the file after reading the header. */
181 rawfile->seek(rawfile, 0, SEEK_SET);
182
183 /* Try to identify the file type from the header. */
184 FileReader *file = nullptr;
185 if (BLI_str_startswith(header, "BLENDER")) {
186 file = rawfile;
187 rawfile = nullptr;
188 }
189 else if (BLI_file_magic_is_gzip(header)) {
190 file = BLI_filereader_new_gzip(rawfile);
191 if (file != nullptr) {
192 rawfile = nullptr; /* The GZIP #FileReader takes ownership of raw-file. */
193 }
194 }
195 else if (BLI_file_magic_is_zstd(header)) {
196 file = BLI_filereader_new_zstd(rawfile);
197 if (file != nullptr) {
198 rawfile = nullptr; /* The ZSTD #FileReader takes ownership of raw-file. */
199 }
200 }
201
202 /* Clean up rawfile if it wasn't taken over. */
203 if (rawfile != nullptr) {
204 rawfile->close(rawfile);
205 }
206
207 if (file == nullptr) {
208 return BT_ERROR;
209 }
210
211 /* Re-read header in case we had compression. */
212 if (file->read(file, header, sizeof(header)) != sizeof(header)) {
213 file->close(file);
214 return BT_ERROR;
215 }
216
217 /* Check if the header format is valid for a .blend file. */
218 if (!blend_header_check_magic(header)) {
219 file->close(file);
220 return BT_INVALID_FILE;
221 }
222
223 /* Check if the file is new enough to contain a thumbnail. */
224 if (!blend_header_is_version_valid(header)) {
225 file->close(file);
226 return BT_EARLY_VERSION;
227 }
228
229 /* Depending on where it was saved, the file can use different pointer size or endianness. */
230 int bhead_size = 16 + blend_header_pointer_size(header);
232
233 /* Read the thumbnail. */
234 eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, bhead_size, endian_switch);
235 file->close(file);
236 if (err != BT_OK) {
237 return err;
238 }
239
241 return BT_OK;
242}
#define BLI_array_alloca(arr, realsize)
Definition BLI_alloca.h:25
#define B_ENDIAN
#define L_ENDIAN
#define ENDIAN_ORDER
BLI_INLINE void BLI_endian_switch_int32(int *val) ATTR_NONNULL(1)
File and directory operations.
bool BLI_file_magic_is_gzip(const char header[4])
Definition fileops_c.cc:257
bool BLI_file_magic_is_zstd(const char header[4])
Definition fileops_c.cc:264
Wrapper for reading from various sources (e.g. raw files, compressed files, memory....
FileReader * BLI_filereader_new_zstd(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
FileReader * BLI_filereader_new_gzip(FileReader *base) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void BLI_kdtree_nd_ free(KDTree *tree)
int bool BLI_str_startswith(const char *__restrict str, const char *__restrict start) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
#define UNLIKELY(x)
#define ELEM(...)
eThumbStatus
Definition blendthumb.hh:28
@ BT_EARLY_VERSION
Definition blendthumb.hh:34
@ BT_INVALID_FILE
Definition blendthumb.hh:33
@ BT_ERROR
Definition blendthumb.hh:36
@ BT_INVALID_THUMB
Definition blendthumb.hh:35
@ BT_OK
Definition blendthumb.hh:29
#define MAKE_ID(a, b, c, d)
Definition blendthumb.hh:53
static bool blend_header_is_version_valid(const char header[12])
eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *thumb)
static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
static int32_t bytes_to_native_i32(const uint8_t bytes[4], bool endian_switch)
static bool file_seek(FileReader *file, size_t len)
static int blend_header_pointer_size(const char header[12])
static bool blend_header_is_endian_switch_needed(const char header[12])
static void thumb_data_vertical_flip(Thumbnail *thumb)
static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file, Thumbnail *thumb, const size_t bhead_size, const bool endian_switch)
static bool blend_header_check_magic(const char header[12])
const T * data() const
Definition BLI_array.hh:301
int len
static void endian_switch(uint8_t *ptr, int type_size)
unsigned int uint32_t
Definition stdint.h:80
signed int int32_t
Definition stdint.h:77
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
FileReaderSeekFn seek
FileReaderCloseFn close
FileReaderReadFn read
blender::Array< uint8_t > data
Definition blendthumb.hh:23