Blender V4.3
openimageio_support.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
6#include <OpenImageIO/imagebuf.h>
7#include <OpenImageIO/imagebufalgo.h>
8
9#include "BLI_blenlib.h"
10
11#include "BKE_idprop.hh"
12#include "DNA_ID.h" /* ID property definitions. */
13
14#include "IMB_allocimbuf.hh"
16#include "IMB_metadata.hh"
17
18OIIO_NAMESPACE_USING
19
20using std::string;
21using std::unique_ptr;
22
23namespace blender::imbuf {
24
25/* An OIIO IOProxy used during file packing to write into an in-memory #ImBuf buffer. */
26class ImBufMemWriter : public Filesystem::IOProxy {
27 public:
28 ImBufMemWriter(ImBuf *ibuf) : IOProxy("", Write), ibuf_(ibuf) {}
29
30 const char *proxytype() const override
31 {
32 return "ImBufMemWriter";
33 }
34
35 size_t write(const void *buf, size_t size) override
36 {
37 size = pwrite(buf, size, m_pos);
38 m_pos += size;
39 return size;
40 }
41
42 size_t pwrite(const void *buf, size_t size, int64_t offset) override
43 {
44 /* If buffer is too small increase it. */
45 size_t end = offset + size;
46 while (end > ibuf_->encoded_buffer_size) {
48 /* Out of memory. */
49 return 0;
50 }
51 }
52
53 memcpy(ibuf_->encoded_buffer.data + offset, buf, size);
54
55 if (end > ibuf_->encoded_size) {
56 ibuf_->encoded_size = end;
57 }
58
59 return size;
60 }
61
62 size_t size() const override
63 {
64 return ibuf_->encoded_size;
65 }
66
67 private:
68 ImBuf *ibuf_;
69};
70
71/* Utility to in-place expand an n-component pixel buffer into a 4-component buffer. */
72template<typename T>
73static void fill_all_channels(T *pixels, int width, int height, int components, T alpha)
74{
75 const int64_t pixel_count = int64_t(width) * height;
76 if (components == 3) {
77 for (int64_t i = 0; i < pixel_count; i++) {
78 pixels[i * 4 + 3] = alpha;
79 }
80 }
81 else if (components == 1) {
82 for (int64_t i = 0; i < pixel_count; i++) {
83 pixels[i * 4 + 3] = alpha;
84 pixels[i * 4 + 2] = pixels[i * 4 + 0];
85 pixels[i * 4 + 1] = pixels[i * 4 + 0];
86 }
87 }
88 else if (components == 2) {
89 for (int64_t i = 0; i < pixel_count; i++) {
90 pixels[i * 4 + 3] = pixels[i * 4 + 1];
91 pixels[i * 4 + 2] = pixels[i * 4 + 0];
92 pixels[i * 4 + 1] = pixels[i * 4 + 0];
93 }
94 }
95}
96
97template<typename T>
99 ImageInput *in, int width, int height, int channels, int flags, bool use_all_planes)
100{
101 /* Allocate the ImBuf for the image. */
102 constexpr bool is_float = sizeof(T) > 1;
103 const uint format_flag = (is_float ? IB_rectfloat : IB_rect) | IB_uninitialized_pixels;
104 const uint ibuf_flags = (flags & IB_test) ? 0 : format_flag;
105 const int planes = use_all_planes ? 32 : 8 * channels;
106 ImBuf *ibuf = IMB_allocImBuf(width, height, planes, ibuf_flags);
107 if (!ibuf) {
108 return nullptr;
109 }
110
111 /* No need to load actual pixel data during the test phase. */
112 if (flags & IB_test) {
113 return ibuf;
114 }
115
116 /* Calculate an appropriate stride to read n-channels directly into
117 * the ImBuf 4-channel layout. */
118 const stride_t ibuf_xstride = sizeof(T) * 4;
119 const stride_t ibuf_ystride = ibuf_xstride * width;
120 const TypeDesc format = is_float ? TypeDesc::FLOAT : TypeDesc::UINT8;
121 uchar *rect = is_float ? reinterpret_cast<uchar *>(ibuf->float_buffer.data) :
122 reinterpret_cast<uchar *>(ibuf->byte_buffer.data);
123 void *ibuf_data = rect + ((stride_t(height) - 1) * ibuf_ystride);
124
125 bool ok = in->read_image(
126 0, 0, 0, channels, format, ibuf_data, ibuf_xstride, -ibuf_ystride, AutoStride);
127 if (!ok) {
128 fprintf(stderr, "ImageInput::read_image() failed: %s\n", in->geterror().c_str());
129
130 IMB_freeImBuf(ibuf);
131 return nullptr;
132 }
133
134 /* ImBuf always needs 4 channels */
135 const T alpha_fill = is_float ? 1.0f : 0xFF;
136 fill_all_channels<T>(reinterpret_cast<T *>(rect), width, height, channels, alpha_fill);
137
138 return ibuf;
139}
140
141static void set_colorspace_name(char colorspace[IM_MAX_SPACE],
142 const ReadContext &ctx,
143 const ImageSpec &spec,
144 bool is_float)
145{
146 const bool is_colorspace_set = (colorspace[0] != '\0');
147 if (is_colorspace_set) {
148 return;
149 }
150
151 /* Use a default role unless otherwise specified. */
152 if (ctx.use_colorspace_role >= 0) {
154 }
155 else if (is_float) {
157 }
158 else {
160 }
161
162 /* Override if necessary. */
163 if (ctx.use_embedded_colorspace) {
164 string ics = spec.get_string_attribute("oiio:ColorSpace");
165 char file_colorspace[IM_MAX_SPACE];
166 STRNCPY(file_colorspace, ics.c_str());
167
168 /* Only use color-spaces that exist. */
169 if (colormanage_colorspace_get_named(file_colorspace)) {
170 BLI_strncpy(colorspace, file_colorspace, IM_MAX_SPACE);
171 }
172 }
173}
174
178static ImBuf *get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, char colorspace[IM_MAX_SPACE])
179{
180 const ImageSpec &spec = in->spec();
181 const int width = spec.width;
182 const int height = spec.height;
183 const bool has_alpha = spec.alpha_channel != -1;
184 const bool is_float = spec.format.basesize() > 1;
185
186 /* Only a maximum of 4 channels are supported by ImBuf. */
187 const int channels = spec.nchannels <= 4 ? spec.nchannels : 4;
188 if (channels < 1) {
189 return nullptr;
190 }
191
192 const bool use_all_planes = has_alpha || ctx.use_all_planes;
193
194 ImBuf *ibuf = nullptr;
195 if (is_float) {
196 ibuf = load_pixels<float>(in, width, height, channels, ctx.flags, use_all_planes);
197 }
198 else {
199 ibuf = load_pixels<uchar>(in, width, height, channels, ctx.flags, use_all_planes);
200 }
201
202 /* Fill in common ibuf properties. */
203 if (ibuf) {
204 ibuf->ftype = ctx.file_type;
205 ibuf->flags |= (spec.format == TypeDesc::HALF) ? IB_halffloat : 0;
206
207 set_colorspace_name(colorspace, ctx, spec, is_float);
208
209 float x_res = spec.get_float_attribute("XResolution", 0.0f);
210 float y_res = spec.get_float_attribute("YResolution", 0.0f);
211 if (x_res > 0.0f && y_res > 0.0f) {
212 double scale = 1.0;
213 auto unit = spec.get_string_attribute("ResolutionUnit", "");
214 if (ELEM(unit, "in", "inch")) {
215 scale = 100.0 / 2.54;
216 }
217 else if (unit == "cm") {
218 scale = 100.0;
219 }
220 ibuf->ppm[0] = scale * x_res;
221 ibuf->ppm[1] = scale * y_res;
222 }
223
224 /* Transfer metadata to the ibuf if necessary. */
225 if (ctx.flags & IB_metadata) {
227 ibuf->flags |= spec.extra_attribs.empty() ? 0 : IB_metadata;
228
229 for (const auto &attrib : spec.extra_attribs) {
230 if (attrib.name().find("ICCProfile") != string::npos) {
231 continue;
232 }
233 IMB_metadata_set_field(ibuf->metadata, attrib.name().c_str(), attrib.get_string().c_str());
234 }
235 }
236 }
237
238 return ibuf;
239}
240
246static unique_ptr<ImageInput> get_oiio_reader(const char *format,
247 const ImageSpec &config,
248 Filesystem::IOMemReader &mem_reader,
249 ImageSpec &r_newspec)
250{
251 /* Attempt to create a reader based on the passed in format. */
252 unique_ptr<ImageInput> in = ImageInput::create(format);
253 if (!(in && in->valid_file(&mem_reader))) {
254 return nullptr;
255 }
256
257 /* Open the reader using the ioproxy. */
258 in->set_ioproxy(&mem_reader);
259 bool ok = in->open("", r_newspec, config);
260 if (!ok) {
261 return nullptr;
262 }
263
264 return in;
265}
266
267bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format)
268{
269 ImageSpec config, spec;
270
271 /* This memory proxy must remain alive for the full duration of the read. */
272 Filesystem::IOMemReader mem_reader(cspan<uchar>(mem, mem_size));
273 unique_ptr<ImageInput> in = ImageInput::create(file_format);
274 return in && in->valid_file(&mem_reader);
275}
276
278 const ImageSpec &config,
279 char colorspace[IM_MAX_SPACE],
280 ImageSpec &r_newspec)
281{
282 /* This memory proxy must remain alive for the full duration of the read. */
283 Filesystem::IOMemReader mem_reader(cspan<uchar>(ctx.mem_start, ctx.mem_size));
284 unique_ptr<ImageInput> in = get_oiio_reader(ctx.file_format, config, mem_reader, r_newspec);
285 if (!in) {
286 return nullptr;
287 }
288
289 return get_oiio_ibuf(in.get(), ctx, colorspace);
290}
291
292bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec)
293{
294 unique_ptr<ImageOutput> out = ImageOutput::create(ctx.file_format);
295 if (!out) {
296 return false;
297 }
298
299 ImageBuf orig_buf(ctx.mem_spec, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride);
300 ImageBuf final_buf{};
301
302 /* Grayscale images need to be based on luminance weights rather than only
303 * using a single channel from the source. */
304 if (ctx.ibuf->channels > 1 && file_spec.nchannels == 1) {
305 float weights[4] = {};
307 ImageBufAlgo::channel_sum(final_buf, orig_buf, {weights, orig_buf.nchannels()});
308 }
309 else {
310 /* If we are moving from an 1-channel format to n-channel we need to
311 * ensure the original data is copied into the higher channels. */
312 if (ctx.ibuf->channels == 1 && file_spec.nchannels > 1) {
313 final_buf = ImageBuf(file_spec, InitializePixels::No);
314 ImageBufAlgo::paste(final_buf, 0, 0, 0, 0, orig_buf);
315 ImageBufAlgo::paste(final_buf, 0, 0, 0, 1, orig_buf);
316 ImageBufAlgo::paste(final_buf, 0, 0, 0, 2, orig_buf);
317 if (file_spec.alpha_channel == 3) {
318 ROI alpha_roi = file_spec.roi();
319 alpha_roi.chbegin = file_spec.alpha_channel;
320 ImageBufAlgo::fill(final_buf, {0, 0, 0, 1.0f}, alpha_roi);
321 }
322 }
323 else {
324 final_buf = std::move(orig_buf);
325 }
326 }
327
328 bool write_ok = false;
329 bool close_ok = false;
330 if (ctx.flags & IB_mem) {
331 /* This memory proxy must remain alive until the ImageOutput is finally closed. */
332 ImBufMemWriter writer(ctx.ibuf);
333
335 out->set_ioproxy(&writer);
336 if (out->open("", file_spec)) {
337 write_ok = final_buf.write(out.get());
338 close_ok = out->close();
339 }
340 }
341 else {
342 if (out->open(filepath, file_spec)) {
343 write_ok = final_buf.write(out.get());
344 close_ok = out->close();
345 }
346 }
347
348 return write_ok && close_ok;
349}
350
352 ImBuf *ibuf,
353 int flags,
354 bool prefer_float)
355{
356 WriteContext ctx{};
357 ctx.file_format = file_format;
358 ctx.ibuf = ibuf;
359 ctx.flags = flags;
360
361 const int width = ibuf->x;
362 const int height = ibuf->y;
363 const bool use_float = prefer_float && (ibuf->float_buffer.data != nullptr);
364 if (use_float) {
365 const int mem_channels = ibuf->channels ? ibuf->channels : 4;
366 ctx.mem_xstride = sizeof(float) * mem_channels;
367 ctx.mem_ystride = width * ctx.mem_xstride;
368 ctx.mem_start = reinterpret_cast<uchar *>(ibuf->float_buffer.data);
369 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::FLOAT);
370 }
371 else {
372 const int mem_channels = 4;
373 ctx.mem_xstride = sizeof(uchar) * mem_channels;
374 ctx.mem_ystride = width * ctx.mem_xstride;
375 ctx.mem_start = ibuf->byte_buffer.data;
376 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::UINT8);
377 }
378
379 /* We always write using a negative y-stride so ensure we start at the end. */
380 ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride);
381
382 return ctx;
383}
384
385ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
386{
387 const int width = ctx.ibuf->x;
388 const int height = ctx.ibuf->y;
389 ImageSpec file_spec(width, height, file_channels, data_format);
390
391 /* Populate the spec with all common attributes.
392 *
393 * Care must be taken with the metadata:
394 * - It should be processed first, before the "Resolution" metadata below, to
395 * ensure the proper values end up in the #ImageSpec
396 * - It needs to filter format-specific metadata that may no longer apply to
397 * the current format being written (e.g. metadata for tiff being written to a `PNG`)
398 */
399
400 if (ctx.ibuf->metadata) {
402 if (prop->type == IDP_STRING) {
403 /* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to
404 * oiio or a different format, then skip. */
405 if (char *colon = strchr(prop->name, ':')) {
406 std::string prefix(prop->name, colon);
407 Strutil::to_lower(prefix);
408 if (prefix == "oiio" ||
409 (!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix)))
410 {
411 /* Skip this attribute. */
412 continue;
413 }
414 }
415
416 file_spec.attribute(prop->name, IDP_String(prop));
417 }
418 }
419 }
420
421 if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) {
422 /* More OIIO formats support inch than meter. */
423 file_spec.attribute("ResolutionUnit", "in");
424 file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254));
425 file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254));
426 }
427
428 return file_spec;
429}
430
431} // namespace blender::imbuf
#define IDP_String(prop)
#define LISTBASE_FOREACH(type, var, list)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
unsigned char uchar
unsigned int uint
#define ELEM(...)
#define STREQ(a, b)
ID and Library types, which are fundamental for SDNA.
@ IDP_STRING
@ COLOR_ROLE_DEFAULT_FLOAT
@ COLOR_ROLE_DEFAULT_BYTE
BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3])
#define IM_MAX_SPACE
Definition IMB_imbuf.hh:49
@ IB_halffloat
@ IB_rectfloat
@ IB_uninitialized_pixels
@ IB_metadata
@ IB_mem
@ IB_test
@ IB_rect
void IMB_metadata_set_field(IDProperty *metadata, const char *key, const char *value)
Definition metadata.cc:69
void IMB_metadata_ensure(IDProperty **metadata)
Definition metadata.cc:24
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue channels(Deprecated)") DefNode(ShaderNode
bool imb_enlargeencodedbufferImBuf(ImBuf *ibuf)
bool imb_addencodedbufferImBuf(ImBuf *ibuf)
size_t write(const void *buf, size_t size) override
size_t pwrite(const void *buf, size_t size, int64_t offset) override
const char * proxytype() const override
void colorspace_set_default_role(char *colorspace, int size, int role)
ColorSpace * colormanage_colorspace_get_named(const char *name)
draw_view in_light_buf[] float
struct ImBuf * IMB_allocImBuf(unsigned int, unsigned int, unsigned char, unsigned int)
void IMB_freeImBuf(ImBuf *)
format
#define T
bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec)
WriteContext imb_create_write_context(const char *file_format, ImBuf *ibuf, int flags, bool prefer_float)
static unique_ptr< ImageInput > get_oiio_reader(const char *format, const ImageSpec &config, Filesystem::IOMemReader &mem_reader, ImageSpec &r_newspec)
ImBuf * imb_oiio_read(const ReadContext &ctx, const ImageSpec &config, char colorspace[IM_MAX_SPACE], ImageSpec &r_newspec)
static void set_colorspace_name(char colorspace[IM_MAX_SPACE], const ReadContext &ctx, const ImageSpec &spec, bool is_float)
bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format)
ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
static ImBuf * get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, char colorspace[IM_MAX_SPACE])
static ImBuf * load_pixels(ImageInput *in, int width, int height, int channels, int flags, bool use_all_planes)
static void fill_all_channels(T *pixels, int width, int height, int components, T alpha)
long long TypeDesc
__int64 int64_t
Definition stdint.h:89
ListBase group
Definition DNA_ID.h:146
char name[64]
Definition DNA_ID.h:163
IDPropertyData data
Definition DNA_ID.h:168
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
unsigned int encoded_buffer_size
IDProperty * metadata
ImBufByteBuffer encoded_buffer
double ppm[2]
unsigned int encoded_size