Blender V5.0
format_webp.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#ifdef _WIN32
10# include <io.h>
11#else
12# include <unistd.h>
13#endif
14
15#include <cstdio>
16#include <cstdlib>
17#include <fcntl.h>
18#include <webp/decode.h>
19#include <webp/encode.h>
20#include <webp/mux.h>
21
22#include "BLI_fileops.h"
23#include "BLI_mmap.h"
24
25#include "IMB_allocimbuf.hh"
27#include "IMB_filetype.hh"
28#include "IMB_imbuf.hh"
29#include "IMB_imbuf_types.hh"
30
31#include "MEM_guardedalloc.h"
32
33#include "CLG_log.h"
34
35static CLG_LogRef LOG = {"image.webp"};
36
37bool imb_is_a_webp(const uchar *mem, size_t size)
38{
39 if (WebPGetInfo(mem, size, nullptr, nullptr)) {
40 return true;
41 }
42 return false;
43}
44
45ImBuf *imb_loadwebp(const uchar *mem, size_t size, int flags, ImFileColorSpace & /*r_colorspace*/)
46{
47 if (!imb_is_a_webp(mem, size)) {
48 return nullptr;
49 }
50
51 WebPBitstreamFeatures features;
52 if (WebPGetFeatures(mem, size, &features) != VP8_STATUS_OK) {
53 CLOG_ERROR(&LOG, "Failed to parse features");
54 return nullptr;
55 }
56
57 const int planes = features.has_alpha ? 32 : 24;
58 ImBuf *ibuf = IMB_allocImBuf(features.width, features.height, planes, 0);
59
60 if (ibuf == nullptr) {
61 CLOG_ERROR(&LOG, "Failed to allocate image memory");
62 return nullptr;
63 }
64
65 if ((flags & IB_test) == 0) {
66 ibuf->ftype = IMB_FTYPE_WEBP;
68 /* Flip the image during decoding to match Blender. */
69 uchar *last_row = ibuf->byte_buffer.data + (4 * size_t(ibuf->y - 1) * size_t(ibuf->x));
70 if (WebPDecodeRGBAInto(mem, size, last_row, size_t(ibuf->x) * ibuf->y * 4, -4 * ibuf->x) ==
71 nullptr)
72 {
73 CLOG_ERROR(&LOG, "Failed to decode image");
74 }
75 }
76
77 return ibuf;
78}
79
81 const int /*flags*/,
82 const size_t max_thumb_size,
83 ImFileColorSpace & /*r_colorspace*/,
84 size_t *r_width,
85 size_t *r_height)
86{
87 const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
88 if (file == -1) {
89 return nullptr;
90 }
91
92 BLI_mmap_file *mmap_file = BLI_mmap_open(file);
93 close(file);
94 if (mmap_file == nullptr) {
95 return nullptr;
96 }
97
98 const uchar *data = static_cast<const uchar *>(BLI_mmap_get_pointer(mmap_file));
99 const size_t data_size = BLI_mmap_get_length(mmap_file);
100
101 WebPDecoderConfig config;
102 if (!data || !WebPInitDecoderConfig(&config) ||
103 WebPGetFeatures(data, data_size, &config.input) != VP8_STATUS_OK ||
104 BLI_mmap_any_io_error(mmap_file))
105 {
106 CLOG_ERROR(&LOG, "Invalid file");
107 BLI_mmap_free(mmap_file);
108 return nullptr;
109 }
110
111 /* Return full size of the image. */
112 *r_width = size_t(config.input.width);
113 *r_height = size_t(config.input.height);
114
115 const float scale = float(max_thumb_size) / std::max(config.input.width, config.input.height);
116 const int dest_w = std::max(int(config.input.width * scale), 1);
117 const int dest_h = std::max(int(config.input.height * scale), 1);
118
119 ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, 32, IB_byte_data);
120 if (ibuf == nullptr) {
121 CLOG_ERROR(&LOG, "Failed to allocate image memory");
122 BLI_mmap_free(mmap_file);
123 return nullptr;
124 }
125
126 config.options.no_fancy_upsampling = 1;
127 config.options.use_scaling = 1;
128 config.options.scaled_width = dest_w;
129 config.options.scaled_height = dest_h;
130 config.options.bypass_filtering = 1;
131 config.options.use_threads = 0;
132 config.options.flip = 1;
133 config.output.is_external_memory = 1;
134 config.output.colorspace = MODE_RGBA;
135 config.output.u.RGBA.rgba = ibuf->byte_buffer.data;
136 config.output.u.RGBA.stride = 4 * ibuf->x;
137 config.output.u.RGBA.size = size_t(config.output.u.RGBA.stride) * size_t(ibuf->y);
138
139 if (WebPDecode(data, data_size, &config) != VP8_STATUS_OK || BLI_mmap_any_io_error(mmap_file)) {
140 CLOG_ERROR(&LOG, "Failed to decode image");
141 IMB_freeImBuf(ibuf);
142 BLI_mmap_free(mmap_file);
143 return nullptr;
144 }
145
146 /* Free the output buffer. */
147 WebPFreeDecBuffer(&config.output);
148
149 BLI_mmap_free(mmap_file);
150
151 return ibuf;
152}
153
154bool imb_savewebp(ImBuf *ibuf, const char *filepath, int /*flags*/)
155{
156 const uint limit = 16383;
157 if (ibuf->x > limit || ibuf->y > limit) {
158 CLOG_ERROR(&LOG, "image x/y exceeds %u", limit);
159 return false;
160 }
161
162 const int bytesperpixel = (ibuf->planes + 7) >> 3;
163 uchar *encoded_data, *last_row;
164 size_t encoded_data_size;
165
166 if (bytesperpixel == 3) {
167 /* We must convert the ImBuf RGBA buffer to RGB as WebP expects a RGB buffer. */
168 const size_t num_pixels = IMB_get_pixel_count(ibuf);
169 const uint8_t *rgba_rect = ibuf->byte_buffer.data;
170 uint8_t *rgb_rect = MEM_malloc_arrayN<uint8_t>(num_pixels * 3, "webp rgb_rect");
171 for (size_t i = 0; i < num_pixels; i++) {
172 rgb_rect[i * 3 + 0] = rgba_rect[i * 4 + 0];
173 rgb_rect[i * 3 + 1] = rgba_rect[i * 4 + 1];
174 rgb_rect[i * 3 + 2] = rgba_rect[i * 4 + 2];
175 }
176
177 last_row = (uchar *)(rgb_rect + (size_t(ibuf->y - 1) * size_t(ibuf->x) * 3));
178
179 if (ibuf->foptions.quality == 100.0f) {
180 encoded_data_size = WebPEncodeLosslessRGB(
181 last_row, ibuf->x, ibuf->y, -3 * ibuf->x, &encoded_data);
182 }
183 else {
184 encoded_data_size = WebPEncodeRGB(
185 last_row, ibuf->x, ibuf->y, -3 * ibuf->x, ibuf->foptions.quality, &encoded_data);
186 }
187 MEM_freeN(rgb_rect);
188 }
189 else if (bytesperpixel == 4) {
190 last_row = ibuf->byte_buffer.data + 4 * size_t(ibuf->y - 1) * size_t(ibuf->x);
191
192 if (ibuf->foptions.quality == 100.0f) {
193 encoded_data_size = WebPEncodeLosslessRGBA(
194 last_row, ibuf->x, ibuf->y, -4 * ibuf->x, &encoded_data);
195 }
196 else {
197 encoded_data_size = WebPEncodeRGBA(
198 last_row, ibuf->x, ibuf->y, -4 * ibuf->x, ibuf->foptions.quality, &encoded_data);
199 }
200 }
201 else {
202 CLOG_ERROR(&LOG, "Unsupported bytes per pixel: %d for file: '%s'", bytesperpixel, filepath);
203 return false;
204 }
205
206 if (encoded_data == nullptr) {
207 return false;
208 }
209
210 WebPMux *mux = WebPMuxNew();
211 WebPData image_data = {encoded_data, encoded_data_size};
212 WebPMuxSetImage(mux, &image_data, false /* Don't copy data */);
213
214 /* Write ICC profile if there is one associated with the colorspace. */
215 const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
216 if (colorspace) {
218 if (!icc_profile.is_empty()) {
219 WebPData icc_chunk = {reinterpret_cast<const uint8_t *>(icc_profile.data()),
220 size_t(icc_profile.size())};
221 WebPMuxSetChunk(mux, "ICCP", &icc_chunk, true /* copy data */);
222 }
223 }
224
225 /* Assemble image and metadata. */
226 WebPData output_data;
227 if (WebPMuxAssemble(mux, &output_data) != WEBP_MUX_OK) {
228 CLOG_ERROR(&LOG, "Error in mux assemble writing file: '%s'", filepath);
229 WebPMuxDelete(mux);
230 WebPFree(encoded_data);
231 return false;
232 }
233
234 /* Write to file. */
235 bool ok = true;
236 FILE *fp = BLI_fopen(filepath, "wb");
237 if (fp) {
238 if (fwrite(output_data.bytes, output_data.size, 1, fp) != 1) {
239 CLOG_ERROR(&LOG, "Unknown error writing file: '%s'", filepath);
240 ok = false;
241 }
242
243 fclose(fp);
244 }
245 else {
246 ok = false;
247 CLOG_ERROR(&LOG, "Cannot open file for writing: '%s'", filepath);
248 }
249
250 WebPMuxDelete(mux);
251 WebPFree(encoded_data);
252 WebPDataClear(&output_data);
253
254 return ok;
255}
blender::ocio::ColorSpace ColorSpace
Definition BLF_api.hh:38
File and directory operations.
#define O_BINARY
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void * BLI_mmap_get_pointer(BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:472
void BLI_mmap_free(BLI_mmap_file *file) ATTR_NONNULL(1)
Definition BLI_mmap.cc:487
bool BLI_mmap_any_io_error(const BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:482
BLI_mmap_file * BLI_mmap_open(int fd) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:367
size_t BLI_mmap_get_length(const BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:477
unsigned char uchar
unsigned int uint
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
blender::Vector< char > IMB_colormanagement_space_to_icc_profile(const ColorSpace *colorspace)
bool IMB_alloc_byte_pixels(ImBuf *ibuf, bool initialize_pixels=true)
void IMB_freeImBuf(ImBuf *ibuf)
size_t IMB_get_pixel_count(const ImBuf *ibuf)
Get the length of the data of the given image buffer in pixels.
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
@ IB_byte_data
@ IB_test
Read Guarded memory(de)allocation.
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
int64_t size() const
bool is_empty() const
nullptr float
bool imb_is_a_webp(const uchar *mem, size_t size)
ImBuf * imb_loadwebp(const uchar *mem, size_t size, int flags, ImFileColorSpace &)
ImBuf * imb_load_filepath_thumbnail_webp(const char *filepath, const int, const size_t max_thumb_size, ImFileColorSpace &, size_t *r_width, size_t *r_height)
bool imb_savewebp(ImBuf *ibuf, const char *filepath, int)
#define LOG(level)
Definition log.h:97
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
const ColorSpace * colorspace
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
unsigned char planes
enum eImbFileType ftype
i
Definition text_draw.cc:230