1#ifndef TINYEXR_H_
2#define TINYEXR_H_
3/*
4Copyright (c) 2014 - 2021, Syoyo Fujita and many contributors.
5All rights reserved.
6
7Redistribution and use in source and binary forms, with or without
8modification, are permitted provided that the following conditions are met:
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in the
13 documentation and/or other materials provided with the distribution.
14 * Neither the name of the Syoyo Fujita nor the
15 names of its contributors may be used to endorse or promote products
16 derived from this software without specific prior written permission.
17
18THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*/
29
30// TinyEXR contains some OpenEXR code, which is licensed under ------------
31
32///////////////////////////////////////////////////////////////////////////
33//
34// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
35// Digital Ltd. LLC
36//
37// All rights reserved.
38//
39// Redistribution and use in source and binary forms, with or without
40// modification, are permitted provided that the following conditions are
41// met:
42// * Redistributions of source code must retain the above copyright
43// notice, this list of conditions and the following disclaimer.
44// * Redistributions in binary form must reproduce the above
45// copyright notice, this list of conditions and the following disclaimer
46// in the documentation and/or other materials provided with the
47// distribution.
48// * Neither the name of Industrial Light & Magic nor the names of
49// its contributors may be used to endorse or promote products derived
50// from this software without specific prior written permission.
51//
52// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
53// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
54// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
55// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
56// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
57// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
58// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
59// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
60// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
61// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
62// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63//
64///////////////////////////////////////////////////////////////////////////
65
66// End of OpenEXR license -------------------------------------------------
67
68
69//
70//
71// Do this:
72// #define TINYEXR_IMPLEMENTATION
73// before you include this file in *one* C or C++ file to create the
74// implementation.
75//
76// // i.e. it should look like this:
77// #include ...
78// #include ...
79// #include ...
80// #define TINYEXR_IMPLEMENTATION
81// #include "tinyexr.h"
82//
83//
84
85#include <stddef.h> // for size_t
86#include <stdint.h> // guess stdint.h is available(C99)
87
88#ifdef __cplusplus
89extern "C" {
90#endif
91
92#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \
93 defined(__i386) || defined(__i486__) || defined(__i486) || \
94 defined(i386) || defined(__ia64__) || defined(__x86_64__)
95#define TINYEXR_X86_OR_X64_CPU 1
96#else
97#define TINYEXR_X86_OR_X64_CPU 0
98#endif
99
100#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || TINYEXR_X86_OR_X64_CPU
101#define TINYEXR_LITTLE_ENDIAN 1
102#else
103#define TINYEXR_LITTLE_ENDIAN 0
104#endif
105
106// Use miniz or not to decode ZIP format pixel. Linking with zlib
107// required if this flag is 0 and TINYEXR_USE_STB_ZLIB is 0.
108#ifndef TINYEXR_USE_MINIZ
109#define TINYEXR_USE_MINIZ (1)
110#endif
111
112// Use the ZIP implementation of stb_image.h and stb_image_write.h.
113#ifndef TINYEXR_USE_STB_ZLIB
114#define TINYEXR_USE_STB_ZLIB (0)
115#endif
116
117// Use nanozlib.
118#ifndef TINYEXR_USE_NANOZLIB
119#define TINYEXR_USE_NANOZLIB (0)
120#endif
121
122// Disable PIZ compression when applying cpplint.
123#ifndef TINYEXR_USE_PIZ
124#define TINYEXR_USE_PIZ (1)
125#endif
126
127#ifndef TINYEXR_USE_ZFP
128#define TINYEXR_USE_ZFP (0) // TinyEXR extension.
129// http://computation.llnl.gov/projects/floating-point-compression
130#endif
131
132#ifndef TINYEXR_USE_THREAD
133#define TINYEXR_USE_THREAD (0) // No threaded loading.
134#else
135// When using threading a reduced custom upperbound can be specified by setting TINYEXR_MAX_THREADS
136#ifndef TINYEXR_MAX_THREADS // if not defined define it as 0 meaning upper limit is taken from hardware_concurrency()
137#define TINYEXR_MAX_THREADS (0)
138#endif
139#endif
140
141#ifndef TINYEXR_USE_OPENMP
142#ifdef _OPENMP
143#define TINYEXR_USE_OPENMP (1)
144#else
145#define TINYEXR_USE_OPENMP (0)
146#endif
147#endif
148
149#ifndef TINYEXR_USE_COMPILER_FP16
150#define TINYEXR_USE_COMPILER_FP16 (0)
151#endif
152
153#if TINYEXR_USE_COMPILER_FP16
154#ifndef _MSC_VER
155#if defined( __GNUC__ ) || defined( __clang__ )
156#if defined( __SSE2__ )
157#if ( __GNUC__ > 11 ) || ( __clang_major__ > 14 )
158#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
159#define __STDC_WANT_IEC_60559_TYPES_EXT__
160#endif
161#include <float.h>
162#include <math.h>
163#define TINYEXR_FP16_COMPILER_TYPE _Float16
164#endif
165#endif
166#if defined( __ARM_NEON__ ) || defined( __ARM_NEON )
167#define TINYEXR_FP16_COMPILER_TYPE __fp16
168#endif
169#endif
170#else
171#if (defined(_M_IX86) || defined(_M_X64)) && defined(__AVX2__)
172#include <intrin.h>
173#define TINYEXR_FP16_COMPILER_TYPE uint16_t
174#endif
175#endif
176#endif
177
178#ifdef TINYEXR_FP16_COMPILER_TYPE
179#define TINYEXR_HAS_FP16_COMPILER_TYPE (1)
180#else
181#define TINYEXR_HAS_FP16_COMPILER_TYPE (0)
182#endif
183
184#define TINYEXR_SUCCESS (0)
185#define TINYEXR_ERROR_INVALID_MAGIC_NUMBER (-1)
186#define TINYEXR_ERROR_INVALID_EXR_VERSION (-2)
187#define TINYEXR_ERROR_INVALID_ARGUMENT (-3)
188#define TINYEXR_ERROR_INVALID_DATA (-4)
189#define TINYEXR_ERROR_INVALID_FILE (-5)
190#define TINYEXR_ERROR_INVALID_PARAMETER (-6)
191#define TINYEXR_ERROR_CANT_OPEN_FILE (-7)
192#define TINYEXR_ERROR_UNSUPPORTED_FORMAT (-8)
193#define TINYEXR_ERROR_INVALID_HEADER (-9)
194#define TINYEXR_ERROR_UNSUPPORTED_FEATURE (-10)
195#define TINYEXR_ERROR_CANT_WRITE_FILE (-11)
196#define TINYEXR_ERROR_SERIALIZATION_FAILED (-12)
197#define TINYEXR_ERROR_LAYER_NOT_FOUND (-13)
198#define TINYEXR_ERROR_DATA_TOO_LARGE (-14)
199
200// @note { OpenEXR file format: http://www.openexr.com/openexrfilelayout.pdf }
201
202// pixel type: possible values are: UINT = 0 HALF = 1 FLOAT = 2
203#define TINYEXR_PIXELTYPE_UINT (0)
204#define TINYEXR_PIXELTYPE_HALF (1)
205#define TINYEXR_PIXELTYPE_FLOAT (2)
206
207#define TINYEXR_MAX_HEADER_ATTRIBUTES (1024)
208#define TINYEXR_MAX_CUSTOM_ATTRIBUTES (128)
209
210#define TINYEXR_COMPRESSIONTYPE_NONE (0)
211#define TINYEXR_COMPRESSIONTYPE_RLE (1)
212#define TINYEXR_COMPRESSIONTYPE_ZIPS (2)
213#define TINYEXR_COMPRESSIONTYPE_ZIP (3)
214#define TINYEXR_COMPRESSIONTYPE_PIZ (4)
215#define TINYEXR_COMPRESSIONTYPE_ZFP (128) // TinyEXR extension
216
217#define TINYEXR_ZFP_COMPRESSIONTYPE_RATE (0)
218#define TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION (1)
219#define TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY (2)
220
221#define TINYEXR_TILE_ONE_LEVEL (0)
222#define TINYEXR_TILE_MIPMAP_LEVELS (1)
223#define TINYEXR_TILE_RIPMAP_LEVELS (2)
224
225#define TINYEXR_TILE_ROUND_DOWN (0)
226#define TINYEXR_TILE_ROUND_UP (1)
227
228typedef struct TEXRVersion {
229 int version; // this must be 2
230 // tile format image;
231 // not zero for only a single-part "normal" tiled file (according to spec.)
232 int tiled;
233 int long_name; // long name attribute
234 // deep image(EXR 2.0);
235 // for a multi-part file, indicates that at least one part is of type deep* (according to spec.)
236 int non_image;
237 int multipart; // multi-part(EXR 2.0)
238} EXRVersion;
239
240typedef struct TEXRAttribute {
241 char name[256]; // name and type are up to 255 chars long.
242 char type[256];
243 unsigned char *value; // uint8_t*
244 int size;
245 int pad0;
246} EXRAttribute;
247
248typedef struct TEXRChannelInfo {
249 char name[256]; // less than 255 bytes long
250 int pixel_type;
251 int x_sampling;
252 int y_sampling;
253 unsigned char p_linear;
254 unsigned char pad[3];
255} EXRChannelInfo;
256
257typedef struct TEXRTile {
258 int offset_x;
259 int offset_y;
260 int level_x;
261 int level_y;
262
263 int width; // actual width in a tile.
264 int height; // actual height int a tile.
265
266 unsigned char **images; // image[channels][pixels]
267} EXRTile;
268
269typedef struct TEXRBox2i {
270 int min_x;
271 int min_y;
272 int max_x;
273 int max_y;
274} EXRBox2i;
275
276typedef struct TEXRHeader {
277 float pixel_aspect_ratio;
278 int line_order;
279 EXRBox2i data_window;
280 EXRBox2i display_window;
281 float screen_window_center[2];
282 float screen_window_width;
283
284 int chunk_count;
285
286 // Properties for tiled format(`tiledesc`).
287 int tiled;
288 int tile_size_x;
289 int tile_size_y;
290 int tile_level_mode;
291 int tile_rounding_mode;
292
293 int long_name;
294 // for a single-part file, agree with the version field bit 11
295 // for a multi-part file, it is consistent with the type of part
296 int non_image;
297 int multipart;
298 unsigned int header_len;
299
300 // Custom attributes(exludes required attributes(e.g. `channels`,
301 // `compression`, etc)
302 int num_custom_attributes;
303 EXRAttribute *custom_attributes; // array of EXRAttribute. size =
304 // `num_custom_attributes`.
305
306 EXRChannelInfo *channels; // [num_channels]
307
308 int *pixel_types; // Loaded pixel type(TINYEXR_PIXELTYPE_*) of `images` for
309 // each channel. This is overwritten with `requested_pixel_types` when
310 // loading.
311 int num_channels;
312
313 int compression_type; // compression type(TINYEXR_COMPRESSIONTYPE_*)
314 int *requested_pixel_types; // Filled initially by
315 // ParseEXRHeaderFrom(Meomory|File), then users
316 // can edit it(only valid for HALF pixel type
317 // channel)
318 // name attribute required for multipart files;
319 // must be unique and non empty (according to spec.);
320 // use EXRSetNameAttr for setting value;
321 // max 255 character allowed - excluding terminating zero
322 char name[256];
323} EXRHeader;
324
325typedef struct TEXRMultiPartHeader {
326 int num_headers;
327 EXRHeader *headers;
328
329} EXRMultiPartHeader;
330
331typedef struct TEXRImage {
332 EXRTile *tiles; // Tiled pixel data. The application must reconstruct image
333 // from tiles manually. NULL if scanline format.
334 struct TEXRImage* next_level; // NULL if scanline format or image is the last level.
335 int level_x; // x level index
336 int level_y; // y level index
337
338 unsigned char **images; // image[channels][pixels]. NULL if tiled format.
339
340 int width;
341 int height;
342 int num_channels;
343
344 // Properties for tile format.
345 int num_tiles;
346
347} EXRImage;
348
349typedef struct TEXRMultiPartImage {
350 int num_images;
351 EXRImage *images;
352
353} EXRMultiPartImage;
354
355typedef struct TDeepImage {
356 const char **channel_names;
357 float ***image; // image[channels][scanlines][samples]
358 int **offset_table; // offset_table[scanline][offsets]
359 int num_channels;
360 int width;
361 int height;
362 int pad0;
363} DeepImage;
364
365// @deprecated { For backward compatibility. Not recommended to use. }
366// Loads single-frame OpenEXR image. Assume EXR image contains A(single channel
367// alpha) or RGB(A) channels.
368// Application must free image data as returned by `out_rgba`
369// Result image format is: float x RGBA x width x hight
370// Returns negative value and may set error string in `err` when there's an
371// error
372extern int LoadEXR(float **out_rgba, int *width, int *height,
373 const char *filename, const char **err);
374
375// Loads single-frame OpenEXR image by specifying layer name. Assume EXR image
376// contains A(single channel alpha) or RGB(A) channels. Application must free
377// image data as returned by `out_rgba` Result image format is: float x RGBA x
378// width x hight Returns negative value and may set error string in `err` when
379// there's an error When the specified layer name is not found in the EXR file,
380// the function will return `TINYEXR_ERROR_LAYER_NOT_FOUND`.
381extern int LoadEXRWithLayer(float **out_rgba, int *width, int *height,
382 const char *filename, const char *layer_name,
383 const char **err);
384
385//
386// Get layer infos from EXR file.
387//
388// @param[out] layer_names List of layer names. Application must free memory
389// after using this.
390// @param[out] num_layers The number of layers
391// @param[out] err Error string(will be filled when the function returns error
392// code). Free it using FreeEXRErrorMessage after using this value.
393//
394// @return TINYEXR_SUCCEES upon success.
395//
396extern int EXRLayers(const char *filename, const char **layer_names[],
397 int *num_layers, const char **err);
398
399// @deprecated
400// Simple wrapper API for ParseEXRHeaderFromFile.
401// checking given file is a EXR file(by just look up header)
402// @return TINYEXR_SUCCEES for EXR image, TINYEXR_ERROR_INVALID_HEADER for
403// others
404extern int IsEXR(const char *filename);
405
406// Simple wrapper API for ParseEXRHeaderFromMemory.
407// Check if given data is a EXR image(by just looking up a header section)
408// @return TINYEXR_SUCCEES for EXR image, TINYEXR_ERROR_INVALID_HEADER for
409// others
410extern int IsEXRFromMemory(const unsigned char *memory, size_t size);
411
412// @deprecated
413// Saves single-frame OpenEXR image to a buffer. Assume EXR image contains RGB(A) channels.
414// components must be 1(Grayscale), 3(RGB) or 4(RGBA).
415// Input image format is: `float x width x height`, or `float x RGB(A) x width x
416// hight`
417// Save image as fp16(HALF) format when `save_as_fp16` is positive non-zero
418// value.
419// Save image as fp32(FLOAT) format when `save_as_fp16` is 0.
420// Use ZIP compression by default.
421// `buffer` is the pointer to write EXR data.
422// Memory for `buffer` is allocated internally in SaveEXRToMemory.
423// Returns the data size of EXR file when the value is positive(up to 2GB EXR data).
424// Returns negative value and may set error string in `err` when there's an
425// error
426extern int SaveEXRToMemory(const float *data, const int width, const int height,
427 const int components, const int save_as_fp16,
428 unsigned char **buffer, const char **err);
429
430// @deprecated { Not recommended, but handy to use. }
431// Saves single-frame OpenEXR image to a buffer. Assume EXR image contains RGB(A) channels.
432// components must be 1(Grayscale), 3(RGB) or 4(RGBA).
433// Input image format is: `float x width x height`, or `float x RGB(A) x width x
434// hight`
435// Save image as fp16(HALF) format when `save_as_fp16` is positive non-zero
436// value.
437// Save image as fp32(FLOAT) format when `save_as_fp16` is 0.
438// Use ZIP compression by default.
439// Returns TINYEXR_SUCCEES(0) when success.
440// Returns negative value and may set error string in `err` when there's an
441// error
442extern int SaveEXR(const float *data, const int width, const int height,
443 const int components, const int save_as_fp16,
444 const char *filename, const char **err);
445
446// Returns the number of resolution levels of the image (including the base)
447extern int EXRNumLevels(const EXRImage* exr_image);
448
449// Initialize EXRHeader struct
450extern void InitEXRHeader(EXRHeader *exr_header);
451
452// Set name attribute of EXRHeader struct (it makes a copy)
453extern void EXRSetNameAttr(EXRHeader *exr_header, const char* name);
454
455// Initialize EXRImage struct
456extern void InitEXRImage(EXRImage *exr_image);
457
458// Frees internal data of EXRHeader struct
459extern int FreeEXRHeader(EXRHeader *exr_header);
460
461// Frees internal data of EXRImage struct
462extern int FreeEXRImage(EXRImage *exr_image);
463
464// Frees error message
465extern void FreeEXRErrorMessage(const char *msg);
466
467// Parse EXR version header of a file.
468extern int ParseEXRVersionFromFile(EXRVersion *version, const char *filename);
469
470// Parse EXR version header from memory-mapped EXR data.
471extern int ParseEXRVersionFromMemory(EXRVersion *version,
472 const unsigned char *memory, size_t size);
473
474// Parse single-part OpenEXR header from a file and initialize `EXRHeader`.
475// When there was an error message, Application must free `err` with
476// FreeEXRErrorMessage()
477extern int ParseEXRHeaderFromFile(EXRHeader *header, const EXRVersion *version,
478 const char *filename, const char **err);
479
480// Parse single-part OpenEXR header from a memory and initialize `EXRHeader`.
481// When there was an error message, Application must free `err` with
482// FreeEXRErrorMessage()
483extern int ParseEXRHeaderFromMemory(EXRHeader *header,
484 const EXRVersion *version,
485 const unsigned char *memory, size_t size,
486 const char **err);
487
488// Parse multi-part OpenEXR headers from a file and initialize `EXRHeader*`
489// array.
490// When there was an error message, Application must free `err` with
491// FreeEXRErrorMessage()
492extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers,
493 int *num_headers,
494 const EXRVersion *version,
495 const char *filename,
496 const char **err);
497
498// Parse multi-part OpenEXR headers from a memory and initialize `EXRHeader*`
499// array
500// When there was an error message, Application must free `err` with
501// FreeEXRErrorMessage()
502extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers,
503 int *num_headers,
504 const EXRVersion *version,
505 const unsigned char *memory,
506 size_t size, const char **err);
507
508// Loads single-part OpenEXR image from a file.
509// Application must setup `ParseEXRHeaderFromFile` before calling this function.
510// Application can free EXRImage using `FreeEXRImage`
511// Returns negative value and may set error string in `err` when there's an
512// error
513// When there was an error message, Application must free `err` with
514// FreeEXRErrorMessage()
515extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header,
516 const char *filename, const char **err);
517
518// Loads single-part OpenEXR image from a memory.
519// Application must setup `EXRHeader` with
520// `ParseEXRHeaderFromMemory` before calling this function.
521// Application can free EXRImage using `FreeEXRImage`
522// Returns negative value and may set error string in `err` when there's an
523// error
524// When there was an error message, Application must free `err` with
525// FreeEXRErrorMessage()
526extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header,
527 const unsigned char *memory,
528 const size_t size, const char **err);
529
530// Loads multi-part OpenEXR image from a file.
531// Application must setup `ParseEXRMultipartHeaderFromFile` before calling this
532// function.
533// Application can free EXRImage using `FreeEXRImage`
534// Returns negative value and may set error string in `err` when there's an
535// error
536// When there was an error message, Application must free `err` with
537// FreeEXRErrorMessage()
538extern int LoadEXRMultipartImageFromFile(EXRImage *images,
539 const EXRHeader **headers,
540 unsigned int num_parts,
541 const char *filename,
542 const char **err);
543
544// Loads multi-part OpenEXR image from a memory.
545// Application must setup `EXRHeader*` array with
546// `ParseEXRMultipartHeaderFromMemory` before calling this function.
547// Application can free EXRImage using `FreeEXRImage`
548// Returns negative value and may set error string in `err` when there's an
549// error
550// When there was an error message, Application must free `err` with
551// FreeEXRErrorMessage()
552extern int LoadEXRMultipartImageFromMemory(EXRImage *images,
553 const EXRHeader **headers,
554 unsigned int num_parts,
555 const unsigned char *memory,
556 const size_t size, const char **err);
557
558// Saves multi-channel, single-frame OpenEXR image to a file.
559// Returns negative value and may set error string in `err` when there's an
560// error
561// When there was an error message, Application must free `err` with
562// FreeEXRErrorMessage()
563extern int SaveEXRImageToFile(const EXRImage *image,
564 const EXRHeader *exr_header, const char *filename,
565 const char **err);
566
567// Saves multi-channel, single-frame OpenEXR image to a memory.
568// Image is compressed using EXRImage.compression value.
569// Return the number of bytes if success.
570// Return zero and will set error string in `err` when there's an
571// error.
572// When there was an error message, Application must free `err` with
573// FreeEXRErrorMessage()
574extern size_t SaveEXRImageToMemory(const EXRImage *image,
575 const EXRHeader *exr_header,
576 unsigned char **memory, const char **err);
577
578// Saves multi-channel, multi-frame OpenEXR image to a memory.
579// Image is compressed using EXRImage.compression value.
580// File global attributes (eg. display_window) must be set in the first header.
581// Returns negative value and may set error string in `err` when there's an
582// error
583// When there was an error message, Application must free `err` with
584// FreeEXRErrorMessage()
585extern int SaveEXRMultipartImageToFile(const EXRImage *images,
586 const EXRHeader **exr_headers,
587 unsigned int num_parts,
588 const char *filename, const char **err);
589
590// Saves multi-channel, multi-frame OpenEXR image to a memory.
591// Image is compressed using EXRImage.compression value.
592// File global attributes (eg. display_window) must be set in the first header.
593// Return the number of bytes if success.
594// Return zero and will set error string in `err` when there's an
595// error.
596// When there was an error message, Application must free `err` with
597// FreeEXRErrorMessage()
598extern size_t SaveEXRMultipartImageToMemory(const EXRImage *images,
599 const EXRHeader **exr_headers,
600 unsigned int num_parts,
601 unsigned char **memory, const char **err);
602// Loads single-frame OpenEXR deep image.
603// Application must free memory of variables in DeepImage(image, offset_table)
604// Returns negative value and may set error string in `err` when there's an
605// error
606// When there was an error message, Application must free `err` with
607// FreeEXRErrorMessage()
608extern int LoadDeepEXR(DeepImage *out_image, const char *filename,
609 const char **err);
610
611// NOT YET IMPLEMENTED:
612// Saves single-frame OpenEXR deep image.
613// Returns negative value and may set error string in `err` when there's an
614// error
615// extern int SaveDeepEXR(const DeepImage *in_image, const char *filename,
616// const char **err);
617
618// NOT YET IMPLEMENTED:
619// Loads multi-part OpenEXR deep image.
620// Application must free memory of variables in DeepImage(image, offset_table)
621// extern int LoadMultiPartDeepEXR(DeepImage **out_image, int num_parts, const
622// char *filename,
623// const char **err);
624
625// For emscripten.
626// Loads single-frame OpenEXR image from memory. Assume EXR image contains
627// RGB(A) channels.
628// Returns negative value and may set error string in `err` when there's an
629// error
630// When there was an error message, Application must free `err` with
631// FreeEXRErrorMessage()
632extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height,
633 const unsigned char *memory, size_t size,
634 const char **err);
635
636#ifdef __cplusplus
637}
638#endif
639
640#endif // TINYEXR_H_
641
642#ifdef TINYEXR_IMPLEMENTATION
643#ifndef TINYEXR_IMPLEMENTATION_DEFINED
644#define TINYEXR_IMPLEMENTATION_DEFINED
645
646#ifdef _WIN32
647
648#ifndef WIN32_LEAN_AND_MEAN
649#define WIN32_LEAN_AND_MEAN
650#endif
651#ifndef NOMINMAX
652#define NOMINMAX
653#endif
654#include <windows.h> // for UTF-8 and memory-mapping
655
656#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)
657#define TINYEXR_USE_WIN32_MMAP (1)
658#endif
659
660#elif defined(__linux__) || defined(__unix__)
661#include <fcntl.h> // for open()
662#include <sys/mman.h> // for memory-mapping
663#include <sys/stat.h> // for stat
664#include <unistd.h> // for close()
665#define TINYEXR_USE_POSIX_MMAP (1)
666#endif
667
668#include <algorithm>
669#include <cstdio>
670#include <cstdlib>
671#include <cstring>
672#include <sstream>
673
674//#include <iostream> // debug
675
676#include <limits>
677#include <string>
678#include <vector>
679#include <set>
680
681// https://stackoverflow.com/questions/5047971/how-do-i-check-for-c11-support
682#if __cplusplus > 199711L || (defined(_MSC_VER) && _MSC_VER >= 1900)
683#define TINYEXR_HAS_CXX11 (1)
684// C++11
685#include <cstdint>
686
687#if TINYEXR_USE_THREAD
688#include <atomic>
689#include <thread>
690#endif
691
692#else // __cplusplus > 199711L
693#define TINYEXR_HAS_CXX11 (0)
694#endif // __cplusplus > 199711L
695
696#if TINYEXR_USE_OPENMP
697#include <omp.h>
698#endif
699
700#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
701#include <miniz.h>
702#else
703// Issue #46. Please include your own zlib-compatible API header before
704// including `tinyexr.h`
705//#include "zlib.h"
706#endif
707
708#if defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
709#define NANOZLIB_IMPLEMENTATION
710#include "nanozlib.h"
711#endif
712
713#if TINYEXR_USE_STB_ZLIB
714// Since we don't know where a project has stb_image.h and stb_image_write.h
715// and whether they are in the include path, we don't include them here, and
716// instead declare the two relevant functions manually.
717// from stb_image.h:
718extern "C" int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
719// from stb_image_write.h:
720extern "C" unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality);
721#endif
722
723
724#if TINYEXR_USE_ZFP
725
726#ifdef __clang__
727#pragma clang diagnostic push
728#pragma clang diagnostic ignored "-Weverything"
729#endif
730
731#include "zfp.h"
732
733#ifdef __clang__
734#pragma clang diagnostic pop
735#endif
736
737#endif
738
739// cond: conditional expression
740// msg: std::string
741// err: std::string*
742#define TINYEXR_CHECK_AND_RETURN_MSG(cond, msg, err) do { \
743 if (!(cond)) { \
744 if (!err) { \
745 std::ostringstream ss_e; \
746 ss_e << __func__ << "():" << __LINE__ << msg << "\n"; \
747 (*err) += ss_e.str(); \
748 } \
749 return false;\
750 } \
751 } while(0)
752
753// no error message.
754#define TINYEXR_CHECK_AND_RETURN_C(cond, retcode) do { \
755 if (!(cond)) { \
756 return retcode; \
757 } \
758 } while(0)
759
760namespace tinyexr {
761
762#if __cplusplus > 199711L
763// C++11
764typedef uint64_t tinyexr_uint64;
765typedef int64_t tinyexr_int64;
766#else
767// Although `long long` is not a standard type pre C++11, assume it is defined
768// as a compiler's extension.
769#ifdef __clang__
770#pragma clang diagnostic push
771#pragma clang diagnostic ignored "-Wc++11-long-long"
772#endif
773typedef unsigned long long tinyexr_uint64;
774typedef long long tinyexr_int64;
775#ifdef __clang__
776#pragma clang diagnostic pop
777#endif
778#endif
779
780// static bool IsBigEndian(void) {
781// union {
782// unsigned int i;
783// char c[4];
784// } bint = {0x01020304};
785//
786// return bint.c[0] == 1;
787//}
788
789static void SetErrorMessage(const std::string &msg, const char **err) {
790 if (err) {
791#ifdef _WIN32
792 (*err) = _strdup(msg.c_str());
793#else
794 (*err) = strdup(s: msg.c_str());
795#endif
796 }
797}
798
799#if 0
800static void SetWarningMessage(const std::string &msg, const char **warn) {
801 if (warn) {
802#ifdef _WIN32
803 (*warn) = _strdup(msg.c_str());
804#else
805 (*warn) = strdup(msg.c_str());
806#endif
807 }
808}
809#endif
810
811static const int kEXRVersionSize = 8;
812
813static void inline cpy2(unsigned short *dst_val, const unsigned short *src_val) {
814 unsigned char *dst = reinterpret_cast<unsigned char *>(dst_val);
815 const unsigned char *src = reinterpret_cast<const unsigned char *>(src_val);
816
817 dst[0] = src[0];
818 dst[1] = src[1];
819}
820
821static void inline swap2(unsigned short *val) {
822#if TINYEXR_LITTLE_ENDIAN
823 (void)val;
824#else
825 unsigned short tmp = *val;
826 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
827 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
828
829 dst[0] = src[1];
830 dst[1] = src[0];
831#endif
832}
833
834#ifdef __clang__
835#pragma clang diagnostic push
836#pragma clang diagnostic ignored "-Wunused-function"
837#endif
838
839#ifdef __GNUC__
840#pragma GCC diagnostic push
841#pragma GCC diagnostic ignored "-Wunused-function"
842#endif
843static void inline cpy4(int *dst_val, const int *src_val) {
844 unsigned char *dst = reinterpret_cast<unsigned char *>(dst_val);
845 const unsigned char *src = reinterpret_cast<const unsigned char *>(src_val);
846
847 dst[0] = src[0];
848 dst[1] = src[1];
849 dst[2] = src[2];
850 dst[3] = src[3];
851}
852
853static void inline cpy4(unsigned int *dst_val, const unsigned int *src_val) {
854 unsigned char *dst = reinterpret_cast<unsigned char *>(dst_val);
855 const unsigned char *src = reinterpret_cast<const unsigned char *>(src_val);
856
857 dst[0] = src[0];
858 dst[1] = src[1];
859 dst[2] = src[2];
860 dst[3] = src[3];
861}
862
863static void inline cpy4(float *dst_val, const float *src_val) {
864 unsigned char *dst = reinterpret_cast<unsigned char *>(dst_val);
865 const unsigned char *src = reinterpret_cast<const unsigned char *>(src_val);
866
867 dst[0] = src[0];
868 dst[1] = src[1];
869 dst[2] = src[2];
870 dst[3] = src[3];
871}
872#ifdef __clang__
873#pragma clang diagnostic pop
874#endif
875
876#ifdef __GNUC__
877#pragma GCC diagnostic pop
878#endif
879
880static void inline swap4(unsigned int *val) {
881#if TINYEXR_LITTLE_ENDIAN
882 (void)val;
883#else
884 unsigned int tmp = *val;
885 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
886 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
887
888 dst[0] = src[3];
889 dst[1] = src[2];
890 dst[2] = src[1];
891 dst[3] = src[0];
892#endif
893}
894
895static void inline swap4(int *val) {
896#if TINYEXR_LITTLE_ENDIAN
897 (void)val;
898#else
899 int tmp = *val;
900 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
901 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
902
903 dst[0] = src[3];
904 dst[1] = src[2];
905 dst[2] = src[1];
906 dst[3] = src[0];
907#endif
908}
909
910static void inline swap4(float *val) {
911#if TINYEXR_LITTLE_ENDIAN
912 (void)val;
913#else
914 float tmp = *val;
915 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
916 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
917
918 dst[0] = src[3];
919 dst[1] = src[2];
920 dst[2] = src[1];
921 dst[3] = src[0];
922#endif
923}
924
925#if 0
926static void inline cpy8(tinyexr::tinyexr_uint64 *dst_val, const tinyexr::tinyexr_uint64 *src_val) {
927 unsigned char *dst = reinterpret_cast<unsigned char *>(dst_val);
928 const unsigned char *src = reinterpret_cast<const unsigned char *>(src_val);
929
930 dst[0] = src[0];
931 dst[1] = src[1];
932 dst[2] = src[2];
933 dst[3] = src[3];
934 dst[4] = src[4];
935 dst[5] = src[5];
936 dst[6] = src[6];
937 dst[7] = src[7];
938}
939#endif
940
941static void inline swap8(tinyexr::tinyexr_uint64 *val) {
942#if TINYEXR_LITTLE_ENDIAN
943 (void)val;
944#else
945 tinyexr::tinyexr_uint64 tmp = (*val);
946 unsigned char *dst = reinterpret_cast<unsigned char *>(val);
947 unsigned char *src = reinterpret_cast<unsigned char *>(&tmp);
948
949 dst[0] = src[7];
950 dst[1] = src[6];
951 dst[2] = src[5];
952 dst[3] = src[4];
953 dst[4] = src[3];
954 dst[5] = src[2];
955 dst[6] = src[1];
956 dst[7] = src[0];
957#endif
958}
959
960// https://gist.github.com/rygorous/2156668
961#if TINYEXR_HAS_FP16_COMPILER_TYPE && (TINYEXR_USE_COMPILER_FP16 > 0)
962union FP32 {
963 float f;
964};
965#else
966union FP32 {
967 unsigned int u;
968 float f;
969 struct {
970#if TINYEXR_LITTLE_ENDIAN
971 unsigned int Mantissa : 23;
972 unsigned int Exponent : 8;
973 unsigned int Sign : 1;
974#else
975 unsigned int Sign : 1;
976 unsigned int Exponent : 8;
977 unsigned int Mantissa : 23;
978#endif
979 } s;
980};
981#endif
982
983#ifdef __clang__
984#pragma clang diagnostic push
985#pragma clang diagnostic ignored "-Wpadded"
986#endif
987
988#if TINYEXR_HAS_FP16_COMPILER_TYPE && (TINYEXR_USE_COMPILER_FP16 > 0)
989union FP16 {
990 TINYEXR_FP16_COMPILER_TYPE f;
991 unsigned short u;
992};
993
994#else
995
996union FP16 {
997 unsigned short u;
998 struct {
999#if TINYEXR_LITTLE_ENDIAN
1000 unsigned int Mantissa : 10;
1001 unsigned int Exponent : 5;
1002 unsigned int Sign : 1;
1003#else
1004 unsigned int Sign : 1;
1005 unsigned int Exponent : 5;
1006 unsigned int Mantissa : 10;
1007#endif
1008 } s;
1009};
1010#endif
1011
1012#ifdef __clang__
1013#pragma clang diagnostic pop
1014#endif
1015
1016#if TINYEXR_HAS_FP16_COMPILER_TYPE && (TINYEXR_USE_COMPILER_FP16 > 0)
1017static inline FP32 half_to_float(FP16 h) {
1018 FP32 o;
1019#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) && defined(__AVX2__)
1020 o.f =_mm_cvtss_f32(_mm_cvtph_ps(_mm_cvtsi32_si128(static_cast<int> (h.u))));
1021#else
1022 o.f = static_cast<float> (h.f);
1023#endif
1024 return o;
1025}
1026static inline FP16 float_to_half_full(FP32 f) {
1027 FP16 o;
1028#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) && defined(__AVX2__)
1029 o.f = static_cast<TINYEXR_FP16_COMPILER_TYPE> (_mm_cvtsi128_si32(_mm_cvtps_ph(_mm_set_ss(f.f), _MM_FROUND_CUR_DIRECTION)));
1030#else
1031 o.f = static_cast<TINYEXR_FP16_COMPILER_TYPE> (f.f);
1032#endif
1033 return o;
1034}
1035#else
1036static FP32 half_to_float(FP16 h) {
1037 static const FP32 magic = {.u: 113 << 23};
1038 static const unsigned int shifted_exp = 0x7c00
1039 << 13; // exponent mask after shift
1040 FP32 o;
1041
1042 o.u = (h.u & 0x7fffU) << 13U; // exponent/mantissa bits
1043 unsigned int exp_ = shifted_exp & o.u; // just the exponent
1044 o.u += (127 - 15) << 23; // exponent adjust
1045
1046 // handle exponent special cases
1047 if (exp_ == shifted_exp) // Inf/NaN?
1048 o.u += (128 - 16) << 23; // extra exp adjust
1049 else if (exp_ == 0) // Zero/Denormal?
1050 {
1051 o.u += 1 << 23; // extra exp adjust
1052 o.f -= magic.f; // renormalize
1053 }
1054
1055 o.u |= (h.u & 0x8000U) << 16U; // sign bit
1056 return o;
1057}
1058
1059static FP16 float_to_half_full(FP32 f) {
1060 FP16 o = {.u: 0};
1061
1062 // Based on ISPC reference code (with minor modifications)
1063 if (f.s.Exponent == 0) // Signed zero/denormal (which will underflow)
1064 o.s.Exponent = 0;
1065 else if (f.s.Exponent == 255) // Inf or NaN (all exponent bits set)
1066 {
1067 o.s.Exponent = 31;
1068 o.s.Mantissa = f.s.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf
1069 } else // Normalized number
1070 {
1071 // Exponent unbias the single, then bias the halfp
1072 int newexp = f.s.Exponent - 127 + 15;
1073 if (newexp >= 31) // Overflow, return signed infinity
1074 o.s.Exponent = 31;
1075 else if (newexp <= 0) // Underflow
1076 {
1077 if ((14 - newexp) <= 24) // Mantissa might be non-zero
1078 {
1079 unsigned int mant = f.s.Mantissa | 0x800000; // Hidden 1 bit
1080 o.s.Mantissa = mant >> (14 - newexp);
1081 if ((mant >> (13 - newexp)) & 1) // Check for rounding
1082 o.u++; // Round, might overflow into exp bit, but this is OK
1083 }
1084 } else {
1085 o.s.Exponent = static_cast<unsigned int>(newexp);
1086 o.s.Mantissa = f.s.Mantissa >> 13;
1087 if (f.s.Mantissa & 0x1000) // Check for rounding
1088 o.u++; // Round, might overflow to inf, this is OK
1089 }
1090 }
1091
1092 o.s.Sign = f.s.Sign;
1093 return o;
1094}
1095#endif
1096// NOTE: From OpenEXR code
1097// #define IMF_INCREASING_Y 0
1098// #define IMF_DECREASING_Y 1
1099// #define IMF_RAMDOM_Y 2
1100//
1101// #define IMF_NO_COMPRESSION 0
1102// #define IMF_RLE_COMPRESSION 1
1103// #define IMF_ZIPS_COMPRESSION 2
1104// #define IMF_ZIP_COMPRESSION 3
1105// #define IMF_PIZ_COMPRESSION 4
1106// #define IMF_PXR24_COMPRESSION 5
1107// #define IMF_B44_COMPRESSION 6
1108// #define IMF_B44A_COMPRESSION 7
1109
1110#ifdef __clang__
1111#pragma clang diagnostic push
1112
1113#if __has_warning("-Wzero-as-null-pointer-constant")
1114#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
1115#endif
1116
1117#endif
1118
1119static const char *ReadString(std::string *s, const char *ptr, size_t len) {
1120 // Read untile NULL(\0).
1121 const char *p = ptr;
1122 const char *q = ptr;
1123 while ((size_t(q - ptr) < len) && (*q) != 0) {
1124 q++;
1125 }
1126
1127 if (size_t(q - ptr) >= len) {
1128 (*s).clear();
1129 return NULL;
1130 }
1131
1132 (*s) = std::string(p, q);
1133
1134 return q + 1; // skip '\0'
1135}
1136
1137static bool ReadAttribute(std::string *name, std::string *type,
1138 std::vector<unsigned char> *data, size_t *marker_size,
1139 const char *marker, size_t size) {
1140 size_t name_len = strnlen(string: marker, maxlen: size);
1141 if (name_len == size) {
1142 // String does not have a terminating character.
1143 return false;
1144 }
1145 *name = std::string(marker, name_len);
1146
1147 marker += name_len + 1;
1148 size -= name_len + 1;
1149
1150 size_t type_len = strnlen(string: marker, maxlen: size);
1151 if (type_len == size) {
1152 return false;
1153 }
1154 *type = std::string(marker, type_len);
1155
1156 marker += type_len + 1;
1157 size -= type_len + 1;
1158
1159 if (size < sizeof(uint32_t)) {
1160 return false;
1161 }
1162
1163 uint32_t data_len;
1164 memcpy(dest: &data_len, src: marker, n: sizeof(uint32_t));
1165 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&data_len));
1166
1167 if (data_len == 0) {
1168 if ((*type).compare(s: "string") == 0) {
1169 // Accept empty string attribute.
1170
1171 marker += sizeof(uint32_t);
1172 size -= sizeof(uint32_t);
1173
1174 *marker_size = name_len + 1 + type_len + 1 + sizeof(uint32_t);
1175
1176 data->resize(new_size: 1);
1177 (*data)[0] = '\0';
1178
1179 return true;
1180 } else {
1181 return false;
1182 }
1183 }
1184
1185 marker += sizeof(uint32_t);
1186 size -= sizeof(uint32_t);
1187
1188 if (size < data_len) {
1189 return false;
1190 }
1191
1192 data->resize(new_size: static_cast<size_t>(data_len));
1193 memcpy(dest: &data->at(n: 0), src: marker, n: static_cast<size_t>(data_len));
1194
1195 *marker_size = name_len + 1 + type_len + 1 + sizeof(uint32_t) + data_len;
1196 return true;
1197}
1198
1199static void WriteAttributeToMemory(std::vector<unsigned char> *out,
1200 const char *name, const char *type,
1201 const unsigned char *data, int len) {
1202 out->insert(position: out->end(), first: name, last: name + strlen(s: name) + 1);
1203 out->insert(position: out->end(), first: type, last: type + strlen(s: type) + 1);
1204
1205 int outLen = len;
1206 tinyexr::swap4(val: &outLen);
1207 out->insert(position: out->end(), first: reinterpret_cast<unsigned char *>(&outLen),
1208 last: reinterpret_cast<unsigned char *>(&outLen) + sizeof(int));
1209 out->insert(position: out->end(), first: data, last: data + len);
1210}
1211
1212typedef struct TChannelInfo {
1213 std::string name; // less than 255 bytes long
1214 int pixel_type;
1215 int requested_pixel_type;
1216 int x_sampling;
1217 int y_sampling;
1218 unsigned char p_linear;
1219 unsigned char pad[3];
1220} ChannelInfo;
1221
1222typedef struct {
1223 int min_x;
1224 int min_y;
1225 int max_x;
1226 int max_y;
1227} Box2iInfo;
1228
1229struct HeaderInfo {
1230 std::vector<tinyexr::ChannelInfo> channels;
1231 std::vector<EXRAttribute> attributes;
1232
1233 Box2iInfo data_window;
1234 int line_order;
1235 Box2iInfo display_window;
1236 float screen_window_center[2];
1237 float screen_window_width;
1238 float pixel_aspect_ratio;
1239
1240 int chunk_count;
1241
1242 // Tiled format
1243 int tiled; // Non-zero if the part is tiled.
1244 int tile_size_x;
1245 int tile_size_y;
1246 int tile_level_mode;
1247 int tile_rounding_mode;
1248
1249 unsigned int header_len;
1250
1251 int compression_type;
1252
1253 // required for multi-part or non-image files
1254 std::string name;
1255 // required for multi-part or non-image files
1256 std::string type;
1257
1258 void clear() {
1259 channels.clear();
1260 attributes.clear();
1261
1262 data_window.min_x = 0;
1263 data_window.min_y = 0;
1264 data_window.max_x = 0;
1265 data_window.max_y = 0;
1266 line_order = 0;
1267 display_window.min_x = 0;
1268 display_window.min_y = 0;
1269 display_window.max_x = 0;
1270 display_window.max_y = 0;
1271 screen_window_center[0] = 0.0f;
1272 screen_window_center[1] = 0.0f;
1273 screen_window_width = 0.0f;
1274 pixel_aspect_ratio = 0.0f;
1275
1276 chunk_count = 0;
1277
1278 // Tiled format
1279 tiled = 0;
1280 tile_size_x = 0;
1281 tile_size_y = 0;
1282 tile_level_mode = 0;
1283 tile_rounding_mode = 0;
1284
1285 header_len = 0;
1286 compression_type = 0;
1287
1288 name.clear();
1289 type.clear();
1290 }
1291};
1292
1293static bool ReadChannelInfo(std::vector<ChannelInfo> &channels,
1294 const std::vector<unsigned char> &data) {
1295 const char *p = reinterpret_cast<const char *>(&data.at(n: 0));
1296
1297 for (;;) {
1298 if ((*p) == 0) {
1299 break;
1300 }
1301 ChannelInfo info;
1302 info.requested_pixel_type = 0;
1303
1304 tinyexr_int64 data_len = static_cast<tinyexr_int64>(data.size()) -
1305 (p - reinterpret_cast<const char *>(data.data()));
1306 if (data_len < 0) {
1307 return false;
1308 }
1309
1310 p = ReadString(s: &info.name, ptr: p, len: size_t(data_len));
1311 if ((p == NULL) && (info.name.empty())) {
1312 // Buffer overrun. Issue #51.
1313 return false;
1314 }
1315
1316 const unsigned char *data_end =
1317 reinterpret_cast<const unsigned char *>(p) + 16;
1318 if (data_end >= (data.data() + data.size())) {
1319 return false;
1320 }
1321
1322 memcpy(dest: &info.pixel_type, src: p, n: sizeof(int));
1323 p += 4;
1324 info.p_linear = static_cast<unsigned char>(p[0]); // uchar
1325 p += 1 + 3; // reserved: uchar[3]
1326 memcpy(dest: &info.x_sampling, src: p, n: sizeof(int)); // int
1327 p += 4;
1328 memcpy(dest: &info.y_sampling, src: p, n: sizeof(int)); // int
1329 p += 4;
1330
1331 tinyexr::swap4(val: &info.pixel_type);
1332 tinyexr::swap4(val: &info.x_sampling);
1333 tinyexr::swap4(val: &info.y_sampling);
1334
1335 channels.push_back(x: info);
1336 }
1337
1338 return true;
1339}
1340
1341static void WriteChannelInfo(std::vector<unsigned char> &data,
1342 const std::vector<ChannelInfo> &channels) {
1343 size_t sz = 0;
1344
1345 // Calculate total size.
1346 for (size_t c = 0; c < channels.size(); c++) {
1347 sz += channels[c].name.length() + 1; // +1 for \0
1348 sz += 16; // 4 * int
1349 }
1350 data.resize(new_size: sz + 1);
1351
1352 unsigned char *p = &data.at(n: 0);
1353
1354 for (size_t c = 0; c < channels.size(); c++) {
1355 memcpy(dest: p, src: channels[c].name.c_str(), n: channels[c].name.length());
1356 p += channels[c].name.length();
1357 (*p) = '\0';
1358 p++;
1359
1360 int pixel_type = channels[c].requested_pixel_type;
1361 int x_sampling = channels[c].x_sampling;
1362 int y_sampling = channels[c].y_sampling;
1363 tinyexr::swap4(val: &pixel_type);
1364 tinyexr::swap4(val: &x_sampling);
1365 tinyexr::swap4(val: &y_sampling);
1366
1367 memcpy(dest: p, src: &pixel_type, n: sizeof(int));
1368 p += sizeof(int);
1369
1370 (*p) = channels[c].p_linear;
1371 p += 4;
1372
1373 memcpy(dest: p, src: &x_sampling, n: sizeof(int));
1374 p += sizeof(int);
1375
1376 memcpy(dest: p, src: &y_sampling, n: sizeof(int));
1377 p += sizeof(int);
1378 }
1379
1380 (*p) = '\0';
1381}
1382
1383static bool CompressZip(unsigned char *dst,
1384 tinyexr::tinyexr_uint64 &compressedSize,
1385 const unsigned char *src, unsigned long src_size) {
1386 std::vector<unsigned char> tmpBuf(src_size);
1387
1388 //
1389 // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
1390 // ImfZipCompressor.cpp
1391 //
1392
1393 //
1394 // Reorder the pixel data.
1395 //
1396
1397 const char *srcPtr = reinterpret_cast<const char *>(src);
1398
1399 {
1400 char *t1 = reinterpret_cast<char *>(&tmpBuf.at(n: 0));
1401 char *t2 = reinterpret_cast<char *>(&tmpBuf.at(n: 0)) + (src_size + 1) / 2;
1402 const char *stop = srcPtr + src_size;
1403
1404 for (;;) {
1405 if (srcPtr < stop)
1406 *(t1++) = *(srcPtr++);
1407 else
1408 break;
1409
1410 if (srcPtr < stop)
1411 *(t2++) = *(srcPtr++);
1412 else
1413 break;
1414 }
1415 }
1416
1417 //
1418 // Predictor.
1419 //
1420
1421 {
1422 unsigned char *t = &tmpBuf.at(n: 0) + 1;
1423 unsigned char *stop = &tmpBuf.at(n: 0) + src_size;
1424 int p = t[-1];
1425
1426 while (t < stop) {
1427 int d = int(t[0]) - p + (128 + 256);
1428 p = t[0];
1429 t[0] = static_cast<unsigned char>(d);
1430 ++t;
1431 }
1432 }
1433
1434#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
1435 //
1436 // Compress the data using miniz
1437 //
1438
1439 mz_ulong outSize = mz_compressBound(src_size);
1440 int ret = mz_compress(
1441 dst, &outSize, static_cast<const unsigned char *>(&tmpBuf.at(0)),
1442 src_size);
1443 if (ret != MZ_OK) {
1444 return false;
1445 }
1446
1447 compressedSize = outSize;
1448#elif defined(TINYEXR_USE_STB_ZLIB) && (TINYEXR_USE_STB_ZLIB==1)
1449 int outSize;
1450 unsigned char* ret = stbi_zlib_compress(const_cast<unsigned char*>(&tmpBuf.at(0)), src_size, &outSize, 8);
1451 if (!ret) {
1452 return false;
1453 }
1454 memcpy(dst, ret, outSize);
1455 free(ret);
1456
1457 compressedSize = outSize;
1458#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
1459 uint64_t dstSize = nanoz_compressBound(static_cast<uint64_t>(src_size));
1460 int outSize{0};
1461 unsigned char *ret = nanoz_compress(&tmpBuf.at(0), src_size, &outSize, /* quality */8);
1462 if (!ret) {
1463 return false;
1464 }
1465
1466 memcpy(dst, ret, outSize);
1467 free(ret);
1468
1469 compressedSize = outSize;
1470#else
1471 uLong outSize = compressBound(sourceLen: static_cast<uLong>(src_size));
1472 int ret = compress(dest: dst, destLen: &outSize, source: static_cast<const Bytef *>(&tmpBuf.at(n: 0)),
1473 sourceLen: src_size);
1474 if (ret != Z_OK) {
1475 return false;
1476 }
1477
1478 compressedSize = outSize;
1479#endif
1480
1481 // Use uncompressed data when compressed data is larger than uncompressed.
1482 // (Issue 40)
1483 if (compressedSize >= src_size) {
1484 compressedSize = src_size;
1485 memcpy(dest: dst, src: src, n: src_size);
1486 }
1487
1488 return true;
1489}
1490
1491static bool DecompressZip(unsigned char *dst,
1492 unsigned long *uncompressed_size /* inout */,
1493 const unsigned char *src, unsigned long src_size) {
1494 if ((*uncompressed_size) == src_size) {
1495 // Data is not compressed(Issue 40).
1496 memcpy(dest: dst, src: src, n: src_size);
1497 return true;
1498 }
1499 std::vector<unsigned char> tmpBuf(*uncompressed_size);
1500
1501#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
1502 int ret =
1503 mz_uncompress(&tmpBuf.at(0), uncompressed_size, src, src_size);
1504 if (MZ_OK != ret) {
1505 return false;
1506 }
1507#elif TINYEXR_USE_STB_ZLIB
1508 int ret = stbi_zlib_decode_buffer(reinterpret_cast<char*>(&tmpBuf.at(0)),
1509 *uncompressed_size, reinterpret_cast<const char*>(src), src_size);
1510 if (ret < 0) {
1511 return false;
1512 }
1513#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB==1)
1514 uint64_t dest_size = (*uncompressed_size);
1515 uint64_t uncomp_size{0};
1516 nanoz_status_t ret =
1517 nanoz_uncompress(src, src_size, dest_size, &tmpBuf.at(0), &uncomp_size);
1518 if (NANOZ_SUCCESS != ret) {
1519 return false;
1520 }
1521 if ((*uncompressed_size) != uncomp_size) {
1522 return false;
1523 }
1524#else
1525 int ret = uncompress(dest: &tmpBuf.at(n: 0), destLen: uncompressed_size, source: src, sourceLen: src_size);
1526 if (Z_OK != ret) {
1527 return false;
1528 }
1529#endif
1530
1531 //
1532 // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
1533 // ImfZipCompressor.cpp
1534 //
1535
1536 // Predictor.
1537 {
1538 unsigned char *t = &tmpBuf.at(n: 0) + 1;
1539 unsigned char *stop = &tmpBuf.at(n: 0) + (*uncompressed_size);
1540
1541 while (t < stop) {
1542 int d = int(t[-1]) + int(t[0]) - 128;
1543 t[0] = static_cast<unsigned char>(d);
1544 ++t;
1545 }
1546 }
1547
1548 // Reorder the pixel data.
1549 {
1550 const char *t1 = reinterpret_cast<const char *>(&tmpBuf.at(n: 0));
1551 const char *t2 = reinterpret_cast<const char *>(&tmpBuf.at(n: 0)) +
1552 (*uncompressed_size + 1) / 2;
1553 char *s = reinterpret_cast<char *>(dst);
1554 char *stop = s + (*uncompressed_size);
1555
1556 for (;;) {
1557 if (s < stop)
1558 *(s++) = *(t1++);
1559 else
1560 break;
1561
1562 if (s < stop)
1563 *(s++) = *(t2++);
1564 else
1565 break;
1566 }
1567 }
1568
1569 return true;
1570}
1571
1572// RLE code from OpenEXR --------------------------------------
1573
1574#ifdef __clang__
1575#pragma clang diagnostic push
1576#pragma clang diagnostic ignored "-Wsign-conversion"
1577#if __has_warning("-Wextra-semi-stmt")
1578#pragma clang diagnostic ignored "-Wextra-semi-stmt"
1579#endif
1580#endif
1581
1582#ifdef _MSC_VER
1583#pragma warning(push)
1584#pragma warning(disable : 4204) // nonstandard extension used : non-constant
1585 // aggregate initializer (also supported by GNU
1586 // C and C99, so no big deal)
1587#pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to
1588 // 'int', possible loss of data
1589#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to
1590 // 'int', possible loss of data
1591#pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is
1592 // deprecated. Instead, use the ISO C and C++
1593 // conformant name: _strdup.
1594#endif
1595
1596const int MIN_RUN_LENGTH = 3;
1597const int MAX_RUN_LENGTH = 127;
1598
1599//
1600// Compress an array of bytes, using run-length encoding,
1601// and return the length of the compressed data.
1602//
1603
1604static int rleCompress(int inLength, const char in[], signed char out[]) {
1605 const char *inEnd = in + inLength;
1606 const char *runStart = in;
1607 const char *runEnd = in + 1;
1608 signed char *outWrite = out;
1609
1610 while (runStart < inEnd) {
1611 while (runEnd < inEnd && *runStart == *runEnd &&
1612 runEnd - runStart - 1 < MAX_RUN_LENGTH) {
1613 ++runEnd;
1614 }
1615
1616 if (runEnd - runStart >= MIN_RUN_LENGTH) {
1617 //
1618 // Compressible run
1619 //
1620
1621 *outWrite++ = static_cast<char>(runEnd - runStart) - 1;
1622 *outWrite++ = *(reinterpret_cast<const signed char *>(runStart));
1623 runStart = runEnd;
1624 } else {
1625 //
1626 // Uncompressable run
1627 //
1628
1629 while (runEnd < inEnd &&
1630 ((runEnd + 1 >= inEnd || *runEnd != *(runEnd + 1)) ||
1631 (runEnd + 2 >= inEnd || *(runEnd + 1) != *(runEnd + 2))) &&
1632 runEnd - runStart < MAX_RUN_LENGTH) {
1633 ++runEnd;
1634 }
1635
1636 *outWrite++ = static_cast<char>(runStart - runEnd);
1637
1638 while (runStart < runEnd) {
1639 *outWrite++ = *(reinterpret_cast<const signed char *>(runStart++));
1640 }
1641 }
1642
1643 ++runEnd;
1644 }
1645
1646 return static_cast<int>(outWrite - out);
1647}
1648
1649//
1650// Uncompress an array of bytes compressed with rleCompress().
1651// Returns the length of the uncompressed data, or 0 if the
1652// length of the uncompressed data would be more than maxLength.
1653//
1654
1655static int rleUncompress(int inLength, int maxLength, const signed char in[],
1656 char out[]) {
1657 char *outStart = out;
1658
1659 while (inLength > 0) {
1660 if (*in < 0) {
1661 int count = -(static_cast<int>(*in++));
1662 inLength -= count + 1;
1663
1664 // Fixes #116: Add bounds check to in buffer.
1665 if ((0 > (maxLength -= count)) || (inLength < 0)) return 0;
1666
1667 memcpy(dest: out, src: in, n: count);
1668 out += count;
1669 in += count;
1670 } else {
1671 int count = *in++;
1672 inLength -= 2;
1673
1674 if ((0 > (maxLength -= count + 1)) || (inLength < 0)) return 0;
1675
1676 memset(s: out, c: *reinterpret_cast<const char *>(in), n: count + 1);
1677 out += count + 1;
1678
1679 in++;
1680 }
1681 }
1682
1683 return static_cast<int>(out - outStart);
1684}
1685
1686#ifdef __clang__
1687#pragma clang diagnostic pop
1688#endif
1689
1690// End of RLE code from OpenEXR -----------------------------------
1691
1692static bool CompressRle(unsigned char *dst,
1693 tinyexr::tinyexr_uint64 &compressedSize,
1694 const unsigned char *src, unsigned long src_size) {
1695 std::vector<unsigned char> tmpBuf(src_size);
1696
1697 //
1698 // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
1699 // ImfRleCompressor.cpp
1700 //
1701
1702 //
1703 // Reorder the pixel data.
1704 //
1705
1706 const char *srcPtr = reinterpret_cast<const char *>(src);
1707
1708 {
1709 char *t1 = reinterpret_cast<char *>(&tmpBuf.at(n: 0));
1710 char *t2 = reinterpret_cast<char *>(&tmpBuf.at(n: 0)) + (src_size + 1) / 2;
1711 const char *stop = srcPtr + src_size;
1712
1713 for (;;) {
1714 if (srcPtr < stop)
1715 *(t1++) = *(srcPtr++);
1716 else
1717 break;
1718
1719 if (srcPtr < stop)
1720 *(t2++) = *(srcPtr++);
1721 else
1722 break;
1723 }
1724 }
1725
1726 //
1727 // Predictor.
1728 //
1729
1730 {
1731 unsigned char *t = &tmpBuf.at(n: 0) + 1;
1732 unsigned char *stop = &tmpBuf.at(n: 0) + src_size;
1733 int p = t[-1];
1734
1735 while (t < stop) {
1736 int d = int(t[0]) - p + (128 + 256);
1737 p = t[0];
1738 t[0] = static_cast<unsigned char>(d);
1739 ++t;
1740 }
1741 }
1742
1743 // outSize will be (srcSiz * 3) / 2 at max.
1744 int outSize = rleCompress(inLength: static_cast<int>(src_size),
1745 in: reinterpret_cast<const char *>(&tmpBuf.at(n: 0)),
1746 out: reinterpret_cast<signed char *>(dst));
1747 TINYEXR_CHECK_AND_RETURN_C(outSize > 0, false);
1748
1749 compressedSize = static_cast<tinyexr::tinyexr_uint64>(outSize);
1750
1751 // Use uncompressed data when compressed data is larger than uncompressed.
1752 // (Issue 40)
1753 if (compressedSize >= src_size) {
1754 compressedSize = src_size;
1755 memcpy(dest: dst, src: src, n: src_size);
1756 }
1757
1758 return true;
1759}
1760
1761static bool DecompressRle(unsigned char *dst,
1762 const unsigned long uncompressed_size,
1763 const unsigned char *src, unsigned long src_size) {
1764 if (uncompressed_size == src_size) {
1765 // Data is not compressed(Issue 40).
1766 memcpy(dest: dst, src: src, n: src_size);
1767 return true;
1768 }
1769
1770 // Workaround for issue #112.
1771 // TODO(syoyo): Add more robust out-of-bounds check in `rleUncompress`.
1772 if (src_size <= 2) {
1773 return false;
1774 }
1775
1776 std::vector<unsigned char> tmpBuf(uncompressed_size);
1777
1778 int ret = rleUncompress(inLength: static_cast<int>(src_size),
1779 maxLength: static_cast<int>(uncompressed_size),
1780 in: reinterpret_cast<const signed char *>(src),
1781 out: reinterpret_cast<char *>(&tmpBuf.at(n: 0)));
1782 if (ret != static_cast<int>(uncompressed_size)) {
1783 return false;
1784 }
1785
1786 //
1787 // Apply EXR-specific? postprocess. Grabbed from OpenEXR's
1788 // ImfRleCompressor.cpp
1789 //
1790
1791 // Predictor.
1792 {
1793 unsigned char *t = &tmpBuf.at(n: 0) + 1;
1794 unsigned char *stop = &tmpBuf.at(n: 0) + uncompressed_size;
1795
1796 while (t < stop) {
1797 int d = int(t[-1]) + int(t[0]) - 128;
1798 t[0] = static_cast<unsigned char>(d);
1799 ++t;
1800 }
1801 }
1802
1803 // Reorder the pixel data.
1804 {
1805 const char *t1 = reinterpret_cast<const char *>(&tmpBuf.at(n: 0));
1806 const char *t2 = reinterpret_cast<const char *>(&tmpBuf.at(n: 0)) +
1807 (uncompressed_size + 1) / 2;
1808 char *s = reinterpret_cast<char *>(dst);
1809 char *stop = s + uncompressed_size;
1810
1811 for (;;) {
1812 if (s < stop)
1813 *(s++) = *(t1++);
1814 else
1815 break;
1816
1817 if (s < stop)
1818 *(s++) = *(t2++);
1819 else
1820 break;
1821 }
1822 }
1823
1824 return true;
1825}
1826
1827#if TINYEXR_USE_PIZ
1828
1829#ifdef __clang__
1830#pragma clang diagnostic push
1831#pragma clang diagnostic ignored "-Wc++11-long-long"
1832#pragma clang diagnostic ignored "-Wold-style-cast"
1833#pragma clang diagnostic ignored "-Wpadded"
1834#pragma clang diagnostic ignored "-Wsign-conversion"
1835#pragma clang diagnostic ignored "-Wc++11-extensions"
1836#pragma clang diagnostic ignored "-Wconversion"
1837#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
1838
1839#if __has_warning("-Wcast-qual")
1840#pragma clang diagnostic ignored "-Wcast-qual"
1841#endif
1842
1843#if __has_warning("-Wextra-semi-stmt")
1844#pragma clang diagnostic ignored "-Wextra-semi-stmt"
1845#endif
1846
1847#endif
1848
1849//
1850// PIZ compress/uncompress, based on OpenEXR's ImfPizCompressor.cpp
1851//
1852// -----------------------------------------------------------------
1853// Copyright (c) 2004, Industrial Light & Magic, a division of Lucas
1854// Digital Ltd. LLC)
1855// (3 clause BSD license)
1856//
1857
1858struct PIZChannelData {
1859 unsigned short *start;
1860 unsigned short *end;
1861 int nx;
1862 int ny;
1863 int ys;
1864 int size;
1865};
1866
1867//-----------------------------------------------------------------------------
1868//
1869// 16-bit Haar Wavelet encoding and decoding
1870//
1871// The source code in this file is derived from the encoding
1872// and decoding routines written by Christian Rouet for his
1873// PIZ image file format.
1874//
1875//-----------------------------------------------------------------------------
1876
1877//
1878// Wavelet basis functions without modulo arithmetic; they produce
1879// the best compression ratios when the wavelet-transformed data are
1880// Huffman-encoded, but the wavelet transform works only for 14-bit
1881// data (untransformed data values must be less than (1 << 14)).
1882//
1883
1884inline void wenc14(unsigned short a, unsigned short b, unsigned short &l,
1885 unsigned short &h) {
1886 short as = static_cast<short>(a);
1887 short bs = static_cast<short>(b);
1888
1889 short ms = (as + bs) >> 1;
1890 short ds = as - bs;
1891
1892 l = static_cast<unsigned short>(ms);
1893 h = static_cast<unsigned short>(ds);
1894}
1895
1896inline void wdec14(unsigned short l, unsigned short h, unsigned short &a,
1897 unsigned short &b) {
1898 short ls = static_cast<short>(l);
1899 short hs = static_cast<short>(h);
1900
1901 int hi = hs;
1902 int ai = ls + (hi & 1) + (hi >> 1);
1903
1904 short as = static_cast<short>(ai);
1905 short bs = static_cast<short>(ai - hi);
1906
1907 a = static_cast<unsigned short>(as);
1908 b = static_cast<unsigned short>(bs);
1909}
1910
1911//
1912// Wavelet basis functions with modulo arithmetic; they work with full
1913// 16-bit data, but Huffman-encoding the wavelet-transformed data doesn't
1914// compress the data quite as well.
1915//
1916
1917const int NBITS = 16;
1918const int A_OFFSET = 1 << (NBITS - 1);
1919const int M_OFFSET = 1 << (NBITS - 1);
1920const int MOD_MASK = (1 << NBITS) - 1;
1921
1922inline void wenc16(unsigned short a, unsigned short b, unsigned short &l,
1923 unsigned short &h) {
1924 int ao = (a + A_OFFSET) & MOD_MASK;
1925 int m = ((ao + b) >> 1);
1926 int d = ao - b;
1927
1928 if (d < 0) m = (m + M_OFFSET) & MOD_MASK;
1929
1930 d &= MOD_MASK;
1931
1932 l = static_cast<unsigned short>(m);
1933 h = static_cast<unsigned short>(d);
1934}
1935
1936inline void wdec16(unsigned short l, unsigned short h, unsigned short &a,
1937 unsigned short &b) {
1938 int m = l;
1939 int d = h;
1940 int bb = (m - (d >> 1)) & MOD_MASK;
1941 int aa = (d + bb - A_OFFSET) & MOD_MASK;
1942 b = static_cast<unsigned short>(bb);
1943 a = static_cast<unsigned short>(aa);
1944}
1945
1946//
1947// 2D Wavelet encoding:
1948//
1949
1950static void wav2Encode(
1951 unsigned short *in, // io: values are transformed in place
1952 int nx, // i : x size
1953 int ox, // i : x offset
1954 int ny, // i : y size
1955 int oy, // i : y offset
1956 unsigned short mx) // i : maximum in[x][y] value
1957{
1958 bool w14 = (mx < (1 << 14));
1959 int n = (nx > ny) ? ny : nx;
1960 int p = 1; // == 1 << level
1961 int p2 = 2; // == 1 << (level+1)
1962
1963 //
1964 // Hierarchical loop on smaller dimension n
1965 //
1966
1967 while (p2 <= n) {
1968 unsigned short *py = in;
1969 unsigned short *ey = in + oy * (ny - p2);
1970 int oy1 = oy * p;
1971 int oy2 = oy * p2;
1972 int ox1 = ox * p;
1973 int ox2 = ox * p2;
1974 unsigned short i00, i01, i10, i11;
1975
1976 //
1977 // Y loop
1978 //
1979
1980 for (; py <= ey; py += oy2) {
1981 unsigned short *px = py;
1982 unsigned short *ex = py + ox * (nx - p2);
1983
1984 //
1985 // X loop
1986 //
1987
1988 for (; px <= ex; px += ox2) {
1989 unsigned short *p01 = px + ox1;
1990 unsigned short *p10 = px + oy1;
1991 unsigned short *p11 = p10 + ox1;
1992
1993 //
1994 // 2D wavelet encoding
1995 //
1996
1997 if (w14) {
1998 wenc14(a: *px, b: *p01, l&: i00, h&: i01);
1999 wenc14(a: *p10, b: *p11, l&: i10, h&: i11);
2000 wenc14(a: i00, b: i10, l&: *px, h&: *p10);
2001 wenc14(a: i01, b: i11, l&: *p01, h&: *p11);
2002 } else {
2003 wenc16(a: *px, b: *p01, l&: i00, h&: i01);
2004 wenc16(a: *p10, b: *p11, l&: i10, h&: i11);
2005 wenc16(a: i00, b: i10, l&: *px, h&: *p10);
2006 wenc16(a: i01, b: i11, l&: *p01, h&: *p11);
2007 }
2008 }
2009
2010 //
2011 // Encode (1D) odd column (still in Y loop)
2012 //
2013
2014 if (nx & p) {
2015 unsigned short *p10 = px + oy1;
2016
2017 if (w14)
2018 wenc14(a: *px, b: *p10, l&: i00, h&: *p10);
2019 else
2020 wenc16(a: *px, b: *p10, l&: i00, h&: *p10);
2021
2022 *px = i00;
2023 }
2024 }
2025
2026 //
2027 // Encode (1D) odd line (must loop in X)
2028 //
2029
2030 if (ny & p) {
2031 unsigned short *px = py;
2032 unsigned short *ex = py + ox * (nx - p2);
2033
2034 for (; px <= ex; px += ox2) {
2035 unsigned short *p01 = px + ox1;
2036
2037 if (w14)
2038 wenc14(a: *px, b: *p01, l&: i00, h&: *p01);
2039 else
2040 wenc16(a: *px, b: *p01, l&: i00, h&: *p01);
2041
2042 *px = i00;
2043 }
2044 }
2045
2046 //
2047 // Next level
2048 //
2049
2050 p = p2;
2051 p2 <<= 1;
2052 }
2053}
2054
2055//
2056// 2D Wavelet decoding:
2057//
2058
2059static void wav2Decode(
2060 unsigned short *in, // io: values are transformed in place
2061 int nx, // i : x size
2062 int ox, // i : x offset
2063 int ny, // i : y size
2064 int oy, // i : y offset
2065 unsigned short mx) // i : maximum in[x][y] value
2066{
2067 bool w14 = (mx < (1 << 14));
2068 int n = (nx > ny) ? ny : nx;
2069 int p = 1;
2070 int p2;
2071
2072 //
2073 // Search max level
2074 //
2075
2076 while (p <= n) p <<= 1;
2077
2078 p >>= 1;
2079 p2 = p;
2080 p >>= 1;
2081
2082 //
2083 // Hierarchical loop on smaller dimension n
2084 //
2085
2086 while (p >= 1) {
2087 unsigned short *py = in;
2088 unsigned short *ey = in + oy * (ny - p2);
2089 int oy1 = oy * p;
2090 int oy2 = oy * p2;
2091 int ox1 = ox * p;
2092 int ox2 = ox * p2;
2093 unsigned short i00, i01, i10, i11;
2094
2095 //
2096 // Y loop
2097 //
2098
2099 for (; py <= ey; py += oy2) {
2100 unsigned short *px = py;
2101 unsigned short *ex = py + ox * (nx - p2);
2102
2103 //
2104 // X loop
2105 //
2106
2107 for (; px <= ex; px += ox2) {
2108 unsigned short *p01 = px + ox1;
2109 unsigned short *p10 = px + oy1;
2110 unsigned short *p11 = p10 + ox1;
2111
2112 //
2113 // 2D wavelet decoding
2114 //
2115
2116 if (w14) {
2117 wdec14(l: *px, h: *p10, a&: i00, b&: i10);
2118 wdec14(l: *p01, h: *p11, a&: i01, b&: i11);
2119 wdec14(l: i00, h: i01, a&: *px, b&: *p01);
2120 wdec14(l: i10, h: i11, a&: *p10, b&: *p11);
2121 } else {
2122 wdec16(l: *px, h: *p10, a&: i00, b&: i10);
2123 wdec16(l: *p01, h: *p11, a&: i01, b&: i11);
2124 wdec16(l: i00, h: i01, a&: *px, b&: *p01);
2125 wdec16(l: i10, h: i11, a&: *p10, b&: *p11);
2126 }
2127 }
2128
2129 //
2130 // Decode (1D) odd column (still in Y loop)
2131 //
2132
2133 if (nx & p) {
2134 unsigned short *p10 = px + oy1;
2135
2136 if (w14)
2137 wdec14(l: *px, h: *p10, a&: i00, b&: *p10);
2138 else
2139 wdec16(l: *px, h: *p10, a&: i00, b&: *p10);
2140
2141 *px = i00;
2142 }
2143 }
2144
2145 //
2146 // Decode (1D) odd line (must loop in X)
2147 //
2148
2149 if (ny & p) {
2150 unsigned short *px = py;
2151 unsigned short *ex = py + ox * (nx - p2);
2152
2153 for (; px <= ex; px += ox2) {
2154 unsigned short *p01 = px + ox1;
2155
2156 if (w14)
2157 wdec14(l: *px, h: *p01, a&: i00, b&: *p01);
2158 else
2159 wdec16(l: *px, h: *p01, a&: i00, b&: *p01);
2160
2161 *px = i00;
2162 }
2163 }
2164
2165 //
2166 // Next level
2167 //
2168
2169 p2 = p;
2170 p >>= 1;
2171 }
2172}
2173
2174//-----------------------------------------------------------------------------
2175//
2176// 16-bit Huffman compression and decompression.
2177//
2178// The source code in this file is derived from the 8-bit
2179// Huffman compression and decompression routines written
2180// by Christian Rouet for his PIZ image file format.
2181//
2182//-----------------------------------------------------------------------------
2183
2184// Adds some modification for tinyexr.
2185
2186const int HUF_ENCBITS = 16; // literal (value) bit length
2187const int HUF_DECBITS = 14; // decoding bit size (>= 8)
2188
2189const int HUF_ENCSIZE = (1 << HUF_ENCBITS) + 1; // encoding table size
2190const int HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
2191const int HUF_DECMASK = HUF_DECSIZE - 1;
2192
2193struct HufDec { // short code long code
2194 //-------------------------------
2195 unsigned int len : 8; // code length 0
2196 unsigned int lit : 24; // lit p size
2197 unsigned int *p; // 0 lits
2198};
2199
2200inline long long hufLength(long long code) { return code & 63; }
2201
2202inline long long hufCode(long long code) { return code >> 6; }
2203
2204inline void outputBits(int nBits, long long bits, long long &c, int &lc,
2205 char *&out) {
2206 c <<= nBits;
2207 lc += nBits;
2208
2209 c |= bits;
2210
2211 while (lc >= 8) *out++ = static_cast<char>((c >> (lc -= 8)));
2212}
2213
2214inline long long getBits(int nBits, long long &c, int &lc, const char *&in) {
2215 while (lc < nBits) {
2216 c = (c << 8) | *(reinterpret_cast<const unsigned char *>(in++));
2217 lc += 8;
2218 }
2219
2220 lc -= nBits;
2221 return (c >> lc) & ((1 << nBits) - 1);
2222}
2223
2224//
2225// ENCODING TABLE BUILDING & (UN)PACKING
2226//
2227
2228//
2229// Build a "canonical" Huffman code table:
2230// - for each (uncompressed) symbol, hcode contains the length
2231// of the corresponding code (in the compressed data)
2232// - canonical codes are computed and stored in hcode
2233// - the rules for constructing canonical codes are as follows:
2234// * shorter codes (if filled with zeroes to the right)
2235// have a numerically higher value than longer codes
2236// * for codes with the same length, numerical values
2237// increase with numerical symbol values
2238// - because the canonical code table can be constructed from
2239// symbol lengths alone, the code table can be transmitted
2240// without sending the actual code values
2241// - see http://www.compressconsult.com/huffman/
2242//
2243
2244static void hufCanonicalCodeTable(long long hcode[HUF_ENCSIZE]) {
2245 long long n[59];
2246
2247 //
2248 // For each i from 0 through 58, count the
2249 // number of different codes of length i, and
2250 // store the count in n[i].
2251 //
2252
2253 for (int i = 0; i <= 58; ++i) n[i] = 0;
2254
2255 for (int i = 0; i < HUF_ENCSIZE; ++i) n[hcode[i]] += 1;
2256
2257 //
2258 // For each i from 58 through 1, compute the
2259 // numerically lowest code with length i, and
2260 // store that code in n[i].
2261 //
2262
2263 long long c = 0;
2264
2265 for (int i = 58; i > 0; --i) {
2266 long long nc = ((c + n[i]) >> 1);
2267 n[i] = c;
2268 c = nc;
2269 }
2270
2271 //
2272 // hcode[i] contains the length, l, of the
2273 // code for symbol i. Assign the next available
2274 // code of length l to the symbol and store both
2275 // l and the code in hcode[i].
2276 //
2277
2278 for (int i = 0; i < HUF_ENCSIZE; ++i) {
2279 int l = static_cast<int>(hcode[i]);
2280
2281 if (l > 0) hcode[i] = l | (n[l]++ << 6);
2282 }
2283}
2284
2285//
2286// Compute Huffman codes (based on frq input) and store them in frq:
2287// - code structure is : [63:lsb - 6:msb] | [5-0: bit length];
2288// - max code length is 58 bits;
2289// - codes outside the range [im-iM] have a null length (unused values);
2290// - original frequencies are destroyed;
2291// - encoding tables are used by hufEncode() and hufBuildDecTable();
2292//
2293
2294struct FHeapCompare {
2295 bool operator()(long long *a, long long *b) { return *a > *b; }
2296};
2297
2298static bool hufBuildEncTable(
2299 long long *frq, // io: input frequencies [HUF_ENCSIZE], output table
2300 int *im, // o: min frq index
2301 int *iM) // o: max frq index
2302{
2303 //
2304 // This function assumes that when it is called, array frq
2305 // indicates the frequency of all possible symbols in the data
2306 // that are to be Huffman-encoded. (frq[i] contains the number
2307 // of occurrences of symbol i in the data.)
2308 //
2309 // The loop below does three things:
2310 //
2311 // 1) Finds the minimum and maximum indices that point
2312 // to non-zero entries in frq:
2313 //
2314 // frq[im] != 0, and frq[i] == 0 for all i < im
2315 // frq[iM] != 0, and frq[i] == 0 for all i > iM
2316 //
2317 // 2) Fills array fHeap with pointers to all non-zero
2318 // entries in frq.
2319 //
2320 // 3) Initializes array hlink such that hlink[i] == i
2321 // for all array entries.
2322 //
2323
2324 std::vector<int> hlink(HUF_ENCSIZE);
2325 std::vector<long long *> fHeap(HUF_ENCSIZE);
2326
2327 *im = 0;
2328
2329 while (!frq[*im]) (*im)++;
2330
2331 int nf = 0;
2332
2333 for (int i = *im; i < HUF_ENCSIZE; i++) {
2334 hlink[i] = i;
2335
2336 if (frq[i]) {
2337 fHeap[nf] = &frq[i];
2338 nf++;
2339 *iM = i;
2340 }
2341 }
2342
2343 //
2344 // Add a pseudo-symbol, with a frequency count of 1, to frq;
2345 // adjust the fHeap and hlink array accordingly. Function
2346 // hufEncode() uses the pseudo-symbol for run-length encoding.
2347 //
2348
2349 (*iM)++;
2350 frq[*iM] = 1;
2351 fHeap[nf] = &frq[*iM];
2352 nf++;
2353
2354 //
2355 // Build an array, scode, such that scode[i] contains the number
2356 // of bits assigned to symbol i. Conceptually this is done by
2357 // constructing a tree whose leaves are the symbols with non-zero
2358 // frequency:
2359 //
2360 // Make a heap that contains all symbols with a non-zero frequency,
2361 // with the least frequent symbol on top.
2362 //
2363 // Repeat until only one symbol is left on the heap:
2364 //
2365 // Take the two least frequent symbols off the top of the heap.
2366 // Create a new node that has first two nodes as children, and
2367 // whose frequency is the sum of the frequencies of the first
2368 // two nodes. Put the new node back into the heap.
2369 //
2370 // The last node left on the heap is the root of the tree. For each
2371 // leaf node, the distance between the root and the leaf is the length
2372 // of the code for the corresponding symbol.
2373 //
2374 // The loop below doesn't actually build the tree; instead we compute
2375 // the distances of the leaves from the root on the fly. When a new
2376 // node is added to the heap, then that node's descendants are linked
2377 // into a single linear list that starts at the new node, and the code
2378 // lengths of the descendants (that is, their distance from the root
2379 // of the tree) are incremented by one.
2380 //
2381
2382 std::make_heap(first: &fHeap[0], last: &fHeap[nf], comp: FHeapCompare());
2383
2384 std::vector<long long> scode(HUF_ENCSIZE);
2385 memset(s: scode.data(), c: 0, n: sizeof(long long) * HUF_ENCSIZE);
2386
2387 while (nf > 1) {
2388 //
2389 // Find the indices, mm and m, of the two smallest non-zero frq
2390 // values in fHeap, add the smallest frq to the second-smallest
2391 // frq, and remove the smallest frq value from fHeap.
2392 //
2393
2394 int mm = fHeap[0] - frq;
2395 std::pop_heap(first: &fHeap[0], last: &fHeap[nf], comp: FHeapCompare());
2396 --nf;
2397
2398 int m = fHeap[0] - frq;
2399 std::pop_heap(first: &fHeap[0], last: &fHeap[nf], comp: FHeapCompare());
2400
2401 frq[m] += frq[mm];
2402 std::push_heap(first: &fHeap[0], last: &fHeap[nf], comp: FHeapCompare());
2403
2404 //
2405 // The entries in scode are linked into lists with the
2406 // entries in hlink serving as "next" pointers and with
2407 // the end of a list marked by hlink[j] == j.
2408 //
2409 // Traverse the lists that start at scode[m] and scode[mm].
2410 // For each element visited, increment the length of the
2411 // corresponding code by one bit. (If we visit scode[j]
2412 // during the traversal, then the code for symbol j becomes
2413 // one bit longer.)
2414 //
2415 // Merge the lists that start at scode[m] and scode[mm]
2416 // into a single list that starts at scode[m].
2417 //
2418
2419 //
2420 // Add a bit to all codes in the first list.
2421 //
2422
2423 for (int j = m;; j = hlink[j]) {
2424 scode[j]++;
2425
2426 TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
2427
2428 if (hlink[j] == j) {
2429 //
2430 // Merge the two lists.
2431 //
2432
2433 hlink[j] = mm;
2434 break;
2435 }
2436 }
2437
2438 //
2439 // Add a bit to all codes in the second list
2440 //
2441
2442 for (int j = mm;; j = hlink[j]) {
2443 scode[j]++;
2444
2445 TINYEXR_CHECK_AND_RETURN_C(scode[j] <= 58, false);
2446
2447 if (hlink[j] == j) break;
2448 }
2449 }
2450
2451 //
2452 // Build a canonical Huffman code table, replacing the code
2453 // lengths in scode with (code, code length) pairs. Copy the
2454 // code table from scode into frq.
2455 //
2456
2457 hufCanonicalCodeTable(hcode: scode.data());
2458 memcpy(dest: frq, src: scode.data(), n: sizeof(long long) * HUF_ENCSIZE);
2459
2460 return true;
2461}
2462
2463//
2464// Pack an encoding table:
2465// - only code lengths, not actual codes, are stored
2466// - runs of zeroes are compressed as follows:
2467//
2468// unpacked packed
2469// --------------------------------
2470// 1 zero 0 (6 bits)
2471// 2 zeroes 59
2472// 3 zeroes 60
2473// 4 zeroes 61
2474// 5 zeroes 62
2475// n zeroes (6 or more) 63 n-6 (6 + 8 bits)
2476//
2477
2478const int SHORT_ZEROCODE_RUN = 59;
2479const int LONG_ZEROCODE_RUN = 63;
2480const int SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
2481const int LONGEST_LONG_RUN = 255 + SHORTEST_LONG_RUN;
2482
2483static void hufPackEncTable(
2484 const long long *hcode, // i : encoding table [HUF_ENCSIZE]
2485 int im, // i : min hcode index
2486 int iM, // i : max hcode index
2487 char **pcode) // o: ptr to packed table (updated)
2488{
2489 char *p = *pcode;
2490 long long c = 0;
2491 int lc = 0;
2492
2493 for (; im <= iM; im++) {
2494 int l = hufLength(code: hcode[im]);
2495
2496 if (l == 0) {
2497 int zerun = 1;
2498
2499 while ((im < iM) && (zerun < LONGEST_LONG_RUN)) {
2500 if (hufLength(code: hcode[im + 1]) > 0) break;
2501 im++;
2502 zerun++;
2503 }
2504
2505 if (zerun >= 2) {
2506 if (zerun >= SHORTEST_LONG_RUN) {
2507 outputBits(nBits: 6, bits: LONG_ZEROCODE_RUN, c, lc, out&: p);
2508 outputBits(nBits: 8, bits: zerun - SHORTEST_LONG_RUN, c, lc, out&: p);
2509 } else {
2510 outputBits(nBits: 6, bits: SHORT_ZEROCODE_RUN + zerun - 2, c, lc, out&: p);
2511 }
2512 continue;
2513 }
2514 }
2515
2516 outputBits(nBits: 6, bits: l, c, lc, out&: p);
2517 }
2518
2519 if (lc > 0) *p++ = (unsigned char)(c << (8 - lc));
2520
2521 *pcode = p;
2522}
2523
2524//
2525// Unpack an encoding table packed by hufPackEncTable():
2526//
2527
2528static bool hufUnpackEncTable(
2529 const char **pcode, // io: ptr to packed table (updated)
2530 int ni, // i : input size (in bytes)
2531 int im, // i : min hcode index
2532 int iM, // i : max hcode index
2533 long long *hcode) // o: encoding table [HUF_ENCSIZE]
2534{
2535 memset(s: hcode, c: 0, n: sizeof(long long) * HUF_ENCSIZE);
2536
2537 const char *p = *pcode;
2538 long long c = 0;
2539 int lc = 0;
2540
2541 for (; im <= iM; im++) {
2542 if (p - *pcode >= ni) {
2543 return false;
2544 }
2545
2546 long long l = hcode[im] = getBits(nBits: 6, c, lc, in&: p); // code length
2547
2548 if (l == (long long)LONG_ZEROCODE_RUN) {
2549 if (p - *pcode > ni) {
2550 return false;
2551 }
2552
2553 int zerun = getBits(nBits: 8, c, lc, in&: p) + SHORTEST_LONG_RUN;
2554
2555 if (im + zerun > iM + 1) {
2556 return false;
2557 }
2558
2559 while (zerun--) hcode[im++] = 0;
2560
2561 im--;
2562 } else if (l >= (long long)SHORT_ZEROCODE_RUN) {
2563 int zerun = l - SHORT_ZEROCODE_RUN + 2;
2564
2565 if (im + zerun > iM + 1) {
2566 return false;
2567 }
2568
2569 while (zerun--) hcode[im++] = 0;
2570
2571 im--;
2572 }
2573 }
2574
2575 *pcode = const_cast<char *>(p);
2576
2577 hufCanonicalCodeTable(hcode);
2578
2579 return true;
2580}
2581
2582//
2583// DECODING TABLE BUILDING
2584//
2585
2586//
2587// Clear a newly allocated decoding table so that it contains only zeroes.
2588//
2589
2590static void hufClearDecTable(HufDec *hdecod) // io: (allocated by caller)
2591// decoding table [HUF_DECSIZE]
2592{
2593 for (int i = 0; i < HUF_DECSIZE; i++) {
2594 hdecod[i].len = 0;
2595 hdecod[i].lit = 0;
2596 hdecod[i].p = NULL;
2597 }
2598 // memset(hdecod, 0, sizeof(HufDec) * HUF_DECSIZE);
2599}
2600
2601//
2602// Build a decoding hash table based on the encoding table hcode:
2603// - short codes (<= HUF_DECBITS) are resolved with a single table access;
2604// - long code entry allocations are not optimized, because long codes are
2605// unfrequent;
2606// - decoding tables are used by hufDecode();
2607//
2608
2609static bool hufBuildDecTable(const long long *hcode, // i : encoding table
2610 int im, // i : min index in hcode
2611 int iM, // i : max index in hcode
2612 HufDec *hdecod) // o: (allocated by caller)
2613// decoding table [HUF_DECSIZE]
2614{
2615 //
2616 // Init hashtable & loop on all codes.
2617 // Assumes that hufClearDecTable(hdecod) has already been called.
2618 //
2619
2620 for (; im <= iM; im++) {
2621 long long c = hufCode(code: hcode[im]);
2622 int l = hufLength(code: hcode[im]);
2623
2624 if (c >> l) {
2625 //
2626 // Error: c is supposed to be an l-bit code,
2627 // but c contains a value that is greater
2628 // than the largest l-bit number.
2629 //
2630
2631 // invalidTableEntry();
2632 return false;
2633 }
2634
2635 if (l > HUF_DECBITS) {
2636 //
2637 // Long code: add a secondary entry
2638 //
2639
2640 HufDec *pl = hdecod + (c >> (l - HUF_DECBITS));
2641
2642 if (pl->len) {
2643 //
2644 // Error: a short code has already
2645 // been stored in table entry *pl.
2646 //
2647
2648 // invalidTableEntry();
2649 return false;
2650 }
2651
2652 pl->lit++;
2653
2654 if (pl->p) {
2655 unsigned int *p = pl->p;
2656 pl->p = new unsigned int[pl->lit];
2657
2658 for (unsigned int i = 0; i < pl->lit - 1u; ++i) pl->p[i] = p[i];
2659
2660 delete[] p;
2661 } else {
2662 pl->p = new unsigned int[1];
2663 }
2664
2665 pl->p[pl->lit - 1] = im;
2666 } else if (l) {
2667 //
2668 // Short code: init all primary entries
2669 //
2670
2671 HufDec *pl = hdecod + (c << (HUF_DECBITS - l));
2672
2673 for (long long i = 1ULL << (HUF_DECBITS - l); i > 0; i--, pl++) {
2674 if (pl->len || pl->p) {
2675 //
2676 // Error: a short code or a long code has
2677 // already been stored in table entry *pl.
2678 //
2679
2680 // invalidTableEntry();
2681 return false;
2682 }
2683
2684 pl->len = l;
2685 pl->lit = im;
2686 }
2687 }
2688 }
2689
2690 return true;
2691}
2692
2693//
2694// Free the long code entries of a decoding table built by hufBuildDecTable()
2695//
2696
2697static void hufFreeDecTable(HufDec *hdecod) // io: Decoding table
2698{
2699 for (int i = 0; i < HUF_DECSIZE; i++) {
2700 if (hdecod[i].p) {
2701 delete[] hdecod[i].p;
2702 hdecod[i].p = 0;
2703 }
2704 }
2705}
2706
2707//
2708// ENCODING
2709//
2710
2711inline void outputCode(long long code, long long &c, int &lc, char *&out) {
2712 outputBits(nBits: hufLength(code), bits: hufCode(code), c, lc, out);
2713}
2714
2715inline void sendCode(long long sCode, int runCount, long long runCode,
2716 long long &c, int &lc, char *&out) {
2717 //
2718 // Output a run of runCount instances of the symbol sCount.
2719 // Output the symbols explicitly, or if that is shorter, output
2720 // the sCode symbol once followed by a runCode symbol and runCount
2721 // expressed as an 8-bit number.
2722 //
2723
2724 if (hufLength(code: sCode) + hufLength(code: runCode) + 8 < hufLength(code: sCode) * runCount) {
2725 outputCode(code: sCode, c, lc, out);
2726 outputCode(code: runCode, c, lc, out);
2727 outputBits(nBits: 8, bits: runCount, c, lc, out);
2728 } else {
2729 while (runCount-- >= 0) outputCode(code: sCode, c, lc, out);
2730 }
2731}
2732
2733//
2734// Encode (compress) ni values based on the Huffman encoding table hcode:
2735//
2736
2737static int hufEncode // return: output size (in bits)
2738 (const long long *hcode, // i : encoding table
2739 const unsigned short *in, // i : uncompressed input buffer
2740 const int ni, // i : input buffer size (in bytes)
2741 int rlc, // i : rl code
2742 char *out) // o: compressed output buffer
2743{
2744 char *outStart = out;
2745 long long c = 0; // bits not yet written to out
2746 int lc = 0; // number of valid bits in c (LSB)
2747 int s = in[0];
2748 int cs = 0;
2749
2750 //
2751 // Loop on input values
2752 //
2753
2754 for (int i = 1; i < ni; i++) {
2755 //
2756 // Count same values or send code
2757 //
2758
2759 if (s == in[i] && cs < 255) {
2760 cs++;
2761 } else {
2762 sendCode(sCode: hcode[s], runCount: cs, runCode: hcode[rlc], c, lc, out);
2763 cs = 0;
2764 }
2765
2766 s = in[i];
2767 }
2768
2769 //
2770 // Send remaining code
2771 //
2772
2773 sendCode(sCode: hcode[s], runCount: cs, runCode: hcode[rlc], c, lc, out);
2774
2775 if (lc) *out = (c << (8 - lc)) & 0xff;
2776
2777 return (out - outStart) * 8 + lc;
2778}
2779
2780//
2781// DECODING
2782//
2783
2784//
2785// In order to force the compiler to inline them,
2786// getChar() and getCode() are implemented as macros
2787// instead of "inline" functions.
2788//
2789
2790#define getChar(c, lc, in) \
2791 { \
2792 c = (c << 8) | *(unsigned char *)(in++); \
2793 lc += 8; \
2794 }
2795
2796#if 0
2797#define getCode(po, rlc, c, lc, in, out, ob, oe) \
2798 { \
2799 if (po == rlc) { \
2800 if (lc < 8) getChar(c, lc, in); \
2801 \
2802 lc -= 8; \
2803 \
2804 unsigned char cs = (c >> lc); \
2805 \
2806 if (out + cs > oe) return false; \
2807 \
2808 /* TinyEXR issue 78 */ \
2809 unsigned short s = out[-1]; \
2810 \
2811 while (cs-- > 0) *out++ = s; \
2812 } else if (out < oe) { \
2813 *out++ = po; \
2814 } else { \
2815 return false; \
2816 } \
2817 }
2818#else
2819static bool getCode(int po, int rlc, long long &c, int &lc, const char *&in,
2820 const char *in_end, unsigned short *&out,
2821 const unsigned short *ob, const unsigned short *oe) {
2822 (void)ob;
2823 if (po == rlc) {
2824 if (lc < 8) {
2825 /* TinyEXR issue 78 */
2826 /* TinyEXR issue 160. in + 1 -> in */
2827 if (in >= in_end) {
2828 return false;
2829 }
2830
2831 getChar(c, lc, in);
2832 }
2833
2834 lc -= 8;
2835
2836 unsigned char cs = (c >> lc);
2837
2838 if (out + cs > oe) return false;
2839
2840 // Bounds check for safety
2841 // Issue 100.
2842 if ((out - 1) < ob) return false;
2843 unsigned short s = out[-1];
2844
2845 while (cs-- > 0) *out++ = s;
2846 } else if (out < oe) {
2847 *out++ = po;
2848 } else {
2849 return false;
2850 }
2851 return true;
2852}
2853#endif
2854
2855//
2856// Decode (uncompress) ni bits based on encoding & decoding tables:
2857//
2858
2859static bool hufDecode(const long long *hcode, // i : encoding table
2860 const HufDec *hdecod, // i : decoding table
2861 const char *in, // i : compressed input buffer
2862 int ni, // i : input size (in bits)
2863 int rlc, // i : run-length code
2864 int no, // i : expected output size (in bytes)
2865 unsigned short *out) // o: uncompressed output buffer
2866{
2867 long long c = 0;
2868 int lc = 0;
2869 unsigned short *outb = out; // begin
2870 unsigned short *oe = out + no; // end
2871 const char *ie = in + (ni + 7) / 8; // input byte size
2872
2873 //
2874 // Loop on input bytes
2875 //
2876
2877 while (in < ie) {
2878 getChar(c, lc, in);
2879
2880 //
2881 // Access decoding table
2882 //
2883
2884 while (lc >= HUF_DECBITS) {
2885 const HufDec pl = hdecod[(c >> (lc - HUF_DECBITS)) & HUF_DECMASK];
2886
2887 if (pl.len) {
2888 //
2889 // Get short code
2890 //
2891
2892 lc -= pl.len;
2893 // std::cout << "lit = " << pl.lit << std::endl;
2894 // std::cout << "rlc = " << rlc << std::endl;
2895 // std::cout << "c = " << c << std::endl;
2896 // std::cout << "lc = " << lc << std::endl;
2897 // std::cout << "in = " << in << std::endl;
2898 // std::cout << "out = " << out << std::endl;
2899 // std::cout << "oe = " << oe << std::endl;
2900 if (!getCode(po: pl.lit, rlc, c, lc, in, in_end: ie, out, ob: outb, oe)) {
2901 return false;
2902 }
2903 } else {
2904 if (!pl.p) {
2905 return false;
2906 }
2907 // invalidCode(); // wrong code
2908
2909 //
2910 // Search long code
2911 //
2912
2913 unsigned int j;
2914
2915 for (j = 0; j < pl.lit; j++) {
2916 int l = hufLength(code: hcode[pl.p[j]]);
2917
2918 while (lc < l && in < ie) // get more bits
2919 getChar(c, lc, in);
2920
2921 if (lc >= l) {
2922 if (hufCode(code: hcode[pl.p[j]]) ==
2923 ((c >> (lc - l)) & (((long long)(1) << l) - 1))) {
2924 //
2925 // Found : get long code
2926 //
2927
2928 lc -= l;
2929 if (!getCode(po: pl.p[j], rlc, c, lc, in, in_end: ie, out, ob: outb, oe)) {
2930 return false;
2931 }
2932 break;
2933 }
2934 }
2935 }
2936
2937 if (j == pl.lit) {
2938 return false;
2939 // invalidCode(); // Not found
2940 }
2941 }
2942 }
2943 }
2944
2945 //
2946 // Get remaining (short) codes
2947 //
2948
2949 int i = (8 - ni) & 7;
2950 c >>= i;
2951 lc -= i;
2952
2953 while (lc > 0) {
2954 const HufDec pl = hdecod[(c << (HUF_DECBITS - lc)) & HUF_DECMASK];
2955
2956 if (pl.len) {
2957 lc -= pl.len;
2958 if (!getCode(po: pl.lit, rlc, c, lc, in, in_end: ie, out, ob: outb, oe)) {
2959 return false;
2960 }
2961 } else {
2962 return false;
2963 // invalidCode(); // wrong (long) code
2964 }
2965 }
2966
2967 if (out - outb != no) {
2968 return false;
2969 }
2970 // notEnoughData ();
2971
2972 return true;
2973}
2974
2975static void countFrequencies(std::vector<long long> &freq,
2976 const unsigned short data[/*n*/], int n) {
2977 for (int i = 0; i < HUF_ENCSIZE; ++i) freq[i] = 0;
2978
2979 for (int i = 0; i < n; ++i) ++freq[data[i]];
2980}
2981
2982static void writeUInt(char buf[4], unsigned int i) {
2983 unsigned char *b = (unsigned char *)buf;
2984
2985 b[0] = i;
2986 b[1] = i >> 8;
2987 b[2] = i >> 16;
2988 b[3] = i >> 24;
2989}
2990
2991static unsigned int readUInt(const char buf[4]) {
2992 const unsigned char *b = (const unsigned char *)buf;
2993
2994 return (b[0] & 0x000000ff) | ((b[1] << 8) & 0x0000ff00) |
2995 ((b[2] << 16) & 0x00ff0000) | ((b[3] << 24) & 0xff000000);
2996}
2997
2998//
2999// EXTERNAL INTERFACE
3000//
3001
3002static int hufCompress(const unsigned short raw[], int nRaw,
3003 char compressed[]) {
3004 if (nRaw == 0) return 0;
3005
3006 std::vector<long long> freq(HUF_ENCSIZE);
3007
3008 countFrequencies(freq, data: raw, n: nRaw);
3009
3010 int im = 0;
3011 int iM = 0;
3012 hufBuildEncTable(frq: freq.data(), im: &im, iM: &iM);
3013
3014 char *tableStart = compressed + 20;
3015 char *tableEnd = tableStart;
3016 hufPackEncTable(hcode: freq.data(), im, iM, pcode: &tableEnd);
3017 int tableLength = tableEnd - tableStart;
3018
3019 char *dataStart = tableEnd;
3020 int nBits = hufEncode(hcode: freq.data(), in: raw, ni: nRaw, rlc: iM, out: dataStart);
3021 int data_length = (nBits + 7) / 8;
3022
3023 writeUInt(buf: compressed, i: im);
3024 writeUInt(buf: compressed + 4, i: iM);
3025 writeUInt(buf: compressed + 8, i: tableLength);
3026 writeUInt(buf: compressed + 12, i: nBits);
3027 writeUInt(buf: compressed + 16, i: 0); // room for future extensions
3028
3029 return dataStart + data_length - compressed;
3030}
3031
3032static bool hufUncompress(const char compressed[], int nCompressed,
3033 std::vector<unsigned short> *raw) {
3034 if (nCompressed == 0) {
3035 if (raw->size() != 0) return false;
3036
3037 return false;
3038 }
3039
3040 int im = readUInt(buf: compressed);
3041 int iM = readUInt(buf: compressed + 4);
3042 // int tableLength = readUInt (compressed + 8);
3043 int nBits = readUInt(buf: compressed + 12);
3044
3045 if (im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE) return false;
3046
3047 const char *ptr = compressed + 20;
3048
3049 //
3050 // Fast decoder needs at least 2x64-bits of compressed data, and
3051 // needs to be run-able on this platform. Otherwise, fall back
3052 // to the original decoder
3053 //
3054
3055 // if (FastHufDecoder::enabled() && nBits > 128)
3056 //{
3057 // FastHufDecoder fhd (ptr, nCompressed - (ptr - compressed), im, iM, iM);
3058 // fhd.decode ((unsigned char*)ptr, nBits, raw, nRaw);
3059 //}
3060 // else
3061 {
3062 std::vector<long long> freq(HUF_ENCSIZE);
3063 std::vector<HufDec> hdec(HUF_DECSIZE);
3064
3065 hufClearDecTable(hdecod: &hdec.at(n: 0));
3066
3067 hufUnpackEncTable(pcode: &ptr, ni: nCompressed - (ptr - compressed), im, iM,
3068 hcode: &freq.at(n: 0));
3069
3070 {
3071 if (nBits > 8 * (nCompressed - (ptr - compressed))) {
3072 return false;
3073 }
3074
3075 hufBuildDecTable(hcode: &freq.at(n: 0), im, iM, hdecod: &hdec.at(n: 0));
3076 hufDecode(hcode: &freq.at(n: 0), hdecod: &hdec.at(n: 0), in: ptr, ni: nBits, rlc: iM, no: raw->size(),
3077 out: raw->data());
3078 }
3079 // catch (...)
3080 //{
3081 // hufFreeDecTable (hdec);
3082 // throw;
3083 //}
3084
3085 hufFreeDecTable(hdecod: &hdec.at(n: 0));
3086 }
3087
3088 return true;
3089}
3090
3091//
3092// Functions to compress the range of values in the pixel data
3093//
3094
3095const int USHORT_RANGE = (1 << 16);
3096const int BITMAP_SIZE = (USHORT_RANGE >> 3);
3097
3098static void bitmapFromData(const unsigned short data[/*nData*/], int nData,
3099 unsigned char bitmap[BITMAP_SIZE],
3100 unsigned short &minNonZero,
3101 unsigned short &maxNonZero) {
3102 for (int i = 0; i < BITMAP_SIZE; ++i) bitmap[i] = 0;
3103
3104 for (int i = 0; i < nData; ++i) bitmap[data[i] >> 3] |= (1 << (data[i] & 7));
3105
3106 bitmap[0] &= ~1; // zero is not explicitly stored in
3107 // the bitmap; we assume that the
3108 // data always contain zeroes
3109 minNonZero = BITMAP_SIZE - 1;
3110 maxNonZero = 0;
3111
3112 for (int i = 0; i < BITMAP_SIZE; ++i) {
3113 if (bitmap[i]) {
3114 if (minNonZero > i) minNonZero = i;
3115 if (maxNonZero < i) maxNonZero = i;
3116 }
3117 }
3118}
3119
3120static unsigned short forwardLutFromBitmap(
3121 const unsigned char bitmap[BITMAP_SIZE], unsigned short lut[USHORT_RANGE]) {
3122 int k = 0;
3123
3124 for (int i = 0; i < USHORT_RANGE; ++i) {
3125 if ((i == 0) || (bitmap[i >> 3] & (1 << (i & 7))))
3126 lut[i] = k++;
3127 else
3128 lut[i] = 0;
3129 }
3130
3131 return k - 1; // maximum value stored in lut[],
3132} // i.e. number of ones in bitmap minus 1
3133
3134static unsigned short reverseLutFromBitmap(
3135 const unsigned char bitmap[BITMAP_SIZE], unsigned short lut[USHORT_RANGE]) {
3136 int k = 0;
3137
3138 for (int i = 0; i < USHORT_RANGE; ++i) {
3139 if ((i == 0) || (bitmap[i >> 3] & (1 << (i & 7)))) lut[k++] = i;
3140 }
3141
3142 int n = k - 1;
3143
3144 while (k < USHORT_RANGE) lut[k++] = 0;
3145
3146 return n; // maximum k where lut[k] is non-zero,
3147} // i.e. number of ones in bitmap minus 1
3148
3149static void applyLut(const unsigned short lut[USHORT_RANGE],
3150 unsigned short data[/*nData*/], int nData) {
3151 for (int i = 0; i < nData; ++i) data[i] = lut[data[i]];
3152}
3153
3154#ifdef __clang__
3155#pragma clang diagnostic pop
3156#endif // __clang__
3157
3158#ifdef _MSC_VER
3159#pragma warning(pop)
3160#endif
3161
3162static bool CompressPiz(unsigned char *outPtr, unsigned int *outSize,
3163 const unsigned char *inPtr, size_t inSize,
3164 const std::vector<ChannelInfo> &channelInfo,
3165 int data_width, int num_lines) {
3166 std::vector<unsigned char> bitmap(BITMAP_SIZE);
3167 unsigned short minNonZero;
3168 unsigned short maxNonZero;
3169
3170#if !TINYEXR_LITTLE_ENDIAN
3171 // @todo { PIZ compression on BigEndian architecture. }
3172 return false;
3173#endif
3174
3175 // Assume `inSize` is multiple of 2 or 4.
3176 std::vector<unsigned short> tmpBuffer(inSize / sizeof(unsigned short));
3177
3178 std::vector<PIZChannelData> channelData(channelInfo.size());
3179 unsigned short *tmpBufferEnd = &tmpBuffer.at(n: 0);
3180
3181 for (size_t c = 0; c < channelData.size(); c++) {
3182 PIZChannelData &cd = channelData[c];
3183
3184 cd.start = tmpBufferEnd;
3185 cd.end = cd.start;
3186
3187 cd.nx = data_width;
3188 cd.ny = num_lines;
3189 // cd.ys = c.channel().ySampling;
3190
3191 size_t pixelSize = sizeof(int); // UINT and FLOAT
3192 if (channelInfo[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) {
3193 pixelSize = sizeof(short);
3194 }
3195
3196 cd.size = static_cast<int>(pixelSize / sizeof(short));
3197
3198 tmpBufferEnd += cd.nx * cd.ny * cd.size;
3199 }
3200
3201 const unsigned char *ptr = inPtr;
3202 for (int y = 0; y < num_lines; ++y) {
3203 for (size_t i = 0; i < channelData.size(); ++i) {
3204 PIZChannelData &cd = channelData[i];
3205
3206 // if (modp (y, cd.ys) != 0)
3207 // continue;
3208
3209 size_t n = static_cast<size_t>(cd.nx * cd.size);
3210 memcpy(dest: cd.end, src: ptr, n: n * sizeof(unsigned short));
3211 ptr += n * sizeof(unsigned short);
3212 cd.end += n;
3213 }
3214 }
3215
3216 bitmapFromData(data: &tmpBuffer.at(n: 0), nData: static_cast<int>(tmpBuffer.size()),
3217 bitmap: bitmap.data(), minNonZero, maxNonZero);
3218
3219 std::vector<unsigned short> lut(USHORT_RANGE);
3220 unsigned short maxValue = forwardLutFromBitmap(bitmap: bitmap.data(), lut: lut.data());
3221 applyLut(lut: lut.data(), data: &tmpBuffer.at(n: 0), nData: static_cast<int>(tmpBuffer.size()));
3222
3223 //
3224 // Store range compression info in _outBuffer
3225 //
3226
3227 char *buf = reinterpret_cast<char *>(outPtr);
3228
3229 memcpy(dest: buf, src: &minNonZero, n: sizeof(unsigned short));
3230 buf += sizeof(unsigned short);
3231 memcpy(dest: buf, src: &maxNonZero, n: sizeof(unsigned short));
3232 buf += sizeof(unsigned short);
3233
3234 if (minNonZero <= maxNonZero) {
3235 memcpy(dest: buf, src: reinterpret_cast<char *>(&bitmap[0] + minNonZero),
3236 n: maxNonZero - minNonZero + 1);
3237 buf += maxNonZero - minNonZero + 1;
3238 }
3239
3240 //
3241 // Apply wavelet encoding
3242 //
3243
3244 for (size_t i = 0; i < channelData.size(); ++i) {
3245 PIZChannelData &cd = channelData[i];
3246
3247 for (int j = 0; j < cd.size; ++j) {
3248 wav2Encode(in: cd.start + j, nx: cd.nx, ox: cd.size, ny: cd.ny, oy: cd.nx * cd.size,
3249 mx: maxValue);
3250 }
3251 }
3252
3253 //
3254 // Apply Huffman encoding; append the result to _outBuffer
3255 //
3256
3257 // length header(4byte), then huff data. Initialize length header with zero,
3258 // then later fill it by `length`.
3259 char *lengthPtr = buf;
3260 int zero = 0;
3261 memcpy(dest: buf, src: &zero, n: sizeof(int));
3262 buf += sizeof(int);
3263
3264 int length =
3265 hufCompress(raw: &tmpBuffer.at(n: 0), nRaw: static_cast<int>(tmpBuffer.size()), compressed: buf);
3266 memcpy(dest: lengthPtr, src: &length, n: sizeof(int));
3267
3268 (*outSize) = static_cast<unsigned int>(
3269 (reinterpret_cast<unsigned char *>(buf) - outPtr) +
3270 static_cast<unsigned int>(length));
3271
3272 // Use uncompressed data when compressed data is larger than uncompressed.
3273 // (Issue 40)
3274 if ((*outSize) >= inSize) {
3275 (*outSize) = static_cast<unsigned int>(inSize);
3276 memcpy(dest: outPtr, src: inPtr, n: inSize);
3277 }
3278 return true;
3279}
3280
3281static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr,
3282 size_t tmpBufSizeInBytes, size_t inLen, int num_channels,
3283 const EXRChannelInfo *channels, int data_width,
3284 int num_lines) {
3285 if (inLen == tmpBufSizeInBytes) {
3286 // Data is not compressed(Issue 40).
3287 memcpy(dest: outPtr, src: inPtr, n: inLen);
3288 return true;
3289 }
3290
3291 std::vector<unsigned char> bitmap(BITMAP_SIZE);
3292 unsigned short minNonZero;
3293 unsigned short maxNonZero;
3294
3295#if !TINYEXR_LITTLE_ENDIAN
3296 // @todo { PIZ compression on BigEndian architecture. }
3297 return false;
3298#endif
3299
3300 memset(s: bitmap.data(), c: 0, n: BITMAP_SIZE);
3301
3302 if (inLen < 4) {
3303 return false;
3304 }
3305
3306 size_t readLen = 0;
3307
3308 const unsigned char *ptr = inPtr;
3309 // minNonZero = *(reinterpret_cast<const unsigned short *>(ptr));
3310 tinyexr::cpy2(dst_val: &minNonZero, src_val: reinterpret_cast<const unsigned short *>(ptr));
3311 // maxNonZero = *(reinterpret_cast<const unsigned short *>(ptr + 2));
3312 tinyexr::cpy2(dst_val: &maxNonZero, src_val: reinterpret_cast<const unsigned short *>(ptr + 2));
3313 ptr += 4;
3314 readLen += 4;
3315
3316 if (maxNonZero >= BITMAP_SIZE) {
3317 return false;
3318 }
3319
3320 //printf("maxNonZero = %d\n", maxNonZero);
3321 //printf("minNonZero = %d\n", minNonZero);
3322 //printf("len = %d\n", (maxNonZero - minNonZero + 1));
3323 //printf("BITMAPSIZE - min = %d\n", (BITMAP_SIZE - minNonZero));
3324
3325 if (minNonZero <= maxNonZero) {
3326 if (((maxNonZero - minNonZero + 1) + readLen) > inLen) {
3327 // Input too short
3328 return false;
3329 }
3330
3331 memcpy(dest: reinterpret_cast<char *>(&bitmap[0] + minNonZero), src: ptr,
3332 n: maxNonZero - minNonZero + 1);
3333 ptr += maxNonZero - minNonZero + 1;
3334 readLen += maxNonZero - minNonZero + 1;
3335 } else {
3336 // Issue 194
3337 if ((minNonZero == (BITMAP_SIZE - 1)) && (maxNonZero == 0)) {
3338 // OK. all pixels are zero. And no need to read `bitmap` data.
3339 } else {
3340 // invalid minNonZero/maxNonZero combination.
3341 return false;
3342 }
3343 }
3344
3345 std::vector<unsigned short> lut(USHORT_RANGE);
3346 memset(s: lut.data(), c: 0, n: sizeof(unsigned short) * USHORT_RANGE);
3347 unsigned short maxValue = reverseLutFromBitmap(bitmap: bitmap.data(), lut: lut.data());
3348
3349 //
3350 // Huffman decoding
3351 //
3352
3353 if ((readLen + 4) > inLen) {
3354 return false;
3355 }
3356
3357 int length=0;
3358
3359 // length = *(reinterpret_cast<const int *>(ptr));
3360 tinyexr::cpy4(dst_val: &length, src_val: reinterpret_cast<const int *>(ptr));
3361 ptr += sizeof(int);
3362
3363 if (size_t((ptr - inPtr) + length) > inLen) {
3364 return false;
3365 }
3366
3367 std::vector<unsigned short> tmpBuffer(tmpBufSizeInBytes / sizeof(unsigned short));
3368 hufUncompress(compressed: reinterpret_cast<const char *>(ptr), nCompressed: length, raw: &tmpBuffer);
3369
3370 //
3371 // Wavelet decoding
3372 //
3373
3374 std::vector<PIZChannelData> channelData(static_cast<size_t>(num_channels));
3375
3376 unsigned short *tmpBufferEnd = &tmpBuffer.at(n: 0);
3377
3378 for (size_t i = 0; i < static_cast<size_t>(num_channels); ++i) {
3379 const EXRChannelInfo &chan = channels[i];
3380
3381 size_t pixelSize = sizeof(int); // UINT and FLOAT
3382 if (chan.pixel_type == TINYEXR_PIXELTYPE_HALF) {
3383 pixelSize = sizeof(short);
3384 }
3385
3386 channelData[i].start = tmpBufferEnd;
3387 channelData[i].end = channelData[i].start;
3388 channelData[i].nx = data_width;
3389 channelData[i].ny = num_lines;
3390 // channelData[i].ys = 1;
3391 channelData[i].size = static_cast<int>(pixelSize / sizeof(short));
3392
3393 tmpBufferEnd += channelData[i].nx * channelData[i].ny * channelData[i].size;
3394 }
3395
3396 for (size_t i = 0; i < channelData.size(); ++i) {
3397 PIZChannelData &cd = channelData[i];
3398
3399 for (int j = 0; j < cd.size; ++j) {
3400 wav2Decode(in: cd.start + j, nx: cd.nx, ox: cd.size, ny: cd.ny, oy: cd.nx * cd.size,
3401 mx: maxValue);
3402 }
3403 }
3404
3405 //
3406 // Expand the pixel data to their original range
3407 //
3408
3409 applyLut(lut: lut.data(), data: &tmpBuffer.at(n: 0), nData: static_cast<int>(tmpBufSizeInBytes / sizeof(unsigned short)));
3410
3411 for (int y = 0; y < num_lines; y++) {
3412 for (size_t i = 0; i < channelData.size(); ++i) {
3413 PIZChannelData &cd = channelData[i];
3414
3415 // if (modp (y, cd.ys) != 0)
3416 // continue;
3417
3418 size_t n = static_cast<size_t>(cd.nx * cd.size);
3419 memcpy(dest: outPtr, src: cd.end, n: static_cast<size_t>(n * sizeof(unsigned short)));
3420 outPtr += n * sizeof(unsigned short);
3421 cd.end += n;
3422 }
3423 }
3424
3425 return true;
3426}
3427#endif // TINYEXR_USE_PIZ
3428
3429#if TINYEXR_USE_ZFP
3430
3431struct ZFPCompressionParam {
3432 double rate;
3433 unsigned int precision;
3434 unsigned int __pad0;
3435 double tolerance;
3436 int type; // TINYEXR_ZFP_COMPRESSIONTYPE_*
3437 unsigned int __pad1;
3438
3439 ZFPCompressionParam() {
3440 type = TINYEXR_ZFP_COMPRESSIONTYPE_RATE;
3441 rate = 2.0;
3442 precision = 0;
3443 tolerance = 0.0;
3444 }
3445};
3446
3447static bool FindZFPCompressionParam(ZFPCompressionParam *param,
3448 const EXRAttribute *attributes,
3449 int num_attributes, std::string *err) {
3450 bool foundType = false;
3451
3452 for (int i = 0; i < num_attributes; i++) {
3453 if ((strcmp(attributes[i].name, "zfpCompressionType") == 0)) {
3454 if (attributes[i].size == 1) {
3455 param->type = static_cast<int>(attributes[i].value[0]);
3456 foundType = true;
3457 break;
3458 } else {
3459 if (err) {
3460 (*err) +=
3461 "zfpCompressionType attribute must be uchar(1 byte) type.\n";
3462 }
3463 return false;
3464 }
3465 }
3466 }
3467
3468 if (!foundType) {
3469 if (err) {
3470 (*err) += "`zfpCompressionType` attribute not found.\n";
3471 }
3472 return false;
3473 }
3474
3475 if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) {
3476 for (int i = 0; i < num_attributes; i++) {
3477 if ((strcmp(attributes[i].name, "zfpCompressionRate") == 0) &&
3478 (attributes[i].size == 8)) {
3479 param->rate = *(reinterpret_cast<double *>(attributes[i].value));
3480 return true;
3481 }
3482 }
3483
3484 if (err) {
3485 (*err) += "`zfpCompressionRate` attribute not found.\n";
3486 }
3487
3488 } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) {
3489 for (int i = 0; i < num_attributes; i++) {
3490 if ((strcmp(attributes[i].name, "zfpCompressionPrecision") == 0) &&
3491 (attributes[i].size == 4)) {
3492 param->rate = *(reinterpret_cast<int *>(attributes[i].value));
3493 return true;
3494 }
3495 }
3496
3497 if (err) {
3498 (*err) += "`zfpCompressionPrecision` attribute not found.\n";
3499 }
3500
3501 } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) {
3502 for (int i = 0; i < num_attributes; i++) {
3503 if ((strcmp(attributes[i].name, "zfpCompressionTolerance") == 0) &&
3504 (attributes[i].size == 8)) {
3505 param->tolerance = *(reinterpret_cast<double *>(attributes[i].value));
3506 return true;
3507 }
3508 }
3509
3510 if (err) {
3511 (*err) += "`zfpCompressionTolerance` attribute not found.\n";
3512 }
3513 } else {
3514 if (err) {
3515 (*err) += "Unknown value specified for `zfpCompressionType`.\n";
3516 }
3517 }
3518
3519 return false;
3520}
3521
3522// Assume pixel format is FLOAT for all channels.
3523static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines,
3524 size_t num_channels, const unsigned char *src,
3525 unsigned long src_size,
3526 const ZFPCompressionParam &param) {
3527 size_t uncompressed_size =
3528 size_t(dst_width) * size_t(dst_num_lines) * num_channels;
3529
3530 if (uncompressed_size == src_size) {
3531 // Data is not compressed(Issue 40).
3532 memcpy(dst, src, src_size);
3533 }
3534
3535 zfp_stream *zfp = NULL;
3536 zfp_field *field = NULL;
3537
3538 TINYEXR_CHECK_AND_RETURN_C((dst_width % 4) == 0, false);
3539 TINYEXR_CHECK_AND_RETURN_C((dst_num_lines % 4) == 0, false);
3540
3541 if ((size_t(dst_width) & 3U) || (size_t(dst_num_lines) & 3U)) {
3542 return false;
3543 }
3544
3545 field =
3546 zfp_field_2d(reinterpret_cast<void *>(const_cast<unsigned char *>(src)),
3547 zfp_type_float, static_cast<unsigned int>(dst_width),
3548 static_cast<unsigned int>(dst_num_lines) *
3549 static_cast<unsigned int>(num_channels));
3550 zfp = zfp_stream_open(NULL);
3551
3552 if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) {
3553 zfp_stream_set_rate(zfp, param.rate, zfp_type_float, /* dimension */ 2,
3554 /* write random access */ 0);
3555 } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) {
3556 zfp_stream_set_precision(zfp, param.precision);
3557 } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) {
3558 zfp_stream_set_accuracy(zfp, param.tolerance);
3559 } else {
3560 return false;
3561 }
3562
3563 size_t buf_size = zfp_stream_maximum_size(zfp, field);
3564 std::vector<unsigned char> buf(buf_size);
3565 memcpy(&buf.at(0), src, src_size);
3566
3567 bitstream *stream = stream_open(&buf.at(0), buf_size);
3568 zfp_stream_set_bit_stream(zfp, stream);
3569 zfp_stream_rewind(zfp);
3570
3571 size_t image_size = size_t(dst_width) * size_t(dst_num_lines);
3572
3573 for (size_t c = 0; c < size_t(num_channels); c++) {
3574 // decompress 4x4 pixel block.
3575 for (size_t y = 0; y < size_t(dst_num_lines); y += 4) {
3576 for (size_t x = 0; x < size_t(dst_width); x += 4) {
3577 float fblock[16];
3578 zfp_decode_block_float_2(zfp, fblock);
3579 for (size_t j = 0; j < 4; j++) {
3580 for (size_t i = 0; i < 4; i++) {
3581 dst[c * image_size + ((y + j) * size_t(dst_width) + (x + i))] =
3582 fblock[j * 4 + i];
3583 }
3584 }
3585 }
3586 }
3587 }
3588
3589 zfp_field_free(field);
3590 zfp_stream_close(zfp);
3591 stream_close(stream);
3592
3593 return true;
3594}
3595
3596// Assume pixel format is FLOAT for all channels.
3597static bool CompressZfp(std::vector<unsigned char> *outBuf,
3598 unsigned int *outSize, const float *inPtr, int width,
3599 int num_lines, int num_channels,
3600 const ZFPCompressionParam &param) {
3601 zfp_stream *zfp = NULL;
3602 zfp_field *field = NULL;
3603
3604 TINYEXR_CHECK_AND_RETURN_C((width % 4) == 0, false);
3605 TINYEXR_CHECK_AND_RETURN_C((num_lines % 4) == 0, false);
3606
3607 if ((size_t(width) & 3U) || (size_t(num_lines) & 3U)) {
3608 return false;
3609 }
3610
3611 // create input array.
3612 field = zfp_field_2d(reinterpret_cast<void *>(const_cast<float *>(inPtr)),
3613 zfp_type_float, static_cast<unsigned int>(width),
3614 static_cast<unsigned int>(num_lines * num_channels));
3615
3616 zfp = zfp_stream_open(NULL);
3617
3618 if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) {
3619 zfp_stream_set_rate(zfp, param.rate, zfp_type_float, 2, 0);
3620 } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) {
3621 zfp_stream_set_precision(zfp, param.precision);
3622 } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) {
3623 zfp_stream_set_accuracy(zfp, param.tolerance);
3624 } else {
3625 return false;
3626 }
3627
3628 size_t buf_size = zfp_stream_maximum_size(zfp, field);
3629
3630 outBuf->resize(buf_size);
3631
3632 bitstream *stream = stream_open(&outBuf->at(0), buf_size);
3633 zfp_stream_set_bit_stream(zfp, stream);
3634 zfp_field_free(field);
3635
3636 size_t image_size = size_t(width) * size_t(num_lines);
3637
3638 for (size_t c = 0; c < size_t(num_channels); c++) {
3639 // compress 4x4 pixel block.
3640 for (size_t y = 0; y < size_t(num_lines); y += 4) {
3641 for (size_t x = 0; x < size_t(width); x += 4) {
3642 float fblock[16];
3643 for (size_t j = 0; j < 4; j++) {
3644 for (size_t i = 0; i < 4; i++) {
3645 fblock[j * 4 + i] =
3646 inPtr[c * image_size + ((y + j) * size_t(width) + (x + i))];
3647 }
3648 }
3649 zfp_encode_block_float_2(zfp, fblock);
3650 }
3651 }
3652 }
3653
3654 zfp_stream_flush(zfp);
3655 (*outSize) = static_cast<unsigned int>(zfp_stream_compressed_size(zfp));
3656
3657 zfp_stream_close(zfp);
3658
3659 return true;
3660}
3661
3662#endif
3663
3664//
3665// -----------------------------------------------------------------
3666//
3667
3668// heuristics
3669#define TINYEXR_DIMENSION_THRESHOLD (1024 * 8192)
3670
3671// TODO(syoyo): Refactor function arguments.
3672static bool DecodePixelData(/* out */ unsigned char **out_images,
3673 const int *requested_pixel_types,
3674 const unsigned char *data_ptr, size_t data_len,
3675 int compression_type, int line_order, int width,
3676 int height, int x_stride, int y, int line_no,
3677 int num_lines, size_t pixel_data_size,
3678 size_t num_attributes,
3679 const EXRAttribute *attributes, size_t num_channels,
3680 const EXRChannelInfo *channels,
3681 const std::vector<size_t> &channel_offset_list) {
3682 if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { // PIZ
3683#if TINYEXR_USE_PIZ
3684 if ((width == 0) || (num_lines == 0) || (pixel_data_size == 0)) {
3685 // Invalid input #90
3686 return false;
3687 }
3688
3689 // Allocate original data size.
3690 std::vector<unsigned char> outBuf(static_cast<size_t>(
3691 static_cast<size_t>(width * num_lines) * pixel_data_size));
3692 size_t tmpBufLen = outBuf.size();
3693
3694 bool ret = tinyexr::DecompressPiz(
3695 outPtr: reinterpret_cast<unsigned char *>(&outBuf.at(n: 0)), inPtr: data_ptr, tmpBufSizeInBytes: tmpBufLen,
3696 inLen: data_len, num_channels: static_cast<int>(num_channels), channels, data_width: width, num_lines);
3697
3698 if (!ret) {
3699 return false;
3700 }
3701
3702 // For PIZ_COMPRESSION:
3703 // pixel sample data for channel 0 for scanline 0
3704 // pixel sample data for channel 1 for scanline 0
3705 // pixel sample data for channel ... for scanline 0
3706 // pixel sample data for channel n for scanline 0
3707 // pixel sample data for channel 0 for scanline 1
3708 // pixel sample data for channel 1 for scanline 1
3709 // pixel sample data for channel ... for scanline 1
3710 // pixel sample data for channel n for scanline 1
3711 // ...
3712 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
3713 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
3714 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3715 const unsigned short *line_ptr = reinterpret_cast<unsigned short *>(
3716 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
3717 channel_offset_list[c] * static_cast<size_t>(width)));
3718 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3719 FP16 hf;
3720
3721 // hf.u = line_ptr[u];
3722 // use `cpy` to avoid unaligned memory access when compiler's
3723 // optimization is on.
3724 tinyexr::cpy2(dst_val: &(hf.u), src_val: line_ptr + u);
3725
3726 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&hf.u));
3727
3728 if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) {
3729 unsigned short *image =
3730 reinterpret_cast<unsigned short **>(out_images)[c];
3731 if (line_order == 0) {
3732 image += (static_cast<size_t>(line_no) + v) *
3733 static_cast<size_t>(x_stride) +
3734 u;
3735 } else {
3736 image += static_cast<size_t>(
3737 (height - 1 - (line_no + static_cast<int>(v)))) *
3738 static_cast<size_t>(x_stride) +
3739 u;
3740 }
3741 *image = hf.u;
3742 } else { // HALF -> FLOAT
3743 FP32 f32 = half_to_float(h: hf);
3744 float *image = reinterpret_cast<float **>(out_images)[c];
3745 size_t offset = 0;
3746 if (line_order == 0) {
3747 offset = (static_cast<size_t>(line_no) + v) *
3748 static_cast<size_t>(x_stride) +
3749 u;
3750 } else {
3751 offset = static_cast<size_t>(
3752 (height - 1 - (line_no + static_cast<int>(v)))) *
3753 static_cast<size_t>(x_stride) +
3754 u;
3755 }
3756 image += offset;
3757 *image = f32.f;
3758 }
3759 }
3760 }
3761 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
3762 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
3763
3764 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3765 const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
3766 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
3767 channel_offset_list[c] * static_cast<size_t>(width)));
3768 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3769 unsigned int val;
3770 // val = line_ptr[u];
3771 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
3772
3773 tinyexr::swap4(val: &val);
3774
3775 unsigned int *image =
3776 reinterpret_cast<unsigned int **>(out_images)[c];
3777 if (line_order == 0) {
3778 image += (static_cast<size_t>(line_no) + v) *
3779 static_cast<size_t>(x_stride) +
3780 u;
3781 } else {
3782 image += static_cast<size_t>(
3783 (height - 1 - (line_no + static_cast<int>(v)))) *
3784 static_cast<size_t>(x_stride) +
3785 u;
3786 }
3787 *image = val;
3788 }
3789 }
3790 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
3791 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
3792 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3793 const float *line_ptr = reinterpret_cast<float *>(&outBuf.at(
3794 n: v * pixel_data_size * static_cast<size_t>(width) +
3795 channel_offset_list[c] * static_cast<size_t>(width)));
3796 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3797 float val;
3798 // val = line_ptr[u];
3799 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
3800
3801 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&val));
3802
3803 float *image = reinterpret_cast<float **>(out_images)[c];
3804 if (line_order == 0) {
3805 image += (static_cast<size_t>(line_no) + v) *
3806 static_cast<size_t>(x_stride) +
3807 u;
3808 } else {
3809 image += static_cast<size_t>(
3810 (height - 1 - (line_no + static_cast<int>(v)))) *
3811 static_cast<size_t>(x_stride) +
3812 u;
3813 }
3814 *image = val;
3815 }
3816 }
3817 } else {
3818 return false;
3819 }
3820 }
3821#else
3822 return false;
3823#endif
3824
3825 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS ||
3826 compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) {
3827 // Allocate original data size.
3828 std::vector<unsigned char> outBuf(static_cast<size_t>(width) *
3829 static_cast<size_t>(num_lines) *
3830 pixel_data_size);
3831
3832 unsigned long dstLen = static_cast<unsigned long>(outBuf.size());
3833 TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false);
3834 if (!tinyexr::DecompressZip(
3835 dst: reinterpret_cast<unsigned char *>(&outBuf.at(n: 0)), uncompressed_size: &dstLen, src: data_ptr,
3836 src_size: static_cast<unsigned long>(data_len))) {
3837 return false;
3838 }
3839
3840 // For ZIP_COMPRESSION:
3841 // pixel sample data for channel 0 for scanline 0
3842 // pixel sample data for channel 1 for scanline 0
3843 // pixel sample data for channel ... for scanline 0
3844 // pixel sample data for channel n for scanline 0
3845 // pixel sample data for channel 0 for scanline 1
3846 // pixel sample data for channel 1 for scanline 1
3847 // pixel sample data for channel ... for scanline 1
3848 // pixel sample data for channel n for scanline 1
3849 // ...
3850 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
3851 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
3852 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3853 const unsigned short *line_ptr = reinterpret_cast<unsigned short *>(
3854 &outBuf.at(n: v * static_cast<size_t>(pixel_data_size) *
3855 static_cast<size_t>(width) +
3856 channel_offset_list[c] * static_cast<size_t>(width)));
3857 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3858 tinyexr::FP16 hf;
3859
3860 // hf.u = line_ptr[u];
3861 tinyexr::cpy2(dst_val: &(hf.u), src_val: line_ptr + u);
3862
3863 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&hf.u));
3864
3865 if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) {
3866 unsigned short *image =
3867 reinterpret_cast<unsigned short **>(out_images)[c];
3868 if (line_order == 0) {
3869 image += (static_cast<size_t>(line_no) + v) *
3870 static_cast<size_t>(x_stride) +
3871 u;
3872 } else {
3873 image += (static_cast<size_t>(height) - 1U -
3874 (static_cast<size_t>(line_no) + v)) *
3875 static_cast<size_t>(x_stride) +
3876 u;
3877 }
3878 *image = hf.u;
3879 } else { // HALF -> FLOAT
3880 tinyexr::FP32 f32 = half_to_float(h: hf);
3881 float *image = reinterpret_cast<float **>(out_images)[c];
3882 size_t offset = 0;
3883 if (line_order == 0) {
3884 offset = (static_cast<size_t>(line_no) + v) *
3885 static_cast<size_t>(x_stride) +
3886 u;
3887 } else {
3888 offset = (static_cast<size_t>(height) - 1U -
3889 (static_cast<size_t>(line_no) + v)) *
3890 static_cast<size_t>(x_stride) +
3891 u;
3892 }
3893 image += offset;
3894
3895 *image = f32.f;
3896 }
3897 }
3898 }
3899 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
3900 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
3901
3902 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3903 const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
3904 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
3905 channel_offset_list[c] * static_cast<size_t>(width)));
3906 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3907 unsigned int val;
3908 // val = line_ptr[u];
3909 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
3910
3911 tinyexr::swap4(val: &val);
3912
3913 unsigned int *image =
3914 reinterpret_cast<unsigned int **>(out_images)[c];
3915 if (line_order == 0) {
3916 image += (static_cast<size_t>(line_no) + v) *
3917 static_cast<size_t>(x_stride) +
3918 u;
3919 } else {
3920 image += (static_cast<size_t>(height) - 1U -
3921 (static_cast<size_t>(line_no) + v)) *
3922 static_cast<size_t>(x_stride) +
3923 u;
3924 }
3925 *image = val;
3926 }
3927 }
3928 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
3929 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
3930 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3931 const float *line_ptr = reinterpret_cast<float *>(
3932 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
3933 channel_offset_list[c] * static_cast<size_t>(width)));
3934 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3935 float val;
3936 // val = line_ptr[u];
3937 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
3938
3939 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&val));
3940
3941 float *image = reinterpret_cast<float **>(out_images)[c];
3942 if (line_order == 0) {
3943 image += (static_cast<size_t>(line_no) + v) *
3944 static_cast<size_t>(x_stride) +
3945 u;
3946 } else {
3947 image += (static_cast<size_t>(height) - 1U -
3948 (static_cast<size_t>(line_no) + v)) *
3949 static_cast<size_t>(x_stride) +
3950 u;
3951 }
3952 *image = val;
3953 }
3954 }
3955 } else {
3956 return false;
3957 }
3958 }
3959 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) {
3960 // Allocate original data size.
3961 std::vector<unsigned char> outBuf(static_cast<size_t>(width) *
3962 static_cast<size_t>(num_lines) *
3963 pixel_data_size);
3964
3965 unsigned long dstLen = static_cast<unsigned long>(outBuf.size());
3966 if (dstLen == 0) {
3967 return false;
3968 }
3969
3970 if (!tinyexr::DecompressRle(
3971 dst: reinterpret_cast<unsigned char *>(&outBuf.at(n: 0)), uncompressed_size: dstLen, src: data_ptr,
3972 src_size: static_cast<unsigned long>(data_len))) {
3973 return false;
3974 }
3975
3976 // For RLE_COMPRESSION:
3977 // pixel sample data for channel 0 for scanline 0
3978 // pixel sample data for channel 1 for scanline 0
3979 // pixel sample data for channel ... for scanline 0
3980 // pixel sample data for channel n for scanline 0
3981 // pixel sample data for channel 0 for scanline 1
3982 // pixel sample data for channel 1 for scanline 1
3983 // pixel sample data for channel ... for scanline 1
3984 // pixel sample data for channel n for scanline 1
3985 // ...
3986 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
3987 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
3988 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
3989 const unsigned short *line_ptr = reinterpret_cast<unsigned short *>(
3990 &outBuf.at(n: v * static_cast<size_t>(pixel_data_size) *
3991 static_cast<size_t>(width) +
3992 channel_offset_list[c] * static_cast<size_t>(width)));
3993 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
3994 tinyexr::FP16 hf;
3995
3996 // hf.u = line_ptr[u];
3997 tinyexr::cpy2(dst_val: &(hf.u), src_val: line_ptr + u);
3998
3999 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&hf.u));
4000
4001 if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) {
4002 unsigned short *image =
4003 reinterpret_cast<unsigned short **>(out_images)[c];
4004 if (line_order == 0) {
4005 image += (static_cast<size_t>(line_no) + v) *
4006 static_cast<size_t>(x_stride) +
4007 u;
4008 } else {
4009 image += (static_cast<size_t>(height) - 1U -
4010 (static_cast<size_t>(line_no) + v)) *
4011 static_cast<size_t>(x_stride) +
4012 u;
4013 }
4014 *image = hf.u;
4015 } else { // HALF -> FLOAT
4016 tinyexr::FP32 f32 = half_to_float(h: hf);
4017 float *image = reinterpret_cast<float **>(out_images)[c];
4018 if (line_order == 0) {
4019 image += (static_cast<size_t>(line_no) + v) *
4020 static_cast<size_t>(x_stride) +
4021 u;
4022 } else {
4023 image += (static_cast<size_t>(height) - 1U -
4024 (static_cast<size_t>(line_no) + v)) *
4025 static_cast<size_t>(x_stride) +
4026 u;
4027 }
4028 *image = f32.f;
4029 }
4030 }
4031 }
4032 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
4033 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_UINT, false);
4034
4035 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
4036 const unsigned int *line_ptr = reinterpret_cast<unsigned int *>(
4037 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
4038 channel_offset_list[c] * static_cast<size_t>(width)));
4039 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
4040 unsigned int val;
4041 // val = line_ptr[u];
4042 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
4043
4044 tinyexr::swap4(val: &val);
4045
4046 unsigned int *image =
4047 reinterpret_cast<unsigned int **>(out_images)[c];
4048 if (line_order == 0) {
4049 image += (static_cast<size_t>(line_no) + v) *
4050 static_cast<size_t>(x_stride) +
4051 u;
4052 } else {
4053 image += (static_cast<size_t>(height) - 1U -
4054 (static_cast<size_t>(line_no) + v)) *
4055 static_cast<size_t>(x_stride) +
4056 u;
4057 }
4058 *image = val;
4059 }
4060 }
4061 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
4062 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
4063 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
4064 const float *line_ptr = reinterpret_cast<float *>(
4065 &outBuf.at(n: v * pixel_data_size * static_cast<size_t>(width) +
4066 channel_offset_list[c] * static_cast<size_t>(width)));
4067 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
4068 float val;
4069 // val = line_ptr[u];
4070 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
4071
4072 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&val));
4073
4074 float *image = reinterpret_cast<float **>(out_images)[c];
4075 if (line_order == 0) {
4076 image += (static_cast<size_t>(line_no) + v) *
4077 static_cast<size_t>(x_stride) +
4078 u;
4079 } else {
4080 image += (static_cast<size_t>(height) - 1U -
4081 (static_cast<size_t>(line_no) + v)) *
4082 static_cast<size_t>(x_stride) +
4083 u;
4084 }
4085 *image = val;
4086 }
4087 }
4088 } else {
4089 return false;
4090 }
4091 }
4092 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
4093#if TINYEXR_USE_ZFP
4094 tinyexr::ZFPCompressionParam zfp_compression_param;
4095 std::string e;
4096 if (!tinyexr::FindZFPCompressionParam(&zfp_compression_param, attributes,
4097 int(num_attributes), &e)) {
4098 // This code path should not be reachable.
4099 return false;
4100 }
4101
4102 // Allocate original data size.
4103 std::vector<unsigned char> outBuf(static_cast<size_t>(width) *
4104 static_cast<size_t>(num_lines) *
4105 pixel_data_size);
4106
4107 unsigned long dstLen = outBuf.size();
4108 TINYEXR_CHECK_AND_RETURN_C(dstLen > 0, false);
4109 tinyexr::DecompressZfp(reinterpret_cast<float *>(&outBuf.at(0)), width,
4110 num_lines, num_channels, data_ptr,
4111 static_cast<unsigned long>(data_len),
4112 zfp_compression_param);
4113
4114 // For ZFP_COMPRESSION:
4115 // pixel sample data for channel 0 for scanline 0
4116 // pixel sample data for channel 1 for scanline 0
4117 // pixel sample data for channel ... for scanline 0
4118 // pixel sample data for channel n for scanline 0
4119 // pixel sample data for channel 0 for scanline 1
4120 // pixel sample data for channel 1 for scanline 1
4121 // pixel sample data for channel ... for scanline 1
4122 // pixel sample data for channel n for scanline 1
4123 // ...
4124 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
4125 TINYEXR_CHECK_AND_RETURN_C(channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT, false);
4126 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
4127 TINYEXR_CHECK_AND_RETURN_C(requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT, false);
4128 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
4129 const float *line_ptr = reinterpret_cast<float *>(
4130 &outBuf.at(v * pixel_data_size * static_cast<size_t>(width) +
4131 channel_offset_list[c] * static_cast<size_t>(width)));
4132 for (size_t u = 0; u < static_cast<size_t>(width); u++) {
4133 float val;
4134 tinyexr::cpy4(&val, line_ptr + u);
4135
4136 tinyexr::swap4(reinterpret_cast<unsigned int *>(&val));
4137
4138 float *image = reinterpret_cast<float **>(out_images)[c];
4139 if (line_order == 0) {
4140 image += (static_cast<size_t>(line_no) + v) *
4141 static_cast<size_t>(x_stride) +
4142 u;
4143 } else {
4144 image += (static_cast<size_t>(height) - 1U -
4145 (static_cast<size_t>(line_no) + v)) *
4146 static_cast<size_t>(x_stride) +
4147 u;
4148 }
4149 *image = val;
4150 }
4151 }
4152 } else {
4153 return false;
4154 }
4155 }
4156#else
4157 (void)attributes;
4158 (void)num_attributes;
4159 (void)num_channels;
4160 return false;
4161#endif
4162 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) {
4163 for (size_t c = 0; c < num_channels; c++) {
4164 for (size_t v = 0; v < static_cast<size_t>(num_lines); v++) {
4165 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
4166 const unsigned short *line_ptr =
4167 reinterpret_cast<const unsigned short *>(
4168 data_ptr + v * pixel_data_size * size_t(width) +
4169 channel_offset_list[c] * static_cast<size_t>(width));
4170
4171 if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) {
4172 unsigned short *outLine =
4173 reinterpret_cast<unsigned short *>(out_images[c]);
4174 if (line_order == 0) {
4175 outLine += (size_t(y) + v) * size_t(x_stride);
4176 } else {
4177 outLine +=
4178 (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride);
4179 }
4180
4181 for (int u = 0; u < width; u++) {
4182 tinyexr::FP16 hf;
4183
4184 // hf.u = line_ptr[u];
4185 tinyexr::cpy2(dst_val: &(hf.u), src_val: line_ptr + u);
4186
4187 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&hf.u));
4188
4189 outLine[u] = hf.u;
4190 }
4191 } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) {
4192 float *outLine = reinterpret_cast<float *>(out_images[c]);
4193 if (line_order == 0) {
4194 outLine += (size_t(y) + v) * size_t(x_stride);
4195 } else {
4196 outLine +=
4197 (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride);
4198 }
4199
4200 if (reinterpret_cast<const unsigned char *>(line_ptr + width) >
4201 (data_ptr + data_len)) {
4202 // Insufficient data size
4203 return false;
4204 }
4205
4206 for (int u = 0; u < width; u++) {
4207 tinyexr::FP16 hf;
4208
4209 // address may not be aligned. use byte-wise copy for safety.#76
4210 // hf.u = line_ptr[u];
4211 tinyexr::cpy2(dst_val: &(hf.u), src_val: line_ptr + u);
4212
4213 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&hf.u));
4214
4215 tinyexr::FP32 f32 = half_to_float(h: hf);
4216
4217 outLine[u] = f32.f;
4218 }
4219 } else {
4220 return false;
4221 }
4222 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
4223 const float *line_ptr = reinterpret_cast<const float *>(
4224 data_ptr + v * pixel_data_size * size_t(width) +
4225 channel_offset_list[c] * static_cast<size_t>(width));
4226
4227 float *outLine = reinterpret_cast<float *>(out_images[c]);
4228 if (line_order == 0) {
4229 outLine += (size_t(y) + v) * size_t(x_stride);
4230 } else {
4231 outLine +=
4232 (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride);
4233 }
4234
4235 if (reinterpret_cast<const unsigned char *>(line_ptr + width) >
4236 (data_ptr + data_len)) {
4237 // Insufficient data size
4238 return false;
4239 }
4240
4241 for (int u = 0; u < width; u++) {
4242 float val;
4243 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
4244
4245 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&val));
4246
4247 outLine[u] = val;
4248 }
4249 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
4250 const unsigned int *line_ptr = reinterpret_cast<const unsigned int *>(
4251 data_ptr + v * pixel_data_size * size_t(width) +
4252 channel_offset_list[c] * static_cast<size_t>(width));
4253
4254 unsigned int *outLine =
4255 reinterpret_cast<unsigned int *>(out_images[c]);
4256 if (line_order == 0) {
4257 outLine += (size_t(y) + v) * size_t(x_stride);
4258 } else {
4259 outLine +=
4260 (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride);
4261 }
4262
4263 if (reinterpret_cast<const unsigned char *>(line_ptr + width) >
4264 (data_ptr + data_len)) {
4265 // Corrupted data
4266 return false;
4267 }
4268
4269 for (int u = 0; u < width; u++) {
4270
4271 unsigned int val;
4272 tinyexr::cpy4(dst_val: &val, src_val: line_ptr + u);
4273
4274 tinyexr::swap4(val: reinterpret_cast<unsigned int *>(&val));
4275
4276 outLine[u] = val;
4277 }
4278 }
4279 }
4280 }
4281 }
4282
4283 return true;
4284}
4285
4286static bool DecodeTiledPixelData(
4287 unsigned char **out_images, int *width, int *height,
4288 const int *requested_pixel_types, const unsigned char *data_ptr,
4289 size_t data_len, int compression_type, int data_width,
4290 int data_height, int tile_offset_x, int tile_offset_y, int tile_size_x,
4291 int tile_size_y, size_t pixel_data_size, size_t num_attributes,
4292 const EXRAttribute *attributes, size_t num_channels,
4293 const EXRChannelInfo *channels,
4294 const std::vector<size_t> &channel_offset_list) {
4295 // Here, data_width and data_height are the dimensions of the current (sub)level.
4296 if (tile_size_x * tile_offset_x > data_width ||
4297 tile_size_y * tile_offset_y > data_height) {
4298 return false;
4299 }
4300
4301 // Compute actual image size in a tile.
4302 if ((tile_offset_x + 1) * tile_size_x >= data_width) {
4303 (*width) = data_width - (tile_offset_x * tile_size_x);
4304 } else {
4305 (*width) = tile_size_x;
4306 }
4307
4308 if ((tile_offset_y + 1) * tile_size_y >= data_height) {
4309 (*height) = data_height - (tile_offset_y * tile_size_y);
4310 } else {
4311 (*height) = tile_size_y;
4312 }
4313
4314 // Image size = tile size.
4315 // Line order within tiles is always increasing.
4316 return DecodePixelData(out_images, requested_pixel_types, data_ptr, data_len,
4317 compression_type, /* line_order*/ line_order: 0, width: (*width), height: tile_size_y,
4318 /* stride */ x_stride: tile_size_x, /* y */ y: 0, /* line_no */ line_no: 0,
4319 num_lines: (*height), pixel_data_size, num_attributes, attributes,
4320 num_channels, channels, channel_offset_list);
4321}
4322
4323static bool ComputeChannelLayout(std::vector<size_t> *channel_offset_list,
4324 int *pixel_data_size, size_t *channel_offset,
4325 int num_channels,
4326 const EXRChannelInfo *channels) {
4327 channel_offset_list->resize(new_size: static_cast<size_t>(num_channels));
4328
4329 (*pixel_data_size) = 0;
4330 (*channel_offset) = 0;
4331
4332 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
4333 (*channel_offset_list)[c] = (*channel_offset);
4334 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
4335 (*pixel_data_size) += sizeof(unsigned short);
4336 (*channel_offset) += sizeof(unsigned short);
4337 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
4338 (*pixel_data_size) += sizeof(float);
4339 (*channel_offset) += sizeof(float);
4340 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
4341 (*pixel_data_size) += sizeof(unsigned int);
4342 (*channel_offset) += sizeof(unsigned int);
4343 } else {
4344 // ???
4345 return false;
4346 }
4347 }
4348 return true;
4349}
4350
4351// TODO: Simply return nullptr when failed to allocate?
4352static unsigned char **AllocateImage(int num_channels,
4353 const EXRChannelInfo *channels,
4354 const int *requested_pixel_types,
4355 int data_width, int data_height, bool *success) {
4356 unsigned char **images =
4357 reinterpret_cast<unsigned char **>(static_cast<float **>(
4358 malloc(size: sizeof(float *) * static_cast<size_t>(num_channels))));
4359
4360 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
4361 images[c] = NULL;
4362 }
4363
4364 bool valid = true;
4365
4366 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
4367 size_t data_len =
4368 static_cast<size_t>(data_width) * static_cast<size_t>(data_height);
4369 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
4370 // pixel_data_size += sizeof(unsigned short);
4371 // channel_offset += sizeof(unsigned short);
4372 // Alloc internal image for half type.
4373 if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) {
4374 images[c] =
4375 reinterpret_cast<unsigned char *>(static_cast<unsigned short *>(
4376 malloc(size: sizeof(unsigned short) * data_len)));
4377 } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) {
4378 images[c] = reinterpret_cast<unsigned char *>(
4379 static_cast<float *>(malloc(size: sizeof(float) * data_len)));
4380 } else {
4381 images[c] = NULL; // just in case.
4382 valid = false;
4383 break;
4384 }
4385 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
4386 // pixel_data_size += sizeof(float);
4387 // channel_offset += sizeof(float);
4388 images[c] = reinterpret_cast<unsigned char *>(
4389 static_cast<float *>(malloc(size: sizeof(float) * data_len)));
4390 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
4391 // pixel_data_size += sizeof(unsigned int);
4392 // channel_offset += sizeof(unsigned int);
4393 images[c] = reinterpret_cast<unsigned char *>(
4394 static_cast<unsigned int *>(malloc(size: sizeof(unsigned int) * data_len)));
4395 } else {
4396 images[c] = NULL; // just in case.
4397 valid = false;
4398 break;
4399 }
4400 }
4401
4402 if (!valid) {
4403 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
4404 if (images[c]) {
4405 free(ptr: images[c]);
4406 images[c] = NULL;
4407 }
4408 }
4409
4410 if (success) {
4411 (*success) = false;
4412 }
4413 } else {
4414 if (success) {
4415 (*success) = true;
4416 }
4417 }
4418
4419 return images;
4420}
4421
4422#ifdef _WIN32
4423static inline std::wstring UTF8ToWchar(const std::string &str) {
4424 int wstr_size =
4425 MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), NULL, 0);
4426 std::wstring wstr(wstr_size, 0);
4427 MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0],
4428 (int)wstr.size());
4429 return wstr;
4430}
4431#endif
4432
4433
4434static int ParseEXRHeader(HeaderInfo *info, bool *empty_header,
4435 const EXRVersion *version, std::string *err,
4436 const unsigned char *buf, size_t size) {
4437 const char *marker = reinterpret_cast<const char *>(&buf[0]);
4438
4439 if (empty_header) {
4440 (*empty_header) = false;
4441 }
4442
4443 if (version->multipart) {
4444 if (size > 0 && marker[0] == '\0') {
4445 // End of header list.
4446 if (empty_header) {
4447 (*empty_header) = true;
4448 }
4449 return TINYEXR_SUCCESS;
4450 }
4451 }
4452
4453 // According to the spec, the header of every OpenEXR file must contain at
4454 // least the following attributes:
4455 //
4456 // channels chlist
4457 // compression compression
4458 // dataWindow box2i
4459 // displayWindow box2i
4460 // lineOrder lineOrder
4461 // pixelAspectRatio float
4462 // screenWindowCenter v2f
4463 // screenWindowWidth float
4464 bool has_channels = false;
4465 bool has_compression = false;
4466 bool has_data_window = false;
4467 bool has_display_window = false;
4468 bool has_line_order = false;
4469 bool has_pixel_aspect_ratio = false;
4470 bool has_screen_window_center = false;
4471 bool has_screen_window_width = false;
4472 bool has_name = false;
4473 bool has_type = false;
4474
4475 info->name.clear();
4476 info->type.clear();
4477
4478 info->data_window.min_x = 0;
4479 info->data_window.min_y = 0;
4480 info->data_window.max_x = 0;
4481 info->data_window.max_y = 0;
4482 info->line_order = 0; // @fixme
4483 info->display_window.min_x = 0;
4484 info->display_window.min_y = 0;
4485 info->display_window.max_x = 0;
4486 info->display_window.max_y = 0;
4487 info->screen_window_center[0] = 0.0f;
4488 info->screen_window_center[1] = 0.0f;
4489 info->screen_window_width = -1.0f;
4490 info->pixel_aspect_ratio = -1.0f;
4491
4492 info->tiled = 0;
4493 info->tile_size_x = -1;
4494 info->tile_size_y = -1;
4495 info->tile_level_mode = -1;
4496 info->tile_rounding_mode = -1;
4497
4498 info->attributes.clear();
4499
4500 // Read attributes
4501 size_t orig_size = size;
4502 for (size_t nattr = 0; nattr < TINYEXR_MAX_HEADER_ATTRIBUTES; nattr++) {
4503 if (0 == size) {
4504 if (err) {
4505 (*err) += "Insufficient data size for attributes.\n";
4506 }
4507 return TINYEXR_ERROR_INVALID_DATA;
4508 } else if (marker[0] == '\0') {
4509 size--;
4510 break;
4511 }
4512
4513 std::string attr_name;
4514 std::string attr_type;
4515 std::vector<unsigned char> data;
4516 size_t marker_size;
4517 if (!tinyexr::ReadAttribute(name: &attr_name, type: &attr_type, data: &data, marker_size: &marker_size,
4518 marker, size)) {
4519 if (err) {
4520 (*err) += "Failed to read attribute.\n";
4521 }
4522 return TINYEXR_ERROR_INVALID_DATA;
4523 }
4524 marker += marker_size;
4525 size -= marker_size;
4526
4527 // For a multipart file, the version field 9th bit is 0.
4528 if ((version->tiled || version->multipart || version->non_image) && attr_name.compare(s: "tiles") == 0) {
4529 unsigned int x_size, y_size;
4530 unsigned char tile_mode;
4531 if (data.size() != 9) {
4532 if (err) {
4533 (*err) += "(ParseEXRHeader) Invalid attribute data size. Attribute data size must be 9.\n";
4534 }
4535 return TINYEXR_ERROR_INVALID_DATA;
4536 }
4537
4538 memcpy(dest: &x_size, src: &data.at(n: 0), n: sizeof(int));
4539 memcpy(dest: &y_size, src: &data.at(n: 4), n: sizeof(int));
4540 tile_mode = data[8];
4541 tinyexr::swap4(val: &x_size);
4542 tinyexr::swap4(val: &y_size);
4543
4544 if (x_size > static_cast<unsigned int>(std::numeric_limits<int>::max()) ||
4545 y_size > static_cast<unsigned int>(std::numeric_limits<int>::max())) {
4546 if (err) {
4547 (*err) = "Tile sizes were invalid.";
4548 }
4549 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
4550 }
4551
4552 info->tile_size_x = static_cast<int>(x_size);
4553 info->tile_size_y = static_cast<int>(y_size);
4554
4555 // mode = levelMode + roundingMode * 16
4556 info->tile_level_mode = tile_mode & 0x3;
4557 info->tile_rounding_mode = (tile_mode >> 4) & 0x1;
4558 info->tiled = 1;
4559 } else if (attr_name.compare(s: "compression") == 0) {
4560 bool ok = false;
4561 if (data[0] < TINYEXR_COMPRESSIONTYPE_PIZ) {
4562 ok = true;
4563 }
4564
4565 if (data[0] == TINYEXR_COMPRESSIONTYPE_PIZ) {
4566#if TINYEXR_USE_PIZ
4567 ok = true;
4568#else
4569 if (err) {
4570 (*err) = "PIZ compression is not supported.";
4571 }
4572 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
4573#endif
4574 }
4575
4576 if (data[0] == TINYEXR_COMPRESSIONTYPE_ZFP) {
4577#if TINYEXR_USE_ZFP
4578 ok = true;
4579#else
4580 if (err) {
4581 (*err) = "ZFP compression is not supported.";
4582 }
4583 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
4584#endif
4585 }
4586
4587 if (!ok) {
4588 if (err) {
4589 (*err) = "Unknown compression type.";
4590 }
4591 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
4592 }
4593
4594 info->compression_type = static_cast<int>(data[0]);
4595 has_compression = true;
4596
4597 } else if (attr_name.compare(s: "channels") == 0) {
4598 // name: zero-terminated string, from 1 to 255 bytes long
4599 // pixel type: int, possible values are: UINT = 0 HALF = 1 FLOAT = 2
4600 // pLinear: unsigned char, possible values are 0 and 1
4601 // reserved: three chars, should be zero
4602 // xSampling: int
4603 // ySampling: int
4604
4605 if (!ReadChannelInfo(channels&: info->channels, data)) {
4606 if (err) {
4607 (*err) += "Failed to parse channel info.\n";
4608 }
4609 return TINYEXR_ERROR_INVALID_DATA;
4610 }
4611
4612 if (info->channels.size() < 1) {
4613 if (err) {
4614 (*err) += "# of channels is zero.\n";
4615 }
4616 return TINYEXR_ERROR_INVALID_DATA;
4617 }
4618
4619 has_channels = true;
4620
4621 } else if (attr_name.compare(s: "dataWindow") == 0) {
4622 if (data.size() >= 16) {
4623 memcpy(dest: &info->data_window.min_x, src: &data.at(n: 0), n: sizeof(int));
4624 memcpy(dest: &info->data_window.min_y, src: &data.at(n: 4), n: sizeof(int));
4625 memcpy(dest: &info->data_window.max_x, src: &data.at(n: 8), n: sizeof(int));
4626 memcpy(dest: &info->data_window.max_y, src: &data.at(n: 12), n: sizeof(int));
4627 tinyexr::swap4(val: &info->data_window.min_x);
4628 tinyexr::swap4(val: &info->data_window.min_y);
4629 tinyexr::swap4(val: &info->data_window.max_x);
4630 tinyexr::swap4(val: &info->data_window.max_y);
4631 has_data_window = true;
4632 }
4633 } else if (attr_name.compare(s: "displayWindow") == 0) {
4634 if (data.size() >= 16) {
4635 memcpy(dest: &info->display_window.min_x, src: &data.at(n: 0), n: sizeof(int));
4636 memcpy(dest: &info->display_window.min_y, src: &data.at(n: 4), n: sizeof(int));
4637 memcpy(dest: &info->display_window.max_x, src: &data.at(n: 8), n: sizeof(int));
4638 memcpy(dest: &info->display_window.max_y, src: &data.at(n: 12), n: sizeof(int));
4639 tinyexr::swap4(val: &info->display_window.min_x);
4640 tinyexr::swap4(val: &info->display_window.min_y);
4641 tinyexr::swap4(val: &info->display_window.max_x);
4642 tinyexr::swap4(val: &info->display_window.max_y);
4643
4644 has_display_window = true;
4645 }
4646 } else if (attr_name.compare(s: "lineOrder") == 0) {
4647 if (data.size() >= 1) {
4648 info->line_order = static_cast<int>(data[0]);
4649 has_line_order = true;
4650 }
4651 } else if (attr_name.compare(s: "pixelAspectRatio") == 0) {
4652 if (data.size() >= sizeof(float)) {
4653 memcpy(dest: &info->pixel_aspect_ratio, src: &data.at(n: 0), n: sizeof(float));
4654 tinyexr::swap4(val: &info->pixel_aspect_ratio);
4655 has_pixel_aspect_ratio = true;
4656 }
4657 } else if (attr_name.compare(s: "screenWindowCenter") == 0) {
4658 if (data.size() >= 8) {
4659 memcpy(dest: &info->screen_window_center[0], src: &data.at(n: 0), n: sizeof(float));
4660 memcpy(dest: &info->screen_window_center[1], src: &data.at(n: 4), n: sizeof(float));
4661 tinyexr::swap4(val: &info->screen_window_center[0]);
4662 tinyexr::swap4(val: &info->screen_window_center[1]);
4663 has_screen_window_center = true;
4664 }
4665 } else if (attr_name.compare(s: "screenWindowWidth") == 0) {
4666 if (data.size() >= sizeof(float)) {
4667 memcpy(dest: &info->screen_window_width, src: &data.at(n: 0), n: sizeof(float));
4668 tinyexr::swap4(val: &info->screen_window_width);
4669
4670 has_screen_window_width = true;
4671 }
4672 } else if (attr_name.compare(s: "chunkCount") == 0) {
4673 if (data.size() >= sizeof(int)) {
4674 memcpy(dest: &info->chunk_count, src: &data.at(n: 0), n: sizeof(int));
4675 tinyexr::swap4(val: &info->chunk_count);
4676 }
4677 } else if (attr_name.compare(s: "name") == 0) {
4678 if (!data.empty() && data[0]) {
4679 data.push_back(x: 0);
4680 size_t len = strlen(s: reinterpret_cast<const char*>(&data[0]));
4681 info->name.resize(n: len);
4682 info->name.assign(s: reinterpret_cast<const char*>(&data[0]), n: len);
4683 has_name = true;
4684 }
4685 } else if (attr_name.compare(s: "type") == 0) {
4686 if (!data.empty() && data[0]) {
4687 data.push_back(x: 0);
4688 size_t len = strlen(s: reinterpret_cast<const char*>(&data[0]));
4689 info->type.resize(n: len);
4690 info->type.assign(s: reinterpret_cast<const char*>(&data[0]), n: len);
4691 has_type = true;
4692 }
4693 } else {
4694 // Custom attribute(up to TINYEXR_MAX_CUSTOM_ATTRIBUTES)
4695 if (info->attributes.size() < TINYEXR_MAX_CUSTOM_ATTRIBUTES) {
4696 EXRAttribute attrib;
4697#ifdef _MSC_VER
4698 strncpy_s(attrib.name, attr_name.c_str(), 255);
4699 strncpy_s(attrib.type, attr_type.c_str(), 255);
4700#else
4701 strncpy(dest: attrib.name, src: attr_name.c_str(), n: 255);
4702 strncpy(dest: attrib.type, src: attr_type.c_str(), n: 255);
4703#endif
4704 attrib.name[255] = '\0';
4705 attrib.type[255] = '\0';
4706 //std::cout << "i = " << info->attributes.size() << ", dsize = " << data.size() << "\n";
4707 attrib.size = static_cast<int>(data.size());
4708 attrib.value = static_cast<unsigned char *>(malloc(size: data.size()));
4709 memcpy(dest: reinterpret_cast<char *>(attrib.value), src: &data.at(n: 0),
4710 n: data.size());
4711 info->attributes.push_back(x: attrib);
4712 }
4713 }
4714 }
4715
4716 // Check if required attributes exist
4717 {
4718 std::stringstream ss_err;
4719
4720 if (!has_compression) {
4721 ss_err << "\"compression\" attribute not found in the header."
4722 << std::endl;
4723 }
4724
4725 if (!has_channels) {
4726 ss_err << "\"channels\" attribute not found in the header." << std::endl;
4727 }
4728
4729 if (!has_line_order) {
4730 ss_err << "\"lineOrder\" attribute not found in the header." << std::endl;
4731 }
4732
4733 if (!has_display_window) {
4734 ss_err << "\"displayWindow\" attribute not found in the header."
4735 << std::endl;
4736 }
4737
4738 if (!has_data_window) {
4739 ss_err << "\"dataWindow\" attribute not found in the header or invalid."
4740 << std::endl;
4741 }
4742
4743 if (!has_pixel_aspect_ratio) {
4744 ss_err << "\"pixelAspectRatio\" attribute not found in the header."
4745 << std::endl;
4746 }
4747
4748 if (!has_screen_window_width) {
4749 ss_err << "\"screenWindowWidth\" attribute not found in the header."
4750 << std::endl;
4751 }
4752
4753 if (!has_screen_window_center) {
4754 ss_err << "\"screenWindowCenter\" attribute not found in the header."
4755 << std::endl;
4756 }
4757
4758 if (version->multipart || version->non_image) {
4759 if (!has_name) {
4760 ss_err << "\"name\" attribute not found in the header."
4761 << std::endl;
4762 }
4763 if (!has_type) {
4764 ss_err << "\"type\" attribute not found in the header."
4765 << std::endl;
4766 }
4767 }
4768
4769 if (!(ss_err.str().empty())) {
4770 if (err) {
4771 (*err) += ss_err.str();
4772 }
4773
4774 return TINYEXR_ERROR_INVALID_HEADER;
4775 }
4776 }
4777
4778 info->header_len = static_cast<unsigned int>(orig_size - size);
4779
4780 return TINYEXR_SUCCESS;
4781}
4782
4783// C++ HeaderInfo to C EXRHeader conversion.
4784static bool ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info, std::string *warn, std::string *err) {
4785 exr_header->pixel_aspect_ratio = info.pixel_aspect_ratio;
4786 exr_header->screen_window_center[0] = info.screen_window_center[0];
4787 exr_header->screen_window_center[1] = info.screen_window_center[1];
4788 exr_header->screen_window_width = info.screen_window_width;
4789 exr_header->chunk_count = info.chunk_count;
4790 exr_header->display_window.min_x = info.display_window.min_x;
4791 exr_header->display_window.min_y = info.display_window.min_y;
4792 exr_header->display_window.max_x = info.display_window.max_x;
4793 exr_header->display_window.max_y = info.display_window.max_y;
4794 exr_header->data_window.min_x = info.data_window.min_x;
4795 exr_header->data_window.min_y = info.data_window.min_y;
4796 exr_header->data_window.max_x = info.data_window.max_x;
4797 exr_header->data_window.max_y = info.data_window.max_y;
4798 exr_header->line_order = info.line_order;
4799 exr_header->compression_type = info.compression_type;
4800 exr_header->tiled = info.tiled;
4801 exr_header->tile_size_x = info.tile_size_x;
4802 exr_header->tile_size_y = info.tile_size_y;
4803 exr_header->tile_level_mode = info.tile_level_mode;
4804 exr_header->tile_rounding_mode = info.tile_rounding_mode;
4805
4806 EXRSetNameAttr(exr_header, name: info.name.c_str());
4807
4808
4809 if (!info.type.empty()) {
4810 bool valid = true;
4811 if (info.type == "scanlineimage") {
4812 if (exr_header->tiled) {
4813 if (err) {
4814 (*err) += "(ConvertHeader) tiled bit must be off for `scanlineimage` type.\n";
4815 }
4816 valid = false;
4817 }
4818 } else if (info.type == "tiledimage") {
4819 if (!exr_header->tiled) {
4820 if (err) {
4821 (*err) += "(ConvertHeader) tiled bit must be on for `tiledimage` type.\n";
4822 }
4823 valid = false;
4824 }
4825 } else if (info.type == "deeptile") {
4826 exr_header->non_image = 1;
4827 if (!exr_header->tiled) {
4828 if (err) {
4829 (*err) += "(ConvertHeader) tiled bit must be on for `deeptile` type.\n";
4830 }
4831 valid = false;
4832 }
4833 } else if (info.type == "deepscanline") {
4834 exr_header->non_image = 1;
4835 if (exr_header->tiled) {
4836 if (err) {
4837 (*err) += "(ConvertHeader) tiled bit must be off for `deepscanline` type.\n";
4838 }
4839 //valid = false;
4840 }
4841 } else {
4842 if (warn) {
4843 std::stringstream ss;
4844 ss << "(ConvertHeader) Unsupported or unknown info.type: " << info.type << "\n";
4845 (*warn) += ss.str();
4846 }
4847 }
4848
4849 if (!valid) {
4850 return false;
4851 }
4852 }
4853
4854 exr_header->num_channels = static_cast<int>(info.channels.size());
4855
4856 exr_header->channels = static_cast<EXRChannelInfo *>(malloc(
4857 size: sizeof(EXRChannelInfo) * static_cast<size_t>(exr_header->num_channels)));
4858 for (size_t c = 0; c < static_cast<size_t>(exr_header->num_channels); c++) {
4859#ifdef _MSC_VER
4860 strncpy_s(exr_header->channels[c].name, info.channels[c].name.c_str(), 255);
4861#else
4862 strncpy(dest: exr_header->channels[c].name, src: info.channels[c].name.c_str(), n: 255);
4863#endif
4864 // manually add '\0' for safety.
4865 exr_header->channels[c].name[255] = '\0';
4866
4867 exr_header->channels[c].pixel_type = info.channels[c].pixel_type;
4868 exr_header->channels[c].p_linear = info.channels[c].p_linear;
4869 exr_header->channels[c].x_sampling = info.channels[c].x_sampling;
4870 exr_header->channels[c].y_sampling = info.channels[c].y_sampling;
4871 }
4872
4873 exr_header->pixel_types = static_cast<int *>(
4874 malloc(size: sizeof(int) * static_cast<size_t>(exr_header->num_channels)));
4875 for (size_t c = 0; c < static_cast<size_t>(exr_header->num_channels); c++) {
4876 exr_header->pixel_types[c] = info.channels[c].pixel_type;
4877 }
4878
4879 // Initially fill with values of `pixel_types`
4880 exr_header->requested_pixel_types = static_cast<int *>(
4881 malloc(size: sizeof(int) * static_cast<size_t>(exr_header->num_channels)));
4882 for (size_t c = 0; c < static_cast<size_t>(exr_header->num_channels); c++) {
4883 exr_header->requested_pixel_types[c] = info.channels[c].pixel_type;
4884 }
4885
4886 exr_header->num_custom_attributes = static_cast<int>(info.attributes.size());
4887
4888 if (exr_header->num_custom_attributes > 0) {
4889 // TODO(syoyo): Report warning when # of attributes exceeds
4890 // `TINYEXR_MAX_CUSTOM_ATTRIBUTES`
4891 if (exr_header->num_custom_attributes > TINYEXR_MAX_CUSTOM_ATTRIBUTES) {
4892 exr_header->num_custom_attributes = TINYEXR_MAX_CUSTOM_ATTRIBUTES;
4893 }
4894
4895 exr_header->custom_attributes = static_cast<EXRAttribute *>(malloc(
4896 size: sizeof(EXRAttribute) * size_t(exr_header->num_custom_attributes)));
4897
4898 for (size_t i = 0; i < size_t(exr_header->num_custom_attributes); i++) {
4899 memcpy(dest: exr_header->custom_attributes[i].name, src: info.attributes[i].name,
4900 n: 256);
4901 memcpy(dest: exr_header->custom_attributes[i].type, src: info.attributes[i].type,
4902 n: 256);
4903 exr_header->custom_attributes[i].size = info.attributes[i].size;
4904 // Just copy pointer
4905 exr_header->custom_attributes[i].value = info.attributes[i].value;
4906 }
4907
4908 } else {
4909 exr_header->custom_attributes = NULL;
4910 }
4911
4912 exr_header->header_len = info.header_len;
4913
4914 return true;
4915}
4916
4917struct OffsetData {
4918 OffsetData() : num_x_levels(0), num_y_levels(0) {}
4919 std::vector<std::vector<std::vector <tinyexr::tinyexr_uint64> > > offsets;
4920 int num_x_levels;
4921 int num_y_levels;
4922};
4923
4924// -1 = error
4925static int LevelIndex(int lx, int ly, int tile_level_mode, int num_x_levels) {
4926 switch (tile_level_mode) {
4927 case TINYEXR_TILE_ONE_LEVEL:
4928 return 0;
4929
4930 case TINYEXR_TILE_MIPMAP_LEVELS:
4931 return lx;
4932
4933 case TINYEXR_TILE_RIPMAP_LEVELS:
4934 return lx + ly * num_x_levels;
4935
4936 default:
4937 return -1;
4938 }
4939 return 0;
4940}
4941
4942static int LevelSize(int toplevel_size, int level, int tile_rounding_mode) {
4943 if (level < 0) {
4944 return -1;
4945 }
4946
4947 int b = static_cast<int>(1u << static_cast<unsigned int>(level));
4948 int level_size = toplevel_size / b;
4949
4950 if (tile_rounding_mode == TINYEXR_TILE_ROUND_UP && level_size * b < toplevel_size)
4951 level_size += 1;
4952
4953 return std::max(a: level_size, b: 1);
4954}
4955
4956static int DecodeTiledLevel(EXRImage* exr_image, const EXRHeader* exr_header,
4957 const OffsetData& offset_data,
4958 const std::vector<size_t>& channel_offset_list,
4959 int pixel_data_size,
4960 const unsigned char* head, const size_t size,
4961 std::string* err) {
4962 int num_channels = exr_header->num_channels;
4963
4964 int level_index = LevelIndex(lx: exr_image->level_x, ly: exr_image->level_y, tile_level_mode: exr_header->tile_level_mode, num_x_levels: offset_data.num_x_levels);
4965 int num_y_tiles = int(offset_data.offsets[size_t(level_index)].size());
4966 if (num_y_tiles < 1) {
4967 return TINYEXR_ERROR_INVALID_DATA;
4968 }
4969 int num_x_tiles = int(offset_data.offsets[size_t(level_index)][0].size());
4970 if (num_x_tiles < 1) {
4971 return TINYEXR_ERROR_INVALID_DATA;
4972 }
4973 int num_tiles = num_x_tiles * num_y_tiles;
4974
4975 int err_code = TINYEXR_SUCCESS;
4976
4977 enum {
4978 EF_SUCCESS = 0,
4979 EF_INVALID_DATA = 1,
4980 EF_INSUFFICIENT_DATA = 2,
4981 EF_FAILED_TO_DECODE = 4
4982 };
4983#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
4984 std::atomic<unsigned> error_flag(EF_SUCCESS);
4985#else
4986 unsigned error_flag(EF_SUCCESS);
4987#endif
4988
4989 // Although the spec says : "...the data window is subdivided into an array of smaller rectangles...",
4990 // the IlmImf library allows the dimensions of the tile to be larger (or equal) than the dimensions of the data window.
4991#if 0
4992 if ((exr_header->tile_size_x > exr_image->width || exr_header->tile_size_y > exr_image->height) &&
4993 exr_image->level_x == 0 && exr_image->level_y == 0) {
4994 if (err) {
4995 (*err) += "Failed to decode tile data.\n";
4996 }
4997 err_code = TINYEXR_ERROR_INVALID_DATA;
4998 }
4999#endif
5000 exr_image->tiles = static_cast<EXRTile*>(
5001 calloc(nmemb: static_cast<size_t>(num_tiles), size: sizeof(EXRTile)));
5002
5003#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
5004 std::vector<std::thread> workers;
5005 std::atomic<int> tile_count(0);
5006
5007 int num_threads = std::max(a: 1, b: int(std::thread::hardware_concurrency()));
5008#if (TINYEXR_MAX_THREADS > 0)
5009 num_threads = std::min(num_threads,TINYEXR_MAX_THREADS);
5010#endif
5011 if (num_threads > int(num_tiles)) {
5012 num_threads = int(num_tiles);
5013 }
5014 for (int t = 0; t < num_threads; t++) {
5015 workers.emplace_back(args: std::thread([&]()
5016 {
5017 int tile_idx = 0;
5018 while ((tile_idx = tile_count++) < num_tiles) {
5019
5020#else
5021#if TINYEXR_USE_OPENMP
5022#pragma omp parallel for
5023#endif
5024 for (int tile_idx = 0; tile_idx < num_tiles; tile_idx++) {
5025#endif
5026 // Allocate memory for each tile.
5027 bool alloc_success = false;
5028 exr_image->tiles[tile_idx].images = tinyexr::AllocateImage(
5029 num_channels, channels: exr_header->channels,
5030 requested_pixel_types: exr_header->requested_pixel_types, data_width: exr_header->tile_size_x,
5031 data_height: exr_header->tile_size_y, success: &alloc_success);
5032
5033 if (!alloc_success) {
5034 error_flag |= EF_INVALID_DATA;
5035 continue;
5036 }
5037
5038 int x_tile = tile_idx % num_x_tiles;
5039 int y_tile = tile_idx / num_x_tiles;
5040 // 16 byte: tile coordinates
5041 // 4 byte : data size
5042 // ~ : data(uncompressed or compressed)
5043 tinyexr::tinyexr_uint64 offset = offset_data.offsets[size_t(level_index)][size_t(y_tile)][size_t(x_tile)];
5044 if (offset + sizeof(int) * 5 > size) {
5045 // Insufficient data size.
5046 error_flag |= EF_INSUFFICIENT_DATA;
5047 continue;
5048 }
5049
5050 size_t data_size =
5051 size_t(size - (offset + sizeof(int) * 5));
5052 const unsigned char* data_ptr =
5053 reinterpret_cast<const unsigned char*>(head + offset);
5054
5055 int tile_coordinates[4];
5056 memcpy(dest: tile_coordinates, src: data_ptr, n: sizeof(int) * 4);
5057 tinyexr::swap4(val: &tile_coordinates[0]);
5058 tinyexr::swap4(val: &tile_coordinates[1]);
5059 tinyexr::swap4(val: &tile_coordinates[2]);
5060 tinyexr::swap4(val: &tile_coordinates[3]);
5061
5062 if (tile_coordinates[2] != exr_image->level_x) {
5063 // Invalid data.
5064 error_flag |= EF_INVALID_DATA;
5065 continue;
5066 }
5067 if (tile_coordinates[3] != exr_image->level_y) {
5068 // Invalid data.
5069 error_flag |= EF_INVALID_DATA;
5070 continue;
5071 }
5072
5073 int data_len;
5074 memcpy(dest: &data_len, src: data_ptr + 16,
5075 n: sizeof(int)); // 16 = sizeof(tile_coordinates)
5076 tinyexr::swap4(val: &data_len);
5077
5078 if (data_len < 2 || size_t(data_len) > data_size) {
5079 // Insufficient data size.
5080 error_flag |= EF_INSUFFICIENT_DATA;
5081 continue;
5082 }
5083
5084 // Move to data addr: 20 = 16 + 4;
5085 data_ptr += 20;
5086 bool ret = tinyexr::DecodeTiledPixelData(
5087 out_images: exr_image->tiles[tile_idx].images,
5088 width: &(exr_image->tiles[tile_idx].width),
5089 height: &(exr_image->tiles[tile_idx].height),
5090 requested_pixel_types: exr_header->requested_pixel_types, data_ptr,
5091 data_len: static_cast<size_t>(data_len), compression_type: exr_header->compression_type,
5092 data_width: exr_image->width, data_height: exr_image->height,
5093 tile_offset_x: tile_coordinates[0], tile_offset_y: tile_coordinates[1], tile_size_x: exr_header->tile_size_x,
5094 tile_size_y: exr_header->tile_size_y, pixel_data_size: static_cast<size_t>(pixel_data_size),
5095 num_attributes: static_cast<size_t>(exr_header->num_custom_attributes),
5096 attributes: exr_header->custom_attributes,
5097 num_channels: static_cast<size_t>(exr_header->num_channels),
5098 channels: exr_header->channels, channel_offset_list);
5099
5100 if (!ret) {
5101 // Failed to decode tile data.
5102 error_flag |= EF_FAILED_TO_DECODE;
5103 }
5104
5105 exr_image->tiles[tile_idx].offset_x = tile_coordinates[0];
5106 exr_image->tiles[tile_idx].offset_y = tile_coordinates[1];
5107 exr_image->tiles[tile_idx].level_x = tile_coordinates[2];
5108 exr_image->tiles[tile_idx].level_y = tile_coordinates[3];
5109
5110#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
5111 }
5112 }));
5113 } // num_thread loop
5114
5115 for (auto& t : workers) {
5116 t.join();
5117 }
5118
5119#else
5120 } // parallel for
5121#endif
5122
5123 // Even in the event of an error, the reserved memory may be freed.
5124 exr_image->num_channels = num_channels;
5125 exr_image->num_tiles = static_cast<int>(num_tiles);
5126
5127 if (error_flag) err_code = TINYEXR_ERROR_INVALID_DATA;
5128 if (err) {
5129 if (error_flag & EF_INSUFFICIENT_DATA) {
5130 (*err) += "Insufficient data length.\n";
5131 }
5132 if (error_flag & EF_FAILED_TO_DECODE) {
5133 (*err) += "Failed to decode tile data.\n";
5134 }
5135 }
5136 return err_code;
5137}
5138
5139static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header,
5140 const OffsetData& offset_data,
5141 const unsigned char *head, const size_t size,
5142 std::string *err) {
5143 int num_channels = exr_header->num_channels;
5144
5145 int num_scanline_blocks = 1;
5146 if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) {
5147 num_scanline_blocks = 16;
5148 } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
5149 num_scanline_blocks = 32;
5150 } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
5151 num_scanline_blocks = 16;
5152
5153#if TINYEXR_USE_ZFP
5154 tinyexr::ZFPCompressionParam zfp_compression_param;
5155 if (!FindZFPCompressionParam(&zfp_compression_param,
5156 exr_header->custom_attributes,
5157 int(exr_header->num_custom_attributes), err)) {
5158 return TINYEXR_ERROR_INVALID_HEADER;
5159 }
5160#endif
5161 }
5162
5163 if (exr_header->data_window.max_x < exr_header->data_window.min_x ||
5164 exr_header->data_window.max_y < exr_header->data_window.min_y) {
5165 if (err) {
5166 (*err) += "Invalid data window.\n";
5167 }
5168 return TINYEXR_ERROR_INVALID_DATA;
5169 }
5170
5171 tinyexr_int64 data_width =
5172 static_cast<tinyexr_int64>(exr_header->data_window.max_x) - static_cast<tinyexr_int64>(exr_header->data_window.min_x) + static_cast<tinyexr_int64>(1);
5173 tinyexr_int64 data_height =
5174 static_cast<tinyexr_int64>(exr_header->data_window.max_y) - static_cast<tinyexr_int64>(exr_header->data_window.min_y) + static_cast<tinyexr_int64>(1);
5175
5176 if (data_width <= 0) {
5177 if (err) {
5178 (*err) += "Invalid data window width.\n";
5179 }
5180 return TINYEXR_ERROR_INVALID_DATA;
5181 }
5182
5183 if (data_height <= 0) {
5184 if (err) {
5185 (*err) += "Invalid data window height.\n";
5186 }
5187 return TINYEXR_ERROR_INVALID_DATA;
5188 }
5189
5190 // Do not allow too large data_width and data_height. header invalid?
5191 {
5192 if ((data_width > TINYEXR_DIMENSION_THRESHOLD) || (data_height > TINYEXR_DIMENSION_THRESHOLD)) {
5193 if (err) {
5194 std::stringstream ss;
5195 ss << "data_with or data_height too large. data_width: " << data_width
5196 << ", "
5197 << "data_height = " << data_height << std::endl;
5198 (*err) += ss.str();
5199 }
5200 return TINYEXR_ERROR_INVALID_DATA;
5201 }
5202 if (exr_header->tiled) {
5203 if ((exr_header->tile_size_x > TINYEXR_DIMENSION_THRESHOLD) || (exr_header->tile_size_y > TINYEXR_DIMENSION_THRESHOLD)) {
5204 if (err) {
5205 std::stringstream ss;
5206 ss << "tile with or tile height too large. tile width: " << exr_header->tile_size_x
5207 << ", "
5208 << "tile height = " << exr_header->tile_size_y << std::endl;
5209 (*err) += ss.str();
5210 }
5211 return TINYEXR_ERROR_INVALID_DATA;
5212 }
5213 }
5214 }
5215
5216 const std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data.offsets[0][0];
5217 size_t num_blocks = offsets.size();
5218
5219 std::vector<size_t> channel_offset_list;
5220 int pixel_data_size = 0;
5221 size_t channel_offset = 0;
5222 if (!tinyexr::ComputeChannelLayout(channel_offset_list: &channel_offset_list, pixel_data_size: &pixel_data_size,
5223 channel_offset: &channel_offset, num_channels,
5224 channels: exr_header->channels)) {
5225 if (err) {
5226 (*err) += "Failed to compute channel layout.\n";
5227 }
5228 return TINYEXR_ERROR_INVALID_DATA;
5229 }
5230
5231#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
5232 std::atomic<bool> invalid_data(false);
5233#else
5234 bool invalid_data(false);
5235#endif
5236
5237 if (exr_header->tiled) {
5238 // value check
5239 if (exr_header->tile_size_x < 0) {
5240 if (err) {
5241 std::stringstream ss;
5242 ss << "Invalid tile size x : " << exr_header->tile_size_x << "\n";
5243 (*err) += ss.str();
5244 }
5245 return TINYEXR_ERROR_INVALID_HEADER;
5246 }
5247
5248 if (exr_header->tile_size_y < 0) {
5249 if (err) {
5250 std::stringstream ss;
5251 ss << "Invalid tile size y : " << exr_header->tile_size_y << "\n";
5252 (*err) += ss.str();
5253 }
5254 return TINYEXR_ERROR_INVALID_HEADER;
5255 }
5256 if (exr_header->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) {
5257 EXRImage* level_image = NULL;
5258 for (int level = 0; level < offset_data.num_x_levels; ++level) {
5259 if (!level_image) {
5260 level_image = exr_image;
5261 } else {
5262 level_image->next_level = new EXRImage;
5263 InitEXRImage(exr_image: level_image->next_level);
5264 level_image = level_image->next_level;
5265 }
5266 level_image->width =
5267 LevelSize(toplevel_size: exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level, tile_rounding_mode: exr_header->tile_rounding_mode);
5268 if (level_image->width < 1) {
5269 return TINYEXR_ERROR_INVALID_DATA;
5270 }
5271
5272 level_image->height =
5273 LevelSize(toplevel_size: exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level, tile_rounding_mode: exr_header->tile_rounding_mode);
5274
5275 if (level_image->height < 1) {
5276 return TINYEXR_ERROR_INVALID_DATA;
5277 }
5278
5279 level_image->level_x = level;
5280 level_image->level_y = level;
5281
5282 int ret = DecodeTiledLevel(exr_image: level_image, exr_header,
5283 offset_data,
5284 channel_offset_list,
5285 pixel_data_size,
5286 head, size,
5287 err);
5288 if (ret != TINYEXR_SUCCESS) return ret;
5289 }
5290 } else {
5291 EXRImage* level_image = NULL;
5292 for (int level_y = 0; level_y < offset_data.num_y_levels; ++level_y)
5293 for (int level_x = 0; level_x < offset_data.num_x_levels; ++level_x) {
5294 if (!level_image) {
5295 level_image = exr_image;
5296 } else {
5297 level_image->next_level = new EXRImage;
5298 InitEXRImage(exr_image: level_image->next_level);
5299 level_image = level_image->next_level;
5300 }
5301
5302 level_image->width =
5303 LevelSize(toplevel_size: exr_header->data_window.max_x - exr_header->data_window.min_x + 1, level: level_x, tile_rounding_mode: exr_header->tile_rounding_mode);
5304 if (level_image->width < 1) {
5305 return TINYEXR_ERROR_INVALID_DATA;
5306 }
5307
5308 level_image->height =
5309 LevelSize(toplevel_size: exr_header->data_window.max_y - exr_header->data_window.min_y + 1, level: level_y, tile_rounding_mode: exr_header->tile_rounding_mode);
5310 if (level_image->height < 1) {
5311 return TINYEXR_ERROR_INVALID_DATA;
5312 }
5313
5314 level_image->level_x = level_x;
5315 level_image->level_y = level_y;
5316
5317 int ret = DecodeTiledLevel(exr_image: level_image, exr_header,
5318 offset_data,
5319 channel_offset_list,
5320 pixel_data_size,
5321 head, size,
5322 err);
5323 if (ret != TINYEXR_SUCCESS) return ret;
5324 }
5325 }
5326 } else { // scanline format
5327 // Don't allow too large image(256GB * pixel_data_size or more). Workaround
5328 // for #104.
5329 size_t total_data_len =
5330 size_t(data_width) * size_t(data_height) * size_t(num_channels);
5331 const bool total_data_len_overflown =
5332 sizeof(void *) == 8 ? (total_data_len >= 0x4000000000) : false;
5333 if ((total_data_len == 0) || total_data_len_overflown) {
5334 if (err) {
5335 std::stringstream ss;
5336 ss << "Image data size is zero or too large: width = " << data_width
5337 << ", height = " << data_height << ", channels = " << num_channels
5338 << std::endl;
5339 (*err) += ss.str();
5340 }
5341 return TINYEXR_ERROR_INVALID_DATA;
5342 }
5343
5344 bool alloc_success = false;
5345 exr_image->images = tinyexr::AllocateImage(
5346 num_channels, channels: exr_header->channels, requested_pixel_types: exr_header->requested_pixel_types,
5347 data_width: int(data_width), data_height: int(data_height), success: &alloc_success);
5348
5349 if (!alloc_success) {
5350 if (err) {
5351 std::stringstream ss;
5352 ss << "Failed to allocate memory for Images. Maybe EXR header is corrupted or Image data size is too large: width = " << data_width
5353 << ", height = " << data_height << ", channels = " << num_channels
5354 << std::endl;
5355 (*err) += ss.str();
5356 }
5357 return TINYEXR_ERROR_INVALID_DATA;
5358 }
5359
5360#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
5361 std::vector<std::thread> workers;
5362 std::atomic<int> y_count(0);
5363
5364 int num_threads = std::max(a: 1, b: int(std::thread::hardware_concurrency()));
5365#if (TINYEXR_MAX_THREADS > 0)
5366 num_threads = std::min(num_threads,TINYEXR_MAX_THREADS);
5367#endif
5368 if (num_threads > int(num_blocks)) {
5369 num_threads = int(num_blocks);
5370 }
5371 for (int t = 0; t < num_threads; t++) {
5372 workers.emplace_back(args: std::thread([&]() {
5373 int y = 0;
5374 while ((y = y_count++) < int(num_blocks)) {
5375
5376#else
5377
5378#if TINYEXR_USE_OPENMP
5379#pragma omp parallel for
5380#endif
5381 for (int y = 0; y < static_cast<int>(num_blocks); y++) {
5382
5383#endif
5384 size_t y_idx = static_cast<size_t>(y);
5385
5386 if (offsets[y_idx] + sizeof(int) * 2 > size) {
5387 invalid_data = true;
5388 } else {
5389 // 4 byte: scan line
5390 // 4 byte: data size
5391 // ~ : pixel data(uncompressed or compressed)
5392 size_t data_size =
5393 size_t(size - (offsets[y_idx] + sizeof(int) * 2));
5394 const unsigned char *data_ptr =
5395 reinterpret_cast<const unsigned char *>(head + offsets[y_idx]);
5396
5397 int line_no;
5398 memcpy(dest: &line_no, src: data_ptr, n: sizeof(int));
5399 int data_len;
5400 memcpy(dest: &data_len, src: data_ptr + 4, n: sizeof(int));
5401 tinyexr::swap4(val: &line_no);
5402 tinyexr::swap4(val: &data_len);
5403
5404 if (size_t(data_len) > data_size) {
5405 invalid_data = true;
5406
5407 } else if ((line_no > (2 << 20)) || (line_no < -(2 << 20))) {
5408 // Too large value. Assume this is invalid
5409 // 2**20 = 1048576 = heuristic value.
5410 invalid_data = true;
5411 } else if (data_len == 0) {
5412 // TODO(syoyo): May be ok to raise the threshold for example
5413 // `data_len < 4`
5414 invalid_data = true;
5415 } else {
5416 // line_no may be negative.
5417 int end_line_no = (std::min)(a: line_no + num_scanline_blocks,
5418 b: (exr_header->data_window.max_y + 1));
5419
5420 int num_lines = end_line_no - line_no;
5421
5422 if (num_lines <= 0) {
5423 invalid_data = true;
5424 } else {
5425 // Move to data addr: 8 = 4 + 4;
5426 data_ptr += 8;
5427
5428 // Adjust line_no with data_window.bmin.y
5429
5430 // overflow check
5431 tinyexr_int64 lno =
5432 static_cast<tinyexr_int64>(line_no) -
5433 static_cast<tinyexr_int64>(exr_header->data_window.min_y);
5434 if (lno > std::numeric_limits<int>::max()) {
5435 line_no = -1; // invalid
5436 } else if (lno < -std::numeric_limits<int>::max()) {
5437 line_no = -1; // invalid
5438 } else {
5439 line_no -= exr_header->data_window.min_y;
5440 }
5441
5442 if (line_no < 0) {
5443 invalid_data = true;
5444 } else {
5445 // Line order is increasing because we read in line offset table order.
5446 if (!tinyexr::DecodePixelData(
5447 out_images: exr_image->images, requested_pixel_types: exr_header->requested_pixel_types,
5448 data_ptr, data_len: static_cast<size_t>(data_len),
5449 compression_type: exr_header->compression_type, /* line_order*/ line_order: 0,
5450 width: int(data_width), height: int(data_height), x_stride: int(data_width), y, line_no,
5451 num_lines, pixel_data_size: static_cast<size_t>(pixel_data_size),
5452 num_attributes: static_cast<size_t>(
5453 exr_header->num_custom_attributes),
5454 attributes: exr_header->custom_attributes,
5455 num_channels: static_cast<size_t>(exr_header->num_channels),
5456 channels: exr_header->channels, channel_offset_list)) {
5457 invalid_data = true;
5458 }
5459 }
5460 }
5461 }
5462 }
5463
5464#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
5465 }
5466 }));
5467 }
5468
5469 for (auto &t : workers) {
5470 t.join();
5471 }
5472#else
5473 } // omp parallel
5474#endif
5475 }
5476
5477 if (invalid_data) {
5478 if (err) {
5479 (*err) += "Invalid/Corrupted data found when decoding pixels.\n";
5480 }
5481
5482 // free alloced image.
5483 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
5484 if (exr_image->images[c]) {
5485 free(ptr: exr_image->images[c]);
5486 exr_image->images[c] = NULL;
5487 }
5488 }
5489 return TINYEXR_ERROR_INVALID_DATA;
5490 }
5491
5492 // Overwrite `pixel_type` with `requested_pixel_type`.
5493 {
5494 for (int c = 0; c < exr_header->num_channels; c++) {
5495 exr_header->pixel_types[c] = exr_header->requested_pixel_types[c];
5496 }
5497 }
5498
5499 {
5500 exr_image->num_channels = num_channels;
5501
5502 exr_image->width = int(data_width);
5503 exr_image->height = int(data_height);
5504 }
5505
5506 return TINYEXR_SUCCESS;
5507}
5508
5509static bool ReconstructLineOffsets(
5510 std::vector<tinyexr::tinyexr_uint64> *offsets, size_t n,
5511 const unsigned char *head, const unsigned char *marker, const size_t size) {
5512 if (head >= marker) {
5513 return false;
5514 }
5515 if (offsets->size() != n) {
5516 return false;
5517 }
5518
5519 for (size_t i = 0; i < n; i++) {
5520 size_t offset = static_cast<size_t>(marker - head);
5521 // Offset should not exceed whole EXR file/data size.
5522 if ((offset + sizeof(tinyexr::tinyexr_uint64)) >= size) {
5523 return false;
5524 }
5525
5526 int y;
5527 unsigned int data_len;
5528
5529 memcpy(dest: &y, src: marker, n: sizeof(int));
5530 memcpy(dest: &data_len, src: marker + 4, n: sizeof(unsigned int));
5531
5532 if (data_len >= size) {
5533 return false;
5534 }
5535
5536 tinyexr::swap4(val: &y);
5537 tinyexr::swap4(val: &data_len);
5538
5539 (*offsets)[i] = offset;
5540
5541 marker += data_len + 8; // 8 = 4 bytes(y) + 4 bytes(data_len)
5542 }
5543
5544 return true;
5545}
5546
5547
5548static int FloorLog2(unsigned x) {
5549 //
5550 // For x > 0, floorLog2(y) returns floor(log(x)/log(2)).
5551 //
5552 int y = 0;
5553 while (x > 1) {
5554 y += 1;
5555 x >>= 1u;
5556 }
5557 return y;
5558}
5559
5560
5561static int CeilLog2(unsigned x) {
5562 //
5563 // For x > 0, ceilLog2(y) returns ceil(log(x)/log(2)).
5564 //
5565 int y = 0;
5566 int r = 0;
5567 while (x > 1) {
5568 if (x & 1)
5569 r = 1;
5570
5571 y += 1;
5572 x >>= 1u;
5573 }
5574 return y + r;
5575}
5576
5577static int RoundLog2(int x, int tile_rounding_mode) {
5578 return (tile_rounding_mode == TINYEXR_TILE_ROUND_DOWN) ? FloorLog2(x: static_cast<unsigned>(x)) : CeilLog2(x: static_cast<unsigned>(x));
5579}
5580
5581static int CalculateNumXLevels(const EXRHeader* exr_header) {
5582 int min_x = exr_header->data_window.min_x;
5583 int max_x = exr_header->data_window.max_x;
5584 int min_y = exr_header->data_window.min_y;
5585 int max_y = exr_header->data_window.max_y;
5586
5587 int num = 0;
5588 switch (exr_header->tile_level_mode) {
5589 case TINYEXR_TILE_ONE_LEVEL:
5590
5591 num = 1;
5592 break;
5593
5594 case TINYEXR_TILE_MIPMAP_LEVELS:
5595
5596 {
5597 int w = max_x - min_x + 1;
5598 int h = max_y - min_y + 1;
5599 num = RoundLog2(x: std::max(a: w, b: h), tile_rounding_mode: exr_header->tile_rounding_mode) + 1;
5600 }
5601 break;
5602
5603 case TINYEXR_TILE_RIPMAP_LEVELS:
5604
5605 {
5606 int w = max_x - min_x + 1;
5607 num = RoundLog2(x: w, tile_rounding_mode: exr_header->tile_rounding_mode) + 1;
5608 }
5609 break;
5610
5611 default:
5612
5613 return -1;
5614 }
5615
5616 return num;
5617}
5618
5619static int CalculateNumYLevels(const EXRHeader* exr_header) {
5620 int min_x = exr_header->data_window.min_x;
5621 int max_x = exr_header->data_window.max_x;
5622 int min_y = exr_header->data_window.min_y;
5623 int max_y = exr_header->data_window.max_y;
5624 int num = 0;
5625
5626 switch (exr_header->tile_level_mode) {
5627 case TINYEXR_TILE_ONE_LEVEL:
5628
5629 num = 1;
5630 break;
5631
5632 case TINYEXR_TILE_MIPMAP_LEVELS:
5633
5634 {
5635 int w = max_x - min_x + 1;
5636 int h = max_y - min_y + 1;
5637 num = RoundLog2(x: std::max(a: w, b: h), tile_rounding_mode: exr_header->tile_rounding_mode) + 1;
5638 }
5639 break;
5640
5641 case TINYEXR_TILE_RIPMAP_LEVELS:
5642
5643 {
5644 int h = max_y - min_y + 1;
5645 num = RoundLog2(x: h, tile_rounding_mode: exr_header->tile_rounding_mode) + 1;
5646 }
5647 break;
5648
5649 default:
5650
5651 return -1;
5652 }
5653
5654 return num;
5655}
5656
5657static bool CalculateNumTiles(std::vector<int>& numTiles,
5658 int toplevel_size,
5659 int size,
5660 int tile_rounding_mode) {
5661 for (unsigned i = 0; i < numTiles.size(); i++) {
5662 int l = LevelSize(toplevel_size, level: int(i), tile_rounding_mode);
5663 if (l < 0) {
5664 return false;
5665 }
5666 TINYEXR_CHECK_AND_RETURN_C(l <= std::numeric_limits<int>::max() - size + 1, false);
5667
5668 numTiles[i] = (l + size - 1) / size;
5669 }
5670 return true;
5671}
5672
5673static bool PrecalculateTileInfo(std::vector<int>& num_x_tiles,
5674 std::vector<int>& num_y_tiles,
5675 const EXRHeader* exr_header) {
5676 int min_x = exr_header->data_window.min_x;
5677 int max_x = exr_header->data_window.max_x;
5678 int min_y = exr_header->data_window.min_y;
5679 int max_y = exr_header->data_window.max_y;
5680
5681 int num_x_levels = CalculateNumXLevels(exr_header);
5682
5683 if (num_x_levels < 0) {
5684 return false;
5685 }
5686
5687 int num_y_levels = CalculateNumYLevels(exr_header);
5688
5689 if (num_y_levels < 0) {
5690 return false;
5691 }
5692
5693 num_x_tiles.resize(new_size: size_t(num_x_levels));
5694 num_y_tiles.resize(new_size: size_t(num_y_levels));
5695
5696 if (!CalculateNumTiles(numTiles&: num_x_tiles,
5697 toplevel_size: max_x - min_x + 1,
5698 size: exr_header->tile_size_x,
5699 tile_rounding_mode: exr_header->tile_rounding_mode)) {
5700 return false;
5701 }
5702
5703 if (!CalculateNumTiles(numTiles&: num_y_tiles,
5704 toplevel_size: max_y - min_y + 1,
5705 size: exr_header->tile_size_y,
5706 tile_rounding_mode: exr_header->tile_rounding_mode)) {
5707 return false;
5708 }
5709
5710 return true;
5711}
5712
5713static void InitSingleResolutionOffsets(OffsetData& offset_data, size_t num_blocks) {
5714 offset_data.offsets.resize(new_size: 1);
5715 offset_data.offsets[0].resize(new_size: 1);
5716 offset_data.offsets[0][0].resize(new_size: num_blocks);
5717 offset_data.num_x_levels = 1;
5718 offset_data.num_y_levels = 1;
5719}
5720
5721// Return sum of tile blocks.
5722// 0 = error
5723static int InitTileOffsets(OffsetData& offset_data,
5724 const EXRHeader* exr_header,
5725 const std::vector<int>& num_x_tiles,
5726 const std::vector<int>& num_y_tiles) {
5727 int num_tile_blocks = 0;
5728 offset_data.num_x_levels = static_cast<int>(num_x_tiles.size());
5729 offset_data.num_y_levels = static_cast<int>(num_y_tiles.size());
5730 switch (exr_header->tile_level_mode) {
5731 case TINYEXR_TILE_ONE_LEVEL:
5732 case TINYEXR_TILE_MIPMAP_LEVELS:
5733 TINYEXR_CHECK_AND_RETURN_C(offset_data.num_x_levels == offset_data.num_y_levels, 0);
5734 offset_data.offsets.resize(new_size: size_t(offset_data.num_x_levels));
5735
5736 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) {
5737 offset_data.offsets[l].resize(new_size: size_t(num_y_tiles[l]));
5738
5739 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) {
5740 offset_data.offsets[l][dy].resize(new_size: size_t(num_x_tiles[l]));
5741 num_tile_blocks += num_x_tiles[l];
5742 }
5743 }
5744 break;
5745
5746 case TINYEXR_TILE_RIPMAP_LEVELS:
5747
5748 offset_data.offsets.resize(new_size: static_cast<size_t>(offset_data.num_x_levels) * static_cast<size_t>(offset_data.num_y_levels));
5749
5750 for (int ly = 0; ly < offset_data.num_y_levels; ++ly) {
5751 for (int lx = 0; lx < offset_data.num_x_levels; ++lx) {
5752 int l = ly * offset_data.num_x_levels + lx;
5753 offset_data.offsets[size_t(l)].resize(new_size: size_t(num_y_tiles[size_t(ly)]));
5754
5755 for (size_t dy = 0; dy < offset_data.offsets[size_t(l)].size(); ++dy) {
5756 offset_data.offsets[size_t(l)][dy].resize(new_size: size_t(num_x_tiles[size_t(lx)]));
5757 num_tile_blocks += num_x_tiles[size_t(lx)];
5758 }
5759 }
5760 }
5761 break;
5762
5763 default:
5764 return 0;
5765 }
5766 return num_tile_blocks;
5767}
5768
5769static bool IsAnyOffsetsAreInvalid(const OffsetData& offset_data) {
5770 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l)
5771 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy)
5772 for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx)
5773 if (reinterpret_cast<const tinyexr::tinyexr_int64&>(offset_data.offsets[l][dy][dx]) <= 0)
5774 return true;
5775
5776 return false;
5777}
5778
5779static bool isValidTile(const EXRHeader* exr_header,
5780 const OffsetData& offset_data,
5781 int dx, int dy, int lx, int ly) {
5782 if (lx < 0 || ly < 0 || dx < 0 || dy < 0) return false;
5783 int num_x_levels = offset_data.num_x_levels;
5784 int num_y_levels = offset_data.num_y_levels;
5785 switch (exr_header->tile_level_mode) {
5786 case TINYEXR_TILE_ONE_LEVEL:
5787
5788 if (lx == 0 &&
5789 ly == 0 &&
5790 offset_data.offsets.size() > 0 &&
5791 offset_data.offsets[0].size() > static_cast<size_t>(dy) &&
5792 offset_data.offsets[0][size_t(dy)].size() > static_cast<size_t>(dx)) {
5793 return true;
5794 }
5795
5796 break;
5797
5798 case TINYEXR_TILE_MIPMAP_LEVELS:
5799
5800 if (lx < num_x_levels &&
5801 ly < num_y_levels &&
5802 offset_data.offsets.size() > static_cast<size_t>(lx) &&
5803 offset_data.offsets[size_t(lx)].size() > static_cast<size_t>(dy) &&
5804 offset_data.offsets[size_t(lx)][size_t(dy)].size() > static_cast<size_t>(dx)) {
5805 return true;
5806 }
5807
5808 break;
5809
5810 case TINYEXR_TILE_RIPMAP_LEVELS:
5811 {
5812 size_t idx = static_cast<size_t>(lx) + static_cast<size_t>(ly)* static_cast<size_t>(num_x_levels);
5813 if (lx < num_x_levels &&
5814 ly < num_y_levels &&
5815 (offset_data.offsets.size() > idx) &&
5816 offset_data.offsets[idx].size() > static_cast<size_t>(dy) &&
5817 offset_data.offsets[idx][size_t(dy)].size() > static_cast<size_t>(dx)) {
5818 return true;
5819 }
5820 }
5821
5822 break;
5823
5824 default:
5825
5826 return false;
5827 }
5828
5829 return false;
5830}
5831
5832static bool ReconstructTileOffsets(OffsetData& offset_data,
5833 const EXRHeader* exr_header,
5834 const unsigned char* head, const unsigned char* marker, const size_t size,
5835 bool isMultiPartFile,
5836 bool isDeep) {
5837 int numXLevels = offset_data.num_x_levels;
5838 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) {
5839 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) {
5840 for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) {
5841 tinyexr::tinyexr_uint64 tileOffset = tinyexr::tinyexr_uint64(marker - head);
5842
5843
5844 if (isMultiPartFile) {
5845 if ((marker + sizeof(int)) >= (head + size)) {
5846 return false;
5847 }
5848
5849 //int partNumber;
5850 marker += sizeof(int);
5851 }
5852
5853 if ((marker + 4 * sizeof(int)) >= (head + size)) {
5854 return false;
5855 }
5856
5857 int tileX;
5858 memcpy(dest: &tileX, src: marker, n: sizeof(int));
5859 tinyexr::swap4(val: &tileX);
5860 marker += sizeof(int);
5861
5862 int tileY;
5863 memcpy(dest: &tileY, src: marker, n: sizeof(int));
5864 tinyexr::swap4(val: &tileY);
5865 marker += sizeof(int);
5866
5867 int levelX;
5868 memcpy(dest: &levelX, src: marker, n: sizeof(int));
5869 tinyexr::swap4(val: &levelX);
5870 marker += sizeof(int);
5871
5872 int levelY;
5873 memcpy(dest: &levelY, src: marker, n: sizeof(int));
5874 tinyexr::swap4(val: &levelY);
5875 marker += sizeof(int);
5876
5877 if (isDeep) {
5878 if ((marker + 2 * sizeof(tinyexr::tinyexr_int64)) >= (head + size)) {
5879 return false;
5880 }
5881 tinyexr::tinyexr_int64 packed_offset_table_size;
5882 memcpy(dest: &packed_offset_table_size, src: marker, n: sizeof(tinyexr::tinyexr_int64));
5883 tinyexr::swap8(val: reinterpret_cast<tinyexr::tinyexr_uint64*>(&packed_offset_table_size));
5884 marker += sizeof(tinyexr::tinyexr_int64);
5885
5886 tinyexr::tinyexr_int64 packed_sample_size;
5887 memcpy(dest: &packed_sample_size, src: marker, n: sizeof(tinyexr::tinyexr_int64));
5888 tinyexr::swap8(val: reinterpret_cast<tinyexr::tinyexr_uint64*>(&packed_sample_size));
5889 marker += sizeof(tinyexr::tinyexr_int64);
5890
5891 // next Int64 is unpacked sample size - skip that too
5892 marker += packed_offset_table_size + packed_sample_size + 8;
5893
5894 if (marker >= (head + size)) {
5895 return false;
5896 }
5897
5898 } else {
5899
5900 if ((marker + sizeof(uint32_t)) >= (head + size)) {
5901 return false;
5902 }
5903
5904 uint32_t dataSize;
5905 memcpy(dest: &dataSize, src: marker, n: sizeof(uint32_t));
5906 tinyexr::swap4(val: &dataSize);
5907 marker += sizeof(uint32_t);
5908
5909 marker += dataSize;
5910
5911 if (marker >= (head + size)) {
5912 return false;
5913 }
5914 }
5915
5916 if (!isValidTile(exr_header, offset_data,
5917 dx: tileX, dy: tileY, lx: levelX, ly: levelY)) {
5918 return false;
5919 }
5920
5921 int level_idx = LevelIndex(lx: levelX, ly: levelY, tile_level_mode: exr_header->tile_level_mode, num_x_levels: numXLevels);
5922 if (level_idx < 0) {
5923 return false;
5924 }
5925
5926 if (size_t(level_idx) >= offset_data.offsets.size()) {
5927 return false;
5928 }
5929
5930 if (size_t(tileY) >= offset_data.offsets[size_t(level_idx)].size()) {
5931 return false;
5932 }
5933
5934 if (size_t(tileX) >= offset_data.offsets[size_t(level_idx)][size_t(tileY)].size()) {
5935 return false;
5936 }
5937
5938 offset_data.offsets[size_t(level_idx)][size_t(tileY)][size_t(tileX)] = tileOffset;
5939 }
5940 }
5941 }
5942 return true;
5943}
5944
5945// marker output is also
5946static int ReadOffsets(OffsetData& offset_data,
5947 const unsigned char* head,
5948 const unsigned char*& marker,
5949 const size_t size,
5950 const char** err) {
5951 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) {
5952 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) {
5953 for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) {
5954 tinyexr::tinyexr_uint64 offset;
5955 if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) {
5956 tinyexr::SetErrorMessage(msg: "Insufficient data size in offset table.", err);
5957 return TINYEXR_ERROR_INVALID_DATA;
5958 }
5959
5960 memcpy(dest: &offset, src: marker, n: sizeof(tinyexr::tinyexr_uint64));
5961 tinyexr::swap8(val: &offset);
5962 if (offset >= size) {
5963 tinyexr::SetErrorMessage(msg: "Invalid offset value in DecodeEXRImage.", err);
5964 return TINYEXR_ERROR_INVALID_DATA;
5965 }
5966 marker += sizeof(tinyexr::tinyexr_uint64); // = 8
5967 offset_data.offsets[l][dy][dx] = offset;
5968 }
5969 }
5970 }
5971 return TINYEXR_SUCCESS;
5972}
5973
5974static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header,
5975 const unsigned char *head,
5976 const unsigned char *marker, const size_t size,
5977 const char **err) {
5978 if (exr_image == NULL || exr_header == NULL || head == NULL ||
5979 marker == NULL || (size <= tinyexr::kEXRVersionSize)) {
5980 tinyexr::SetErrorMessage(msg: "Invalid argument for DecodeEXRImage().", err);
5981 return TINYEXR_ERROR_INVALID_ARGUMENT;
5982 }
5983
5984 int num_scanline_blocks = 1;
5985 if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) {
5986 num_scanline_blocks = 16;
5987 } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
5988 num_scanline_blocks = 32;
5989 } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
5990 num_scanline_blocks = 16;
5991 }
5992
5993 if (exr_header->data_window.max_x < exr_header->data_window.min_x ||
5994 exr_header->data_window.max_x - exr_header->data_window.min_x ==
5995 std::numeric_limits<int>::max()) {
5996 // Issue 63
5997 tinyexr::SetErrorMessage(msg: "Invalid data width value", err);
5998 return TINYEXR_ERROR_INVALID_DATA;
5999 }
6000 tinyexr_int64 data_width =
6001 static_cast<tinyexr_int64>(exr_header->data_window.max_x) - static_cast<tinyexr_int64>(exr_header->data_window.min_x) + static_cast<tinyexr_int64>(1);
6002 if (data_width <= 0) {
6003 tinyexr::SetErrorMessage(msg: "Invalid data window width value", err);
6004 return TINYEXR_ERROR_INVALID_DATA;
6005 }
6006
6007 if (exr_header->data_window.max_y < exr_header->data_window.min_y ||
6008 exr_header->data_window.max_y - exr_header->data_window.min_y ==
6009 std::numeric_limits<int>::max()) {
6010 tinyexr::SetErrorMessage(msg: "Invalid data height value", err);
6011 return TINYEXR_ERROR_INVALID_DATA;
6012 }
6013 tinyexr_int64 data_height =
6014 static_cast<tinyexr_int64>(exr_header->data_window.max_y) - static_cast<tinyexr_int64>(exr_header->data_window.min_y) + static_cast<tinyexr_int64>(1);
6015
6016 if (data_height <= 0) {
6017 tinyexr::SetErrorMessage(msg: "Invalid data window height value", err);
6018 return TINYEXR_ERROR_INVALID_DATA;
6019 }
6020
6021 // Do not allow too large data_width and data_height. header invalid?
6022 {
6023 if (data_width > TINYEXR_DIMENSION_THRESHOLD) {
6024 tinyexr::SetErrorMessage(msg: "data width too large.", err);
6025 return TINYEXR_ERROR_INVALID_DATA;
6026 }
6027 if (data_height > TINYEXR_DIMENSION_THRESHOLD) {
6028 tinyexr::SetErrorMessage(msg: "data height too large.", err);
6029 return TINYEXR_ERROR_INVALID_DATA;
6030 }
6031 }
6032
6033 if (exr_header->tiled) {
6034 if (exr_header->tile_size_x > TINYEXR_DIMENSION_THRESHOLD) {
6035 tinyexr::SetErrorMessage(msg: "tile width too large.", err);
6036 return TINYEXR_ERROR_INVALID_DATA;
6037 }
6038 if (exr_header->tile_size_y > TINYEXR_DIMENSION_THRESHOLD) {
6039 tinyexr::SetErrorMessage(msg: "tile height too large.", err);
6040 return TINYEXR_ERROR_INVALID_DATA;
6041 }
6042 }
6043
6044 // Read offset tables.
6045 OffsetData offset_data;
6046 size_t num_blocks = 0;
6047 // For a multi-resolution image, the size of the offset table will be calculated from the other attributes of the header.
6048 // If chunk_count > 0 then chunk_count must be equal to the calculated tile count.
6049 if (exr_header->tiled) {
6050 {
6051 std::vector<int> num_x_tiles, num_y_tiles;
6052 if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header)) {
6053 tinyexr::SetErrorMessage(msg: "Failed to precalculate tile info.", err);
6054 return TINYEXR_ERROR_INVALID_DATA;
6055 }
6056 num_blocks = size_t(InitTileOffsets(offset_data, exr_header, num_x_tiles, num_y_tiles));
6057 if (exr_header->chunk_count > 0) {
6058 if (exr_header->chunk_count != static_cast<int>(num_blocks)) {
6059 tinyexr::SetErrorMessage(msg: "Invalid offset table size.", err);
6060 return TINYEXR_ERROR_INVALID_DATA;
6061 }
6062 }
6063 }
6064
6065 int ret = ReadOffsets(offset_data, head, marker, size, err);
6066 if (ret != TINYEXR_SUCCESS) return ret;
6067 if (IsAnyOffsetsAreInvalid(offset_data)) {
6068 if (!ReconstructTileOffsets(offset_data, exr_header,
6069 head, marker, size,
6070 isMultiPartFile: exr_header->multipart, isDeep: exr_header->non_image)) {
6071
6072 tinyexr::SetErrorMessage(msg: "Invalid Tile Offsets data.", err);
6073 return TINYEXR_ERROR_INVALID_DATA;
6074 }
6075 }
6076 } else if (exr_header->chunk_count > 0) {
6077 // Use `chunkCount` attribute.
6078 num_blocks = static_cast<size_t>(exr_header->chunk_count);
6079 InitSingleResolutionOffsets(offset_data, num_blocks);
6080 } else {
6081 num_blocks = static_cast<size_t>(data_height) /
6082 static_cast<size_t>(num_scanline_blocks);
6083 if (num_blocks * static_cast<size_t>(num_scanline_blocks) <
6084 static_cast<size_t>(data_height)) {
6085 num_blocks++;
6086 }
6087
6088 InitSingleResolutionOffsets(offset_data, num_blocks);
6089 }
6090
6091 if (!exr_header->tiled) {
6092 std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data.offsets[0][0];
6093 for (size_t y = 0; y < num_blocks; y++) {
6094 tinyexr::tinyexr_uint64 offset;
6095 // Issue #81
6096 if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) {
6097 tinyexr::SetErrorMessage(msg: "Insufficient data size in offset table.", err);
6098 return TINYEXR_ERROR_INVALID_DATA;
6099 }
6100
6101 memcpy(dest: &offset, src: marker, n: sizeof(tinyexr::tinyexr_uint64));
6102 tinyexr::swap8(val: &offset);
6103 if (offset >= size) {
6104 tinyexr::SetErrorMessage(msg: "Invalid offset value in DecodeEXRImage.", err);
6105 return TINYEXR_ERROR_INVALID_DATA;
6106 }
6107 marker += sizeof(tinyexr::tinyexr_uint64); // = 8
6108 offsets[y] = offset;
6109 }
6110
6111 // If line offsets are invalid, we try to reconstruct it.
6112 // See OpenEXR/IlmImf/ImfScanLineInputFile.cpp::readLineOffsets() for details.
6113 for (size_t y = 0; y < num_blocks; y++) {
6114 if (offsets[y] <= 0) {
6115 // TODO(syoyo) Report as warning?
6116 // if (err) {
6117 // stringstream ss;
6118 // ss << "Incomplete lineOffsets." << std::endl;
6119 // (*err) += ss.str();
6120 //}
6121 bool ret =
6122 ReconstructLineOffsets(offsets: &offsets, n: num_blocks, head, marker, size);
6123 if (ret) {
6124 // OK
6125 break;
6126 } else {
6127 tinyexr::SetErrorMessage(
6128 msg: "Cannot reconstruct lineOffset table in DecodeEXRImage.", err);
6129 return TINYEXR_ERROR_INVALID_DATA;
6130 }
6131 }
6132 }
6133 }
6134
6135 {
6136 std::string e;
6137 int ret = DecodeChunk(exr_image, exr_header, offset_data, head, size, err: &e);
6138
6139 if (ret != TINYEXR_SUCCESS) {
6140 if (!e.empty()) {
6141 tinyexr::SetErrorMessage(msg: e, err);
6142 }
6143
6144#if 1
6145 FreeEXRImage(exr_image);
6146#else
6147 // release memory(if exists)
6148 if ((exr_header->num_channels > 0) && exr_image && exr_image->images) {
6149 for (size_t c = 0; c < size_t(exr_header->num_channels); c++) {
6150 if (exr_image->images[c]) {
6151 free(exr_image->images[c]);
6152 exr_image->images[c] = NULL;
6153 }
6154 }
6155 free(exr_image->images);
6156 exr_image->images = NULL;
6157 }
6158#endif
6159 }
6160
6161 return ret;
6162 }
6163}
6164
6165static void GetLayers(const EXRHeader &exr_header,
6166 std::vector<std::string> &layer_names) {
6167 // Naive implementation
6168 // Group channels by layers
6169 // go over all channel names, split by periods
6170 // collect unique names
6171 layer_names.clear();
6172 for (int c = 0; c < exr_header.num_channels; c++) {
6173 std::string full_name(exr_header.channels[c].name);
6174 const size_t pos = full_name.find_last_of(c: '.');
6175 if (pos != std::string::npos && pos != 0 && pos + 1 < full_name.size()) {
6176 full_name.erase(pos: pos);
6177 if (std::find(first: layer_names.begin(), last: layer_names.end(), val: full_name) ==
6178 layer_names.end())
6179 layer_names.push_back(x: full_name);
6180 }
6181 }
6182}
6183
6184struct LayerChannel {
6185 explicit LayerChannel(size_t i, std::string n) : index(i), name(n) {}
6186 size_t index;
6187 std::string name;
6188};
6189
6190static void ChannelsInLayer(const EXRHeader &exr_header,
6191 const std::string &layer_name,
6192 std::vector<LayerChannel> &channels) {
6193 channels.clear();
6194 //std::cout << "layer_name = " << layer_name << "\n";
6195 for (int c = 0; c < exr_header.num_channels; c++) {
6196 //std::cout << "chan[" << c << "] = " << exr_header.channels[c].name << "\n";
6197 std::string ch_name(exr_header.channels[c].name);
6198 if (layer_name.empty()) {
6199 const size_t pos = ch_name.find_last_of(c: '.');
6200 if (pos != std::string::npos && pos < ch_name.size()) {
6201 if (pos != 0) continue;
6202 ch_name = ch_name.substr(pos: pos + 1);
6203 }
6204 } else {
6205 const size_t pos = ch_name.find(str: layer_name + '.');
6206 if (pos == std::string::npos) continue;
6207 if (pos == 0) {
6208 ch_name = ch_name.substr(pos: layer_name.size() + 1);
6209 }
6210 }
6211 LayerChannel ch(size_t(c), ch_name);
6212 channels.push_back(x: ch);
6213 }
6214}
6215
6216} // namespace tinyexr
6217
6218int EXRLayers(const char *filename, const char **layer_names[], int *num_layers,
6219 const char **err) {
6220 EXRVersion exr_version;
6221 EXRHeader exr_header;
6222 InitEXRHeader(exr_header: &exr_header);
6223
6224 {
6225 int ret = ParseEXRVersionFromFile(version: &exr_version, filename);
6226 if (ret != TINYEXR_SUCCESS) {
6227 tinyexr::SetErrorMessage(msg: "Invalid EXR header.", err);
6228 return ret;
6229 }
6230
6231 if (exr_version.multipart || exr_version.non_image) {
6232 tinyexr::SetErrorMessage(
6233 msg: "Loading multipart or DeepImage is not supported in LoadEXR() API",
6234 err);
6235 return TINYEXR_ERROR_INVALID_DATA; // @fixme.
6236 }
6237 }
6238
6239 int ret = ParseEXRHeaderFromFile(header: &exr_header, version: &exr_version, filename, err);
6240 if (ret != TINYEXR_SUCCESS) {
6241 FreeEXRHeader(exr_header: &exr_header);
6242 return ret;
6243 }
6244
6245 std::vector<std::string> layer_vec;
6246 tinyexr::GetLayers(exr_header, layer_names&: layer_vec);
6247
6248 (*num_layers) = int(layer_vec.size());
6249 (*layer_names) = static_cast<const char **>(
6250 malloc(size: sizeof(const char *) * static_cast<size_t>(layer_vec.size())));
6251 for (size_t c = 0; c < static_cast<size_t>(layer_vec.size()); c++) {
6252#ifdef _MSC_VER
6253 (*layer_names)[c] = _strdup(layer_vec[c].c_str());
6254#else
6255 (*layer_names)[c] = strdup(s: layer_vec[c].c_str());
6256#endif
6257 }
6258
6259 FreeEXRHeader(exr_header: &exr_header);
6260 return TINYEXR_SUCCESS;
6261}
6262
6263int LoadEXR(float **out_rgba, int *width, int *height, const char *filename,
6264 const char **err) {
6265 return LoadEXRWithLayer(out_rgba, width, height, filename,
6266 /* layername */ NULL, err);
6267}
6268
6269int LoadEXRWithLayer(float **out_rgba, int *width, int *height,
6270 const char *filename, const char *layername,
6271 const char **err) {
6272 if (out_rgba == NULL) {
6273 tinyexr::SetErrorMessage(msg: "Invalid argument for LoadEXR()", err);
6274 return TINYEXR_ERROR_INVALID_ARGUMENT;
6275 }
6276
6277 EXRVersion exr_version;
6278 EXRImage exr_image;
6279 EXRHeader exr_header;
6280 InitEXRHeader(exr_header: &exr_header);
6281 InitEXRImage(exr_image: &exr_image);
6282
6283 {
6284 int ret = ParseEXRVersionFromFile(version: &exr_version, filename);
6285 if (ret != TINYEXR_SUCCESS) {
6286 std::stringstream ss;
6287 ss << "Failed to open EXR file or read version info from EXR file. code("
6288 << ret << ")";
6289 tinyexr::SetErrorMessage(msg: ss.str(), err);
6290 return ret;
6291 }
6292
6293 if (exr_version.multipart || exr_version.non_image) {
6294 tinyexr::SetErrorMessage(
6295 msg: "Loading multipart or DeepImage is not supported in LoadEXR() API",
6296 err);
6297 return TINYEXR_ERROR_INVALID_DATA; // @fixme.
6298 }
6299 }
6300
6301 {
6302 int ret = ParseEXRHeaderFromFile(header: &exr_header, version: &exr_version, filename, err);
6303 if (ret != TINYEXR_SUCCESS) {
6304 FreeEXRHeader(exr_header: &exr_header);
6305 return ret;
6306 }
6307 }
6308
6309 // Read HALF channel as FLOAT.
6310 for (int i = 0; i < exr_header.num_channels; i++) {
6311 if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
6312 exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
6313 }
6314 }
6315
6316 // TODO: Probably limit loading to layers (channels) selected by layer index
6317 {
6318 int ret = LoadEXRImageFromFile(image: &exr_image, header: &exr_header, filename, err);
6319 if (ret != TINYEXR_SUCCESS) {
6320 FreeEXRHeader(exr_header: &exr_header);
6321 return ret;
6322 }
6323 }
6324
6325 // RGBA
6326 int idxR = -1;
6327 int idxG = -1;
6328 int idxB = -1;
6329 int idxA = -1;
6330
6331 std::vector<std::string> layer_names;
6332 tinyexr::GetLayers(exr_header, layer_names);
6333
6334 std::vector<tinyexr::LayerChannel> channels;
6335 tinyexr::ChannelsInLayer(
6336 exr_header, layer_name: layername == NULL ? "" : std::string(layername), channels);
6337
6338
6339 if (channels.size() < 1) {
6340 if (layername == NULL) {
6341 tinyexr::SetErrorMessage(msg: "Layer Not Found. Seems EXR contains channels with layer(e.g. `diffuse.R`). if you are using LoadEXR(), please try LoadEXRWithLayer(). LoadEXR() cannot load EXR having channels with layer.", err);
6342
6343 } else {
6344 tinyexr::SetErrorMessage(msg: "Layer Not Found", err);
6345 }
6346 FreeEXRHeader(exr_header: &exr_header);
6347 FreeEXRImage(exr_image: &exr_image);
6348 return TINYEXR_ERROR_LAYER_NOT_FOUND;
6349 }
6350
6351 size_t ch_count = channels.size() < 4 ? channels.size() : 4;
6352 for (size_t c = 0; c < ch_count; c++) {
6353 const tinyexr::LayerChannel &ch = channels[c];
6354
6355 if (ch.name == "R") {
6356 idxR = int(ch.index);
6357 } else if (ch.name == "G") {
6358 idxG = int(ch.index);
6359 } else if (ch.name == "B") {
6360 idxB = int(ch.index);
6361 } else if (ch.name == "A") {
6362 idxA = int(ch.index);
6363 }
6364 }
6365
6366 if (channels.size() == 1) {
6367 int chIdx = int(channels.front().index);
6368 // Grayscale channel only.
6369
6370 (*out_rgba) = reinterpret_cast<float *>(
6371 malloc(size: 4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
6372 static_cast<size_t>(exr_image.height)));
6373
6374 if (exr_header.tiled) {
6375 const size_t tile_size_x = static_cast<size_t>(exr_header.tile_size_x);
6376 const size_t tile_size_y = static_cast<size_t>(exr_header.tile_size_y);
6377 for (int it = 0; it < exr_image.num_tiles; it++) {
6378 for (size_t j = 0; j < tile_size_y; j++) {
6379 for (size_t i = 0; i < tile_size_x; i++) {
6380 const size_t ii =
6381 static_cast<size_t>(exr_image.tiles[it].offset_x) * tile_size_x +
6382 i;
6383 const size_t jj =
6384 static_cast<size_t>(exr_image.tiles[it].offset_y) * tile_size_y +
6385 j;
6386 const size_t idx = ii + jj * static_cast<size_t>(exr_image.width);
6387
6388 // out of region check.
6389 if (ii >= static_cast<size_t>(exr_image.width)) {
6390 continue;
6391 }
6392 if (jj >= static_cast<size_t>(exr_image.height)) {
6393 continue;
6394 }
6395 const size_t srcIdx = i + j * tile_size_x;
6396 unsigned char **src = exr_image.tiles[it].images;
6397 (*out_rgba)[4 * idx + 0] =
6398 reinterpret_cast<float **>(src)[chIdx][srcIdx];
6399 (*out_rgba)[4 * idx + 1] =
6400 reinterpret_cast<float **>(src)[chIdx][srcIdx];
6401 (*out_rgba)[4 * idx + 2] =
6402 reinterpret_cast<float **>(src)[chIdx][srcIdx];
6403 (*out_rgba)[4 * idx + 3] =
6404 reinterpret_cast<float **>(src)[chIdx][srcIdx];
6405 }
6406 }
6407 }
6408 } else {
6409 const size_t pixel_size = static_cast<size_t>(exr_image.width) *
6410 static_cast<size_t>(exr_image.height);
6411 for (size_t i = 0; i < pixel_size; i++) {
6412 const float val =
6413 reinterpret_cast<float **>(exr_image.images)[chIdx][i];
6414 (*out_rgba)[4 * i + 0] = val;
6415 (*out_rgba)[4 * i + 1] = val;
6416 (*out_rgba)[4 * i + 2] = val;
6417 (*out_rgba)[4 * i + 3] = val;
6418 }
6419 }
6420 } else {
6421 // Assume RGB(A)
6422
6423 if (idxR == -1) {
6424 tinyexr::SetErrorMessage(msg: "R channel not found", err);
6425
6426 FreeEXRHeader(exr_header: &exr_header);
6427 FreeEXRImage(exr_image: &exr_image);
6428 return TINYEXR_ERROR_INVALID_DATA;
6429 }
6430
6431 if (idxG == -1) {
6432 tinyexr::SetErrorMessage(msg: "G channel not found", err);
6433 FreeEXRHeader(exr_header: &exr_header);
6434 FreeEXRImage(exr_image: &exr_image);
6435 return TINYEXR_ERROR_INVALID_DATA;
6436 }
6437
6438 if (idxB == -1) {
6439 tinyexr::SetErrorMessage(msg: "B channel not found", err);
6440 FreeEXRHeader(exr_header: &exr_header);
6441 FreeEXRImage(exr_image: &exr_image);
6442 return TINYEXR_ERROR_INVALID_DATA;
6443 }
6444
6445 (*out_rgba) = reinterpret_cast<float *>(
6446 malloc(size: 4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
6447 static_cast<size_t>(exr_image.height)));
6448 if (exr_header.tiled) {
6449 const size_t tile_size_x = static_cast<size_t>(exr_header.tile_size_x);
6450 const size_t tile_size_y = static_cast<size_t>(exr_header.tile_size_y);
6451 for (int it = 0; it < exr_image.num_tiles; it++) {
6452 for (size_t j = 0; j < tile_size_y; j++) {
6453 for (size_t i = 0; i < tile_size_x; i++) {
6454 const size_t ii =
6455 static_cast<size_t>(exr_image.tiles[it].offset_x) *
6456 tile_size_x +
6457 i;
6458 const size_t jj =
6459 static_cast<size_t>(exr_image.tiles[it].offset_y) *
6460 tile_size_y +
6461 j;
6462 const size_t idx = ii + jj * static_cast<size_t>(exr_image.width);
6463
6464 // out of region check.
6465 if (ii >= static_cast<size_t>(exr_image.width)) {
6466 continue;
6467 }
6468 if (jj >= static_cast<size_t>(exr_image.height)) {
6469 continue;
6470 }
6471 const size_t srcIdx = i + j * tile_size_x;
6472 unsigned char **src = exr_image.tiles[it].images;
6473 (*out_rgba)[4 * idx + 0] =
6474 reinterpret_cast<float **>(src)[idxR][srcIdx];
6475 (*out_rgba)[4 * idx + 1] =
6476 reinterpret_cast<float **>(src)[idxG][srcIdx];
6477 (*out_rgba)[4 * idx + 2] =
6478 reinterpret_cast<float **>(src)[idxB][srcIdx];
6479 if (idxA != -1) {
6480 (*out_rgba)[4 * idx + 3] =
6481 reinterpret_cast<float **>(src)[idxA][srcIdx];
6482 } else {
6483 (*out_rgba)[4 * idx + 3] = 1.0;
6484 }
6485 }
6486 }
6487 }
6488 } else {
6489 const size_t pixel_size = static_cast<size_t>(exr_image.width) *
6490 static_cast<size_t>(exr_image.height);
6491 for (size_t i = 0; i < pixel_size; i++) {
6492 (*out_rgba)[4 * i + 0] =
6493 reinterpret_cast<float **>(exr_image.images)[idxR][i];
6494 (*out_rgba)[4 * i + 1] =
6495 reinterpret_cast<float **>(exr_image.images)[idxG][i];
6496 (*out_rgba)[4 * i + 2] =
6497 reinterpret_cast<float **>(exr_image.images)[idxB][i];
6498 if (idxA != -1) {
6499 (*out_rgba)[4 * i + 3] =
6500 reinterpret_cast<float **>(exr_image.images)[idxA][i];
6501 } else {
6502 (*out_rgba)[4 * i + 3] = 1.0;
6503 }
6504 }
6505 }
6506 }
6507
6508 (*width) = exr_image.width;
6509 (*height) = exr_image.height;
6510
6511 FreeEXRHeader(exr_header: &exr_header);
6512 FreeEXRImage(exr_image: &exr_image);
6513
6514 return TINYEXR_SUCCESS;
6515}
6516
6517int IsEXR(const char *filename) {
6518 EXRVersion exr_version;
6519
6520 int ret = ParseEXRVersionFromFile(version: &exr_version, filename);
6521 if (ret != TINYEXR_SUCCESS) {
6522 return ret;
6523 }
6524
6525 return TINYEXR_SUCCESS;
6526}
6527
6528int IsEXRFromMemory(const unsigned char *memory, size_t size) {
6529 EXRVersion exr_version;
6530
6531 int ret = ParseEXRVersionFromMemory(version: &exr_version, memory, size);
6532 if (ret != TINYEXR_SUCCESS) {
6533 return ret;
6534 }
6535
6536 return TINYEXR_SUCCESS;
6537}
6538
6539int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version,
6540 const unsigned char *memory, size_t size,
6541 const char **err) {
6542 if (memory == NULL || exr_header == NULL) {
6543 tinyexr::SetErrorMessage(
6544 msg: "Invalid argument. `memory` or `exr_header` argument is null in "
6545 "ParseEXRHeaderFromMemory()",
6546 err);
6547
6548 // Invalid argument
6549 return TINYEXR_ERROR_INVALID_ARGUMENT;
6550 }
6551
6552 if (size < tinyexr::kEXRVersionSize) {
6553 tinyexr::SetErrorMessage(msg: "Insufficient header/data size.\n", err);
6554 return TINYEXR_ERROR_INVALID_DATA;
6555 }
6556
6557 const unsigned char *marker = memory + tinyexr::kEXRVersionSize;
6558 size_t marker_size = size - tinyexr::kEXRVersionSize;
6559
6560 tinyexr::HeaderInfo info;
6561 info.clear();
6562
6563 int ret;
6564 {
6565 std::string err_str;
6566 ret = ParseEXRHeader(info: &info, NULL, version, err: &err_str, buf: marker, size: marker_size);
6567
6568 if (ret != TINYEXR_SUCCESS) {
6569 if (err && !err_str.empty()) {
6570 tinyexr::SetErrorMessage(msg: err_str, err);
6571 }
6572 }
6573 }
6574
6575 {
6576 std::string warn;
6577 std::string err_str;
6578
6579 if (!ConvertHeader(exr_header, info, warn: &warn, err: &err_str)) {
6580 // release mem
6581 for (size_t i = 0; i < info.attributes.size(); i++) {
6582 if (info.attributes[i].value) {
6583 free(ptr: info.attributes[i].value);
6584 }
6585 }
6586 if (err && !err_str.empty()) {
6587 tinyexr::SetErrorMessage(msg: err_str, err);
6588 }
6589 ret = TINYEXR_ERROR_INVALID_HEADER;
6590 }
6591 }
6592
6593 exr_header->multipart = version->multipart ? 1 : 0;
6594 exr_header->non_image = version->non_image ? 1 : 0;
6595
6596 return ret;
6597}
6598
6599int LoadEXRFromMemory(float **out_rgba, int *width, int *height,
6600 const unsigned char *memory, size_t size,
6601 const char **err) {
6602 if (out_rgba == NULL || memory == NULL) {
6603 tinyexr::SetErrorMessage(msg: "Invalid argument for LoadEXRFromMemory", err);
6604 return TINYEXR_ERROR_INVALID_ARGUMENT;
6605 }
6606
6607 EXRVersion exr_version;
6608 EXRImage exr_image;
6609 EXRHeader exr_header;
6610
6611 InitEXRHeader(exr_header: &exr_header);
6612
6613 int ret = ParseEXRVersionFromMemory(version: &exr_version, memory, size);
6614 if (ret != TINYEXR_SUCCESS) {
6615 std::stringstream ss;
6616 ss << "Failed to parse EXR version. code(" << ret << ")";
6617 tinyexr::SetErrorMessage(msg: ss.str(), err);
6618 return ret;
6619 }
6620
6621 ret = ParseEXRHeaderFromMemory(exr_header: &exr_header, version: &exr_version, memory, size, err);
6622 if (ret != TINYEXR_SUCCESS) {
6623 return ret;
6624 }
6625
6626 // Read HALF channel as FLOAT.
6627 for (int i = 0; i < exr_header.num_channels; i++) {
6628 if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
6629 exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
6630 }
6631 }
6632
6633 InitEXRImage(exr_image: &exr_image);
6634 ret = LoadEXRImageFromMemory(image: &exr_image, header: &exr_header, memory, size, err);
6635 if (ret != TINYEXR_SUCCESS) {
6636 return ret;
6637 }
6638
6639 // RGBA
6640 int idxR = -1;
6641 int idxG = -1;
6642 int idxB = -1;
6643 int idxA = -1;
6644 for (int c = 0; c < exr_header.num_channels; c++) {
6645 if (strcmp(s1: exr_header.channels[c].name, s2: "R") == 0) {
6646 idxR = c;
6647 } else if (strcmp(s1: exr_header.channels[c].name, s2: "G") == 0) {
6648 idxG = c;
6649 } else if (strcmp(s1: exr_header.channels[c].name, s2: "B") == 0) {
6650 idxB = c;
6651 } else if (strcmp(s1: exr_header.channels[c].name, s2: "A") == 0) {
6652 idxA = c;
6653 }
6654 }
6655
6656 // TODO(syoyo): Refactor removing same code as used in LoadEXR().
6657 if (exr_header.num_channels == 1) {
6658 // Grayscale channel only.
6659
6660 (*out_rgba) = reinterpret_cast<float *>(
6661 malloc(size: 4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
6662 static_cast<size_t>(exr_image.height)));
6663
6664 if (exr_header.tiled) {
6665 const size_t tile_size_x = static_cast<size_t>(exr_header.tile_size_x);
6666 const size_t tile_size_y = static_cast<size_t>(exr_header.tile_size_y);
6667 for (int it = 0; it < exr_image.num_tiles; it++) {
6668 for (size_t j = 0; j < tile_size_y; j++) {
6669 for (size_t i = 0; i < tile_size_x; i++) {
6670 const size_t ii =
6671 static_cast<size_t>(exr_image.tiles[it].offset_x) *
6672 tile_size_x +
6673 i;
6674 const size_t jj =
6675 static_cast<size_t>(exr_image.tiles[it].offset_y) *
6676 tile_size_y +
6677 j;
6678 const size_t idx = ii + jj * static_cast<size_t>(exr_image.width);
6679
6680 // out of region check.
6681 if (ii >= static_cast<size_t>(exr_image.width)) {
6682 continue;
6683 }
6684 if (jj >= static_cast<size_t>(exr_image.height)) {
6685 continue;
6686 }
6687 const size_t srcIdx = i + j * tile_size_x;
6688 unsigned char **src = exr_image.tiles[it].images;
6689 (*out_rgba)[4 * idx + 0] =
6690 reinterpret_cast<float **>(src)[0][srcIdx];
6691 (*out_rgba)[4 * idx + 1] =
6692 reinterpret_cast<float **>(src)[0][srcIdx];
6693 (*out_rgba)[4 * idx + 2] =
6694 reinterpret_cast<float **>(src)[0][srcIdx];
6695 (*out_rgba)[4 * idx + 3] =
6696 reinterpret_cast<float **>(src)[0][srcIdx];
6697 }
6698 }
6699 }
6700 } else {
6701 const size_t pixel_size = static_cast<size_t>(exr_image.width) *
6702 static_cast<size_t>(exr_image.height);
6703 for (size_t i = 0; i < pixel_size; i++) {
6704 const float val = reinterpret_cast<float **>(exr_image.images)[0][i];
6705 (*out_rgba)[4 * i + 0] = val;
6706 (*out_rgba)[4 * i + 1] = val;
6707 (*out_rgba)[4 * i + 2] = val;
6708 (*out_rgba)[4 * i + 3] = val;
6709 }
6710 }
6711
6712 } else {
6713 // TODO(syoyo): Support non RGBA image.
6714
6715 if (idxR == -1) {
6716 tinyexr::SetErrorMessage(msg: "R channel not found", err);
6717
6718 // @todo { free exr_image }
6719 return TINYEXR_ERROR_INVALID_DATA;
6720 }
6721
6722 if (idxG == -1) {
6723 tinyexr::SetErrorMessage(msg: "G channel not found", err);
6724 // @todo { free exr_image }
6725 return TINYEXR_ERROR_INVALID_DATA;
6726 }
6727
6728 if (idxB == -1) {
6729 tinyexr::SetErrorMessage(msg: "B channel not found", err);
6730 // @todo { free exr_image }
6731 return TINYEXR_ERROR_INVALID_DATA;
6732 }
6733
6734 (*out_rgba) = reinterpret_cast<float *>(
6735 malloc(size: 4 * sizeof(float) * static_cast<size_t>(exr_image.width) *
6736 static_cast<size_t>(exr_image.height)));
6737
6738 if (exr_header.tiled) {
6739 const size_t tile_size_x = static_cast<size_t>(exr_header.tile_size_x);
6740 const size_t tile_size_y = static_cast<size_t>(exr_header.tile_size_y);
6741 for (int it = 0; it < exr_image.num_tiles; it++) {
6742 for (size_t j = 0; j < tile_size_y; j++)
6743 for (size_t i = 0; i < tile_size_x; i++) {
6744 const size_t ii =
6745 static_cast<size_t>(exr_image.tiles[it].offset_x) *
6746 tile_size_x +
6747 i;
6748 const size_t jj =
6749 static_cast<size_t>(exr_image.tiles[it].offset_y) *
6750 tile_size_y +
6751 j;
6752 const size_t idx = ii + jj * static_cast<size_t>(exr_image.width);
6753
6754 // out of region check.
6755 if (ii >= static_cast<size_t>(exr_image.width)) {
6756 continue;
6757 }
6758 if (jj >= static_cast<size_t>(exr_image.height)) {
6759 continue;
6760 }
6761 const size_t srcIdx = i + j * tile_size_x;
6762 unsigned char **src = exr_image.tiles[it].images;
6763 (*out_rgba)[4 * idx + 0] =
6764 reinterpret_cast<float **>(src)[idxR][srcIdx];
6765 (*out_rgba)[4 * idx + 1] =
6766 reinterpret_cast<float **>(src)[idxG][srcIdx];
6767 (*out_rgba)[4 * idx + 2] =
6768 reinterpret_cast<float **>(src)[idxB][srcIdx];
6769 if (idxA != -1) {
6770 (*out_rgba)[4 * idx + 3] =
6771 reinterpret_cast<float **>(src)[idxA][srcIdx];
6772 } else {
6773 (*out_rgba)[4 * idx + 3] = 1.0;
6774 }
6775 }
6776 }
6777 } else {
6778 const size_t pixel_size = static_cast<size_t>(exr_image.width) *
6779 static_cast<size_t>(exr_image.height);
6780 for (size_t i = 0; i < pixel_size; i++) {
6781 (*out_rgba)[4 * i + 0] =
6782 reinterpret_cast<float **>(exr_image.images)[idxR][i];
6783 (*out_rgba)[4 * i + 1] =
6784 reinterpret_cast<float **>(exr_image.images)[idxG][i];
6785 (*out_rgba)[4 * i + 2] =
6786 reinterpret_cast<float **>(exr_image.images)[idxB][i];
6787 if (idxA != -1) {
6788 (*out_rgba)[4 * i + 3] =
6789 reinterpret_cast<float **>(exr_image.images)[idxA][i];
6790 } else {
6791 (*out_rgba)[4 * i + 3] = 1.0;
6792 }
6793 }
6794 }
6795 }
6796
6797 (*width) = exr_image.width;
6798 (*height) = exr_image.height;
6799
6800 FreeEXRHeader(exr_header: &exr_header);
6801 FreeEXRImage(exr_image: &exr_image);
6802
6803 return TINYEXR_SUCCESS;
6804}
6805
6806// Represents a read-only file mapped to an address space in memory.
6807// If no memory-mapping API is available, falls back to allocating a buffer
6808// with a copy of the file's data.
6809struct MemoryMappedFile {
6810 unsigned char *data; // To the start of the file's data.
6811 size_t size; // The size of the file in bytes.
6812#ifdef TINYEXR_USE_WIN32_MMAP
6813 HANDLE windows_file;
6814 HANDLE windows_file_mapping;
6815#elif defined(TINYEXR_USE_POSIX_MMAP)
6816 int posix_descriptor;
6817#endif
6818
6819 // MemoryMappedFile's constructor tries to map memory to a file.
6820 // If this succeeds, valid() will return true and all fields
6821 // are usable; otherwise, valid() will return false.
6822 MemoryMappedFile(const char *filename) {
6823 data = NULL;
6824 size = 0;
6825#ifdef TINYEXR_USE_WIN32_MMAP
6826 windows_file_mapping = NULL;
6827 windows_file =
6828 CreateFileW(tinyexr::UTF8ToWchar(filename).c_str(), // lpFileName
6829 GENERIC_READ, // dwDesiredAccess
6830 FILE_SHARE_READ, // dwShareMode
6831 NULL, // lpSecurityAttributes
6832 OPEN_EXISTING, // dwCreationDisposition
6833 FILE_ATTRIBUTE_READONLY, // dwFlagsAndAttributes
6834 NULL); // hTemplateFile
6835 if (windows_file == INVALID_HANDLE_VALUE) {
6836 return;
6837 }
6838
6839 windows_file_mapping = CreateFileMapping(windows_file, // hFile
6840 NULL, // lpFileMappingAttributes
6841 PAGE_READONLY, // flProtect
6842 0, // dwMaximumSizeHigh
6843 0, // dwMaximumSizeLow
6844 NULL); // lpName
6845 if (windows_file_mapping == NULL) {
6846 return;
6847 }
6848
6849 data = reinterpret_cast<unsigned char *>(
6850 MapViewOfFile(windows_file_mapping, // hFileMappingObject
6851 FILE_MAP_READ, // dwDesiredAccess
6852 0, // dwFileOffsetHigh
6853 0, // dwFileOffsetLow
6854 0)); // dwNumberOfBytesToMap
6855 if (!data) {
6856 return;
6857 }
6858
6859 LARGE_INTEGER windows_file_size = {};
6860 if (!GetFileSizeEx(windows_file, &windows_file_size) ||
6861 static_cast<ULONGLONG>(windows_file_size.QuadPart) >
6862 std::numeric_limits<size_t>::max()) {
6863 UnmapViewOfFile(data);
6864 data = NULL;
6865 return;
6866 }
6867 size = static_cast<size_t>(windows_file_size.QuadPart);
6868#elif defined(TINYEXR_USE_POSIX_MMAP)
6869 posix_descriptor = open(file: filename, O_RDONLY);
6870 if (posix_descriptor == -1) {
6871 return;
6872 }
6873
6874 struct stat info;
6875 if (fstat(fd: posix_descriptor, buf: &info) < 0) {
6876 return;
6877 }
6878 // Make sure st_size is in the valid range for a size_t. The second case
6879 // can only fail if a POSIX implementation defines off_t to be a larger
6880 // type than size_t - for instance, compiling with _FILE_OFFSET_BITS=64
6881 // on a 32-bit system. On current 64-bit systems, this check can never
6882 // fail, so we turn off clang's Wtautological-type-limit-compare warning
6883 // around this code.
6884#ifdef __clang__
6885#pragma clang diagnostic push
6886#pragma clang diagnostic ignored "-Wtautological-type-limit-compare"
6887#endif
6888 if (info.st_size < 0 ||
6889 info.st_size > std::numeric_limits<ssize_t>::max()) {
6890 return;
6891 }
6892#ifdef __clang__
6893#pragma clang diagnostic pop
6894#endif
6895 size = static_cast<size_t>(info.st_size);
6896
6897 data = reinterpret_cast<unsigned char *>(
6898 mmap(addr: 0, len: size, PROT_READ, MAP_SHARED, fd: posix_descriptor, offset: 0));
6899 if (data == MAP_FAILED) {
6900 data = nullptr;
6901 return;
6902 }
6903#else
6904 FILE *fp = fopen(filename, "rb");
6905 if (!fp) {
6906 return;
6907 }
6908
6909 // Calling fseek(fp, 0, SEEK_END) isn't strictly-conforming C code, but
6910 // since neither the WIN32 nor POSIX APIs are available in this branch, this
6911 // is a reasonable fallback option.
6912 if (fseek(fp, 0, SEEK_END) != 0) {
6913 fclose(fp);
6914 return;
6915 }
6916 const long ftell_result = ftell(fp);
6917 if (ftell_result < 0) {
6918 // Error from ftell
6919 fclose(fp);
6920 return;
6921 }
6922 size = static_cast<size_t>(ftell_result);
6923 if (fseek(fp, 0, SEEK_SET) != 0) {
6924 fclose(fp);
6925 size = 0;
6926 return;
6927 }
6928
6929 data = reinterpret_cast<unsigned char *>(malloc(size));
6930 if (!data) {
6931 size = 0;
6932 fclose(fp);
6933 return;
6934 }
6935 size_t read_bytes = fread(data, 1, size, fp);
6936 if (read_bytes != size) {
6937 // TODO: Try to read data until reading `size` bytes.
6938 fclose(fp);
6939 size = 0;
6940 data = nullptr;
6941 return;
6942 }
6943 fclose(fp);
6944#endif
6945 }
6946
6947 // MemoryMappedFile's destructor closes all its handles.
6948 ~MemoryMappedFile() {
6949#ifdef TINYEXR_USE_WIN32_MMAP
6950 if (data) {
6951 (void)UnmapViewOfFile(data);
6952 data = NULL;
6953 }
6954
6955 if (windows_file_mapping != NULL) {
6956 (void)CloseHandle(windows_file_mapping);
6957 }
6958
6959 if (windows_file != INVALID_HANDLE_VALUE) {
6960 (void)CloseHandle(windows_file);
6961 }
6962#elif defined(TINYEXR_USE_POSIX_MMAP)
6963 if (data) {
6964 (void)munmap(addr: data, len: size);
6965 data = NULL;
6966 }
6967
6968 if (posix_descriptor != -1) {
6969 (void)close(fd: posix_descriptor);
6970 }
6971#else
6972 if (data) {
6973 (void)free(data);
6974 }
6975 data = NULL;
6976#endif
6977 }
6978
6979 // A MemoryMappedFile cannot be copied or moved.
6980 // Only check for this when compiling with C++11 or higher, since deleted
6981 // function definitions were added then.
6982#if TINYEXR_HAS_CXX11
6983#ifdef __clang__
6984#pragma clang diagnostic push
6985#pragma clang diagnostic ignored "-Wc++98-compat"
6986#endif
6987 MemoryMappedFile(const MemoryMappedFile &) = delete;
6988 MemoryMappedFile &operator=(const MemoryMappedFile &) = delete;
6989 MemoryMappedFile(MemoryMappedFile &&other) noexcept = delete;
6990 MemoryMappedFile &operator=(MemoryMappedFile &&other) noexcept = delete;
6991#ifdef __clang__
6992#pragma clang diagnostic pop
6993#endif
6994#endif
6995
6996 // Returns whether this was successfully opened.
6997 bool valid() const { return data; }
6998};
6999
7000int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header,
7001 const char *filename, const char **err) {
7002 if (exr_image == NULL) {
7003 tinyexr::SetErrorMessage(msg: "Invalid argument for LoadEXRImageFromFile", err);
7004 return TINYEXR_ERROR_INVALID_ARGUMENT;
7005 }
7006
7007 MemoryMappedFile file(filename);
7008 if (!file.valid()) {
7009 tinyexr::SetErrorMessage(msg: "Cannot read file " + std::string(filename), err);
7010 return TINYEXR_ERROR_CANT_OPEN_FILE;
7011 }
7012
7013 if (file.size < 16) {
7014 tinyexr::SetErrorMessage(msg: "File size too short : " + std::string(filename),
7015 err);
7016 return TINYEXR_ERROR_INVALID_FILE;
7017 }
7018
7019 return LoadEXRImageFromMemory(image: exr_image, header: exr_header, memory: file.data, size: file.size,
7020 err);
7021}
7022
7023int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header,
7024 const unsigned char *memory, const size_t size,
7025 const char **err) {
7026 if (exr_image == NULL || memory == NULL ||
7027 (size < tinyexr::kEXRVersionSize)) {
7028 tinyexr::SetErrorMessage(msg: "Invalid argument for LoadEXRImageFromMemory",
7029 err);
7030 return TINYEXR_ERROR_INVALID_ARGUMENT;
7031 }
7032
7033 if (exr_header->header_len == 0) {
7034 tinyexr::SetErrorMessage(msg: "EXRHeader variable is not initialized.", err);
7035 return TINYEXR_ERROR_INVALID_ARGUMENT;
7036 }
7037
7038 const unsigned char *head = memory;
7039 const unsigned char *marker = reinterpret_cast<const unsigned char *>(
7040 memory + exr_header->header_len +
7041 8); // +8 for magic number + version header.
7042 return tinyexr::DecodeEXRImage(exr_image, exr_header, head, marker, size,
7043 err);
7044}
7045
7046namespace tinyexr
7047{
7048
7049#ifdef __clang__
7050#pragma clang diagnostic push
7051#pragma clang diagnostic ignored "-Wsign-conversion"
7052#endif
7053
7054// out_data must be allocated initially with the block-header size
7055// of the current image(-part) type
7056static bool EncodePixelData(/* out */ std::vector<unsigned char>& out_data,
7057 const unsigned char* const* images,
7058 int compression_type,
7059 int /*line_order*/,
7060 int width, // for tiled : tile.width
7061 int /*height*/, // for tiled : header.tile_size_y
7062 int x_stride, // for tiled : header.tile_size_x
7063 int line_no, // for tiled : 0
7064 int num_lines, // for tiled : tile.height
7065 size_t pixel_data_size,
7066 const std::vector<ChannelInfo>& channels,
7067 const std::vector<size_t>& channel_offset_list,
7068 std::string *err,
7069 const void* compression_param = 0) // zfp compression param
7070{
7071 size_t buf_size = static_cast<size_t>(width) *
7072 static_cast<size_t>(num_lines) *
7073 static_cast<size_t>(pixel_data_size);
7074 //int last2bit = (buf_size & 3);
7075 // buf_size must be multiple of four
7076 //if(last2bit) buf_size += 4 - last2bit;
7077 std::vector<unsigned char> buf(buf_size);
7078
7079 size_t start_y = static_cast<size_t>(line_no);
7080 for (size_t c = 0; c < channels.size(); c++) {
7081 if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) {
7082 if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
7083 for (int y = 0; y < num_lines; y++) {
7084 // Assume increasing Y
7085 float *line_ptr = reinterpret_cast<float *>(&buf.at(
7086 n: static_cast<size_t>(pixel_data_size * size_t(y) * size_t(width)) +
7087 channel_offset_list[c] *
7088 static_cast<size_t>(width)));
7089 for (int x = 0; x < width; x++) {
7090 tinyexr::FP16 h16;
7091 h16.u = reinterpret_cast<const unsigned short * const *>(
7092 images)[c][(y + start_y) * size_t(x_stride) + size_t(x)];
7093
7094 tinyexr::FP32 f32 = half_to_float(h: h16);
7095
7096 tinyexr::swap4(val: &f32.f);
7097
7098 // line_ptr[x] = f32.f;
7099 tinyexr::cpy4(dst_val: line_ptr + x, src_val: &(f32.f));
7100 }
7101 }
7102 } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) {
7103 for (int y = 0; y < num_lines; y++) {
7104 // Assume increasing Y
7105 unsigned short *line_ptr = reinterpret_cast<unsigned short *>(
7106 &buf.at(n: static_cast<size_t>(pixel_data_size * y *
7107 width) +
7108 channel_offset_list[c] *
7109 static_cast<size_t>(width)));
7110 for (int x = 0; x < width; x++) {
7111 unsigned short val = reinterpret_cast<const unsigned short * const *>(
7112 images)[c][(y + start_y) * x_stride + x];
7113
7114 tinyexr::swap2(val: &val);
7115
7116 // line_ptr[x] = val;
7117 tinyexr::cpy2(dst_val: line_ptr + x, src_val: &val);
7118 }
7119 }
7120 } else {
7121 if (err) {
7122 (*err) += "Invalid requested_pixel_type.\n";
7123 }
7124 return false;
7125 }
7126
7127 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
7128 if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) {
7129 for (int y = 0; y < num_lines; y++) {
7130 // Assume increasing Y
7131 unsigned short *line_ptr = reinterpret_cast<unsigned short *>(
7132 &buf.at(n: static_cast<size_t>(pixel_data_size * y *
7133 width) +
7134 channel_offset_list[c] *
7135 static_cast<size_t>(width)));
7136 for (int x = 0; x < width; x++) {
7137 tinyexr::FP32 f32;
7138 f32.f = reinterpret_cast<const float * const *>(
7139 images)[c][(y + start_y) * x_stride + x];
7140
7141 tinyexr::FP16 h16;
7142 h16 = float_to_half_full(f: f32);
7143
7144 tinyexr::swap2(val: reinterpret_cast<unsigned short *>(&h16.u));
7145
7146 // line_ptr[x] = h16.u;
7147 tinyexr::cpy2(dst_val: line_ptr + x, src_val: &(h16.u));
7148 }
7149 }
7150 } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_FLOAT) {
7151 for (int y = 0; y < num_lines; y++) {
7152 // Assume increasing Y
7153 float *line_ptr = reinterpret_cast<float *>(&buf.at(
7154 n: static_cast<size_t>(pixel_data_size * y * width) +
7155 channel_offset_list[c] *
7156 static_cast<size_t>(width)));
7157 for (int x = 0; x < width; x++) {
7158 float val = reinterpret_cast<const float * const *>(
7159 images)[c][(y + start_y) * x_stride + x];
7160
7161 tinyexr::swap4(val: &val);
7162
7163 // line_ptr[x] = val;
7164 tinyexr::cpy4(dst_val: line_ptr + x, src_val: &val);
7165 }
7166 }
7167 } else {
7168 if (err) {
7169 (*err) += "Invalid requested_pixel_type.\n";
7170 }
7171 return false;
7172 }
7173 } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) {
7174 for (int y = 0; y < num_lines; y++) {
7175 // Assume increasing Y
7176 unsigned int *line_ptr = reinterpret_cast<unsigned int *>(&buf.at(
7177 n: static_cast<size_t>(pixel_data_size * y * width) +
7178 channel_offset_list[c] * static_cast<size_t>(width)));
7179 for (int x = 0; x < width; x++) {
7180 unsigned int val = reinterpret_cast<const unsigned int * const *>(
7181 images)[c][(y + start_y) * x_stride + x];
7182
7183 tinyexr::swap4(val: &val);
7184
7185 // line_ptr[x] = val;
7186 tinyexr::cpy4(dst_val: line_ptr + x, src_val: &val);
7187 }
7188 }
7189 }
7190 }
7191
7192 if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) {
7193 // 4 byte: scan line
7194 // 4 byte: data size
7195 // ~ : pixel data(uncompressed)
7196 out_data.insert(position: out_data.end(), first: buf.begin(), last: buf.end());
7197
7198 } else if ((compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) ||
7199 (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP)) {
7200#if defined(TINYEXR_USE_MINIZ) && (TINYEXR_USE_MINIZ==1)
7201 std::vector<unsigned char> block(mz_compressBound(
7202 static_cast<unsigned long>(buf.size())));
7203#elif TINYEXR_USE_STB_ZLIB
7204 // there is no compressBound() function, so we use a value that
7205 // is grossly overestimated, but should always work
7206 std::vector<unsigned char> block(256 + 2 * buf.size());
7207#elif defined(TINYEXR_USE_NANOZLIB) && (TINYEXR_USE_NANOZLIB == 1)
7208 std::vector<unsigned char> block(nanoz_compressBound(
7209 static_cast<unsigned long>(buf.size())));
7210#else
7211 std::vector<unsigned char> block(
7212 compressBound(sourceLen: static_cast<uLong>(buf.size())));
7213#endif
7214 tinyexr::tinyexr_uint64 outSize = block.size();
7215
7216 if (!tinyexr::CompressZip(dst: &block.at(n: 0), compressedSize&: outSize,
7217 src: reinterpret_cast<const unsigned char *>(&buf.at(n: 0)),
7218 src_size: static_cast<unsigned long>(buf.size()))) {
7219 if (err) {
7220 (*err) += "Zip compresssion failed.\n";
7221 }
7222 return false;
7223 }
7224
7225 // 4 byte: scan line
7226 // 4 byte: data size
7227 // ~ : pixel data(compressed)
7228 unsigned int data_len = static_cast<unsigned int>(outSize); // truncate
7229
7230 out_data.insert(position: out_data.end(), first: block.begin(), last: block.begin() + data_len);
7231
7232 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) {
7233 // (buf.size() * 3) / 2 would be enough.
7234 std::vector<unsigned char> block((buf.size() * 3) / 2);
7235
7236 tinyexr::tinyexr_uint64 outSize = block.size();
7237
7238 if (!tinyexr::CompressRle(dst: &block.at(n: 0), compressedSize&: outSize,
7239 src: reinterpret_cast<const unsigned char *>(&buf.at(n: 0)),
7240 src_size: static_cast<unsigned long>(buf.size()))) {
7241 if (err) {
7242 (*err) += "RLE compresssion failed.\n";
7243 }
7244 return false;
7245 }
7246
7247 // 4 byte: scan line
7248 // 4 byte: data size
7249 // ~ : pixel data(compressed)
7250 unsigned int data_len = static_cast<unsigned int>(outSize); // truncate
7251 out_data.insert(position: out_data.end(), first: block.begin(), last: block.begin() + data_len);
7252
7253 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
7254#if TINYEXR_USE_PIZ
7255 unsigned int bufLen =
7256 8192 + static_cast<unsigned int>(
7257 2 * static_cast<unsigned int>(
7258 buf.size())); // @fixme { compute good bound. }
7259 std::vector<unsigned char> block(bufLen);
7260 unsigned int outSize = static_cast<unsigned int>(block.size());
7261
7262 if (!CompressPiz(outPtr: &block.at(n: 0), outSize: &outSize,
7263 inPtr: reinterpret_cast<const unsigned char *>(&buf.at(n: 0)),
7264 inSize: buf.size(), channelInfo: channels, data_width: width, num_lines)) {
7265 if (err) {
7266 (*err) += "PIZ compresssion failed.\n";
7267 }
7268 return false;
7269 }
7270
7271 // 4 byte: scan line
7272 // 4 byte: data size
7273 // ~ : pixel data(compressed)
7274 unsigned int data_len = outSize;
7275 out_data.insert(position: out_data.end(), first: block.begin(), last: block.begin() + data_len);
7276
7277#else
7278 if (err) {
7279 (*err) += "PIZ compression is disabled in this build.\n";
7280 }
7281 return false;
7282#endif
7283 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
7284#if TINYEXR_USE_ZFP
7285 const ZFPCompressionParam* zfp_compression_param = reinterpret_cast<const ZFPCompressionParam*>(compression_param);
7286 std::vector<unsigned char> block;
7287 unsigned int outSize;
7288
7289 tinyexr::CompressZfp(
7290 &block, &outSize, reinterpret_cast<const float *>(&buf.at(0)),
7291 width, num_lines, static_cast<int>(channels.size()), *zfp_compression_param);
7292
7293 // 4 byte: scan line
7294 // 4 byte: data size
7295 // ~ : pixel data(compressed)
7296 unsigned int data_len = outSize;
7297 out_data.insert(out_data.end(), block.begin(), block.begin() + data_len);
7298
7299#else
7300 if (err) {
7301 (*err) += "ZFP compression is disabled in this build.\n";
7302 }
7303 (void)compression_param;
7304 return false;
7305#endif
7306 } else {
7307 return false;
7308 }
7309
7310 return true;
7311}
7312
7313static int EncodeTiledLevel(const EXRImage* level_image, const EXRHeader* exr_header,
7314 const std::vector<tinyexr::ChannelInfo>& channels,
7315 std::vector<std::vector<unsigned char> >& data_list,
7316 size_t start_index, // for data_list
7317 int num_x_tiles, int num_y_tiles,
7318 const std::vector<size_t>& channel_offset_list,
7319 int pixel_data_size,
7320 const void* compression_param, // must be set if zfp compression is enabled
7321 std::string* err) {
7322 int num_tiles = num_x_tiles * num_y_tiles;
7323 if (num_tiles != level_image->num_tiles) {
7324 if (err) {
7325 (*err) += "Invalid number of tiles in argument.\n";
7326 }
7327 return TINYEXR_ERROR_INVALID_ARGUMENT;
7328 }
7329
7330 if ((exr_header->tile_size_x > level_image->width || exr_header->tile_size_y > level_image->height) &&
7331 level_image->level_x == 0 && level_image->level_y == 0) {
7332 if (err) {
7333 (*err) += "Failed to encode tile data.\n";
7334 }
7335 return TINYEXR_ERROR_INVALID_DATA;
7336 }
7337
7338
7339#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
7340 std::atomic<bool> invalid_data(false);
7341#else
7342 bool invalid_data(false);
7343#endif
7344
7345#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
7346 std::vector<std::thread> workers;
7347 std::atomic<int> tile_count(0);
7348
7349 int num_threads = std::max(a: 1, b: int(std::thread::hardware_concurrency()));
7350#if (TINYEXR_MAX_THREADS > 0)
7351 num_threads = std::min(num_threads,TINYEXR_MAX_THREADS);
7352#endif
7353 if (num_threads > int(num_tiles)) {
7354 num_threads = int(num_tiles);
7355 }
7356
7357 for (int t = 0; t < num_threads; t++) {
7358 workers.emplace_back(args: std::thread([&]() {
7359 int i = 0;
7360 while ((i = tile_count++) < num_tiles) {
7361
7362#else
7363 // Use signed int since some OpenMP compiler doesn't allow unsigned type for
7364 // `parallel for`
7365#if TINYEXR_USE_OPENMP
7366#pragma omp parallel for
7367#endif
7368 for (int i = 0; i < num_tiles; i++) {
7369
7370#endif
7371 size_t tile_idx = static_cast<size_t>(i);
7372 size_t data_idx = tile_idx + start_index;
7373
7374 int x_tile = i % num_x_tiles;
7375 int y_tile = i / num_x_tiles;
7376
7377 EXRTile& tile = level_image->tiles[tile_idx];
7378
7379 const unsigned char* const* images =
7380 static_cast<const unsigned char* const*>(tile.images);
7381
7382 data_list[data_idx].resize(new_size: 5*sizeof(int));
7383 size_t data_header_size = data_list[data_idx].size();
7384 bool ret = EncodePixelData(out_data&: data_list[data_idx],
7385 images,
7386 compression_type: exr_header->compression_type,
7387 0, // increasing y
7388 width: tile.width,
7389 exr_header->tile_size_y,
7390 x_stride: exr_header->tile_size_x,
7391 line_no: 0,
7392 num_lines: tile.height,
7393 pixel_data_size,
7394 channels,
7395 channel_offset_list,
7396 err, compression_param);
7397 if (!ret) {
7398 invalid_data = true;
7399 continue;
7400 }
7401 if (data_list[data_idx].size() <= data_header_size) {
7402 invalid_data = true;
7403 continue;
7404 }
7405
7406 int data_len = static_cast<int>(data_list[data_idx].size() - data_header_size);
7407 //tileX, tileY, levelX, levelY // pixel_data_size(int)
7408 memcpy(dest: &data_list[data_idx][0], src: &x_tile, n: sizeof(int));
7409 memcpy(dest: &data_list[data_idx][4], src: &y_tile, n: sizeof(int));
7410 memcpy(dest: &data_list[data_idx][8], src: &level_image->level_x, n: sizeof(int));
7411 memcpy(dest: &data_list[data_idx][12], src: &level_image->level_y, n: sizeof(int));
7412 memcpy(dest: &data_list[data_idx][16], src: &data_len, n: sizeof(int));
7413
7414 swap4(val: reinterpret_cast<int*>(&data_list[data_idx][0]));
7415 swap4(val: reinterpret_cast<int*>(&data_list[data_idx][4]));
7416 swap4(val: reinterpret_cast<int*>(&data_list[data_idx][8]));
7417 swap4(val: reinterpret_cast<int*>(&data_list[data_idx][12]));
7418 swap4(val: reinterpret_cast<int*>(&data_list[data_idx][16]));
7419
7420#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
7421 }
7422}));
7423 }
7424
7425 for (auto &t : workers) {
7426 t.join();
7427 }
7428#else
7429 } // omp parallel
7430#endif
7431
7432 if (invalid_data) {
7433 if (err) {
7434 (*err) += "Failed to encode tile data.\n";
7435 }
7436 return TINYEXR_ERROR_INVALID_DATA;
7437 }
7438 return TINYEXR_SUCCESS;
7439}
7440
7441static int NumScanlines(int compression_type) {
7442 int num_scanlines = 1;
7443 if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) {
7444 num_scanlines = 16;
7445 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
7446 num_scanlines = 32;
7447 } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
7448 num_scanlines = 16;
7449 }
7450 return num_scanlines;
7451}
7452
7453static int EncodeChunk(const EXRImage* exr_image, const EXRHeader* exr_header,
7454 const std::vector<ChannelInfo>& channels,
7455 int num_blocks,
7456 tinyexr_uint64 chunk_offset, // starting offset of current chunk
7457 bool is_multipart,
7458 OffsetData& offset_data, // output block offsets, must be initialized
7459 std::vector<std::vector<unsigned char> >& data_list, // output
7460 tinyexr_uint64& total_size, // output: ending offset of current chunk
7461 std::string* err) {
7462 int num_scanlines = NumScanlines(compression_type: exr_header->compression_type);
7463
7464 data_list.resize(new_size: num_blocks);
7465
7466 std::vector<size_t> channel_offset_list(
7467 static_cast<size_t>(exr_header->num_channels));
7468
7469 int pixel_data_size = 0;
7470 {
7471 size_t channel_offset = 0;
7472 for (size_t c = 0; c < static_cast<size_t>(exr_header->num_channels); c++) {
7473 channel_offset_list[c] = channel_offset;
7474 if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_HALF) {
7475 pixel_data_size += sizeof(unsigned short);
7476 channel_offset += sizeof(unsigned short);
7477 } else if (channels[c].requested_pixel_type ==
7478 TINYEXR_PIXELTYPE_FLOAT) {
7479 pixel_data_size += sizeof(float);
7480 channel_offset += sizeof(float);
7481 } else if (channels[c].requested_pixel_type == TINYEXR_PIXELTYPE_UINT) {
7482 pixel_data_size += sizeof(unsigned int);
7483 channel_offset += sizeof(unsigned int);
7484 } else {
7485 if (err) {
7486 (*err) += "Invalid requested_pixel_type.\n";
7487 }
7488 return TINYEXR_ERROR_INVALID_DATA;
7489 }
7490 }
7491 }
7492
7493 const void* compression_param = 0;
7494#if TINYEXR_USE_ZFP
7495 tinyexr::ZFPCompressionParam zfp_compression_param;
7496
7497 // Use ZFP compression parameter from custom attributes(if such a parameter
7498 // exists)
7499 {
7500 std::string e;
7501 bool ret = tinyexr::FindZFPCompressionParam(
7502 &zfp_compression_param, exr_header->custom_attributes,
7503 exr_header->num_custom_attributes, &e);
7504
7505 if (!ret) {
7506 // Use predefined compression parameter.
7507 zfp_compression_param.type = 0;
7508 zfp_compression_param.rate = 2;
7509 }
7510 compression_param = &zfp_compression_param;
7511 }
7512#endif
7513
7514 tinyexr_uint64 offset = chunk_offset;
7515 tinyexr_uint64 doffset = is_multipart ? 4u : 0u;
7516
7517 if (exr_image->tiles) {
7518 const EXRImage* level_image = exr_image;
7519 size_t block_idx = 0;
7520 //tinyexr::tinyexr_uint64 block_data_size = 0;
7521 int num_levels = (exr_header->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) ?
7522 offset_data.num_x_levels : (offset_data.num_x_levels * offset_data.num_y_levels);
7523 for (int level_index = 0; level_index < num_levels; ++level_index) {
7524 if (!level_image) {
7525 if (err) {
7526 (*err) += "Invalid number of tiled levels for EncodeChunk\n";
7527 }
7528 return TINYEXR_ERROR_INVALID_DATA;
7529 }
7530
7531 int level_index_from_image = LevelIndex(lx: level_image->level_x, ly: level_image->level_y,
7532 tile_level_mode: exr_header->tile_level_mode, num_x_levels: offset_data.num_x_levels);
7533 if (level_index_from_image < 0) {
7534 if (err) {
7535 (*err) += "Invalid tile level mode\n";
7536 }
7537 return TINYEXR_ERROR_INVALID_DATA;
7538 }
7539
7540 if (level_index_from_image != level_index) {
7541 if (err) {
7542 (*err) += "Incorrect level ordering in tiled image\n";
7543 }
7544 return TINYEXR_ERROR_INVALID_DATA;
7545 }
7546 int num_y_tiles = int(offset_data.offsets[level_index].size());
7547 if (num_y_tiles <= 0) {
7548 if (err) {
7549 (*err) += "Invalid Y tile size\n";
7550 }
7551 return TINYEXR_ERROR_INVALID_DATA;
7552 }
7553
7554 int num_x_tiles = int(offset_data.offsets[level_index][0].size());
7555 if (num_x_tiles <= 0) {
7556 if (err) {
7557 (*err) += "Invalid X tile size\n";
7558 }
7559 return TINYEXR_ERROR_INVALID_DATA;
7560 }
7561
7562 std::string e;
7563 int ret = EncodeTiledLevel(level_image,
7564 exr_header,
7565 channels,
7566 data_list,
7567 start_index: block_idx,
7568 num_x_tiles,
7569 num_y_tiles,
7570 channel_offset_list,
7571 pixel_data_size,
7572 compression_param,
7573 err: &e);
7574 if (ret != TINYEXR_SUCCESS) {
7575 if (!e.empty() && err) {
7576 (*err) += e;
7577 }
7578 return ret;
7579 }
7580
7581 for (size_t j = 0; j < static_cast<size_t>(num_y_tiles); ++j)
7582 for (size_t i = 0; i < static_cast<size_t>(num_x_tiles); ++i) {
7583 offset_data.offsets[level_index][j][i] = offset;
7584 swap8(val: reinterpret_cast<tinyexr_uint64*>(&offset_data.offsets[level_index][j][i]));
7585 offset += data_list[block_idx].size() + doffset;
7586 //block_data_size += data_list[block_idx].size();
7587 ++block_idx;
7588 }
7589 level_image = level_image->next_level;
7590 }
7591 TINYEXR_CHECK_AND_RETURN_C(static_cast<int>(block_idx) == num_blocks, TINYEXR_ERROR_INVALID_DATA);
7592 total_size = offset;
7593 } else { // scanlines
7594 std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data.offsets[0][0];
7595
7596#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
7597 std::atomic<bool> invalid_data(false);
7598 std::vector<std::thread> workers;
7599 std::atomic<int> block_count(0);
7600
7601 int num_threads = std::min(a: std::max(a: 1, b: int(std::thread::hardware_concurrency())), b: num_blocks);
7602#if (TINYEXR_MAX_THREADS > 0)
7603 num_threads = std::min(num_threads,TINYEXR_MAX_THREADS);
7604#endif
7605 for (int t = 0; t < num_threads; t++) {
7606 workers.emplace_back(args: std::thread([&]() {
7607 int i = 0;
7608 while ((i = block_count++) < num_blocks) {
7609
7610#else
7611 bool invalid_data(false);
7612#if TINYEXR_USE_OPENMP
7613#pragma omp parallel for
7614#endif
7615 for (int i = 0; i < num_blocks; i++) {
7616
7617#endif
7618 int start_y = num_scanlines * i;
7619 int end_Y = (std::min)(a: num_scanlines * (i + 1), b: exr_image->height);
7620 int num_lines = end_Y - start_y;
7621
7622 const unsigned char* const* images =
7623 static_cast<const unsigned char* const*>(exr_image->images);
7624
7625 data_list[i].resize(new_size: 2*sizeof(int));
7626 size_t data_header_size = data_list[i].size();
7627
7628 bool ret = EncodePixelData(out_data&: data_list[i],
7629 images,
7630 compression_type: exr_header->compression_type,
7631 0, // increasing y
7632 width: exr_image->width,
7633 exr_image->height,
7634 x_stride: exr_image->width,
7635 line_no: start_y,
7636 num_lines,
7637 pixel_data_size,
7638 channels,
7639 channel_offset_list,
7640 err,
7641 compression_param);
7642 if (!ret) {
7643 invalid_data = true;
7644 continue; // "break" cannot be used with OpenMP
7645 }
7646 if (data_list[i].size() <= data_header_size) {
7647 invalid_data = true;
7648 continue; // "break" cannot be used with OpenMP
7649 }
7650 int data_len = static_cast<int>(data_list[i].size() - data_header_size);
7651 memcpy(dest: &data_list[i][0], src: &start_y, n: sizeof(int));
7652 memcpy(dest: &data_list[i][4], src: &data_len, n: sizeof(int));
7653
7654 swap4(val: reinterpret_cast<int*>(&data_list[i][0]));
7655 swap4(val: reinterpret_cast<int*>(&data_list[i][4]));
7656#if TINYEXR_HAS_CXX11 && (TINYEXR_USE_THREAD > 0)
7657 }
7658 }));
7659 }
7660
7661 for (auto &t : workers) {
7662 t.join();
7663 }
7664#else
7665 } // omp parallel
7666#endif
7667
7668 if (invalid_data) {
7669 if (err) {
7670 (*err) += "Failed to encode scanline data.\n";
7671 }
7672 return TINYEXR_ERROR_INVALID_DATA;
7673 }
7674
7675 for (size_t i = 0; i < static_cast<size_t>(num_blocks); i++) {
7676 offsets[i] = offset;
7677 tinyexr::swap8(val: reinterpret_cast<tinyexr::tinyexr_uint64 *>(&offsets[i]));
7678 offset += data_list[i].size() + doffset;
7679 }
7680
7681 total_size = static_cast<size_t>(offset);
7682 }
7683 return TINYEXR_SUCCESS;
7684}
7685
7686// can save a single or multi-part image (no deep* formats)
7687static size_t SaveEXRNPartImageToMemory(const EXRImage* exr_images,
7688 const EXRHeader** exr_headers,
7689 unsigned int num_parts,
7690 unsigned char** memory_out, const char** err) {
7691 if (exr_images == NULL || exr_headers == NULL || num_parts == 0 ||
7692 memory_out == NULL) {
7693 SetErrorMessage(msg: "Invalid argument for SaveEXRNPartImageToMemory",
7694 err);
7695 return 0;
7696 }
7697 {
7698 for (unsigned int i = 0; i < num_parts; ++i) {
7699 if (exr_headers[i]->compression_type < 0) {
7700 SetErrorMessage(msg: "Invalid argument for SaveEXRNPartImageToMemory",
7701 err);
7702 return 0;
7703 }
7704#if !TINYEXR_USE_PIZ
7705 if (exr_headers[i]->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
7706 SetErrorMessage("PIZ compression is not supported in this build",
7707 err);
7708 return 0;
7709 }
7710#endif
7711 if (exr_headers[i]->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
7712#if !TINYEXR_USE_ZFP
7713 SetErrorMessage(msg: "ZFP compression is not supported in this build",
7714 err);
7715 return 0;
7716#else
7717 // All channels must be fp32.
7718 // No fp16 support in ZFP atm(as of 2023 June)
7719 // https://github.com/LLNL/fpzip/issues/2
7720 for (int c = 0; c < exr_headers[i]->num_channels; ++c) {
7721 if (exr_headers[i]->requested_pixel_types[c] != TINYEXR_PIXELTYPE_FLOAT) {
7722 SetErrorMessage("Pixel type must be FLOAT for ZFP compression",
7723 err);
7724 return 0;
7725 }
7726 }
7727#endif
7728 }
7729 }
7730 }
7731
7732 std::vector<unsigned char> memory;
7733
7734 // Header
7735 {
7736 const char header[] = { 0x76, 0x2f, 0x31, 0x01 };
7737 memory.insert(position: memory.end(), first: header, last: header + 4);
7738 }
7739
7740 // Version
7741 // using value from the first header
7742 int long_name = exr_headers[0]->long_name;
7743 {
7744 char marker[] = { 2, 0, 0, 0 };
7745 /* @todo
7746 if (exr_header->non_image) {
7747 marker[1] |= 0x8;
7748 }
7749 */
7750 // tiled
7751 if (num_parts == 1 && exr_images[0].tiles) {
7752 marker[1] |= 0x2;
7753 }
7754 // long_name
7755 if (long_name) {
7756 marker[1] |= 0x4;
7757 }
7758 // multipart
7759 if (num_parts > 1) {
7760 marker[1] |= 0x10;
7761 }
7762 memory.insert(position: memory.end(), first: marker, last: marker + 4);
7763 }
7764
7765 int total_chunk_count = 0;
7766 std::vector<int> chunk_count(num_parts);
7767 std::vector<OffsetData> offset_data(num_parts);
7768 for (unsigned int i = 0; i < num_parts; ++i) {
7769 if (!exr_images[i].tiles) {
7770 int num_scanlines = NumScanlines(compression_type: exr_headers[i]->compression_type);
7771 chunk_count[i] =
7772 (exr_images[i].height + num_scanlines - 1) / num_scanlines;
7773 InitSingleResolutionOffsets(offset_data&: offset_data[i], num_blocks: chunk_count[i]);
7774 total_chunk_count += chunk_count[i];
7775 } else {
7776 {
7777 std::vector<int> num_x_tiles, num_y_tiles;
7778 if (!PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header: exr_headers[i])) {
7779 SetErrorMessage(msg: "Failed to precalculate Tile info",
7780 err);
7781 return TINYEXR_ERROR_INVALID_DATA;
7782 }
7783 int ntiles = InitTileOffsets(offset_data&: offset_data[i], exr_header: exr_headers[i], num_x_tiles, num_y_tiles);
7784 if (ntiles > 0) {
7785 chunk_count[i] = ntiles;
7786 } else {
7787 SetErrorMessage(msg: "Failed to compute Tile offsets",
7788 err);
7789 return TINYEXR_ERROR_INVALID_DATA;
7790
7791 }
7792 total_chunk_count += chunk_count[i];
7793 }
7794 }
7795 }
7796 // Write attributes to memory buffer.
7797 std::vector< std::vector<tinyexr::ChannelInfo> > channels(num_parts);
7798 {
7799 std::set<std::string> partnames;
7800 for (unsigned int i = 0; i < num_parts; ++i) {
7801 //channels
7802 {
7803 std::vector<unsigned char> data;
7804
7805 for (int c = 0; c < exr_headers[i]->num_channels; c++) {
7806 tinyexr::ChannelInfo info;
7807 info.p_linear = 0;
7808 info.pixel_type = exr_headers[i]->pixel_types[c];
7809 info.requested_pixel_type = exr_headers[i]->requested_pixel_types[c];
7810 info.x_sampling = 1;
7811 info.y_sampling = 1;
7812 info.name = std::string(exr_headers[i]->channels[c].name);
7813 channels[i].push_back(x: info);
7814 }
7815
7816 tinyexr::WriteChannelInfo(data, channels: channels[i]);
7817
7818 tinyexr::WriteAttributeToMemory(out: &memory, name: "channels", type: "chlist", data: &data.at(n: 0),
7819 len: static_cast<int>(data.size()));
7820 }
7821
7822 {
7823 int comp = exr_headers[i]->compression_type;
7824 swap4(val: &comp);
7825 WriteAttributeToMemory(
7826 out: &memory, name: "compression", type: "compression",
7827 data: reinterpret_cast<const unsigned char*>(&comp), len: 1);
7828 }
7829
7830 {
7831 int data[4] = { 0, 0, exr_images[i].width - 1, exr_images[i].height - 1 };
7832 swap4(val: &data[0]);
7833 swap4(val: &data[1]);
7834 swap4(val: &data[2]);
7835 swap4(val: &data[3]);
7836 WriteAttributeToMemory(
7837 out: &memory, name: "dataWindow", type: "box2i",
7838 data: reinterpret_cast<const unsigned char*>(data), len: sizeof(int) * 4);
7839
7840 int data0[4] = { 0, 0, exr_images[0].width - 1, exr_images[0].height - 1 };
7841 swap4(val: &data0[0]);
7842 swap4(val: &data0[1]);
7843 swap4(val: &data0[2]);
7844 swap4(val: &data0[3]);
7845 // Note: must be the same across parts (currently, using value from the first header)
7846 WriteAttributeToMemory(
7847 out: &memory, name: "displayWindow", type: "box2i",
7848 data: reinterpret_cast<const unsigned char*>(data0), len: sizeof(int) * 4);
7849 }
7850
7851 {
7852 unsigned char line_order = 0; // @fixme { read line_order from EXRHeader }
7853 WriteAttributeToMemory(out: &memory, name: "lineOrder", type: "lineOrder",
7854 data: &line_order, len: 1);
7855 }
7856
7857 {
7858 // Note: must be the same across parts
7859 float aspectRatio = 1.0f;
7860 swap4(val: &aspectRatio);
7861 WriteAttributeToMemory(
7862 out: &memory, name: "pixelAspectRatio", type: "float",
7863 data: reinterpret_cast<const unsigned char*>(&aspectRatio), len: sizeof(float));
7864 }
7865
7866 {
7867 float center[2] = { 0.0f, 0.0f };
7868 swap4(val: &center[0]);
7869 swap4(val: &center[1]);
7870 WriteAttributeToMemory(
7871 out: &memory, name: "screenWindowCenter", type: "v2f",
7872 data: reinterpret_cast<const unsigned char*>(center), len: 2 * sizeof(float));
7873 }
7874
7875 {
7876 float w = 1.0f;
7877 swap4(val: &w);
7878 WriteAttributeToMemory(out: &memory, name: "screenWindowWidth", type: "float",
7879 data: reinterpret_cast<const unsigned char*>(&w),
7880 len: sizeof(float));
7881 }
7882
7883 if (exr_images[i].tiles) {
7884 unsigned char tile_mode = static_cast<unsigned char>(exr_headers[i]->tile_level_mode & 0x3);
7885 if (exr_headers[i]->tile_rounding_mode) tile_mode |= (1u << 4u);
7886 //unsigned char data[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
7887 unsigned int datai[3] = { 0, 0, 0 };
7888 unsigned char* data = reinterpret_cast<unsigned char*>(&datai[0]);
7889 datai[0] = static_cast<unsigned int>(exr_headers[i]->tile_size_x);
7890 datai[1] = static_cast<unsigned int>(exr_headers[i]->tile_size_y);
7891 data[8] = tile_mode;
7892 swap4(val: reinterpret_cast<unsigned int*>(&data[0]));
7893 swap4(val: reinterpret_cast<unsigned int*>(&data[4]));
7894 WriteAttributeToMemory(
7895 out: &memory, name: "tiles", type: "tiledesc",
7896 data: reinterpret_cast<const unsigned char*>(data), len: 9);
7897 }
7898
7899 // must be present for multi-part files - according to spec.
7900 if (num_parts > 1) {
7901 // name
7902 {
7903 size_t len = 0;
7904 if ((len = strlen(s: exr_headers[i]->name)) > 0) {
7905#if TINYEXR_HAS_CXX11
7906 partnames.emplace(args: exr_headers[i]->name);
7907#else
7908 partnames.insert(std::string(exr_headers[i]->name));
7909#endif
7910 if (partnames.size() != i + 1) {
7911 SetErrorMessage(msg: "'name' attributes must be unique for a multi-part file", err);
7912 return 0;
7913 }
7914 WriteAttributeToMemory(
7915 out: &memory, name: "name", type: "string",
7916 data: reinterpret_cast<const unsigned char*>(exr_headers[i]->name),
7917 len: static_cast<int>(len));
7918 } else {
7919 SetErrorMessage(msg: "Invalid 'name' attribute for a multi-part file", err);
7920 return 0;
7921 }
7922 }
7923 // type
7924 {
7925 const char* type = "scanlineimage";
7926 if (exr_images[i].tiles) type = "tiledimage";
7927 WriteAttributeToMemory(
7928 out: &memory, name: "type", type: "string",
7929 data: reinterpret_cast<const unsigned char*>(type),
7930 len: static_cast<int>(strlen(s: type)));
7931 }
7932 // chunkCount
7933 {
7934 WriteAttributeToMemory(
7935 out: &memory, name: "chunkCount", type: "int",
7936 data: reinterpret_cast<const unsigned char*>(&chunk_count[i]),
7937 len: 4);
7938 }
7939 }
7940
7941 // Custom attributes
7942 if (exr_headers[i]->num_custom_attributes > 0) {
7943 for (int j = 0; j < exr_headers[i]->num_custom_attributes; j++) {
7944 tinyexr::WriteAttributeToMemory(
7945 out: &memory, name: exr_headers[i]->custom_attributes[j].name,
7946 type: exr_headers[i]->custom_attributes[j].type,
7947 data: reinterpret_cast<const unsigned char*>(
7948 exr_headers[i]->custom_attributes[j].value),
7949 len: exr_headers[i]->custom_attributes[j].size);
7950 }
7951 }
7952
7953 { // end of header
7954 memory.push_back(x: 0);
7955 }
7956 }
7957 }
7958 if (num_parts > 1) {
7959 // end of header list
7960 memory.push_back(x: 0);
7961 }
7962
7963 tinyexr_uint64 chunk_offset = memory.size() + size_t(total_chunk_count) * sizeof(tinyexr_uint64);
7964
7965 tinyexr_uint64 total_size = 0;
7966 std::vector< std::vector< std::vector<unsigned char> > > data_lists(num_parts);
7967 for (unsigned int i = 0; i < num_parts; ++i) {
7968 std::string e;
7969 int ret = EncodeChunk(exr_image: &exr_images[i], exr_header: exr_headers[i],
7970 channels: channels[i],
7971 num_blocks: chunk_count[i],
7972 // starting offset of current chunk after part-number
7973 chunk_offset,
7974 is_multipart: num_parts > 1,
7975 offset_data&: offset_data[i], // output: block offsets, must be initialized
7976 data_list&: data_lists[i], // output
7977 total_size, // output
7978 err: &e);
7979 if (ret != TINYEXR_SUCCESS) {
7980 if (!e.empty()) {
7981 tinyexr::SetErrorMessage(msg: e, err);
7982 }
7983 return 0;
7984 }
7985 chunk_offset = total_size;
7986 }
7987
7988 // Allocating required memory
7989 if (total_size == 0) { // something went wrong
7990 tinyexr::SetErrorMessage(msg: "Output memory size is zero", err);
7991 return TINYEXR_ERROR_INVALID_DATA;
7992 }
7993 (*memory_out) = static_cast<unsigned char*>(malloc(size: size_t(total_size)));
7994
7995 // Writing header
7996 memcpy(dest: (*memory_out), src: &memory[0], n: memory.size());
7997 unsigned char* memory_ptr = *memory_out + memory.size();
7998 size_t sum = memory.size();
7999
8000 // Writing offset data for chunks
8001 for (unsigned int i = 0; i < num_parts; ++i) {
8002 if (exr_images[i].tiles) {
8003 const EXRImage* level_image = &exr_images[i];
8004 int num_levels = (exr_headers[i]->tile_level_mode != TINYEXR_TILE_RIPMAP_LEVELS) ?
8005 offset_data[i].num_x_levels : (offset_data[i].num_x_levels * offset_data[i].num_y_levels);
8006 for (int level_index = 0; level_index < num_levels; ++level_index) {
8007 for (size_t j = 0; j < offset_data[i].offsets[level_index].size(); ++j) {
8008 size_t num_bytes = sizeof(tinyexr_uint64) * offset_data[i].offsets[level_index][j].size();
8009 sum += num_bytes;
8010 if (sum > total_size) {
8011 tinyexr::SetErrorMessage(msg: "Invalid offset bytes in Tiled Part image.", err);
8012 return TINYEXR_ERROR_INVALID_DATA;
8013 }
8014
8015 memcpy(dest: memory_ptr,
8016 src: reinterpret_cast<unsigned char*>(&offset_data[i].offsets[level_index][j][0]),
8017 n: num_bytes);
8018 memory_ptr += num_bytes;
8019 }
8020 level_image = level_image->next_level;
8021 }
8022 } else {
8023 size_t num_bytes = sizeof(tinyexr::tinyexr_uint64) * static_cast<size_t>(chunk_count[i]);
8024 sum += num_bytes;
8025 if (sum > total_size) {
8026 tinyexr::SetErrorMessage(msg: "Invalid offset bytes in Part image.", err);
8027 return TINYEXR_ERROR_INVALID_DATA;
8028 }
8029 std::vector<tinyexr::tinyexr_uint64>& offsets = offset_data[i].offsets[0][0];
8030 memcpy(dest: memory_ptr, src: reinterpret_cast<unsigned char*>(&offsets[0]), n: num_bytes);
8031 memory_ptr += num_bytes;
8032 }
8033 }
8034
8035 // Writing chunk data
8036 for (unsigned int i = 0; i < num_parts; ++i) {
8037 for (size_t j = 0; j < static_cast<size_t>(chunk_count[i]); ++j) {
8038 if (num_parts > 1) {
8039 sum += 4;
8040 if (sum > total_size) {
8041 tinyexr::SetErrorMessage(msg: "Buffer overrun in reading Part image chunk data.", err);
8042 return TINYEXR_ERROR_INVALID_DATA;
8043 }
8044 unsigned int part_number = i;
8045 swap4(val: &part_number);
8046 memcpy(dest: memory_ptr, src: &part_number, n: 4);
8047 memory_ptr += 4;
8048 }
8049 sum += data_lists[i][j].size();
8050 if (sum > total_size) {
8051 tinyexr::SetErrorMessage(msg: "Buffer overrun in reading Part image chunk data.", err);
8052 return TINYEXR_ERROR_INVALID_DATA;
8053 }
8054 memcpy(dest: memory_ptr, src: &data_lists[i][j][0], n: data_lists[i][j].size());
8055 memory_ptr += data_lists[i][j].size();
8056 }
8057 }
8058
8059 if (sum != total_size) {
8060 tinyexr::SetErrorMessage(msg: "Corrupted Part image chunk data.", err);
8061 return TINYEXR_ERROR_INVALID_DATA;
8062 }
8063
8064 return size_t(total_size); // OK
8065}
8066
8067#ifdef __clang__
8068#pragma clang diagnostic pop
8069#endif
8070
8071} // tinyexr
8072
8073size_t SaveEXRImageToMemory(const EXRImage* exr_image,
8074 const EXRHeader* exr_header,
8075 unsigned char** memory_out, const char** err) {
8076 return tinyexr::SaveEXRNPartImageToMemory(exr_images: exr_image, exr_headers: &exr_header, num_parts: 1, memory_out, err);
8077}
8078
8079int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header,
8080 const char *filename, const char **err) {
8081 if (exr_image == NULL || filename == NULL ||
8082 exr_header->compression_type < 0) {
8083 tinyexr::SetErrorMessage(msg: "Invalid argument for SaveEXRImageToFile", err);
8084 return TINYEXR_ERROR_INVALID_ARGUMENT;
8085 }
8086
8087#if !TINYEXR_USE_PIZ
8088 if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) {
8089 tinyexr::SetErrorMessage("PIZ compression is not supported in this build",
8090 err);
8091 return TINYEXR_ERROR_UNSUPPORTED_FEATURE;
8092 }
8093#endif
8094
8095#if !TINYEXR_USE_ZFP
8096 if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) {
8097 tinyexr::SetErrorMessage(msg: "ZFP compression is not supported in this build",
8098 err);
8099 return TINYEXR_ERROR_UNSUPPORTED_FEATURE;
8100 }
8101#endif
8102
8103 FILE *fp = NULL;
8104#ifdef _WIN32
8105#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang
8106 errno_t errcode =
8107 _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"wb");
8108 if (errcode != 0) {
8109 tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename),
8110 err);
8111 return TINYEXR_ERROR_CANT_WRITE_FILE;
8112 }
8113#else
8114 // Unknown compiler or MinGW without MINGW_HAS_SECURE_API.
8115 fp = fopen(filename, "wb");
8116#endif
8117#else
8118 fp = fopen(filename: filename, modes: "wb");
8119#endif
8120 if (!fp) {
8121 tinyexr::SetErrorMessage(msg: "Cannot write a file: " + std::string(filename),
8122 err);
8123 return TINYEXR_ERROR_CANT_WRITE_FILE;
8124 }
8125
8126 unsigned char *mem = NULL;
8127 size_t mem_size = SaveEXRImageToMemory(exr_image, exr_header, memory_out: &mem, err);
8128 if (mem_size == 0) {
8129 fclose(stream: fp);
8130 return TINYEXR_ERROR_SERIALIZATION_FAILED;
8131 }
8132
8133 size_t written_size = 0;
8134 if ((mem_size > 0) && mem) {
8135 written_size = fwrite(ptr: mem, size: 1, n: mem_size, s: fp);
8136 }
8137 free(ptr: mem);
8138
8139 fclose(stream: fp);
8140
8141 if (written_size != mem_size) {
8142 tinyexr::SetErrorMessage(msg: "Cannot write a file", err);
8143 return TINYEXR_ERROR_CANT_WRITE_FILE;
8144 }
8145
8146 return TINYEXR_SUCCESS;
8147}
8148
8149size_t SaveEXRMultipartImageToMemory(const EXRImage* exr_images,
8150 const EXRHeader** exr_headers,
8151 unsigned int num_parts,
8152 unsigned char** memory_out, const char** err) {
8153 if (exr_images == NULL || exr_headers == NULL || num_parts == 0 ||
8154 memory_out == NULL) {
8155 tinyexr::SetErrorMessage(msg: "Invalid argument for SaveEXRNPartImageToMemory",
8156 err);
8157 return 0;
8158 }
8159 return tinyexr::SaveEXRNPartImageToMemory(exr_images, exr_headers, num_parts, memory_out, err);
8160}
8161
8162int SaveEXRMultipartImageToFile(const EXRImage* exr_images,
8163 const EXRHeader** exr_headers,
8164 unsigned int num_parts,
8165 const char* filename,
8166 const char** err) {
8167 if (exr_images == NULL || exr_headers == NULL || num_parts == 0) {
8168 tinyexr::SetErrorMessage(msg: "Invalid argument for SaveEXRMultipartImageToFile",
8169 err);
8170 return TINYEXR_ERROR_INVALID_ARGUMENT;
8171 }
8172
8173 FILE *fp = NULL;
8174#ifdef _WIN32
8175#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang.
8176 errno_t errcode =
8177 _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"wb");
8178 if (errcode != 0) {
8179 tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename),
8180 err);
8181 return TINYEXR_ERROR_CANT_WRITE_FILE;
8182 }
8183#else
8184 // Unknown compiler or MinGW without MINGW_HAS_SECURE_API.
8185 fp = fopen(filename, "wb");
8186#endif
8187#else
8188 fp = fopen(filename: filename, modes: "wb");
8189#endif
8190 if (!fp) {
8191 tinyexr::SetErrorMessage(msg: "Cannot write a file: " + std::string(filename),
8192 err);
8193 return TINYEXR_ERROR_CANT_WRITE_FILE;
8194 }
8195
8196 unsigned char *mem = NULL;
8197 size_t mem_size = SaveEXRMultipartImageToMemory(exr_images, exr_headers, num_parts, memory_out: &mem, err);
8198 if (mem_size == 0) {
8199 fclose(stream: fp);
8200 return TINYEXR_ERROR_SERIALIZATION_FAILED;
8201 }
8202
8203 size_t written_size = 0;
8204 if ((mem_size > 0) && mem) {
8205 written_size = fwrite(ptr: mem, size: 1, n: mem_size, s: fp);
8206 }
8207 free(ptr: mem);
8208
8209 fclose(stream: fp);
8210
8211 if (written_size != mem_size) {
8212 tinyexr::SetErrorMessage(msg: "Cannot write a file", err);
8213 return TINYEXR_ERROR_CANT_WRITE_FILE;
8214 }
8215
8216 return TINYEXR_SUCCESS;
8217}
8218
8219int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) {
8220 if (deep_image == NULL) {
8221 tinyexr::SetErrorMessage(msg: "Invalid argument for LoadDeepEXR", err);
8222 return TINYEXR_ERROR_INVALID_ARGUMENT;
8223 }
8224
8225 MemoryMappedFile file(filename);
8226 if (!file.valid()) {
8227 tinyexr::SetErrorMessage(msg: "Cannot read file " + std::string(filename), err);
8228 return TINYEXR_ERROR_CANT_OPEN_FILE;
8229 }
8230
8231 if (file.size == 0) {
8232 tinyexr::SetErrorMessage(msg: "File size is zero : " + std::string(filename),
8233 err);
8234 return TINYEXR_ERROR_INVALID_FILE;
8235 }
8236
8237 const char *head = reinterpret_cast<const char *>(file.data);
8238 const char *marker = reinterpret_cast<const char *>(file.data);
8239
8240 // Header check.
8241 {
8242 const char header[] = {0x76, 0x2f, 0x31, 0x01};
8243
8244 if (memcmp(s1: marker, s2: header, n: 4) != 0) {
8245 tinyexr::SetErrorMessage(msg: "Invalid magic number", err);
8246 return TINYEXR_ERROR_INVALID_MAGIC_NUMBER;
8247 }
8248 marker += 4;
8249 }
8250
8251 // Version, scanline.
8252 {
8253 // ver 2.0, scanline, deep bit on(0x800)
8254 // must be [2, 0, 0, 0]
8255 if (marker[0] != 2 || marker[1] != 8 || marker[2] != 0 || marker[3] != 0) {
8256 tinyexr::SetErrorMessage(msg: "Unsupported version or scanline", err);
8257 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
8258 }
8259
8260 marker += 4;
8261 }
8262
8263 int dx = -1;
8264 int dy = -1;
8265 int dw = -1;
8266 int dh = -1;
8267 int num_scanline_blocks = 1; // 16 for ZIP compression.
8268 int compression_type = -1;
8269 int num_channels = -1;
8270 std::vector<tinyexr::ChannelInfo> channels;
8271
8272 // Read attributes
8273 size_t size = file.size - tinyexr::kEXRVersionSize;
8274 for (;;) {
8275 if (0 == size) {
8276 return TINYEXR_ERROR_INVALID_DATA;
8277 } else if (marker[0] == '\0') {
8278 marker++;
8279 size--;
8280 break;
8281 }
8282
8283 std::string attr_name;
8284 std::string attr_type;
8285 std::vector<unsigned char> data;
8286 size_t marker_size;
8287 if (!tinyexr::ReadAttribute(name: &attr_name, type: &attr_type, data: &data, marker_size: &marker_size,
8288 marker, size)) {
8289 std::stringstream ss;
8290 ss << "Failed to parse attribute\n";
8291 tinyexr::SetErrorMessage(msg: ss.str(), err);
8292 return TINYEXR_ERROR_INVALID_DATA;
8293 }
8294 marker += marker_size;
8295 size -= marker_size;
8296
8297 if (attr_name.compare(s: "compression") == 0) {
8298 compression_type = data[0];
8299 if (compression_type > TINYEXR_COMPRESSIONTYPE_PIZ) {
8300 std::stringstream ss;
8301 ss << "Unsupported compression type : " << compression_type;
8302 tinyexr::SetErrorMessage(msg: ss.str(), err);
8303 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
8304 }
8305
8306 if (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) {
8307 num_scanline_blocks = 16;
8308 }
8309
8310 } else if (attr_name.compare(s: "channels") == 0) {
8311 // name: zero-terminated string, from 1 to 255 bytes long
8312 // pixel type: int, possible values are: UINT = 0 HALF = 1 FLOAT = 2
8313 // pLinear: unsigned char, possible values are 0 and 1
8314 // reserved: three chars, should be zero
8315 // xSampling: int
8316 // ySampling: int
8317
8318 if (!tinyexr::ReadChannelInfo(channels, data)) {
8319 tinyexr::SetErrorMessage(msg: "Failed to parse channel info", err);
8320 return TINYEXR_ERROR_INVALID_DATA;
8321 }
8322
8323 num_channels = static_cast<int>(channels.size());
8324
8325 if (num_channels < 1) {
8326 tinyexr::SetErrorMessage(msg: "Invalid channels format", err);
8327 return TINYEXR_ERROR_INVALID_DATA;
8328 }
8329
8330 } else if (attr_name.compare(s: "dataWindow") == 0) {
8331 memcpy(dest: &dx, src: &data.at(n: 0), n: sizeof(int));
8332 memcpy(dest: &dy, src: &data.at(n: 4), n: sizeof(int));
8333 memcpy(dest: &dw, src: &data.at(n: 8), n: sizeof(int));
8334 memcpy(dest: &dh, src: &data.at(n: 12), n: sizeof(int));
8335 tinyexr::swap4(val: &dx);
8336 tinyexr::swap4(val: &dy);
8337 tinyexr::swap4(val: &dw);
8338 tinyexr::swap4(val: &dh);
8339
8340 } else if (attr_name.compare(s: "displayWindow") == 0) {
8341 int x;
8342 int y;
8343 int w;
8344 int h;
8345 memcpy(dest: &x, src: &data.at(n: 0), n: sizeof(int));
8346 memcpy(dest: &y, src: &data.at(n: 4), n: sizeof(int));
8347 memcpy(dest: &w, src: &data.at(n: 8), n: sizeof(int));
8348 memcpy(dest: &h, src: &data.at(n: 12), n: sizeof(int));
8349 tinyexr::swap4(val: &x);
8350 tinyexr::swap4(val: &y);
8351 tinyexr::swap4(val: &w);
8352 tinyexr::swap4(val: &h);
8353 }
8354 }
8355
8356 TINYEXR_CHECK_AND_RETURN_C(dx >= 0, TINYEXR_ERROR_INVALID_DATA);
8357 TINYEXR_CHECK_AND_RETURN_C(dy >= 0, TINYEXR_ERROR_INVALID_DATA);
8358 TINYEXR_CHECK_AND_RETURN_C(dw >= 0, TINYEXR_ERROR_INVALID_DATA);
8359 TINYEXR_CHECK_AND_RETURN_C(dh >= 0, TINYEXR_ERROR_INVALID_DATA);
8360 TINYEXR_CHECK_AND_RETURN_C(num_channels >= 1, TINYEXR_ERROR_INVALID_DATA);
8361
8362 int data_width = dw - dx + 1;
8363 int data_height = dh - dy + 1;
8364
8365 // Read offset tables.
8366 int num_blocks = data_height / num_scanline_blocks;
8367 if (num_blocks * num_scanline_blocks < data_height) {
8368 num_blocks++;
8369 }
8370
8371 std::vector<tinyexr::tinyexr_int64> offsets(static_cast<size_t>(num_blocks));
8372
8373 for (size_t y = 0; y < static_cast<size_t>(num_blocks); y++) {
8374 tinyexr::tinyexr_int64 offset;
8375 memcpy(dest: &offset, src: marker, n: sizeof(tinyexr::tinyexr_int64));
8376 tinyexr::swap8(val: reinterpret_cast<tinyexr::tinyexr_uint64 *>(&offset));
8377 marker += sizeof(tinyexr::tinyexr_int64); // = 8
8378 offsets[y] = offset;
8379 }
8380
8381#if TINYEXR_USE_PIZ
8382 if ((compression_type == TINYEXR_COMPRESSIONTYPE_NONE) ||
8383 (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) ||
8384 (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) ||
8385 (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP) ||
8386 (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ)) {
8387#else
8388 if ((compression_type == TINYEXR_COMPRESSIONTYPE_NONE) ||
8389 (compression_type == TINYEXR_COMPRESSIONTYPE_RLE) ||
8390 (compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) ||
8391 (compression_type == TINYEXR_COMPRESSIONTYPE_ZIP)) {
8392#endif
8393 // OK
8394 } else {
8395 tinyexr::SetErrorMessage(msg: "Unsupported compression format", err);
8396 return TINYEXR_ERROR_UNSUPPORTED_FORMAT;
8397 }
8398
8399 deep_image->image = static_cast<float ***>(
8400 malloc(size: sizeof(float **) * static_cast<size_t>(num_channels)));
8401 for (int c = 0; c < num_channels; c++) {
8402 deep_image->image[c] = static_cast<float **>(
8403 malloc(size: sizeof(float *) * static_cast<size_t>(data_height)));
8404 for (int y = 0; y < data_height; y++) {
8405 }
8406 }
8407
8408 deep_image->offset_table = static_cast<int **>(
8409 malloc(size: sizeof(int *) * static_cast<size_t>(data_height)));
8410 for (int y = 0; y < data_height; y++) {
8411 deep_image->offset_table[y] = static_cast<int *>(
8412 malloc(size: sizeof(int) * static_cast<size_t>(data_width)));
8413 }
8414
8415 for (size_t y = 0; y < static_cast<size_t>(num_blocks); y++) {
8416 const unsigned char *data_ptr =
8417 reinterpret_cast<const unsigned char *>(head + offsets[y]);
8418
8419 // int: y coordinate
8420 // int64: packed size of pixel offset table
8421 // int64: packed size of sample data
8422 // int64: unpacked size of sample data
8423 // compressed pixel offset table
8424 // compressed sample data
8425 int line_no;
8426 tinyexr::tinyexr_int64 packedOffsetTableSize;
8427 tinyexr::tinyexr_int64 packedSampleDataSize;
8428 tinyexr::tinyexr_int64 unpackedSampleDataSize;
8429 memcpy(dest: &line_no, src: data_ptr, n: sizeof(int));
8430 memcpy(dest: &packedOffsetTableSize, src: data_ptr + 4,
8431 n: sizeof(tinyexr::tinyexr_int64));
8432 memcpy(dest: &packedSampleDataSize, src: data_ptr + 12,
8433 n: sizeof(tinyexr::tinyexr_int64));
8434 memcpy(dest: &unpackedSampleDataSize, src: data_ptr + 20,
8435 n: sizeof(tinyexr::tinyexr_int64));
8436
8437 tinyexr::swap4(val: &line_no);
8438 tinyexr::swap8(
8439 val: reinterpret_cast<tinyexr::tinyexr_uint64 *>(&packedOffsetTableSize));
8440 tinyexr::swap8(
8441 val: reinterpret_cast<tinyexr::tinyexr_uint64 *>(&packedSampleDataSize));
8442 tinyexr::swap8(
8443 val: reinterpret_cast<tinyexr::tinyexr_uint64 *>(&unpackedSampleDataSize));
8444
8445 std::vector<int> pixelOffsetTable(static_cast<size_t>(data_width));
8446
8447 // decode pixel offset table.
8448 {
8449 unsigned long dstLen =
8450 static_cast<unsigned long>(pixelOffsetTable.size() * sizeof(int));
8451 if (!tinyexr::DecompressZip(
8452 dst: reinterpret_cast<unsigned char *>(&pixelOffsetTable.at(n: 0)),
8453 uncompressed_size: &dstLen, src: data_ptr + 28,
8454 src_size: static_cast<unsigned long>(packedOffsetTableSize))) {
8455 return false;
8456 }
8457
8458 TINYEXR_CHECK_AND_RETURN_C(dstLen == pixelOffsetTable.size() * sizeof(int), TINYEXR_ERROR_INVALID_DATA);
8459 for (size_t i = 0; i < static_cast<size_t>(data_width); i++) {
8460 deep_image->offset_table[y][i] = pixelOffsetTable[i];
8461 }
8462 }
8463
8464 std::vector<unsigned char> sample_data(
8465 static_cast<size_t>(unpackedSampleDataSize));
8466
8467 // decode sample data.
8468 {
8469 unsigned long dstLen = static_cast<unsigned long>(unpackedSampleDataSize);
8470 if (dstLen) {
8471 if (!tinyexr::DecompressZip(
8472 dst: reinterpret_cast<unsigned char *>(&sample_data.at(n: 0)), uncompressed_size: &dstLen,
8473 src: data_ptr + 28 + packedOffsetTableSize,
8474 src_size: static_cast<unsigned long>(packedSampleDataSize))) {
8475 return false;
8476 }
8477 TINYEXR_CHECK_AND_RETURN_C(dstLen == static_cast<unsigned long>(unpackedSampleDataSize), TINYEXR_ERROR_INVALID_DATA);
8478 }
8479 }
8480
8481 // decode sample
8482 int sampleSize = -1;
8483 std::vector<int> channel_offset_list(static_cast<size_t>(num_channels));
8484 {
8485 int channel_offset = 0;
8486 for (size_t i = 0; i < static_cast<size_t>(num_channels); i++) {
8487 channel_offset_list[i] = channel_offset;
8488 if (channels[i].pixel_type == TINYEXR_PIXELTYPE_UINT) { // UINT
8489 channel_offset += 4;
8490 } else if (channels[i].pixel_type == TINYEXR_PIXELTYPE_HALF) { // half
8491 channel_offset += 2;
8492 } else if (channels[i].pixel_type ==
8493 TINYEXR_PIXELTYPE_FLOAT) { // float
8494 channel_offset += 4;
8495 } else {
8496 tinyexr::SetErrorMessage(msg: "Invalid pixel_type in chnnels.", err);
8497 return TINYEXR_ERROR_INVALID_DATA;
8498 }
8499 }
8500 sampleSize = channel_offset;
8501 }
8502 TINYEXR_CHECK_AND_RETURN_C(sampleSize >= 2, TINYEXR_ERROR_INVALID_DATA);
8503
8504 TINYEXR_CHECK_AND_RETURN_C(static_cast<size_t>(
8505 pixelOffsetTable[static_cast<size_t>(data_width - 1)] *
8506 sampleSize) == sample_data.size(), TINYEXR_ERROR_INVALID_DATA);
8507 int samples_per_line = static_cast<int>(sample_data.size()) / sampleSize;
8508
8509 //
8510 // Alloc memory
8511 //
8512
8513 //
8514 // pixel data is stored as image[channels][pixel_samples]
8515 //
8516 {
8517 tinyexr::tinyexr_uint64 data_offset = 0;
8518 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
8519 deep_image->image[c][y] = static_cast<float *>(
8520 malloc(size: sizeof(float) * static_cast<size_t>(samples_per_line)));
8521
8522 if (channels[c].pixel_type == 0) { // UINT
8523 for (size_t x = 0; x < static_cast<size_t>(samples_per_line); x++) {
8524 unsigned int ui;
8525 unsigned int *src_ptr = reinterpret_cast<unsigned int *>(
8526 &sample_data.at(n: size_t(data_offset) + x * sizeof(int)));
8527 tinyexr::cpy4(dst_val: &ui, src_val: src_ptr);
8528 deep_image->image[c][y][x] = static_cast<float>(ui); // @fixme
8529 }
8530 data_offset +=
8531 sizeof(unsigned int) * static_cast<size_t>(samples_per_line);
8532 } else if (channels[c].pixel_type == 1) { // half
8533 for (size_t x = 0; x < static_cast<size_t>(samples_per_line); x++) {
8534 tinyexr::FP16 f16;
8535 const unsigned short *src_ptr = reinterpret_cast<unsigned short *>(
8536 &sample_data.at(n: size_t(data_offset) + x * sizeof(short)));
8537 tinyexr::cpy2(dst_val: &(f16.u), src_val: src_ptr);
8538 tinyexr::FP32 f32 = half_to_float(h: f16);
8539 deep_image->image[c][y][x] = f32.f;
8540 }
8541 data_offset += sizeof(short) * static_cast<size_t>(samples_per_line);
8542 } else { // float
8543 for (size_t x = 0; x < static_cast<size_t>(samples_per_line); x++) {
8544 float f;
8545 const float *src_ptr = reinterpret_cast<float *>(
8546 &sample_data.at(n: size_t(data_offset) + x * sizeof(float)));
8547 tinyexr::cpy4(dst_val: &f, src_val: src_ptr);
8548 deep_image->image[c][y][x] = f;
8549 }
8550 data_offset += sizeof(float) * static_cast<size_t>(samples_per_line);
8551 }
8552 }
8553 }
8554 } // y
8555
8556 deep_image->width = data_width;
8557 deep_image->height = data_height;
8558
8559 deep_image->channel_names = static_cast<const char **>(
8560 malloc(size: sizeof(const char *) * static_cast<size_t>(num_channels)));
8561 for (size_t c = 0; c < static_cast<size_t>(num_channels); c++) {
8562#ifdef _WIN32
8563 deep_image->channel_names[c] = _strdup(channels[c].name.c_str());
8564#else
8565 deep_image->channel_names[c] = strdup(s: channels[c].name.c_str());
8566#endif
8567 }
8568 deep_image->num_channels = num_channels;
8569
8570 return TINYEXR_SUCCESS;
8571}
8572
8573void InitEXRImage(EXRImage *exr_image) {
8574 if (exr_image == NULL) {
8575 return;
8576 }
8577
8578 exr_image->width = 0;
8579 exr_image->height = 0;
8580 exr_image->num_channels = 0;
8581
8582 exr_image->images = NULL;
8583 exr_image->tiles = NULL;
8584 exr_image->next_level = NULL;
8585 exr_image->level_x = 0;
8586 exr_image->level_y = 0;
8587
8588 exr_image->num_tiles = 0;
8589}
8590
8591void FreeEXRErrorMessage(const char *msg) {
8592 if (msg) {
8593 free(ptr: reinterpret_cast<void *>(const_cast<char *>(msg)));
8594 }
8595 return;
8596}
8597
8598void InitEXRHeader(EXRHeader *exr_header) {
8599 if (exr_header == NULL) {
8600 return;
8601 }
8602
8603 memset(s: exr_header, c: 0, n: sizeof(EXRHeader));
8604}
8605
8606int FreeEXRHeader(EXRHeader *exr_header) {
8607 if (exr_header == NULL) {
8608 return TINYEXR_ERROR_INVALID_ARGUMENT;
8609 }
8610
8611 if (exr_header->channels) {
8612 free(ptr: exr_header->channels);
8613 }
8614
8615 if (exr_header->pixel_types) {
8616 free(ptr: exr_header->pixel_types);
8617 }
8618
8619 if (exr_header->requested_pixel_types) {
8620 free(ptr: exr_header->requested_pixel_types);
8621 }
8622
8623 for (int i = 0; i < exr_header->num_custom_attributes; i++) {
8624 if (exr_header->custom_attributes[i].value) {
8625 free(ptr: exr_header->custom_attributes[i].value);
8626 }
8627 }
8628
8629 if (exr_header->custom_attributes) {
8630 free(ptr: exr_header->custom_attributes);
8631 }
8632
8633 EXRSetNameAttr(exr_header, NULL);
8634
8635 return TINYEXR_SUCCESS;
8636}
8637
8638void EXRSetNameAttr(EXRHeader* exr_header, const char* name) {
8639 if (exr_header == NULL) {
8640 return;
8641 }
8642 memset(s: exr_header->name, c: 0, n: 256);
8643 if (name != NULL) {
8644 size_t len = std::min(a: strlen(s: name), b: size_t(255));
8645 if (len) {
8646 memcpy(dest: exr_header->name, src: name, n: len);
8647 }
8648 }
8649}
8650
8651int EXRNumLevels(const EXRImage* exr_image) {
8652 if (exr_image == NULL) return 0;
8653 if(exr_image->images) return 1; // scanlines
8654 int levels = 1;
8655 const EXRImage* level_image = exr_image;
8656 while((level_image = level_image->next_level)) ++levels;
8657 return levels;
8658}
8659
8660int FreeEXRImage(EXRImage *exr_image) {
8661 if (exr_image == NULL) {
8662 return TINYEXR_ERROR_INVALID_ARGUMENT;
8663 }
8664
8665 if (exr_image->next_level) {
8666 FreeEXRImage(exr_image: exr_image->next_level);
8667 delete exr_image->next_level;
8668 }
8669
8670 for (int i = 0; i < exr_image->num_channels; i++) {
8671 if (exr_image->images && exr_image->images[i]) {
8672 free(ptr: exr_image->images[i]);
8673 }
8674 }
8675
8676 if (exr_image->images) {
8677 free(ptr: exr_image->images);
8678 }
8679
8680 if (exr_image->tiles) {
8681 for (int tid = 0; tid < exr_image->num_tiles; tid++) {
8682 for (int i = 0; i < exr_image->num_channels; i++) {
8683 if (exr_image->tiles[tid].images && exr_image->tiles[tid].images[i]) {
8684 free(ptr: exr_image->tiles[tid].images[i]);
8685 }
8686 }
8687 if (exr_image->tiles[tid].images) {
8688 free(ptr: exr_image->tiles[tid].images);
8689 }
8690 }
8691 free(ptr: exr_image->tiles);
8692 }
8693
8694 return TINYEXR_SUCCESS;
8695}
8696
8697int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version,
8698 const char *filename, const char **err) {
8699 if (exr_header == NULL || exr_version == NULL || filename == NULL) {
8700 tinyexr::SetErrorMessage(msg: "Invalid argument for ParseEXRHeaderFromFile",
8701 err);
8702 return TINYEXR_ERROR_INVALID_ARGUMENT;
8703 }
8704
8705 MemoryMappedFile file(filename);
8706 if (!file.valid()) {
8707 tinyexr::SetErrorMessage(msg: "Cannot read file " + std::string(filename), err);
8708 return TINYEXR_ERROR_CANT_OPEN_FILE;
8709 }
8710
8711 return ParseEXRHeaderFromMemory(exr_header, version: exr_version, memory: file.data, size: file.size,
8712 err);
8713}
8714
8715int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers,
8716 int *num_headers,
8717 const EXRVersion *exr_version,
8718 const unsigned char *memory, size_t size,
8719 const char **err) {
8720 if (memory == NULL || exr_headers == NULL || num_headers == NULL ||
8721 exr_version == NULL) {
8722 // Invalid argument
8723 tinyexr::SetErrorMessage(
8724 msg: "Invalid argument for ParseEXRMultipartHeaderFromMemory", err);
8725 return TINYEXR_ERROR_INVALID_ARGUMENT;
8726 }
8727
8728 if (size < tinyexr::kEXRVersionSize) {
8729 tinyexr::SetErrorMessage(msg: "Data size too short", err);
8730 return TINYEXR_ERROR_INVALID_DATA;
8731 }
8732
8733 const unsigned char *marker = memory + tinyexr::kEXRVersionSize;
8734 size_t marker_size = size - tinyexr::kEXRVersionSize;
8735
8736 std::vector<tinyexr::HeaderInfo> infos;
8737
8738 for (;;) {
8739 tinyexr::HeaderInfo info;
8740 info.clear();
8741
8742 std::string err_str;
8743 bool empty_header = false;
8744 int ret = ParseEXRHeader(info: &info, empty_header: &empty_header, version: exr_version, err: &err_str,
8745 buf: marker, size: marker_size);
8746
8747 if (ret != TINYEXR_SUCCESS) {
8748
8749 // Free malloc-allocated memory here.
8750 for (size_t i = 0; i < info.attributes.size(); i++) {
8751 if (info.attributes[i].value) {
8752 free(ptr: info.attributes[i].value);
8753 }
8754 }
8755
8756 tinyexr::SetErrorMessage(msg: err_str, err);
8757 return ret;
8758 }
8759
8760 if (empty_header) {
8761 marker += 1; // skip '\0'
8762 break;
8763 }
8764
8765 // `chunkCount` must exist in the header.
8766 if (info.chunk_count == 0) {
8767
8768 // Free malloc-allocated memory here.
8769 for (size_t i = 0; i < info.attributes.size(); i++) {
8770 if (info.attributes[i].value) {
8771 free(ptr: info.attributes[i].value);
8772 }
8773 }
8774
8775 tinyexr::SetErrorMessage(
8776 msg: "`chunkCount' attribute is not found in the header.", err);
8777 return TINYEXR_ERROR_INVALID_DATA;
8778 }
8779
8780 infos.push_back(x: info);
8781
8782 // move to next header.
8783 marker += info.header_len;
8784 size -= info.header_len;
8785 }
8786
8787 // allocate memory for EXRHeader and create array of EXRHeader pointers.
8788 (*exr_headers) =
8789 static_cast<EXRHeader **>(malloc(size: sizeof(EXRHeader *) * infos.size()));
8790
8791
8792 int retcode = TINYEXR_SUCCESS;
8793
8794 for (size_t i = 0; i < infos.size(); i++) {
8795 EXRHeader *exr_header = static_cast<EXRHeader *>(malloc(size: sizeof(EXRHeader)));
8796 memset(s: exr_header, c: 0, n: sizeof(EXRHeader));
8797
8798 std::string warn;
8799 std::string _err;
8800 if (!ConvertHeader(exr_header, info: infos[i], warn: &warn, err: &_err)) {
8801
8802 // Free malloc-allocated memory here.
8803 for (size_t k = 0; k < infos[i].attributes.size(); k++) {
8804 if (infos[i].attributes[k].value) {
8805 free(ptr: infos[i].attributes[k].value);
8806 }
8807 }
8808
8809 if (!_err.empty()) {
8810 tinyexr::SetErrorMessage(
8811 msg: _err, err);
8812 }
8813 // continue to converting headers
8814 retcode = TINYEXR_ERROR_INVALID_HEADER;
8815 }
8816
8817 exr_header->multipart = exr_version->multipart ? 1 : 0;
8818
8819 (*exr_headers)[i] = exr_header;
8820 }
8821
8822 (*num_headers) = static_cast<int>(infos.size());
8823
8824 return retcode;
8825}
8826
8827int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers,
8828 const EXRVersion *exr_version,
8829 const char *filename, const char **err) {
8830 if (exr_headers == NULL || num_headers == NULL || exr_version == NULL ||
8831 filename == NULL) {
8832 tinyexr::SetErrorMessage(
8833 msg: "Invalid argument for ParseEXRMultipartHeaderFromFile()", err);
8834 return TINYEXR_ERROR_INVALID_ARGUMENT;
8835 }
8836
8837 MemoryMappedFile file(filename);
8838 if (!file.valid()) {
8839 tinyexr::SetErrorMessage(msg: "Cannot read file " + std::string(filename), err);
8840 return TINYEXR_ERROR_CANT_OPEN_FILE;
8841 }
8842
8843 return ParseEXRMultipartHeaderFromMemory(
8844 exr_headers, num_headers, exr_version, memory: file.data, size: file.size, err);
8845}
8846
8847int ParseEXRVersionFromMemory(EXRVersion *version, const unsigned char *memory,
8848 size_t size) {
8849 if (version == NULL || memory == NULL) {
8850 return TINYEXR_ERROR_INVALID_ARGUMENT;
8851 }
8852
8853 if (size < tinyexr::kEXRVersionSize) {
8854 return TINYEXR_ERROR_INVALID_DATA;
8855 }
8856
8857 const unsigned char *marker = memory;
8858
8859 // Header check.
8860 {
8861 const char header[] = {0x76, 0x2f, 0x31, 0x01};
8862
8863 if (memcmp(s1: marker, s2: header, n: 4) != 0) {
8864 return TINYEXR_ERROR_INVALID_MAGIC_NUMBER;
8865 }
8866 marker += 4;
8867 }
8868
8869 version->tiled = false;
8870 version->long_name = false;
8871 version->non_image = false;
8872 version->multipart = false;
8873
8874 // Parse version header.
8875 {
8876 // must be 2
8877 if (marker[0] != 2) {
8878 return TINYEXR_ERROR_INVALID_EXR_VERSION;
8879 }
8880
8881 if (version == NULL) {
8882 return TINYEXR_SUCCESS; // May OK
8883 }
8884
8885 version->version = 2;
8886
8887 if (marker[1] & 0x2) { // 9th bit
8888 version->tiled = true;
8889 }
8890 if (marker[1] & 0x4) { // 10th bit
8891 version->long_name = true;
8892 }
8893 if (marker[1] & 0x8) { // 11th bit
8894 version->non_image = true; // (deep image)
8895 }
8896 if (marker[1] & 0x10) { // 12th bit
8897 version->multipart = true;
8898 }
8899 }
8900
8901 return TINYEXR_SUCCESS;
8902}
8903
8904int ParseEXRVersionFromFile(EXRVersion *version, const char *filename) {
8905 if (filename == NULL) {
8906 return TINYEXR_ERROR_INVALID_ARGUMENT;
8907 }
8908
8909 FILE *fp = NULL;
8910#ifdef _WIN32
8911#if defined(_MSC_VER) || (defined(MINGW_HAS_SECURE_API) && MINGW_HAS_SECURE_API) // MSVC, MinGW GCC, or Clang.
8912 errno_t err = _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb");
8913 if (err != 0) {
8914 // TODO(syoyo): return wfopen_s erro code
8915 return TINYEXR_ERROR_CANT_OPEN_FILE;
8916 }
8917#else
8918 // Unknown compiler or MinGW without MINGW_HAS_SECURE_API.
8919 fp = fopen(filename, "rb");
8920#endif
8921#else
8922 fp = fopen(filename: filename, modes: "rb");
8923#endif
8924 if (!fp) {
8925 return TINYEXR_ERROR_CANT_OPEN_FILE;
8926 }
8927
8928 // Try to read kEXRVersionSize bytes; if the file is shorter than
8929 // kEXRVersionSize, this will produce an error. This avoids a call to
8930 // fseek(fp, 0, SEEK_END), which is not required to be supported by C
8931 // implementations.
8932 unsigned char buf[tinyexr::kEXRVersionSize];
8933 size_t ret = fread(ptr: &buf[0], size: 1, n: tinyexr::kEXRVersionSize, stream: fp);
8934 fclose(stream: fp);
8935
8936 if (ret != tinyexr::kEXRVersionSize) {
8937 return TINYEXR_ERROR_INVALID_FILE;
8938 }
8939
8940 return ParseEXRVersionFromMemory(version, memory: buf, size: tinyexr::kEXRVersionSize);
8941}
8942
8943int LoadEXRMultipartImageFromMemory(EXRImage *exr_images,
8944 const EXRHeader **exr_headers,
8945 unsigned int num_parts,
8946 const unsigned char *memory,
8947 const size_t size, const char **err) {
8948 if (exr_images == NULL || exr_headers == NULL || num_parts == 0 ||
8949 memory == NULL || (size <= tinyexr::kEXRVersionSize)) {
8950 tinyexr::SetErrorMessage(
8951 msg: "Invalid argument for LoadEXRMultipartImageFromMemory()", err);
8952 return TINYEXR_ERROR_INVALID_ARGUMENT;
8953 }
8954
8955 // compute total header size.
8956 size_t total_header_size = 0;
8957 for (unsigned int i = 0; i < num_parts; i++) {
8958 if (exr_headers[i]->header_len == 0) {
8959 tinyexr::SetErrorMessage(msg: "EXRHeader variable is not initialized.", err);
8960 return TINYEXR_ERROR_INVALID_ARGUMENT;
8961 }
8962
8963 total_header_size += exr_headers[i]->header_len;
8964 }
8965
8966 const char *marker = reinterpret_cast<const char *>(
8967 memory + total_header_size + 4 +
8968 4); // +8 for magic number and version header.
8969
8970 marker += 1; // Skip empty header.
8971
8972 // NOTE 1:
8973 // In multipart image, There is 'part number' before chunk data.
8974 // 4 byte : part number
8975 // 4+ : chunk
8976 //
8977 // NOTE 2:
8978 // EXR spec says 'part number' is 'unsigned long' but actually this is
8979 // 'unsigned int(4 bytes)' in OpenEXR implementation...
8980 // http://www.openexr.com/openexrfilelayout.pdf
8981
8982 // Load chunk offset table.
8983 std::vector<tinyexr::OffsetData> chunk_offset_table_list;
8984 chunk_offset_table_list.reserve(n: num_parts);
8985 for (size_t i = 0; i < static_cast<size_t>(num_parts); i++) {
8986 chunk_offset_table_list.resize(new_size: chunk_offset_table_list.size() + 1);
8987 tinyexr::OffsetData& offset_data = chunk_offset_table_list.back();
8988 if (!exr_headers[i]->tiled || exr_headers[i]->tile_level_mode == TINYEXR_TILE_ONE_LEVEL) {
8989 tinyexr::InitSingleResolutionOffsets(offset_data, num_blocks: size_t(exr_headers[i]->chunk_count));
8990 std::vector<tinyexr::tinyexr_uint64>& offset_table = offset_data.offsets[0][0];
8991
8992 for (size_t c = 0; c < offset_table.size(); c++) {
8993 tinyexr::tinyexr_uint64 offset;
8994 memcpy(dest: &offset, src: marker, n: 8);
8995 tinyexr::swap8(val: &offset);
8996
8997 if (offset >= size) {
8998 tinyexr::SetErrorMessage(msg: "Invalid offset size in EXR header chunks.",
8999 err);
9000 return TINYEXR_ERROR_INVALID_DATA;
9001 }
9002
9003 offset_table[c] = offset + 4; // +4 to skip 'part number'
9004 marker += 8;
9005 }
9006 } else {
9007 {
9008 std::vector<int> num_x_tiles, num_y_tiles;
9009 if (!tinyexr::PrecalculateTileInfo(num_x_tiles, num_y_tiles, exr_header: exr_headers[i])) {
9010 tinyexr::SetErrorMessage(msg: "Invalid tile info.", err);
9011 return TINYEXR_ERROR_INVALID_DATA;
9012 }
9013 int num_blocks = InitTileOffsets(offset_data, exr_header: exr_headers[i], num_x_tiles, num_y_tiles);
9014 if (num_blocks != exr_headers[i]->chunk_count) {
9015 tinyexr::SetErrorMessage(msg: "Invalid offset table size.", err);
9016 return TINYEXR_ERROR_INVALID_DATA;
9017 }
9018 }
9019 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l) {
9020 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy) {
9021 for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) {
9022 tinyexr::tinyexr_uint64 offset;
9023 memcpy(dest: &offset, src: marker, n: sizeof(tinyexr::tinyexr_uint64));
9024 tinyexr::swap8(val: &offset);
9025 if (offset >= size) {
9026 tinyexr::SetErrorMessage(msg: "Invalid offset size in EXR header chunks.",
9027 err);
9028 return TINYEXR_ERROR_INVALID_DATA;
9029 }
9030 offset_data.offsets[l][dy][dx] = offset + 4; // +4 to skip 'part number'
9031 marker += sizeof(tinyexr::tinyexr_uint64); // = 8
9032 }
9033 }
9034 }
9035 }
9036 }
9037
9038 // Decode image.
9039 for (size_t i = 0; i < static_cast<size_t>(num_parts); i++) {
9040 tinyexr::OffsetData &offset_data = chunk_offset_table_list[i];
9041
9042 // First check 'part number' is identical to 'i'
9043 for (unsigned int l = 0; l < offset_data.offsets.size(); ++l)
9044 for (unsigned int dy = 0; dy < offset_data.offsets[l].size(); ++dy)
9045 for (unsigned int dx = 0; dx < offset_data.offsets[l][dy].size(); ++dx) {
9046
9047 const unsigned char *part_number_addr =
9048 memory + offset_data.offsets[l][dy][dx] - 4; // -4 to move to 'part number' field.
9049 unsigned int part_no;
9050 memcpy(dest: &part_no, src: part_number_addr, n: sizeof(unsigned int)); // 4
9051 tinyexr::swap4(val: &part_no);
9052
9053 if (part_no != i) {
9054 tinyexr::SetErrorMessage(msg: "Invalid `part number' in EXR header chunks.",
9055 err);
9056 return TINYEXR_ERROR_INVALID_DATA;
9057 }
9058 }
9059
9060 std::string e;
9061 int ret = tinyexr::DecodeChunk(exr_image: &exr_images[i], exr_header: exr_headers[i], offset_data,
9062 head: memory, size, err: &e);
9063 if (ret != TINYEXR_SUCCESS) {
9064 if (!e.empty()) {
9065 tinyexr::SetErrorMessage(msg: e, err);
9066 }
9067 return ret;
9068 }
9069 }
9070
9071 return TINYEXR_SUCCESS;
9072}
9073
9074int LoadEXRMultipartImageFromFile(EXRImage *exr_images,
9075 const EXRHeader **exr_headers,
9076 unsigned int num_parts, const char *filename,
9077 const char **err) {
9078 if (exr_images == NULL || exr_headers == NULL || num_parts == 0) {
9079 tinyexr::SetErrorMessage(
9080 msg: "Invalid argument for LoadEXRMultipartImageFromFile", err);
9081 return TINYEXR_ERROR_INVALID_ARGUMENT;
9082 }
9083
9084 MemoryMappedFile file(filename);
9085 if (!file.valid()) {
9086 tinyexr::SetErrorMessage(msg: "Cannot read file " + std::string(filename), err);
9087 return TINYEXR_ERROR_CANT_OPEN_FILE;
9088 }
9089
9090 return LoadEXRMultipartImageFromMemory(exr_images, exr_headers, num_parts,
9091 memory: file.data, size: file.size, err);
9092}
9093
9094int SaveEXRToMemory(const float *data, int width, int height, int components,
9095 const int save_as_fp16, unsigned char **outbuf, const char **err) {
9096
9097 if ((components == 1) || components == 3 || components == 4) {
9098 // OK
9099 } else {
9100 std::stringstream ss;
9101 ss << "Unsupported component value : " << components << std::endl;
9102
9103 tinyexr::SetErrorMessage(msg: ss.str(), err);
9104 return TINYEXR_ERROR_INVALID_ARGUMENT;
9105 }
9106
9107 EXRHeader header;
9108 InitEXRHeader(exr_header: &header);
9109
9110 if ((width < 16) && (height < 16)) {
9111 // No compression for small image.
9112 header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE;
9113 } else {
9114 header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
9115 }
9116
9117 EXRImage image;
9118 InitEXRImage(exr_image: &image);
9119
9120 image.num_channels = components;
9121
9122 std::vector<float> images[4];
9123
9124 if (components == 1) {
9125 images[0].resize(new_size: static_cast<size_t>(width * height));
9126 memcpy(dest: images[0].data(), src: data, n: sizeof(float) * size_t(width * height));
9127 } else {
9128 images[0].resize(new_size: static_cast<size_t>(width * height));
9129 images[1].resize(new_size: static_cast<size_t>(width * height));
9130 images[2].resize(new_size: static_cast<size_t>(width * height));
9131 images[3].resize(new_size: static_cast<size_t>(width * height));
9132
9133 // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers
9134 if (components == 4) {
9135 for (size_t i = 0; i < static_cast<size_t>(width * height); i++) {
9136 images[0][i] = data[static_cast<size_t>(components) * i + 0];
9137 images[1][i] = data[static_cast<size_t>(components) * i + 1];
9138 images[2][i] = data[static_cast<size_t>(components) * i + 2];
9139 images[3][i] = data[static_cast<size_t>(components) * i + 3];
9140 }
9141 } else {
9142 for (size_t i = 0; i < static_cast<size_t>(width * height); i++) {
9143 images[0][i] = data[static_cast<size_t>(components) * i + 0];
9144 images[1][i] = data[static_cast<size_t>(components) * i + 1];
9145 images[2][i] = data[static_cast<size_t>(components) * i + 2];
9146 }
9147 }
9148 }
9149
9150 float *image_ptr[4] = {0, 0, 0, 0};
9151 if (components == 4) {
9152 image_ptr[0] = &(images[3].at(n: 0)); // A
9153 image_ptr[1] = &(images[2].at(n: 0)); // B
9154 image_ptr[2] = &(images[1].at(n: 0)); // G
9155 image_ptr[3] = &(images[0].at(n: 0)); // R
9156 } else if (components == 3) {
9157 image_ptr[0] = &(images[2].at(n: 0)); // B
9158 image_ptr[1] = &(images[1].at(n: 0)); // G
9159 image_ptr[2] = &(images[0].at(n: 0)); // R
9160 } else if (components == 1) {
9161 image_ptr[0] = &(images[0].at(n: 0)); // A
9162 }
9163
9164 image.images = reinterpret_cast<unsigned char **>(image_ptr);
9165 image.width = width;
9166 image.height = height;
9167
9168 header.num_channels = components;
9169 header.channels = static_cast<EXRChannelInfo *>(malloc(
9170 size: sizeof(EXRChannelInfo) * static_cast<size_t>(header.num_channels)));
9171 // Must be (A)BGR order, since most of EXR viewers expect this channel order.
9172 if (components == 4) {
9173#ifdef _MSC_VER
9174 strncpy_s(header.channels[0].name, "A", 255);
9175 strncpy_s(header.channels[1].name, "B", 255);
9176 strncpy_s(header.channels[2].name, "G", 255);
9177 strncpy_s(header.channels[3].name, "R", 255);
9178#else
9179 strncpy(dest: header.channels[0].name, src: "A", n: 255);
9180 strncpy(dest: header.channels[1].name, src: "B", n: 255);
9181 strncpy(dest: header.channels[2].name, src: "G", n: 255);
9182 strncpy(dest: header.channels[3].name, src: "R", n: 255);
9183#endif
9184 header.channels[0].name[strlen(s: "A")] = '\0';
9185 header.channels[1].name[strlen(s: "B")] = '\0';
9186 header.channels[2].name[strlen(s: "G")] = '\0';
9187 header.channels[3].name[strlen(s: "R")] = '\0';
9188 } else if (components == 3) {
9189#ifdef _MSC_VER
9190 strncpy_s(header.channels[0].name, "B", 255);
9191 strncpy_s(header.channels[1].name, "G", 255);
9192 strncpy_s(header.channels[2].name, "R", 255);
9193#else
9194 strncpy(dest: header.channels[0].name, src: "B", n: 255);
9195 strncpy(dest: header.channels[1].name, src: "G", n: 255);
9196 strncpy(dest: header.channels[2].name, src: "R", n: 255);
9197#endif
9198 header.channels[0].name[strlen(s: "B")] = '\0';
9199 header.channels[1].name[strlen(s: "G")] = '\0';
9200 header.channels[2].name[strlen(s: "R")] = '\0';
9201 } else {
9202#ifdef _MSC_VER
9203 strncpy_s(header.channels[0].name, "A", 255);
9204#else
9205 strncpy(dest: header.channels[0].name, src: "A", n: 255);
9206#endif
9207 header.channels[0].name[strlen(s: "A")] = '\0';
9208 }
9209
9210 header.pixel_types = static_cast<int *>(
9211 malloc(size: sizeof(int) * static_cast<size_t>(header.num_channels)));
9212 header.requested_pixel_types = static_cast<int *>(
9213 malloc(size: sizeof(int) * static_cast<size_t>(header.num_channels)));
9214 for (int i = 0; i < header.num_channels; i++) {
9215 header.pixel_types[i] =
9216 TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
9217
9218 if (save_as_fp16 > 0) {
9219 header.requested_pixel_types[i] =
9220 TINYEXR_PIXELTYPE_HALF; // save with half(fp16) pixel format
9221 } else {
9222 header.requested_pixel_types[i] =
9223 TINYEXR_PIXELTYPE_FLOAT; // save with float(fp32) pixel format(i.e.
9224 // no precision reduction)
9225 }
9226 }
9227
9228
9229 unsigned char *mem_buf;
9230 size_t mem_size = SaveEXRImageToMemory(exr_image: &image, exr_header: &header, memory_out: &mem_buf, err);
9231
9232 if (mem_size == 0) {
9233 return TINYEXR_ERROR_SERIALIZATION_FAILED;
9234 }
9235
9236 free(ptr: header.channels);
9237 free(ptr: header.pixel_types);
9238 free(ptr: header.requested_pixel_types);
9239
9240 if (mem_size > size_t(std::numeric_limits<int>::max())) {
9241 free(ptr: mem_buf);
9242 return TINYEXR_ERROR_DATA_TOO_LARGE;
9243 }
9244
9245 (*outbuf) = mem_buf;
9246
9247 return int(mem_size);
9248}
9249
9250int SaveEXR(const float *data, int width, int height, int components,
9251 const int save_as_fp16, const char *outfilename, const char **err) {
9252 if ((components == 1) || components == 3 || components == 4) {
9253 // OK
9254 } else {
9255 std::stringstream ss;
9256 ss << "Unsupported component value : " << components << std::endl;
9257
9258 tinyexr::SetErrorMessage(msg: ss.str(), err);
9259 return TINYEXR_ERROR_INVALID_ARGUMENT;
9260 }
9261
9262 EXRHeader header;
9263 InitEXRHeader(exr_header: &header);
9264
9265 if ((width < 16) && (height < 16)) {
9266 // No compression for small image.
9267 header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE;
9268 } else {
9269 header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
9270 }
9271
9272 EXRImage image;
9273 InitEXRImage(exr_image: &image);
9274
9275 image.num_channels = components;
9276
9277 std::vector<float> images[4];
9278 const size_t pixel_count =
9279 static_cast<size_t>(width) * static_cast<size_t>(height);
9280
9281 if (components == 1) {
9282 images[0].resize(new_size: pixel_count);
9283 memcpy(dest: images[0].data(), src: data, n: sizeof(float) * pixel_count);
9284 } else {
9285 images[0].resize(new_size: pixel_count);
9286 images[1].resize(new_size: pixel_count);
9287 images[2].resize(new_size: pixel_count);
9288 images[3].resize(new_size: pixel_count);
9289
9290 // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers
9291 if (components == 4) {
9292 for (size_t i = 0; i < pixel_count; i++) {
9293 images[0][i] = data[static_cast<size_t>(components) * i + 0];
9294 images[1][i] = data[static_cast<size_t>(components) * i + 1];
9295 images[2][i] = data[static_cast<size_t>(components) * i + 2];
9296 images[3][i] = data[static_cast<size_t>(components) * i + 3];
9297 }
9298 } else {
9299 for (size_t i = 0; i < pixel_count; i++) {
9300 images[0][i] = data[static_cast<size_t>(components) * i + 0];
9301 images[1][i] = data[static_cast<size_t>(components) * i + 1];
9302 images[2][i] = data[static_cast<size_t>(components) * i + 2];
9303 }
9304 }
9305 }
9306
9307 float *image_ptr[4] = {0, 0, 0, 0};
9308 if (components == 4) {
9309 image_ptr[0] = &(images[3].at(n: 0)); // A
9310 image_ptr[1] = &(images[2].at(n: 0)); // B
9311 image_ptr[2] = &(images[1].at(n: 0)); // G
9312 image_ptr[3] = &(images[0].at(n: 0)); // R
9313 } else if (components == 3) {
9314 image_ptr[0] = &(images[2].at(n: 0)); // B
9315 image_ptr[1] = &(images[1].at(n: 0)); // G
9316 image_ptr[2] = &(images[0].at(n: 0)); // R
9317 } else if (components == 1) {
9318 image_ptr[0] = &(images[0].at(n: 0)); // A
9319 }
9320
9321 image.images = reinterpret_cast<unsigned char **>(image_ptr);
9322 image.width = width;
9323 image.height = height;
9324
9325 header.num_channels = components;
9326 header.channels = static_cast<EXRChannelInfo *>(malloc(
9327 size: sizeof(EXRChannelInfo) * static_cast<size_t>(header.num_channels)));
9328 // Must be (A)BGR order, since most of EXR viewers expect this channel order.
9329 if (components == 4) {
9330#ifdef _MSC_VER
9331 strncpy_s(header.channels[0].name, "A", 255);
9332 strncpy_s(header.channels[1].name, "B", 255);
9333 strncpy_s(header.channels[2].name, "G", 255);
9334 strncpy_s(header.channels[3].name, "R", 255);
9335#else
9336 strncpy(dest: header.channels[0].name, src: "A", n: 255);
9337 strncpy(dest: header.channels[1].name, src: "B", n: 255);
9338 strncpy(dest: header.channels[2].name, src: "G", n: 255);
9339 strncpy(dest: header.channels[3].name, src: "R", n: 255);
9340#endif
9341 header.channels[0].name[strlen(s: "A")] = '\0';
9342 header.channels[1].name[strlen(s: "B")] = '\0';
9343 header.channels[2].name[strlen(s: "G")] = '\0';
9344 header.channels[3].name[strlen(s: "R")] = '\0';
9345 } else if (components == 3) {
9346#ifdef _MSC_VER
9347 strncpy_s(header.channels[0].name, "B", 255);
9348 strncpy_s(header.channels[1].name, "G", 255);
9349 strncpy_s(header.channels[2].name, "R", 255);
9350#else
9351 strncpy(dest: header.channels[0].name, src: "B", n: 255);
9352 strncpy(dest: header.channels[1].name, src: "G", n: 255);
9353 strncpy(dest: header.channels[2].name, src: "R", n: 255);
9354#endif
9355 header.channels[0].name[strlen(s: "B")] = '\0';
9356 header.channels[1].name[strlen(s: "G")] = '\0';
9357 header.channels[2].name[strlen(s: "R")] = '\0';
9358 } else {
9359#ifdef _MSC_VER
9360 strncpy_s(header.channels[0].name, "A", 255);
9361#else
9362 strncpy(dest: header.channels[0].name, src: "A", n: 255);
9363#endif
9364 header.channels[0].name[strlen(s: "A")] = '\0';
9365 }
9366
9367 header.pixel_types = static_cast<int *>(
9368 malloc(size: sizeof(int) * static_cast<size_t>(header.num_channels)));
9369 header.requested_pixel_types = static_cast<int *>(
9370 malloc(size: sizeof(int) * static_cast<size_t>(header.num_channels)));
9371 for (int i = 0; i < header.num_channels; i++) {
9372 header.pixel_types[i] =
9373 TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image
9374
9375 if (save_as_fp16 > 0) {
9376 header.requested_pixel_types[i] =
9377 TINYEXR_PIXELTYPE_HALF; // save with half(fp16) pixel format
9378 } else {
9379 header.requested_pixel_types[i] =
9380 TINYEXR_PIXELTYPE_FLOAT; // save with float(fp32) pixel format(i.e.
9381 // no precision reduction)
9382 }
9383 }
9384
9385 int ret = SaveEXRImageToFile(exr_image: &image, exr_header: &header, filename: outfilename, err);
9386
9387 free(ptr: header.channels);
9388 free(ptr: header.pixel_types);
9389 free(ptr: header.requested_pixel_types);
9390
9391 return ret;
9392}
9393
9394#ifdef __clang__
9395// zero-as-null-pointer-constant
9396#pragma clang diagnostic pop
9397#endif
9398
9399#endif // TINYEXR_IMPLEMENTATION_DEFINED
9400#endif // TINYEXR_IMPLEMENTATION
9401

source code of qtquick3d/src/3rdparty/tinyexr/tinyexr.h