diff --git a/BUILD.bazel b/BUILD.bazel index b2ee435ebb..e914fa76c3 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -70,6 +70,9 @@ expand_template( out = "src/lib/OpenEXR/OpenEXRConfigInternal.h", substitutions = { "#cmakedefine OPENEXR_USE_INTERNAL_DEFLATE 1": "#define OPENEXR_USE_INTERNAL_DEFLATE 0", + "#cmakedefine OPENEXR_ENABLE_GDEFLATE 1": "/* #undef OPENEXR_ENABLE_GDEFLATE */", + # For gdeflate, set `_enable_gdeflate` to True in `MODULE.bazel` and replace the above line with: + # "#cmakedefine OPENEXR_ENABLE_GDEFLATE 1": "#define OPENEXR_ENABLE_GDEFLATE 1", "#cmakedefine OPENEXR_IMF_HAVE_COMPLETE_IOMANIP 1": "#define OPENEXR_IMF_HAVE_COMPLETE_IOMANIP 1", "#cmakedefine OPENEXR_IMF_HAVE_DARWIN 1": "/* #undef OPENEXR_IMF_HAVE_DARWIN */", "#cmakedefine OPENEXR_IMF_HAVE_GCC_INLINE_ASM_AVX 1": "/* #undef OPENEXR_IMF_HAVE_GCC_INLINE_ASM_AVX */", @@ -193,6 +196,7 @@ cc_library( "src/lib/OpenEXRCore/internal_dwa_simd.h", "src/lib/OpenEXRCore/internal_file.h", "src/lib/OpenEXRCore/internal_float_vector.h", + "src/lib/OpenEXRCore/internal_gdeflate_wrapper.h", "src/lib/OpenEXRCore/internal_ht.cpp", "src/lib/OpenEXRCore/internal_ht_common.h", "src/lib/OpenEXRCore/internal_ht_common.cpp", @@ -291,6 +295,7 @@ cc_library( "src/lib/OpenEXR/ImfCompression.cpp", "src/lib/OpenEXR/ImfCompressionAttribute.cpp", "src/lib/OpenEXR/ImfCompressor.cpp", + "src/lib/OpenEXR/ImfGdeflateCompressor.cpp", "src/lib/OpenEXR/ImfContext.cpp", "src/lib/OpenEXR/ImfContextInit.cpp", "src/lib/OpenEXR/ImfConvert.cpp", @@ -395,6 +400,7 @@ cc_library( "src/lib/OpenEXR/ImfCompression.h", "src/lib/OpenEXR/ImfCompressionAttribute.h", "src/lib/OpenEXR/ImfCompressor.h", + "src/lib/OpenEXR/ImfGdeflateCompressor.h", "src/lib/OpenEXR/ImfContext.h", "src/lib/OpenEXR/ImfContextInit.h", "src/lib/OpenEXR/ImfConvert.h", diff --git a/MODULE.bazel b/MODULE.bazel index 80677a7ba0..5891f0aef8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,6 +6,19 @@ module( compatibility_level = 1, ) +# Toggle this to fetch the NVIDIA libdeflate fork, then update OPENEXR_ENABLE_GDEFLATE in BUILD.bazel. +_enable_gdeflate = False + +_enable_gdeflate and archive_override( + module_name = "libdeflate", + patches = [ + "//bazel:libdeflate_gdeflate_add_build_file.patch", + "//bazel:libdeflate_module_dot_bazel.patch", + ], + strip_prefix = "libdeflate-gdeflate", + urls = ["https://github.com/NVIDIA/libdeflate/archive/refs/heads/gdeflate.zip"], +) + bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "imath", version = "3.2.2") bazel_dep(name = "libdeflate", version = "1.24") diff --git a/cmake/OpenEXRConfigInternal.h.in b/cmake/OpenEXRConfigInternal.h.in index faf4f80489..7cd34ab414 100644 --- a/cmake/OpenEXRConfigInternal.h.in +++ b/cmake/OpenEXRConfigInternal.h.in @@ -17,6 +17,10 @@ // deflate or the system provided version #cmakedefine OPENEXR_USE_INTERNAL_DEFLATE 1 +// +// Whether libdeflate has gdeflate support (NVIDIA fork) +#cmakedefine OPENEXR_ENABLE_GDEFLATE 1 + // // Define and set to 1 if the target system supports a proc filesystem // compatible with the Linux kernel's proc filesystem. Note that this diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index d11cba032a..9fe94375aa 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -202,9 +202,14 @@ endif() set (ILMTHREAD_USE_TBB ${OPENEXR_USE_TBB}) option(OPENEXR_FORCE_INTERNAL_DEFLATE "Force using an internal libdeflate" OFF) +option(OPENEXR_FORCE_FETCHCONTENT_DEFLATE "Force fetching libdeflate from git repo (NVIDIA fork with gdeflate)" OFF) +set(OPENEXR_DEFLATE_REPO "https://github.com/NVIDIA/libdeflate.git" CACHE STRING "Git repo for FetchContent libdeflate source") +set(OPENEXR_DEFLATE_TAG "gdeflate" CACHE STRING "Git tag/branch for FetchContent libdeflate source") + set (OPENEXR_USE_INTERNAL_DEFLATE OFF) +set (OPENEXR_USE_FETCHCONTENT_DEFLATE OFF) -if(NOT OPENEXR_FORCE_INTERNAL_DEFLATE) +if(NOT OPENEXR_FORCE_INTERNAL_DEFLATE AND NOT OPENEXR_FORCE_FETCHCONTENT_DEFLATE) #TODO: ^^ Release should not clone from main, this is a place holder set(CMAKE_IGNORE_PATH "${CMAKE_CURRENT_BINARY_DIR}/_deps/deflate-src/config;${CMAKE_CURRENT_BINARY_DIR}/_deps/deflate-build/config") # First try cmake config @@ -238,6 +243,50 @@ if(EXR_DEFLATE_LIB) message(STATUS "Using externally provided libdeflate: ${EXR_DEFLATE_VERSION}") # For OpenEXR.pc.in for static build set(EXR_DEFLATE_PKGCONFIG_REQUIRES "libdeflate >= ${EXR_DEFLATE_VERSION}") +elseif(OPENEXR_FORCE_FETCHCONTENT_DEFLATE) + # Using FetchContent to get NVIDIA fork + message(STATUS "Fetching libdeflate from ${OPENEXR_DEFLATE_REPO} @ ${OPENEXR_DEFLATE_TAG}") + + include(FetchContent) + FetchContent_Declare(Deflate + GIT_REPOSITORY ${OPENEXR_DEFLATE_REPO} + GIT_TAG ${OPENEXR_DEFLATE_TAG} + GIT_SHALLOW ON + ) + + FetchContent_GetProperties(Deflate) + if(NOT Deflate_POPULATED) + FetchContent_Populate(Deflate) + endif() + + # Build libdeflate as an OBJECT library to avoid symbol conflicts + # OBJECT libraries compile source files but don't create a standalone library, + # so symbols remain private when linked into OpenEXRCore + add_library(deflate_internal OBJECT + ${deflate_SOURCE_DIR}/lib/arm/cpu_features.c + ${deflate_SOURCE_DIR}/lib/x86/cpu_features.c + ${deflate_SOURCE_DIR}/lib/utils.c + ${deflate_SOURCE_DIR}/lib/deflate_compress.c + ${deflate_SOURCE_DIR}/lib/deflate_decompress.c + ${deflate_SOURCE_DIR}/lib/adler32.c + ${deflate_SOURCE_DIR}/lib/gdeflate_compress.c + ${deflate_SOURCE_DIR}/lib/gdeflate_decompress.c + ${deflate_SOURCE_DIR}/lib/zlib_compress.c + ${deflate_SOURCE_DIR}/lib/zlib_decompress.c + ) + + target_include_directories(deflate_internal PUBLIC ${deflate_SOURCE_DIR}) + + # Hide all symbols by default to prevent conflicts + set_target_properties(deflate_internal PROPERTIES + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES + POSITION_INDEPENDENT_CODE ON + ) + + set(EXR_DEFLATE_LIB deflate_internal) + set(OPENEXR_USE_FETCHCONTENT_DEFLATE ON) + message(STATUS "Using FetchContent libdeflate with gdeflate support") else() # Using internal deflate if(OPENEXR_FORCE_INTERNAL_DEFLATE) @@ -250,6 +299,41 @@ else() set(EXR_DEFLATE_LIB) endif() +####################################### +# Check for gdeflate support in libdeflate +####################################### + +if(OPENEXR_USE_INTERNAL_DEFLATE) + # Internal libdeflate currently lacks gdeflate support + set(OPENEXR_ENABLE_GDEFLATE OFF) + message(STATUS "Using internal libdeflate - gdeflate DISABLED") +elseif(OPENEXR_USE_FETCHCONTENT_DEFLATE) + set(OPENEXR_ENABLE_GDEFLATE ON) + message(STATUS "Using FetchContent libdeflate from NVIDIA fork - gdeflate ENABLED") +else() + # Check if external libdeflate has gdeflate support + include(CheckCSourceCompiles) + set(CMAKE_REQUIRED_LIBRARIES ${EXR_DEFLATE_LIB}) + check_c_source_compiles(" + #include + int main() { + struct libdeflate_gdeflate_compressor* c = + libdeflate_alloc_gdeflate_compressor(1); + libdeflate_free_gdeflate_compressor(c); + return 0; + } + " HAVE_LIBDEFLATE_GDEFLATE) + set(CMAKE_REQUIRED_LIBRARIES) + + if(HAVE_LIBDEFLATE_GDEFLATE) + set(OPENEXR_ENABLE_GDEFLATE ON) + message(STATUS "External libdeflate has gdeflate support - ENABLED") + else() + set(OPENEXR_ENABLE_GDEFLATE OFF) + message(STATUS "External libdeflate lacks gdeflate support - DISABLED") + endif() +endif() + ####################################### # Find or download OpenJPH diff --git a/src/lib/OpenEXR/CMakeLists.txt b/src/lib/OpenEXR/CMakeLists.txt index 4f3d4be16b..1eb8506793 100644 --- a/src/lib/OpenEXR/CMakeLists.txt +++ b/src/lib/OpenEXR/CMakeLists.txt @@ -24,6 +24,8 @@ openexr_define_library(OpenEXR ImfCompressionAttribute.cpp ImfCompressor.cpp ImfCompressor.h + ImfGdeflateCompressor.cpp + ImfGdeflateCompressor.h ImfContext.cpp ImfContextInit.cpp ImfConvert.cpp diff --git a/src/lib/OpenEXR/ImfCRgbaFile.h b/src/lib/OpenEXR/ImfCRgbaFile.h index f94eba7399..7683b5e541 100644 --- a/src/lib/OpenEXR/ImfCRgbaFile.h +++ b/src/lib/OpenEXR/ImfCRgbaFile.h @@ -82,7 +82,8 @@ typedef struct ImfRgba ImfRgba; #define IMF_DWAB_COMPRESSION 9 #define IMF_HTJ2K256_COMPRESSION 10 #define IMF_HTJ2K32_COMPRESSION 11 -#define IMF_NUM_COMPRESSION_METHODS 12 +#define IMF_GDEFLATE_COMPRESSION 12 +#define IMF_NUM_COMPRESSION_METHODS 13 /* ** Channels; values must be the same as in Imf::RgbaChannels. diff --git a/src/lib/OpenEXR/ImfCompression.cpp b/src/lib/OpenEXR/ImfCompression.cpp index cc18fd939b..e82c7c0d92 100644 --- a/src/lib/OpenEXR/ImfCompression.cpp +++ b/src/lib/OpenEXR/ImfCompression.cpp @@ -188,6 +188,12 @@ static const CompressionDesc IdToDesc[] = { 32, true, false), + CompressionDesc ( + "gdeflate", + "gdeflate compression, in blocks of 16 scan lines.", + 16, + false, + false), }; // clang-format on @@ -206,6 +212,7 @@ static const std::map CompressionNameToId = { {"dwab", Compression::DWAB_COMPRESSION}, {"htj2k256", Compression::HTJ2K256_COMPRESSION}, {"htj2k32", Compression::HTJ2K32_COMPRESSION}, + {"gdeflate", Compression::GDEFLATE_COMPRESSION}, }; #define UNKNOWN_COMPRESSION_ID_MSG "INVALID COMPRESSION ID" diff --git a/src/lib/OpenEXR/ImfCompression.h b/src/lib/OpenEXR/ImfCompression.h index a25c202a2a..c21d7b0720 100644 --- a/src/lib/OpenEXR/ImfCompression.h +++ b/src/lib/OpenEXR/ImfCompression.h @@ -55,6 +55,8 @@ enum IMF_EXPORT_ENUM Compression HTJ2K32_COMPRESSION = 11, // High-Throughput JPEG2000 (HTJ2K), 32 scanlines + GDEFLATE_COMPRESSION = 12, // gdeflate compression, in blocks of 16 scan lines + NUM_COMPRESSION_METHODS // number of different compression methods }; diff --git a/src/lib/OpenEXR/ImfCompressor.cpp b/src/lib/OpenEXR/ImfCompressor.cpp index 5ba51ed028..58d902681e 100644 --- a/src/lib/OpenEXR/ImfCompressor.cpp +++ b/src/lib/OpenEXR/ImfCompressor.cpp @@ -13,6 +13,7 @@ #include "ImfNamespace.h" #include "ImfCompressor.h" #include "ImfB44Compressor.h" +#include "ImfGdeflateCompressor.h" #include "ImfDwaCompressor.h" #include "ImfPizCompressor.h" #include "ImfPxr24Compressor.h" @@ -344,6 +345,11 @@ newCompressor (Compression c, size_t maxScanLineSize, const Header& hdr) return new HTCompressor (hdr, static_cast (maxScanLineSize), 32); + case GDEFLATE_COMPRESSION: + + ret = new GdeflateCompressor (hdr, maxScanLineSize, 16); + break; + default: break; } // clang-format on @@ -433,6 +439,12 @@ newTileCompressor ( static_cast (tileLineSize), static_cast (numTileLines)); + case GDEFLATE_COMPRESSION: + + ret = new GdeflateCompressor ( + hdr, tileLineSize, static_cast (numTileLines)); + break; + default: break; } // clang-format on diff --git a/src/lib/OpenEXR/ImfGdeflateCompressor.cpp b/src/lib/OpenEXR/ImfGdeflateCompressor.cpp new file mode 100644 index 0000000000..33d0a14f48 --- /dev/null +++ b/src/lib/OpenEXR/ImfGdeflateCompressor.cpp @@ -0,0 +1,55 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +//----------------------------------------------------------------------------- +// +// class GdeflateCompressor +// +//----------------------------------------------------------------------------- + +#include "ImfGdeflateCompressor.h" +#include "OpenEXRConfigInternal.h" + +#ifndef OPENEXR_ENABLE_GDEFLATE +#include "IexBaseExc.h" +#endif + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER + +#ifdef OPENEXR_ENABLE_GDEFLATE + +GdeflateCompressor::GdeflateCompressor ( + const Header& hdr, size_t maxScanLineSize, int numScanLines) + : Compressor ( + hdr, + EXR_COMPRESSION_GDEFLATE, + maxScanLineSize, + numScanLines) +{ +} + +GdeflateCompressor::~GdeflateCompressor () +{ +} + +#else + +GdeflateCompressor::GdeflateCompressor ( + const Header& hdr, size_t maxScanLineSize, int numScanLines) + : Compressor (hdr, EXR_COMPRESSION_LAST_TYPE, maxScanLineSize, numScanLines) +{ + throw IEX_NAMESPACE::NoImplExc ( + "Gdeflate support is not enabled in this build of OpenEXR."); +} + +GdeflateCompressor::~GdeflateCompressor () +{ +} + +#endif + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT + + diff --git a/src/lib/OpenEXR/ImfGdeflateCompressor.h b/src/lib/OpenEXR/ImfGdeflateCompressor.h new file mode 100644 index 0000000000..fda7104bfe --- /dev/null +++ b/src/lib/OpenEXR/ImfGdeflateCompressor.h @@ -0,0 +1,38 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#ifndef INCLUDED_IMF_GDEFLATE_COMPRESSOR_H +#define INCLUDED_IMF_GDEFLATE_COMPRESSOR_H + +//----------------------------------------------------------------------------- +// +// class GdeflateCompressor -- performs gdeflate compression +// +//----------------------------------------------------------------------------- + +#include "ImfCompressor.h" + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER + +class GdeflateCompressor : public Compressor +{ +public: + GdeflateCompressor ( + const Header& hdr, size_t maxScanLineSize, int numScanLines); + + virtual ~GdeflateCompressor (); + + GdeflateCompressor (const GdeflateCompressor& other) = delete; + GdeflateCompressor& operator= (const GdeflateCompressor& other) = delete; + GdeflateCompressor (GdeflateCompressor&& other) = delete; + GdeflateCompressor& operator= (GdeflateCompressor&& other) = delete; +}; + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT + +#endif + + + diff --git a/src/lib/OpenEXRCore/compression.c b/src/lib/OpenEXRCore/compression.c index dc0c973b28..9ea47e7b97 100644 --- a/src/lib/OpenEXRCore/compression.c +++ b/src/lib/OpenEXRCore/compression.c @@ -13,6 +13,7 @@ #include "internal_coding.h" #include "internal_file.h" #include "internal_huf.h" +#include "internal_xdr.h" #include "OpenEXRConfigInternal.h" @@ -28,9 +29,13 @@ # include "../../../external/deflate/lib/adler32.c" # include "../../../external/deflate/lib/zlib_compress.c" # include "../../../external/deflate/lib/zlib_decompress.c" +/* Internal libdeflate currently lacks gdeflate support */ #else # include +/* OPENEXR_ENABLE_GDEFLATE is set by CMake if external libdeflate has gdeflate */ #endif +#include "internal_gdeflate_wrapper.h" + #include #if ( \ @@ -209,6 +214,274 @@ exr_uncompress_buffer ( return EXR_ERR_OUT_OF_MEMORY; } +/**************************************/ + +static size_t +internal_compress_pad_buffer_size (size_t in_bytes, size_t r) +{ + size_t extra; + + /* + * lib deflate has a message about needing a 9 byte boundary + * but is unclear if it actually adds that or not + * (see the comment on libdeflate_deflate_compress) + */ + if (r > (SIZE_MAX - 9)) return (size_t) (SIZE_MAX); + r += 9; + + /* + * old library had uiAdd( uiAdd( in, ceil(in * 0.01) ), 100 ) + */ + extra = (in_bytes * (size_t) 130); + if (extra < in_bytes) return (size_t) (SIZE_MAX); + extra /= (size_t) 128; + + if (extra > (SIZE_MAX - 100)) return (size_t) (SIZE_MAX); + + if (extra > r) r = extra; + return r; +} + +size_t +exr_compress_gdeflate_max_buffer_size ( + size_t in_bytes, uint64_t* out_page_count, uint64_t* out_page_size) +{ + size_t r; + size_t metadata_size; + uint64_t page_count = 0; + size_t page_size = 0; + + r = wrap_gdeflate_compress_bound (NULL, in_bytes, &page_count); + r = internal_compress_pad_buffer_size (in_bytes, r); + + if (page_count == 0) page_count = 1; + + /* The gdeflate page size is 64KB, but that's not exposed in the API. */ + page_size = r / (size_t) page_count; + + if (out_page_count) *out_page_count = page_count; + if (out_page_size) *out_page_size = page_size; + + /* Add space for page metadata: page_count + page_sizes array */ + metadata_size = sizeof (uint32_t) * (1 + page_count); + r += metadata_size; + + return r; +} + +/**************************************/ + +exr_result_t +exr_compress_buffer_gdeflate ( + exr_const_context_t ctxt, + int level, + const void* in, + size_t in_bytes, + void* out, + size_t out_bytes_avail, + size_t* actual_out) +{ + enum { GDEFLATE_STACK_PAGE_THRESHOLD = 256 }; + uint64_t page_count = 0; + size_t metadata_size = 0; + size_t page_data_avail; + size_t out_page_size = 0; + uint8_t* out_base; + uint8_t* page_data_start; + struct libdeflate_gdeflate_out_page stack_pages[GDEFLATE_STACK_PAGE_THRESHOLD]; + struct libdeflate_gdeflate_out_page* out_pages; + struct libdeflate_gdeflate_compressor* comp = NULL; + exr_result_t rv; + size_t i; + + if (level < 0) + { + exr_get_default_zip_compression_level (&level); + /* truly unset anywhere */ + if (level < 0) level = EXR_DEFAULT_ZLIB_COMPRESS_LEVEL; + } + + exr_compress_gdeflate_max_buffer_size (in_bytes, &page_count, &out_page_size); + if (page_count == 0) return EXR_ERR_OUT_OF_MEMORY; + + /* Reserve space for page metadata at beginning of output buffer */ + metadata_size = sizeof (uint32_t) * (1 + page_count); + if (out_bytes_avail < metadata_size) return EXR_ERR_OUT_OF_MEMORY; + + out_base = (uint8_t*) out; + page_data_start = out_base + metadata_size; + page_data_avail = out_bytes_avail - metadata_size; + + out_pages = stack_pages; + if (page_count > GDEFLATE_STACK_PAGE_THRESHOLD) + { + out_pages = (struct libdeflate_gdeflate_out_page*) + (ctxt ? ctxt->alloc_fn : internal_exr_alloc) ( + sizeof (*out_pages) * page_count); + if (!out_pages) return EXR_ERR_OUT_OF_MEMORY; + } + + rv = wrap_alloc_gdeflate_compressor (level, ctxt, &comp); + if (rv != EXR_ERR_SUCCESS) goto cleanup; + + { + size_t last_page = page_count - 1; + size_t outsz; + + /* Set up page buffers after metadata space */ + for (i = 0; i <= last_page; ++i) + { + out_pages[i].data = page_data_start + i * out_page_size; + out_pages[i].nbytes = + (i < last_page) ? out_page_size + : page_data_avail - last_page * out_page_size; + } + + outsz = wrap_gdeflate_compress ( + comp, in, in_bytes, out_pages, (size_t) page_count); + + wrap_free_gdeflate_compressor (comp); + comp = NULL; + + if (outsz == 0) + { + rv = EXR_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + { + uint32_t* metadata; + uint8_t* dest; + size_t total_compressed = 0; + + /* Write page metadata to beginning of buffer: + * [page_count][page_0_size][page_1_size]...[page_{N-1}_size] + * followed by contiguous compressed page data. + */ + metadata = (uint32_t*) out_base; + metadata[0] = one_from_native32 ((uint32_t) page_count); + + for (i = 0; i < page_count; ++i) + { + metadata[i + 1] = one_from_native32 ( + (uint32_t) out_pages[i].nbytes); + total_compressed += out_pages[i].nbytes; + } + + /* Compact the compressed page data to immediately follow metadata */ + dest = page_data_start; + for (i = 0; i < page_count; ++i) + { + if (dest != out_pages[i].data) + memmove (dest, out_pages[i].data, out_pages[i].nbytes); + dest += out_pages[i].nbytes; + } + + if (actual_out) *actual_out = metadata_size + total_compressed; + } + + rv = EXR_ERR_SUCCESS; + } + +cleanup: + if (comp) wrap_free_gdeflate_compressor (comp); + if (out_pages != stack_pages) + (ctxt ? ctxt->free_fn : internal_exr_free) (out_pages); + return rv; +} + +/**************************************/ + +exr_result_t +exr_uncompress_buffer_gdeflate ( + exr_const_context_t ctxt, + const void* in, + size_t in_bytes, + void* out, + size_t out_bytes_avail, + size_t* actual_out) +{ + enum { GDEFLATE_STACK_PAGE_THRESHOLD = 256 }; + uint32_t page_count; + size_t metadata_size; + const uint8_t* in_base; + const uint32_t* metadata; + struct libdeflate_gdeflate_in_page stack_pages[GDEFLATE_STACK_PAGE_THRESHOLD]; + struct libdeflate_gdeflate_in_page* in_pages; + exr_result_t rv; + size_t i; + + in_base = (const uint8_t*) in; + + /* Read and validate page metadata: [page_count][page_0_size]...[page_{N-1}_size] */ + if (in_bytes < sizeof (uint32_t)) return EXR_ERR_CORRUPT_CHUNK; + + metadata = (const uint32_t*) in_base; + page_count = one_to_native32 (metadata[0]); + metadata_size = sizeof (uint32_t) * (1 + page_count); + + if (page_count == 0 || in_bytes < metadata_size) return EXR_ERR_CORRUPT_CHUNK; + + /* Allocate page info array: stack for small counts, heap for large */ + in_pages = stack_pages; + if (page_count > GDEFLATE_STACK_PAGE_THRESHOLD) + { + in_pages = (struct libdeflate_gdeflate_in_page*) + (ctxt ? ctxt->alloc_fn : internal_exr_alloc) ( + sizeof (*in_pages) * page_count); + if (!in_pages) return EXR_ERR_OUT_OF_MEMORY; + } + + /* Calculate page data pointers from metadata. */ + { + const uint8_t* page_data = in_base + metadata_size; + size_t offset = 0; + + for (i = 0; i < page_count; ++i) + { + uint32_t page_size = one_to_native32 (metadata[i + 1]); + + if (offset + page_size > in_bytes - metadata_size) + { + if (in_pages != stack_pages) + (ctxt ? ctxt->free_fn : internal_exr_free) (in_pages); + return EXR_ERR_CORRUPT_CHUNK; + } + + in_pages[i].data = page_data + offset; + in_pages[i].nbytes = page_size; + offset += page_size; + } + } + + { + struct libdeflate_gdeflate_decompressor* decomp; + enum libdeflate_result res; + + rv = wrap_alloc_gdeflate_decompressor (ctxt, &decomp); + if (rv != EXR_ERR_SUCCESS) goto cleanup; + + if (actual_out) *actual_out = 0; + res = wrap_gdeflate_decompress ( + decomp, + in_pages, + (size_t) page_count, + out, + out_bytes_avail, + actual_out); + + wrap_free_gdeflate_decompressor (decomp); + + rv = (res == LIBDEFLATE_SUCCESS) ? EXR_ERR_SUCCESS : EXR_ERR_CORRUPT_CHUNK; + } + +cleanup: + if (in_pages != stack_pages) + (ctxt ? ctxt->free_fn : internal_exr_free) (in_pages); + + return rv; +} + /**************************************/ /**************************************/ @@ -237,6 +510,7 @@ int exr_compression_lines_per_chunk (exr_compression_t comptype) case EXR_COMPRESSION_RLE: case EXR_COMPRESSION_ZIPS: linePerChunk = 1; break; case EXR_COMPRESSION_ZIP: + case EXR_COMPRESSION_GDEFLATE: case EXR_COMPRESSION_PXR24: linePerChunk = 16; break; case EXR_COMPRESSION_PIZ: case EXR_COMPRESSION_B44: @@ -281,12 +555,31 @@ exr_compress_chunk (exr_encode_pipeline_t* encode) if (encode->packed_bytes > maxbytes) maxbytes = encode->packed_bytes; - rv = internal_encode_alloc_buffer ( - encode, - EXR_TRANSCODE_BUFFER_COMPRESSED, - &(encode->compressed_buffer), - &(encode->compressed_alloc_size), - exr_compress_max_buffer_size (maxbytes)); + /* Special handling for gdeflate which needs page info */ + if (part->comp_type == EXR_COMPRESSION_GDEFLATE) + { + uint64_t page_count = 0; + uint64_t page_size = 0; + size_t alloc_size = exr_compress_gdeflate_max_buffer_size ( + maxbytes, + &page_count, + &page_size); + rv = internal_encode_alloc_buffer ( + encode, + EXR_TRANSCODE_BUFFER_COMPRESSED, + &(encode->compressed_buffer), + &(encode->compressed_alloc_size), + alloc_size); + } + else + { + rv = internal_encode_alloc_buffer ( + encode, + EXR_TRANSCODE_BUFFER_COMPRESSED, + &(encode->compressed_buffer), + &(encode->compressed_alloc_size), + exr_compress_max_buffer_size (maxbytes)); + } if (rv != EXR_ERR_SUCCESS) return ctxt->print_error ( ctxt, @@ -342,6 +635,9 @@ exr_compress_chunk (exr_encode_pipeline_t* encode) case EXR_COMPRESSION_RLE: rv = internal_exr_apply_rle (encode); break; case EXR_COMPRESSION_ZIP: case EXR_COMPRESSION_ZIPS: rv = internal_exr_apply_zip (encode); break; + case EXR_COMPRESSION_GDEFLATE: + rv = internal_exr_apply_gdeflate (encode); + break; default: rv = EXR_ERR_INVALID_ARGUMENT; @@ -370,6 +666,7 @@ exr_compress_chunk (exr_encode_pipeline_t* encode) case EXR_COMPRESSION_RLE: rv = internal_exr_apply_rle (encode); break; case EXR_COMPRESSION_ZIP: case EXR_COMPRESSION_ZIPS: rv = internal_exr_apply_zip (encode); break; + case EXR_COMPRESSION_GDEFLATE: rv = internal_exr_apply_gdeflate (encode); break; case EXR_COMPRESSION_PIZ: rv = internal_exr_apply_piz (encode); break; case EXR_COMPRESSION_PXR24: rv = internal_exr_apply_pxr24 (encode); @@ -433,6 +730,10 @@ decompress_data ( rv = internal_exr_undo_zip ( decode, packbufptr, packsz, unpackbufptr, unpacksz); break; + case EXR_COMPRESSION_GDEFLATE: + rv = internal_exr_undo_gdeflate ( + decode, packbufptr, packsz, unpackbufptr, unpacksz); + break; case EXR_COMPRESSION_PIZ: rv = internal_exr_undo_piz ( decode, packbufptr, packsz, unpackbufptr, unpacksz); diff --git a/src/lib/OpenEXRCore/internal_compress.h b/src/lib/OpenEXRCore/internal_compress.h index 1cdc484a85..055c541a28 100644 --- a/src/lib/OpenEXRCore/internal_compress.h +++ b/src/lib/OpenEXRCore/internal_compress.h @@ -21,6 +21,8 @@ exr_result_t internal_exr_apply_rle (exr_encode_pipeline_t* encode); exr_result_t internal_exr_apply_zip (exr_encode_pipeline_t* encode); +exr_result_t internal_exr_apply_gdeflate (exr_encode_pipeline_t* encode); + exr_result_t internal_exr_apply_piz (exr_encode_pipeline_t* encode); exr_result_t internal_exr_apply_pxr24 (exr_encode_pipeline_t* encode); diff --git a/src/lib/OpenEXRCore/internal_decompress.h b/src/lib/OpenEXRCore/internal_decompress.h index 6251c2d9c1..1599a659cb 100644 --- a/src/lib/OpenEXRCore/internal_decompress.h +++ b/src/lib/OpenEXRCore/internal_decompress.h @@ -31,6 +31,13 @@ exr_result_t internal_exr_undo_zip ( void* uncompressed_data, uint64_t uncompressed_size); +exr_result_t internal_exr_undo_gdeflate ( + exr_decode_pipeline_t* decode, + const void* compressed_data, + uint64_t comp_buf_size, + void* uncompressed_data, + uint64_t uncompressed_size); + exr_result_t internal_exr_undo_piz ( exr_decode_pipeline_t* decode, const void* compressed_data, diff --git a/src/lib/OpenEXRCore/internal_gdeflate_wrapper.h b/src/lib/OpenEXRCore/internal_gdeflate_wrapper.h new file mode 100644 index 0000000000..b7e777e9f4 --- /dev/null +++ b/src/lib/OpenEXRCore/internal_gdeflate_wrapper.h @@ -0,0 +1,173 @@ +/* +** SPDX-License-Identifier: BSD-3-Clause +** Copyright Contributors to the OpenEXR Project. +*/ + +#ifndef OPENEXR_CORE_GDEFLATE_WRAPPER_H +#define OPENEXR_CORE_GDEFLATE_WRAPPER_H + +/* + * GDEFLATE SUPPORT WRAPPERS + * + * Conditional compilation for gdeflate support is isolated here. + * OPENEXR_ENABLE_GDEFLATE is set by CMake based on libdeflate detection. + * + * Note: This header must be included after libdeflate.h (or internal deflate + * sources) are already included, as it depends on libdeflate types. + */ + +#include "internal_memory.h" +#include "openexr_context.h" +#include "openexr_errors.h" + +/* libdeflate types already available via compression.c includes */ + +#ifndef OPENEXR_ENABLE_GDEFLATE +/* Stub types when gdeflate not available */ +struct libdeflate_gdeflate_compressor +{ + int unused; +}; +struct libdeflate_gdeflate_decompressor +{ + int unused; +}; +struct libdeflate_gdeflate_out_page +{ + void* data; + size_t nbytes; +}; +struct libdeflate_gdeflate_in_page +{ + const void* data; + size_t nbytes; +}; +#endif + +static inline size_t +wrap_gdeflate_compress_bound ( + void* compressor, size_t in_bytes, uint64_t* out_page_count) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + size_t page_count; + size_t result = libdeflate_gdeflate_compress_bound ( + compressor, in_bytes, &page_count); + *out_page_count = page_count; + return result; +#else + (void) compressor; + (void) in_bytes; + *out_page_count = 0; + return 0; +#endif +} + +static inline exr_result_t +wrap_alloc_gdeflate_compressor ( + int level, + exr_const_context_t ctxt, + struct libdeflate_gdeflate_compressor** out_comp) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + /* Note: gdeflate does not have _ex() allocator variants, so we always use + * libdeflate_set_memory_allocator() regardless of libdeflate version */ + libdeflate_set_memory_allocator ( + ctxt ? ctxt->alloc_fn : internal_exr_alloc, + ctxt ? ctxt->free_fn : internal_exr_free); + *out_comp = libdeflate_alloc_gdeflate_compressor (level); + return *out_comp ? EXR_ERR_SUCCESS : EXR_ERR_OUT_OF_MEMORY; +#else + (void) level; + (void) ctxt; + *out_comp = NULL; + return EXR_ERR_FEATURE_NOT_IMPLEMENTED; +#endif +} + +static inline void +wrap_free_gdeflate_compressor (struct libdeflate_gdeflate_compressor* comp) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + libdeflate_free_gdeflate_compressor (comp); +#else + (void) comp; +#endif +} + +static inline size_t +wrap_gdeflate_compress ( + struct libdeflate_gdeflate_compressor* comp, + const void* in, + size_t in_bytes, + struct libdeflate_gdeflate_out_page* out_pages, + size_t out_page_count) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + return libdeflate_gdeflate_compress ( + comp, in, in_bytes, out_pages, out_page_count); +#else + (void) comp; + (void) in; + (void) in_bytes; + (void) out_pages; + (void) out_page_count; + return 0; +#endif +} + +static inline exr_result_t +wrap_alloc_gdeflate_decompressor ( + exr_const_context_t ctxt, + struct libdeflate_gdeflate_decompressor** out_decomp) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + /* Note: gdeflate does not have _ex() allocator variants, so we always use + * libdeflate_set_memory_allocator() regardless of libdeflate version */ + libdeflate_set_memory_allocator ( + ctxt ? ctxt->alloc_fn : internal_exr_alloc, + ctxt ? ctxt->free_fn : internal_exr_free); + *out_decomp = libdeflate_alloc_gdeflate_decompressor (); + return *out_decomp ? EXR_ERR_SUCCESS : EXR_ERR_OUT_OF_MEMORY; +#else + (void) ctxt; + *out_decomp = NULL; + return EXR_ERR_FEATURE_NOT_IMPLEMENTED; +#endif +} + +static inline void +wrap_free_gdeflate_decompressor ( + struct libdeflate_gdeflate_decompressor* decomp) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + libdeflate_free_gdeflate_decompressor (decomp); +#else + (void) decomp; +#endif +} + +static inline enum libdeflate_result +wrap_gdeflate_decompress ( + struct libdeflate_gdeflate_decompressor* decomp, + struct libdeflate_gdeflate_in_page* in_pages, + size_t in_page_count, + void* out, + size_t out_bytes_avail, + size_t* actual_out) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + return libdeflate_gdeflate_decompress ( + decomp, in_pages, in_page_count, out, out_bytes_avail, actual_out); +#else + (void) decomp; + (void) in_pages; + (void) in_page_count; + (void) out; + (void) out_bytes_avail; + (void) actual_out; + return LIBDEFLATE_BAD_DATA; +#endif +} + +#endif /* OPENEXR_CORE_GDEFLATE_WRAPPER_H */ + diff --git a/src/lib/OpenEXRCore/internal_zip.c b/src/lib/OpenEXRCore/internal_zip.c index 41a3d775d8..ebf1259322 100644 --- a/src/lib/OpenEXRCore/internal_zip.c +++ b/src/lib/OpenEXRCore/internal_zip.c @@ -278,12 +278,12 @@ undo_zip_impl ( const void* compressed_data, const uint64_t comp_buf_size, void* uncompressed_data, - const uint64_t uncompressed_size, + uint64_t uncompressed_size, void* scratch_data, - const uint64_t scratch_size) + uint64_t scratch_size) { - size_t actual_out_bytes; exr_result_t res; + size_t actual_out_bytes = 0; if (scratch_size < uncompressed_size) return EXR_ERR_INVALID_ARGUMENT; @@ -427,3 +427,152 @@ internal_exr_apply_zip (exr_encode_pipeline_t* encode) return apply_zip_impl (encode); } + +/**************************************/ + +static exr_result_t +apply_gdeflate_impl (exr_encode_pipeline_t* encode) +{ + exr_result_t rv; + size_t compbufsz = 0; + int level; + + rv = exr_get_zip_compression_level ( + encode->context, encode->part_index, &level); + if (rv != EXR_ERR_SUCCESS) return rv; + + internal_zip_deconstruct_bytes ( + encode->scratch_buffer_1, encode->packed_buffer, encode->packed_bytes); + + rv = exr_compress_buffer_gdeflate ( + encode->context, + level, + encode->scratch_buffer_1, + encode->packed_bytes, + encode->compressed_buffer, + encode->compressed_alloc_size, + &compbufsz); + + if (rv != EXR_ERR_SUCCESS) + { + exr_const_context_t pctxt = encode->context; + if (pctxt) + pctxt->report_error ( + pctxt, rv, "Unable to compress gdeflate data"); + return rv; + } + + if (compbufsz > encode->packed_bytes) + { + memcpy ( + encode->compressed_buffer, + encode->packed_buffer, + encode->packed_bytes); + compbufsz = encode->packed_bytes; + } + encode->compressed_bytes = compbufsz; + + return EXR_ERR_SUCCESS; +} + +exr_result_t +internal_exr_apply_gdeflate (exr_encode_pipeline_t* encode) +{ + exr_result_t rv; + + rv = internal_encode_alloc_buffer ( + encode, + EXR_TRANSCODE_BUFFER_SCRATCH1, + &(encode->scratch_buffer_1), + &(encode->scratch_alloc_size_1), + encode->packed_bytes); + if (rv != EXR_ERR_SUCCESS) + { + exr_const_context_t pctxt = encode->context; + if (pctxt) + pctxt->print_error ( + pctxt, + rv, + "Unable to allocate scratch buffer for gdeflate of %" PRIu64 + " bytes", + encode->packed_bytes); + return rv; + } + + return apply_gdeflate_impl (encode); +} + +/**************************************/ + +static exr_result_t +undo_gdeflate_impl ( + exr_decode_pipeline_t* decode, + const void* compressed_data, + const uint64_t comp_buf_size, + void* uncompressed_data, + const uint64_t uncompressed_size, + void* scratch_data, + const uint64_t scratch_size) +{ + size_t actual_out_bytes; + exr_result_t res; + + if (scratch_size < uncompressed_size) return EXR_ERR_INVALID_ARGUMENT; + + res = exr_uncompress_buffer_gdeflate ( + decode->context, + compressed_data, + comp_buf_size, + scratch_data, + scratch_size, + &actual_out_bytes); + + if (res != EXR_ERR_SUCCESS) return res; + + decode->bytes_decompressed = actual_out_bytes; + if (comp_buf_size > actual_out_bytes || + actual_out_bytes > uncompressed_size) + return EXR_ERR_CORRUPT_CHUNK; + + internal_zip_reconstruct_bytes ( + uncompressed_data, scratch_data, actual_out_bytes); + + return EXR_ERR_SUCCESS; +} + +exr_result_t +internal_exr_undo_gdeflate ( + exr_decode_pipeline_t* decode, + const void* compressed_data, + const uint64_t comp_buf_size, + void* uncompressed_data, + const uint64_t uncompressed_size) +{ + exr_result_t rv; + uint64_t scratchbufsz = uncompressed_size; + if (comp_buf_size > scratchbufsz) scratchbufsz = comp_buf_size; + + if (comp_buf_size == uncompressed_size) + { + decode->bytes_decompressed = comp_buf_size; + if (compressed_data != uncompressed_data) + memcpy (uncompressed_data, compressed_data, comp_buf_size); + return EXR_ERR_SUCCESS; + } + + rv = internal_decode_alloc_buffer ( + decode, + EXR_TRANSCODE_BUFFER_SCRATCH1, + &(decode->scratch_buffer_1), + &(decode->scratch_alloc_size_1), + scratchbufsz); + if (rv != EXR_ERR_SUCCESS) return rv; + return undo_gdeflate_impl ( + decode, + compressed_data, + comp_buf_size, + uncompressed_data, + uncompressed_size, + decode->scratch_buffer_1, + decode->scratch_alloc_size_1); +} diff --git a/src/lib/OpenEXRCore/openexr_attr.h b/src/lib/OpenEXRCore/openexr_attr.h index 8c51f96c43..a6a7f59a23 100644 --- a/src/lib/OpenEXRCore/openexr_attr.h +++ b/src/lib/OpenEXRCore/openexr_attr.h @@ -47,6 +47,7 @@ typedef enum EXR_COMPRESSION_DWAB = 9, EXR_COMPRESSION_HTJ2K256 = 10, EXR_COMPRESSION_HTJ2K32 = 11, + EXR_COMPRESSION_GDEFLATE = 12, EXR_COMPRESSION_LAST_TYPE /**< Invalid value, provided for range checking. */ } exr_compression_t; diff --git a/src/lib/OpenEXRCore/openexr_compression.h b/src/lib/OpenEXRCore/openexr_compression.h index 990f21f085..43f26cbdda 100644 --- a/src/lib/OpenEXRCore/openexr_compression.h +++ b/src/lib/OpenEXRCore/openexr_compression.h @@ -65,6 +65,37 @@ size_t exr_rle_uncompress_buffer ( const void* in, void* out); +/** Computes a buffer large enough to hold gdeflate-compressed data and returns + * the page count and size required for compression. */ +EXR_EXPORT +size_t exr_compress_gdeflate_max_buffer_size ( + size_t in_bytes, + uint64_t* out_page_count, + uint64_t* out_page_size); + +/** Compresses a buffer using gdeflate compression with page-based output. + * + * If the level is -1, will use the default compression set to the library + * \ref exr_set_default_zip_compression_level */ +EXR_EXPORT +exr_result_t exr_compress_buffer_gdeflate ( + exr_const_context_t ctxt, + int level, + const void* in, + size_t in_bytes, + void* out, + size_t out_bytes_avail, + size_t* actual_out); + +/** Decompresses a buffer using gdeflate compression. */ +EXR_EXPORT +exr_result_t exr_uncompress_buffer_gdeflate ( + exr_const_context_t ctxt, + const void* in, + size_t in_bytes, + void* out, + size_t out_bytes_avail, + size_t* actual_out); /** Routine to query the lines required per chunk to compress with the * specified method. diff --git a/src/test/OpenEXRCoreTest/CMakeLists.txt b/src/test/OpenEXRCoreTest/CMakeLists.txt index 9f4825a173..974384d16d 100644 --- a/src/test/OpenEXRCoreTest/CMakeLists.txt +++ b/src/test/OpenEXRCoreTest/CMakeLists.txt @@ -120,6 +120,7 @@ define_openexrcore_tests( testRLECompression testZIPCompression testZIPSCompression + testGdeflateCompression testPIZCompression testPXR24Compression testB44Compression diff --git a/src/test/OpenEXRCoreTest/compression.cpp b/src/test/OpenEXRCoreTest/compression.cpp index 58e1cc632c..d72c570ec2 100644 --- a/src/test/OpenEXRCoreTest/compression.cpp +++ b/src/test/OpenEXRCoreTest/compression.cpp @@ -9,6 +9,8 @@ # define NOMINMAX #endif +#include "OpenEXRConfigInternal.h" + #include "write.h" #include "test_value.h" @@ -686,9 +688,9 @@ struct pixels } } }; -static const int IMG_WIDTH = 1371; +static const int IMG_WIDTH = 2049; // large enough to trigger a multi-page chunk in gdeflate static const int IMG_HEIGHT = 159; -static const int IMG_STRIDE_X = 1376; +static const int IMG_STRIDE_X = 2064; static const int IMG_DATA_X = 17; static const int IMG_DATA_Y = 29; @@ -1433,6 +1435,7 @@ doWriteRead ( case EXR_COMPRESSION_RLE: case EXR_COMPRESSION_ZIP: case EXR_COMPRESSION_ZIPS: + case EXR_COMPRESSION_GDEFLATE: restore.compareExact (p, "orig", "C loaded C"); break; case EXR_COMPRESSION_PIZ: @@ -1660,6 +1663,17 @@ testZIPSCompression (const std::string& tempdir) testComp (tempdir, EXR_COMPRESSION_ZIPS); } +void +testGdeflateCompression (const std::string& tempdir) +{ +#ifdef OPENEXR_ENABLE_GDEFLATE + testComp (tempdir, EXR_COMPRESSION_GDEFLATE); +#else + std::cout << " gdeflate support not available - test skipped" << std::endl; + (void) tempdir; +#endif +} + void testPIZCompression (const std::string& tempdir) { diff --git a/src/test/OpenEXRCoreTest/compression.h b/src/test/OpenEXRCoreTest/compression.h index d85f326c10..2fde68d5c7 100644 --- a/src/test/OpenEXRCoreTest/compression.h +++ b/src/test/OpenEXRCoreTest/compression.h @@ -16,6 +16,7 @@ void testNoCompression (const std::string& tempdir); void testRLECompression (const std::string& tempdir); void testZIPCompression (const std::string& tempdir); void testZIPSCompression (const std::string& tempdir); +void testGdeflateCompression (const std::string& tempdir); void testPIZCompression (const std::string& tempdir); void testPXR24Compression (const std::string& tempdir); void testB44Compression (const std::string& tempdir); diff --git a/src/test/OpenEXRCoreTest/main.cpp b/src/test/OpenEXRCoreTest/main.cpp index 33a13bb7b9..5bafea4cfc 100644 --- a/src/test/OpenEXRCoreTest/main.cpp +++ b/src/test/OpenEXRCoreTest/main.cpp @@ -202,6 +202,7 @@ main (int argc, char* argv[]) TEST (testRLECompression, "core_compression"); TEST (testZIPCompression, "core_compression"); TEST (testZIPSCompression, "core_compression"); + TEST (testGdeflateCompression, "core_compression"); TEST (testPIZCompression, "core_compression"); TEST (testPXR24Compression, "core_compression"); TEST (testB44Compression, "core_compression"); diff --git a/src/test/OpenEXRTest/testCompressionApi.cpp b/src/test/OpenEXRTest/testCompressionApi.cpp index 85a6993074..502d4ced40 100644 --- a/src/test/OpenEXRTest/testCompressionApi.cpp +++ b/src/test/OpenEXRTest/testCompressionApi.cpp @@ -28,11 +28,11 @@ testCompressionApi (const string& tempDir) cout << "Testing compression API functions." << endl; // update this if you add a new compressor. - string codecList = "none/rle/zips/zip/piz/pxr24/b44/b44a/dwaa/dwab/htj2k256/htj2k32"; + string codecList = "none/rle/zips/zip/piz/pxr24/b44/b44a/dwaa/dwab/htj2k256/htj2k32/gdeflate"; int numMethods = static_cast (NUM_COMPRESSION_METHODS); // update this if you add a new compressor. - assert (numMethods == 12); + assert (numMethods == 13); for (int i = 0; i < numMethods; i++) { @@ -64,6 +64,7 @@ testCompressionApi (const string& tempDir) case ZIPS_COMPRESSION: case ZIP_COMPRESSION: case PIZ_COMPRESSION: + case GDEFLATE_COMPRESSION: assert (isLossyCompression (c) == false); break;