diff --git a/renderdoc/3rdparty/tinyexr/README.md b/renderdoc/3rdparty/tinyexr/README.md index 86d0b51fc..227ac5cb0 100644 --- a/renderdoc/3rdparty/tinyexr/README.md +++ b/renderdoc/3rdparty/tinyexr/README.md @@ -1,40 +1,116 @@ -From https://github.com/syoyo/tinyexr : 9b30f25383bdb9dd517592552be528c5905474be +From https://github.com/syoyo/tinyexr : 1.0 # Tiny OpenEXR image library. +[![Total alerts](https://img.shields.io/lgtm/alerts/g/syoyo/tinyexr.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/syoyo/tinyexr/alerts/) + ![Example](https://github.com/syoyo/tinyexr/blob/master/asakusa.png?raw=true) -`tinyexr` is a small, single header-only library to load and save OpenEXR(.exr) images. -`tinyexr` is written in portable C++(no library dependency except for STL), thus `tinyexr` is good to embed into your application. +[![AppVeyor build status](https://ci.appveyor.com/api/projects/status/k07ftfe4ph057qau/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyexr/branch/master) + +[![Travis build Status](https://travis-ci.org/syoyo/tinyexr.svg)](https://travis-ci.org/syoyo/tinyexr) + +[![Coverity Scan Build Status](https://scan.coverity.com/projects/5827/badge.svg)](https://scan.coverity.com/projects/5827) + +`tinyexr` is a small, single header-only library to load and save OpenEXR (.exr) images. +`tinyexr` is written in portable C++ (no library dependency except for STL), thus `tinyexr` is good to embed into your application. To use `tinyexr`, simply copy `tinyexr.h` into your project. -`tinyexr` currently supports: +# Features -* OpenEXR version 1.x. -* Normal image - * Scanline format. - * Uncompress("compress" = 0), ZIPS("compress" = 2), ZIP compression("compress" = 3) and PIZ compression("compress" = 4). - * Half/Uint/Float pixel type. - * Custom attributes(up to 128) -* Deep image - * Scanline format. - * ZIPS compression("compress" = 2). - * Half, float pixel type. -* Litte endian machine. -* Limited support for big endian machine. - * read/write normal image. +Current status of `tinyexr` is: + +- OpenEXR v1 image + - [x] Scanline format + - [ ] Tiled format + - [x] Tile format with no LoD (load). + - [ ] Tile format with LoD (load). + - [ ] Tile format with no LoD (save). + - [ ] Tile format with LoD (save). + - [x] Custom attributes +- OpenEXR v2 image + - [ ] Multipart format + - [x] Load multi-part image + - [ ] Save multi-part image + - [ ] Load multi-part deep image + - [ ] Save multi-part deep image +- OpenEXR v2 deep image + - [x] Loading scanline + ZIPS + HALF or FLOAT pixel type. +- Compression + - [x] NONE + - [x] RLE + - [x] ZIP + - [x] ZIPS + - [x] PIZ + - [x] ZFP (tinyexr extension) + - [ ] B44? + - [ ] B44A? + - [ ] PIX24? +- Line order. + - [x] Increasing, decreasing (load) + - [ ] Random? + - [ ] Increasing, decreasing (save) +- Pixel format (UINT, FLOAT). + - [x] UINT, FLOAT (load) + - [x] UINT, FLOAT (deep load) + - [x] UINT, FLOAT (save) + - [ ] UINT, FLOAT (deep save) +- Support for big endian machine. + - [x] Loading scanline image + - [x] Saving scanline image + - [ ] Loading multi-part channel EXR + - [ ] Saving multi-part channel EXR + - [ ] Loading deep image + - [ ] Saving deep image +- Optimization + - [x] C++11 thread loading + - [ ] C++11 thread saving + - [ ] ISPC? + - [x] OpenMP multi-threading in EXR loading. + - [x] OpenMP multi-threading in EXR saving. + - [ ] OpenMP multi-threading in deep image loading. + - [ ] OpenMP multi-threading in deep image saving. * C interface. - * You can easily write language bindings(e.g. golang) -* EXR saving - * with ZIP compression. -* JavaScript library - * Through emscripten. + * You can easily write language bindings (e.g. golang) -# Use case +# Supported platform + +* [x] x86-64 + * [x] Windows 7 or later + * [x] Linux(posix) system + * [x] macOS +* [x] AARCH64 + * [x] aarch64 linux(e.g. Raspberry Pi) + * [x] Android + * [x] iOS + * [ ] macOS(Should work) +* [ ] RISC-V(Should work) +* [x] Big endian machine(not maintained, but should work) + * SPARC, PowerPC, ... +* [x] WebAssembly(JavaScript) + * Loader only(See ![js](experimental/js/)) +* [x] Python binding + * Loader only https://pypi.org/project/pytinyexr/ + +# Requirements + +* C++ compiler(C++11 recommended. C++03 may work) + +# Use case + +## New TinyEXR (v0.9.5+) + +* Godot. Multi-platform 2D and 3D game engine https://godotengine.org/ +* Filament. PBR engine(used in a converter tool). https://github.com/google/filament +* PyEXR. Loading OpenEXR (.exr) images using Python. https://github.com/ialhashim/PyEXR +* The-Forge. The Forge Cross-Platform Rendering Framework PC, Linux, Ray Tracing, macOS / iOS, Android, XBOX, PS4 https://github.com/ConfettiFX/The-Forge +* Your project here! + +## Older TinyEXR (v0.9.0) * mallie https://github.com/lighttransport/mallie * Cinder 0.9.0 https://libcinder.org/notes/v0.9.0 -* Piccante(develop branch) http://piccantelib.net/ +* Piccante (develop branch) http://piccantelib.net/ * Your project here! ## Examples @@ -42,79 +118,240 @@ To use `tinyexr`, simply copy `tinyexr.h` into your project. * [examples/deepview/](examples/deepview) Deep image view * [examples/rgbe2exr/](examples/rgbe2exr) .hdr to EXR converter * [examples/exr2rgbe/](examples/exr2rgbe) EXR to .hdr converter +* [examples/ldr2exr/](examples/exr2rgbe) LDR to EXR converter +* [examples/exr2ldr/](examples/exr2ldr) EXR to LDR converter +* [examples/exr2fptiff/](examples/exr2fptiff) EXR to 32bit floating point TIFF converter + * for 32bit floating point TIFF to EXR convert, see https://github.com/syoyo/tinydngloader/tree/master/examples/fptiff2exr +* [examples/cube2longlat/](examples/cube2longlat) Cubemap to longlat (equirectangler) converter + +## Experimental + +* [experimental/js/](experimental/js) JavaScript port using Emscripten ## Usage NOTE: **API is still subject to change**. See the source code for details. -Include `tinyexr.h` with `TINYEXR_IMPLEMENTATION` flag(do this only for **one** .cc file). +Include `tinyexr.h` with `TINYEXR_IMPLEMENTATION` flag (do this only for **one** .cc file). -``` +```cpp +//Please include your own zlib-compatible API header before +//including `tinyexr.h` when you disable `TINYEXR_USE_MINIZ` +//#define TINYEXR_USE_MINIZ 0 +//#include "zlib.h" #define TINYEXR_IMPLEMENTATION #include "tinyexr.h" ``` -Quickly reading RGB(A) EXR file. +### Compile flags -``` +* `TINYEXR_USE_MINIZ` Use embedded miniz (default = 1). Please include `zlib.h` header (before `tinyexr.h`) if you disable miniz support. +* `TINYEXR_USE_PIZ` Enable PIZ compression support (default = 1) +* `TINYEXR_USE_ZFP` Enable ZFP compression supoort (TinyEXR extension, default = 0) +* `TINYEXR_USE_THREAD` Enable threaded loading using C++11 thread (Requires C++11 compiler, default = 0) +* `TINYEXR_USE_OPENMP` Enable OpenMP threading support (default = 1 if `_OPENMP` is defined) + * Use `TINYEXR_USE_OPENMP=0` to force disable OpenMP code path even if OpenMP is available/enabled in the compiler. + +### Quickly reading RGB(A) EXR file. + +```cpp const char* input = "asakusa.exr"; float* out; // width * height * RGBA int width; int height; - const char* err; + const char* err = NULL; // or nullptr in C++11 int ret = LoadEXR(&out, &width, &height, input, &err); -``` -Loading EXR from a file. + if (ret != TINYEXR_SUCCESS) { + if (err) { + fprintf(stderr, "ERR : %s\n", err); + FreeEXRErrorMessage(err); // release memory of error message. + } + } else { + ... + free(out); // release memory of image data + } ``` - const char* input = "asakusa.exr"; - const char* err; - EXRImage exrImage; - InitEXRImage(&exrImage); +### Reading layered RGB(A) EXR file. - int ret = ParseMultiChannelEXRHeaderFromFile(&exrImage, input, &err); +If you want to read EXR image with layer info (channel has a name with delimiter `.`), please use `LoadEXRWithLayer` API. + +You need to know layer name in advance (e.g. through `EXRLayers` API). + +```cpp + const char* input = ...; + const char* layer_name = "diffuse"; // or use EXRLayers to get list of layer names in .exr + float* out; // width * height * RGBA + int width; + int height; + const char* err = NULL; // or nullptr in C++11 + + // will read `diffuse.R`, `diffuse.G`, `diffuse.B`, (`diffuse.A`) channels + int ret = LoadEXRWithLayer(&out, &width, &height, input, layer_name, &err); + + if (ret != TINYEXR_SUCCESS) { + if (err) { + fprintf(stderr, "ERR : %s\n", err); + FreeEXRErrorMessage(err); // release memory of error message. + } + } else { + ... + free(out); // release memory of image data + } + +``` + +### Loading Singlepart EXR from a file. + +Scanline and tiled format are supported. + +```cpp + // 1. Read EXR version. + EXRVersion exr_version; + + int ret = ParseEXRVersionFromFile(&exr_version, argv[1]); + if (ret != 0) { + fprintf(stderr, "Invalid EXR file: %s\n", argv[1]); + return -1; + } + + if (exr_version.multipart) { + // must be multipart flag is false. + return -1; + } + + // 2. Read EXR header + EXRHeader exr_header; + InitEXRHeader(&exr_header); + + const char* err = NULL; // or `nullptr` in C++11 or later. + ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, argv[1], &err); if (ret != 0) { fprintf(stderr, "Parse EXR err: %s\n", err); - return; + FreeEXRErrorMessage(err); // free's buffer for an error message + return ret; } - //// Uncomment if you want reading HALF image as FLOAT. - //for (int i = 0; i < exrImage.num_channels; i++) { - // if (exrImage.pixel_types[i] = TINYEXR_PIXELTYPE_HALF) { - // exrImage.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; - // } - //} + // // Read HALF channel as FLOAT. + // for (int i = 0; i < exr_header.num_channels; i++) { + // if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { + // exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; + // } + // } - ret = LoadMultiChannelEXRFromFile(&exrImage, input, &err); + EXRImage exr_image; + InitEXRImage(&exr_image); + + ret = LoadEXRImageFromFile(&exr_image, &exr_header, argv[1], &err); if (ret != 0) { fprintf(stderr, "Load EXR err: %s\n", err); - return; + FreeEXRHeader(&exr_header); + FreeEXRErrorMessage(err); // free's buffer for an error message + return ret; } + + // 3. Access image data + // `exr_image.images` will be filled when EXR is scanline format. + // `exr_image.tiled` will be filled when EXR is tiled format. + + // 4. Free image data + FreeEXRImage(&exr_image); + FreeEXRHeader(&exr_header); ``` -Saving EXR file. +### Loading Multipart EXR from a file. +Scanline and tiled format are supported. + +```cpp + // 1. Read EXR version. + EXRVersion exr_version; + + int ret = ParseEXRVersionFromFile(&exr_version, argv[1]); + if (ret != 0) { + fprintf(stderr, "Invalid EXR file: %s\n", argv[1]); + return -1; + } + + if (!exr_version.multipart) { + // must be multipart flag is true. + return -1; + } + + // 2. Read EXR headers in the EXR. + EXRHeader **exr_headers; // list of EXRHeader pointers. + int num_exr_headers; + const char *err = NULL; // or nullptr in C++11 or later + + // Memory for EXRHeader is allocated inside of ParseEXRMultipartHeaderFromFile, + ret = ParseEXRMultipartHeaderFromFile(&exr_headers, &num_exr_headers, &exr_version, argv[1], &err); + if (ret != 0) { + fprintf(stderr, "Parse EXR err: %s\n", err); + FreeEXRErrorMessage(err); // free's buffer for an error message + return ret; + } + + printf("num parts = %d\n", num_exr_headers); + + + // 3. Load images. + + // Prepare array of EXRImage. + std::vector images(num_exr_headers); + for (int i =0; i < num_exr_headers; i++) { + InitEXRImage(&images[i]); + } + + ret = LoadEXRMultipartImageFromFile(&images.at(0), const_cast(exr_headers), num_exr_headers, argv[1], &err); + if (ret != 0) { + fprintf(stderr, "Parse EXR err: %s\n", err); + FreeEXRErrorMessage(err); // free's buffer for an error message + return ret; + } + + printf("Loaded %d part images\n", num_exr_headers); + + // 4. Access image data + // `exr_image.images` will be filled when EXR is scanline format. + // `exr_image.tiled` will be filled when EXR is tiled format. + + // 5. Free images + for (int i =0; i < num_exr_headers; i++) { + FreeEXRImage(&images.at(i)); + } + + // 6. Free headers. + for (int i =0; i < num_exr_headers; i++) { + FreeEXRHeader(exr_headers[i]); + free(exr_headers[i]); + } + free(exr_headers); ``` + + +Saving Scanline EXR file. + +```cpp + // See `examples/rgbe2exr/` for more details. bool SaveEXR(const float* rgb, int width, int height, const char* outfilename) { - float* channels[3]; + EXRHeader header; + InitEXRHeader(&header); EXRImage image; InitEXRImage(&image); image.num_channels = 3; - // Must be BGR(A) order, since most of EXR viewers expect this channel order. - const char* channel_names[] = {"B", "G", "R"}; // "B", "G", "R", "A" for RGBA image - std::vector images[3]; images[0].resize(width * height); images[1].resize(width * height); images[2].resize(width * height); + // Split RGBRGBRGB... into R, G and B layer for (int i = 0; i < width * height; i++) { images[0][i] = rgb[3*i+0]; images[1][i] = rgb[3*i+1]; @@ -126,31 +363,38 @@ Saving EXR file. image_ptr[1] = &(images[1].at(0)); // G image_ptr[2] = &(images[0].at(0)); // R - image.channel_names = channel_names; image.images = (unsigned char**)image_ptr; image.width = width; image.height = height; - image.compression = TINYEXR_COMPRESSIONTYPE_ZIP; - image.pixel_types = (int *)malloc(sizeof(int) * image.num_channels); - image.requested_pixel_types = (int *)malloc(sizeof(int) * image.num_channels); - for (int i = 0; i < image.num_channels; i++) { - image.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image - image.requested_pixel_types[i] = TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be stored in .EXR + header.num_channels = 3; + header.channels = (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels); + // Must be (A)BGR order, since most of EXR viewers expect this channel order. + strncpy(header.channels[0].name, "B", 255); header.channels[0].name[strlen("B")] = '\0'; + strncpy(header.channels[1].name, "G", 255); header.channels[1].name[strlen("G")] = '\0'; + strncpy(header.channels[2].name, "R", 255); header.channels[2].name[strlen("R")] = '\0'; + + header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels); + header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels); + for (int i = 0; i < header.num_channels; i++) { + header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image + header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be stored in .EXR } - const char* err; - int ret = SaveMultiChannelEXRToFile(&image, outfilename, &err); - if (ret != 0) { + const char* err = NULL; // or nullptr in C++11 or later. + int ret = SaveEXRImageToFile(&image, &header, outfilename, &err); + if (ret != TINYEXR_SUCCESS) { fprintf(stderr, "Save EXR err: %s\n", err); + FreeEXRErrorMessage(err); // free's buffer for an error message return ret; } printf("Saved exr file. [ %s ] \n", outfilename); - free(image.pixel_types); - free(image.requested_pixel_types); + free(rgb); - return ret; + free(header.channels); + free(header.pixel_types); + free(header.requested_pixel_types); } ``` @@ -159,14 +403,14 @@ Saving EXR file. Reading deep image EXR file. See `example/deepview` for actual usage. -``` +```cpp const char* input = "deepimage.exr"; - const char* err; + const char* err = NULL; // or nullptr DeepImage deepImage; int ret = LoadDeepEXR(&deepImage, input, &err); - // acccess to each sample in the deep pixel. + // access to each sample in the deep pixel. for (int y = 0; y < deepImage.height; y++) { int sampleNum = deepImage.offset_table[y][deepImage.width-1]; for (int x = 0; x < deepImage.width-1; x++) { @@ -191,49 +435,101 @@ See `example/deepview` for actual usage. ![DeepViewExample](https://github.com/syoyo/tinyexr/blob/master/examples/deepview/deepview_screencast.gif?raw=true) +## TinyEXR extension + +### ZFP + +#### NOTE + +TinyEXR adds ZFP compression as an experimemtal support (Linux and MacOSX only). + +ZFP only supports FLOAT format pixel, and its image width and height must be the multiple of 4, since ZFP compresses pixels with 4x4 pixel block. + +#### Setup + +Checkout zfp repo as an submodule. + + $ git submodule update --init + +#### Build + +Then build ZFP + + $ cd deps/ZFP + $ mkdir -p lib # Create `lib` directory if not exist + $ make + +Set `1` to `TINYEXT_USE_ZFP` define in `tinyexr.h` + +Build your app with linking `deps/ZFP/lib/libzfp.a` + +#### ZFP attribute + +For ZFP EXR image, the following attribute must exist in its EXR image. + +* `zfpCompressionType` (uchar). + * 0 = fixed rate compression + * 1 = precision based variable rate compression + * 2 = accuracy based variable rate compression + +And the one of following attributes must exist in EXR, depending on the `zfpCompressionType` value. + +* `zfpCompressionRate` (double) + * Specifies compression rate for fixed rate compression. +* `zfpCompressionPrecision` (int32) + * Specifies the number of bits for precision based variable rate compression. +* `zfpCompressionTolerance` (double) + * Specifies the tolerance value for accuracy based variable rate compression. + +#### Note on ZFP compression. + +At least ZFP code itself works well on big endian machine. + +## Unit tests + +See `test/unit` directory. + ## TODO Contribution is welcome! - [ ] Compression - - [ ] NONE("compress" = 0, load) - - [ ] RLE("compress" = 1, load) - - [x] ZIPS("compress" = 2, load) - - [x] ZIP("compress" = 3, load) - - [x] PIZ("compress" = 4, load) - - [x] NONE("compress" = 0, save) - - [ ] RLE("compress" = 1, save) - - [x] ZIPS("compress" = 2, save) - - [x] ZIP("compress" = 3, save) - - [x] PIZ("compress" = 4, save) + - [ ] B44? + - [ ] B44A? + - [ ] PIX24? - [ ] Custom attributes - - [x] Normal image(EXR 1.x) - - [ ] Deep image(EXR 2.x) -- [ ] JavaScript library + - [x] Normal image (EXR 1.x) + - [ ] Deep image (EXR 2.x) +- [ ] JavaScript library (experimental, using Emscripten) - [x] LoadEXRFromMemory - [ ] SaveMultiChannelEXR - [ ] Deep image save/load - [ ] Write from/to memory buffer. - - [x] SaveMultiChannelEXR - - [x] LoadMultiChannelEXR - [ ] Deep image save/load - [ ] Tile format. -- [ ] Support for various compression type. - - [x] zstd compression(Not in OpenEXR spec, though) + - [x] Tile format with no LoD (load). + - [ ] Tile format with LoD (load). + - [ ] Tile format with no LoD (save). + - [ ] Tile format with LoD (save). +- [ ] Support for custom compression type. + - [x] zfp compression (Not in OpenEXR spec, though) + - [ ] zstd? - [x] Multi-channel. -- [ ] Multi-part(EXR2.0) +- [ ] Multi-part (EXR2.0) + - [x] Load multi-part image + - [ ] Load multi-part deep image - [ ] Line order. - - [x] Increasing, decreasing(load) + - [x] Increasing, decreasing (load) - [ ] Random? - - [ ] Increasing, decreasing(save) -- [ ] Pixel format(UINT, FLOAT). - - [x] UINT, FLOAT(load) - - [x] UINT, FLOAT(deep load) - - [x] UINT, FLOAT(save) - - [ ] UINT, FLOAT(deep save) -- [ ] Full support for big endian machine. - - [x] Loading multi channel EXR - - [x] Saving multi channel EXR + - [ ] Increasing, decreasing (save) +- [ ] Pixel format (UINT, FLOAT). + - [x] UINT, FLOAT (load) + - [x] UINT, FLOAT (deep load) + - [x] UINT, FLOAT (save) + - [ ] UINT, FLOAT (deep save) +- [ ] Support for big endian machine. + - [ ] Loading multi-part channel EXR + - [ ] Saving multi-part channel EXR - [ ] Loading deep image - [ ] Saving deep image - [ ] Optimization @@ -243,6 +539,10 @@ Contribution is welcome! - [ ] OpenMP multi-threading in deep image loading. - [ ] OpenMP multi-threading in deep image saving. +## Python bindings + +`pytinyexr` is available: https://pypi.org/project/pytinyexr/ (loading only as of 0.9.1) + ## Similar or related projects * miniexr: https://github.com/aras-p/miniexr (Write OpenEXR) @@ -259,11 +559,11 @@ Contribution is welcome! ## Author(s) -Syoyo Fujita(syoyo@lighttransport.com) +Syoyo Fujita (syoyo@lighttransport.com) ## Contributor(s) -* Matt Ebb (http://mattebb.com) : deep image example. Thanks! -* Matt Pharr (http://pharr.org/matt/) : Testing tinyexr with OpenEXR(IlmImf). Thanks! -* Andrew Bell (https://github.com/andrewfb) & Richard Eakin (https://github.com/richardeakin) : Improving TinyEXR API. Thanks! -* Mike Wong (https://github.com/mwkm) : ZIPS compression support in loading. Thanks! +* Matt Ebb (http://mattebb.com): deep image example. Thanks! +* Matt Pharr (http://pharr.org/matt/): Testing tinyexr with OpenEXR(IlmImf). Thanks! +* Andrew Bell (https://github.com/andrewfb) & Richard Eakin (https://github.com/richardeakin): Improving TinyEXR API. Thanks! +* Mike Wong (https://github.com/mwkm): ZIPS compression support in loading. Thanks! diff --git a/renderdoc/3rdparty/tinyexr/tinyexr.cpp b/renderdoc/3rdparty/tinyexr/tinyexr.cpp index a5b327409..c6bd32a83 100644 --- a/renderdoc/3rdparty/tinyexr/tinyexr.cpp +++ b/renderdoc/3rdparty/tinyexr/tinyexr.cpp @@ -1,11 +1,18 @@ +#if defined(_WIN32) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif #include #include #include "miniz/miniz.h" -#pragma warning(disable : 5208) // unnamed class used in typedef name cannot declare members - // other than non-static data members, member enumerations, or - // member classes +// local variable is initialized but not referenced +#pragma warning(disable : 4189) +// signed/unsigned mismatch +#pragma warning(disable : 4018) +#pragma warning(disable : 4389) #define TINYEXR_IMPLEMENTATION #include "tinyexr.h" diff --git a/renderdoc/3rdparty/tinyexr/tinyexr.h b/renderdoc/3rdparty/tinyexr/tinyexr.h index 54f698a24..698ccf168 100644 --- a/renderdoc/3rdparty/tinyexr/tinyexr.h +++ b/renderdoc/3rdparty/tinyexr/tinyexr.h @@ -1,5 +1,7 @@ +#ifndef TINYEXR_H_ +#define TINYEXR_H_ /* -Copyright (c) 2014 - 2018, Syoyo Fujita and many contributors. +Copyright (c) 2014 - 2020, Syoyo Fujita and many contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -63,9 +65,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // End of OpenEXR license ------------------------------------------------- -#ifndef TINYEXR_H_ -#define TINYEXR_H_ - // // // Do this: @@ -105,17 +104,33 @@ extern "C" { // http://computation.llnl.gov/projects/floating-point-compression #endif +#ifndef TINYEXR_USE_THREAD +#define TINYEXR_USE_THREAD (0) // No threaded loading. +// http://computation.llnl.gov/projects/floating-point-compression +#endif + +#ifndef TINYEXR_USE_OPENMP +#ifdef _OPENMP +#define TINYEXR_USE_OPENMP (1) +#else +#define TINYEXR_USE_OPENMP (0) +#endif +#endif + #define TINYEXR_SUCCESS (0) #define TINYEXR_ERROR_INVALID_MAGIC_NUMBER (-1) #define TINYEXR_ERROR_INVALID_EXR_VERSION (-2) #define TINYEXR_ERROR_INVALID_ARGUMENT (-3) #define TINYEXR_ERROR_INVALID_DATA (-4) #define TINYEXR_ERROR_INVALID_FILE (-5) -#define TINYEXR_ERROR_INVALID_PARAMETER (-5) -#define TINYEXR_ERROR_CANT_OPEN_FILE (-6) -#define TINYEXR_ERROR_UNSUPPORTED_FORMAT (-7) -#define TINYEXR_ERROR_INVALID_HEADER (-8) -#define TINYEXR_ERROR_UNSUPPORTED_FEATURE (-9) +#define TINYEXR_ERROR_INVALID_PARAMETER (-6) +#define TINYEXR_ERROR_CANT_OPEN_FILE (-7) +#define TINYEXR_ERROR_UNSUPPORTED_FORMAT (-8) +#define TINYEXR_ERROR_INVALID_HEADER (-9) +#define TINYEXR_ERROR_UNSUPPORTED_FEATURE (-10) +#define TINYEXR_ERROR_CANT_WRITE_FILE (-11) +#define TINYEXR_ERROR_SERIALZATION_FAILED (-12) +#define TINYEXR_ERROR_LAYER_NOT_FOUND (-13) // @note { OpenEXR file format: http://www.openexr.com/openexrfilelayout.pdf } @@ -124,7 +139,8 @@ extern "C" { #define TINYEXR_PIXELTYPE_HALF (1) #define TINYEXR_PIXELTYPE_FLOAT (2) -#define TINYEXR_MAX_ATTRIBUTES (128) +#define TINYEXR_MAX_HEADER_ATTRIBUTES (1024) +#define TINYEXR_MAX_CUSTOM_ATTRIBUTES (128) #define TINYEXR_COMPRESSIONTYPE_NONE (0) #define TINYEXR_COMPRESSIONTYPE_RLE (1) @@ -181,11 +197,18 @@ typedef struct _EXRTile { unsigned char **images; // image[channels][pixels] } EXRTile; +typedef struct _EXRBox2i { + int min_x; + int min_y; + int max_x; + int max_y; +} EXRBox2i; + typedef struct _EXRHeader { float pixel_aspect_ratio; int line_order; - int data_window[4]; - int display_window[4]; + EXRBox2i data_window; + EXRBox2i display_window; float screen_window_center[2]; float screen_window_width; @@ -206,7 +229,8 @@ typedef struct _EXRHeader { // Custom attributes(exludes required attributes(e.g. `channels`, // `compression`, etc) int num_custom_attributes; - EXRAttribute custom_attributes[TINYEXR_MAX_ATTRIBUTES]; + EXRAttribute *custom_attributes; // array of EXRAttribute. size = + // `num_custom_attributes`. EXRChannelInfo *channels; // [num_channels] @@ -259,7 +283,7 @@ typedef struct _DeepImage { int pad0; } DeepImage; -// @deprecated { to be removed. } +// @deprecated { For backward compatibility. Not recommended to use. } // Loads single-frame OpenEXR image. Assume EXR image contains A(single channel // alpha) or RGB(A) channels. // Application must free image data as returned by `out_rgba` @@ -269,6 +293,37 @@ typedef struct _DeepImage { extern int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, const char **err); +// Loads single-frame OpenEXR image by specifying layer name. Assume EXR image +// contains A(single channel alpha) or RGB(A) channels. Application must free +// image data as returned by `out_rgba` Result image format is: float x RGBA x +// width x hight Returns negative value and may set error string in `err` when +// there's an error When the specified layer name is not found in the EXR file, +// the function will return `TINYEXR_ERROR_LAYER_NOT_FOUND`. +extern int LoadEXRWithLayer(float **out_rgba, int *width, int *height, + const char *filename, const char *layer_name, + const char **err); + +// +// Get layer infos from EXR file. +// +// @param[out] layer_names List of layer names. Application must free memory +// after using this. +// @param[out] num_layers The number of layers +// @param[out] err Error string(will be filled when the function returns error +// code). Free it using FreeEXRErrorMessage after using this value. +// +// @return TINYEXR_SUCCEES upon success. +// +extern int EXRLayers(const char *filename, const char **layer_names[], + int *num_layers, const char **err); + +// @deprecated { to be removed. } +// Simple wrapper API for ParseEXRHeaderFromFile. +// checking given file is a EXR file(by just look up header) +// @return TINYEXR_SUCCEES for EXR image, TINYEXR_ERROR_INVALID_HEADER for +// others +extern int IsEXR(const char *filename); + // @deprecated { to be removed. } // Saves single-frame OpenEXR image. Assume EXR image contains RGB(A) channels. // components must be 1(Grayscale), 3(RGB) or 4(RGBA). @@ -277,9 +332,12 @@ extern int LoadEXR(float **out_rgba, int *width, int *height, // Save image as fp16(HALF) format when `save_as_fp16` is positive non-zero // value. // Save image as fp32(FLOAT) format when `save_as_fp16` is 0. +// Use ZIP compression by default. +// Returns negative value and may set error string in `err` when there's an +// error extern int SaveEXR(const float *data, const int width, const int height, const int components, const int save_as_fp16, - const char *filename); + const char *filename, const char **err); // Initialize EXRHeader struct extern void InitEXRHeader(EXRHeader *exr_header); @@ -287,12 +345,15 @@ extern void InitEXRHeader(EXRHeader *exr_header); // Initialize EXRImage struct extern void InitEXRImage(EXRImage *exr_image); -// Free's internal data of EXRHeader struct +// Frees internal data of EXRHeader struct extern int FreeEXRHeader(EXRHeader *exr_header); -// Free's internal data of EXRImage struct +// Frees internal data of EXRImage struct extern int FreeEXRImage(EXRImage *exr_image); +// Frees error message +extern void FreeEXRErrorMessage(const char *msg); + // Parse EXR version header of a file. extern int ParseEXRVersionFromFile(EXRVersion *version, const char *filename); @@ -301,10 +362,14 @@ extern int ParseEXRVersionFromMemory(EXRVersion *version, const unsigned char *memory, size_t size); // Parse single-part OpenEXR header from a file and initialize `EXRHeader`. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRHeaderFromFile(EXRHeader *header, const EXRVersion *version, const char *filename, const char **err); // Parse single-part OpenEXR header from a memory and initialize `EXRHeader`. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRHeaderFromMemory(EXRHeader *header, const EXRVersion *version, const unsigned char *memory, size_t size, @@ -312,6 +377,8 @@ extern int ParseEXRHeaderFromMemory(EXRHeader *header, // Parse multi-part OpenEXR headers from a file and initialize `EXRHeader*` // array. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers, int *num_headers, const EXRVersion *version, @@ -320,6 +387,8 @@ extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers, // Parse multi-part OpenEXR headers from a memory and initialize `EXRHeader*` // array +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers, int *num_headers, const EXRVersion *version, @@ -331,6 +400,8 @@ extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header, const char *filename, const char **err); @@ -340,6 +411,8 @@ extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header, const unsigned char *memory, const size_t size, const char **err); @@ -350,6 +423,8 @@ extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRMultipartImageFromFile(EXRImage *images, const EXRHeader **headers, unsigned int num_parts, @@ -362,6 +437,8 @@ extern int LoadEXRMultipartImageFromFile(EXRImage *images, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRMultipartImageFromMemory(EXRImage *images, const EXRHeader **headers, unsigned int num_parts, @@ -371,15 +448,19 @@ extern int LoadEXRMultipartImageFromMemory(EXRImage *images, // Saves multi-channel, single-frame OpenEXR image to a file. // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int SaveEXRImageToFile(const EXRImage *image, const EXRHeader *exr_header, const char *filename, const char **err); // Saves multi-channel, single-frame OpenEXR image to a memory. // Image is compressed using EXRImage.compression value. -// Return the number of bytes if succes. -// Returns negative value and may set error string in `err` when there's an -// error +// Return the number of bytes if success. +// Return zero and will set error string in `err` when there's an +// error. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern size_t SaveEXRImageToMemory(const EXRImage *image, const EXRHeader *exr_header, unsigned char **memory, const char **err); @@ -388,6 +469,8 @@ extern size_t SaveEXRImageToMemory(const EXRImage *image, // Application must free memory of variables in DeepImage(image, offset_table) // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadDeepEXR(DeepImage *out_image, const char *filename, const char **err); @@ -410,6 +493,8 @@ extern int LoadDeepEXR(DeepImage *out_image, const char *filename, // RGB(A) channels. // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, const unsigned char *memory, size_t size, const char **err); @@ -421,8 +506,17 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, #endif // TINYEXR_H_ #ifdef TINYEXR_IMPLEMENTATION -#ifndef TINYEXR_IMPLEMENTATION_DEIFNED -#define TINYEXR_IMPLEMENTATION_DEIFNED +#ifndef TINYEXR_IMPLEMENTATION_DEFINED +#define TINYEXR_IMPLEMENTATION_DEFINED + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include // for UTF-8 + +#endif #include #include @@ -431,6 +525,8 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, #include #include +// #include // debug + #include #include #include @@ -438,9 +534,15 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, #if __cplusplus > 199711L // C++11 #include + +#if TINYEXR_USE_THREAD +#include +#include +#endif + #endif // __cplusplus > 199711L -#ifdef _OPENMP +#if TINYEXR_USE_OPENMP #include #endif @@ -452,7 +554,18 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, #endif #if TINYEXR_USE_ZFP + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + #include "zfp.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + #endif namespace tinyexr { @@ -494,15 +607,27 @@ namespace miniz { #if __has_warning("-Wcomma") #pragma clang diagnostic ignored "-Wcomma" #endif + #if __has_warning("-Wmacro-redefined") #pragma clang diagnostic ignored "-Wmacro-redefined" #endif + #if __has_warning("-Wcast-qual") #pragma clang diagnostic ignored "-Wcast-qual" #endif + #if __has_warning("-Wzero-as-null-pointer-constant") #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" #endif + +#if __has_warning("-Wtautological-constant-compare") +#pragma clang diagnostic ignored "-Wtautological-constant-compare" +#endif + +#if __has_warning("-Wextra-semi-stmt") +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#endif + #endif /* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP @@ -523,7 +648,7 @@ namespace miniz { - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug - would only have occured in earlier versions if you explicitly used this + would only have occurred in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you can't switch to v1.15 but want to fix @@ -2488,10 +2613,10 @@ tinfl_status tinfl_decompress(tinfl_decompressor *r, tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; - const mz_uint8 *pIn_buf_cur = pIn_buf_next, - *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, - *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = + pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = + pOut_buf_next + *pOut_buf_size; size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 @@ -2908,8 +3033,9 @@ void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, tinfl_status status = tinfl_decompress( &decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, - &dst_buf_size, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | - TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { MZ_FREE(pBuf); *pOut_len = 0; @@ -2962,9 +3088,8 @@ int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, - (flags & - ~(TINFL_FLAG_HAS_MORE_INPUT | - TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); in_buf_ofs += in_buf_size; if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) @@ -3089,7 +3214,9 @@ static const mz_uint8 s_tdefl_large_dist_extra[128] = { // Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted // values. -typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +typedef struct { + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) { @@ -4393,9 +4520,8 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, // C and C99, so no big deal) #pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to // 'int', possible loss of data -#pragma warning( \ - disable : 4267) // 'argument': conversion from '__int64' to 'int', - // possible loss of data +#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to + // 'int', possible loss of data #pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is // deprecated. Instead, use the ISO C and C++ // conformant name: _strdup. @@ -5234,9 +5360,10 @@ mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); pStat->m_comment_size = n; - memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + - MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + - MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), + memcpy(pStat->m_comment, + p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; @@ -6860,6 +6987,10 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, } #endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #endif // MINIZ_HEADER_FILE_ONLY /* @@ -6895,14 +7026,18 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, #pragma clang diagnostic pop #endif -#ifdef _MSC_VER -#pragma warning(pop) -#endif -} +} // namespace miniz #else // Reuse MINIZ_LITTE_ENDIAN macro +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__i486__) || defined(__i486) || \ + defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + #if defined(__sparcv9) // Big endian #else @@ -6923,8 +7058,26 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, // return bint.c[0] == 1; //} +static void SetErrorMessage(const std::string &msg, const char **err) { + if (err) { +#ifdef _WIN32 + (*err) = _strdup(msg.c_str()); +#else + (*err) = strdup(msg.c_str()); +#endif + } +} + static const int kEXRVersionSize = 8; +static void cpy2(unsigned short *dst_val, const unsigned short *src_val) { + unsigned char *dst = reinterpret_cast(dst_val); + const unsigned char *src = reinterpret_cast(src_val); + + dst[0] = src[0]; + dst[1] = src[1]; +} + static void swap2(unsigned short *val) { #ifdef MINIZ_LITTLE_ENDIAN (void)val; @@ -6938,6 +7091,52 @@ static void swap2(unsigned short *val) { #endif } +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif +static void cpy4(int *dst_val, const int *src_val) { + unsigned char *dst = reinterpret_cast(dst_val); + const unsigned char *src = reinterpret_cast(src_val); + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; +} + +static void cpy4(unsigned int *dst_val, const unsigned int *src_val) { + unsigned char *dst = reinterpret_cast(dst_val); + const unsigned char *src = reinterpret_cast(src_val); + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; +} + +static void cpy4(float *dst_val, const float *src_val) { + unsigned char *dst = reinterpret_cast(dst_val); + const unsigned char *src = reinterpret_cast(src_val); + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; +} +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + static void swap4(unsigned int *val) { #ifdef MINIZ_LITTLE_ENDIAN (void)val; @@ -6953,6 +7152,52 @@ static void swap4(unsigned int *val) { #endif } +static void swap4(int *val) { +#ifdef MINIZ_LITTLE_ENDIAN + (void)val; +#else + int tmp = *val; + unsigned char *dst = reinterpret_cast(val); + unsigned char *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +#endif +} + +static void swap4(float *val) { +#ifdef MINIZ_LITTLE_ENDIAN + (void)val; +#else + float tmp = *val; + unsigned char *dst = reinterpret_cast(val); + unsigned char *src = reinterpret_cast(&tmp); + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; +#endif +} + +#if 0 +static void cpy8(tinyexr::tinyexr_uint64 *dst_val, const tinyexr::tinyexr_uint64 *src_val) { + unsigned char *dst = reinterpret_cast(dst_val); + const unsigned char *src = reinterpret_cast(src_val); + + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = src[3]; + dst[4] = src[4]; + dst[5] = src[5]; + dst[6] = src[6]; + dst[7] = src[7]; +} +#endif + static void swap8(tinyexr::tinyexr_uint64 *val) { #ifdef MINIZ_LITTLE_ENDIAN (void)val; @@ -7146,7 +7391,21 @@ static bool ReadAttribute(std::string *name, std::string *type, tinyexr::swap4(reinterpret_cast(&data_len)); if (data_len == 0) { - return false; + if ((*type).compare("string") == 0) { + // Accept empty string attribute. + + marker += sizeof(uint32_t); + size -= sizeof(uint32_t); + + *marker_size = name_len + 1 + type_len + 1 + sizeof(uint32_t); + + data->resize(1); + (*data)[0] = '\0'; + + return true; + } else { + return false; + } } marker += sizeof(uint32_t); @@ -7170,7 +7429,7 @@ static void WriteAttributeToMemory(std::vector *out, out->insert(out->end(), type, type + strlen(type) + 1); int outLen = len; - tinyexr::swap4(reinterpret_cast(&outLen)); + tinyexr::swap4(&outLen); out->insert(out->end(), reinterpret_cast(&outLen), reinterpret_cast(&outLen) + sizeof(int)); out->insert(out->end(), data, data + len); @@ -7186,12 +7445,19 @@ typedef struct { } ChannelInfo; typedef struct { + int min_x; + int min_y; + int max_x; + int max_y; +} Box2iInfo; + +struct HeaderInfo { std::vector channels; std::vector attributes; - int data_window[4]; + Box2iInfo data_window; int line_order; - int display_window[4]; + Box2iInfo display_window; float screen_window_center[2]; float screen_window_width; float pixel_aspect_ratio; @@ -7212,15 +7478,15 @@ typedef struct { channels.clear(); attributes.clear(); - data_window[0] = 0; - data_window[1] = 0; - data_window[2] = 0; - data_window[3] = 0; + data_window.min_x = 0; + data_window.min_y = 0; + data_window.max_x = 0; + data_window.max_y = 0; line_order = 0; - display_window[0] = 0; - display_window[1] = 0; - display_window[2] = 0; - display_window[3] = 0; + display_window.min_x = 0; + display_window.min_y = 0; + display_window.max_x = 0; + display_window.max_y = 0; screen_window_center[0] = 0.0f; screen_window_center[1] = 0.0f; screen_window_width = 0.0f; @@ -7237,7 +7503,7 @@ typedef struct { header_len = 0; compression_type = 0; } -} HeaderInfo; +}; static bool ReadChannelInfo(std::vector &channels, const std::vector &data) { @@ -7261,6 +7527,12 @@ static bool ReadChannelInfo(std::vector &channels, return false; } + const unsigned char *data_end = + reinterpret_cast(p) + 16; + if (data_end >= (data.data() + data.size())) { + return false; + } + memcpy(&info.pixel_type, p, sizeof(int)); p += 4; info.p_linear = static_cast(p[0]); // uchar @@ -7270,9 +7542,9 @@ static bool ReadChannelInfo(std::vector &channels, memcpy(&info.y_sampling, p, sizeof(int)); // int p += 4; - tinyexr::swap4(reinterpret_cast(&info.pixel_type)); - tinyexr::swap4(reinterpret_cast(&info.x_sampling)); - tinyexr::swap4(reinterpret_cast(&info.y_sampling)); + tinyexr::swap4(&info.pixel_type); + tinyexr::swap4(&info.x_sampling); + tinyexr::swap4(&info.y_sampling); channels.push_back(info); } @@ -7302,9 +7574,9 @@ static void WriteChannelInfo(std::vector &data, int pixel_type = channels[c].pixel_type; int x_sampling = channels[c].x_sampling; int y_sampling = channels[c].y_sampling; - tinyexr::swap4(reinterpret_cast(&pixel_type)); - tinyexr::swap4(reinterpret_cast(&x_sampling)); - tinyexr::swap4(reinterpret_cast(&y_sampling)); + tinyexr::swap4(&pixel_type); + tinyexr::swap4(&x_sampling); + tinyexr::swap4(&y_sampling); memcpy(p, &pixel_type, sizeof(int)); p += sizeof(int); @@ -7391,7 +7663,6 @@ static void CompressZip(unsigned char *dst, int ret = compress(dst, &outSize, static_cast(&tmpBuf.at(0)), src_size); assert(ret == Z_OK); - (void)ret; compressedSize = outSize; #endif @@ -7473,6 +7744,9 @@ static bool DecompressZip(unsigned char *dst, #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wsign-conversion" +#if __has_warning("-Wextra-semi-stmt") +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#endif #endif #ifdef _MSC_VER @@ -7482,9 +7756,8 @@ static bool DecompressZip(unsigned char *dst, // C and C99, so no big deal) #pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to // 'int', possible loss of data -#pragma warning( \ - disable : 4267) // 'argument': conversion from '__int64' to 'int', - // possible loss of data +#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to + // 'int', possible loss of data #pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is // deprecated. Instead, use the ISO C and C++ // conformant name: _strdup. @@ -7512,7 +7785,7 @@ static int rleCompress(int inLength, const char in[], signed char out[]) { if (runEnd - runStart >= MIN_RUN_LENGTH) { // - // Compressable run + // Compressible run // *outWrite++ = static_cast(runEnd - runStart) - 1; @@ -7558,7 +7831,8 @@ static int rleUncompress(int inLength, int maxLength, const signed char in[], int count = -(static_cast(*in++)); inLength -= count + 1; - if (0 > (maxLength -= count)) return 0; + // Fixes #116: Add bounds check to in buffer. + if ((0 > (maxLength -= count)) || (inLength < 0)) return 0; memcpy(out, in, count); out += count; @@ -7652,13 +7926,19 @@ static void CompressRle(unsigned char *dst, } } -static void DecompressRle(unsigned char *dst, +static bool DecompressRle(unsigned char *dst, const unsigned long uncompressed_size, const unsigned char *src, unsigned long src_size) { if (uncompressed_size == src_size) { // Data is not compressed(Issue 40). memcpy(dst, src, src_size); - return; + return true; + } + + // Workaround for issue #112. + // TODO(syoyo): Add more robust out-of-bounds check in `rleUncompress`. + if (src_size <= 2) { + return false; } std::vector tmpBuf(uncompressed_size); @@ -7667,8 +7947,9 @@ static void DecompressRle(unsigned char *dst, static_cast(uncompressed_size), reinterpret_cast(src), reinterpret_cast(&tmpBuf.at(0))); - assert(ret == static_cast(uncompressed_size)); - (void)ret; + if (ret != static_cast(uncompressed_size)) { + return false; + } // // Apply EXR-specific? postprocess. Grabbed from OpenEXR's @@ -7707,6 +7988,8 @@ static void DecompressRle(unsigned char *dst, break; } } + + return true; } #if TINYEXR_USE_PIZ @@ -7725,6 +8008,10 @@ static void DecompressRle(unsigned char *dst, #pragma clang diagnostic ignored "-Wcast-qual" #endif +#if __has_warning("-Wextra-semi-stmt") +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#endif + #endif // @@ -7842,7 +8129,7 @@ static void wav2Encode( int p2 = 2; // == 1 << (level+1) // - // Hierachical loop on smaller dimension n + // Hierarchical loop on smaller dimension n // while (p2 <= n) { @@ -8073,9 +8360,9 @@ const int HUF_DECMASK = HUF_DECSIZE - 1; struct HufDec { // short code long code //------------------------------- - int len : 8; // code length 0 - int lit : 24; // lit p size - int *p; // 0 lits + unsigned int len : 8; // code length 0 + unsigned int lit : 24; // lit p size + unsigned int *p; // 0 lits }; inline long long hufLength(long long code) { return code & 63; } @@ -8202,8 +8489,8 @@ static void hufBuildEncTable( // for all array entries. // - int hlink[HUF_ENCSIZE]; - long long *fHeap[HUF_ENCSIZE]; + std::vector hlink(HUF_ENCSIZE); + std::vector fHeap(HUF_ENCSIZE); *im = 0; @@ -8262,8 +8549,8 @@ static void hufBuildEncTable( std::make_heap(&fHeap[0], &fHeap[nf], FHeapCompare()); - long long scode[HUF_ENCSIZE]; - memset(scode, 0, sizeof(long long) * HUF_ENCSIZE); + std::vector scode(HUF_ENCSIZE); + memset(scode.data(), 0, sizeof(long long) * HUF_ENCSIZE); while (nf > 1) { // @@ -8335,8 +8622,8 @@ static void hufBuildEncTable( // code table from scode into frq. // - hufCanonicalCodeTable(scode); - memcpy(frq, scode, sizeof(long long) * HUF_ENCSIZE); + hufCanonicalCodeTable(scode.data()); + memcpy(frq, scode.data(), sizeof(long long) * HUF_ENCSIZE); } // @@ -8418,7 +8705,7 @@ static bool hufUnpackEncTable( int lc = 0; for (; im <= iM; im++) { - if (p - *pcode > ni) { + if (p - *pcode >= ni) { return false; } @@ -8531,14 +8818,14 @@ static bool hufBuildDecTable(const long long *hcode, // i : encoding table pl->lit++; if (pl->p) { - int *p = pl->p; - pl->p = new int[pl->lit]; + unsigned int *p = pl->p; + pl->p = new unsigned int[pl->lit]; for (int i = 0; i < pl->lit - 1; ++i) pl->p[i] = p[i]; delete[] p; } else { - pl->p = new int[1]; + pl->p = new unsigned int[1]; } pl->p[pl->lit - 1] = im; @@ -8672,26 +8959,63 @@ static int hufEncode // return: output size (in bits) lc += 8; \ } -#define getCode(po, rlc, c, lc, in, out, oe) \ - { \ - if (po == rlc) { \ - if (lc < 8) getChar(c, lc, in); \ - \ - lc -= 8; \ - \ - unsigned char cs = (c >> lc); \ - \ - if (out + cs > oe) return false; \ - \ - unsigned short s = out[-1]; \ - \ - while (cs-- > 0) *out++ = s; \ - } else if (out < oe) { \ - *out++ = po; \ - } else { \ - return false; \ - } \ +#if 0 +#define getCode(po, rlc, c, lc, in, out, ob, oe) \ + { \ + if (po == rlc) { \ + if (lc < 8) getChar(c, lc, in); \ + \ + lc -= 8; \ + \ + unsigned char cs = (c >> lc); \ + \ + if (out + cs > oe) return false; \ + \ + /* TinyEXR issue 78 */ \ + unsigned short s = out[-1]; \ + \ + while (cs-- > 0) *out++ = s; \ + } else if (out < oe) { \ + *out++ = po; \ + } else { \ + return false; \ + } \ } +#else +static bool getCode(int po, int rlc, long long &c, int &lc, const char *&in, + const char *in_end, unsigned short *&out, + const unsigned short *ob, const unsigned short *oe) { + (void)ob; + if (po == rlc) { + if (lc < 8) { + /* TinyEXR issue 78 */ + if ((in + 1) >= in_end) { + return false; + } + + getChar(c, lc, in); + } + + lc -= 8; + + unsigned char cs = (c >> lc); + + if (out + cs > oe) return false; + + // Bounds check for safety + // Issue 100. + if ((out - 1) < ob) return false; + unsigned short s = out[-1]; + + while (cs-- > 0) *out++ = s; + } else if (out < oe) { + *out++ = po; + } else { + return false; + } + return true; +} +#endif // // Decode (uncompress) ni bits based on encoding & decoding tables: @@ -8707,8 +9031,8 @@ static bool hufDecode(const long long *hcode, // i : encoding table { long long c = 0; int lc = 0; - unsigned short *outb = out; - unsigned short *oe = out + no; + unsigned short *outb = out; // begin + unsigned short *oe = out + no; // end const char *ie = in + (ni + 7) / 8; // input byte size // @@ -8731,7 +9055,16 @@ static bool hufDecode(const long long *hcode, // i : encoding table // lc -= pl.len; - getCode(pl.lit, rlc, c, lc, in, out, oe); + // std::cout << "lit = " << pl.lit << std::endl; + // std::cout << "rlc = " << rlc << std::endl; + // std::cout << "c = " << c << std::endl; + // std::cout << "lc = " << lc << std::endl; + // std::cout << "in = " << in << std::endl; + // std::cout << "out = " << out << std::endl; + // std::cout << "oe = " << oe << std::endl; + if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } } else { if (!pl.p) { return false; @@ -8758,7 +9091,9 @@ static bool hufDecode(const long long *hcode, // i : encoding table // lc -= l; - getCode(pl.p[j], rlc, c, lc, in, out, oe); + if (!getCode(pl.p[j], rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } break; } } @@ -8785,7 +9120,9 @@ static bool hufDecode(const long long *hcode, // i : encoding table if (pl.len) { lc -= pl.len; - getCode(pl.lit, rlc, c, lc, in, out, oe); + if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } } else { return false; // invalidCode(); // wrong (long) code @@ -8800,7 +9137,7 @@ static bool hufDecode(const long long *hcode, // i : encoding table return true; } -static void countFrequencies(long long freq[HUF_ENCSIZE], +static void countFrequencies(std::vector &freq, const unsigned short data[/*n*/], int n) { for (int i = 0; i < HUF_ENCSIZE; ++i) freq[i] = 0; @@ -8831,21 +9168,21 @@ static int hufCompress(const unsigned short raw[], int nRaw, char compressed[]) { if (nRaw == 0) return 0; - long long freq[HUF_ENCSIZE]; + std::vector freq(HUF_ENCSIZE); countFrequencies(freq, raw, nRaw); int im = 0; int iM = 0; - hufBuildEncTable(freq, &im, &iM); + hufBuildEncTable(freq.data(), &im, &iM); char *tableStart = compressed + 20; char *tableEnd = tableStart; - hufPackEncTable(freq, im, iM, &tableEnd); + hufPackEncTable(freq.data(), im, iM, &tableEnd); int tableLength = tableEnd - tableStart; char *dataStart = tableEnd; - int nBits = hufEncode(freq, raw, nRaw, iM, dataStart); + int nBits = hufEncode(freq.data(), raw, nRaw, iM, dataStart); int data_length = (nBits + 7) / 8; writeUInt(compressed, im); @@ -8858,9 +9195,9 @@ static int hufCompress(const unsigned short raw[], int nRaw, } static bool hufUncompress(const char compressed[], int nCompressed, - unsigned short raw[], int nRaw) { + std::vector *raw) { if (nCompressed == 0) { - if (nRaw != 0) return false; + if (raw->size() != 0) return false; return false; } @@ -8901,7 +9238,8 @@ static bool hufUncompress(const char compressed[], int nCompressed, } hufBuildDecTable(&freq.at(0), im, iM, &hdec.at(0)); - hufDecode(&freq.at(0), &hdec.at(0), ptr, nBits, iM, nRaw, raw); + hufDecode(&freq.at(0), &hdec.at(0), ptr, nBits, iM, raw->size(), + raw->data()); } // catch (...) //{ @@ -8990,7 +9328,7 @@ static bool CompressPiz(unsigned char *outPtr, unsigned int *outSize, const unsigned char *inPtr, size_t inSize, const std::vector &channelInfo, int data_width, int num_lines) { - unsigned char bitmap[BITMAP_SIZE]; + std::vector bitmap(BITMAP_SIZE); unsigned short minNonZero; unsigned short maxNonZero; @@ -9041,12 +9379,12 @@ static bool CompressPiz(unsigned char *outPtr, unsigned int *outSize, } } - bitmapFromData(&tmpBuffer.at(0), static_cast(tmpBuffer.size()), bitmap, - minNonZero, maxNonZero); + bitmapFromData(&tmpBuffer.at(0), static_cast(tmpBuffer.size()), + bitmap.data(), minNonZero, maxNonZero); - unsigned short lut[USHORT_RANGE]; - unsigned short maxValue = forwardLutFromBitmap(bitmap, lut); - applyLut(lut, &tmpBuffer.at(0), static_cast(tmpBuffer.size())); + std::vector lut(USHORT_RANGE); + unsigned short maxValue = forwardLutFromBitmap(bitmap.data(), lut.data()); + applyLut(lut.data(), &tmpBuffer.at(0), static_cast(tmpBuffer.size())); // // Store range compression info in _outBuffer @@ -9116,7 +9454,7 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, return true; } - unsigned char bitmap[BITMAP_SIZE]; + std::vector bitmap(BITMAP_SIZE); unsigned short minNonZero; unsigned short maxNonZero; @@ -9126,11 +9464,13 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, return false; #endif - memset(bitmap, 0, BITMAP_SIZE); + memset(bitmap.data(), 0, BITMAP_SIZE); const unsigned char *ptr = inPtr; - minNonZero = *(reinterpret_cast(ptr)); - maxNonZero = *(reinterpret_cast(ptr + 2)); + // minNonZero = *(reinterpret_cast(ptr)); + tinyexr::cpy2(&minNonZero, reinterpret_cast(ptr)); + // maxNonZero = *(reinterpret_cast(ptr + 2)); + tinyexr::cpy2(&maxNonZero, reinterpret_cast(ptr + 2)); ptr += 4; if (maxNonZero >= BITMAP_SIZE) { @@ -9143,9 +9483,9 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, ptr += maxNonZero - minNonZero + 1; } - unsigned short lut[USHORT_RANGE]; - memset(lut, 0, sizeof(unsigned short) * USHORT_RANGE); - unsigned short maxValue = reverseLutFromBitmap(bitmap, lut); + std::vector lut(USHORT_RANGE); + memset(lut.data(), 0, sizeof(unsigned short) * USHORT_RANGE); + unsigned short maxValue = reverseLutFromBitmap(bitmap.data(), lut.data()); // // Huffman decoding @@ -9153,12 +9493,16 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, int length; - length = *(reinterpret_cast(ptr)); + // length = *(reinterpret_cast(ptr)); + tinyexr::cpy4(&length, reinterpret_cast(ptr)); ptr += sizeof(int); + if (size_t((ptr - inPtr) + length) > inLen) { + return false; + } + std::vector tmpBuffer(tmpBufSize); - hufUncompress(reinterpret_cast(ptr), length, &tmpBuffer.at(0), - static_cast(tmpBufSize)); + hufUncompress(reinterpret_cast(ptr), length, &tmpBuffer); // // Wavelet decoding @@ -9199,7 +9543,7 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, // Expand the pixel data to their original range // - applyLut(lut, &tmpBuffer.at(0), static_cast(tmpBufSize)); + applyLut(lut.data(), &tmpBuffer.at(0), static_cast(tmpBufSize)); for (int y = 0; y < num_lines; y++) { for (size_t i = 0; i < channelData.size(); ++i) { @@ -9220,35 +9564,48 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, #endif // TINYEXR_USE_PIZ #if TINYEXR_USE_ZFP + struct ZFPCompressionParam { double rate; - int precision; + unsigned int precision; + unsigned int __pad0; double tolerance; int type; // TINYEXR_ZFP_COMPRESSIONTYPE_* + unsigned int __pad1; ZFPCompressionParam() { type = TINYEXR_ZFP_COMPRESSIONTYPE_RATE; rate = 2.0; precision = 0; - tolerance = 0.0f; + tolerance = 0.0; } }; -bool FindZFPCompressionParam(ZFPCompressionParam *param, - const EXRAttribute *attributes, - int num_attributes) { +static bool FindZFPCompressionParam(ZFPCompressionParam *param, + const EXRAttribute *attributes, + int num_attributes, std::string *err) { bool foundType = false; for (int i = 0; i < num_attributes; i++) { - if ((strcmp(attributes[i].name, "zfpCompressionType") == 0) && - (attributes[i].size == 1)) { - param->type = static_cast(attributes[i].value[0]); - - foundType = true; + if ((strcmp(attributes[i].name, "zfpCompressionType") == 0)) { + if (attributes[i].size == 1) { + param->type = static_cast(attributes[i].value[0]); + foundType = true; + break; + } else { + if (err) { + (*err) += + "zfpCompressionType attribute must be uchar(1 byte) type.\n"; + } + return false; + } } } if (!foundType) { + if (err) { + (*err) += "`zfpCompressionType` attribute not found.\n"; + } return false; } @@ -9260,6 +9617,11 @@ bool FindZFPCompressionParam(ZFPCompressionParam *param, return true; } } + + if (err) { + (*err) += "`zfpCompressionRate` attribute not found.\n"; + } + } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { for (int i = 0; i < num_attributes; i++) { if ((strcmp(attributes[i].name, "zfpCompressionPrecision") == 0) && @@ -9268,6 +9630,11 @@ bool FindZFPCompressionParam(ZFPCompressionParam *param, return true; } } + + if (err) { + (*err) += "`zfpCompressionPrecision` attribute not found.\n"; + } + } else if (param->type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { for (int i = 0; i < num_attributes; i++) { if ((strcmp(attributes[i].name, "zfpCompressionTolerance") == 0) && @@ -9276,8 +9643,14 @@ bool FindZFPCompressionParam(ZFPCompressionParam *param, return true; } } + + if (err) { + (*err) += "`zfpCompressionTolerance` attribute not found.\n"; + } } else { - assert(0); + if (err) { + (*err) += "Unknown value specified for `zfpCompressionType`.\n"; + } } return false; @@ -9285,10 +9658,11 @@ bool FindZFPCompressionParam(ZFPCompressionParam *param, // Assume pixel format is FLOAT for all channels. static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, - int num_channels, const unsigned char *src, + size_t num_channels, const unsigned char *src, unsigned long src_size, const ZFPCompressionParam ¶m) { - size_t uncompressed_size = dst_width * dst_num_lines * num_channels; + size_t uncompressed_size = + size_t(dst_width) * size_t(dst_num_lines) * num_channels; if (uncompressed_size == src_size) { // Data is not compressed(Issue 40). @@ -9301,22 +9675,24 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, assert((dst_width % 4) == 0); assert((dst_num_lines % 4) == 0); - if ((dst_width & 3U) || (dst_num_lines & 3U)) { + if ((size_t(dst_width) & 3U) || (size_t(dst_num_lines) & 3U)) { return false; } field = zfp_field_2d(reinterpret_cast(const_cast(src)), - zfp_type_float, dst_width, dst_num_lines * num_channels); + zfp_type_float, static_cast(dst_width), + static_cast(dst_num_lines) * + static_cast(num_channels)); zfp = zfp_stream_open(NULL); if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) { - zfp_stream_set_rate(zfp, param.rate, zfp_type_float, /* dimention */ 2, + zfp_stream_set_rate(zfp, param.rate, zfp_type_float, /* dimension */ 2, /* write random access */ 0); } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { - zfp_stream_set_precision(zfp, param.precision, zfp_type_float); + zfp_stream_set_precision(zfp, param.precision); } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { - zfp_stream_set_accuracy(zfp, param.tolerance, zfp_type_float); + zfp_stream_set_accuracy(zfp, param.tolerance); } else { assert(0); } @@ -9329,17 +9705,17 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, zfp_stream_set_bit_stream(zfp, stream); zfp_stream_rewind(zfp); - size_t image_size = dst_width * dst_num_lines; + size_t image_size = size_t(dst_width) * size_t(dst_num_lines); - for (int c = 0; c < num_channels; c++) { + for (size_t c = 0; c < size_t(num_channels); c++) { // decompress 4x4 pixel block. - for (int y = 0; y < dst_num_lines; y += 4) { - for (int x = 0; x < dst_width; x += 4) { + for (size_t y = 0; y < size_t(dst_num_lines); y += 4) { + for (size_t x = 0; x < size_t(dst_width); x += 4) { float fblock[16]; zfp_decode_block_float_2(zfp, fblock); - for (int j = 0; j < 4; j++) { - for (int i = 0; i < 4; i++) { - dst[c * image_size + ((y + j) * dst_width + (x + i))] = + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { + dst[c * image_size + ((y + j) * size_t(dst_width) + (x + i))] = fblock[j * 4 + i]; } } @@ -9355,31 +9731,33 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, } // Assume pixel format is FLOAT for all channels. -bool CompressZfp(std::vector *outBuf, unsigned int *outSize, - const float *inPtr, int width, int num_lines, int num_channels, - const ZFPCompressionParam ¶m) { +static bool CompressZfp(std::vector *outBuf, + unsigned int *outSize, const float *inPtr, int width, + int num_lines, int num_channels, + const ZFPCompressionParam ¶m) { zfp_stream *zfp = NULL; zfp_field *field = NULL; assert((width % 4) == 0); assert((num_lines % 4) == 0); - if ((width & 3U) || (num_lines & 3U)) { + if ((size_t(width) & 3U) || (size_t(num_lines) & 3U)) { return false; } // create input array. field = zfp_field_2d(reinterpret_cast(const_cast(inPtr)), - zfp_type_float, width, num_lines * num_channels); + zfp_type_float, static_cast(width), + static_cast(num_lines * num_channels)); zfp = zfp_stream_open(NULL); if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_RATE) { zfp_stream_set_rate(zfp, param.rate, zfp_type_float, 2, 0); } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_PRECISION) { - zfp_stream_set_precision(zfp, param.precision, zfp_type_float); + zfp_stream_set_precision(zfp, param.precision); } else if (param.type == TINYEXR_ZFP_COMPRESSIONTYPE_ACCURACY) { - zfp_stream_set_accuracy(zfp, param.tolerance, zfp_type_float); + zfp_stream_set_accuracy(zfp, param.tolerance); } else { assert(0); } @@ -9392,17 +9770,17 @@ bool CompressZfp(std::vector *outBuf, unsigned int *outSize, zfp_stream_set_bit_stream(zfp, stream); zfp_field_free(field); - size_t image_size = width * num_lines; + size_t image_size = size_t(width) * size_t(num_lines); - for (int c = 0; c < num_channels; c++) { + for (size_t c = 0; c < size_t(num_channels); c++) { // compress 4x4 pixel block. - for (int y = 0; y < num_lines; y += 4) { - for (int x = 0; x < width; x += 4) { + for (size_t y = 0; y < size_t(num_lines); y += 4) { + for (size_t x = 0; x < size_t(width); x += 4) { float fblock[16]; - for (int j = 0; j < 4; j++) { - for (int i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + for (size_t i = 0; i < 4; i++) { fblock[j * 4 + i] = - inPtr[c * image_size + ((y + j) * width + (x + i))]; + inPtr[c * image_size + ((y + j) * size_t(width) + (x + i))]; } } zfp_encode_block_float_2(zfp, fblock); @@ -9411,7 +9789,7 @@ bool CompressZfp(std::vector *outBuf, unsigned int *outSize, } zfp_stream_flush(zfp); - (*outSize) = zfp_stream_compressed_size(zfp); + (*outSize) = static_cast(zfp_stream_compressed_size(zfp)); zfp_stream_close(zfp); @@ -9424,6 +9802,7 @@ bool CompressZfp(std::vector *outBuf, unsigned int *outSize, // ----------------------------------------------------------------- // +// TODO(syoyo): Refactor function arguments. static bool DecodePixelData(/* out */ unsigned char **out_images, const int *requested_pixel_types, const unsigned char *data_ptr, size_t data_len, @@ -9436,6 +9815,11 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, const std::vector &channel_offset_list) { if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { // PIZ #if TINYEXR_USE_PIZ + if ((width == 0) || (num_lines == 0) || (pixel_data_size == 0)) { + // Invalid input #90 + return false; + } + // Allocate original data size. std::vector outBuf(static_cast( static_cast(width * num_lines) * pixel_data_size)); @@ -9445,8 +9829,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, reinterpret_cast(&outBuf.at(0)), data_ptr, tmpBufLen, data_len, static_cast(num_channels), channels, width, num_lines); - assert(ret); - (void)ret; + if (!ret) { + return false; + } // For PIZ_COMPRESSION: // pixel sample data for channel 0 for scanline 0 @@ -9467,7 +9852,10 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, for (size_t u = 0; u < static_cast(width); u++) { FP16 hf; - hf.u = line_ptr[u]; + // hf.u = line_ptr[u]; + // use `cpy` to avoid unaligned memory access when compiler's + // optimization is on. + tinyexr::cpy2(&(hf.u), line_ptr + u); tinyexr::swap2(reinterpret_cast(&hf.u)); @@ -9488,16 +9876,18 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, } else { // HALF -> FLOAT FP32 f32 = half_to_float(hf); float *image = reinterpret_cast(out_images)[c]; + size_t offset = 0; if (line_order == 0) { - image += (static_cast(line_no) + v) * + offset = (static_cast(line_no) + v) * static_cast(x_stride) + u; } else { - image += static_cast( + offset = static_cast( (height - 1 - (line_no + static_cast(v)))) * static_cast(x_stride) + u; } + image += offset; *image = f32.f; } } @@ -9510,7 +9900,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - unsigned int val = line_ptr[u]; + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(&val); @@ -9536,7 +9928,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, v * pixel_data_size * static_cast(x_stride) + channel_offset_list[c] * static_cast(x_stride))); for (size_t u = 0; u < static_cast(width); u++) { - float val = line_ptr[u]; + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(reinterpret_cast(&val)); @@ -9598,7 +9992,8 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, for (size_t u = 0; u < static_cast(width); u++) { tinyexr::FP16 hf; - hf.u = line_ptr[u]; + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); tinyexr::swap2(reinterpret_cast(&hf.u)); @@ -9619,16 +10014,19 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, } else { // HALF -> FLOAT tinyexr::FP32 f32 = half_to_float(hf); float *image = reinterpret_cast(out_images)[c]; + size_t offset = 0; if (line_order == 0) { - image += (static_cast(line_no) + v) * + offset = (static_cast(line_no) + v) * static_cast(x_stride) + u; } else { - image += (static_cast(height) - 1U - + offset = (static_cast(height) - 1U - (static_cast(line_no) + v)) * static_cast(x_stride) + u; } + image += offset; + *image = f32.f; } } @@ -9641,7 +10039,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - unsigned int val = line_ptr[u]; + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(&val); @@ -9667,7 +10067,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - float val = line_ptr[u]; + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(reinterpret_cast(&val)); @@ -9697,10 +10099,15 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, pixel_data_size); unsigned long dstLen = static_cast(outBuf.size()); - assert(dstLen > 0); - tinyexr::DecompressRle(reinterpret_cast(&outBuf.at(0)), - dstLen, data_ptr, - static_cast(data_len)); + if (dstLen == 0) { + return false; + } + + if (!tinyexr::DecompressRle( + reinterpret_cast(&outBuf.at(0)), dstLen, data_ptr, + static_cast(data_len))) { + return false; + } // For RLE_COMPRESSION: // pixel sample data for channel 0 for scanline 0 @@ -9722,7 +10129,8 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, for (size_t u = 0; u < static_cast(width); u++) { tinyexr::FP16 hf; - hf.u = line_ptr[u]; + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); tinyexr::swap2(reinterpret_cast(&hf.u)); @@ -9765,7 +10173,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - unsigned int val = line_ptr[u]; + unsigned int val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(&val); @@ -9791,7 +10201,9 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - float val = line_ptr[u]; + float val; + // val = line_ptr[u]; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(reinterpret_cast(&val)); @@ -9817,8 +10229,10 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, } else if (compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { #if TINYEXR_USE_ZFP tinyexr::ZFPCompressionParam zfp_compression_param; - if (!FindZFPCompressionParam(&zfp_compression_param, attributes, - num_attributes)) { + std::string e; + if (!tinyexr::FindZFPCompressionParam(&zfp_compression_param, attributes, + int(num_attributes), &e)) { + // This code path should not be reachable. assert(0); return false; } @@ -9854,7 +10268,8 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, &outBuf.at(v * pixel_data_size * static_cast(width) + channel_offset_list[c] * static_cast(width))); for (size_t u = 0; u < static_cast(width); u++) { - float val = line_ptr[u]; + float val; + tinyexr::cpy4(&val, line_ptr + u); tinyexr::swap4(reinterpret_cast(&val)); @@ -9886,88 +10301,120 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, #endif } else if (compression_type == TINYEXR_COMPRESSIONTYPE_NONE) { for (size_t c = 0; c < num_channels; c++) { - if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { - const unsigned short *line_ptr = - reinterpret_cast( - data_ptr + - c * static_cast(width) * sizeof(unsigned short)); + for (size_t v = 0; v < static_cast(num_lines); v++) { + if (channels[c].pixel_type == TINYEXR_PIXELTYPE_HALF) { + const unsigned short *line_ptr = + reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); - if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { - unsigned short *outLine = - reinterpret_cast(out_images[c]); - if (line_order == 0) { - outLine += y * x_stride; + if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { + unsigned short *outLine = + reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + for (int u = 0; u < width; u++) { + tinyexr::FP16 hf; + + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + outLine[u] = hf.u; + } + } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { + float *outLine = reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; + } + + for (int u = 0; u < width; u++) { + tinyexr::FP16 hf; + + // address may not be aliged. use byte-wise copy for safety.#76 + // hf.u = line_ptr[u]; + tinyexr::cpy2(&(hf.u), line_ptr + u); + + tinyexr::swap2(reinterpret_cast(&hf.u)); + + tinyexr::FP32 f32 = half_to_float(hf); + + outLine[u] = f32.f; + } } else { - outLine += (height - 1 - y) * x_stride; + assert(0); + return false; } + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { + const float *line_ptr = reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); - for (int u = 0; u < width; u++) { - tinyexr::FP16 hf; - - hf.u = line_ptr[u]; - - tinyexr::swap2(reinterpret_cast(&hf.u)); - - outLine[u] = hf.u; - } - } else if (requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { float *outLine = reinterpret_cast(out_images[c]); if (line_order == 0) { - outLine += y * x_stride; + outLine += (size_t(y) + v) * size_t(x_stride); } else { - outLine += (height - 1 - y) * x_stride; + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } + + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; } for (int u = 0; u < width; u++) { - tinyexr::FP16 hf; + float val; + tinyexr::cpy4(&val, line_ptr + u); - hf.u = line_ptr[u]; + tinyexr::swap4(reinterpret_cast(&val)); - tinyexr::swap2(reinterpret_cast(&hf.u)); - - tinyexr::FP32 f32 = half_to_float(hf); - - outLine[u] = f32.f; + outLine[u] = val; } - } else { - assert(0); - return false; - } - } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_FLOAT) { - const float *line_ptr = reinterpret_cast( - data_ptr + c * static_cast(width) * sizeof(float)); + } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { + const unsigned int *line_ptr = reinterpret_cast( + data_ptr + v * pixel_data_size * size_t(width) + + channel_offset_list[c] * static_cast(width)); - float *outLine = reinterpret_cast(out_images[c]); - if (line_order == 0) { - outLine += y * x_stride; - } else { - outLine += (height - 1 - y) * x_stride; - } + unsigned int *outLine = + reinterpret_cast(out_images[c]); + if (line_order == 0) { + outLine += (size_t(y) + v) * size_t(x_stride); + } else { + outLine += + (size_t(height) - 1 - (size_t(y) + v)) * size_t(x_stride); + } - for (int u = 0; u < width; u++) { - float val = line_ptr[u]; + for (int u = 0; u < width; u++) { + if (reinterpret_cast(line_ptr + u) >= + (data_ptr + data_len)) { + // Corrupsed data? + return false; + } - tinyexr::swap4(reinterpret_cast(&val)); + unsigned int val; + tinyexr::cpy4(&val, line_ptr + u); - outLine[u] = val; - } - } else if (channels[c].pixel_type == TINYEXR_PIXELTYPE_UINT) { - const unsigned int *line_ptr = reinterpret_cast( - data_ptr + c * static_cast(width) * sizeof(unsigned int)); + tinyexr::swap4(reinterpret_cast(&val)); - unsigned int *outLine = reinterpret_cast(out_images[c]); - if (line_order == 0) { - outLine += y * x_stride; - } else { - outLine += (height - 1 - y) * x_stride; - } - - for (int u = 0; u < width; u++) { - unsigned int val = line_ptr[u]; - - tinyexr::swap4(reinterpret_cast(&val)); - - outLine[u] = val; + outLine[u] = val; + } } } } @@ -9976,7 +10423,7 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, return true; } -static void DecodeTiledPixelData( +static bool DecodeTiledPixelData( unsigned char **out_images, int *width, int *height, const int *requested_pixel_types, const unsigned char *data_ptr, size_t data_len, int compression_type, int line_order, int data_width, @@ -9985,8 +10432,11 @@ static void DecodeTiledPixelData( const EXRAttribute *attributes, size_t num_channels, const EXRChannelInfo *channels, const std::vector &channel_offset_list) { - assert(tile_offset_x * tile_size_x < data_width); - assert(tile_offset_y * tile_size_y < data_height); + if (tile_size_x > data_width || tile_size_y > data_height || + tile_size_x * tile_offset_x > data_width || + tile_size_y * tile_offset_y > data_height) { + return false; + } // Compute actual image size in a tile. if ((tile_offset_x + 1) * tile_size_x >= data_width) { @@ -10002,14 +10452,14 @@ static void DecodeTiledPixelData( } // Image size = tile size. - DecodePixelData(out_images, requested_pixel_types, data_ptr, data_len, - compression_type, line_order, (*width), tile_size_y, - /* stride */ tile_size_x, /* y */ 0, /* line_no */ 0, - (*height), pixel_data_size, num_attributes, attributes, - num_channels, channels, channel_offset_list); + return DecodePixelData(out_images, requested_pixel_types, data_ptr, data_len, + compression_type, line_order, (*width), tile_size_y, + /* stride */ tile_size_x, /* y */ 0, /* line_no */ 0, + (*height), pixel_data_size, num_attributes, attributes, + num_channels, channels, channel_offset_list); } -static void ComputeChannelLayout(std::vector *channel_offset_list, +static bool ComputeChannelLayout(std::vector *channel_offset_list, int *pixel_data_size, size_t *channel_offset, int num_channels, const EXRChannelInfo *channels) { @@ -10030,9 +10480,11 @@ static void ComputeChannelLayout(std::vector *channel_offset_list, (*pixel_data_size) += sizeof(unsigned int); (*channel_offset) += sizeof(unsigned int); } else { - assert(0); + // ??? + return false; } } + return true; } static unsigned char **AllocateImage(int num_channels, @@ -10078,6 +10530,17 @@ static unsigned char **AllocateImage(int num_channels, return images; } +#ifdef _WIN32 +static inline std::wstring UTF8ToWchar(const std::string &str) { + int wstr_size = + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), NULL, 0); + std::wstring wstr(wstr_size, 0); + MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &wstr[0], + (int)wstr.size()); + return wstr; +} +#endif + static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, const EXRVersion *version, std::string *err, const unsigned char *buf, size_t size) { @@ -10117,15 +10580,15 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, bool has_screen_window_center = false; bool has_screen_window_width = false; - info->data_window[0] = 0; - info->data_window[1] = 0; - info->data_window[2] = 0; - info->data_window[3] = 0; + info->data_window.min_x = 0; + info->data_window.min_y = 0; + info->data_window.max_x = 0; + info->data_window.max_y = 0; info->line_order = 0; // @fixme - info->display_window[0] = 0; - info->display_window[1] = 0; - info->display_window[2] = 0; - info->display_window[3] = 0; + info->display_window.min_x = 0; + info->display_window.min_y = 0; + info->display_window.max_x = 0; + info->display_window.max_y = 0; info->screen_window_center[0] = 0.0f; info->screen_window_center[1] = 0.0f; info->screen_window_width = -1.0f; @@ -10140,8 +10603,11 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, // Read attributes size_t orig_size = size; - for (;;) { + for (size_t nattr = 0; nattr < TINYEXR_MAX_HEADER_ATTRIBUTES; nattr++) { if (0 == size) { + if (err) { + (*err) += "Insufficient data size for attributes.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } else if (marker[0] == '\0') { size--; @@ -10154,6 +10620,9 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, size_t marker_size; if (!tinyexr::ReadAttribute(&attr_name, &attr_type, &data, &marker_size, marker, size)) { + if (err) { + (*err) += "Failed to read attribute.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } marker += marker_size; @@ -10169,6 +10638,14 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, tinyexr::swap4(&x_size); tinyexr::swap4(&y_size); + if (x_size > static_cast(std::numeric_limits::max()) || + y_size > static_cast(std::numeric_limits::max())) { + if (err) { + (*err) = "Tile sizes were invalid."; + } + return TINYEXR_ERROR_UNSUPPORTED_FORMAT; + } + info->tile_size_x = static_cast(x_size); info->tile_size_y = static_cast(y_size); @@ -10224,14 +10701,14 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, if (!ReadChannelInfo(info->channels, data)) { if (err) { - (*err) = "Failed to parse channel info."; + (*err) += "Failed to parse channel info.\n"; } return TINYEXR_ERROR_INVALID_DATA; } if (info->channels.size() < 1) { if (err) { - (*err) = "# of channels is zero."; + (*err) += "# of channels is zero.\n"; } return TINYEXR_ERROR_INVALID_DATA; } @@ -10240,30 +10717,26 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, } else if (attr_name.compare("dataWindow") == 0) { if (data.size() >= 16) { - memcpy(&info->data_window[0], &data.at(0), sizeof(int)); - memcpy(&info->data_window[1], &data.at(4), sizeof(int)); - memcpy(&info->data_window[2], &data.at(8), sizeof(int)); - memcpy(&info->data_window[3], &data.at(12), sizeof(int)); - tinyexr::swap4(reinterpret_cast(&info->data_window[0])); - tinyexr::swap4(reinterpret_cast(&info->data_window[1])); - tinyexr::swap4(reinterpret_cast(&info->data_window[2])); - tinyexr::swap4(reinterpret_cast(&info->data_window[3])); + memcpy(&info->data_window.min_x, &data.at(0), sizeof(int)); + memcpy(&info->data_window.min_y, &data.at(4), sizeof(int)); + memcpy(&info->data_window.max_x, &data.at(8), sizeof(int)); + memcpy(&info->data_window.max_y, &data.at(12), sizeof(int)); + tinyexr::swap4(&info->data_window.min_x); + tinyexr::swap4(&info->data_window.min_y); + tinyexr::swap4(&info->data_window.max_x); + tinyexr::swap4(&info->data_window.max_y); has_data_window = true; } } else if (attr_name.compare("displayWindow") == 0) { if (data.size() >= 16) { - memcpy(&info->display_window[0], &data.at(0), sizeof(int)); - memcpy(&info->display_window[1], &data.at(4), sizeof(int)); - memcpy(&info->display_window[2], &data.at(8), sizeof(int)); - memcpy(&info->display_window[3], &data.at(12), sizeof(int)); - tinyexr::swap4( - reinterpret_cast(&info->display_window[0])); - tinyexr::swap4( - reinterpret_cast(&info->display_window[1])); - tinyexr::swap4( - reinterpret_cast(&info->display_window[2])); - tinyexr::swap4( - reinterpret_cast(&info->display_window[3])); + memcpy(&info->display_window.min_x, &data.at(0), sizeof(int)); + memcpy(&info->display_window.min_y, &data.at(4), sizeof(int)); + memcpy(&info->display_window.max_x, &data.at(8), sizeof(int)); + memcpy(&info->display_window.max_y, &data.at(12), sizeof(int)); + tinyexr::swap4(&info->display_window.min_x); + tinyexr::swap4(&info->display_window.min_y); + tinyexr::swap4(&info->display_window.max_x); + tinyexr::swap4(&info->display_window.max_y); has_display_window = true; } @@ -10275,36 +10748,32 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, } else if (attr_name.compare("pixelAspectRatio") == 0) { if (data.size() >= sizeof(float)) { memcpy(&info->pixel_aspect_ratio, &data.at(0), sizeof(float)); - tinyexr::swap4( - reinterpret_cast(&info->pixel_aspect_ratio)); + tinyexr::swap4(&info->pixel_aspect_ratio); has_pixel_aspect_ratio = true; } } else if (attr_name.compare("screenWindowCenter") == 0) { if (data.size() >= 8) { memcpy(&info->screen_window_center[0], &data.at(0), sizeof(float)); memcpy(&info->screen_window_center[1], &data.at(4), sizeof(float)); - tinyexr::swap4( - reinterpret_cast(&info->screen_window_center[0])); - tinyexr::swap4( - reinterpret_cast(&info->screen_window_center[1])); + tinyexr::swap4(&info->screen_window_center[0]); + tinyexr::swap4(&info->screen_window_center[1]); has_screen_window_center = true; } } else if (attr_name.compare("screenWindowWidth") == 0) { if (data.size() >= sizeof(float)) { memcpy(&info->screen_window_width, &data.at(0), sizeof(float)); - tinyexr::swap4( - reinterpret_cast(&info->screen_window_width)); + tinyexr::swap4(&info->screen_window_width); has_screen_window_width = true; } } else if (attr_name.compare("chunkCount") == 0) { if (data.size() >= sizeof(int)) { memcpy(&info->chunk_count, &data.at(0), sizeof(int)); - tinyexr::swap4(reinterpret_cast(&info->chunk_count)); + tinyexr::swap4(&info->chunk_count); } } else { - // Custom attribute(up to TINYEXR_MAX_ATTRIBUTES) - if (info->attributes.size() < TINYEXR_MAX_ATTRIBUTES) { + // Custom attribute(up to TINYEXR_MAX_CUSTOM_ATTRIBUTES) + if (info->attributes.size() < TINYEXR_MAX_CUSTOM_ATTRIBUTES) { EXRAttribute attrib; #ifdef _MSC_VER strncpy_s(attrib.name, attr_name.c_str(), 255); @@ -10386,14 +10855,14 @@ static void ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info) { exr_header->screen_window_center[1] = info.screen_window_center[1]; exr_header->screen_window_width = info.screen_window_width; exr_header->chunk_count = info.chunk_count; - exr_header->display_window[0] = info.display_window[0]; - exr_header->display_window[1] = info.display_window[1]; - exr_header->display_window[2] = info.display_window[2]; - exr_header->display_window[3] = info.display_window[3]; - exr_header->data_window[0] = info.data_window[0]; - exr_header->data_window[1] = info.data_window[1]; - exr_header->data_window[2] = info.data_window[2]; - exr_header->data_window[3] = info.data_window[3]; + exr_header->display_window.min_x = info.display_window.min_x; + exr_header->display_window.min_y = info.display_window.min_y; + exr_header->display_window.max_x = info.display_window.max_x; + exr_header->display_window.max_y = info.display_window.max_y; + exr_header->data_window.min_x = info.data_window.min_x; + exr_header->data_window.min_y = info.data_window.min_y; + exr_header->data_window.max_x = info.data_window.max_x; + exr_header->data_window.max_y = info.data_window.max_y; exr_header->line_order = info.line_order; exr_header->compression_type = info.compression_type; @@ -10434,15 +10903,30 @@ static void ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info) { exr_header->requested_pixel_types[c] = info.channels[c].pixel_type; } - assert(info.attributes.size() < TINYEXR_MAX_ATTRIBUTES); exr_header->num_custom_attributes = static_cast(info.attributes.size()); - for (size_t i = 0; i < info.attributes.size(); i++) { - memcpy(exr_header->custom_attributes[i].name, info.attributes[i].name, 256); - memcpy(exr_header->custom_attributes[i].type, info.attributes[i].type, 256); - exr_header->custom_attributes[i].size = info.attributes[i].size; - // Just copy poiner - exr_header->custom_attributes[i].value = info.attributes[i].value; + if (exr_header->num_custom_attributes > 0) { + // TODO(syoyo): Report warning when # of attributes exceeds + // `TINYEXR_MAX_CUSTOM_ATTRIBUTES` + if (exr_header->num_custom_attributes > TINYEXR_MAX_CUSTOM_ATTRIBUTES) { + exr_header->num_custom_attributes = TINYEXR_MAX_CUSTOM_ATTRIBUTES; + } + + exr_header->custom_attributes = static_cast(malloc( + sizeof(EXRAttribute) * size_t(exr_header->num_custom_attributes))); + + for (size_t i = 0; i < info.attributes.size(); i++) { + memcpy(exr_header->custom_attributes[i].name, info.attributes[i].name, + 256); + memcpy(exr_header->custom_attributes[i].type, info.attributes[i].type, + 256); + exr_header->custom_attributes[i].size = info.attributes[i].size; + // Just copy pointer + exr_header->custom_attributes[i].value = info.attributes[i].value; + } + + } else { + exr_header->custom_attributes = NULL; } exr_header->header_len = info.header_len; @@ -10450,7 +10934,8 @@ static void ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info) { static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, const std::vector &offsets, - const unsigned char *head, const size_t size) { + const unsigned char *head, const size_t size, + std::string *err) { int num_channels = exr_header->num_channels; int num_scanline_blocks = 1; @@ -10460,163 +10945,350 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, num_scanline_blocks = 32; } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { num_scanline_blocks = 16; + +#if TINYEXR_USE_ZFP + tinyexr::ZFPCompressionParam zfp_compression_param; + if (!FindZFPCompressionParam(&zfp_compression_param, + exr_header->custom_attributes, + int(exr_header->num_custom_attributes), err)) { + return TINYEXR_ERROR_INVALID_HEADER; + } +#endif } - int data_width = exr_header->data_window[2] - exr_header->data_window[0] + 1; - int data_height = exr_header->data_window[3] - exr_header->data_window[1] + 1; + if (exr_header->data_window.max_x < exr_header->data_window.min_x || + exr_header->data_window.max_y < exr_header->data_window.min_y) { + if (err) { + (*err) += "Invalid data window.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } + + int data_width = + exr_header->data_window.max_x - exr_header->data_window.min_x + 1; + int data_height = + exr_header->data_window.max_y - exr_header->data_window.min_y + 1; + + // Do not allow too large data_width and data_height. header invalid? + { + const int threshold = 1024 * 8192; // heuristics + if ((data_width > threshold) || (data_height > threshold)) { + if (err) { + std::stringstream ss; + ss << "data_with or data_height too large. data_width: " << data_width + << ", " + << "data_height = " << data_height << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + } size_t num_blocks = offsets.size(); std::vector channel_offset_list; int pixel_data_size = 0; size_t channel_offset = 0; - tinyexr::ComputeChannelLayout(&channel_offset_list, &pixel_data_size, - &channel_offset, num_channels, - exr_header->channels); + if (!tinyexr::ComputeChannelLayout(&channel_offset_list, &pixel_data_size, + &channel_offset, num_channels, + exr_header->channels)) { + if (err) { + (*err) += "Failed to compute channel layout.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } bool invalid_data = false; // TODO(LTE): Use atomic lock for MT safety. if (exr_header->tiled) { + // value check + if (exr_header->tile_size_x < 0) { + if (err) { + std::stringstream ss; + ss << "Invalid tile size x : " << exr_header->tile_size_x << "\n"; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_HEADER; + } + + if (exr_header->tile_size_y < 0) { + if (err) { + std::stringstream ss; + ss << "Invalid tile size y : " << exr_header->tile_size_y << "\n"; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_HEADER; + } + size_t num_tiles = offsets.size(); // = # of blocks exr_image->tiles = static_cast( calloc(sizeof(EXRTile), static_cast(num_tiles))); - for (size_t tile_idx = 0; tile_idx < num_tiles; tile_idx++) { - // Allocate memory for each tile. - exr_image->tiles[tile_idx].images = tinyexr::AllocateImage( - num_channels, exr_header->channels, exr_header->requested_pixel_types, - exr_header->tile_size_x, exr_header->tile_size_y); + int err_code = TINYEXR_SUCCESS; - // 16 byte: tile coordinates - // 4 byte : data size - // ~ : data(uncompressed or compressed) - if (offsets[tile_idx] + sizeof(int) * 5 > size) { - return TINYEXR_ERROR_INVALID_DATA; - } +#if (__cplusplus > 199711L) && (TINYEXR_USE_THREAD > 0) - size_t data_size = size - (size_t(offsets[tile_idx]) + sizeof(int) * 5); - const unsigned char *data_ptr = - reinterpret_cast(head + size_t(offsets[tile_idx])); + std::vector workers; + std::atomic tile_count(0); - int tile_coordinates[4]; - memcpy(tile_coordinates, data_ptr, sizeof(int) * 4); - tinyexr::swap4(reinterpret_cast(&tile_coordinates[0])); - tinyexr::swap4(reinterpret_cast(&tile_coordinates[1])); - tinyexr::swap4(reinterpret_cast(&tile_coordinates[2])); - tinyexr::swap4(reinterpret_cast(&tile_coordinates[3])); - - // @todo{ LoD } - if (tile_coordinates[2] != 0) { - return TINYEXR_ERROR_UNSUPPORTED_FEATURE; - } - if (tile_coordinates[3] != 0) { - return TINYEXR_ERROR_UNSUPPORTED_FEATURE; - } - - int data_len; - memcpy(&data_len, data_ptr + 16, - sizeof(int)); // 16 = sizeof(tile_coordinates) - tinyexr::swap4(reinterpret_cast(&data_len)); - - if (data_len < 4 || size_t(data_len) > data_size) { - return TINYEXR_ERROR_INVALID_DATA; - } - - // Move to data addr: 20 = 16 + 4; - data_ptr += 20; - - tinyexr::DecodeTiledPixelData( - exr_image->tiles[tile_idx].images, - &(exr_image->tiles[tile_idx].width), - &(exr_image->tiles[tile_idx].height), - exr_header->requested_pixel_types, data_ptr, - static_cast(data_len), exr_header->compression_type, - exr_header->line_order, data_width, data_height, tile_coordinates[0], - tile_coordinates[1], exr_header->tile_size_x, exr_header->tile_size_y, - static_cast(pixel_data_size), - static_cast(exr_header->num_custom_attributes), - exr_header->custom_attributes, - static_cast(exr_header->num_channels), exr_header->channels, - channel_offset_list); - - exr_image->tiles[tile_idx].offset_x = tile_coordinates[0]; - exr_image->tiles[tile_idx].offset_y = tile_coordinates[1]; - exr_image->tiles[tile_idx].level_x = tile_coordinates[2]; - exr_image->tiles[tile_idx].level_y = tile_coordinates[3]; - - exr_image->num_tiles = static_cast(num_tiles); + int num_threads = std::max(1, int(std::thread::hardware_concurrency())); + if (num_threads > int(num_tiles)) { + num_threads = int(num_tiles); } + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() { + size_t tile_idx = 0; + while ((tile_idx = tile_count++) < num_tiles) { + +#else + for (size_t tile_idx = 0; tile_idx < num_tiles; tile_idx++) { +#endif + // Allocate memory for each tile. + exr_image->tiles[tile_idx].images = tinyexr::AllocateImage( + num_channels, exr_header->channels, + exr_header->requested_pixel_types, exr_header->tile_size_x, + exr_header->tile_size_y); + + // 16 byte: tile coordinates + // 4 byte : data size + // ~ : data(uncompressed or compressed) + if (offsets[tile_idx] + sizeof(int) * 5 > size) { + // TODO(LTE): atomic + if (err) { + (*err) += "Insufficient data size.\n"; + } + err_code = TINYEXR_ERROR_INVALID_DATA; + break; + } + + size_t data_size = + size_t(size - (offsets[tile_idx] + sizeof(int) * 5)); + const unsigned char *data_ptr = + reinterpret_cast(head + offsets[tile_idx]); + + int tile_coordinates[4]; + memcpy(tile_coordinates, data_ptr, sizeof(int) * 4); + tinyexr::swap4(&tile_coordinates[0]); + tinyexr::swap4(&tile_coordinates[1]); + tinyexr::swap4(&tile_coordinates[2]); + tinyexr::swap4(&tile_coordinates[3]); + + // @todo{ LoD } + if (tile_coordinates[2] != 0) { + err_code = TINYEXR_ERROR_UNSUPPORTED_FEATURE; + break; + } + if (tile_coordinates[3] != 0) { + err_code = TINYEXR_ERROR_UNSUPPORTED_FEATURE; + break; + } + + int data_len; + memcpy(&data_len, data_ptr + 16, + sizeof(int)); // 16 = sizeof(tile_coordinates) + tinyexr::swap4(&data_len); + + if (data_len < 4 || size_t(data_len) > data_size) { + // TODO(LTE): atomic + if (err) { + (*err) += "Insufficient data length.\n"; + } + err_code = TINYEXR_ERROR_INVALID_DATA; + break; + } + + // Move to data addr: 20 = 16 + 4; + data_ptr += 20; + + bool ret = tinyexr::DecodeTiledPixelData( + exr_image->tiles[tile_idx].images, + &(exr_image->tiles[tile_idx].width), + &(exr_image->tiles[tile_idx].height), + exr_header->requested_pixel_types, data_ptr, + static_cast(data_len), exr_header->compression_type, + exr_header->line_order, data_width, data_height, + tile_coordinates[0], tile_coordinates[1], exr_header->tile_size_x, + exr_header->tile_size_y, static_cast(pixel_data_size), + static_cast(exr_header->num_custom_attributes), + exr_header->custom_attributes, + static_cast(exr_header->num_channels), + exr_header->channels, channel_offset_list); + + if (!ret) { + // TODO(LTE): atomic + if (err) { + (*err) += "Failed to decode tile data.\n"; + } + err_code = TINYEXR_ERROR_INVALID_DATA; + } + + exr_image->tiles[tile_idx].offset_x = tile_coordinates[0]; + exr_image->tiles[tile_idx].offset_y = tile_coordinates[1]; + exr_image->tiles[tile_idx].level_x = tile_coordinates[2]; + exr_image->tiles[tile_idx].level_y = tile_coordinates[3]; + +#if (__cplusplus > 199711L) && (TINYEXR_USE_THREAD > 0) + } + })); + } // num_thread loop + + for (auto &t : workers) { + t.join(); + } + +#else + } +#endif + + if (err_code != TINYEXR_SUCCESS) { + return err_code; + } + + exr_image->num_tiles = static_cast(num_tiles); } else { // scanline format + // Don't allow too large image(256GB * pixel_data_size or more). Workaround + // for #104. + size_t total_data_len = + size_t(data_width) * size_t(data_height) * size_t(num_channels); + const bool total_data_len_overflown = + sizeof(void *) == 8 ? (total_data_len >= 0x4000000000) : false; + if ((total_data_len == 0) || total_data_len_overflown) { + if (err) { + std::stringstream ss; + ss << "Image data size is zero or too large: width = " << data_width + << ", height = " << data_height << ", channels = " << num_channels + << std::endl; + (*err) += ss.str(); + } + return TINYEXR_ERROR_INVALID_DATA; + } + exr_image->images = tinyexr::AllocateImage( num_channels, exr_header->channels, exr_header->requested_pixel_types, data_width, data_height); -#ifdef _OPENMP +#if (__cplusplus > 199711L) && (TINYEXR_USE_THREAD > 0) + std::vector workers; + std::atomic y_count(0); + + int num_threads = std::max(1, int(std::thread::hardware_concurrency())); + if (num_threads > int(num_blocks)) { + num_threads = int(num_blocks); + } + + for (int t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&]() { + int y = 0; + while ((y = y_count++) < int(num_blocks)) { + +#else + +#if TINYEXR_USE_OPENMP #pragma omp parallel for #endif for (int y = 0; y < static_cast(num_blocks); y++) { - size_t y_idx = static_cast(y); - if (offsets[y_idx] + sizeof(int) * 2 > size) { - invalid_data = true; - } else { - // 4 byte: scan line - // 4 byte: data size - // ~ : pixel data(uncompressed or compressed) - size_t data_size = size - (size_t(offsets[y_idx]) + sizeof(int) * 2); - const unsigned char *data_ptr = - reinterpret_cast(head + size_t(offsets[y_idx])); +#endif + size_t y_idx = static_cast(y); - int line_no; - memcpy(&line_no, data_ptr, sizeof(int)); - int data_len; - memcpy(&data_len, data_ptr + 4, sizeof(int)); - tinyexr::swap4(reinterpret_cast(&line_no)); - tinyexr::swap4(reinterpret_cast(&data_len)); - - if (size_t(data_len) > data_size) { - invalid_data = true; - } else { - int end_line_no = (std::min)(line_no + num_scanline_blocks, - (exr_header->data_window[3] + 1)); - - int num_lines = end_line_no - line_no; - // assert(num_lines > 0); - - if (num_lines <= 0) { + if (offsets[y_idx] + sizeof(int) * 2 > size) { invalid_data = true; } else { - // Move to data addr: 8 = 4 + 4; - data_ptr += 8; + // 4 byte: scan line + // 4 byte: data size + // ~ : pixel data(uncompressed or compressed) + size_t data_size = + size_t(size - (offsets[y_idx] + sizeof(int) * 2)); + const unsigned char *data_ptr = + reinterpret_cast(head + offsets[y_idx]); - // Adjust line_no with data_window.bmin.y - line_no -= exr_header->data_window[1]; + int line_no; + memcpy(&line_no, data_ptr, sizeof(int)); + int data_len; + memcpy(&data_len, data_ptr + 4, sizeof(int)); + tinyexr::swap4(&line_no); + tinyexr::swap4(&data_len); - if (line_no < 0) { + if (size_t(data_len) > data_size) { + invalid_data = true; + + } else if ((line_no > (2 << 20)) || (line_no < -(2 << 20))) { + // Too large value. Assume this is invalid + // 2**20 = 1048576 = heuristic value. + invalid_data = true; + } else if (data_len == 0) { + // TODO(syoyo): May be ok to raise the threshold for example + // `data_len < 4` invalid_data = true; } else { - if (!tinyexr::DecodePixelData( - exr_image->images, exr_header->requested_pixel_types, - data_ptr, static_cast(data_len), - exr_header->compression_type, exr_header->line_order, - data_width, data_height, data_width, y, line_no, - num_lines, static_cast(pixel_data_size), - static_cast(exr_header->num_custom_attributes), - exr_header->custom_attributes, - static_cast(exr_header->num_channels), - exr_header->channels, channel_offset_list)) { + // line_no may be negative. + int end_line_no = (std::min)(line_no + num_scanline_blocks, + (exr_header->data_window.max_y + 1)); + + int num_lines = end_line_no - line_no; + + if (num_lines <= 0) { invalid_data = true; + } else { + // Move to data addr: 8 = 4 + 4; + data_ptr += 8; + + // Adjust line_no with data_window.bmin.y + + // overflow check + tinyexr_int64 lno = + static_cast(line_no) - + static_cast(exr_header->data_window.min_y); + if (lno > std::numeric_limits::max()) { + line_no = -1; // invalid + } else if (lno < -std::numeric_limits::max()) { + line_no = -1; // invalid + } else { + line_no -= exr_header->data_window.min_y; + } + + if (line_no < 0) { + invalid_data = true; + } else { + if (!tinyexr::DecodePixelData( + exr_image->images, exr_header->requested_pixel_types, + data_ptr, static_cast(data_len), + exr_header->compression_type, exr_header->line_order, + data_width, data_height, data_width, y, line_no, + num_lines, static_cast(pixel_data_size), + static_cast( + exr_header->num_custom_attributes), + exr_header->custom_attributes, + static_cast(exr_header->num_channels), + exr_header->channels, channel_offset_list)) { + invalid_data = true; + } + } } } } + +#if (__cplusplus > 199711L) && (TINYEXR_USE_THREAD > 0) } - } + })); + } + + for (auto &t : workers) { + t.join(); + } +#else } // omp parallel +#endif } if (invalid_data) { + if (err) { + std::stringstream ss; + (*err) += "Invalid data found when decoding pixels.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } @@ -10660,8 +11332,8 @@ static bool ReconstructLineOffsets( return false; } - tinyexr::swap4(reinterpret_cast(&y)); - tinyexr::swap4(reinterpret_cast(&data_len)); + tinyexr::swap4(&y); + tinyexr::swap4(&data_len); (*offsets)[i] = offset; @@ -10677,9 +11349,7 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, const char **err) { if (exr_image == NULL || exr_header == NULL || head == NULL || marker == NULL || (size <= tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for DecodeEXRImage().", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -10692,30 +11362,36 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, num_scanline_blocks = 16; } - int data_width = exr_header->data_window[2] - exr_header->data_window[0]; - if (data_width >= std::numeric_limits::max()) { + if (exr_header->data_window.max_x < exr_header->data_window.min_x || + exr_header->data_window.max_x - exr_header->data_window.min_x == + std::numeric_limits::max()) { // Issue 63 - if (err) { - (*err) = "Invalid data window value."; - } + tinyexr::SetErrorMessage("Invalid data width value", err); return TINYEXR_ERROR_INVALID_DATA; } - data_width++; + int data_width = + exr_header->data_window.max_x - exr_header->data_window.min_x + 1; - int data_height = exr_header->data_window[3] - exr_header->data_window[1]; - if (data_height >= std::numeric_limits::max()) { - if (err) { - (*err) = "Invalid data height value."; - } + if (exr_header->data_window.max_y < exr_header->data_window.min_y || + exr_header->data_window.max_y - exr_header->data_window.min_y == + std::numeric_limits::max()) { + tinyexr::SetErrorMessage("Invalid data height value", err); return TINYEXR_ERROR_INVALID_DATA; } - data_height++; + int data_height = + exr_header->data_window.max_y - exr_header->data_window.min_y + 1; - if ((data_width < 0) || (data_height < 0)) { - if (err) { - (*err) = "Invalid data window value."; + // Do not allow too large data_width and data_height. header invalid? + { + const int threshold = 1024 * 8192; // heuristics + if (data_width > threshold) { + tinyexr::SetErrorMessage("data width too large.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + if (data_height > threshold) { + tinyexr::SetErrorMessage("data height too large.", err); + return TINYEXR_ERROR_INVALID_DATA; } - return TINYEXR_ERROR_INVALID_DATA; } // Read offset tables. @@ -10726,6 +11402,12 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, num_blocks = static_cast(exr_header->chunk_count); } else if (exr_header->tiled) { // @todo { LoD } + if (exr_header->tile_size_x > data_width || exr_header->tile_size_x < 1 || + exr_header->tile_size_y > data_height || exr_header->tile_size_y < 1) { + tinyexr::SetErrorMessage("tile sizes are invalid.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + size_t num_x_tiles = static_cast(data_width) / static_cast(exr_header->tile_size_x); if (num_x_tiles * static_cast(exr_header->tile_size_x) < @@ -10753,12 +11435,16 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, for (size_t y = 0; y < num_blocks; y++) { tinyexr::tinyexr_uint64 offset; + // Issue #81 + if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) { + tinyexr::SetErrorMessage("Insufficient data size in offset table.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); tinyexr::swap8(&offset); if (offset >= size) { - if (err) { - (*err) = "Invalid offset value."; - } + tinyexr::SetErrorMessage("Invalid offset value in DecodeEXRImage.", err); return TINYEXR_ERROR_INVALID_DATA; } marker += sizeof(tinyexr::tinyexr_uint64); // = 8 @@ -10781,25 +11467,149 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, // OK break; } else { - if (err) { - (*err) = "Cannot reconstruct lineOffset table."; - } + tinyexr::SetErrorMessage( + "Cannot reconstruct lineOffset table in DecodeEXRImage.", err); return TINYEXR_ERROR_INVALID_DATA; } } } - return DecodeChunk(exr_image, exr_header, offsets, head, size); + { + std::string e; + int ret = DecodeChunk(exr_image, exr_header, offsets, head, size, &e); + + if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } + +#if 1 + FreeEXRImage(exr_image); +#else + // release memory(if exists) + if ((exr_header->num_channels > 0) && exr_image && exr_image->images) { + for (size_t c = 0; c < size_t(exr_header->num_channels); c++) { + if (exr_image->images[c]) { + free(exr_image->images[c]); + exr_image->images[c] = NULL; + } + } + free(exr_image->images); + exr_image->images = NULL; + } +#endif + } + + return ret; + } +} + +static void GetLayers(const EXRHeader &exr_header, + std::vector &layer_names) { + // Naive implementation + // Group channels by layers + // go over all channel names, split by periods + // collect unique names + layer_names.clear(); + for (int c = 0; c < exr_header.num_channels; c++) { + std::string full_name(exr_header.channels[c].name); + const size_t pos = full_name.find_last_of('.'); + if (pos != std::string::npos && pos != 0 && pos + 1 < full_name.size()) { + full_name.erase(pos); + if (std::find(layer_names.begin(), layer_names.end(), full_name) == + layer_names.end()) + layer_names.push_back(full_name); + } + } +} + +struct LayerChannel { + explicit LayerChannel(size_t i, std::string n) : index(i), name(n) {} + size_t index; + std::string name; +}; + +static void ChannelsInLayer(const EXRHeader &exr_header, + const std::string layer_name, + std::vector &channels) { + channels.clear(); + for (int c = 0; c < exr_header.num_channels; c++) { + std::string ch_name(exr_header.channels[c].name); + if (layer_name.empty()) { + const size_t pos = ch_name.find_last_of('.'); + if (pos != std::string::npos && pos < ch_name.size()) { + ch_name = ch_name.substr(pos + 1); + } + } else { + const size_t pos = ch_name.find(layer_name + '.'); + if (pos == std::string::npos) continue; + if (pos == 0) { + ch_name = ch_name.substr(layer_name.size() + 1); + } + } + LayerChannel ch(size_t(c), ch_name); + channels.push_back(ch); + } } } // namespace tinyexr +int EXRLayers(const char *filename, const char **layer_names[], int *num_layers, + const char **err) { + EXRVersion exr_version; + EXRHeader exr_header; + InitEXRHeader(&exr_header); + + { + int ret = ParseEXRVersionFromFile(&exr_version, filename); + if (ret != TINYEXR_SUCCESS) { + tinyexr::SetErrorMessage("Invalid EXR header.", err); + return ret; + } + + if (exr_version.multipart || exr_version.non_image) { + tinyexr::SetErrorMessage( + "Loading multipart or DeepImage is not supported in LoadEXR() API", + err); + return TINYEXR_ERROR_INVALID_DATA; // @fixme. + } + } + + int ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, err); + if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); + return ret; + } + + std::vector layer_vec; + tinyexr::GetLayers(exr_header, layer_vec); + + (*num_layers) = int(layer_vec.size()); + (*layer_names) = static_cast( + malloc(sizeof(const char *) * static_cast(layer_vec.size()))); + for (size_t c = 0; c < static_cast(layer_vec.size()); c++) { +#ifdef _MSC_VER + (*layer_names)[c] = _strdup(layer_vec[c].c_str()); +#else + (*layer_names)[c] = strdup(layer_vec[c].c_str()); +#endif + } + + FreeEXRHeader(&exr_header); + return TINYEXR_SUCCESS; +} + int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, const char **err) { + return LoadEXRWithLayer(out_rgba, width, height, filename, + /* layername */ NULL, err); +} + +int LoadEXRWithLayer(float **out_rgba, int *width, int *height, + const char *filename, const char *layername, + const char **err) { if (out_rgba == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXR()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -10812,13 +11622,17 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, { int ret = ParseEXRVersionFromFile(&exr_version, filename); if (ret != TINYEXR_SUCCESS) { + std::stringstream ss; + ss << "Failed to open EXR file or read version info from EXR file. code(" + << ret << ")"; + tinyexr::SetErrorMessage(ss.str(), err); return ret; } if (exr_version.multipart || exr_version.non_image) { - if (err) { - (*err) = "Loading multipart or DeepImage is not supported yet.\n"; - } + tinyexr::SetErrorMessage( + "Loading multipart or DeepImage is not supported in LoadEXR() API", + err); return TINYEXR_ERROR_INVALID_DATA; // @fixme. } } @@ -10826,6 +11640,7 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, { int ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, err); if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); return ret; } } @@ -10837,13 +11652,281 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, } } + // TODO: Probably limit loading to layers (channels) selected by layer index { int ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename, err); if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); return ret; } } + // RGBA + int idxR = -1; + int idxG = -1; + int idxB = -1; + int idxA = -1; + + std::vector layer_names; + tinyexr::GetLayers(exr_header, layer_names); + + std::vector channels; + tinyexr::ChannelsInLayer( + exr_header, layername == NULL ? "" : std::string(layername), channels); + + if (channels.size() < 1) { + tinyexr::SetErrorMessage("Layer Not Found", err); + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_LAYER_NOT_FOUND; + } + + size_t ch_count = channels.size() < 4 ? channels.size() : 4; + for (size_t c = 0; c < ch_count; c++) { + const tinyexr::LayerChannel &ch = channels[c]; + + if (ch.name == "R") { + idxR = int(ch.index); + } else if (ch.name == "G") { + idxG = int(ch.index); + } else if (ch.name == "B") { + idxB = int(ch.index); + } else if (ch.name == "A") { + idxA = int(ch.index); + } + } + + if (channels.size() == 1) { + int chIdx = int(channels.front().index); + // Grayscale channel only. + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + + if (exr_header.tiled) { + for (int it = 0; it < exr_image.num_tiles; it++) { + for (int j = 0; j < exr_header.tile_size_y; j++) { + for (int i = 0; i < exr_header.tile_size_x; i++) { + const int ii = exr_image.tiles[it].offset_x * + static_cast(exr_header.tile_size_x) + + i; + const int jj = exr_image.tiles[it].offset_y * + static_cast(exr_header.tile_size_y) + + j; + const int idx = ii + jj * static_cast(exr_image.width); + + // out of region check. + if (ii >= exr_image.width) { + continue; + } + if (jj >= exr_image.height) { + continue; + } + const int srcIdx = i + j * exr_header.tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[chIdx][srcIdx]; + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[chIdx][srcIdx]; + } + } + } + } else { + for (int i = 0; i < exr_image.width * exr_image.height; i++) { + const float val = + reinterpret_cast(exr_image.images)[chIdx][i]; + (*out_rgba)[4 * i + 0] = val; + (*out_rgba)[4 * i + 1] = val; + (*out_rgba)[4 * i + 2] = val; + (*out_rgba)[4 * i + 3] = val; + } + } + } else { + // Assume RGB(A) + + if (idxR == -1) { + tinyexr::SetErrorMessage("R channel not found", err); + + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxG == -1) { + tinyexr::SetErrorMessage("G channel not found", err); + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + if (idxB == -1) { + tinyexr::SetErrorMessage("B channel not found", err); + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + return TINYEXR_ERROR_INVALID_DATA; + } + + (*out_rgba) = reinterpret_cast( + malloc(4 * sizeof(float) * static_cast(exr_image.width) * + static_cast(exr_image.height))); + if (exr_header.tiled) { + for (int it = 0; it < exr_image.num_tiles; it++) { + for (int j = 0; j < exr_header.tile_size_y; j++) { + for (int i = 0; i < exr_header.tile_size_x; i++) { + const int ii = + exr_image.tiles[it].offset_x * exr_header.tile_size_x + i; + const int jj = + exr_image.tiles[it].offset_y * exr_header.tile_size_y + j; + const int idx = ii + jj * exr_image.width; + + // out of region check. + if (ii >= exr_image.width) { + continue; + } + if (jj >= exr_image.height) { + continue; + } + const int srcIdx = i + j * exr_header.tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[idxR][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[idxG][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[idxB][srcIdx]; + if (idxA != -1) { + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[idxA][srcIdx]; + } else { + (*out_rgba)[4 * idx + 3] = 1.0; + } + } + } + } + } else { + for (int i = 0; i < exr_image.width * exr_image.height; i++) { + (*out_rgba)[4 * i + 0] = + reinterpret_cast(exr_image.images)[idxR][i]; + (*out_rgba)[4 * i + 1] = + reinterpret_cast(exr_image.images)[idxG][i]; + (*out_rgba)[4 * i + 2] = + reinterpret_cast(exr_image.images)[idxB][i]; + if (idxA != -1) { + (*out_rgba)[4 * i + 3] = + reinterpret_cast(exr_image.images)[idxA][i]; + } else { + (*out_rgba)[4 * i + 3] = 1.0; + } + } + } + } + + (*width) = exr_image.width; + (*height) = exr_image.height; + + FreeEXRHeader(&exr_header); + FreeEXRImage(&exr_image); + + return TINYEXR_SUCCESS; +} + +int IsEXR(const char *filename) { + EXRVersion exr_version; + + int ret = ParseEXRVersionFromFile(&exr_version, filename); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + return TINYEXR_SUCCESS; +} + +int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, + const unsigned char *memory, size_t size, + const char **err) { + if (memory == NULL || exr_header == NULL) { + tinyexr::SetErrorMessage( + "Invalid argument. `memory` or `exr_header` argument is null in " + "ParseEXRHeaderFromMemory()", + err); + + // Invalid argument + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage("Insufficient header/data size.\n", err); + return TINYEXR_ERROR_INVALID_DATA; + } + + const unsigned char *marker = memory + tinyexr::kEXRVersionSize; + size_t marker_size = size - tinyexr::kEXRVersionSize; + + tinyexr::HeaderInfo info; + info.clear(); + + std::string err_str; + int ret = ParseEXRHeader(&info, NULL, version, &err_str, marker, marker_size); + + if (ret != TINYEXR_SUCCESS) { + if (err && !err_str.empty()) { + tinyexr::SetErrorMessage(err_str, err); + } + } + + ConvertHeader(exr_header, info); + + // transfoer `tiled` from version. + exr_header->tiled = version->tiled; + + return ret; +} + +int LoadEXRFromMemory(float **out_rgba, int *width, int *height, + const unsigned char *memory, size_t size, + const char **err) { + if (out_rgba == NULL || memory == NULL) { + tinyexr::SetErrorMessage("Invalid argument for LoadEXRFromMemory", err); + return TINYEXR_ERROR_INVALID_ARGUMENT; + } + + EXRVersion exr_version; + EXRImage exr_image; + EXRHeader exr_header; + + InitEXRHeader(&exr_header); + + int ret = ParseEXRVersionFromMemory(&exr_version, memory, size); + if (ret != TINYEXR_SUCCESS) { + std::stringstream ss; + ss << "Failed to parse EXR version. code(" << ret << ")"; + tinyexr::SetErrorMessage(ss.str(), err); + return ret; + } + + ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, memory, size, err); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + + // Read HALF channel as FLOAT. + for (int i = 0; i < exr_header.num_channels; i++) { + if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { + exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; + } + } + + InitEXRImage(&exr_image); + ret = LoadEXRImageFromMemory(&exr_image, &exr_header, memory, size, err); + if (ret != TINYEXR_SUCCESS) { + return ret; + } + // RGBA int idxR = -1; int idxG = -1; @@ -10861,46 +11944,72 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, } } - if ((idxA == 0) && (idxR == -1) && (idxG == -1) && (idxB == -1)) { - // Alpha channel only. + // TODO(syoyo): Refactor removing same code as used in LoadEXR(). + if (exr_header.num_channels == 1) { + // Grayscale channel only. - if (exr_header.tiled) { - // todo.implement this - } (*out_rgba) = reinterpret_cast( malloc(4 * sizeof(float) * static_cast(exr_image.width) * static_cast(exr_image.height))); - for (int i = 0; i < exr_image.width * exr_image.height; i++) { - const float val = reinterpret_cast(exr_image.images)[0][i]; - (*out_rgba)[4 * i + 0] = val; - (*out_rgba)[4 * i + 1] = val; - (*out_rgba)[4 * i + 2] = val; - (*out_rgba)[4 * i + 3] = val; + + if (exr_header.tiled) { + for (int it = 0; it < exr_image.num_tiles; it++) { + for (int j = 0; j < exr_header.tile_size_y; j++) { + for (int i = 0; i < exr_header.tile_size_x; i++) { + const int ii = + exr_image.tiles[it].offset_x * exr_header.tile_size_x + i; + const int jj = + exr_image.tiles[it].offset_y * exr_header.tile_size_y + j; + const int idx = ii + jj * exr_image.width; + + // out of region check. + if (ii >= exr_image.width) { + continue; + } + if (jj >= exr_image.height) { + continue; + } + const int srcIdx = i + j * exr_header.tile_size_x; + unsigned char **src = exr_image.tiles[it].images; + (*out_rgba)[4 * idx + 0] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 1] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 2] = + reinterpret_cast(src)[0][srcIdx]; + (*out_rgba)[4 * idx + 3] = + reinterpret_cast(src)[0][srcIdx]; + } + } + } + } else { + for (int i = 0; i < exr_image.width * exr_image.height; i++) { + const float val = reinterpret_cast(exr_image.images)[0][i]; + (*out_rgba)[4 * i + 0] = val; + (*out_rgba)[4 * i + 1] = val; + (*out_rgba)[4 * i + 2] = val; + (*out_rgba)[4 * i + 3] = val; + } } + } else { - // Assume RGB(A) + // TODO(syoyo): Support non RGBA image. if (idxR == -1) { - if (err) { - (*err) = "R channel not found\n"; - } + tinyexr::SetErrorMessage("R channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } if (idxG == -1) { - if (err) { - (*err) = "G channel not found\n"; - } + tinyexr::SetErrorMessage("G channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } if (idxB == -1) { - if (err) { - (*err) = "B channel not found\n"; - } + tinyexr::SetErrorMessage("B channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } @@ -10908,6 +12017,7 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, (*out_rgba) = reinterpret_cast( malloc(4 * sizeof(float) * static_cast(exr_image.width) * static_cast(exr_image.height))); + if (exr_header.tiled) { for (int it = 0; it < exr_image.num_tiles; it++) { for (int j = 0; j < exr_header.tile_size_y; j++) @@ -10968,177 +12078,32 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, return TINYEXR_SUCCESS; } -int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, - const unsigned char *memory, size_t size, - const char **err) { - if (memory == NULL || exr_header == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } - - // Invalid argument - return TINYEXR_ERROR_INVALID_ARGUMENT; - } - - if (size < tinyexr::kEXRVersionSize) { - return TINYEXR_ERROR_INVALID_DATA; - } - - const unsigned char *marker = memory + tinyexr::kEXRVersionSize; - size_t marker_size = size - tinyexr::kEXRVersionSize; - - tinyexr::HeaderInfo info; - info.clear(); - - std::string err_str; - int ret = ParseEXRHeader(&info, NULL, version, &err_str, marker, marker_size); - - if (ret != TINYEXR_SUCCESS) { - if (err && !err_str.empty()) { -#ifdef _WIN32 - (*err) = _strdup(err_str.c_str()); // May leak -#else - (*err) = strdup(err_str.c_str()); // May leak -#endif - } - } - - ConvertHeader(exr_header, info); - - // transfoer `tiled` from version. - exr_header->tiled = version->tiled; - - return ret; -} - -int LoadEXRFromMemory(float **out_rgba, int *width, int *height, - const unsigned char *memory, size_t size, - const char **err) { - if (out_rgba == NULL || memory == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } - return TINYEXR_ERROR_INVALID_ARGUMENT; - } - - EXRVersion exr_version; - EXRImage exr_image; - EXRHeader exr_header; - - InitEXRHeader(&exr_header); - - int ret = ParseEXRVersionFromMemory(&exr_version, memory, size); - if (ret != TINYEXR_SUCCESS) { - return ret; - } - - ret = ParseEXRHeaderFromMemory(&exr_header, &exr_version, memory, size, err); - if (ret != TINYEXR_SUCCESS) { - return ret; - } - - // Read HALF channel as FLOAT. - for (int i = 0; i < exr_header.num_channels; i++) { - if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) { - exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; - } - } - - InitEXRImage(&exr_image); - ret = LoadEXRImageFromMemory(&exr_image, &exr_header, memory, size, err); - if (ret != TINYEXR_SUCCESS) { - return ret; - } - - // RGBA - int idxR = -1; - int idxG = -1; - int idxB = -1; - int idxA = -1; - for (int c = 0; c < exr_header.num_channels; c++) { - if (strcmp(exr_header.channels[c].name, "R") == 0) { - idxR = c; - } else if (strcmp(exr_header.channels[c].name, "G") == 0) { - idxG = c; - } else if (strcmp(exr_header.channels[c].name, "B") == 0) { - idxB = c; - } else if (strcmp(exr_header.channels[c].name, "A") == 0) { - idxA = c; - } - } - - if (idxR == -1) { - if (err) { - (*err) = "R channel not found\n"; - } - - // @todo { free exr_image } - return TINYEXR_ERROR_INVALID_DATA; - } - - if (idxG == -1) { - if (err) { - (*err) = "G channel not found\n"; - } - // @todo { free exr_image } - return TINYEXR_ERROR_INVALID_DATA; - } - - if (idxB == -1) { - if (err) { - (*err) = "B channel not found\n"; - } - // @todo { free exr_image } - return TINYEXR_ERROR_INVALID_DATA; - } - - (*out_rgba) = reinterpret_cast( - malloc(4 * sizeof(float) * static_cast(exr_image.width) * - static_cast(exr_image.height))); - - for (int i = 0; i < exr_image.width * exr_image.height; i++) { - (*out_rgba)[4 * i + 0] = - reinterpret_cast(exr_image.images)[idxR][i]; - (*out_rgba)[4 * i + 1] = - reinterpret_cast(exr_image.images)[idxG][i]; - (*out_rgba)[4 * i + 2] = - reinterpret_cast(exr_image.images)[idxB][i]; - if (idxA != -1) { - (*out_rgba)[4 * i + 3] = - reinterpret_cast(exr_image.images)[idxA][i]; - } else { - (*out_rgba)[4 * i + 3] = 1.0; - } - } - - (*width) = exr_image.width; - (*height) = exr_image.height; - - FreeEXRHeader(&exr_header); - FreeEXRImage(&exr_image); - - return TINYEXR_SUCCESS; -} - int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, const char *filename, const char **err) { if (exr_image == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "rb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + // TODO(syoyo): return wfopen_s erro code + return TINYEXR_ERROR_CANT_OPEN_FILE; + } #else - FILE *fp = fopen(filename, "rb"); + // Unknown compiler + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -11148,6 +12113,12 @@ int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, filesize = static_cast(ftell(fp)); fseek(fp, 0, SEEK_SET); + if (filesize < 16) { + tinyexr::SetErrorMessage("File size too short " + std::string(filename), + err); + return TINYEXR_ERROR_INVALID_FILE; + } + std::vector buf(filesize); // @todo { use mmap } { size_t ret; @@ -11166,16 +12137,13 @@ int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header, const char **err) { if (exr_image == NULL || memory == NULL || (size < tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromMemory", + err); return TINYEXR_ERROR_INVALID_ARGUMENT; } if (exr_header->header_len == 0) { - if (err) { - (*err) = "EXRHeader is not initialized."; - } + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -11192,26 +12160,22 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, unsigned char **memory_out, const char **err) { if (exr_image == NULL || memory_out == NULL || exr_header->compression_type < 0) { - if (err) { - (*err) = "Invalid argument."; - } - return 0; // @fixme + tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToMemory", err); + return 0; } #if !TINYEXR_USE_PIZ if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "PIZ compression is not supported in this build."; - } + tinyexr::SetErrorMessage("PIZ compression is not supported in this build", + err); return 0; } #endif #if !TINYEXR_USE_ZFP if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { - if (err) { - (*err) = "ZFP compression is not supported in this build."; - } + tinyexr::SetErrorMessage("ZFP compression is not supported in this build", + err); return 0; } #endif @@ -11219,9 +12183,8 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, #if TINYEXR_USE_ZFP for (size_t i = 0; i < static_cast(exr_header->num_channels); i++) { if (exr_header->requested_pixel_types[i] != TINYEXR_PIXELTYPE_FLOAT) { - if (err) { - (*err) = "Pixel type must be FLOAT for ZFP compression."; - } + tinyexr::SetErrorMessage("Pixel type must be FLOAT for ZFP compression", + err); return 0; } } @@ -11287,7 +12250,7 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, { int comp = exr_header->compression_type; - tinyexr::swap4(reinterpret_cast(&comp)); + tinyexr::swap4(&comp); tinyexr::WriteAttributeToMemory( &memory, "compression", "compression", reinterpret_cast(&comp), 1); @@ -11295,10 +12258,10 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, { int data[4] = {0, 0, exr_image->width - 1, exr_image->height - 1}; - tinyexr::swap4(reinterpret_cast(&data[0])); - tinyexr::swap4(reinterpret_cast(&data[1])); - tinyexr::swap4(reinterpret_cast(&data[2])); - tinyexr::swap4(reinterpret_cast(&data[3])); + tinyexr::swap4(&data[0]); + tinyexr::swap4(&data[1]); + tinyexr::swap4(&data[2]); + tinyexr::swap4(&data[3]); tinyexr::WriteAttributeToMemory( &memory, "dataWindow", "box2i", reinterpret_cast(data), sizeof(int) * 4); @@ -11315,7 +12278,7 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, { float aspectRatio = 1.0f; - tinyexr::swap4(reinterpret_cast(&aspectRatio)); + tinyexr::swap4(&aspectRatio); tinyexr::WriteAttributeToMemory( &memory, "pixelAspectRatio", "float", reinterpret_cast(&aspectRatio), sizeof(float)); @@ -11323,8 +12286,8 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, { float center[2] = {0.0f, 0.0f}; - tinyexr::swap4(reinterpret_cast(¢er[0])); - tinyexr::swap4(reinterpret_cast(¢er[1])); + tinyexr::swap4(¢er[0]); + tinyexr::swap4(¢er[1]); tinyexr::WriteAttributeToMemory( &memory, "screenWindowCenter", "v2f", reinterpret_cast(center), 2 * sizeof(float)); @@ -11332,7 +12295,7 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, { float w = static_cast(exr_image->width); - tinyexr::swap4(reinterpret_cast(&w)); + tinyexr::swap4(&w); tinyexr::WriteAttributeToMemory(&memory, "screenWindowWidth", "float", reinterpret_cast(&w), sizeof(float)); @@ -11369,8 +12332,6 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, sizeof( tinyexr::tinyexr_int64); // sizeof(header) + sizeof(offsetTable) - std::vector data; - std::vector > data_list( static_cast(num_blocks)); std::vector channel_offset_list( @@ -11401,9 +12362,10 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, // Use ZFP compression parameter from custom attributes(if such a parameter // exists) { + std::string e; bool ret = tinyexr::FindZFPCompressionParam( &zfp_compression_param, exr_header->custom_attributes, - exr_header->num_custom_attributes); + exr_header->num_custom_attributes, &e); if (!ret) { // Use predefined compression parameter. @@ -11413,9 +12375,11 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } #endif + // TODO(LTE): C++11 thread + // Use signed int since some OpenMP compiler doesn't allow unsigned type for // `parallel for` -#ifdef _OPENMP +#if TINYEXR_USE_OPENMP #pragma omp parallel for #endif for (int i = 0; i < num_blocks; i++) { @@ -11431,6 +12395,11 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { tinyexr::FP16 h16; h16.u = reinterpret_cast( @@ -11438,32 +12407,29 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, tinyexr::FP32 f32 = half_to_float(h16); - tinyexr::swap4(reinterpret_cast(&f32.f)); + tinyexr::swap4(&f32.f); - // Assume increasing Y - float *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); - line_ptr[x] = f32.f; + // line_ptr[x] = f32.f; + tinyexr::cpy4(line_ptr + x, &(f32.f)); } } } else if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { unsigned short val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; tinyexr::swap2(&val); - // Assume increasing Y - unsigned short *line_ptr = reinterpret_cast( - &buf.at(static_cast(pixel_data_size * y * - exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); - line_ptr[x] = val; + // line_ptr[x] = val; + tinyexr::cpy2(line_ptr + x, &val); } } } else { @@ -11473,6 +12439,12 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { tinyexr::FP32 f32; f32.f = reinterpret_cast( @@ -11483,30 +12455,26 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, tinyexr::swap2(reinterpret_cast(&h16.u)); - // Assume increasing Y - unsigned short *line_ptr = reinterpret_cast( - &buf.at(static_cast(pixel_data_size * y * - exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); - line_ptr[x] = h16.u; + // line_ptr[x] = h16.u; + tinyexr::cpy2(line_ptr + x, &(h16.u)); } } } else if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { float val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; - tinyexr::swap4(reinterpret_cast(&val)); + tinyexr::swap4(&val); - // Assume increasing Y - float *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); - line_ptr[x] = val; + // line_ptr[x] = val; + tinyexr::cpy4(line_ptr + x, &val); } } } else { @@ -11514,18 +12482,18 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } } else if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_UINT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned int *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { unsigned int val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; tinyexr::swap4(&val); - // Assume increasing Y - unsigned int *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); - line_ptr[x] = val; + // line_ptr[x] = val; + tinyexr::cpy4(line_ptr + x, &val); } } } @@ -11605,9 +12573,9 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { #if TINYEXR_USE_PIZ unsigned int bufLen = - 1024 + static_cast( - 1.2 * static_cast( - buf.size())); // @fixme { compute good bound. } + 8192 + static_cast( + 2 * static_cast( + buf.size())); // @fixme { compute good bound. } std::vector block(bufLen); unsigned int outSize = static_cast(block.size()); @@ -11666,13 +12634,12 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } // omp parallel for (size_t i = 0; i < static_cast(num_blocks); i++) { - data.insert(data.end(), data_list[i].begin(), data_list[i].end()); - offsets[i] = offset; tinyexr::swap8(reinterpret_cast(&offsets[i])); offset += data_list[i].size(); } + size_t totalSize = static_cast(offset); { memory.insert( memory.end(), reinterpret_cast(&offsets.at(0)), @@ -11680,93 +12647,122 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, sizeof(tinyexr::tinyexr_uint64) * static_cast(num_blocks)); } - { memory.insert(memory.end(), data.begin(), data.end()); } + if (memory.size() == 0) { + tinyexr::SetErrorMessage("Output memory size is zero", err); + return 0; + } - assert(memory.size() > 0); - - (*memory_out) = static_cast(malloc(memory.size())); + (*memory_out) = static_cast(malloc(totalSize)); memcpy((*memory_out), &memory.at(0), memory.size()); + unsigned char *memory_ptr = *memory_out + memory.size(); - return memory.size(); // OK + for (size_t i = 0; i < static_cast(num_blocks); i++) { + memcpy(memory_ptr, &data_list[i].at(0), data_list[i].size()); + memory_ptr += data_list[i].size(); + } + + return totalSize; // OK } int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header, const char *filename, const char **err) { if (exr_image == NULL || filename == NULL || exr_header->compression_type < 0) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } #if !TINYEXR_USE_PIZ if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "PIZ compression is not supported in this build."; - } - return 0; + tinyexr::SetErrorMessage("PIZ compression is not supported in this build", + err); + return TINYEXR_ERROR_UNSUPPORTED_FEATURE; } #endif #if !TINYEXR_USE_ZFP if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { - if (err) { - (*err) = "ZFP compression is not supported in this build."; - } - return 0; + tinyexr::SetErrorMessage("ZFP compression is not supported in this build", + err); + return TINYEXR_ERROR_UNSUPPORTED_FEATURE; } #endif -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "wb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"wb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } #else - FILE *fp = fopen(filename, "wb"); + // Unknown compiler + fp = fopen(filename, "wb"); +#endif +#else + fp = fopen(filename, "wb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot write a file."; - } - return TINYEXR_ERROR_CANT_OPEN_FILE; + tinyexr::SetErrorMessage("Cannot write a file: " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_WRITE_FILE; } unsigned char *mem = NULL; size_t mem_size = SaveEXRImageToMemory(exr_image, exr_header, &mem, err); + if (mem_size == 0) { + return TINYEXR_ERROR_SERIALZATION_FAILED; + } + size_t written_size = 0; if ((mem_size > 0) && mem) { - fwrite(mem, 1, mem_size, fp); + written_size = fwrite(mem, 1, mem_size, fp); } free(mem); fclose(fp); + if (written_size != mem_size) { + tinyexr::SetErrorMessage("Cannot write a file", err); + return TINYEXR_ERROR_CANT_WRITE_FILE; + } + return TINYEXR_SUCCESS; } int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (deep_image == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadDeepEXR", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _MSC_VER +#ifdef _WIN32 FILE *fp = NULL; - errno_t errcode = fopen_s(&fp, filename, "rb"); - if ((0 != errcode) || (!fp)) { - if (err) { - (*err) = "Cannot read file."; - } +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot read a file " + std::string(filename), + err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } +#else + // Unknown compiler + fp = fopen(filename, "rb"); +#endif + if (!fp) { + tinyexr::SetErrorMessage("Cannot read a file " + std::string(filename), + err); return TINYEXR_ERROR_CANT_OPEN_FILE; } #else FILE *fp = fopen(filename, "rb"); if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read a file " + std::string(filename), + err); return TINYEXR_ERROR_CANT_OPEN_FILE; } #endif @@ -11779,9 +12775,8 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (filesize == 0) { fclose(fp); - if (err) { - (*err) = "File size is zero."; - } + tinyexr::SetErrorMessage("File size is zero : " + std::string(filename), + err); return TINYEXR_ERROR_INVALID_FILE; } @@ -11802,9 +12797,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { const char header[] = {0x76, 0x2f, 0x31, 0x01}; if (memcmp(marker, header, 4) != 0) { - if (err) { - (*err) = "Invalid magic number."; - } + tinyexr::SetErrorMessage("Invalid magic number", err); return TINYEXR_ERROR_INVALID_MAGIC_NUMBER; } marker += 4; @@ -11815,9 +12808,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // ver 2.0, scanline, deep bit on(0x800) // must be [2, 0, 0, 0] if (marker[0] != 2 || marker[1] != 8 || marker[2] != 0 || marker[3] != 0) { - if (err) { - (*err) = "Unsupported version or scanline."; - } + tinyexr::SetErrorMessage("Unsupported version or scanline", err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -11850,6 +12841,9 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { size_t marker_size; if (!tinyexr::ReadAttribute(&attr_name, &attr_type, &data, &marker_size, marker, size)) { + std::stringstream ss; + ss << "Failed to parse attribute\n"; + tinyexr::SetErrorMessage(ss.str(), err); return TINYEXR_ERROR_INVALID_DATA; } marker += marker_size; @@ -11858,9 +12852,9 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (attr_name.compare("compression") == 0) { compression_type = data[0]; if (compression_type > TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "Unsupported compression type."; - } + std::stringstream ss; + ss << "Unsupported compression type : " << compression_type; + tinyexr::SetErrorMessage(ss.str(), err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -11877,18 +12871,14 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // ySampling: int if (!tinyexr::ReadChannelInfo(channels, data)) { - if (err) { - (*err) = "Failed to parse channel info."; - } + tinyexr::SetErrorMessage("Failed to parse channel info", err); return TINYEXR_ERROR_INVALID_DATA; } num_channels = static_cast(channels.size()); if (num_channels < 1) { - if (err) { - (*err) = "Invalid channels format."; - } + tinyexr::SetErrorMessage("Invalid channels format", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -11897,10 +12887,10 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { memcpy(&dy, &data.at(4), sizeof(int)); memcpy(&dw, &data.at(8), sizeof(int)); memcpy(&dh, &data.at(12), sizeof(int)); - tinyexr::swap4(reinterpret_cast(&dx)); - tinyexr::swap4(reinterpret_cast(&dy)); - tinyexr::swap4(reinterpret_cast(&dw)); - tinyexr::swap4(reinterpret_cast(&dh)); + tinyexr::swap4(&dx); + tinyexr::swap4(&dy); + tinyexr::swap4(&dw); + tinyexr::swap4(&dh); } else if (attr_name.compare("displayWindow") == 0) { int x; @@ -11911,10 +12901,10 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { memcpy(&y, &data.at(4), sizeof(int)); memcpy(&w, &data.at(8), sizeof(int)); memcpy(&h, &data.at(12), sizeof(int)); - tinyexr::swap4(reinterpret_cast(&x)); - tinyexr::swap4(reinterpret_cast(&y)); - tinyexr::swap4(reinterpret_cast(&w)); - tinyexr::swap4(reinterpret_cast(&h)); + tinyexr::swap4(&x); + tinyexr::swap4(&y); + tinyexr::swap4(&w); + tinyexr::swap4(&h); } } @@ -11960,9 +12950,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { #endif // OK } else { - if (err) { - (*err) = "Unsupported format."; - } + tinyexr::SetErrorMessage("Unsupported compression format", err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -12004,7 +12992,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { memcpy(&unpackedSampleDataSize, data_ptr + 20, sizeof(tinyexr::tinyexr_int64)); - tinyexr::swap4(reinterpret_cast(&line_no)); + tinyexr::swap4(&line_no); tinyexr::swap8( reinterpret_cast(&packedOffsetTableSize)); tinyexr::swap8( @@ -12090,8 +13078,10 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (channels[c].pixel_type == 0) { // UINT for (size_t x = 0; x < static_cast(samples_per_line); x++) { - unsigned int ui = *reinterpret_cast( + unsigned int ui; + unsigned int *src_ptr = reinterpret_cast( &sample_data.at(size_t(data_offset) + x * sizeof(int))); + tinyexr::cpy4(&ui, src_ptr); deep_image->image[c][y][x] = static_cast(ui); // @fixme } data_offset += @@ -12099,16 +13089,19 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { } else if (channels[c].pixel_type == 1) { // half for (size_t x = 0; x < static_cast(samples_per_line); x++) { tinyexr::FP16 f16; - f16.u = *reinterpret_cast( + const unsigned short *src_ptr = reinterpret_cast( &sample_data.at(size_t(data_offset) + x * sizeof(short))); + tinyexr::cpy2(&(f16.u), src_ptr); tinyexr::FP32 f32 = half_to_float(f16); deep_image->image[c][y][x] = f32.f; } data_offset += sizeof(short) * static_cast(samples_per_line); } else { // float for (size_t x = 0; x < static_cast(samples_per_line); x++) { - float f = *reinterpret_cast( + float f; + const float *src_ptr = reinterpret_cast( &sample_data.at(size_t(data_offset) + x * sizeof(float))); + tinyexr::cpy4(&f, src_ptr); deep_image->image[c][y][x] = f; } data_offset += sizeof(float) * static_cast(samples_per_line); @@ -12149,6 +13142,13 @@ void InitEXRImage(EXRImage *exr_image) { exr_image->num_tiles = 0; } +void FreeEXRErrorMessage(const char *msg) { + if (msg) { + free(reinterpret_cast(const_cast(msg))); + } + return; +} + void InitEXRHeader(EXRHeader *exr_header) { if (exr_header == NULL) { return; @@ -12180,6 +13180,10 @@ int FreeEXRHeader(EXRHeader *exr_header) { } } + if (exr_header->custom_attributes) { + free(exr_header->custom_attributes); + } + return TINYEXR_SUCCESS; } @@ -12218,22 +13222,29 @@ int FreeEXRImage(EXRImage *exr_image) { int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, const char *filename, const char **err) { if (exr_header == NULL || exr_version == NULL || filename == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for ParseEXRHeaderFromFile", + err); return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "rb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_INVALID_FILE; + } #else - FILE *fp = fopen(filename, "rb"); + // Unknown compiler + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -12251,9 +13262,8 @@ int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, fclose(fp); if (ret != filesize) { - if (err) { - (*err) = "fread error."; - } + tinyexr::SetErrorMessage("fread() error on " + std::string(filename), + err); return TINYEXR_ERROR_INVALID_FILE; } } @@ -12270,10 +13280,13 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, if (memory == NULL || exr_headers == NULL || num_headers == NULL || exr_version == NULL) { // Invalid argument + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromMemory", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage("Data size too short", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12292,13 +13305,7 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, marker, marker_size); if (ret != TINYEXR_SUCCESS) { - if (err) { -#ifdef _WIN32 - (*err) = _strdup(err_str.c_str()); // may leak -#else - (*err) = strdup(err_str.c_str()); // may leak -#endif - } + tinyexr::SetErrorMessage(err_str, err); return ret; } @@ -12309,9 +13316,8 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, // `chunkCount` must exist in the header. if (info.chunk_count == 0) { - if (err) { - (*err) = "`chunkCount' attribute is not found in the header."; - } + tinyexr::SetErrorMessage( + "`chunkCount' attribute is not found in the header.", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12346,22 +13352,29 @@ int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, const char *filename, const char **err) { if (exr_headers == NULL || num_headers == NULL || exr_version == NULL || filename == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromFile()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "rb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_INVALID_FILE; + } #else - FILE *fp = fopen(filename, "rb"); + // Unknown compiler + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -12379,9 +13392,7 @@ int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, fclose(fp); if (ret != filesize) { - if (err) { - (*err) = "fread error."; - } + tinyexr::SetErrorMessage("`fread' error. file may be corrupted.", err); return TINYEXR_ERROR_INVALID_FILE; } } @@ -12452,11 +13463,20 @@ int ParseEXRVersionFromFile(EXRVersion *version, const char *filename) { return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "rb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t err = _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (err != 0) { + // TODO(syoyo): return wfopen_s erro code + return TINYEXR_ERROR_CANT_OPEN_FILE; + } #else - FILE *fp = fopen(filename, "rb"); + // Unknown compiler + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); #endif if (!fp) { return TINYEXR_ERROR_CANT_OPEN_FILE; @@ -12490,9 +13510,8 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, const size_t size, const char **err) { if (exr_images == NULL || exr_headers == NULL || num_parts == 0 || memory == NULL || (size <= tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromMemory()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12500,9 +13519,7 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, size_t total_header_size = 0; for (unsigned int i = 0; i < num_parts; i++) { if (exr_headers[i]->header_len == 0) { - if (err) { - (*err) = "EXRHeader is not initialized."; - } + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12537,9 +13554,8 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, tinyexr::swap8(&offset); if (offset >= size) { - if (err) { - (*err) = "Invalid offset size."; - } + tinyexr::SetErrorMessage("Invalid offset size in EXR header chunks.", + err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12564,14 +13580,19 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, tinyexr::swap4(&part_no); if (part_no != i) { - assert(0); + tinyexr::SetErrorMessage("Invalid `part number' in EXR header chunks.", + err); return TINYEXR_ERROR_INVALID_DATA; } } + std::string e; int ret = tinyexr::DecodeChunk(&exr_images[i], exr_headers[i], offset_table, - memory, size); + memory, size, &e); if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } return ret; } } @@ -12584,22 +13605,29 @@ int LoadEXRMultipartImageFromFile(EXRImage *exr_images, unsigned int num_parts, const char *filename, const char **err) { if (exr_images == NULL || exr_headers == NULL || num_parts == 0) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } -#ifdef _WIN32 FILE *fp = NULL; - fopen_s(&fp, filename, "rb"); +#ifdef _WIN32 +#if defined(_MSC_VER) || defined(__MINGW32__) // MSVC, MinGW gcc or clang + errno_t errcode = + _wfopen_s(&fp, tinyexr::UTF8ToWchar(filename).c_str(), L"rb"); + if (errcode != 0) { + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); + return TINYEXR_ERROR_CANT_OPEN_FILE; + } #else - FILE *fp = fopen(filename, "rb"); + // Unknown compiler + fp = fopen(filename, "rb"); +#endif +#else + fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -12623,20 +13651,27 @@ int LoadEXRMultipartImageFromFile(EXRImage *exr_images, } int SaveEXR(const float *data, int width, int height, int components, - const int save_as_fp16, const char *outfilename) { + const int save_as_fp16, const char *outfilename, const char **err) { if ((components == 1) || components == 3 || components == 4) { // OK } else { + std::stringstream ss; + ss << "Unsupported component value : " << components << std::endl; + + tinyexr::SetErrorMessage(ss.str(), err); return TINYEXR_ERROR_INVALID_ARGUMENT; } - // Assume at least 16x16 pixels. - if (width < 16) return TINYEXR_ERROR_INVALID_ARGUMENT; - if (height < 16) return TINYEXR_ERROR_INVALID_ARGUMENT; - EXRHeader header; InitEXRHeader(&header); + if ((width < 16) && (height < 16)) { + // No compression for small image. + header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE; + } else { + header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP; + } + EXRImage image; InitEXRImage(&image); @@ -12742,8 +13777,7 @@ int SaveEXR(const float *data, int width, int height, int components, } } - const char *err; - int ret = SaveEXRImageToFile(&image, &header, outfilename, &err); + int ret = SaveEXRImageToFile(&image, &header, outfilename, err); if (ret != TINYEXR_SUCCESS) { return ret; } @@ -12760,5 +13794,5 @@ int SaveEXR(const float *data, int width, int height, int components, #pragma clang diagnostic pop #endif -#endif // TINYEXR_IMPLEMENTATION_DEIFNED +#endif // TINYEXR_IMPLEMENTATION_DEFINED #endif // TINYEXR_IMPLEMENTATION