Blender V4.5
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_endian_switch.h"
18#include "BLI_fileops.h"
19#include "BLI_filereader.h"
20#include "BLI_string.h"
21
22#include "BLO_core_bhead.hh"
24
25#include "blendthumb.hh"
26
28{
29 uint32_t *rect = (uint32_t *)thumb->data.data();
30 int x = thumb->width, y = thumb->height;
31 uint32_t *top = rect;
32 uint32_t *bottom = top + ((y - 1) * x);
33 uint32_t *line = (uint32_t *)malloc(x * sizeof(uint32_t));
34
35 y >>= 1;
36 for (; y > 0; y--) {
37 memcpy(line, top, x * sizeof(uint32_t));
38 memcpy(top, bottom, x * sizeof(uint32_t));
39 memcpy(bottom, line, x * sizeof(uint32_t));
40 bottom -= x;
41 top += x;
42 }
43 free(line);
44}
45
46static int32_t bytes_to_native_i32(const uint8_t bytes[4], bool endian_switch)
47{
49 memcpy(&data, bytes, 4);
50 if (endian_switch) {
52 }
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 const bool endian_switch = header.endian != ENDIAN_ORDER;
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(
92 file, header.bhead_type(), endian_switch);
93 if (!bhead.has_value()) {
94 /* File has ended. */
95 return BT_INVALID_THUMB;
96 }
97 if (bhead->len < 0) {
98 /* Avoid parsing bad data. */
99 return BT_INVALID_THUMB;
100 }
101 switch (bhead->code) {
102 case MAKE_ID('T', 'E', 'S', 'T'): {
103 uint8_t shape[8];
104 if (!file_read(file, shape, sizeof(shape))) {
105 return BT_INVALID_THUMB;
106 }
107 thumb->width = bytes_to_native_i32(&shape[0], endian_switch);
108 thumb->height = bytes_to_native_i32(&shape[4], endian_switch);
109
110 /* Verify that image dimensions and data size make sense. */
111 size_t data_size = bhead->len - sizeof(shape);
112 const uint64_t expected_size = uint64_t(thumb->width) * uint64_t(thumb->height) * 4;
113 if (thumb->width < 0 || thumb->height < 0 || data_size != expected_size) {
114 return BT_INVALID_THUMB;
115 }
116
117 thumb->data = blender::Array<uint8_t>(data_size);
118 if (!file_read(file, thumb->data.data(), data_size)) {
119 return BT_INVALID_THUMB;
120 }
121 return BT_OK;
122 }
123 case MAKE_ID('R', 'E', 'N', 'D'): {
124 if (!file_seek(file, bhead->len)) {
125 return BT_INVALID_THUMB;
126 }
127 /* Check the next block. */
128 break;
129 }
130 default: {
131 /* Early exit if there are no `TEST` or `REND` blocks.
132 * This saves scanning the entire blend file which could be slow. */
133 return BT_INVALID_THUMB;
134 }
135 }
136 }
137 return BT_INVALID_THUMB;
138}
139
141{
142 /* Read header in order to identify file type. */
143 char magic_bytes[12];
144 if (rawfile->read(rawfile, magic_bytes, sizeof(magic_bytes)) != sizeof(magic_bytes)) {
145 rawfile->close(rawfile);
146 return BT_ERROR;
147 }
148
149 /* Rewind the file after reading the header. */
150 rawfile->seek(rawfile, 0, SEEK_SET);
151
152 /* Try to identify the file type from the header. */
153 FileReader *file = nullptr;
154 if (BLI_str_startswith(magic_bytes, "BLENDER")) {
155 file = rawfile;
156 rawfile = nullptr;
157 }
158 else if (BLI_file_magic_is_gzip(magic_bytes)) {
159 file = BLI_filereader_new_gzip(rawfile);
160 if (file != nullptr) {
161 rawfile = nullptr; /* The GZIP #FileReader takes ownership of raw-file. */
162 }
163 }
164 else if (BLI_file_magic_is_zstd(magic_bytes)) {
165 file = BLI_filereader_new_zstd(rawfile);
166 if (file != nullptr) {
167 rawfile = nullptr; /* The ZSTD #FileReader takes ownership of raw-file. */
168 }
169 }
170
171 /* Clean up rawfile if it wasn't taken over. */
172 if (rawfile != nullptr) {
173 rawfile->close(rawfile);
174 }
175
176 if (file == nullptr) {
177 return BT_ERROR;
178 }
179
180 const BlenderHeaderVariant header_variant = BLO_readfile_blender_header_decode(file);
181 if (!std::holds_alternative<BlenderHeader>(header_variant)) {
182 file->close(file);
183 return BT_ERROR;
184 }
185 const BlenderHeader &header = std::get<BlenderHeader>(header_variant);
186
187 /* Check if the file is new enough to contain a thumbnail. */
188 if (header.file_version < 250) {
189 file->close(file);
190 return BT_EARLY_VERSION;
191 }
192
193 /* Read the thumbnail. */
194 eThumbStatus err = blendthumb_extract_from_file_impl(file, thumb, header);
195 file->close(file);
196 if (err != BT_OK) {
197 return err;
198 }
199
201 return BT_OK;
202}
#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
std::optional< BHead > BLO_readfile_read_bhead(FileReader *file, BHeadType type, bool do_endian_swap)
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_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
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 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:301
const T * data() const
Definition BLI_array.hh:301
uint top
static void endian_switch(uint8_t *ptr, int type_size)
BHeadType bhead_type() const
FileReaderSeekFn seek
FileReaderCloseFn close
FileReaderReadFn read
blender::Array< uint8_t > data
Definition blendthumb.hh:23
uint len