Blender V4.5
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 <algorithm>
10
11#include "BLI_listbase.h"
12#include "BLI_string.h"
13
14#include "BKE_idprop.hh"
15
16#include "DNA_ID.h"
17#include "IMB_allocimbuf.hh"
19#include "IMB_filetype.hh"
20#include "IMB_metadata.hh"
21
22OIIO_NAMESPACE_USING
23
24using std::string;
25using std::unique_ptr;
26
27namespace blender::imbuf {
28
29/* An OIIO IOProxy used during file packing to write into an in-memory #ImBuf buffer. */
30class ImBufMemWriter : public Filesystem::IOProxy {
31 public:
32 ImBufMemWriter(ImBuf *ibuf) : IOProxy("", Write), ibuf_(ibuf) {}
33
34 const char *proxytype() const override
35 {
36 return "ImBufMemWriter";
37 }
38
39 size_t write(const void *buf, size_t size) override
40 {
41 size = pwrite(buf, size, m_pos);
42 m_pos += size;
43 return size;
44 }
45
46 size_t pwrite(const void *buf, size_t size, int64_t offset) override
47 {
48 /* If buffer is too small increase it. */
49 size_t end = offset + size;
50 while (end > ibuf_->encoded_buffer_size) {
52 /* Out of memory. */
53 return 0;
54 }
55 }
56
57 memcpy(ibuf_->encoded_buffer.data + offset, buf, size);
58
59 ibuf_->encoded_size = std::max<size_t>(end, ibuf_->encoded_size);
60
61 return size;
62 }
63
64 size_t size() const override
65 {
66 return ibuf_->encoded_size;
67 }
68
69 private:
70 ImBuf *ibuf_;
71};
72
73/* Utility to in-place expand an n-component pixel buffer into a 4-component buffer. */
74template<typename T>
75static void fill_all_channels(T *pixels, int width, int height, int components, T alpha)
76{
77 const int64_t pixel_count = int64_t(width) * height;
78 if (components == 3) {
79 for (int64_t i = 0; i < pixel_count; i++) {
80 pixels[i * 4 + 3] = alpha;
81 }
82 }
83 else if (components == 1) {
84 for (int64_t i = 0; i < pixel_count; i++) {
85 pixels[i * 4 + 3] = alpha;
86 pixels[i * 4 + 2] = pixels[i * 4 + 0];
87 pixels[i * 4 + 1] = pixels[i * 4 + 0];
88 }
89 }
90 else if (components == 2) {
91 for (int64_t i = 0; i < pixel_count; i++) {
92 pixels[i * 4 + 3] = pixels[i * 4 + 1];
93 pixels[i * 4 + 2] = pixels[i * 4 + 0];
94 pixels[i * 4 + 1] = pixels[i * 4 + 0];
95 }
96 }
97}
98
99template<typename T>
101 ImageInput *in, int width, int height, int channels, int flags, bool use_all_planes)
102{
103 /* Allocate the ImBuf for the image. */
104 constexpr bool is_float = sizeof(T) > 1;
105 const uint format_flag = (is_float ? IB_float_data : IB_byte_data) | IB_uninitialized_pixels;
106 const uint ibuf_flags = (flags & IB_test) ? 0 : format_flag;
107 const int planes = use_all_planes ? 32 : 8 * channels;
108 ImBuf *ibuf = IMB_allocImBuf(width, height, planes, ibuf_flags);
109 if (!ibuf) {
110 return nullptr;
111 }
112
113 /* No need to load actual pixel data during the test phase. */
114 if (flags & IB_test) {
115 return ibuf;
116 }
117
118 /* Calculate an appropriate stride to read n-channels directly into
119 * the ImBuf 4-channel layout. */
120 const stride_t ibuf_xstride = sizeof(T) * 4;
121 const stride_t ibuf_ystride = ibuf_xstride * width;
122 const TypeDesc format = is_float ? TypeDesc::FLOAT : TypeDesc::UINT8;
123 uchar *rect = is_float ? reinterpret_cast<uchar *>(ibuf->float_buffer.data) :
124 reinterpret_cast<uchar *>(ibuf->byte_buffer.data);
125 void *ibuf_data = rect + ((stride_t(height) - 1) * ibuf_ystride);
126
127 bool ok = in->read_image(
128 0, 0, 0, channels, format, ibuf_data, ibuf_xstride, -ibuf_ystride, AutoStride);
129 if (!ok) {
130 fprintf(stderr, "ImageInput::read_image() failed: %s\n", in->geterror().c_str());
131
132 IMB_freeImBuf(ibuf);
133 return nullptr;
134 }
135
136 /* ImBuf always needs 4 channels */
137 const T alpha_fill = is_float ? 1.0f : 0xFF;
138 fill_all_channels<T>(reinterpret_cast<T *>(rect), width, height, channels, alpha_fill);
139
140 return ibuf;
141}
142
143static void set_file_colorspace(ImFileColorSpace &r_colorspace,
144 const ReadContext &ctx,
145 const ImageSpec &spec,
146 bool is_float)
147{
148 /* Guess float data types means HDR colors. File formats can override this later. */
149 r_colorspace.is_hdr_float = is_float;
150
151 /* Override if necessary. */
152 if (ctx.use_metadata_colorspace) {
153 string ics = spec.get_string_attribute("oiio:ColorSpace");
154 STRNCPY(r_colorspace.metadata_colorspace, ics.c_str());
155 }
156}
157
161static ImBuf *get_oiio_ibuf(ImageInput *in, const ReadContext &ctx, ImFileColorSpace &r_colorspace)
162{
163 const ImageSpec &spec = in->spec();
164 const int width = spec.width;
165 const int height = spec.height;
166 const bool has_alpha = spec.alpha_channel != -1;
167 const bool is_float = spec.format.basesize() > 1;
168
169 /* Only a maximum of 4 channels are supported by ImBuf. */
170 const int channels = spec.nchannels <= 4 ? spec.nchannels : 4;
171 if (channels < 1) {
172 return nullptr;
173 }
174
175 const bool use_all_planes = has_alpha || ctx.use_all_planes;
176
177 ImBuf *ibuf = nullptr;
178 if (is_float) {
179 ibuf = load_pixels<float>(in, width, height, channels, ctx.flags, use_all_planes);
180 }
181 else {
182 ibuf = load_pixels<uchar>(in, width, height, channels, ctx.flags, use_all_planes);
183 }
184
185 /* Fill in common ibuf properties. */
186 if (ibuf) {
187 ibuf->ftype = ctx.file_type;
188 ibuf->foptions.flag |= (spec.format == TypeDesc::HALF) ? OPENEXR_HALF : 0;
189
190 set_file_colorspace(r_colorspace, ctx, spec, is_float);
191
192 double x_res = spec.get_float_attribute("XResolution", 0.0f);
193 double y_res = spec.get_float_attribute("YResolution", 0.0f);
194 /* Some formats store the resolution as integers. */
195 if (!(x_res > 0.0f && y_res > 0.0f)) {
196 x_res = spec.get_int_attribute("XResolution", 0);
197 y_res = spec.get_int_attribute("YResolution", 0);
198 }
199
200 if (x_res > 0.0f && y_res > 0.0f) {
201 double scale = 1.0;
202 auto unit = spec.get_string_attribute("ResolutionUnit", "");
203 if (ELEM(unit, "in", "inch")) {
204 scale = 100.0 / 2.54;
205 }
206 else if (unit == "cm") {
207 scale = 100.0;
208 }
209 ibuf->ppm[0] = scale * x_res;
210 ibuf->ppm[1] = scale * y_res;
211 }
212
213 /* Transfer metadata to the ibuf if necessary. */
214 if (ctx.flags & IB_metadata) {
216 ibuf->flags |= spec.extra_attribs.empty() ? 0 : IB_metadata;
217
218 for (const auto &attrib : spec.extra_attribs) {
219 if (attrib.name().find("ICCProfile") != string::npos) {
220 continue;
221 }
222 IMB_metadata_set_field(ibuf->metadata, attrib.name().c_str(), attrib.get_string().c_str());
223 }
224 }
225 }
226
227 return ibuf;
228}
229
236 const ImageSpec &config,
237 Filesystem::IOMemReader &mem_reader,
238 ImageSpec &r_newspec)
239{
240 /* Attempt to create a reader based on the passed in format. */
241 unique_ptr<ImageInput> in = ImageInput::create(format);
242 if (!(in && in->valid_file(&mem_reader))) {
243 return nullptr;
244 }
245
246 /* Open the reader using the ioproxy. */
247 in->set_ioproxy(&mem_reader);
248 bool ok = in->open("", r_newspec, config);
249 if (!ok) {
250 return nullptr;
251 }
252
253 return in;
254}
255
256bool imb_oiio_check(const uchar *mem, size_t mem_size, const char *file_format)
257{
258 ImageSpec config, spec;
259
260 /* This memory proxy must remain alive for the full duration of the read. */
261 Filesystem::IOMemReader mem_reader(cspan<uchar>(mem, mem_size));
262 unique_ptr<ImageInput> in = ImageInput::create(file_format);
263 return in && in->valid_file(&mem_reader);
264}
265
267 const ImageSpec &config,
268 ImFileColorSpace &r_colorspace,
269 ImageSpec &r_newspec)
270{
271 /* This memory proxy must remain alive for the full duration of the read. */
272 Filesystem::IOMemReader mem_reader(cspan<uchar>(ctx.mem_start, ctx.mem_size));
273 unique_ptr<ImageInput> in = get_oiio_reader(ctx.file_format, config, mem_reader, r_newspec);
274 if (!in) {
275 return nullptr;
276 }
277
278 return get_oiio_ibuf(in.get(), ctx, r_colorspace);
279}
280
281bool imb_oiio_write(const WriteContext &ctx, const char *filepath, const ImageSpec &file_spec)
282{
283 unique_ptr<ImageOutput> out = ImageOutput::create(ctx.file_format);
284 if (!out) {
285 return false;
286 }
287
288 ImageBuf orig_buf(ctx.mem_spec, ctx.mem_start, ctx.mem_xstride, -ctx.mem_ystride, AutoStride);
289 ImageBuf final_buf{};
290
291#if OIIO_VERSION_MAJOR >= 3
292 const size_t original_channels_count = orig_buf.nchannels();
293#else
294 const int original_channels_count = orig_buf.nchannels();
295#endif
296
297 if (original_channels_count > 1 && file_spec.nchannels == 1) {
298 /* Convert to gray-scale image by computing the luminance. Make sure the weight of alpha
299 * channel is zero since it should not contribute to the luminance. */
300 float weights[4] = {0.0f, 0.0f, 0.0f, 0.0f};
302 ImageBufAlgo::channel_sum(final_buf, orig_buf, {weights, original_channels_count});
303 }
304 else if (original_channels_count == 1 && file_spec.nchannels > 1) {
305 /* Broadcast the gray-scale channel to as many channels as needed, filling the alpha channel
306 * with ones if needed. 0 channel order mean we will be copying from the first channel, while
307 * -1 means we will be filling based on the corresponding value from the defined channel
308 * values. */
309 const int channel_order[] = {0, 0, 0, -1};
310 const float channel_values[] = {0.0f, 0.0f, 0.0f, 1.0f};
311 const std::string channel_names[] = {"R", "G", "B", "A"};
312 ImageBufAlgo::channels(final_buf,
313 orig_buf,
314 file_spec.nchannels,
315 cspan<int>(channel_order, file_spec.nchannels),
316 cspan<float>(channel_values, file_spec.nchannels),
317 cspan<std::string>(channel_names, file_spec.nchannels));
318 }
319 else if (original_channels_count != file_spec.nchannels) {
320 /* Either trim or fill new channels based on the needed channels count. */
321 int channel_order[4];
322 for (int i = 0; i < 4; i++) {
323 /* If a channel exists in the original buffer, we copy it, if not, we fill it by supplying
324 * -1, which is a special value that means filling based on the value in the defined channels
325 * values. So alpha is filled with 1, and other channels are filled with zero. */
326 const bool channel_exists = i + 1 <= original_channels_count;
327 channel_order[i] = channel_exists ? i : -1;
328 }
329 const float channel_values[] = {0.0f, 0.0f, 0.0f, 1.0f};
330 const std::string channel_names[] = {"R", "G", "B", "A"};
331 ImageBufAlgo::channels(final_buf,
332 orig_buf,
333 file_spec.nchannels,
334 cspan<int>(channel_order, file_spec.nchannels),
335 cspan<float>(channel_values, file_spec.nchannels),
336 cspan<std::string>(channel_names, file_spec.nchannels));
337 }
338 else {
339 final_buf = std::move(orig_buf);
340 }
341
342 bool write_ok = false;
343 bool close_ok = false;
344 if (ctx.flags & IB_mem) {
345 /* This memory proxy must remain alive until the ImageOutput is finally closed. */
346 ImBufMemWriter writer(ctx.ibuf);
347
349 out->set_ioproxy(&writer);
350 if (out->open("", file_spec)) {
351 write_ok = final_buf.write(out.get());
352 close_ok = out->close();
353 }
354 }
355 else {
356 if (out->open(filepath, file_spec)) {
357 write_ok = final_buf.write(out.get());
358 close_ok = out->close();
359 }
360 }
361
362 return write_ok && close_ok;
363}
364
366 ImBuf *ibuf,
367 int flags,
368 bool prefer_float)
369{
370 WriteContext ctx{};
371 ctx.file_format = file_format;
372 ctx.ibuf = ibuf;
373 ctx.flags = flags;
374
375 const int width = ibuf->x;
376 const int height = ibuf->y;
377 const bool use_float = prefer_float && (ibuf->float_buffer.data != nullptr);
378 if (use_float) {
379 const int mem_channels = ibuf->channels ? ibuf->channels : 4;
380 ctx.mem_xstride = sizeof(float) * mem_channels;
381 ctx.mem_ystride = width * ctx.mem_xstride;
382 ctx.mem_start = reinterpret_cast<uchar *>(ibuf->float_buffer.data);
383 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::FLOAT);
384 }
385 else {
386 const int mem_channels = 4;
387 ctx.mem_xstride = sizeof(uchar) * mem_channels;
388 ctx.mem_ystride = width * ctx.mem_xstride;
389 ctx.mem_start = ibuf->byte_buffer.data;
390 ctx.mem_spec = ImageSpec(width, height, mem_channels, TypeDesc::UINT8);
391 }
392
393 /* We always write using a negative y-stride so ensure we start at the end. */
394 ctx.mem_start = ctx.mem_start + ((stride_t(height) - 1) * ctx.mem_ystride);
395
396 return ctx;
397}
398
399ImageSpec imb_create_write_spec(const WriteContext &ctx, int file_channels, TypeDesc data_format)
400{
401 const int width = ctx.ibuf->x;
402 const int height = ctx.ibuf->y;
403 ImageSpec file_spec(width, height, file_channels, data_format);
404
405 /* Populate the spec with all common attributes.
406 *
407 * Care must be taken with the metadata:
408 * - It should be processed first, before the "Resolution" metadata below, to
409 * ensure the proper values end up in the #ImageSpec
410 * - It needs to filter format-specific metadata that may no longer apply to
411 * the current format being written (e.g. metadata for tiff being written to a `PNG`)
412 */
413
414 if (ctx.ibuf->metadata) {
416 if (prop->type == IDP_STRING) {
417 /* If this property has a prefixed name (oiio:, tiff:, etc.) and it belongs to
418 * oiio or a different format, then skip. */
419 if (char *colon = strchr(prop->name, ':')) {
420 std::string prefix(prop->name, colon);
421 Strutil::to_lower(prefix);
422 if (prefix == "oiio" ||
423 (!STREQ(prefix.c_str(), ctx.file_format) && OIIO::is_imageio_format_name(prefix)))
424 {
425 /* Skip this attribute. */
426 continue;
427 }
428 }
429
430 file_spec.attribute(prop->name, IDP_String(prop));
431 }
432 }
433 }
434
435 if (ctx.ibuf->ppm[0] > 0.0 && ctx.ibuf->ppm[1] > 0.0) {
436 if (STREQ(ctx.file_format, "bmp")) {
437 /* BMP only supports meters as integers. */
438 file_spec.attribute("ResolutionUnit", "m");
439 file_spec.attribute("XResolution", int(round(ctx.ibuf->ppm[0])));
440 file_spec.attribute("YResolution", int(round(ctx.ibuf->ppm[1])));
441 }
442 else {
443 /* More OIIO formats support inch than meter. */
444 file_spec.attribute("ResolutionUnit", "in");
445 file_spec.attribute("XResolution", float(ctx.ibuf->ppm[0] * 0.0254));
446 file_spec.attribute("YResolution", float(ctx.ibuf->ppm[1] * 0.0254));
447 }
448 }
449
450 return file_spec;
451}
452
453} // namespace blender::imbuf
#define IDP_String(prop)
#define LISTBASE_FOREACH(type, var, list)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned char uchar
unsigned int uint
#define ELEM(...)
#define STREQ(a, b)
ID and Library types, which are fundamental for SDNA.
@ IDP_STRING
BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3])
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
@ IB_float_data
@ IB_byte_data
@ IB_uninitialized_pixels
@ IB_metadata
@ IB_mem
@ IB_test
void IMB_metadata_set_field(IDProperty *metadata, const char *key, const char *value)
Definition metadata.cc:68
void IMB_metadata_ensure(IDProperty **metadata)
Definition metadata.cc:23
bool imb_enlargeencodedbufferImBuf(ImBuf *ibuf)
bool imb_addencodedbufferImBuf(ImBuf *ibuf)
long long int int64_t
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
#define round
#define in
#define out
#define OPENEXR_HALF
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, ImFileColorSpace &r_colorspace, ImageSpec &r_newspec)
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, ImFileColorSpace &r_colorspace)
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)
static void set_file_colorspace(ImFileColorSpace &r_colorspace, const ReadContext &ctx, const ImageSpec &spec, bool is_float)
long long TypeDesc
ListBase group
Definition DNA_ID.h:138
char name[64]
Definition DNA_ID.h:154
IDPropertyData data
Definition DNA_ID.h:159
ImBufFloatBuffer float_buffer
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
IDProperty * metadata
double ppm[2]
char metadata_colorspace[IM_MAX_SPACE]
i
Definition text_draw.cc:230