From f69affebc9a963f0426f6f3d24339ea89dfd66e8 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Sun, 17 Dec 2017 21:42:04 -0400 Subject: [PATCH] Update tinyexr. --- src/libraries/tinyexr/tinyexr.h | 635 +++++++++++++++++------- src/modules/image/magpie/EXRHandler.cpp | 6 +- 2 files changed, 462 insertions(+), 179 deletions(-) mode change 100644 => 100755 src/libraries/tinyexr/tinyexr.h diff --git a/src/libraries/tinyexr/tinyexr.h b/src/libraries/tinyexr/tinyexr.h old mode 100644 new mode 100755 index 925f0d4b4..606c19756 --- a/src/libraries/tinyexr/tinyexr.h +++ b/src/libraries/tinyexr/tinyexr.h @@ -1,5 +1,5 @@ /* -Copyright (c) 2014 - 2016, Syoyo Fujita +Copyright (c) 2014 - 2017, Syoyo Fujita All rights reserved. Redistribution and use in source and binary forms, with or without @@ -9,7 +9,7 @@ modification, are permitted provided that the following conditions are met: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of the nor the + * Neither the name of the Syoyo Fujita nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -259,7 +259,8 @@ typedef struct _DeepImage { } DeepImage; // @deprecated { to be removed. } -// Loads single-frame OpenEXR image. Assume EXR image contains RGB(A) channels. +// 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` // 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 @@ -269,9 +270,14 @@ extern int LoadEXR(float **out_rgba, int *width, int *height, // @deprecated { to be removed. } // Saves single-frame OpenEXR image. Assume EXR image contains RGB(A) channels. -// components must be 3(RGB) or 4(RGBA). -// Result image format is: float x RGB(A) x width x hight -extern int SaveEXR(const float *data, int width, int height, int components, +// components must be 1(Grayscale), 3(RGB) or 4(RGBA). +// Input image format is: `float x width x height`, or `float x RGB(A) x width x +// hight` +// 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. +extern int SaveEXR(const float *data, const int width, const int height, + const int components, const int save_as_fp16, const char *filename); // Initialize EXRHeader struct @@ -335,7 +341,7 @@ extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header, // error extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header, const unsigned char *memory, - const char **err); + const size_t size, const char **err); // Loads multi-part OpenEXR image from a file. // Application must setup `ParseEXRMultipartHeaderFromFile` before calling this @@ -359,7 +365,7 @@ extern int LoadEXRMultipartImageFromMemory(EXRImage *images, const EXRHeader **headers, unsigned int num_parts, const unsigned char *memory, - const char **err); + const size_t size, const char **err); // Saves multi-channel, single-frame OpenEXR image to a file. // Returns negative value and may set error string in `err` when there's an @@ -401,18 +407,22 @@ extern int LoadDeepEXR(DeepImage *out_image, const char *filename, // For emscripten. // Loads single-frame OpenEXR image from memory. Assume EXR image contains // RGB(A) channels. -// `out_rgba` must have enough memory(at least sizeof(float) x 4(RGBA) x width x -// hight) // Returns negative value and may set error string in `err` when there's an // error -extern int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, - size_t size, const char **err); +extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, + const unsigned char *memory, size_t size, + const char **err); #ifdef __cplusplus } #endif +#endif // TINYEXR_H_ + #ifdef TINYEXR_IMPLEMENTATION +#ifndef TINYEXR_IMPLEMENTATION_DEIFNED +#define TINYEXR_IMPLEMENTATION_DEIFNED + #include #include #include @@ -423,8 +433,10 @@ extern int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, #include #include -// @todo { remove including tinyexr.h } -#include "tinyexr.h" +#if __cplusplus > 199711L +// C++11 +#include +#endif // __cplusplus > 199711L #ifdef _OPENMP #include @@ -432,7 +444,8 @@ extern int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, #if TINYEXR_USE_MINIZ #else -#include "zlib.h" +// Issue #46. Please include your own zlib-compatible API header before including `tinyexr.h` +//#include "zlib.h" #endif #if TINYEXR_USE_ZFP @@ -443,7 +456,6 @@ namespace tinyexr { #if __cplusplus > 199711L // C++11 -#include typedef uint64_t tinyexr_uint64; typedef int64_t tinyexr_int64; #else @@ -472,6 +484,10 @@ namespace miniz { #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wc++11-extensions" #pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wunused-function" +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" +#endif #endif /* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP @@ -731,7 +747,7 @@ namespace miniz { #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED -#include +//#include // Defines to completely disable specific portions of miniz.c: // If all macros here are defined the only functionality remaining will be @@ -749,7 +765,7 @@ namespace miniz { #define MINIZ_NO_TIME // Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. -//#define MINIZ_NO_ARCHIVE_APIS +#define MINIZ_NO_ARCHIVE_APIS // Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive // API's. @@ -779,7 +795,7 @@ namespace miniz { #endif #if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) -#include +//#include #endif #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ @@ -1800,8 +1816,8 @@ typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; -#include -#include +//#include +//#include #define MZ_ASSERT(x) assert(x) @@ -1901,11 +1917,11 @@ static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } -static void *def_realloc_func(void *opaque, void *address, size_t items, - size_t size) { - (void)opaque, (void)address, (void)items, (void)size; - return MZ_REALLOC(address, items * size); -} +// static void *def_realloc_func(void *opaque, void *address, size_t items, +// size_t size) { +// (void)opaque, (void)address, (void)items, (void)size; +// return MZ_REALLOC(address, items * size); +//} const char *mz_version(void) { return MZ_VERSION; } @@ -2877,8 +2893,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; @@ -3525,9 +3542,10 @@ static int tdefl_flush_block(tdefl_compressor *d, int flush) { mz_uint saved_bit_buf, saved_bits_in; mz_uint8 *pSaved_output_buf; mz_bool comp_block_succeeded = MZ_FALSE; - int n, use_raw_block = - ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && - (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + int n, + use_raw_block = + ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && + (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) @@ -3557,8 +3575,9 @@ static int tdefl_flush_block(tdefl_compressor *d, int flush) { if (!use_raw_block) comp_block_succeeded = - tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || - (d->m_total_lz_bytes < 48)); + tdefl_compress_block(d, + (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || + (d->m_total_lz_bytes < 48)); // If the block gets expanded, forget the current contents of the output // buffer and send a raw block instead. @@ -4494,6 +4513,7 @@ void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, // ------------------- .ZIP archive reading #ifndef MINIZ_NO_ARCHIVE_APIS +#error "No arvhive APIs" #ifdef MINIZ_NO_STDIO #define MZ_FILE void * @@ -5202,9 +5222,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'; @@ -6862,6 +6883,12 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, #ifdef __clang__ #pragma clang diagnostic pop #endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + } #else @@ -6887,6 +6914,8 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, // return bint.c[0] == 1; //} +static const int kEXRVersionSize = 8; + static void swap2(unsigned short *val) { #ifdef MINIZ_LITTLE_ENDIAN (void)val; @@ -7323,11 +7352,23 @@ static void CompressZip(unsigned char *dst, compressedSize = outSize; #endif + + // Use uncompressed data when compressed data is larger than uncompressed. + // (Issue 40) + if (compressedSize >= src_size) { + compressedSize = src_size; + memcpy(dst, src, src_size); + } } static void DecompressZip(unsigned char *dst, unsigned long *uncompressed_size /* inout */, 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; + } std::vector tmpBuf(*uncompressed_size); #if TINYEXR_USE_MINIZ @@ -7387,6 +7428,22 @@ static void DecompressZip(unsigned char *dst, #pragma clang diagnostic ignored "-Wsign-conversion" #endif +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) // nonstandard extension used : non-constant + // aggregate initializer (also supported by GNU + // 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 : 4996) // 'strdup': The POSIX name for this item is + // deprecated. Instead, use the ISO C and C++ + // conformant name: _strdup. +#endif + + const int MIN_RUN_LENGTH = 3; const int MAX_RUN_LENGTH = 127; @@ -7479,6 +7536,7 @@ static int rleUncompress(int inLength, int maxLength, const signed char in[], #ifdef __clang__ #pragma clang diagnostic pop #endif + // End of RLE code from OpenEXR ----------------------------------- static void CompressRle(unsigned char *dst, @@ -7488,7 +7546,7 @@ static void CompressRle(unsigned char *dst, // // Apply EXR-specific? postprocess. Grabbed from OpenEXR's - // ImfZipCompressor.cpp + // ImfRleCompressor.cpp // // @@ -7539,23 +7597,36 @@ static void CompressRle(unsigned char *dst, assert(outSize > 0); compressedSize = static_cast(outSize); + + // Use uncompressed data when compressed data is larger than uncompressed. + // (Issue 40) + if (compressedSize >= src_size) { + compressedSize = src_size; + memcpy(dst, src, src_size); + } } static void 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; + } + std::vector tmpBuf(uncompressed_size); int ret = rleUncompress(static_cast(src_size), static_cast(uncompressed_size), reinterpret_cast(src), - reinterpret_cast(dst)); + reinterpret_cast(&tmpBuf.at(0))); assert(ret == static_cast(uncompressed_size)); (void)ret; // // Apply EXR-specific? postprocess. Grabbed from OpenEXR's - // ImfZipCompressor.cpp + // ImfRleCompressor.cpp // // Predictor. @@ -8859,7 +8930,12 @@ static void applyLut(const unsigned short lut[USHORT_RANGE], #pragma clang diagnostic pop #endif // __clang__ -static bool CompressPiz(unsigned char *outPtr, unsigned int &outSize, +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +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) { @@ -8966,16 +9042,29 @@ static bool CompressPiz(unsigned char *outPtr, unsigned int &outSize, hufCompress(&tmpBuffer.at(0), static_cast(tmpBuffer.size()), buf); memcpy(lengthPtr, &length, sizeof(int)); - outSize = static_cast( + (*outSize) = static_cast( (reinterpret_cast(buf) - outPtr) + static_cast(length)); + + // Use uncompressed data when compressed data is larger than uncompressed. + // (Issue 40) + if ((*outSize) >= inSize) { + (*outSize) = static_cast(inSize); + memcpy(outPtr, inPtr, inSize); + } return true; } static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, - size_t tmpBufSize, int num_channels, + size_t tmpBufSize, size_t inLen, int num_channels, const EXRChannelInfo *channels, int data_width, int num_lines) { + if (inLen == tmpBufSize) { + // Data is not compressed(Issue 40). + memcpy(outPtr, inPtr, inLen); + return true; + } + unsigned char bitmap[BITMAP_SIZE]; unsigned short minNonZero; unsigned short maxNonZero; @@ -9150,6 +9239,11 @@ static bool DecompressZfp(float *dst, int dst_width, int dst_num_lines, const ZFPCompressionParam ¶m) { size_t uncompressed_size = dst_width * dst_num_lines * num_channels; + if (uncompressed_size == src_size) { + // Data is not compressed(Issue 40). + memcpy(dst, src, src_size); + } + zfp_stream *zfp = NULL; zfp_field *field = NULL; @@ -9294,14 +9388,14 @@ static void DecodePixelData(/* out */ unsigned char **out_images, // Allocate original data size. std::vector outBuf(static_cast( static_cast(width * num_lines) * pixel_data_size)); - size_t tmpBufLen = static_cast( - static_cast(width * num_lines) * pixel_data_size); + size_t tmpBufLen = outBuf.size(); bool ret = tinyexr::DecompressPiz( reinterpret_cast(&outBuf.at(0)), data_ptr, tmpBufLen, - static_cast(num_channels), channels, width, num_lines); + data_len, static_cast(num_channels), channels, width, num_lines); assert(ret); + (void)ret; // For PIZ_COMPRESSION: // pixel sample data for channel 0 for scanline 0 @@ -9424,7 +9518,7 @@ static void DecodePixelData(/* out */ unsigned char **out_images, static_cast(num_lines) * pixel_data_size); - unsigned long dstLen = outBuf.size(); + unsigned long dstLen = static_cast(outBuf.size()); assert(dstLen > 0); tinyexr::DecompressZip(reinterpret_cast(&outBuf.at(0)), &dstLen, data_ptr, @@ -9547,13 +9641,13 @@ static void DecodePixelData(/* out */ unsigned char **out_images, static_cast(num_lines) * pixel_data_size); - unsigned long dstLen = outBuf.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)); - // For ZIP_COMPRESSION: + // For RLE_COMPRESSION: // pixel sample data for channel 0 for scanline 0 // pixel sample data for channel 1 for scanline 0 // pixel sample data for channel ... for scanline 0 @@ -10023,8 +10117,7 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, } else if (attr_name.compare("compression") == 0) { bool ok = false; - if ((data[0] >= TINYEXR_COMPRESSIONTYPE_NONE) && - (data[0] < TINYEXR_COMPRESSIONTYPE_PIZ)) { + if (data[0] < TINYEXR_COMPRESSIONTYPE_PIZ) { ok = true; } @@ -10134,9 +10227,14 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, // Custom attribute(up to TINYEXR_MAX_ATTRIBUTES) if (info->attributes.size() < TINYEXR_MAX_ATTRIBUTES) { EXRAttribute attrib; +#ifdef _MSC_VER + strncpy_s(attrib.name, attr_name.c_str(), 255); + strncpy_s(attrib.type, attr_type.c_str(), 255); +#else strncpy(attrib.name, attr_name.c_str(), 255); - attrib.name[255] = '\0'; strncpy(attrib.type, attr_type.c_str(), 255); +#endif + attrib.name[255] = '\0'; attrib.type[255] = '\0'; attrib.size = static_cast(data.size()); attrib.value = static_cast(malloc(data.size())); @@ -10230,8 +10328,12 @@ static void ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info) { exr_header->channels = static_cast(malloc( sizeof(EXRChannelInfo) * static_cast(exr_header->num_channels))); for (size_t c = 0; c < static_cast(exr_header->num_channels); c++) { +#ifdef _MSC_VER + strncpy_s(exr_header->channels[c].name, info.channels[c].name.c_str(), 255); +#else strncpy(exr_header->channels[c].name, info.channels[c].name.c_str(), 255); - // manually add '\0' for safety. +#endif + // manually add '\0' for safety. exr_header->channels[c].name[255] = '\0'; exr_header->channels[c].pixel_type = info.channels[c].pixel_type; @@ -10293,6 +10395,8 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, &channel_offset, num_channels, exr_header->channels); + bool invalid_data = false; + if (exr_header->tiled) { size_t num_tiles = offsets.size(); // = # of blocks @@ -10387,18 +10491,26 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, // Adjust line_no with data_window.bmin.y line_no -= exr_header->data_window[1]; - 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); + if (line_no < 0) { + invalid_data = true; + } else { + 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); + } } // omp parallel } + if (invalid_data) { + return TINYEXR_ERROR_INVALID_DATA; + } + // Overwrite `pixel_type` with `requested_pixel_type`. { for (int c = 0; c < exr_header->num_channels; c++) { @@ -10416,11 +10528,46 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, return TINYEXR_SUCCESS; } +static bool ReconstructLineOffsets( + std::vector *offsets, size_t n, + const unsigned char *head, const unsigned char *marker, const size_t size) { + assert(head < marker); + assert(offsets->size() == n); + + for (size_t i = 0; i < n; i++) { + size_t offset = static_cast(marker - head); + // Offset should not exceed whole EXR file/data size. + if (offset >= size) { + return false; + } + + int y; + unsigned int data_len; + + memcpy(&y, marker, sizeof(int)); + memcpy(&data_len, marker + 4, sizeof(unsigned int)); + + if (data_len >= size) { + return false; + } + + tinyexr::swap4(reinterpret_cast(&y)); + tinyexr::swap4(reinterpret_cast(&data_len)); + + (*offsets)[i] = offset; + + marker += data_len + 8; // 8 = 4 bytes(y) + 4 bytes(data_len) + } + + return true; +} + static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, const unsigned char *head, - const unsigned char *marker, const char **err) { + const unsigned char *marker, const size_t size, + const char **err) { if (exr_image == NULL || exr_header == NULL || head == NULL || - marker == NULL) { + marker == NULL || (size <= tinyexr::kEXRVersionSize)) { if (err) { (*err) = "Invalid argument."; } @@ -10476,10 +10623,40 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, tinyexr::tinyexr_uint64 offset; memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); tinyexr::swap8(&offset); + if (offset >= size) { + if (err) { + (*err) = "Invalid offset value."; + } + return TINYEXR_ERROR_INVALID_DATA; + } marker += sizeof(tinyexr::tinyexr_uint64); // = 8 offsets[y] = offset; } + // If line offsets are invalid, we try to reconstruct it. + // See OpenEXR/IlmImf/ImfScanLineInputFile.cpp::readLineOffsets() for details. + for (size_t y = 0; y < num_blocks; y++) { + if (offsets[y] <= 0) { + // TODO(syoyo) Report as warning? + // if (err) { + // stringstream ss; + // ss << "Incomplete lineOffsets." << std::endl; + // (*err) += ss.str(); + //} + bool ret = + ReconstructLineOffsets(&offsets, num_blocks, head, marker, size); + if (ret) { + // OK + break; + } else { + if (err) { + (*err) = "Cannot reconstruct lineOffset table."; + } + return TINYEXR_ERROR_INVALID_DATA; + } + } + } + return DecodeChunk(exr_image, exr_header, offsets, head); } @@ -10552,46 +10729,63 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, } } - if (idxR == -1) { - if (err) { - (*err) = "R channel not found\n"; + if ((idxA == 0) && (idxR == -1) && (idxG == -1) && (idxB == -1)) { + // Alpha channel only. + + (*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; + } + } else { + // Assume RGB(A) + + if (idxR == -1) { + if (err) { + (*err) = "R channel not found\n"; + } + + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; } - // @todo { free exr_image } - return TINYEXR_ERROR_INVALID_DATA; - } - - if (idxG == -1) { - if (err) { - (*err) = "G channel not found\n"; + if (idxG == -1) { + if (err) { + (*err) = "G channel not found\n"; + } + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; } - // @todo { free exr_image } - return TINYEXR_ERROR_INVALID_DATA; - } - if (idxB == -1) { - if (err) { - (*err) = "B channel not found\n"; + if (idxB == -1) { + if (err) { + (*err) = "B channel not found\n"; + } + // @todo { free exr_image } + return TINYEXR_ERROR_INVALID_DATA; } - // @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; + (*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; + } } } @@ -10604,8 +10798,6 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, return TINYEXR_SUCCESS; } -static const int kVersionSize = 8; - int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, const unsigned char *memory, size_t size, const char **err) { @@ -10618,12 +10810,12 @@ int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, return TINYEXR_ERROR_INVALID_ARGUMENT; } - if (size < kVersionSize) { + if (size < tinyexr::kEXRVersionSize) { return TINYEXR_ERROR_INVALID_DATA; } - const unsigned char *marker = memory + kVersionSize; - size_t marker_size = size - kVersionSize; + const unsigned char *marker = memory + tinyexr::kEXRVersionSize; + size_t marker_size = size - tinyexr::kEXRVersionSize; tinyexr::HeaderInfo info; info.clear(); @@ -10633,7 +10825,11 @@ int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, 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 } } @@ -10645,8 +10841,9 @@ int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, return ret; } -int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, size_t size, - const char **err) { +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"; @@ -10669,9 +10866,16 @@ int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, size_t size, 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, err); + ret = LoadEXRImageFromMemory(&exr_image, &exr_header, memory, size, err); if (ret != TINYEXR_SUCCESS) { return ret; } @@ -10718,19 +10922,32 @@ int LoadEXRFromMemory(float *out_rgba, const unsigned char *memory, size_t size, return TINYEXR_ERROR_INVALID_DATA; } - // Assume `out_rgba` have enough memory allocated. + (*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 > 0) { - out_rgba[4 * i + 3] = - reinterpret_cast(exr_image.images)[idxA][i]; - } else { - out_rgba[4 * i + 3] = 1.0; - } + (*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; } @@ -10771,12 +10988,15 @@ int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, (void)ret; } - return LoadEXRImageFromMemory(exr_image, exr_header, &buf.at(0), err); + return LoadEXRImageFromMemory(exr_image, exr_header, &buf.at(0), filesize, + err); } int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header, - const unsigned char *memory, const char **err) { - if (exr_image == NULL || memory == NULL) { + const unsigned char *memory, const size_t size, + const char **err) { + if (exr_image == NULL || memory == NULL || + (size < tinyexr::kEXRVersionSize)) { if (err) { (*err) = "Invalid argument."; } @@ -10794,7 +11014,8 @@ int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header, const unsigned char *marker = reinterpret_cast( memory + exr_header->header_len + 8); // +8 for magic number + version header. - return tinyexr::DecodeEXRImage(exr_image, exr_header, head, marker, err); + return tinyexr::DecodeEXRImage(exr_image, exr_header, head, marker, size, + err); } size_t SaveEXRImageToMemory(const EXRImage *exr_image, @@ -11160,16 +11381,17 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if ((exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIPS) || (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZIP)) { #if TINYEXR_USE_MINIZ - std::vector block( - tinyexr::miniz::mz_compressBound(buf.size())); + std::vector block(tinyexr::miniz::mz_compressBound( + static_cast(buf.size()))); #else - std::vector block(compressBound(buf.size())); + std::vector block( + compressBound(static_cast(buf.size()))); #endif tinyexr::tinyexr_uint64 outSize = block.size(); tinyexr::CompressZip(&block.at(0), outSize, reinterpret_cast(&buf.at(0)), - buf.size()); + static_cast(buf.size())); // 4 byte: scan line // 4 byte: data size @@ -11194,7 +11416,7 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, tinyexr::CompressRle(&block.at(0), outSize, reinterpret_cast(&buf.at(0)), - buf.size()); + static_cast(buf.size())); // 4 byte: scan line // 4 byte: data size @@ -11220,7 +11442,7 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, std::vector block(bufLen); unsigned int outSize = static_cast(block.size()); - CompressPiz(&block.at(0), outSize, + CompressPiz(&block.at(0), &outSize, reinterpret_cast(&buf.at(0)), buf.size(), channels, exr_image->width, h); @@ -11361,6 +11583,16 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { return TINYEXR_ERROR_INVALID_ARGUMENT; } +#ifdef _MSC_VER + FILE *fp = NULL; + errno_t errcode = fopen_s(&fp, filename, "rb"); + if ((!errcode) || (!fp)) { + if (err) { + (*err) = "Cannot read file."; + } + return TINYEXR_ERROR_CANT_OPEN_FILE; + } +#else FILE *fp = fopen(filename, "rb"); if (!fp) { if (err) { @@ -11368,6 +11600,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { } return TINYEXR_ERROR_CANT_OPEN_FILE; } +#endif size_t filesize; // Compute size @@ -11432,11 +11665,12 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { std::vector channels; // Read attributes - size_t size = filesize - kVersionSize; + size_t size = filesize - tinyexr::kEXRVersionSize; for (;;) { if (0 == size) { return TINYEXR_ERROR_INVALID_DATA; } else if (marker[0] == '\0') { + marker++; size--; break; } @@ -11608,10 +11842,11 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // decode pixel offset table. { - unsigned long dstLen = pixelOffsetTable.size() * sizeof(int); + unsigned long dstLen = + static_cast(pixelOffsetTable.size() * sizeof(int)); tinyexr::DecompressZip( reinterpret_cast(&pixelOffsetTable.at(0)), &dstLen, - data_ptr + 28, static_cast(packedOffsetTableSize)); + data_ptr + 28, static_cast(packedOffsetTableSize)); assert(dstLen == pixelOffsetTable.size() * sizeof(int)); for (size_t i = 0; i < static_cast(data_width); i++) { @@ -11625,11 +11860,13 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // decode sample data. { unsigned long dstLen = static_cast(unpackedSampleDataSize); - tinyexr::DecompressZip( - reinterpret_cast(&sample_data.at(0)), &dstLen, - data_ptr + 28 + packedOffsetTableSize, - static_cast(packedSampleDataSize)); - assert(dstLen == static_cast(unpackedSampleDataSize)); + if (dstLen) { + tinyexr::DecompressZip( + reinterpret_cast(&sample_data.at(0)), &dstLen, + data_ptr + 28 + packedOffsetTableSize, + static_cast(packedSampleDataSize)); + assert(dstLen == static_cast(unpackedSampleDataSize)); + } } // decode sample @@ -11675,7 +11912,7 @@ 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( - &sample_data.at(data_offset + x * sizeof(int))); + &sample_data.at(size_t(data_offset) + x * sizeof(int))); deep_image->image[c][y][x] = static_cast(ui); // @fixme } data_offset += @@ -11684,7 +11921,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { for (size_t x = 0; x < static_cast(samples_per_line); x++) { tinyexr::FP16 f16; f16.u = *reinterpret_cast( - &sample_data.at(data_offset + x * sizeof(short))); + &sample_data.at(size_t(data_offset) + x * sizeof(short))); tinyexr::FP32 f32 = half_to_float(f16); deep_image->image[c][y][x] = f32.f; } @@ -11692,7 +11929,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { } else { // float for (size_t x = 0; x < static_cast(samples_per_line); x++) { float f = *reinterpret_cast( - &sample_data.at(data_offset + x * sizeof(float))); + &sample_data.at(size_t(data_offset) + x * sizeof(float))); deep_image->image[c][y][x] = f; } data_offset += sizeof(float) * static_cast(samples_per_line); @@ -11729,6 +11966,8 @@ void InitEXRImage(EXRImage *exr_image) { exr_image->images = NULL; exr_image->tiles = NULL; + + exr_image->num_tiles = 0; } void InitEXRHeader(EXRHeader *exr_header) { @@ -11854,12 +12093,12 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, return TINYEXR_ERROR_INVALID_ARGUMENT; } - if (size < kVersionSize) { + if (size < tinyexr::kEXRVersionSize) { return TINYEXR_ERROR_INVALID_DATA; } - const unsigned char *marker = memory + kVersionSize; - size_t marker_size = size - kVersionSize; + const unsigned char *marker = memory + tinyexr::kEXRVersionSize; + size_t marker_size = size - tinyexr::kEXRVersionSize; std::vector infos; @@ -11874,7 +12113,11 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, 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 } return ret; } @@ -11973,7 +12216,7 @@ int ParseEXRVersionFromMemory(EXRVersion *version, const unsigned char *memory, return TINYEXR_ERROR_INVALID_ARGUMENT; } - if (size < kVersionSize) { + if (size < tinyexr::kEXRVersionSize) { return TINYEXR_ERROR_INVALID_DATA; } @@ -12045,28 +12288,28 @@ int ParseEXRVersionFromFile(EXRVersion *version, const char *filename) { file_size = static_cast(ftell(fp)); fseek(fp, 0, SEEK_SET); - if (file_size < kVersionSize) { + if (file_size < tinyexr::kEXRVersionSize) { return TINYEXR_ERROR_INVALID_FILE; } - unsigned char buf[kVersionSize]; - size_t ret = fread(&buf[0], 1, kVersionSize, fp); + unsigned char buf[tinyexr::kEXRVersionSize]; + size_t ret = fread(&buf[0], 1, tinyexr::kEXRVersionSize, fp); fclose(fp); - if (ret != kVersionSize) { + if (ret != tinyexr::kEXRVersionSize) { return TINYEXR_ERROR_INVALID_FILE; } - return ParseEXRVersionFromMemory(version, buf, kVersionSize); + return ParseEXRVersionFromMemory(version, buf, tinyexr::kEXRVersionSize); } int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, const EXRHeader **exr_headers, unsigned int num_parts, const unsigned char *memory, - const char **err) { + const size_t size, const char **err) { if (exr_images == NULL || exr_headers == NULL || num_parts == 0 || - memory == NULL) { + memory == NULL || (size <= tinyexr::kEXRVersionSize)) { if (err) { (*err) = "Invalid argument."; } @@ -12113,6 +12356,13 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, memcpy(&offset, marker, 8); tinyexr::swap8(&offset); + if (offset >= size) { + if (err) { + (*err) = "Invalid offset size."; + } + return TINYEXR_ERROR_INVALID_DATA; + } + offset_table[c] = offset + 4; // +4 to skip 'part number' marker += 8; } @@ -12189,12 +12439,12 @@ int LoadEXRMultipartImageFromFile(EXRImage *exr_images, } return LoadEXRMultipartImageFromMemory(exr_images, exr_headers, num_parts, - &buf.at(0), err); + &buf.at(0), filesize, err); } int SaveEXR(const float *data, int width, int height, int components, - const char *outfilename) { - if (components == 3 || components == 4) { + const int save_as_fp16, const char *outfilename) { + if ((components == 1) || components == 3 || components == 4) { // OK } else { return TINYEXR_ERROR_INVALID_ARGUMENT; @@ -12213,18 +12463,24 @@ int SaveEXR(const float *data, int width, int height, int components, image.num_channels = components; std::vector images[4]; - images[0].resize(static_cast(width * height)); - images[1].resize(static_cast(width * height)); - images[2].resize(static_cast(width * height)); - images[3].resize(static_cast(width * height)); - // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers - for (size_t i = 0; i < static_cast(width * height); i++) { - images[0][i] = data[static_cast(components) * i + 0]; - images[1][i] = data[static_cast(components) * i + 1]; - images[2][i] = data[static_cast(components) * i + 2]; - if (components == 4) { - images[3][i] = data[static_cast(components) * i + 3]; + if (components == 1) { + images[0].resize(static_cast(width * height)); + memcpy(images[0].data(), data, sizeof(float) * size_t(width * height)); + } else { + images[0].resize(static_cast(width * height)); + images[1].resize(static_cast(width * height)); + images[2].resize(static_cast(width * height)); + images[3].resize(static_cast(width * height)); + + // Split RGB(A)RGB(A)RGB(A)... into R, G and B(and A) layers + for (size_t i = 0; i < static_cast(width * height); i++) { + images[0][i] = data[static_cast(components) * i + 0]; + images[1][i] = data[static_cast(components) * i + 1]; + images[2][i] = data[static_cast(components) * i + 2]; + if (components == 4) { + images[3][i] = data[static_cast(components) * i + 3]; + } } } @@ -12234,10 +12490,12 @@ int SaveEXR(const float *data, int width, int height, int components, image_ptr[1] = &(images[2].at(0)); // B image_ptr[2] = &(images[1].at(0)); // G image_ptr[3] = &(images[0].at(0)); // R - } else { + } else if (components == 3) { image_ptr[0] = &(images[2].at(0)); // B image_ptr[1] = &(images[1].at(0)); // G image_ptr[2] = &(images[0].at(0)); // R + } else if (components == 1) { + image_ptr[0] = &(images[0].at(0)); // A } image.images = reinterpret_cast(image_ptr); @@ -12249,21 +12507,41 @@ int SaveEXR(const float *data, int width, int height, int components, sizeof(EXRChannelInfo) * static_cast(header.num_channels))); // Must be (A)BGR order, since most of EXR viewers expect this channel order. if (components == 4) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); + strncpy_s(header.channels[1].name, "B", 255); + strncpy_s(header.channels[2].name, "G", 255); + strncpy_s(header.channels[3].name, "R", 255); +#else strncpy(header.channels[0].name, "A", 255); - header.channels[0].name[strlen("A")] = '\0'; strncpy(header.channels[1].name, "B", 255); - header.channels[1].name[strlen("B")] = '\0'; strncpy(header.channels[2].name, "G", 255); - header.channels[2].name[strlen("G")] = '\0'; strncpy(header.channels[3].name, "R", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; + header.channels[1].name[strlen("B")] = '\0'; + header.channels[2].name[strlen("G")] = '\0'; header.channels[3].name[strlen("R")] = '\0'; - } else { + } else if (components == 3) { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "B", 255); + strncpy_s(header.channels[1].name, "G", 255); + strncpy_s(header.channels[2].name, "R", 255); +#else 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); +#endif + header.channels[0].name[strlen("B")] = '\0'; + header.channels[1].name[strlen("G")] = '\0'; header.channels[2].name[strlen("R")] = '\0'; + } else { +#ifdef _MSC_VER + strncpy_s(header.channels[0].name, "A", 255); +#else + strncpy(header.channels[0].name, "A", 255); +#endif + header.channels[0].name[strlen("A")] = '\0'; } header.pixel_types = static_cast( @@ -12273,9 +12551,15 @@ int SaveEXR(const float *data, int width, int height, int components, 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 + + if (save_as_fp16 > 0) { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_HALF; // save with half(fp16) pixel format + } else { + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_FLOAT; // save with float(fp32) pixel format(i.e. + // no precision reduction) + } } const char *err; @@ -12291,10 +12575,5 @@ int SaveEXR(const float *data, int width, int height, int components, return ret; } -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif - -#endif // TINYEXR_H_ +#endif // TINYEXR_IMPLEMENTATION_DEIFNED +#endif // TINYEXR_IMPLEMENTATION diff --git a/src/modules/image/magpie/EXRHandler.cpp b/src/modules/image/magpie/EXRHandler.cpp index 602c9cd88..7f8af6a3f 100644 --- a/src/modules/image/magpie/EXRHandler.cpp +++ b/src/modules/image/magpie/EXRHandler.cpp @@ -23,8 +23,12 @@ #include "common/halffloat.h" #include "common/Exception.h" +// zlib (for tinyexr) +#include + // tinyexr #define TINYEXR_IMPLEMENTATION +#define TINYEXR_USE_MINIZ 0 #include "libraries/tinyexr/tinyexr.h" // C @@ -124,7 +128,7 @@ FormatHandler::DecodedImage EXRHandler::decode(Data *data) if (ParseEXRHeaderFromMemory(&exrHeader, &exrVersion, mem, memsize, &err) != TINYEXR_SUCCESS) throw love::Exception("Could not parse EXR image header: %s", err); - if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, &err) != TINYEXR_SUCCESS) + if (LoadEXRImageFromMemory(&exrImage, &exrHeader, mem, memsize, &err) != TINYEXR_SUCCESS) throw love::Exception("Could not decode EXR image: %s", err); int pixelType = exrHeader.pixel_types[0];