Blender V4.5
openexr_api.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2005 `Gernot Ziegler <gz@lysator.liu.se>`. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "IMB_filetype.hh"
10#include <algorithm>
11#include <cctype>
12#include <cerrno>
13#include <cstddef>
14#include <cstdio>
15#include <cstdlib>
16#include <fcntl.h>
17#include <fstream>
18#include <iostream>
19#include <set>
20#include <string>
21
22/* The OpenEXR version can reliably be found in this header file from OpenEXR,
23 * for both 2.x and 3.x:
24 */
25#include <OpenEXR/OpenEXRConfig.h>
26#define COMBINED_OPENEXR_VERSION \
27 ((10000 * OPENEXR_VERSION_MAJOR) + (100 * OPENEXR_VERSION_MINOR) + OPENEXR_VERSION_PATCH)
28
29#if COMBINED_OPENEXR_VERSION >= 20599
30/* >=2.5.99 -> OpenEXR >=3.0 */
31# include <Imath/half.h>
32# include <OpenEXR/ImfFrameBuffer.h>
33# define exr_file_offset_t uint64_t
34#else
35/* OpenEXR 2.x, use the old locations. */
36# include <OpenEXR/half.h>
37# define exr_file_offset_t Int64
38#endif
39
40#include <OpenEXR/Iex.h>
41#include <OpenEXR/ImfArray.h>
42#include <OpenEXR/ImfAttribute.h>
43#include <OpenEXR/ImfChannelList.h>
44#include <OpenEXR/ImfChromaticities.h>
45#include <OpenEXR/ImfCompression.h>
46#include <OpenEXR/ImfCompressionAttribute.h>
47#include <OpenEXR/ImfIO.h>
48#include <OpenEXR/ImfInputFile.h>
49#include <OpenEXR/ImfIntAttribute.h>
50#include <OpenEXR/ImfOutputFile.h>
51#include <OpenEXR/ImfPixelType.h>
52#include <OpenEXR/ImfPreviewImage.h>
53#include <OpenEXR/ImfRgbaFile.h>
54#include <OpenEXR/ImfStandardAttributes.h>
55#include <OpenEXR/ImfStringAttribute.h>
56#include <OpenEXR/ImfVersion.h>
57
58/* multiview/multipart */
59#include <OpenEXR/ImfInputPart.h>
60#include <OpenEXR/ImfMultiPartInputFile.h>
61#include <OpenEXR/ImfMultiPartOutputFile.h>
62#include <OpenEXR/ImfMultiView.h>
63#include <OpenEXR/ImfOutputPart.h>
64#include <OpenEXR/ImfPartHelper.h>
65#include <OpenEXR/ImfPartType.h>
66#include <OpenEXR/ImfTiledOutputPart.h>
67
68#include "DNA_scene_types.h" /* For OpenEXR compression constants */
69
70#include <openexr_api.h>
71
72#if defined(WIN32)
73# include "utfconv.hh"
74# include <io.h>
75#else
76# include <unistd.h>
77#endif
78
79#include "MEM_guardedalloc.h"
80
81#include "BLI_fileops.h"
82#include "BLI_listbase.h"
83#include "BLI_math_base.hh"
84#include "BLI_math_color.h"
85#include "BLI_mmap.h"
86#include "BLI_path_utils.hh"
87#include "BLI_string.h"
88#include "BLI_string_ref.hh"
89#include "BLI_threads.h"
90
91#include "BKE_idprop.hh"
92#include "BKE_image.hh"
93
94#include "IMB_allocimbuf.hh"
96#include "IMB_imbuf.hh"
97#include "IMB_imbuf_types.hh"
98#include "IMB_metadata.hh"
99#include "IMB_openexr.hh"
100
101using namespace Imf;
102using namespace Imath;
103
104/* prototype */
105static struct ExrPass *imb_exr_get_pass(ListBase *lb, const char *passname);
106static bool exr_has_multiview(MultiPartInputFile &file);
107static bool exr_has_multipart_file(MultiPartInputFile &file);
108static bool exr_has_alpha(MultiPartInputFile &file);
109static void exr_printf(const char *__restrict fmt, ...);
110static void imb_exr_type_by_channels(ChannelList &channels,
111 StringVector &views,
112 bool *r_singlelayer,
113 bool *r_multilayer,
114 bool *r_multiview);
115
116/* XYZ with Illuminant E */
117static Imf::Chromaticities CHROMATICITIES_XYZ_E{
118 {1.0f, 0.0f}, {0.0f, 1.0f}, {0.0f, 0.0f}, {1.0f / 3.0f, 1.0f / 3.0f}};
119/* Values matching ChromaticitiesForACES in https://github.com/ampas/aces_container */
120static Imf::Chromaticities CHROMATICITIES_ACES_2065_1{
121 {0.7347f, 0.2653f}, {0.0f, 1.0f}, {0.0001f, -0.077f}, {0.32168f, 0.33767f}};
122
123/* Memory Input Stream */
124
125class IMemStream : public Imf::IStream {
126 public:
127 IMemStream(uchar *exrbuf, size_t exrsize) : IStream("<memory>"), _exrpos(0), _exrsize(exrsize)
128 {
129 _exrbuf = exrbuf;
130 }
131
132 bool read(char c[], int n) override
133 {
134 if (n + _exrpos <= _exrsize) {
135 memcpy(c, (void *)(&_exrbuf[_exrpos]), n);
136 _exrpos += n;
137 return true;
138 }
139
140 /* OpenEXR requests chunks of 4096 bytes even if the file is smaller than that. Return
141 * zeros when reading up to 2x that amount past the end of the file.
142 * This was fixed after the OpenEXR 3.3.2 release, but not in an official release yet. */
143 if (n + _exrpos < _exrsize + 8192) {
144 const size_t remainder = _exrsize - _exrpos;
145 if (remainder > 0) {
146 memcpy(c, (void *)(&_exrbuf[_exrpos]), remainder);
147 memset(c + remainder, 0, n - remainder);
148 _exrpos += n;
149 return true;
150 }
151 }
152
153 return false;
154 }
155
157 {
158 return _exrpos;
159 }
160
162 {
163 _exrpos = pos;
164 }
165
166 void clear() override {}
167
168 private:
169 exr_file_offset_t _exrpos;
170 exr_file_offset_t _exrsize;
171 uchar *_exrbuf;
172};
173
174/* Memory-Mapped Input Stream */
175
176class IMMapStream : public Imf::IStream {
177 public:
178 IMMapStream(const char *filepath) : IStream(filepath)
179 {
180 const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
181 if (file < 0) {
182 throw IEX_NAMESPACE::InputExc("file not found");
183 }
184 _exrpos = 0;
186 _mmap_file = BLI_mmap_open(file);
188 close(file);
189 if (_mmap_file == nullptr) {
190 throw IEX_NAMESPACE::InputExc("BLI_mmap_open failed");
191 }
192 _exrbuf = (uchar *)BLI_mmap_get_pointer(_mmap_file);
193 _exrsize = BLI_mmap_get_length(_mmap_file);
194 }
195
196 ~IMMapStream() override
197 {
199 BLI_mmap_free(_mmap_file);
201 }
202
203 /* This is implementing regular `read`, not `readMemoryMapped`, because DWAA and DWAB
204 * decompressors load on unaligned offsets. Therefore we can't avoid the memory copy. */
205
206 bool read(char c[], int n) override
207 {
208 if (_exrpos + n > _exrsize) {
209 throw Iex::InputExc("Unexpected end of file.");
210 }
211 memcpy(c, _exrbuf + _exrpos, n);
212 _exrpos += n;
213
214 return _exrpos < _exrsize;
215 }
216
218 {
219 return _exrpos;
220 }
221
223 {
224 _exrpos = pos;
225 }
226
227 private:
228 BLI_mmap_file *_mmap_file;
229 exr_file_offset_t _exrpos;
230 exr_file_offset_t _exrsize;
231 uchar *_exrbuf;
232};
233
234/* File Input Stream */
235
236class IFileStream : public Imf::IStream {
237 public:
238 IFileStream(const char *filepath) : IStream(filepath)
239 {
240 /* UTF8 file path support on windows. */
241#if defined(WIN32)
242 wchar_t *wfilepath = alloc_utf16_from_8(filepath, 0);
243 ifs.open(wfilepath, std::ios_base::binary);
244 free(wfilepath);
245#else
246 ifs.open(filepath, std::ios_base::binary);
247#endif
248
249 if (!ifs) {
250 Iex::throwErrnoExc();
251 }
252 }
253
254 bool read(char c[], int n) override
255 {
256 if (!ifs) {
257 throw Iex::InputExc("Unexpected end of file.");
258 }
259
260 errno = 0;
261 ifs.read(c, n);
262 return check_error();
263 }
264
266 {
267 return std::streamoff(ifs.tellg());
268 }
269
271 {
272 ifs.seekg(pos);
273 check_error();
274 }
275
276 void clear() override
277 {
278 ifs.clear();
279 }
280
281 private:
282 bool check_error()
283 {
284 if (!ifs) {
285 if (errno) {
286 Iex::throwErrnoExc();
287 }
288
289 return false;
290 }
291
292 return true;
293 }
294
295 std::ifstream ifs;
296};
297
298/* Memory Output Stream */
299
300class OMemStream : public OStream {
301 public:
302 OMemStream(ImBuf *ibuf_) : OStream("<memory>"), ibuf(ibuf_), offset(0) {}
303
304 void write(const char c[], int n) override
305 {
306 ensure_size(offset + n);
307 memcpy(ibuf->encoded_buffer.data + offset, c, n);
308 offset += n;
309 ibuf->encoded_size += n;
310 }
311
313 {
314 return offset;
315 }
316
318 {
319 offset = pos;
320 ensure_size(offset);
321 }
322
323 private:
324 void ensure_size(exr_file_offset_t size)
325 {
326 /* if buffer is too small increase it. */
327 while (size > ibuf->encoded_buffer_size) {
329 throw Iex::ErrnoExc("Out of memory.");
330 }
331 }
332 }
333
334 ImBuf *ibuf;
335 exr_file_offset_t offset;
336};
337
338/* File Output Stream */
339
340class OFileStream : public OStream {
341 public:
342 OFileStream(const char *filepath) : OStream(filepath)
343 {
344 /* UTF8 file path support on windows. */
345#if defined(WIN32)
346 wchar_t *wfilepath = alloc_utf16_from_8(filepath, 0);
347 ofs.open(wfilepath, std::ios_base::binary);
348 free(wfilepath);
349#else
350 ofs.open(filepath, std::ios_base::binary);
351#endif
352
353 if (!ofs) {
354 Iex::throwErrnoExc();
355 }
356 }
357
358 void write(const char c[], int n) override
359 {
360 errno = 0;
361 ofs.write(c, n);
362 check_error();
363 }
364
366 {
367 return std::streamoff(ofs.tellp());
368 }
369
371 {
372 ofs.seekp(pos);
373 check_error();
374 }
375
376 private:
377 void check_error()
378 {
379 if (!ofs) {
380 if (errno) {
381 Iex::throwErrnoExc();
382 }
383
384 throw Iex::ErrnoExc("File output failed.");
385 }
386 }
387
388 std::ofstream ofs;
389};
390
398
399using RGBAZ = _RGBAZ;
400
401static half float_to_half_safe(const float value)
402{
403 return half(clamp_f(value, -HALF_MAX, HALF_MAX));
404}
405
406bool imb_is_a_openexr(const uchar *mem, const size_t size)
407{
408 /* No define is exposed for this size. */
409 if (size < 4) {
410 return false;
411 }
412 return Imf::isImfMagic((const char *)mem);
413}
414
416{
417 q = blender::math::clamp(q, 0, 100);
418
419 /* Map default JPG quality of 90 to default DWA level of 45,
420 * "lossless" JPG quality of 100 to DWA level of 0, and everything else
421 * linearly based on those. */
422 constexpr int x0 = 100, y0 = 0;
423 constexpr int x1 = 90, y1 = 45;
424 q = y0 + (q - x0) * (y1 - y0) / (x1 - x0);
425 return q;
426}
427
428static void openexr_header_compression(Header *header, int compression, int quality)
429{
430 switch (compression) {
432 header->compression() = NO_COMPRESSION;
433 break;
435 header->compression() = PXR24_COMPRESSION;
436 break;
438 header->compression() = ZIP_COMPRESSION;
439 break;
441 header->compression() = PIZ_COMPRESSION;
442 break;
444 header->compression() = RLE_COMPRESSION;
445 break;
447 header->compression() = ZIPS_COMPRESSION;
448 break;
450 header->compression() = B44_COMPRESSION;
451 break;
453 header->compression() = B44A_COMPRESSION;
454 break;
455#if OPENEXR_VERSION_MAJOR > 2 || (OPENEXR_VERSION_MAJOR >= 2 && OPENEXR_VERSION_MINOR >= 2)
457 header->compression() = DWAA_COMPRESSION;
458 header->dwaCompressionLevel() = openexr_jpg_like_quality_to_dwa_quality(quality);
459 break;
461 header->compression() = DWAB_COMPRESSION;
462 header->dwaCompressionLevel() = openexr_jpg_like_quality_to_dwa_quality(quality);
463 break;
464#endif
465 default:
466 header->compression() = ZIP_COMPRESSION;
467 break;
468 }
469}
470
471static int openexr_header_get_compression(const Header &header)
472{
473 switch (header.compression()) {
474 case NO_COMPRESSION:
476 case RLE_COMPRESSION:
477 return R_IMF_EXR_CODEC_RLE;
478 case ZIPS_COMPRESSION:
480 case ZIP_COMPRESSION:
481 return R_IMF_EXR_CODEC_ZIP;
482 case PIZ_COMPRESSION:
483 return R_IMF_EXR_CODEC_PIZ;
484 case PXR24_COMPRESSION:
486 case B44_COMPRESSION:
487 return R_IMF_EXR_CODEC_B44;
488 case B44A_COMPRESSION:
490 case DWAA_COMPRESSION:
492 case DWAB_COMPRESSION:
494 case NUM_COMPRESSION_METHODS:
496 }
498}
499
500static void openexr_header_metadata(Header *header, ImBuf *ibuf)
501{
502 if (ibuf->metadata) {
503 LISTBASE_FOREACH (IDProperty *, prop, &ibuf->metadata->data.group) {
504 if (prop->type == IDP_STRING && !STREQ(prop->name, "compression")) {
505 header->insert(prop->name, StringAttribute(IDP_String(prop)));
506 }
507 }
508 }
509
510 if (ibuf->ppm[0] > 0.0 && ibuf->ppm[1] > 0.0) {
511 /* Convert meters to inches. */
512 addXDensity(*header, ibuf->ppm[0] * 0.0254);
513 header->pixelAspectRatio() = blender::math::safe_divide(ibuf->ppm[1], ibuf->ppm[0]);
514 }
515
516 /* Write chromaticities for ACES-2065-1, as required by ACES container format. */
517 const ColorSpace *colorspace = (ibuf->float_buffer.data) ? ibuf->float_buffer.colorspace :
518 (ibuf->byte_buffer.data) ? ibuf->byte_buffer.colorspace :
519 nullptr;
520 if (colorspace) {
521 const char *aces_colorspace = IMB_colormanagement_role_colorspace_name_get(
523 const char *ibuf_colorspace = IMB_colormanagement_colorspace_get_name(colorspace);
524
525 if (aces_colorspace && STREQ(aces_colorspace, ibuf_colorspace)) {
526 header->insert("chromaticities", TypedAttribute<Chromaticities>(CHROMATICITIES_ACES_2065_1));
527 header->insert("adoptedNeutral", TypedAttribute<V2f>(CHROMATICITIES_ACES_2065_1.white));
528 }
529 }
530}
531
533 const char *propname,
534 char *prop,
535 int /*len*/)
536{
537 Header *header = (Header *)data;
538 header->insert(propname, StringAttribute(prop));
539}
540
541static bool imb_save_openexr_half(ImBuf *ibuf, const char *filepath, const int flags)
542{
543 const int channels = ibuf->channels;
544 const bool is_alpha = (channels >= 4) && (ibuf->planes == 32);
545 const int width = ibuf->x;
546 const int height = ibuf->y;
547 OStream *file_stream = nullptr;
548
549 try {
550 Header header(width, height);
551
553 &header, ibuf->foptions.flag & OPENEXR_CODEC_MASK, ibuf->foptions.quality);
554 openexr_header_metadata(&header, ibuf);
555
556 /* create channels */
557 header.channels().insert("R", Channel(HALF));
558 header.channels().insert("G", Channel(HALF));
559 header.channels().insert("B", Channel(HALF));
560 if (is_alpha) {
561 header.channels().insert("A", Channel(HALF));
562 }
563
564 FrameBuffer frameBuffer;
565
566 /* Manually create `ofstream`, so we can handle UTF8 file-paths on windows. */
567 if (flags & IB_mem) {
568 file_stream = new OMemStream(ibuf);
569 }
570 else {
571 file_stream = new OFileStream(filepath);
572 }
573 OutputFile file(*file_stream, header);
574
575 /* we store first everything in half array */
576 std::unique_ptr<RGBAZ[]> pixels = std::unique_ptr<RGBAZ[]>(new RGBAZ[int64_t(height) * width]);
577 RGBAZ *to = pixels.get();
578 int xstride = sizeof(RGBAZ);
579 int ystride = xstride * width;
580
581 /* indicate used buffers */
582 frameBuffer.insert("R", Slice(HALF, (char *)&to->r, xstride, ystride));
583 frameBuffer.insert("G", Slice(HALF, (char *)&to->g, xstride, ystride));
584 frameBuffer.insert("B", Slice(HALF, (char *)&to->b, xstride, ystride));
585 if (is_alpha) {
586 frameBuffer.insert("A", Slice(HALF, (char *)&to->a, xstride, ystride));
587 }
588 if (ibuf->float_buffer.data) {
589 float *from;
590
591 for (int i = ibuf->y - 1; i >= 0; i--) {
592 from = ibuf->float_buffer.data + int64_t(channels) * i * width;
593
594 for (int j = ibuf->x; j > 0; j--) {
595 to->r = float_to_half_safe(from[0]);
596 to->g = float_to_half_safe((channels >= 2) ? from[1] : from[0]);
597 to->b = float_to_half_safe((channels >= 3) ? from[2] : from[0]);
598 to->a = float_to_half_safe((channels >= 4) ? from[3] : 1.0f);
599 to++;
600 from += channels;
601 }
602 }
603 }
604 else {
605 uchar *from;
606
607 for (int i = ibuf->y - 1; i >= 0; i--) {
608 from = ibuf->byte_buffer.data + int64_t(4) * i * width;
609
610 for (int j = ibuf->x; j > 0; j--) {
611 to->r = srgb_to_linearrgb(float(from[0]) / 255.0f);
612 to->g = srgb_to_linearrgb(float(from[1]) / 255.0f);
613 to->b = srgb_to_linearrgb(float(from[2]) / 255.0f);
614 to->a = channels >= 4 ? float(from[3]) / 255.0f : 1.0f;
615 to++;
616 from += 4;
617 }
618 }
619 }
620
621 exr_printf("OpenEXR-save: Writing OpenEXR file of height %d.\n", height);
622
623 file.setFrameBuffer(frameBuffer);
624 file.writePixels(height);
625 }
626 catch (const std::exception &exc) {
627 delete file_stream;
628 printf("OpenEXR-save: ERROR: %s\n", exc.what());
629
630 return false;
631 }
632 catch (...) { /* Catch-all for edge cases or compiler bugs. */
633 delete file_stream;
634 printf("OpenEXR-save: UNKNOWN ERROR\n");
635
636 return false;
637 }
638
639 delete file_stream;
640 return true;
641}
642
643static bool imb_save_openexr_float(ImBuf *ibuf, const char *filepath, const int flags)
644{
645 const int channels = ibuf->channels;
646 const bool is_alpha = (channels >= 4) && (ibuf->planes == 32);
647 const int width = ibuf->x;
648 const int height = ibuf->y;
649 OStream *file_stream = nullptr;
650
651 try {
652 Header header(width, height);
653
655 &header, ibuf->foptions.flag & OPENEXR_CODEC_MASK, ibuf->foptions.quality);
656 openexr_header_metadata(&header, ibuf);
657
658 /* create channels */
659 header.channels().insert("R", Channel(Imf::FLOAT));
660 header.channels().insert("G", Channel(Imf::FLOAT));
661 header.channels().insert("B", Channel(Imf::FLOAT));
662 if (is_alpha) {
663 header.channels().insert("A", Channel(Imf::FLOAT));
664 }
665
666 FrameBuffer frameBuffer;
667
668 /* Manually create `ofstream`, so we can handle UTF8 file-paths on windows. */
669 if (flags & IB_mem) {
670 file_stream = new OMemStream(ibuf);
671 }
672 else {
673 file_stream = new OFileStream(filepath);
674 }
675 OutputFile file(*file_stream, header);
676
677 int xstride = sizeof(float) * channels;
678 int ystride = -xstride * width;
679
680 /* Last scan-line, stride negative. */
681 float *rect[4] = {nullptr, nullptr, nullptr, nullptr};
682 rect[0] = ibuf->float_buffer.data + int64_t(channels) * (height - 1) * width;
683 rect[1] = (channels >= 2) ? rect[0] + 1 : rect[0];
684 rect[2] = (channels >= 3) ? rect[0] + 2 : rect[0];
685 rect[3] = (channels >= 4) ?
686 rect[0] + 3 :
687 rect[0]; /* red as alpha, is this needed since alpha isn't written? */
688
689 frameBuffer.insert("R", Slice(Imf::FLOAT, (char *)rect[0], xstride, ystride));
690 frameBuffer.insert("G", Slice(Imf::FLOAT, (char *)rect[1], xstride, ystride));
691 frameBuffer.insert("B", Slice(Imf::FLOAT, (char *)rect[2], xstride, ystride));
692 if (is_alpha) {
693 frameBuffer.insert("A", Slice(Imf::FLOAT, (char *)rect[3], xstride, ystride));
694 }
695
696 file.setFrameBuffer(frameBuffer);
697 file.writePixels(height);
698 }
699 catch (const std::exception &exc) {
700 printf("OpenEXR-save: ERROR: %s\n", exc.what());
701 delete file_stream;
702 return false;
703 }
704 catch (...) { /* Catch-all for edge cases or compiler bugs. */
705 printf("OpenEXR-save: UNKNOWN ERROR\n");
706 delete file_stream;
707 return false;
708 }
709
710 delete file_stream;
711 return true;
712}
713
714bool imb_save_openexr(ImBuf *ibuf, const char *filepath, int flags)
715{
716 if (flags & IB_mem) {
718 ibuf->encoded_size = 0;
719 }
720
721 if (ibuf->foptions.flag & OPENEXR_HALF) {
722 return imb_save_openexr_half(ibuf, filepath, flags);
723 }
724
725 /* when no float rect, we save as half (16 bits is sufficient) */
726 if (ibuf->float_buffer.data == nullptr) {
727 return imb_save_openexr_half(ibuf, filepath, flags);
728 }
729
730 return imb_save_openexr_float(ibuf, filepath, flags);
731}
732
733/* ******* Nicer API, MultiLayer and with Tile file support ************************************ */
734
735/* naming rules:
736 * - parse name from right to left
737 * - last character is channel ID, 1 char like 'A' 'R' 'G' 'B' 'X' 'Y' 'Z' 'W' 'U' 'V'
738 * - separated with a dot; the Pass name (like "Depth", "Color", "Diffuse" or "Combined")
739 * - separated with a dot: the Layer name (like "Light1" or "Walls" or "Characters")
740 */
741
742static ListBase exrhandles = {nullptr, nullptr};
743
744struct ExrHandle {
747
748 IStream *ifile_stream;
749 MultiPartInputFile *ifile;
750
752 MultiPartOutputFile *mpofile;
753 OutputFile *ofile;
754
758
761 StringVector *multiView;
762
763 int parts;
764
765 ListBase channels; /* flattened out, ExrChannel */
766 ListBase layers; /* hierarchical, pointing in end to ExrChannel */
767
770};
771
772/* flattened out channel */
775
776 char name[EXR_TOT_MAXNAME + 1]; /* full name with everything */
777 MultiViewChannelName *m; /* struct to store all multipart channel info */
778 int xstride, ystride; /* step to next pixel, to next scan-line. */
779 float *rect; /* first pointer to write in */
780 char chan_id; /* quick lookup of channel char */
781 int view_id; /* quick lookup of channel view */
782 bool use_half_float; /* when saving use half float for file storage */
783};
784
785/* hierarchical; layers -> passes -> channels[] */
798
804
806
807/* ********************** */
808
810{
811 ExrHandle *data = MEM_callocN<ExrHandle>("exr handle");
812 data->multiView = new StringVector();
813
815 return data;
816}
817
818void *IMB_exr_get_handle_name(const char *name)
819{
821
822 if (data == nullptr) {
824 STRNCPY(data->name, name);
825 }
826 return data;
827}
828
829/* multiview functions */
830
831void IMB_exr_add_view(void *handle, const char *name)
832{
833 ExrHandle *data = (ExrHandle *)handle;
834 data->multiView->emplace_back(name);
835}
836
837static int imb_exr_get_multiView_id(StringVector &views, const std::string &name)
838{
839 int count = 0;
840 for (StringVector::const_iterator i = views.begin(); count < views.size(); ++i) {
841 if (name == *i) {
842 return count;
843 }
844
845 count++;
846 }
847
848 /* no views or wrong name */
849 return -1;
850}
851
852static void imb_exr_get_views(MultiPartInputFile &file, StringVector &views)
853{
854 if (exr_has_multipart_file(file) == false) {
855 if (exr_has_multiview(file)) {
856 StringVector sv = multiView(file.header(0));
857 for (const std::string &view_name : sv) {
858 views.push_back(view_name);
859 }
860 }
861 }
862
863 else {
864 for (int p = 0; p < file.parts(); p++) {
865 std::string view;
866 if (file.header(p).hasView()) {
867 view = file.header(p).view();
868 }
869
870 if (imb_exr_get_multiView_id(views, view) == -1) {
871 views.push_back(view);
872 }
873 }
874 }
875}
876
877/* Multi-layer Blender files have the view name in all the passes (even the default view one). */
878static void imb_exr_insert_view_name(char name_full[EXR_TOT_MAXNAME + 1],
879 const char *passname,
880 const char *viewname)
881{
882 /* Match: `sizeof(ExrChannel::name)`. */
883 const size_t name_full_maxncpy = EXR_TOT_MAXNAME + 1;
884 BLI_assert(!ELEM(name_full, passname, viewname));
885
886 if (viewname == nullptr || viewname[0] == '\0') {
887 BLI_strncpy(name_full, passname, name_full_maxncpy);
888 return;
889 }
890
891 const char delims[] = {'.', '\0'};
892 const char *sep;
893 const char *token;
894 size_t len;
895
896 len = BLI_str_rpartition(passname, delims, &sep, &token);
897
898 if (sep) {
899 BLI_snprintf(name_full, name_full_maxncpy, "%.*s.%s.%s", int(len), passname, viewname, token);
900 }
901 else {
902 BLI_snprintf(name_full, name_full_maxncpy, "%s.%s", passname, viewname);
903 }
904}
905
906void IMB_exr_add_channel(void *handle,
907 const char *layname,
908 const char *passname,
909 const char *viewname,
910 int xstride,
911 int ystride,
912 float *rect,
913 bool use_half_float)
914{
915 ExrHandle *data = (ExrHandle *)handle;
916 ExrChannel *echan;
917
918 echan = MEM_callocN<ExrChannel>("exr channel");
919 echan->m = new MultiViewChannelName();
920
921 if (layname && layname[0] != '\0') {
922 echan->m->name = layname;
923 echan->m->name.append(".");
924 echan->m->name.append(passname);
925 }
926 else {
927 echan->m->name.assign(passname);
928 }
929
930 echan->m->internal_name = echan->m->name;
931
932 echan->m->view.assign(viewname ? viewname : "");
933
934 /* quick look up */
935 echan->view_id = std::max(0, imb_exr_get_multiView_id(*data->multiView, echan->m->view));
936
937 /* name has to be unique, thus it's a combination of layer, pass, view, and channel */
938 if (layname && layname[0] != '\0') {
939 imb_exr_insert_view_name(echan->name, echan->m->name.c_str(), echan->m->view.c_str());
940 }
941 else if (!data->multiView->empty()) {
942 std::string raw_name = insertViewName(echan->m->name, *data->multiView, echan->view_id);
943 STRNCPY(echan->name, raw_name.c_str());
944 }
945 else {
946 STRNCPY(echan->name, echan->m->name.c_str());
947 }
948
949 echan->xstride = xstride;
950 echan->ystride = ystride;
951 echan->rect = rect;
952 echan->use_half_float = use_half_float;
953
954 if (echan->use_half_float) {
955 data->num_half_channels++;
956 }
957
958 exr_printf("added channel %s\n", echan->name);
959 BLI_addtail(&data->channels, echan);
960}
961
962bool IMB_exr_begin_write(void *handle,
963 const char *filepath,
964 int width,
965 int height,
966 const double ppm[2],
967 int compress,
968 int quality,
969 const StampData *stamp)
970{
971 ExrHandle *data = (ExrHandle *)handle;
972 Header header(width, height);
973
974 data->width = width;
975 data->height = height;
976
977 bool is_singlelayer, is_multilayer, is_multiview;
978
979 LISTBASE_FOREACH (ExrChannel *, echan, &data->channels) {
980 header.channels().insert(echan->name, Channel(echan->use_half_float ? Imf::HALF : Imf::FLOAT));
981 }
982
983 openexr_header_compression(&header, compress, quality);
985 &header, const_cast<StampData *>(stamp), openexr_header_metadata_callback, false);
986 /* header.lineOrder() = DECREASING_Y; this crashes in windows for file read! */
987
989 header.channels(), *data->multiView, &is_singlelayer, &is_multilayer, &is_multiview);
990
991 if (is_multilayer) {
992 header.insert("BlenderMultiChannel", StringAttribute("Blender V2.55.1 and newer"));
993 }
994
995 if (is_multiview) {
996 addMultiView(header, *data->multiView);
997 }
998
999 if (ppm[0] != 0.0 && ppm[1] != 0.0) {
1000 addXDensity(header, ppm[0] * 0.0254);
1001 header.pixelAspectRatio() = blender::math::safe_divide(ppm[1], ppm[0]);
1002 }
1003
1004 /* Avoid crash/abort when we don't have permission to write here. */
1005 /* Manually create `ofstream`, so we can handle UTF8 file-paths on windows. */
1006 try {
1007 data->ofile_stream = new OFileStream(filepath);
1008 data->ofile = new OutputFile(*(data->ofile_stream), header);
1009 }
1010 catch (const std::exception &exc) {
1011 std::cerr << "IMB_exr_begin_write: ERROR: " << exc.what() << std::endl;
1012
1013 delete data->ofile;
1014 delete data->ofile_stream;
1015
1016 data->ofile = nullptr;
1017 data->ofile_stream = nullptr;
1018 }
1019 catch (...) { /* Catch-all for edge cases or compiler bugs. */
1020 std::cerr << "IMB_exr_begin_write: UNKNOWN ERROR" << std::endl;
1021
1022 delete data->ofile;
1023 delete data->ofile_stream;
1024
1025 data->ofile = nullptr;
1026 data->ofile_stream = nullptr;
1027 }
1028
1029 return (data->ofile != nullptr);
1030}
1031
1033 void *handle, const char *filepath, int *width, int *height, const bool parse_channels)
1034{
1035 ExrHandle *data = (ExrHandle *)handle;
1036 ExrChannel *echan;
1037
1038 /* 32 is arbitrary, but zero length files crashes exr. */
1039 if (!(BLI_exists(filepath) && BLI_file_size(filepath) > 32)) {
1040 return false;
1041 }
1042
1043 /* avoid crash/abort when we don't have permission to write here */
1044 try {
1045 data->ifile_stream = new IFileStream(filepath);
1046 data->ifile = new MultiPartInputFile(*(data->ifile_stream));
1047 }
1048 catch (...) { /* Catch-all for edge cases or compiler bugs. */
1049 delete data->ifile;
1050 delete data->ifile_stream;
1051
1052 data->ifile = nullptr;
1053 data->ifile_stream = nullptr;
1054 }
1055
1056 if (!data->ifile) {
1057 return false;
1058 }
1059
1060 Box2i dw = data->ifile->header(0).dataWindow();
1061 data->width = *width = dw.max.x - dw.min.x + 1;
1062 data->height = *height = dw.max.y - dw.min.y + 1;
1063
1064 if (parse_channels) {
1065 /* Parse channels into view/layer/pass. */
1067 return false;
1068 }
1069 }
1070 else {
1071 /* Read view and channels without parsing. */
1072 imb_exr_get_views(*data->ifile, *data->multiView);
1073
1074 std::vector<MultiViewChannelName> channels;
1075 GetChannelsInMultiPartFile(*data->ifile, channels);
1076
1077 for (const MultiViewChannelName &channel : channels) {
1079 data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false);
1080
1081 echan = (ExrChannel *)data->channels.last;
1082 echan->m->name = channel.name;
1083 echan->m->view = channel.view;
1084 echan->m->part_number = channel.part_number;
1085 echan->m->internal_name = channel.internal_name;
1086 }
1087 }
1088
1089 return true;
1090}
1091
1093 void *handle, const char *layname, const char *passname, int xstride, int ystride, float *rect)
1094{
1095 ExrHandle *data = (ExrHandle *)handle;
1096 char name[EXR_TOT_MAXNAME + 1];
1097
1098 if (layname && layname[0] != '\0') {
1099 char lay[EXR_LAY_MAXNAME + 1], pass[EXR_PASS_MAXNAME + 1];
1100 BLI_strncpy(lay, layname, EXR_LAY_MAXNAME);
1101 BLI_strncpy(pass, passname, EXR_PASS_MAXNAME);
1102
1103 SNPRINTF(name, "%s.%s", lay, pass);
1104 }
1105 else {
1106 BLI_strncpy(name, passname, EXR_TOT_MAXNAME - 1);
1107 }
1108
1110 &data->channels, name, offsetof(ExrChannel, name));
1111
1112 if (echan == nullptr) {
1113 return false;
1114 }
1115
1116 echan->xstride = xstride;
1117 echan->ystride = ystride;
1118 echan->rect = rect;
1119 return true;
1120}
1121
1122void IMB_exr_write_channels(void *handle)
1123{
1124 ExrHandle *data = (ExrHandle *)handle;
1125 FrameBuffer frameBuffer;
1126
1127 if (data->channels.first) {
1128 const size_t num_pixels = size_t(data->width) * data->height;
1129 half *rect_half = nullptr, *current_rect_half = nullptr;
1130
1131 /* We allocate temporary storage for half pixels for all the channels at once. */
1132 if (data->num_half_channels != 0) {
1133 rect_half = MEM_malloc_arrayN<half>(size_t(data->num_half_channels) * num_pixels, __func__);
1134 current_rect_half = rect_half;
1135 }
1136
1137 LISTBASE_FOREACH (ExrChannel *, echan, &data->channels) {
1138 /* Writing starts from last scan-line, stride negative. */
1139 if (echan->use_half_float) {
1140 const float *rect = echan->rect;
1141 half *cur = current_rect_half;
1142 for (size_t i = 0; i < num_pixels; i++, cur++) {
1143 *cur = float_to_half_safe(rect[i * echan->xstride]);
1144 }
1145 half *rect_to_write = current_rect_half + (data->height - 1L) * data->width;
1146 frameBuffer.insert(
1147 echan->name,
1148 Slice(Imf::HALF, (char *)rect_to_write, sizeof(half), -data->width * sizeof(half)));
1149 current_rect_half += num_pixels;
1150 }
1151 else {
1152 float *rect = echan->rect + echan->xstride * (data->height - 1L) * data->width;
1153 frameBuffer.insert(echan->name,
1154 Slice(Imf::FLOAT,
1155 (char *)rect,
1156 echan->xstride * sizeof(float),
1157 -echan->ystride * sizeof(float)));
1158 }
1159 }
1160
1161 data->ofile->setFrameBuffer(frameBuffer);
1162 try {
1163 data->ofile->writePixels(data->height);
1164 }
1165 catch (const std::exception &exc) {
1166 std::cerr << "OpenEXR-writePixels: ERROR: " << exc.what() << std::endl;
1167 }
1168 catch (...) { /* Catch-all for edge cases or compiler bugs. */
1169 std::cerr << "OpenEXR-writePixels: UNKNOWN ERROR" << std::endl;
1170 }
1171 /* Free temporary buffers. */
1172 if (rect_half != nullptr) {
1173 MEM_freeN(rect_half);
1174 }
1175 }
1176 else {
1177 printf("Error: attempt to save MultiLayer without layers.\n");
1178 }
1179}
1180
1181void IMB_exr_read_channels(void *handle)
1182{
1183 ExrHandle *data = (ExrHandle *)handle;
1184 int numparts = data->ifile->parts();
1185
1186 /* Check if EXR was saved with previous versions of blender which flipped images. */
1187 const StringAttribute *ta = data->ifile->header(0).findTypedAttribute<StringAttribute>(
1188 "BlenderMultiChannel");
1189
1190 /* 'previous multilayer attribute, flipped. */
1191 short flip = (ta && STRPREFIX(ta->value().c_str(), "Blender V2.43"));
1192
1193 exr_printf(
1194 "\nIMB_exr_read_channels\n%s %-6s %-22s "
1195 "\"%s\"\n---------------------------------------------------------------------\n",
1196 "p",
1197 "view",
1198 "name",
1199 "internal_name");
1200
1201 for (int i = 0; i < numparts; i++) {
1202 /* Read part header. */
1203 InputPart in(*data->ifile, i);
1204 Header header = in.header();
1205 Box2i dw = header.dataWindow();
1206
1207 /* Insert all matching channel into frame-buffer. */
1208 FrameBuffer frameBuffer;
1209
1210 LISTBASE_FOREACH (ExrChannel *, echan, &data->channels) {
1211 if (echan->m->part_number != i) {
1212 continue;
1213 }
1214
1215 exr_printf("%d %-6s %-22s \"%s\"\n",
1216 echan->m->part_number,
1217 echan->m->view.c_str(),
1218 echan->m->name.c_str(),
1219 echan->m->internal_name.c_str());
1220
1221 if (echan->rect) {
1222 float *rect = echan->rect;
1223 size_t xstride = echan->xstride * sizeof(float);
1224 size_t ystride = echan->ystride * sizeof(float);
1225
1226 if (!flip) {
1227 /* Inverse correct first pixel for data-window coordinates. */
1228 rect -= echan->xstride * (dw.min.x - dw.min.y * data->width);
1229 /* Move to last scan-line to flip to Blender convention. */
1230 rect += echan->xstride * (data->height - 1) * data->width;
1231 ystride = -ystride;
1232 }
1233 else {
1234 /* Inverse correct first pixel for data-window coordinates. */
1235 rect -= echan->xstride * (dw.min.x + dw.min.y * data->width);
1236 }
1237
1238 frameBuffer.insert(echan->m->internal_name,
1239 Slice(Imf::FLOAT, (char *)rect, xstride, ystride));
1240 }
1241 }
1242
1243 /* Read pixels. */
1244 try {
1245 in.setFrameBuffer(frameBuffer);
1246 exr_printf("readPixels:readPixels[%d]: min.y: %d, max.y: %d\n", i, dw.min.y, dw.max.y);
1247 in.readPixels(dw.min.y, dw.max.y);
1248 }
1249 catch (const std::exception &exc) {
1250 std::cerr << "OpenEXR-readPixels: ERROR: " << exc.what() << std::endl;
1251 break;
1252 }
1253 catch (...) { /* Catch-all for edge cases or compiler bugs. */
1254 std::cerr << "OpenEXR-readPixels: UNKNOWN ERROR: " << std::endl;
1255 break;
1256 }
1257 }
1258}
1259
1261 void *base,
1262 void *(*addview)(void *base, const char *str),
1263 void *(*addlayer)(void *base, const char *str),
1264 void (*addpass)(void *base,
1265 void *lay,
1266 const char *str,
1267 float *rect,
1268 int totchan,
1269 const char *chan_id,
1270 const char *view))
1271{
1272 ExrHandle *data = (ExrHandle *)handle;
1273
1274 /* RenderResult needs at least one RenderView */
1275 if (data->multiView->empty()) {
1276 addview(base, "");
1277 }
1278 else {
1279 /* add views to RenderResult */
1280 for (const std::string &view_name : *data->multiView) {
1281 addview(base, view_name.c_str());
1282 }
1283 }
1284
1285 if (BLI_listbase_is_empty(&data->layers)) {
1286 printf("cannot convert multilayer, no layers in handle\n");
1287 return;
1288 }
1289
1290 LISTBASE_FOREACH (ExrLayer *, lay, &data->layers) {
1291 void *laybase = addlayer(base, lay->name);
1292 if (laybase) {
1293 LISTBASE_FOREACH (ExrPass *, pass, &lay->passes) {
1294 addpass(base,
1295 laybase,
1296 pass->internal_name,
1297 pass->rect,
1298 pass->totchan,
1299 pass->chan_id,
1300 pass->view);
1301 pass->rect = nullptr;
1302 }
1303 }
1304 }
1305}
1306
1307void IMB_exr_close(void *handle)
1308{
1309 ExrHandle *data = (ExrHandle *)handle;
1310
1311 delete data->ifile;
1312 delete data->ifile_stream;
1313 delete data->ofile;
1314 delete data->mpofile;
1315 delete data->ofile_stream;
1316 delete data->multiView;
1317
1318 data->ifile = nullptr;
1319 data->ifile_stream = nullptr;
1320 data->ofile = nullptr;
1321 data->mpofile = nullptr;
1322 data->ofile_stream = nullptr;
1323
1324 LISTBASE_FOREACH (ExrChannel *, chan, &data->channels) {
1325 delete chan->m;
1326 }
1327 BLI_freelistN(&data->channels);
1328
1329 LISTBASE_FOREACH (ExrLayer *, lay, &data->layers) {
1330 LISTBASE_FOREACH (ExrPass *, pass, &lay->passes) {
1331 if (pass->rect) {
1332 MEM_freeN(pass->rect);
1333 }
1334 }
1335 BLI_freelistN(&lay->passes);
1336 }
1337 BLI_freelistN(&data->layers);
1338
1340 MEM_freeN(data);
1341}
1342
1343/* ********* */
1344
1346static int imb_exr_split_token(const char *str, const char *end, const char **token)
1347{
1348 const char delims[] = {'.', '\0'};
1349 const char *sep;
1350
1351 BLI_str_partition_ex(str, end, delims, &sep, token, true);
1352
1353 if (!sep) {
1354 *token = str;
1355 }
1356
1357 return int(end - *token);
1358}
1359
1360static void imb_exr_pass_name_from_channel(char *passname,
1361 const ExrChannel *echan,
1362 const char *channelname,
1363 const bool has_xyz_channels)
1364{
1365 const int passname_maxncpy = EXR_TOT_MAXNAME;
1366
1367 if (echan->chan_id == 'Z' && (!has_xyz_channels || BLI_strcaseeq(channelname, "depth"))) {
1368 BLI_strncpy(passname, "Depth", passname_maxncpy);
1369 }
1370 else if (echan->chan_id == 'Y' && !has_xyz_channels) {
1371 BLI_strncpy(passname, channelname, passname_maxncpy);
1372 }
1373 else if (ELEM(echan->chan_id, 'R', 'G', 'B', 'A', 'V', 'X', 'Y', 'Z')) {
1374 BLI_strncpy(passname, "Combined", passname_maxncpy);
1375 }
1376 else {
1377 BLI_strncpy(passname, channelname, passname_maxncpy);
1378 }
1379}
1380
1381static void imb_exr_pass_name_from_channel_name(char *passname,
1382 const ExrChannel * /*echan*/,
1383 const char *channelname,
1384 const bool /*has_xyz_channels*/)
1385{
1386 const int passname_maxncpy = EXR_TOT_MAXNAME;
1387
1388 /* TODO: Are special tricks similar to imb_exr_pass_name_from_channel() needed here?
1389 * Note that unknown passes are default to chan_id='X'. The place where this function is called
1390 * is when the channel name is more than 1 character, so perhaps using just channel ID is not
1391 * fully correct here. */
1392
1393 BLI_strncpy(passname, channelname, passname_maxncpy);
1394}
1395
1397 char *layname,
1398 char *passname,
1399 bool has_xyz_channels)
1400{
1401 const int layname_maxncpy = EXR_TOT_MAXNAME;
1402 const char *name = echan->m->name.c_str();
1403 const char *end = name + strlen(name);
1404 const char *token;
1405
1406 /* Some multi-layers have the combined buffer with names V, RGBA, or XYZ saved. Additionally, the
1407 * Z channel can be interpreted as a Depth channel, but we only detect it as such if no X and Y
1408 * channels exists, since the Z in this case is part of XYZ. The same goes for the Y channel,
1409 * which can be detected as a luminance channel with the same name. */
1410 if (name[1] == 0) {
1411 /* Notice that we will be comparing with this upper-case version of the channel name, so the
1412 * below comparisons are effectively not case sensitive, and would also consider lowercase
1413 * versions of the listed channels. */
1414 echan->chan_id = BLI_toupper_ascii(name[0]);
1415 layname[0] = '\0';
1416 imb_exr_pass_name_from_channel(passname, echan, name, has_xyz_channels);
1417 return 1;
1418 }
1419
1420 /* last token is channel identifier */
1421 size_t len = imb_exr_split_token(name, end, &token);
1422 if (len == 0) {
1423 printf("multilayer read: bad channel name: %s\n", name);
1424 return 0;
1425 }
1426
1427 char channelname[EXR_TOT_MAXNAME];
1428 BLI_strncpy(channelname, token, std::min(len + 1, sizeof(channelname)));
1429
1430 if (len == 1) {
1431 echan->chan_id = BLI_toupper_ascii(channelname[0]);
1432 }
1433 else {
1434 BLI_assert(len > 1); /* Checks above ensure. */
1435 if (len == 2) {
1436 /* Some multi-layers are using two-letter channels name,
1437 * like, MX or NZ, which is basically has structure of
1438 * <pass_prefix><component>
1439 *
1440 * This is a bit silly, but see file from #35658.
1441 *
1442 * Here we do some magic to distinguish such cases.
1443 */
1444 const char chan_id = BLI_toupper_ascii(channelname[1]);
1445 if (ELEM(chan_id, 'X', 'Y', 'Z', 'R', 'G', 'B', 'U', 'V', 'A')) {
1446 echan->chan_id = chan_id;
1447 }
1448 else {
1449 echan->chan_id = 'X'; /* Default to X if unknown. */
1450 }
1451 }
1452 else if (BLI_strcaseeq(channelname, "red")) {
1453 echan->chan_id = 'R';
1454 }
1455 else if (BLI_strcaseeq(channelname, "green")) {
1456 echan->chan_id = 'G';
1457 }
1458 else if (BLI_strcaseeq(channelname, "blue")) {
1459 echan->chan_id = 'B';
1460 }
1461 else if (BLI_strcaseeq(channelname, "alpha")) {
1462 echan->chan_id = 'A';
1463 }
1464 else if (BLI_strcaseeq(channelname, "depth")) {
1465 echan->chan_id = 'Z';
1466 }
1467 else {
1468 echan->chan_id = 'X'; /* Default to X if unknown. */
1469 }
1470 }
1471 end -= len + 1; /* +1 to skip '.' separator */
1472
1473 if (end > name) {
1474 /* second token is pass name */
1475 len = imb_exr_split_token(name, end, &token);
1476 if (len == 0) {
1477 printf("multilayer read: bad channel name: %s\n", name);
1478 return 0;
1479 }
1480 BLI_strncpy(passname, token, len + 1);
1481 end -= len + 1; /* +1 to skip '.' separator */
1482 }
1483 else {
1484 /* Single token, determine pass name from channel name. */
1485 imb_exr_pass_name_from_channel_name(passname, echan, channelname, has_xyz_channels);
1486 }
1487
1488 /* all preceding tokens combined as layer name */
1489 if (end > name) {
1490 BLI_strncpy(layname, name, std::min(layname_maxncpy, int(end - name) + 1));
1491 }
1492 else {
1493 layname[0] = '\0';
1494 }
1495
1496 return 1;
1497}
1498
1499static ExrLayer *imb_exr_get_layer(ListBase *lb, const char *layname)
1500{
1501 ExrLayer *lay = (ExrLayer *)BLI_findstring(lb, layname, offsetof(ExrLayer, name));
1502
1503 if (lay == nullptr) {
1504 lay = MEM_callocN<ExrLayer>("exr layer");
1505 BLI_addtail(lb, lay);
1506 BLI_strncpy(lay->name, layname, EXR_LAY_MAXNAME);
1507 }
1508
1509 return lay;
1510}
1511
1512static ExrPass *imb_exr_get_pass(ListBase *lb, const char *passname)
1513{
1514 ExrPass *pass = (ExrPass *)BLI_findstring(lb, passname, offsetof(ExrPass, name));
1515
1516 if (pass == nullptr) {
1517 pass = MEM_callocN<ExrPass>("exr pass");
1518
1519 if (STREQ(passname, "Combined")) {
1520 BLI_addhead(lb, pass);
1521 }
1522 else {
1523 BLI_addtail(lb, pass);
1524 }
1525 }
1526
1527 STRNCPY(pass->name, passname);
1528
1529 return pass;
1530}
1531
1532static bool exr_has_xyz_channels(ExrHandle *exr_handle)
1533{
1534 bool x_found = false;
1535 bool y_found = false;
1536 bool z_found = false;
1537 LISTBASE_FOREACH (ExrChannel *, channel, &exr_handle->channels) {
1538 if (ELEM(channel->m->name, "X", "x")) {
1539 x_found = true;
1540 }
1541 if (ELEM(channel->m->name, "Y", "y")) {
1542 y_found = true;
1543 }
1544 if (ELEM(channel->m->name, "Z", "z")) {
1545 z_found = true;
1546 }
1547 }
1548
1549 return x_found && y_found && z_found;
1550}
1551
1552/* Replacement for OpenEXR GetChannelsInMultiPartFile, that also handles the
1553 * case where parts are used for passes instead of multiview. */
1554static std::vector<MultiViewChannelName> exr_channels_in_multi_part_file(
1555 const MultiPartInputFile &file)
1556{
1557 std::vector<MultiViewChannelName> channels;
1558
1559 /* Detect if file has multiview. */
1560 StringVector multiview;
1561 bool has_multiview = false;
1562 if (file.parts() == 1) {
1563 if (hasMultiView(file.header(0))) {
1564 multiview = multiView(file.header(0));
1565 has_multiview = true;
1566 }
1567 }
1568
1569 /* Get channels from each part. */
1570 for (int p = 0; p < file.parts(); p++) {
1571 const ChannelList &c = file.header(p).channels();
1572
1573 blender::StringRef part_view;
1574 if (file.header(p).hasView()) {
1575 part_view = file.header(p).view();
1576 }
1577 blender::StringRef part_name;
1578 if (file.header(p).hasName()) {
1579 part_name = file.header(p).name();
1580 }
1581
1582 /* Strip part suffix from name. */
1583 if (part_name.endswith("." + part_view)) {
1584 part_name = part_name.drop_known_suffix("." + part_view);
1585 }
1586 else if (part_name.endswith("-" + part_view)) {
1587 part_name = part_name.drop_known_suffix("-" + part_view);
1588 }
1589
1590 for (ChannelList::ConstIterator i = c.begin(); i != c.end(); i++) {
1591 MultiViewChannelName m;
1592 m.name = std::string(i.name());
1593 m.internal_name = m.name;
1594
1595 if (has_multiview) {
1596 m.view = viewFromChannelName(m.name, multiview);
1597 m.name = removeViewName(m.internal_name, m.view);
1598 }
1599 else {
1600 m.view = part_view;
1601 }
1602
1603 /* Prepend part name as potential layer or pass name. According to OpenEXR docs
1604 * this should not be needed, but Houdini writes files like this. */
1605 if (!part_name.is_empty() && !blender::StringRef(m.name).startswith(part_name + ".")) {
1606 m.name = part_name + "." + m.name;
1607 }
1608
1609 m.part_number = p;
1610 channels.push_back(m);
1611 }
1612 }
1613
1614 return channels;
1615}
1616
1618{
1619 std::vector<MultiViewChannelName> channels = exr_channels_in_multi_part_file(*data->ifile);
1620
1621 imb_exr_get_views(*data->ifile, *data->multiView);
1622
1623 for (const MultiViewChannelName &channel : channels) {
1625 data, nullptr, channel.name.c_str(), channel.view.c_str(), 0, 0, nullptr, false);
1626
1627 ExrChannel *echan = (ExrChannel *)data->channels.last;
1628 echan->m->name = channel.name;
1629 echan->m->view = channel.view;
1630 echan->m->part_number = channel.part_number;
1631 echan->m->internal_name = channel.internal_name;
1632 }
1633
1634 const bool has_xyz_channels = exr_has_xyz_channels(data);
1635
1636 /* now try to sort out how to assign memory to the channels */
1637 /* first build hierarchical layer list */
1638 ExrChannel *echan = (ExrChannel *)data->channels.first;
1639 for (; echan; echan = echan->next) {
1640 char layname[EXR_TOT_MAXNAME], passname[EXR_TOT_MAXNAME];
1641 if (imb_exr_split_channel_name(echan, layname, passname, has_xyz_channels)) {
1642 const char *view = echan->m->view.c_str();
1643 char internal_name[EXR_PASS_MAXNAME];
1644
1645 STRNCPY(internal_name, passname);
1646
1647 if (view[0] != '\0') {
1648 char tmp_pass[EXR_PASS_MAXNAME];
1649 SNPRINTF(tmp_pass, "%s.%s", passname, view);
1650 STRNCPY(passname, tmp_pass);
1651 }
1652
1653 ExrLayer *lay = imb_exr_get_layer(&data->layers, layname);
1654 ExrPass *pass = imb_exr_get_pass(&lay->passes, passname);
1655
1656 pass->chan[pass->totchan] = echan;
1657 pass->totchan++;
1658 pass->view_id = echan->view_id;
1659 STRNCPY(pass->view, view);
1660 STRNCPY(pass->internal_name, internal_name);
1661
1662 if (pass->totchan >= EXR_PASS_MAXCHAN) {
1663 break;
1664 }
1665 }
1666 }
1667 if (echan) {
1668 printf("error, too many channels in one pass: %s\n", echan->m->name.c_str());
1669 return false;
1670 }
1671
1672 /* with some heuristics, try to merge the channels in buffers */
1673 LISTBASE_FOREACH (ExrLayer *, lay, &data->layers) {
1674 LISTBASE_FOREACH (ExrPass *, pass, &lay->passes) {
1675 if (pass->totchan) {
1676 pass->rect = MEM_calloc_arrayN<float>(
1677 size_t(data->width) * size_t(data->height) * size_t(pass->totchan), "pass rect");
1678 if (pass->totchan == 1) {
1679 ExrChannel *echan = pass->chan[0];
1680 echan->rect = pass->rect;
1681 echan->xstride = 1;
1682 echan->ystride = data->width;
1683 pass->chan_id[0] = echan->chan_id;
1684 }
1685 else {
1686 char lookup[256];
1687
1688 memset(lookup, 0, sizeof(lookup));
1689
1690 /* we can have RGB(A), XYZ(W), UVA */
1691 if (ELEM(pass->totchan, 3, 4)) {
1692 if (pass->chan[0]->chan_id == 'B' || pass->chan[1]->chan_id == 'B' ||
1693 pass->chan[2]->chan_id == 'B')
1694 {
1695 lookup[uint('R')] = 0;
1696 lookup[uint('G')] = 1;
1697 lookup[uint('B')] = 2;
1698 lookup[uint('A')] = 3;
1699 }
1700 else if (pass->chan[0]->chan_id == 'Y' || pass->chan[1]->chan_id == 'Y' ||
1701 pass->chan[2]->chan_id == 'Y')
1702 {
1703 lookup[uint('X')] = 0;
1704 lookup[uint('Y')] = 1;
1705 lookup[uint('Z')] = 2;
1706 lookup[uint('W')] = 3;
1707 }
1708 else {
1709 lookup[uint('U')] = 0;
1710 lookup[uint('V')] = 1;
1711 lookup[uint('A')] = 2;
1712 }
1713 for (int a = 0; a < pass->totchan; a++) {
1714 echan = pass->chan[a];
1715 echan->rect = pass->rect + lookup[uint(echan->chan_id)];
1716 echan->xstride = pass->totchan;
1717 echan->ystride = data->width * pass->totchan;
1718 pass->chan_id[uint(lookup[uint(echan->chan_id)])] = echan->chan_id;
1719 }
1720 }
1721 else { /* unknown */
1722 for (int a = 0; a < pass->totchan; a++) {
1723 ExrChannel *echan = pass->chan[a];
1724 echan->rect = pass->rect + a;
1725 echan->xstride = pass->totchan;
1726 echan->ystride = data->width * pass->totchan;
1727 pass->chan_id[a] = echan->chan_id;
1728 }
1729 }
1730 }
1731 }
1732 }
1733 }
1734
1735 return true;
1736}
1737
1738/* creates channels, makes a hierarchy and assigns memory to channels */
1739static ExrHandle *imb_exr_begin_read_mem(IStream &file_stream,
1740 MultiPartInputFile &file,
1741 int width,
1742 int height)
1743{
1745
1746 data->ifile_stream = &file_stream;
1747 data->ifile = &file;
1748
1749 data->width = width;
1750 data->height = height;
1751
1754 return nullptr;
1755 }
1756
1757 return data;
1758}
1759
1760/* ********************************************************* */
1761
1762/* debug only */
1763static void exr_printf(const char *fmt, ...)
1764{
1765#if 0
1766 va_list args;
1767 va_start(args, fmt);
1768 vprintf(fmt, args);
1769 va_end(args);
1770#else
1771 (void)fmt;
1772#endif
1773}
1774
1775static void exr_print_filecontents(MultiPartInputFile &file)
1776{
1777 int numparts = file.parts();
1778 if (numparts == 1 && hasMultiView(file.header(0))) {
1779 const StringVector views = multiView(file.header(0));
1780 printf("OpenEXR-load: MultiView file\n");
1781 printf("OpenEXR-load: Default view: %s\n", defaultViewName(views).c_str());
1782 for (const std::string &view : views) {
1783 printf("OpenEXR-load: Found view %s\n", view.c_str());
1784 }
1785 }
1786 else if (numparts > 1) {
1787 printf("OpenEXR-load: MultiPart file\n");
1788 for (int i = 0; i < numparts; i++) {
1789 if (file.header(i).hasView()) {
1790 printf("OpenEXR-load: Part %d: view = \"%s\"\n", i, file.header(i).view().c_str());
1791 }
1792 }
1793 }
1794
1795 for (int j = 0; j < numparts; j++) {
1796 const ChannelList &channels = file.header(j).channels();
1797 for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
1798 const Channel &channel = i.channel();
1799 printf("OpenEXR-load: Found channel %s of type %d\n", i.name(), channel.type);
1800 }
1801 }
1802}
1803
1804/* For non-multi-layer, map R G B A channel names to something that's in this file. */
1805static const char *exr_rgba_channelname(MultiPartInputFile &file, const char *chan)
1806{
1807 const ChannelList &channels = file.header(0).channels();
1808
1809 for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
1810 // const Channel &channel = i.channel(); /* Not used yet. */
1811 const char *str = i.name();
1812 int len = strlen(str);
1813 if (len) {
1814 if (BLI_strcasecmp(chan, str + len - 1) == 0) {
1815 return str;
1816 }
1817 }
1818 }
1819 return chan;
1820}
1821
1822static int exr_has_rgb(MultiPartInputFile &file, const char *rgb_channels[3])
1823{
1824 /* Common names for RGB-like channels in order. The V channel name is used by convention for BW
1825 * images, which will be broadcast to RGB channel at the end. */
1826 static const char *channel_names[] = {
1827 "V", "R", "Red", "G", "Green", "B", "Blue", "AR", "RA", "AG", "GA", "AB", "BA", nullptr};
1828
1829 const Header &header = file.header(0);
1830 int num_channels = 0;
1831
1832 for (int i = 0; channel_names[i]; i++) {
1833 /* Also try to match lower case variant of the channel names. */
1834 std::string lower_case_name = std::string(channel_names[i]);
1835 std::transform(lower_case_name.begin(),
1836 lower_case_name.end(),
1837 lower_case_name.begin(),
1838 [](uchar c) { return std::tolower(c); });
1839
1840 if (header.channels().findChannel(channel_names[i]) ||
1841 header.channels().findChannel(lower_case_name))
1842 {
1843 rgb_channels[num_channels++] = channel_names[i];
1844 if (num_channels == 3) {
1845 break;
1846 }
1847 }
1848 }
1849
1850 return num_channels;
1851}
1852
1853static bool exr_has_luma(MultiPartInputFile &file)
1854{
1855 /* Y channel is the luma and should always present fir luma space images,
1856 * optionally it could be also channels for chromas called BY and RY.
1857 */
1858 const Header &header = file.header(0);
1859 return header.channels().findChannel("Y") != nullptr;
1860}
1861
1862static bool exr_has_chroma(MultiPartInputFile &file)
1863{
1864 const Header &header = file.header(0);
1865 return header.channels().findChannel("BY") != nullptr &&
1866 header.channels().findChannel("RY") != nullptr;
1867}
1868
1869static bool exr_has_alpha(MultiPartInputFile &file)
1870{
1871 const Header &header = file.header(0);
1872 return !(header.channels().findChannel("A") == nullptr);
1873}
1874
1875static bool exr_has_xyz(MultiPartInputFile &file)
1876{
1877 const Header &header = file.header(0);
1878 return (header.channels().findChannel("X") != nullptr ||
1879 header.channels().findChannel("x") != nullptr) &&
1880 (header.channels().findChannel("Y") != nullptr ||
1881 header.channels().findChannel("y") != nullptr) &&
1882 (header.channels().findChannel("Z") != nullptr ||
1883 header.channels().findChannel("z") != nullptr);
1884}
1885
1886static bool exr_is_half_float(MultiPartInputFile &file)
1887{
1888 const ChannelList &channels = file.header(0).channels();
1889 for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
1890 const Channel &channel = i.channel();
1891 if (channel.type != HALF) {
1892 return false;
1893 }
1894 }
1895 return true;
1896}
1897
1898static bool imb_exr_is_multilayer_file(MultiPartInputFile &file)
1899{
1900 const ChannelList &channels = file.header(0).channels();
1901 std::set<std::string> layerNames;
1902
1903 /* This will not include empty layer names, so files with just R/G/B/A
1904 * channels without a layer name will be single layer. */
1905 channels.layers(layerNames);
1906
1907 return !layerNames.empty();
1908}
1909
1910static void imb_exr_type_by_channels(ChannelList &channels,
1911 StringVector &views,
1912 bool *r_singlelayer,
1913 bool *r_multilayer,
1914 bool *r_multiview)
1915{
1916 std::set<std::string> layerNames;
1917
1918 *r_singlelayer = true;
1919 *r_multilayer = *r_multiview = false;
1920
1921 /* will not include empty layer names */
1922 channels.layers(layerNames);
1923
1924 if (!views.empty() && !views[0].empty()) {
1925 *r_multiview = true;
1926 }
1927 else {
1928 *r_singlelayer = false;
1929 *r_multilayer = (layerNames.size() > 1);
1930 *r_multiview = false;
1931 return;
1932 }
1933
1934 if (!layerNames.empty()) {
1935 /* If `layerNames` is not empty, it means at least one layer is non-empty,
1936 * but it also could be layers without names in the file and such case
1937 * shall be considered a multi-layer EXR.
1938 *
1939 * That's what we do here: test whether there are empty layer names together
1940 * with non-empty ones in the file.
1941 */
1942 for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); i++) {
1943 for (const std::string &layer_name : layerNames) {
1944 /* see if any layer_name differs from a view_name. */
1945 if (imb_exr_get_multiView_id(views, layer_name) == -1) {
1946 std::string layerName = layer_name;
1947 size_t pos = layerName.rfind('.');
1948
1949 if (pos == std::string::npos) {
1950 *r_multilayer = true;
1951 *r_singlelayer = false;
1952 return;
1953 }
1954 }
1955 }
1956 }
1957 }
1958 else {
1959 *r_singlelayer = true;
1960 *r_multilayer = false;
1961 }
1962
1963 BLI_assert(r_singlelayer != r_multilayer);
1964}
1965
1966static bool exr_has_multiview(MultiPartInputFile &file)
1967{
1968 for (int p = 0; p < file.parts(); p++) {
1969 if (hasMultiView(file.header(p))) {
1970 return true;
1971 }
1972 }
1973
1974 return false;
1975}
1976
1977static bool exr_has_multipart_file(MultiPartInputFile &file)
1978{
1979 return file.parts() > 1;
1980}
1981
1982/* it returns true if the file is multilayer or multiview */
1983static bool imb_exr_is_multi(MultiPartInputFile &file)
1984{
1985 /* Multipart files are treated as multilayer in blender -
1986 * even if they are single layer openexr with multiview. */
1987 if (exr_has_multipart_file(file)) {
1988 return true;
1989 }
1990
1991 if (exr_has_multiview(file)) {
1992 return true;
1993 }
1994
1995 if (imb_exr_is_multilayer_file(file)) {
1996 return true;
1997 }
1998
1999 return false;
2000}
2001
2002bool IMB_exr_has_multilayer(void *handle)
2003{
2004 ExrHandle *data = (ExrHandle *)handle;
2005 return imb_exr_is_multi(*data->ifile);
2006}
2007
2008static bool imb_check_chromaticity_val(float test_v, float ref_v)
2009{
2010 const float tolerance_v = 0.000001f;
2011 return (test_v < (ref_v + tolerance_v)) && (test_v > (ref_v - tolerance_v));
2012}
2013
2014/* https://openexr.com/en/latest/TechnicalIntroduction.html#recommendations */
2015static bool imb_check_chromaticity_matches(const Imf::Chromaticities &a,
2016 const Imf::Chromaticities &b)
2017{
2018 return imb_check_chromaticity_val(a.red.x, b.red.x) &&
2019 imb_check_chromaticity_val(a.red.y, b.red.y) &&
2020 imb_check_chromaticity_val(a.green.x, b.green.x) &&
2021 imb_check_chromaticity_val(a.green.y, b.green.y) &&
2022 imb_check_chromaticity_val(a.blue.x, b.blue.x) &&
2023 imb_check_chromaticity_val(a.blue.y, b.blue.y) &&
2024 imb_check_chromaticity_val(a.white.x, b.white.x) &&
2025 imb_check_chromaticity_val(a.white.y, b.white.y);
2026}
2027
2028static void imb_exr_set_known_colorspace(const Header &header, ImFileColorSpace &r_colorspace)
2029{
2030 r_colorspace.is_hdr_float = true;
2031
2032 /* Read ACES container format metadata. */
2033 const IntAttribute *header_aces_container = header.findTypedAttribute<IntAttribute>(
2034 "acesImageContainerFlag");
2035 const ChromaticitiesAttribute *header_chromaticities =
2036 header.findTypedAttribute<ChromaticitiesAttribute>("chromaticities");
2037
2038 if ((header_aces_container && header_aces_container->value() == 1) ||
2039 (header_chromaticities &&
2040 imb_check_chromaticity_matches(header_chromaticities->value(), CHROMATICITIES_ACES_2065_1)))
2041 {
2042 const char *known_colorspace = IMB_colormanagement_role_colorspace_name_get(
2044 if (known_colorspace) {
2045 STRNCPY(r_colorspace.metadata_colorspace, known_colorspace);
2046 }
2047 }
2048 else if (header_chromaticities &&
2049 (imb_check_chromaticity_matches(header_chromaticities->value(), CHROMATICITIES_XYZ_E)))
2050 {
2051 /* Only works for the Blender default configuration due to fixed name. */
2052 STRNCPY(r_colorspace.metadata_colorspace, "Linear CIE-XYZ E");
2053 }
2054}
2055
2056static bool exr_get_ppm(MultiPartInputFile &file, double ppm[2])
2057{
2058 const Header &header = file.header(0);
2059 if (!hasXDensity(header)) {
2060 return false;
2061 }
2062 ppm[0] = double(xDensity(header)) / 0.0254;
2063 ppm[1] = ppm[0] * double(header.pixelAspectRatio());
2064 return true;
2065}
2066
2067bool IMB_exr_get_ppm(void *handle, double ppm[2])
2068{
2069 ExrHandle *data = (ExrHandle *)handle;
2070 return exr_get_ppm(*data->ifile, ppm);
2071}
2072
2073ImBuf *imb_load_openexr(const uchar *mem, size_t size, int flags, ImFileColorSpace &r_colorspace)
2074{
2075 ImBuf *ibuf = nullptr;
2076 IMemStream *membuf = nullptr;
2077 MultiPartInputFile *file = nullptr;
2078
2079 if (imb_is_a_openexr(mem, size) == 0) {
2080 return nullptr;
2081 }
2082
2083 try {
2084 bool is_multi;
2085
2086 membuf = new IMemStream((uchar *)mem, size);
2087 file = new MultiPartInputFile(*membuf);
2088
2089 const Header &file_header = file->header(0);
2090 Box2i dw = file_header.dataWindow();
2091 const size_t width = dw.max.x - dw.min.x + 1;
2092 const size_t height = dw.max.y - dw.min.y + 1;
2093
2094 // printf("OpenEXR-load: image data window %d %d %d %d\n",
2095 // dw.min.x, dw.min.y, dw.max.x, dw.max.y);
2096
2097 if (false) { /* debug */
2099 }
2100
2101 is_multi = imb_exr_is_multi(*file);
2102
2103 /* do not make an ibuf when */
2104 if (is_multi && !(flags & IB_test) && !(flags & IB_multilayer)) {
2105 printf("Error: can't process EXR multilayer file\n");
2106 }
2107 else {
2108 const bool is_alpha = exr_has_alpha(*file);
2109
2110 ibuf = IMB_allocImBuf(width, height, is_alpha ? 32 : 24, 0);
2111 ibuf->foptions.flag |= exr_is_half_float(*file) ? OPENEXR_HALF : 0;
2112 ibuf->foptions.flag |= openexr_header_get_compression(file_header);
2113
2114 exr_get_ppm(*file, ibuf->ppm);
2115
2116 imb_exr_set_known_colorspace(file_header, r_colorspace);
2117
2118 ibuf->ftype = IMB_FTYPE_OPENEXR;
2119
2120 if (!(flags & IB_test)) {
2121
2122 if (flags & IB_metadata) {
2123 Header::ConstIterator iter;
2124
2126 for (iter = file_header.begin(); iter != file_header.end(); iter++) {
2127 const StringAttribute *attr = file_header.findTypedAttribute<StringAttribute>(
2128 iter.name());
2129
2130 /* not all attributes are string attributes so we might get some NULLs here */
2131 if (attr) {
2132 IMB_metadata_set_field(ibuf->metadata, iter.name(), attr->value().c_str());
2133 ibuf->flags |= IB_metadata;
2134 }
2135 }
2136 }
2137
2138 /* Only enters with IB_multilayer flag set. */
2139 if (is_multi && ((flags & IB_thumbnail) == 0)) {
2140 /* constructs channels for reading, allocates memory in channels */
2141 ExrHandle *handle = imb_exr_begin_read_mem(*membuf, *file, width, height);
2142 if (handle) {
2143 IMB_exr_read_channels(handle);
2144 ibuf->userdata = handle; /* potential danger, the caller has to check for this! */
2145 }
2146 }
2147 else {
2148 const char *rgb_channels[3];
2149 const int num_rgb_channels = exr_has_rgb(*file, rgb_channels);
2150 const bool has_luma = exr_has_luma(*file);
2151 const bool has_xyz = exr_has_xyz(*file);
2152 FrameBuffer frameBuffer;
2153 float *first;
2154 size_t xstride = sizeof(float[4]);
2155 size_t ystride = -xstride * width;
2156
2157 /* No need to clear image memory, it will be fully written below. */
2158 IMB_alloc_float_pixels(ibuf, 4, false);
2159
2160 /* Inverse correct first pixel for data-window
2161 * coordinates (- dw.min.y because of y flip). */
2162 first = ibuf->float_buffer.data - 4 * (dw.min.x - dw.min.y * width);
2163 /* But, since we read y-flipped (negative y stride) we move to last scan-line. */
2164 first += 4 * (height - 1) * width;
2165
2166 if (num_rgb_channels > 0) {
2167 for (int i = 0; i < num_rgb_channels; i++) {
2168 frameBuffer.insert(exr_rgba_channelname(*file, rgb_channels[i]),
2169 Slice(Imf::FLOAT, (char *)(first + i), xstride, ystride));
2170 }
2171 }
2172 else if (has_xyz) {
2173 frameBuffer.insert(exr_rgba_channelname(*file, "X"),
2174 Slice(Imf::FLOAT, (char *)first, xstride, ystride));
2175 frameBuffer.insert(exr_rgba_channelname(*file, "Y"),
2176 Slice(Imf::FLOAT, (char *)(first + 1), xstride, ystride));
2177 frameBuffer.insert(exr_rgba_channelname(*file, "Z"),
2178 Slice(Imf::FLOAT, (char *)(first + 2), xstride, ystride));
2179 }
2180 else if (has_luma) {
2181 frameBuffer.insert(exr_rgba_channelname(*file, "Y"),
2182 Slice(Imf::FLOAT, (char *)first, xstride, ystride));
2183 frameBuffer.insert(
2184 exr_rgba_channelname(*file, "BY"),
2185 Slice(Imf::FLOAT, (char *)(first + 1), xstride, ystride, 1, 1, 0.5f));
2186 frameBuffer.insert(
2187 exr_rgba_channelname(*file, "RY"),
2188 Slice(Imf::FLOAT, (char *)(first + 2), xstride, ystride, 1, 1, 0.5f));
2189 }
2190
2191 /* 1.0 is fill value, this still needs to be assigned even when (is_alpha == 0) */
2192 frameBuffer.insert(exr_rgba_channelname(*file, "A"),
2193 Slice(Imf::FLOAT, (char *)(first + 3), xstride, ystride, 1, 1, 1.0f));
2194
2195 InputPart in(*file, 0);
2196 in.setFrameBuffer(frameBuffer);
2197 in.readPixels(dw.min.y, dw.max.y);
2198
2199 /* XXX, ImBuf has no nice way to deal with this.
2200 * ideally IM_rect would be used when the caller wants a rect BUT
2201 * at the moment all functions use IM_rect.
2202 * Disabling this is ok because all functions should check
2203 * if a rect exists and create one on demand.
2204 *
2205 * Disabling this because the sequencer frees immediate. */
2206#if 0
2207 if (flag & IM_rect) {
2208 IMB_byte_from_float(ibuf);
2209 }
2210#endif
2211
2212 if (num_rgb_channels == 0 && has_luma && exr_has_chroma(*file)) {
2213 for (size_t a = 0; a < size_t(ibuf->x) * ibuf->y; a++) {
2214 float *color = ibuf->float_buffer.data + a * 4;
2215 ycc_to_rgb(color[0] * 255.0f,
2216 color[1] * 255.0f,
2217 color[2] * 255.0f,
2218 &color[0],
2219 &color[1],
2220 &color[2],
2222 }
2223 }
2224 else if (!has_xyz && num_rgb_channels <= 1) {
2225 /* Convert 1 to 3 channels. */
2226 for (size_t a = 0; a < size_t(ibuf->x) * ibuf->y; a++) {
2227 float *color = ibuf->float_buffer.data + a * 4;
2228 color[1] = color[0];
2229 color[2] = color[0];
2230 }
2231 }
2232
2233 /* file is no longer needed */
2234 delete membuf;
2235 delete file;
2236 }
2237 }
2238 else {
2239 delete membuf;
2240 delete file;
2241 }
2242
2243 if (flags & IB_alphamode_detect) {
2244 ibuf->flags |= IB_alphamode_premul;
2245 }
2246 }
2247 return ibuf;
2248 }
2249 catch (const std::exception &exc) {
2250 std::cerr << exc.what() << std::endl;
2251 if (ibuf) {
2252 IMB_freeImBuf(ibuf);
2253 }
2254 delete file;
2255 delete membuf;
2256
2257 return nullptr;
2258 }
2259 catch (...) { /* Catch-all for edge cases or compiler bugs. */
2260 std::cerr << "OpenEXR-Load: UNKNOWN ERROR" << std::endl;
2261 if (ibuf) {
2262 IMB_freeImBuf(ibuf);
2263 }
2264 delete file;
2265 delete membuf;
2266
2267 return nullptr;
2268 }
2269}
2270
2272 const int /*flags*/,
2273 const size_t max_thumb_size,
2274 ImFileColorSpace &r_colorspace,
2275 size_t *r_width,
2276 size_t *r_height)
2277{
2278 ImBuf *ibuf = nullptr;
2279 IStream *stream = nullptr;
2280 Imf::RgbaInputFile *file = nullptr;
2281
2282 /* OpenExr uses exceptions for error-handling. */
2283 try {
2284
2285 /* The memory-mapped stream is faster, but don't use for huge files as it requires contiguous
2286 * address space and we are processing multiple files at once (typically one per processor
2287 * core). The 100 MB limit here is arbitrary, but seems reasonable and conservative. */
2288 if (BLI_file_size(filepath) < 100 * 1024 * 1024) {
2289 stream = new IMMapStream(filepath);
2290 }
2291 else {
2292 stream = new IFileStream(filepath);
2293 }
2294
2295 /* imb_initopenexr() creates a global pool of worker threads. But we thumbnail multiple images
2296 * at once, and by default each file will attempt to use the entire pool for itself, stalling
2297 * the others. So each thumbnail should use a single thread of the pool. */
2298 file = new RgbaInputFile(*stream, 1);
2299
2300 if (!file->isComplete()) {
2301 delete file;
2302 delete stream;
2303 return nullptr;
2304 }
2305
2306 Imath::Box2i dw = file->dataWindow();
2307 int source_w = dw.max.x - dw.min.x + 1;
2308 int source_h = dw.max.y - dw.min.y + 1;
2309 *r_width = source_w;
2310 *r_height = source_h;
2311
2312 const Header &file_header = file->header();
2313
2314 /* If there is an embedded thumbnail, return that instead of making a new one. */
2315 if (file_header.hasPreviewImage()) {
2316 const Imf::PreviewImage &preview = file->header().previewImage();
2317 ImBuf *ibuf = IMB_allocFromBuffer(
2318 (uint8_t *)preview.pixels(), nullptr, preview.width(), preview.height(), 4);
2319 delete file;
2320 delete stream;
2321 IMB_flipy(ibuf);
2322 return ibuf;
2323 }
2324
2325 /* No effect yet for thumbnails, but will work once it is supported. */
2326 imb_exr_set_known_colorspace(file_header, r_colorspace);
2327
2328 /* Create a new thumbnail. */
2329 float scale_factor = std::min(float(max_thumb_size) / float(source_w),
2330 float(max_thumb_size) / float(source_h));
2331 int dest_w = std::max(int(source_w * scale_factor), 1);
2332 int dest_h = std::max(int(source_h * scale_factor), 1);
2333
2334 ibuf = IMB_allocImBuf(dest_w, dest_h, 32, IB_float_data);
2335
2336 /* A single row of source pixels. */
2337 Imf::Array<Imf::Rgba> pixels(source_w);
2338
2339 /* Loop through destination thumbnail rows. */
2340 for (int h = 0; h < dest_h; h++) {
2341
2342 /* Load the single source row that corresponds with destination row. */
2343 int source_y = int(float(h) / scale_factor) + dw.min.y;
2344 file->setFrameBuffer(&pixels[0] - dw.min.x - source_y * source_w, 1, source_w);
2345 file->readPixels(source_y);
2346
2347 for (int w = 0; w < dest_w; w++) {
2348 /* For each destination pixel find single corresponding source pixel. */
2349 int source_x = int(std::min<int>((w / scale_factor), dw.max.x - 1));
2350 float *dest_px = &ibuf->float_buffer.data[(h * dest_w + w) * 4];
2351 dest_px[0] = pixels[source_x].r;
2352 dest_px[1] = pixels[source_x].g;
2353 dest_px[2] = pixels[source_x].b;
2354 dest_px[3] = pixels[source_x].a;
2355 }
2356 }
2357
2358 if (file->lineOrder() == INCREASING_Y) {
2359 IMB_flipy(ibuf);
2360 }
2361
2362 delete file;
2363 delete stream;
2364
2365 return ibuf;
2366 }
2367
2368 catch (const std::exception &exc) {
2369 std::cerr << exc.what() << std::endl;
2370 if (ibuf) {
2371 IMB_freeImBuf(ibuf);
2372 }
2373
2374 delete file;
2375 delete stream;
2376 return nullptr;
2377 }
2378 catch (...) { /* Catch-all for edge cases or compiler bugs. */
2379 std::cerr << "OpenEXR-Thumbnail: UNKNOWN ERROR" << std::endl;
2380 if (ibuf) {
2381 IMB_freeImBuf(ibuf);
2382 }
2383
2384 delete file;
2385 delete stream;
2386 return nullptr;
2387 }
2388
2389 return nullptr;
2390}
2391
2393{
2394 /* In a multithreaded program, staticInitialize() must be called once during startup, before the
2395 * program accesses any other functions or classes in the IlmImf library. */
2396 Imf::staticInitialize();
2397 Imf::setGlobalThreadCount(BLI_system_thread_count());
2398}
2399
2401{
2402 /* Tells OpenEXR to free thread pool, also ensures there is no running tasks. */
2403 Imf::setGlobalThreadCount(0);
2404}
#define IDP_String(prop)
void BKE_stamp_info_callback(void *data, StampData *stamp_data, StampCallback callback, bool noskip)
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:373
#define O_BINARY
size_t BLI_file_size(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:239
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
void BLI_kdtree_nd_ free(KDTree *tree)
#define LISTBASE_FOREACH(type, var, list)
void * BLI_findstring(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:608
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void void BLI_freelistN(ListBase *listbase) ATTR_NONNULL(1)
Definition listbase.cc:497
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
void BLI_addhead(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:91
void * BLI_rfindstring(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:626
MINLINE float clamp_f(float value, float min, float max)
void ycc_to_rgb(float y, float cb, float cr, float *r_r, float *r_g, float *r_b, int colorspace)
float srgb_to_linearrgb(float c)
#define BLI_YCC_ITU_BT709
void * BLI_mmap_get_pointer(BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:212
void BLI_mmap_free(BLI_mmap_file *file) ATTR_NONNULL(1)
Definition BLI_mmap.cc:227
BLI_mmap_file * BLI_mmap_open(int fd) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:133
size_t BLI_mmap_get_length(const BLI_mmap_file *file) ATTR_WARN_UNUSED_RESULT
Definition BLI_mmap.cc:217
#define FILE_MAX
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char BLI_toupper_ascii(const char c) ATTR_WARN_UNUSED_RESULT
Definition string.cc:961
int bool bool bool size_t size_t BLI_str_rpartition(const char *str, const char delim[], const char **sep, const char **suf) ATTR_NONNULL(1
int char char int BLI_strcasecmp(const char *s1, const char *s2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
int bool bool bool size_t size_t size_t BLI_str_partition_ex(const char *str, const char *end, const char delim[], const char **sep, const char **suf, bool from_right) ATTR_NONNULL(1
size_t BLI_snprintf(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
int BLI_strcaseeq(const char *a, const char *b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
unsigned char uchar
unsigned int uint
int BLI_system_thread_count(void)
Definition threads.cc:253
#define STRPREFIX(a, b)
#define ELEM(...)
#define STREQ(a, b)
@ IDP_STRING
@ R_IMF_EXR_CODEC_NONE
@ R_IMF_EXR_CODEC_B44
@ R_IMF_EXR_CODEC_PXR24
@ R_IMF_EXR_CODEC_PIZ
@ R_IMF_EXR_CODEC_DWAB
@ R_IMF_EXR_CODEC_DWAA
@ R_IMF_EXR_CODEC_RLE
@ R_IMF_EXR_CODEC_B44A
@ R_IMF_EXR_CODEC_ZIP
@ R_IMF_EXR_CODEC_ZIPS
static AppView * view
const char * IMB_colormanagement_colorspace_get_name(const ColorSpace *colorspace)
@ COLOR_ROLE_ACES_INTERCHANGE
const char * IMB_colormanagement_role_colorspace_name_get(int role)
blender::ocio::ColorSpace ColorSpace
void IMB_flipy(ImBuf *ibuf)
ImBuf * IMB_allocFromBuffer(const uint8_t *byte_buffer, const float *float_buffer, unsigned int w, unsigned int h, unsigned int channels)
void IMB_byte_from_float(ImBuf *ibuf)
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
bool IMB_alloc_float_pixels(ImBuf *ibuf, const unsigned int channels, bool initialize_pixels=true)
@ IMB_FTYPE_OPENEXR
#define OPENEXR_CODEC_MASK
@ IB_float_data
@ IB_alphamode_premul
@ IB_metadata
@ IB_multilayer
@ IB_alphamode_detect
@ IB_thumbnail
@ 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
#define EXR_LAY_MAXNAME
#define EXR_VIEW_MAXNAME
#define EXR_PASS_MAXNAME
#define EXR_TOT_MAXNAME
#define EXR_PASS_MAXCHAN
Read Guarded memory(de)allocation.
void imb_mmap_lock()
Definition allocimbuf.cc:46
bool imb_enlargeencodedbufferImBuf(ImBuf *ibuf)
void imb_mmap_unlock()
Definition allocimbuf.cc:51
bool imb_addencodedbufferImBuf(ImBuf *ibuf)
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
void clear() override
void seekg(exr_file_offset_t pos) override
bool read(char c[], int n) override
exr_file_offset_t tellg() override
IFileStream(const char *filepath)
~IMMapStream() override
void seekg(exr_file_offset_t pos) override
IMMapStream(const char *filepath)
exr_file_offset_t tellg() override
bool read(char c[], int n) override
IMemStream(uchar *exrbuf, size_t exrsize)
void seekg(exr_file_offset_t pos) override
bool read(char c[], int n) override
void clear() override
exr_file_offset_t tellg() override
OFileStream(const char *filepath)
exr_file_offset_t tellp() override
void seekp(exr_file_offset_t pos) override
void write(const char c[], int n) override
void write(const char c[], int n) override
void seekp(exr_file_offset_t pos) override
OMemStream(ImBuf *ibuf_)
exr_file_offset_t tellp() override
constexpr bool is_empty() const
constexpr bool startswith(StringRef prefix) const
constexpr bool endswith(StringRef suffix) const
constexpr StringRef drop_known_suffix(StringRef suffix) const
Definition half.h:41
@ HALF
#define offsetof(t, d)
#define str(s)
uint pos
#define in
float half
#define printf(...)
#define OPENEXR_HALF
int count
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
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
static bool parse_channels(const ImageSpec &in_spec, vector< MergeImageLayer > &layers, string &error)
Definition merge.cpp:133
T clamp(const T &a, const T &min, const T &max)
T safe_divide(const T &a, const T &b)
static Imf::Chromaticities CHROMATICITIES_ACES_2065_1
static bool imb_exr_multilayer_parse_channels_from_file(ExrHandle *data)
static bool exr_has_multiview(MultiPartInputFile &file)
static std::vector< MultiViewChannelName > exr_channels_in_multi_part_file(const MultiPartInputFile &file)
static void exr_printf(const char *__restrict fmt,...)
void IMB_exr_add_view(void *handle, const char *name)
static int imb_exr_split_channel_name(ExrChannel *echan, char *layname, char *passname, bool has_xyz_channels)
static bool exr_has_xyz(MultiPartInputFile &file)
static void openexr_header_metadata_callback(void *data, const char *propname, char *prop, int)
static int imb_exr_get_multiView_id(StringVector &views, const std::string &name)
bool IMB_exr_has_multilayer(void *handle)
static struct ExrPass * imb_exr_get_pass(ListBase *lb, const char *passname)
void * IMB_exr_get_handle_name(const char *name)
bool IMB_exr_begin_write(void *handle, const char *filepath, int width, int height, const double ppm[2], int compress, int quality, const StampData *stamp)
static bool imb_save_openexr_float(ImBuf *ibuf, const char *filepath, const int flags)
void IMB_exr_close(void *handle)
void imb_initopenexr()
bool IMB_exr_get_ppm(void *handle, double ppm[2])
static ExrHandle * imb_exr_begin_read_mem(IStream &file_stream, MultiPartInputFile &file, int width, int height)
static bool exr_is_half_float(MultiPartInputFile &file)
static void imb_exr_pass_name_from_channel(char *passname, const ExrChannel *echan, const char *channelname, const bool has_xyz_channels)
static bool imb_exr_is_multi(MultiPartInputFile &file)
static bool exr_has_luma(MultiPartInputFile &file)
ImBuf * imb_load_filepath_thumbnail_openexr(const char *filepath, const int, const size_t max_thumb_size, ImFileColorSpace &r_colorspace, size_t *r_width, size_t *r_height)
_RGBAZ RGBAZ
static int exr_has_rgb(MultiPartInputFile &file, const char *rgb_channels[3])
static bool exr_get_ppm(MultiPartInputFile &file, double ppm[2])
bool IMB_exr_begin_read(void *handle, const char *filepath, int *width, int *height, const bool parse_channels)
static bool imb_check_chromaticity_matches(const Imf::Chromaticities &a, const Imf::Chromaticities &b)
static int imb_exr_split_token(const char *str, const char *end, const char **token)
static void imb_exr_set_known_colorspace(const Header &header, ImFileColorSpace &r_colorspace)
void imb_exitopenexr()
static int openexr_jpg_like_quality_to_dwa_quality(int q)
#define exr_file_offset_t
void IMB_exr_add_channel(void *handle, const char *layname, const char *passname, const char *viewname, int xstride, int ystride, float *rect, bool use_half_float)
void IMB_exr_write_channels(void *handle)
static bool exr_has_chroma(MultiPartInputFile &file)
static bool imb_exr_is_multilayer_file(MultiPartInputFile &file)
bool imb_save_openexr(ImBuf *ibuf, const char *filepath, int flags)
bool imb_is_a_openexr(const uchar *mem, const size_t size)
static void imb_exr_insert_view_name(char name_full[EXR_TOT_MAXNAME+1], const char *passname, const char *viewname)
static ExrLayer * imb_exr_get_layer(ListBase *lb, const char *layname)
void IMB_exr_read_channels(void *handle)
static half float_to_half_safe(const float value)
ImBuf * imb_load_openexr(const uchar *mem, size_t size, int flags, ImFileColorSpace &r_colorspace)
static void openexr_header_compression(Header *header, int compression, int quality)
static bool imb_check_chromaticity_val(float test_v, float ref_v)
static ListBase exrhandles
static bool exr_has_multipart_file(MultiPartInputFile &file)
static void exr_print_filecontents(MultiPartInputFile &file)
static void imb_exr_type_by_channels(ChannelList &channels, StringVector &views, bool *r_singlelayer, bool *r_multilayer, bool *r_multiview)
static bool exr_has_xyz_channels(ExrHandle *exr_handle)
static int openexr_header_get_compression(const Header &header)
bool IMB_exr_set_channel(void *handle, const char *layname, const char *passname, int xstride, int ystride, float *rect)
static Imf::Chromaticities CHROMATICITIES_XYZ_E
static void imb_exr_get_views(MultiPartInputFile &file, StringVector &views)
static bool imb_save_openexr_half(ImBuf *ibuf, const char *filepath, const int flags)
void IMB_exr_multilayer_convert(void *handle, void *base, void *(*addview)(void *base, const char *str), void *(*addlayer)(void *base, const char *str), void(*addpass)(void *base, void *lay, const char *str, float *rect, int totchan, const char *chan_id, const char *view))
static void openexr_header_metadata(Header *header, ImBuf *ibuf)
static const char * exr_rgba_channelname(MultiPartInputFile &file, const char *chan)
void * IMB_exr_get_handle()
static bool exr_has_alpha(MultiPartInputFile &file)
static void imb_exr_pass_name_from_channel_name(char *passname, const ExrChannel *, const char *channelname, const bool)
bool use_half_float
ExrChannel * prev
MultiViewChannelName * m
char name[EXR_TOT_MAXNAME+1]
ExrChannel * next
OutputFile * ofile
int num_half_channels
IStream * ifile_stream
OFileStream * ofile_stream
StringVector * multiView
ListBase channels
ExrHandle * prev
MultiPartInputFile * ifile
ListBase layers
MultiPartOutputFile * mpofile
char name[FILE_MAX]
ExrHandle * next
ExrLayer * next
ExrLayer * prev
ListBase passes
char name[EXR_LAY_MAXNAME+1]
char chan_id[EXR_PASS_MAXCHAN]
ExrPass * prev
float * rect
char view[EXR_VIEW_MAXNAME]
char name[EXR_PASS_MAXNAME]
ExrPass * next
char internal_name[EXR_PASS_MAXNAME]
ExrChannel * chan[EXR_PASS_MAXCHAN]
ListBase group
Definition DNA_ID.h:138
char name[64]
Definition DNA_ID.h:154
IDPropertyData data
Definition DNA_ID.h:159
const ColorSpace * colorspace
const ColorSpace * colorspace
void * userdata
ImBufFloatBuffer float_buffer
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
unsigned char planes
enum eImbFileType ftype
unsigned int encoded_buffer_size
IDProperty * metadata
double ppm[2]
unsigned int encoded_size
char metadata_colorspace[IM_MAX_SPACE]
i
Definition text_draw.cc:230
wchar_t * alloc_utf16_from_8(const char *in8, size_t add)
Definition utfconv.cc:292
uint len
uint8_t flag
Definition wm_window.cc:139