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