Blender V5.0
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
11
12#include <cctype>
13#include <cstring>
14
15#include "BLI_alloca.h"
16#include "BLI_endian_defines.h"
17#include "BLI_fileops.h"
18#include "BLI_filereader.h"
19#include "BLI_string.h"
20
21#include "BLO_core_bhead.hh"
23
24#include "blendthumb.hh"
25
26BLI_STATIC_ASSERT(ENDIAN_ORDER == L_ENDIAN, "Blender only builds on little endian systems")
27
29{
30 uint32_t *rect = (uint32_t *)thumb->data.data();
31 int x = thumb->width, y = thumb->height;
32 uint32_t *top = rect;
33 uint32_t *bottom = top + ((y - 1) * x);
34 uint32_t *line = (uint32_t *)malloc(x * sizeof(uint32_t));
35
36 y >>= 1;
37 for (; y > 0; y--) {
38 memcpy(line, top, x * sizeof(uint32_t));
39 memcpy(top, bottom, x * sizeof(uint32_t));
40 memcpy(bottom, line, x * sizeof(uint32_t));
41 bottom -= x;
42 top += x;
43 }
44 free(line);
45}
46
47static int32_t bytes_to_native_i32(const uint8_t bytes[4])
48{
50 memcpy(&data, bytes, 4);
51 /* NOTE: this is endianness-sensitive. */
52 /* PNG is always little-endian, and would require switching on a big-endian system. */
53 return data;
54}
55
56static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
57{
58 return (file->read(file, buf, buf_len) == buf_len);
59}
60
61static bool file_seek(FileReader *file, size_t len)
62{
63 if (file->seek != nullptr) {
64 if (file->seek(file, len, SEEK_CUR) == -1) {
65 return false;
66 }
67 return true;
68 }
69
70 /* File doesn't support seeking (e.g. gzip), so read and discard in chunks. */
71 constexpr size_t dummy_data_size = 4096;
72 blender::Array<char> dummy_data(dummy_data_size);
73 while (len > 0) {
74 const size_t len_chunk = std::min(len, dummy_data_size);
75 if (size_t(file->read(file, dummy_data.data(), len_chunk)) != len_chunk) {
76 return false;
77 }
78 len -= len_chunk;
79 }
80 return true;
81}
82
84 Thumbnail *thumb,
85 const BlenderHeader &header)
86{
87 BLI_assert(header.endian == L_ENDIAN);
88 /* Iterate over file blocks until we find the thumbnail or run out of data. */
89 while (true) {
90 /* Read next BHead. */
91 const std::optional<BHead> bhead = BLO_readfile_read_bhead(file, header.bhead_type());
92 if (!bhead.has_value()) {
93 /* File has ended. */
94 return BT_INVALID_THUMB;
95 }
96 if (bhead->len < 0) {
97 /* Avoid parsing bad data. */
98 return BT_INVALID_THUMB;
99 }
100 switch (bhead->code) {
101 case MAKE_ID('T', 'E', 'S', 'T'): {
102 uint8_t shape[8];
103 if (!file_read(file, shape, sizeof(shape))) {
104 return BT_INVALID_THUMB;
105 }
106 thumb->width = bytes_to_native_i32(&shape[0]);
107 thumb->height = bytes_to_native_i32(&shape[4]);
108
109 /* Verify that image dimensions and data size make sense. */
110 size_t data_size = bhead->len - sizeof(shape);
111 const uint64_t expected_size = uint64_t(thumb->width) * uint64_t(thumb->height) * 4;
112 if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
113 return BT_INVALID_THUMB;
114 }
115
116 thumb->data = blender::Array<uint8_t>(data_size);
117 if (!file_read(file, thumb->data.data(), data_size)) {
118 return BT_INVALID_THUMB;
119 }
120 return BT_OK;
121 }
122 case MAKE_ID('R', 'E', 'N', 'D'): {
123 if (!file_seek(file, bhead->len)) {
124 return BT_INVALID_THUMB;
125 }
126 /* Check the next block. */
127 break;
128 }
129 default: {
130 /* Early exit if there are no `TEST` or `REND` blocks.
131 * This saves scanning the entire blend file which could be slow. */
132 return BT_INVALID_THUMB;
133 }
134 }
135 }
136 return BT_INVALID_THUMB;
137}
138
140{
141 /* Read header in order to identify file type. */
142 char magic_bytes[12];
143 if (rawfile->read(rawfile, magic_bytes, sizeof(magic_bytes)) != sizeof(magic_bytes)) {
144 rawfile->close(rawfile);
145 return BT_ERROR;
146 }
147
148 /* Rewind the file after reading the header. */
149 rawfile->seek(rawfile, 0, SEEK_SET);
150
151 /* Try to identify the file type from the header. */
152 FileReader *file = nullptr;
153 if (BLI_str_startswith(magic_bytes, "BLENDER")) {
154 file = rawfile;
155 rawfile = nullptr;
156 }
157 else if (BLI_file_magic_is_gzip(magic_bytes)) {
158 file = BLI_filereader_new_gzip(rawfile);
159 if (file != nullptr) {
160 rawfile = nullptr; /* The GZIP #FileReader takes ownership of raw-file. */
161 }
162 }
163 else if (BLI_file_magic_is_zstd(magic_bytes)) {
164 file = BLI_filereader_new_zstd(rawfile);
165 if (file != nullptr) {
166 rawfile = nullptr; /* The ZSTD #FileReader takes ownership of raw-file. */
167 }
168 }
169
170 /* Clean up rawfile if it wasn't taken over. */
171 if (rawfile != nullptr) {
172 rawfile->close(rawfile);
173 }
174
175 if (file == nullptr) {
176 return BT_ERROR;
177 }
178
179 const BlenderHeaderVariant header_variant = BLO_readfile_blender_header_decode(file);
180 if (!std::holds_alternative<BlenderHeader>(header_variant)) {
181 file->close(file);
182 return BT_ERROR;
183 }
184 const BlenderHeader &header = std::get<BlenderHeader>(header_variant);
185
186 /* Check if the file is new enough to contain a thumbnail. */
187 if (header.file_version < 250) {
188 file->close(file);
189 return BT_EARLY_VERSION;
190 }
191
192 /* Check if the file was written from a big-endian build. */
193 if (header.endian != L_ENDIAN) {
194 file->close(file);
195 return BT_INVALID_FILE;
196 }
197 BLI_assert(header.endian == ENDIAN_ORDER);
198
199 /* Read the thumbnail. */
200 eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, header);
201 file->close(file);
202 if (err != BT_OK) {
203 return err;
204 }
205
207 return BT_OK;
208}
#define BLI_STATIC_ASSERT(a, msg)
Definition BLI_assert.h:83
#define BLI_assert(a)
Definition BLI_assert.h:46
#define L_ENDIAN
#define ENDIAN_ORDER
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
std::optional< BHead > BLO_readfile_read_bhead(FileReader *file, BHeadType type)
BlenderHeaderVariant BLO_readfile_blender_header_decode(FileReader *file)
std::variant< BlenderHeaderInvalid, BlenderHeaderUnknown, BlenderHeader > BlenderHeaderVariant
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:49
eThumbStatus blendthumb_create_thumb_from_file(FileReader *rawfile, Thumbnail *thumb)
static bool file_read(FileReader *file, uint8_t *buf, size_t buf_len)
static bool file_seek(FileReader *file, size_t len)
static int32_t bytes_to_native_i32(const uint8_t bytes[4])
static eThumbStatus blendthumb_extract_from_file_impl(FileReader *file, Thumbnail *thumb, const BlenderHeader &header)
static void thumb_data_vertical_flip(Thumbnail *thumb)
BMesh const char void * data
unsigned long long int uint64_t
const T * data() const
Definition BLI_array.hh:312
const T * data() const
Definition BLI_array.hh:312
uint top
BHeadType bhead_type() const
FileReaderSeekFn seek
FileReaderCloseFn close
FileReaderReadFn read
blender::Array< uint8_t > data
Definition blendthumb.hh:23
uint len