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