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