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
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] = {};
306#if OIIO_VERSION_MAJOR >= 3
307 const size_t nchannels = orig_buf.nchannels();
308#else
309 const int nchannels = orig_buf.nchannels();
310#endif
312 ImageBufAlgo::channel_sum(final_buf, orig_buf, {weights, nchannels});
313 }
314 else {
315 /* If we are moving from an 1-channel format to n-channel we need to
316 * ensure the original data is copied into the higher channels. */
317 if (ctx.ibuf->channels == 1 && file_spec.nchannels > 1) {
318 final_buf = ImageBuf(file_spec, InitializePixels::No);
319 ImageBufAlgo::paste(final_buf, 0, 0, 0, 0, orig_buf);
320 ImageBufAlgo::paste(final_buf, 0, 0, 0, 1, orig_buf);
321 ImageBufAlgo::paste(final_buf, 0, 0, 0, 2, orig_buf);
322 if (file_spec.alpha_channel == 3) {
323 ROI alpha_roi = file_spec.roi();
324 alpha_roi.chbegin = file_spec.alpha_channel;
325 ImageBufAlgo::fill(final_buf, {0, 0, 0, 1.0f}, alpha_roi);
326 }
327 }
328 else {
329 final_buf = std::move(orig_buf);
330 }
331 }
332
333 bool write_ok = false;
334 bool close_ok = false;
335 if (ctx.flags & IB_mem) {
336 /* This memory proxy must remain alive until the ImageOutput is finally closed. */
337 ImBufMemWriter writer(ctx.ibuf);
338
340 out->set_ioproxy(&writer);
341 if (out->open("", file_spec)) {
342 write_ok = final_buf.write(out.get());
343 close_ok = out->close();
344 }
345 }
346 else {
347 if (out->open(filepath, file_spec)) {
348 write_ok = final_buf.write(out.get());
349 close_ok = out->close();
350 }
351 }
352
353 return write_ok && close_ok;
354}
355
357 ImBuf *ibuf,
358 int flags,
359 bool prefer_float)
360{
361 WriteContext ctx{};
362 ctx.file_format = file_format;
363 ctx.ibuf = ibuf;
364 ctx.flags = flags;
365
366 const int width = ibuf->x;
367 const int height = ibuf->y;
368 const bool use_float = prefer_float && (ibuf->float_buffer.data != nullptr);
369 if (use_float) {
370 const int mem_channels = ibuf->channels ? ibuf->channels : 4;
371 ctx.mem_xstride = sizeof(float) * mem_channels;
372 ctx.mem_ystride = width * ctx.mem_xstride;
373 ctx.mem_start = reinterpret_cast<uchar *>(ibuf->float_buffer.data);
374 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::FLOAT);
375 }
376 else {
377 const int mem_channels = 4;
378 ctx.mem_xstride = sizeof(uchar) * mem_channels;
379 ctx.mem_ystride = width * ctx.mem_xstride;
380 ctx.mem_start = ibuf->byte_buffer.data;
381 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::UINT8);
382 }
383
384 /* We always write using a negative y-stride so ensure we start at the end. */
385 ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride);
386
387 return ctx;
388}
389
390ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
391{
392 const int width = ctx.ibuf->x;
393 const int height = ctx.ibuf->y;
394 ImageSpec file_spec(width, height, file_channels, data_format);
395
396 /* Populate the spec with all common attributes.
397 *
398 * Care must be taken with the metadata:
399 * - It should be processed first, before the "Resolution" metadata below, to
400 * ensure the proper values end up in the #ImageSpec
401 * - It needs to filter format-specific metadata that may no longer apply to
402 * the current format being written (e.g. metadata for tiff being written to a `PNG`)
403 */
404
405 if (ctx.ibuf->metadata) {
407 if (prop->type == IDP_STRING) {
408 /* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to
409 * oiio or a different format, then skip. */
410 if (char *colon = strchr(prop->name, ':')) {
411 std::string prefix(prop->name, colon);
412 Strutil::to_lower(prefix);
413 if (prefix == "oiio" ||
414 (!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix)))
415 {
416 /* Skip this attribute. */
417 continue;
418 }
419 }
420
421 file_spec.attribute(prop->name, IDP_String(prop));
422 }
423 }
424 }
425
426 if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) {
427 /* More OIIO formats support inch than meter. */
428 file_spec.attribute("ResolutionUnit", "in");
429 file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254));
430 file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254));
431 }
432
433 return file_spec;
434}
435
436} // 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
IDProperty * metadata
double ppm[2]