From eb9e7c5118de3ddef776dcfc0f7613d69128daf3 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Mon, 29 Sep 2025 20:41:30 +0300 Subject: [PATCH 01/46] zstd debug Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/debug.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/OpenEXRCore/debug.c b/src/lib/OpenEXRCore/debug.c index 8d8bae2eee..b6241159e8 100644 --- a/src/lib/OpenEXRCore/debug.c +++ b/src/lib/OpenEXRCore/debug.c @@ -86,7 +86,8 @@ print_attr (const exr_attribute_t* a, int verbose) "dwaa", "dwab", "htj2k256", - "htj2k32"}; + "htj2k32", + "zstd"}; printf ( "'%s'", (a->uc < EXR_COMPRESSION_LAST_TYPE ? compressionnames[a->uc] : "")); if (verbose) printf (" (0x%02X)", a->uc); From fcc78aa79188f6b184cca17bb9382c11b6d32948 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Mon, 13 Oct 2025 20:01:45 +0300 Subject: [PATCH 02/46] c-blosc2 Signed-off-by: Todica Ionut --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index f6f90e399c..ba104373de 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,5 +10,6 @@ bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "imath", version = "3.2.1") bazel_dep(name = "libdeflate", version = "1.24") bazel_dep(name = "openjph", version = "0.24.1") +bazel_dep(name = "c-blosc2", version = "2.21.3") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.9") From a3c984ea9f173b1f3080ddedb08ef2926f9c1bf4 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Mon, 13 Oct 2025 20:07:03 +0300 Subject: [PATCH 03/46] Zstd Compressor Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfZstdCompressor.cpp | 17 +++++++++++++++++ src/lib/OpenEXR/ImfZstdCompressor.h | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/lib/OpenEXR/ImfZstdCompressor.cpp create mode 100644 src/lib/OpenEXR/ImfZstdCompressor.h diff --git a/src/lib/OpenEXR/ImfZstdCompressor.cpp b/src/lib/OpenEXR/ImfZstdCompressor.cpp new file mode 100644 index 0000000000..593ccacc51 --- /dev/null +++ b/src/lib/OpenEXR/ImfZstdCompressor.cpp @@ -0,0 +1,17 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#include "ImfZstdCompressor.h" + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER + +ZstdCompressor::ZstdCompressor (const Header& hdr, size_t maxScanLineSize, int scanLines) + : Compressor (hdr, EXR_COMPRESSION_ZSTD, maxScanLineSize, scanLines) +{} + +ZstdCompressor::~ZstdCompressor () +{} + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT \ No newline at end of file diff --git a/src/lib/OpenEXR/ImfZstdCompressor.h b/src/lib/OpenEXR/ImfZstdCompressor.h new file mode 100644 index 0000000000..8a5ff2be6b --- /dev/null +++ b/src/lib/OpenEXR/ImfZstdCompressor.h @@ -0,0 +1,27 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#ifndef INCLUDED_IMF_ZSTD_COMPRESSOR_H +#define INCLUDED_IMF_ZSTD_COMPRESSOR_H + +#include "ImfCompressor.h" + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER + +class ZstdCompressor : public Compressor +{ +public: + ZstdCompressor (const Header& hdr, size_t maxScanLineSize, int scanLines); + virtual ~ZstdCompressor (); + + ZstdCompressor (const ZstdCompressor& other) = delete; + ZstdCompressor& operator= (const ZstdCompressor& other) = delete; + ZstdCompressor (ZstdCompressor&& other) = delete; + ZstdCompressor& operator= (ZstdCompressor&& other) = delete; +}; + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT + +#endif \ No newline at end of file From ca0ddb7f288c01fd6328c028b6e103e864518741 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 10:36:23 +0300 Subject: [PATCH 04/46] Add zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/internal_zstd.c | 1153 +++++++++++++++++++++++++++ 1 file changed, 1153 insertions(+) create mode 100644 src/lib/OpenEXRCore/internal_zstd.c diff --git a/src/lib/OpenEXRCore/internal_zstd.c b/src/lib/OpenEXRCore/internal_zstd.c new file mode 100644 index 0000000000..3c9ed00625 --- /dev/null +++ b/src/lib/OpenEXRCore/internal_zstd.c @@ -0,0 +1,1153 @@ +/* +** SPDX-License-Identifier: BSD-3-Clause +** Copyright Contributors to the OpenEXR Project. +*/ + +#include +#include "internal_compress.h" +#include "internal_decompress.h" +#include "internal_coding.h" +#include "internal_structs.h" +#include +#include "blosc2.h" + +#define ERROR_MSG(pipeline, msg) \ + { \ + exr_const_context_t pctxt = pipeline->context; \ + if (pctxt) pctxt->print_error (pctxt, rv, msg); \ + } +#define ERROR_MSGV(pipeline, msg, ...) \ + { \ + exr_const_context_t pctxt = pipeline->context; \ + if (pctxt) pctxt->print_error (pctxt, rv, msg, __VA_ARGS__); \ + } +#define RETURN_ERROR(pipeline, err_code, msg) \ + { \ + exr_const_context_t pctxt = pipeline->context; \ + if (pctxt) pctxt->print_error (pctxt, err_code, msg); \ + return err_code; \ + } +#define RETURN_ERRORV(pipeline, err_code, msg, ...) \ + { \ + exr_const_context_t pctxt = pipeline->context; \ + if (pctxt) pctxt->print_error (pctxt, err_code, msg, __VA_ARGS__); \ + return err_code; \ + } + +// #define XTRA_MSGS +#ifdef XTRA_MSGS +# define DBGV(msg, ...) fprintf (stderr, msg, __VA_ARGS__); +# define DBG(msg, ...) fprintf (stderr, msg); +#else +# define DBGV(msg, ...) +# define DBG(msg, ...) +#endif + +#define EXR_HALF_PRECISION_SIZE 2 +#define EXR_SINGLE_PRECISION_SIZE 4 + +// ---------------------------------------------------------------------------- + +// FIXME: most fields could be pointers to avoid copying + +struct chan_infos +{ + int8_t type_size; // EXR_HALF_PRECISION_SIZE or EXR_SINGLE_PRECISION_SIZE. + int32_t x_sampling; // x subsampling. + int32_t y_sampling; // y subsampling. + int32_t byte_size; // channel byte size for validation - UNUSED + int32_t offset; // channel offset in an unpacked layout. +}; +typedef struct chan_infos chan_infos_t; + +struct zstd_data +{ + void* buffers[2]; // pointers to half and float buffers + int32_t buffers_size[2]; // byte size of half and float buffers + bool is_deep; // are we handling deep data ? + int32_t width; // buffer pixels width + int32_t height; // buffer pixels height / number of lines + int32_t channel_count; // number of channels + chan_infos_t* channels; // array of channels infos + int32_t* sample_count_per_line_cumulative; +}; +typedef struct zstd_data zstd_data_t; + +static exr_result_t +_new_encoding_zstd_data (exr_encode_pipeline_t* encode) +{ + encode->encoding_user_data = malloc (sizeof (zstd_data_t)); + if (!encode->encoding_user_data) + RETURN_ERROR ( + encode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private encoding data ! (zstd_data_t)"); + + zstd_data_t* zstd_data = (zstd_data_t*) encode->encoding_user_data; + + zstd_data->buffers[0] = NULL; + zstd_data->buffers[1] = NULL; + + zstd_data->buffers_size[0] = 0; + zstd_data->buffers_size[1] = 0; + + zstd_data->is_deep = encode->chunk.sample_count_table_size > 0; + zstd_data->width = encode->chunk.width; + zstd_data->height = encode->chunk.height; + zstd_data->channel_count = encode->channel_count; + + zstd_data->channels = + malloc (sizeof (chan_infos_t) * encode->channel_count); + if (!zstd_data->channels) + RETURN_ERROR ( + encode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private encoding data ! (chan_infos_t)"); + for (int i = 0; i < encode->channel_count; ++i) + { + zstd_data->channels[i].type_size = + encode->channels[i].bytes_per_element; + zstd_data->channels[i].x_sampling = encode->channels[i].x_samples; + zstd_data->channels[i].y_sampling = encode->channels[i].y_samples; + zstd_data->channels[i].byte_size = 0; + zstd_data->channels[i].offset = 0; + } + + zstd_data->sample_count_per_line_cumulative = + malloc (sizeof (int) * (encode->chunk.height + 1)); + if (!zstd_data->sample_count_per_line_cumulative) + RETURN_ERROR ( + encode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private encoding data ! " + "(sample_count_per_line_cumulative)"); + + return EXR_ERR_SUCCESS; +} + +static exr_result_t +_free_encoding_zstd_data (exr_encode_pipeline_t* encode) +{ + zstd_data_t* zstd_data = (zstd_data_t*) encode->encoding_user_data; + free (zstd_data->channels); + free (zstd_data->sample_count_per_line_cumulative); + free (zstd_data); + encode->encoding_user_data = NULL; + return EXR_ERR_SUCCESS; +} + +static exr_result_t +_new_decoding_zstd_data (exr_decode_pipeline_t* decode) +{ + decode->decoding_user_data = malloc (sizeof (zstd_data_t)); + if (!decode->decoding_user_data) + RETURN_ERROR ( + decode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private decoding data ! (zstd_data_t)"); + + zstd_data_t* zstd_data = (zstd_data_t*) decode->decoding_user_data; + + zstd_data->buffers[0] = NULL; // pointer doesn't belong to us + zstd_data->buffers[1] = NULL; // pointer doesn't belong to us + + zstd_data->buffers_size[0] = 0; + zstd_data->buffers_size[1] = 0; + + zstd_data->is_deep = decode->chunk.sample_count_table_size > 0; + zstd_data->width = decode->chunk.width; + zstd_data->height = decode->chunk.height; + zstd_data->channel_count = decode->channel_count; + + zstd_data->channels = + malloc (sizeof (chan_infos_t) * decode->channel_count); + if (!zstd_data->channels) + RETURN_ERROR ( + decode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private decoding data ! (chan_infos_t)"); + for (int i = 0; i < decode->channel_count; ++i) + { + zstd_data->channels[i].type_size = + decode->channels[i].bytes_per_element; + zstd_data->channels[i].x_sampling = decode->channels[i].x_samples; + zstd_data->channels[i].y_sampling = decode->channels[i].y_samples; + zstd_data->channels[i].byte_size = 0; + zstd_data->channels[i].offset = 0; + } + + zstd_data->sample_count_per_line_cumulative = + malloc (sizeof (int) * (decode->chunk.height + 1)); + if (!zstd_data->sample_count_per_line_cumulative) + RETURN_ERROR ( + decode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate private decoding data ! " + "(sample_count_per_line_cumulative)"); + return EXR_ERR_SUCCESS; +} + +static exr_result_t +_free_decoding_zstd_data (exr_decode_pipeline_t* decode) +{ + zstd_data_t* zstd_data = (zstd_data_t*) decode->decoding_user_data; + free (zstd_data->channels); + free (zstd_data->sample_count_per_line_cumulative); + free (zstd_data); + decode->decoding_user_data = NULL; + return EXR_ERR_SUCCESS; +} + +// ---------------------------------------------------------------------------- + +#ifndef NDEBUG +/** + * \brief debug function to inspect the blosc header in the debugger. + * code copied from blosc.c + * + * \param data + * \param size + */ +static void +_check_blosc_header (const void* data, size_t size) +{ + typedef struct blosc_header_s + { + uint8_t version; + uint8_t versionlz; + uint8_t flags; + uint8_t typesize; + int32_t nbytes; + int32_t blocksize; + int32_t cbytes; + // Extended Blosc2 header + uint8_t filter_codes[BLOSC2_MAX_FILTERS]; + uint8_t udcompcode; + uint8_t compcode_meta; + uint8_t filter_meta[BLOSC2_MAX_FILTERS]; + uint8_t reserved2; + uint8_t blosc2_flags; + } blosc_header; + blosc_header header; + memset (&header, 0, sizeof (blosc_header)); + memcpy (&header, data, BLOSC_MIN_HEADER_LENGTH); + int v = header.version; +} +#else +static void +_check_blosc_header (const void* data, size_t size) +{} +#endif + +// ---------------------------------------------------------------------------- + +/** + * \brief Get the zstd level from the context + * + * \param encode in: the encode pipeline + * \return the compression level + */ +static const int32_t +_get_zstd_level (const exr_encode_pipeline_t* encode) +{ + // Get the compression level from the context + int32_t zstd_level = 5; // default compression level + exr_result_t rv = exr_get_zstd_compression_level ( + encode->context, encode->part_index, &zstd_level); + if (rv != EXR_ERR_SUCCESS) + { + ERROR_MSGV ( + encode, + "[zstd] Failed to get compression level ! Defaulting to %d.", + zstd_level); + } + return zstd_level; +} + +/** + * \brief Read the buffer size from the buffer (4.29 GB max) and move + * the read position to the start of buffer data. + * + * \param data the buffer + * \return int32_t the buffer size in bytes + */ +static int32_t +_read_buffer_size (char** src) +{ + int32_t bufByteSize = 0; + memcpy (&bufByteSize, *src, sizeof (bufByteSize)); + assert (bufByteSize <= INT32_MAX && "read buffer size too large"); + *src += sizeof (bufByteSize); // move read position + return bufByteSize; +} + +/** + * \brief Write the buffer size to the buffer (4.29 GB max) and move the write + * position to the start of buffer data. + * + * \param data the buffer + * \param bufByteSize the buffer size in bytes + */ +static void +_write_buffer_size (char** dst, const int32_t bufByteSize) +{ + assert (bufByteSize <= INT32_MAX && "write buffer size too large"); + memcpy (*dst, &bufByteSize, sizeof (bufByteSize)); + *dst += sizeof (bufByteSize); // move write position +} + +static void +_write_buffer_data (char** dst, const int32_t bufByteSize, const void* src) +{ + assert (bufByteSize <= INT32_MAX && "write data buffer size too large"); + if (bufByteSize > 0) + { + memcpy (*dst, src, bufByteSize); + *dst += bufByteSize; // move write position + } +} + +/** + * \brief compress a buffer using blosc zstd + * + * \param inPtr in: input buffer + * \param inSize in: input buffer size + * \param typeSize in: input buffer type size + * \param outPtr out: output buffer + * \param outPtrSize out: output buffer size + * \param zstdLevel in: compression level + * \return size of compressed buffer (in bytes) + */ +static int32_t +_compress_ztsd_blosc_chunk ( + char* inPtr, + int32_t inSize, + int8_t typeSize, + void* outPtr, + int32_t outPtrSize, + int zstdLevel) +{ + blosc2_cparams cparams = BLOSC2_CPARAMS_DEFAULTS; + cparams.typesize = typeSize; + // clevel 9 is about a 20% increase in compression compared to 5. + // Decompression speed is unchanged. + cparams.clevel = zstdLevel; + cparams.nthreads = 1; + cparams.compcode = BLOSC_ZSTD; // Codec + cparams.splitmode = BLOSC_NEVER_SPLIT; // Split => multithreading, + // not split better compression + + blosc2_context* cctx = blosc2_create_cctx (cparams); + int32_t size = + blosc2_compress_ctx (cctx, inPtr, inSize, outPtr, outPtrSize); + blosc2_free_ctx (cctx); + DBGV ( + " blosc2_compress_ctx size: %.02f%% (%d / %d) ", + (float) size / (float) inSize * 100.f, + size, + inSize); + _check_blosc_header (outPtr, outPtrSize); + return size; +} + +/** + * \brief decompress a zstd blosc chunk + * + * if outPtrSize == 0, the function will malloc the output buffer. + * + * \param inPtr in: input buffer + * \param inSize in: input buffer size + * \param outPtr out: output buffer + * \param outPtrSize out: output buffer size + * \return size of decompressed buffer (in bytes) + */ +static int32_t +_uncompress_ztsd_blosc_chunk ( + const char* inPtr, int32_t inSize, void** outPtr, int32_t outPtrSize) +{ + blosc2_dparams dparams = BLOSC2_DPARAMS_DEFAULTS; + dparams.nthreads = 1; + blosc2_context* dctx = blosc2_create_dctx (dparams); + int32_t size = + blosc2_decompress_ctx (dctx, inPtr, inSize, *outPtr, outPtrSize); + DBGV ( + " blosc2_decompress_ctx size: %.02f%% (%d / %d) ", + (float) size / (float) inSize * 100.f, + size, + inSize); + + blosc2_free_ctx (dctx); + _check_blosc_header (inPtr, inSize); + return size; +} + +/** + * \brief Returns the cumulative number of samples per line, irrespective of + * subsampling. + * + * \param zstd_data in: relevant encoding/decoding infos + * \param sampleTableIsCumulative in: true if sampleCountTable is cumulative + * \param sampleCountTable in: samples per pixel for chunk + */ +static void +_cumulative_samples_per_line ( + zstd_data_t* zstd_data, + bool sampleTableIsCumulative, + const int32_t* sampleCountTable) +{ + int32_t* table = zstd_data->sample_count_per_line_cumulative; + table[0] = 0; + + if (!sampleCountTable) + { + // assume 1 sample per pixel if we get an empty table (non-deep data) + for (int y = 0; y < zstd_data->height; ++y) + table[y + 1] = table[y] + zstd_data->width; + return; + } + + // deep data: arbitrary number of samples per pixel + if (sampleTableIsCumulative) + { + for (int y = 0; y < zstd_data->height; ++y) + { + table[y + 1] = sampleCountTable[y + 1]; + } + } + else + { + for (int y = 0; y < zstd_data->height; ++y) + { + table[y + 1] = table[y]; + for (int x = 0; x < zstd_data->width; ++x) + table[y + 1] += sampleCountTable[y * zstd_data->width + x]; + } + } +} + +/** + * \brief Compute the number of active pixels for a given line. + * + * \param line_number the line number we are interested in + * \param width the line's width + * \param x_sampling x sub-sampling + * \param y_sampling y sub-sampling + * \return int32_t The number of active pixels for the given line + */ +static int32_t +_active_pixels_for_line ( + const int32_t line_number, + const int32_t width, + const int32_t x_sampling, + const int32_t y_sampling) +{ + // Check if this line is active based on y_sampling + if ((line_number % y_sampling) != 0) return 0; + + // For active lines, return subsampled width + return (width + x_sampling - 1) / x_sampling; +} + +/** + * \brief Compute the number of active pixels for a given buffer. + * + * \param width the buffer's width + * \param height the buffer's height + * \param x_sampling x sub-sampling + * \param y_sampling y sub-sampling + * \return int32_t + */ +static int32_t +_num_subsampled_pixels ( + const int32_t width, + const int32_t height, + const int32_t x_sampling, + const int32_t y_sampling) +{ + // Round up division to handle non-perfect multiples + int32_t effective_width = (width + x_sampling - 1) / x_sampling; + int32_t effective_height = (height + y_sampling - 1) / y_sampling; + return effective_width * effective_height; +} + +/** + * \brief returns start offsets for all planar channels, + * + * \param zstd_data in: relevant encoding/decoding infos + */ +static void +_channel_offsets (zstd_data_t* zstd_data) +{ + // count the number of half precision channels and precompute the number of + // pixels per channel for the whole buffer. + int32_t n_half = 0; + int32_t sample_counts[zstd_data->channel_count]; + for (int32_t ch = 0; ch < zstd_data->channel_count; ++ch) + { + if (zstd_data->channels[ch].type_size == EXR_HALF_PRECISION_SIZE) + ++n_half; + if (zstd_data->is_deep) + { + // deep data: subsampling is not supported, i.e. same value for + // all channels. + sample_counts[ch] = + zstd_data->sample_count_per_line_cumulative[zstd_data->height]; + } + else + sample_counts[ch] = _num_subsampled_pixels ( + zstd_data->width, + zstd_data->height, + zstd_data->channels[ch].x_sampling, + zstd_data->channels[ch].y_sampling); + } + + // sort channels so half precision comes before single precision + int32_t ch_indices[zstd_data->channel_count]; + int32_t ih = 0, is = n_half; + for (int32_t i = 0; i < zstd_data->channel_count; ++i) + { + if (zstd_data->channels[i].type_size == EXR_HALF_PRECISION_SIZE) + ch_indices[ih++] = i; + else + ch_indices[is++] = i; + } + + // store offsets for each channel + int32_t h_size = 0; + for (int32_t i = 0; i < n_half; i++) + { + int32_t idx = ch_indices[i]; + zstd_data->channels[idx].offset = h_size; + zstd_data->channels[idx].byte_size = + sample_counts[idx] * EXR_HALF_PRECISION_SIZE; + h_size += zstd_data->channels[idx].byte_size; + } + + int32_t s_size = h_size; + for (int32_t i = n_half; i < zstd_data->channel_count; i++) + { + int32_t idx = ch_indices[i]; + zstd_data->channels[idx].offset = s_size; + zstd_data->channels[idx].byte_size = + sample_counts[idx] * EXR_SINGLE_PRECISION_SIZE; + s_size += zstd_data->channels[idx].byte_size; + } + + zstd_data->buffers_size[0] = h_size; + zstd_data->buffers_size[1] = s_size - h_size; +} + +/** + * \brief Unpack a scanline/tile buffer into a size-sorted single buffer. + * + * Half channels come first, followed by float/uint channels). outSplitPos marks + * the begining of float/uint data. + * The buffers contain per-channel planar (multi-line) data. + * Supports deep files by handling arbitrary number of samples per pixel. + * + * Example: 2 lines of 3 pixels with half r, float g, half b, uint i channels: + * + * before: + * [rh rh rh gs gs gs bh bh bh is is is rh rh rh gs gs gs bh bh bh is is is] + * after: + * [rh rh rh rh rh rh bh bh bh bh bh bh gs gs gs gs gs gs is is is is is is] + * ^ ^ + * outPos outSplitPos + * + * \param encode in: the encoding pipeline struct + */ +static void +_unpack_channels ( + zstd_data_t* zstd_data, + const void* inPtr, + const uint64_t inSize, + void* outPtr) +{ + _channel_offsets (zstd_data); + + // Pre-compute line sample counts per channel + int32_t* lineSampleCounts = malloc ( + zstd_data->height * zstd_data->channel_count * sizeof (int32_t)); + for (int32_t ln = 0; ln < zstd_data->height; ++ln) + { + for (int32_t ch = 0; ch < zstd_data->channel_count; ++ch) + { + int32_t idx = ln * zstd_data->channel_count + ch; + lineSampleCounts[idx] = + zstd_data->is_deep + ? zstd_data->sample_count_per_line_cumulative[ln + 1] - + (ln > 0 + ? zstd_data->sample_count_per_line_cumulative[ln] + : 0) + : _active_pixels_for_line ( + ln, + zstd_data->width, + zstd_data->channels[ch].x_sampling, + zstd_data->channels[ch].y_sampling); + } + } + + char* inPos = (char*) inPtr; + char* outPtrPos = (char*) outPtr; + int32_t outPtrSize = 0; + int32_t* chWritePos = calloc (zstd_data->channel_count, sizeof (int32_t)); + + // Main unpacking loop + for (int32_t ln = 0; ln < zstd_data->height; ++ln) + { + for (int32_t ch = 0; ch < zstd_data->channel_count; ++ch) + { + const int32_t lineSampleCount = + lineSampleCounts[ln * zstd_data->channel_count + ch]; + const size_t copySize = + zstd_data->channels[ch].type_size * lineSampleCount; + + char* outPos = + zstd_data->is_deep + ? outPtrPos + zstd_data->channels[ch].offset + + zstd_data->sample_count_per_line_cumulative[ln] * + zstd_data->channels[ch].type_size + : outPtrPos + zstd_data->channels[ch].offset + + chWritePos[ch]; + + assert (outPtrSize <= inSize && "out of bounds"); + + memcpy (outPos, inPos, copySize); + inPos += copySize; + chWritePos[ch] += copySize; + outPtrSize += copySize; + } + } + + free (lineSampleCounts); + free (chWritePos); + + zstd_data->buffers[0] = outPtr; + zstd_data->buffers[1] = outPtr + zstd_data->buffers_size[0]; +} + +/** + * \brief compress a full buffer. + * + * \param encode in: the encoding pipeline + * \param inSize in: size of packed input buffer + * \param outPtr out: pointer to output buffer + * \param outSize out: size of compressed output buffer + * \return exr_result_t: EXR_ERR_SUCCESS or EXR_ERR_OUT_OF_MEMORY + */ +static exr_result_t +_compress_zstd ( + const exr_encode_pipeline_t* encode, + const size_t inSize, + void* outPtr, + size_t* outPtrSize) +{ + exr_result_t rv = EXR_ERR_SUCCESS; + const zstd_data_t* zstd_data = (zstd_data_t*) encode->encoding_user_data; + + // Get the compression level from the context + const int32_t zstd_level = _get_zstd_level (encode); + + DBG ("_compress_zstd: "); + + // compress buffers here + *outPtrSize = 0; + char* writePos = (char*) outPtr; + void* scratch = malloc (inSize); // FIXME: use pipeline scratch buffer + if (scratch == NULL) + RETURN_ERROR ( + encode, + EXR_ERR_OUT_OF_MEMORY, + "[zstd] Failed to allocate compression scratch buffer"); + int8_t bufsDataSize[2] = { + EXR_HALF_PRECISION_SIZE, EXR_SINGLE_PRECISION_SIZE}; + + for (uint32_t b = 0; b < 2; ++b) + { + // compress buffer section + int32_t compressedSize = 0; // may be negative + if (zstd_data->buffers_size[b] > 0) + { + compressedSize = _compress_ztsd_blosc_chunk ( + zstd_data->buffers[b], + zstd_data->buffers_size[b], + bufsDataSize[b], + scratch, + inSize, + zstd_level); + + if (compressedSize < 0) + { + free (scratch); + RETURN_ERRORV ( + encode, + EXR_ERR_COMPRESSION_FAILED, + "[zstd] Failed to compress buffer ! byte size = %d", + bufsDataSize[b]); + } + + if (compressedSize >= zstd_data->buffers_size[b]) + { + // zstd failed to shrink the data + DBGV ( + "[ UNCOMPRESSED BUFFER ! (%s) %d <= %d ] ", + b == 0 ? "half" : "single", + zstd_data->buffers_size[b], + compressedSize); + // we store the uncompressed data + compressedSize = zstd_data->buffers_size[b]; + } + } + + // compute storable data size + // use a signed int32_t to store the size of the compressed buffer (+/- 2.1 GB max) + bool isSmaller = compressedSize < zstd_data->buffers_size[b]; + int32_t outSize = isSmaller ? compressedSize + : zstd_data->buffers_size[b]; + + // check if we have enough space to store the compressed buffer + size_t projected_size = *outPtrSize + outSize + sizeof (uint32_t); + if (projected_size >= inSize) + { + DBG (" Not compressing !\n"); + free (scratch); + return EXR_ERR_COMPRESSION_FAILED; + } + + // Write header (buffer size). + // NOTE: If the data has NOT been compressed, we store a negative size + // and the decompression function will just memcpy the buffer. + _write_buffer_size (&writePos, isSmaller ? outSize : -outSize); + *outPtrSize += sizeof (outSize); // add size header + *outPtrSize += (size_t) outSize; // add data size - can be 0 + _write_buffer_data ( + &writePos, outSize, isSmaller ? scratch : zstd_data->buffers[b]); + + if (b == 0) { DBGV (" half_size = %d ", compressedSize); } + else { DBGV (" single_size = %d ", compressedSize); } + } + free (scratch); + + assert (*outPtrSize < inSize); + + DBGV ( + " total_size = %zu / %zu %s\n", + *outPtrSize, + inSize, + *outPtrSize >= inSize ? "TOO_BIG" : ""); + + return rv; +} + +static void +_decoding_vars (exr_decode_pipeline_t* decode) +{ + zstd_data_t* zstd_data = (zstd_data_t*) decode->decoding_user_data; + + int32_t lineCount = decode->chunk.height; + + bool sampleCountTableIsCumulative = + (decode->decode_flags & EXR_DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL); + + _cumulative_samples_per_line ( + zstd_data, sampleCountTableIsCumulative, decode->sample_count_table); + + _channel_offsets (zstd_data); +} +/** + * \brief pack an unpacked buffer into a scanline/tile buffer. + * + * Half channels come first, followed by float/uint channels). outSplitPos marks + * the begining of float/uint data. + * The input buffers contain per-channel planar (multi-line) data. + * Supports deep files by handling arbitrary number of samples per pixel. + * + * Example + * 2 lines of 3 pixels with half r, float g, half b, uint i channels, 1 sample + * per pixel (non-deep file). + * + * before: + * [rh rh rh rh rh rh bh bh bh bh bh bh gs gs gs gs gs gs is is is is is is] + * + * after: + * [rh rh rh gs gs gs bh bh bh is is is rh rh rh gs gs gs bh bh bh is is is] + * + * \param zstd_data in: relevant encoding/decoding + * \param inPtr in: pointer to input buffer + * \param outPtr out: pointer to output buffer + * \param outPtrSize in: size of output buffer + */ +static void +_pack_channels ( + zstd_data_t* zstd_data, + const void* inPtr, + void* outPtr, + const uint64_t outPtrSize) +{ + // Pre-compute line sample counts per channel + int32_t* lineSampleCounts = malloc ( + zstd_data->height * zstd_data->channel_count * sizeof (int32_t)); + for (int32_t ln = 0; ln < zstd_data->height; ++ln) + { + for (int32_t ch = 0; ch < zstd_data->channel_count; ++ch) + { + int32_t idx = ln * zstd_data->channel_count + ch; + lineSampleCounts[idx] = + zstd_data->is_deep + ? zstd_data->sample_count_per_line_cumulative[ln + 1] - + (ln > 0 + ? zstd_data->sample_count_per_line_cumulative[ln] + : 0) + : _active_pixels_for_line ( + ln, + zstd_data->width, + zstd_data->channels[ch].x_sampling, + zstd_data->channels[ch].y_sampling); + } + } + + char* outPos = (char*) outPtr; + size_t totalByteSize = 0; + int32_t* chReadPos = calloc (zstd_data->channel_count, sizeof (int32_t)); + + // Main packing loop + for (int32_t ln = 0; ln < zstd_data->height; ++ln) + { + for (int32_t ch = 0; ch < zstd_data->channel_count; ++ch) + { + const int32_t lineSampleCount = + lineSampleCounts[ln * zstd_data->channel_count + ch]; + const size_t copySize = + zstd_data->channels[ch].type_size * lineSampleCount; + + const char* inPos = + zstd_data->is_deep + ? (const char*) inPtr + zstd_data->channels[ch].offset + + zstd_data->sample_count_per_line_cumulative[ln] * + zstd_data->channels[ch].type_size + : (const char*) inPtr + zstd_data->channels[ch].offset + + chReadPos[ch]; + + memcpy (outPos, inPos, copySize); + outPos += copySize; + chReadPos[ch] += copySize; + totalByteSize += copySize; + assert (totalByteSize <= outPtrSize); + } + } + + free (lineSampleCounts); + assert (totalByteSize == outPtrSize); +} + +/** + * \brief decompress a zstd buffer. + * + * \param decode in: decoding context + * \param inPtr in: pointer to input buffer + * \param outPtr in: pointer to output buffer + * \param outPtrByteSize out: output buffer size + * \param decompressedByteSizes out: size of decompressed halsf and single buffers + * \return EXR_ERR_SUCCESS or EXR_ERR_DECOMPRESSION_FAILED + */ +static exr_result_t +_uncompress_zstd ( + exr_decode_pipeline_t* decode, + const char* inPtr, + void* outPtr, + const int32_t outPtrByteSize, + const int32_t decompressedByteSizes[2]) +{ + DBG ("_uncompress_zstd: "); + + int32_t outSize = 0; + char* inPtrPos = (char*) inPtr; + int32_t decompSize = outPtrByteSize; + char* decompPtr = outPtr; + char* decompWritePos = decompPtr; + + for (int32_t b = 0; b < 2; ++b) + { + int32_t compressedBufSize = _read_buffer_size (&inPtrPos); + + if (b == 0) { DBGV (" half size = %d ", compressedBufSize); } + else { DBGV (" single size = %d ", compressedBufSize); } + + // there is no data of the current type + if (compressedBufSize == 0) continue; + + // read buffer + int32_t expectedSize = decompressedByteSizes ? decompressedByteSizes[b] + : 0; + int32_t decompressedSize = 0; + if (compressedBufSize > 0) + { + decompressedSize = _uncompress_ztsd_blosc_chunk ( + inPtrPos, + compressedBufSize, + (void**) &decompWritePos, + decompSize); + + if (decompressedSize < 0) + RETURN_ERROR ( + decode, + EXR_ERR_DECOMPRESSION_FAILED, + "[zstd] bloc2 failed to decompress !") + } + else if (compressedBufSize < 0) + { + // we stored the original data because zstd didn't manage to compress it. + compressedBufSize = -compressedBufSize; // make it positive + decompressedSize = compressedBufSize; + memcpy (decompWritePos, inPtrPos, compressedBufSize); + } + + if (decompressedSize != expectedSize) + RETURN_ERRORV ( + decode, + EXR_ERR_CORRUPT_CHUNK, + "[zstd] bloc2 decompressed size %d != expected %d !\n", + decompressedSize, + expectedSize); + + // book-keeping + outSize += decompressedSize; // update reported size + inPtrPos += compressedBufSize; // move read position + decompWritePos += decompressedSize; // move write position + } + + DBGV (" total size = %d\n", outSize); + + // return outSize; + return EXR_ERR_SUCCESS; +} + +/* Public functions --------------------------------------------------------- */ + +/** + * \brief Compression function called by the C API. + * + * \param encode pointer to the encoding pipeline. + * \return exr_result_t + */ +exr_result_t +internal_exr_apply_zstd (exr_encode_pipeline_t* encode) +{ + exr_result_t rv = EXR_ERR_SUCCESS; + + assert (encode->packed_bytes > 0); + assert (encode->packed_buffer != NULL); + + bool isSampleTable = + encode->packed_buffer == encode->packed_sample_count_table; + + if (isSampleTable) + { + // compress the sample table as a single-precision int buffer + // + const int32_t level = _get_zstd_level (encode); + + const int32_t compressedSize = _compress_ztsd_blosc_chunk ( + encode->packed_buffer, + encode->packed_bytes, + EXR_SINGLE_PRECISION_SIZE, + encode->compressed_buffer, + encode->compressed_bytes, + level); + + if (compressedSize < 0) + RETURN_ERROR ( + encode, + EXR_ERR_COMPRESSION_FAILED, + "[zstd] Failed to compress sampleTable !!"); + + encode->compressed_bytes = compressedSize; + } + else + { + // allocate scratch to store the unpacked input buffer + 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) + RETURN_ERRORV ( + encode, + rv, + "[zstd] Failed to allocate scratch buffer 1 (%" PRIu64 + " bytes)", + encode->packed_bytes); + + rv = _new_encoding_zstd_data (encode); + if (rv != EXR_ERR_SUCCESS) return rv; + + zstd_data_t* zstd_data = (zstd_data_t*) encode->encoding_user_data; + + // build the per-line cumulative sample table for this chunk. + bool sampleCountTableIsCumulative = + (encode->encode_flags & + EXR_ENCODE_DATA_SAMPLE_COUNTS_ARE_INDIVIDUAL); + + _cumulative_samples_per_line ( + zstd_data, + sampleCountTableIsCumulative, + encode->sample_count_table); + + _unpack_channels ( + zstd_data, + encode->packed_buffer, + encode->packed_bytes, + encode->scratch_buffer_1); + assert (zstd_data->buffers[0] != NULL && zstd_data->buffers[1] != NULL); + assert ( + zstd_data->buffers_size[0] + zstd_data->buffers_size[1] <= + encode->packed_bytes); + +#if (0) + // validate unpacking / packing is a no-op + rv = internal_encode_alloc_buffer ( + encode, + EXR_TRANSCODE_BUFFER_SCRATCH1, + &(encode->scratch_buffer_2), + &(encode->scratch_alloc_size_2), + encode->packed_bytes); + _pack_channels ( + zstd_data, + encode->scratch_buffer_1, + encode->scratch_buffer_2, + encode->packed_bytes); + assert ( + memcmp ( + encode->packed_buffer, + encode->scratch_buffer_2, + encode->packed_bytes) == 0 && + "pack/unpack failed"); +#endif + + rv = _compress_zstd ( + encode, + encode->packed_bytes, + encode->compressed_buffer, + &(encode->compressed_bytes)); + + if (rv == EXR_ERR_COMPRESSION_FAILED) + { + memcpy ( + encode->compressed_buffer, + encode->packed_buffer, + encode->packed_bytes); + encode->compressed_bytes = encode->packed_bytes; + rv = EXR_ERR_SUCCESS; + DBGV ( + "internal_exr_apply_zstd: compression failed, comp = %zu, packed = %llu\n", + encode->compressed_bytes, + encode->packed_bytes); + } + assert ( + encode->compressed_bytes <= encode->packed_bytes && + "compressed size is too big"); + } + + return rv; +} + +/** + * \brief Decompression function called by C API. + * + * \param decode pointer to the decoding pipeline. + * \param compressed_data pointer to compressed data buffer. + * \param comp_buf_size size of compressed data buffer. + * \param uncompressed_data pointer to uncompressed data buffer. + * \param uncompressed_size expected size of uncompressed data buffer. + * \return exr_result_t return EXR_ERR_SUCCESS or EXR_ERR_DECOMPRESSION_FAILED. + */ +exr_result_t +internal_exr_undo_zstd ( + exr_decode_pipeline_t* decode, + const void* compressed_data, + uint64_t comp_buf_size, + void* uncompressed_data, + uint64_t uncompressed_size) +{ + exr_result_t rv = EXR_ERR_SUCCESS; + + if (comp_buf_size == 0 || compressed_data == NULL) + { + decode->bytes_decompressed = 0; + return EXR_ERR_SUCCESS; + } + + bool isSampleTable = compressed_data == decode->sample_count_table; + + if (isSampleTable) + { + rv = _uncompress_zstd ( + decode, + compressed_data, + uncompressed_data, + (int32_t) uncompressed_size, + NULL); + if (rv != EXR_ERR_SUCCESS) return rv; + } + else + { + // zstd didn't manage to shrink packed data and we stored packed data. + if (comp_buf_size == uncompressed_size) + { + DBG ("internal_exr_undo_zstd: MEMCPY !\n") + memcpy (uncompressed_data, compressed_data, uncompressed_size); + decode->bytes_decompressed = uncompressed_size; + return EXR_ERR_SUCCESS; + } + + // we have an unpacked buffer to decompress. + + rv = internal_decode_alloc_buffer ( + decode, + EXR_TRANSCODE_BUFFER_SCRATCH1, + &(decode->scratch_buffer_1), + &(decode->scratch_alloc_size_1), + uncompressed_size); + if (rv != EXR_ERR_SUCCESS) + { + ERROR_MSGV ( + decode, + "[zstd] Failed to allocate scratch buffer 1 (%" PRIu64 + " bytes)", + uncompressed_size); + return rv; + } + + // setup our user data + rv = _new_decoding_zstd_data (decode); + zstd_data_t* zstd_data = (zstd_data_t*) decode->decoding_user_data; + + // compute expected sizes for validation + _decoding_vars (decode); + + rv = _uncompress_zstd ( + decode, + compressed_data, + decode->scratch_buffer_1, + uncompressed_size, + zstd_data->buffers_size); + if (rv != EXR_ERR_SUCCESS) + { + _free_decoding_zstd_data (decode); + return rv; + } + + _pack_channels ( + zstd_data, + decode->scratch_buffer_1, + uncompressed_data, + uncompressed_size); + + _free_decoding_zstd_data (decode); + } + + return EXR_ERR_SUCCESS; +} \ No newline at end of file From 5b22213caf8a3a6f968bb8dccf634c6072a19b18 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 10:48:59 +0300 Subject: [PATCH 05/46] install Blosc2 Signed-off-by: Todica Ionut --- cmake/OpenEXRSetup.cmake | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index 68037a1a3e..eac10539d6 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -251,6 +251,87 @@ else() endif() +####################################### +# Find or install Blosc2 +####################################### + +set(MINIMUM_BLOSC2_VERSION 2.11.0) +option(OPENEXR_FORCE_INTERNAL_BLOSC2 [=[Force using installed Blosc2.]=] OFF) + +set(BLOSC2_REPO "https://github.com/Blosc/c-blosc2.git" CACHE STRING "Repo path for blosc2 source") +set(BLOSC2_TAG "v${MINIMUM_BLOSC2_VERSION}" CACHE STRING "Tag to use for blosc2 source repo") + +# Try to find a local bloc2 install if allowed to. +if(NOT OPENEXR_FORCE_INTERNAL_BLOSC2) + message(STATUS "Blosc2: Looking for local install...") + set(CMAKE_IGNORE_PATH "${CMAKE_CURRENT_BINARY_DIR}/_deps/blosc2-src/config;${CMAKE_CURRENT_BINARY_DIR}/_deps/blosc2-build/config") + find_package(Blosc2 ${MINIMUM_BLOSC2_VERSION}) + set(CMAKE_IGNORE_PATH) +endif() + +if(NOT TARGET Blosc2::blosc2_static AND NOT Blosc2_FOUND) + # we didn't find a local install: let's get it from its repository. + if(OPENEXR_FORCE_INTERNAL_BLOSC2) + message(STATUS "Blosc2: forced internal, installing from ${BLOSC2_REPO} (${BLOSC2_TAG})") + else() + message(STATUS "Blosc2: no local blosc2 found, installing from ${BLOSC2_REPO} (${BLOSC2_TAG})") + endif() + + # configure the blosc2 build + set(BUILD_BENCHMARKS OFF CACHE INTERNAL "no benchmarks") + set(BUILD_EXAMPLES OFF CACHE INTERNAL "no examples") + set(BUILD_FUZZERS OFF CACHE INTERNAL "no fuzzer") + set(BUILD_SHARED OFF CACHE INTERNAL "no shared library") + set(BUILD_TESTS OFF CACHE INTERNAL "no tests") + + include(FetchContent) + FetchContent_Declare(Blosc2 + GIT_REPOSITORY "${BLOSC2_REPO}" + GIT_TAG "${BLOSC2_TAG}" + GIT_SHALLOW ON + GIT_PROGRESS ON) + + FetchContent_GetProperties(Blosc2) + if(NOT Blosc2_POPULATED) + message(STATUS "Blosc2: Downloading ${BLOSC2_TAG} from ${BLOSC2_REPO}...") + FetchContent_Populate(Blosc2) + add_subdirectory(${blosc2_SOURCE_DIR} ${blosc2_BINARY_DIR}) + else() + message(STATUS "Blosc2: repo code has already been downloaded.") + endif() + + # the install creates this but if we're using the library locally we + # haven't installed the header files yet, so need to extract those + # and make a variable for header only usage + if(TARGET Blosc2::blosc2_static) + message(STATUS "Blosc2: Setting up blosc directories") + + get_target_property(blosc2inc Blosc2::blosc2_static INCLUDE_DIRECTORIES) + set(BLOSC2_INCLUDE_DIRS ${blosc2inc}) + + get_target_property(blosc2libdir Blosc2::blosc2_static BINARY_DIR) + set(BLOSC2_LIB_DIR ${blosc2libdir}) + + if(OPENEXR_RUN_FUZZ_TESTS) + target_compile_options(blosc2_static PUBLIC "-gdwarf-4") + endif() + endif() +else() + message(STATUS "Blosc2: Using installed Blosc2 ${Blosc2_VERSION} from ${Blosc2_DIR}") + # local build + if(TARGET Blosc2::blosc2_static) + message(STATUS "Blosc2: Setting up installed blosc directories") + + get_target_property(blosc2inc Blosc2::blosc2_static INTERFACE_INCLUDE_DIRECTORIES) + _error_if_not_found("INTERFACE_INCLUDE_DIRECTORIES" ${blosc2inc} "") + set(BLOSC2_INCLUDE_DIRS ${blosc2inc}) + + get_target_property(blosc2libdir Blosc2::blosc2_static BINARY_DIR) + _error_if_not_found("BINARY_DIR" ${blosc2libdir} "") + set(BLOSC2_LIB_DIR ${blosc2libdir}) + endif() +endif() + ####################################### # Find or download OpenJPH ####################################### From 00049672712e60510da8f65a259ae29fa61520d1 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 11:08:43 +0300 Subject: [PATCH 06/46] error Add zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/base.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib/OpenEXRCore/base.c b/src/lib/OpenEXRCore/base.c index dda032bf66..a6b62b1e91 100644 --- a/src/lib/OpenEXRCore/base.c +++ b/src/lib/OpenEXRCore/base.c @@ -58,6 +58,8 @@ static const char* the_error_code_names[] = { "EXR_ERR_USE_TILE_NONDEEP_WRITE", "EXR_ERR_INVALID_SAMPLE_DATA", "EXR_ERR_FEATURE_NOT_IMPLEMENTED", + 'EXR_ERR_COMPRESSION_FAILED', + 'EXR_ERR_DECOMPRESSION_FAILED', "EXR_ERR_UNKNOWN"}; static int the_error_code_count = sizeof (the_error_code_names) / sizeof (const char*); @@ -98,6 +100,8 @@ static const char* the_default_errors[] = { "Use non-deep tile write (sample count table invalid for this part type)", "Invalid sample data table value", "Feature not yet implemented, please use C++ library", + "Chunk compression failed", + "Chunk decompression failed", "Unknown error code"}; static int the_default_error_count = sizeof (the_default_errors) / sizeof (const char*); @@ -210,3 +214,21 @@ exr_get_default_dwa_compression_quality (float* q) { if (q) *q = sDefaultDwaLevel; } + + +// 9 is 20% more expensive to compress. Decompression time remains constant. +static int sDefaultZstdLevel = 5; + +void +exr_set_default_zstd_compression_level (int l) +{ + if (l < 0) l = 0; + if (l > 9) l = 9; + sDefaultZstdLevel = l; +} + +void +exr_get_default_zstd_compression_level (int* l) +{ + if (l) *l = sDefaultZstdLevel; +} From 5760525a531fdc44b72e2ed3dc451538aee8609f Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 11:09:37 +0300 Subject: [PATCH 07/46] Fix Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/base.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/OpenEXRCore/base.c b/src/lib/OpenEXRCore/base.c index a6b62b1e91..ecd48749e5 100644 --- a/src/lib/OpenEXRCore/base.c +++ b/src/lib/OpenEXRCore/base.c @@ -58,8 +58,8 @@ static const char* the_error_code_names[] = { "EXR_ERR_USE_TILE_NONDEEP_WRITE", "EXR_ERR_INVALID_SAMPLE_DATA", "EXR_ERR_FEATURE_NOT_IMPLEMENTED", - 'EXR_ERR_COMPRESSION_FAILED', - 'EXR_ERR_DECOMPRESSION_FAILED', + "EXR_ERR_COMPRESSION_FAILED", + "EXR_ERR_DECOMPRESSION_FAILED", "EXR_ERR_UNKNOWN"}; static int the_error_code_count = sizeof (the_error_code_names) / sizeof (const char*); From 049c635802e6ffa0f67bcd4b0c3585afa7f280c5 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 11:11:59 +0300 Subject: [PATCH 08/46] openexr error code Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/openexr_errors.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/OpenEXRCore/openexr_errors.h b/src/lib/OpenEXRCore/openexr_errors.h index 5d74b01f2c..86850b7414 100644 --- a/src/lib/OpenEXRCore/openexr_errors.h +++ b/src/lib/OpenEXRCore/openexr_errors.h @@ -63,6 +63,8 @@ typedef enum EXR_ERR_USE_TILE_NONDEEP_WRITE, EXR_ERR_INVALID_SAMPLE_DATA, EXR_ERR_FEATURE_NOT_IMPLEMENTED, + EXR_ERR_COMPRESSION_FAILED, + EXR_ERR_DECOMPRESSION_FAILED, EXR_ERR_UNKNOWN } exr_error_code_t; From 4f907e840dab8150d8475a03e7cad63450ba346a Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 11:15:56 +0300 Subject: [PATCH 09/46] ZSTD part Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/part.c | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/lib/OpenEXRCore/part.c b/src/lib/OpenEXRCore/part.c index 814fd5ceee..7348670645 100644 --- a/src/lib/OpenEXRCore/part.c +++ b/src/lib/OpenEXRCore/part.c @@ -629,3 +629,47 @@ exr_set_dwa_compression_level (exr_context_t ctxt, int part_index, float level) return EXR_UNLOCK_AND_RETURN (rv); } + +/**************************************/ + +exr_result_t +exr_get_zstd_compression_level ( + exr_const_context_t ctxt, int part_index, int32_t* level) +{ + int32_t l; + EXR_LOCK_WRITE_AND_DEFINE_PART (part_index); + l = part->zstd_compression_level; + if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); + + if (!level) return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); + *level = l; + return EXR_ERR_SUCCESS; +} + +/**************************************/ + +exr_result_t +exr_set_zstd_compression_level (exr_context_t ctxt, int part_index, int level) +{ + exr_result_t rv; + EXR_LOCK_AND_DEFINE_PART (part_index); + + if (ctxt->mode != EXR_CONTEXT_WRITE && ctxt->mode != EXR_CONTEXT_TEMPORARY) + return EXR_UNLOCK_AND_RETURN ( + ctxt->standard_error (ctxt, EXR_ERR_NOT_OPEN_WRITE)); + + if (level > 0 && level <= 9) + { + part->zstd_compression_level = level; + rv = EXR_ERR_SUCCESS; + } + else + { + return EXR_UNLOCK_AND_RETURN (ctxt->report_error ( + ctxt, + EXR_ERR_INVALID_ARGUMENT, + "Invalid zstd quality level specified")); + } + + return EXR_UNLOCK_AND_RETURN (rv); +} From 0ee5700e86994104f464ab1f36436e02e16eff00 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 19:57:41 +0300 Subject: [PATCH 10/46] Zstd build Signed-off-by: Todica Ionut --- BUILD.bazel | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index 4798edb037..978760568e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -213,6 +213,7 @@ cc_library( "src/lib/OpenEXRCore/internal_win32_file_impl.h", "src/lib/OpenEXRCore/internal_xdr.h", "src/lib/OpenEXRCore/internal_zip.c", + "src/lib/OpenEXRCore/internal_zstd.c", "src/lib/OpenEXRCore/memory.c", "src/lib/OpenEXRCore/opaque.c", "src/lib/OpenEXRCore/openexr_version.h", @@ -264,12 +265,14 @@ cc_library( ":windows": [], "//conditions:default": [ "-pthread", + "-ldl", ], }), visibility = ["//visibility:public"], deps = [ "@imath", "@openjph", + "@c-blosc2", "@libdeflate//:deflate", ], ) @@ -373,6 +376,7 @@ cc_library( "src/lib/OpenEXR/ImfWav.cpp", "src/lib/OpenEXR/ImfZip.cpp", "src/lib/OpenEXR/ImfZipCompressor.cpp", + "src/lib/OpenEXR/ImfZstdCompressor.cpp", ], hdrs = [ "src/lib/Iex/IexConfig.h", @@ -493,6 +497,7 @@ cc_library( "src/lib/OpenEXR/ImfXdr.h", "src/lib/OpenEXR/ImfZip.h", "src/lib/OpenEXR/ImfZipCompressor.h", + "src/lib/OpenEXR/ImfZstdCompressor.h", "src/lib/OpenEXR/OpenEXRConfig.h", "src/lib/OpenEXR/OpenEXRConfigInternal.h", ], @@ -509,6 +514,7 @@ cc_library( ":windows": [], "//conditions:default": [ "-pthread", + "-ldl", ], }), visibility = ["//visibility:public"], @@ -517,6 +523,7 @@ cc_library( ":OpenEXRCore", "@imath", "@openjph" + "@c-blosc2", ], ) From 5a10de3403b0e5c01857ba9124dc02d9953364d8 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:02:05 +0300 Subject: [PATCH 11/46] OpenEXR blosc2 Signed-off-by: Todica Ionut --- cmake/OpenEXR.pc.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/OpenEXR.pc.in b/cmake/OpenEXR.pc.in index 6dbdc6234d..1550f95b19 100644 --- a/cmake/OpenEXR.pc.in +++ b/cmake/OpenEXR.pc.in @@ -9,12 +9,13 @@ libdir=@PKG_CONFIG_INSTALL_LIBDIR@ includedir=@PKG_CONFIG_INSTALL_INCLUDEDIR@ OpenEXR_includedir=${includedir}/OpenEXR libsuffix=@LIB_SUFFIX_DASH@ +libbuildsuffix=@LIB_BUILD_SUFFIX@ Name: OpenEXR Description: OpenEXR image library Version: @OPENEXR_VERSION@ -Libs: @exr_pthread_libs@ -L${libdir} -lOpenEXR${libsuffix} -lOpenEXRUtil${libsuffix} -lOpenEXRCore${libsuffix} -lIex${libsuffix} -lIlmThread${libsuffix} +Libs: @exr_pthread_libs@ -L${libdir} -lOpenEXR${libsuffix} -lOpenEXRUtil${libsuffix} -lOpenEXRCore${libsuffix} -lIex${libsuffix} -lIlmThread${libsuffix} -lblosc2${libbuildsuffix} -ldl Cflags: -I${includedir} -I${OpenEXR_includedir} @exr_pthread_cflags@ Requires: Imath Requires.private: @EXR_DEFLATE_PKGCONFIG_REQUIRES@ @EXR_OPENJPH_PKGCONFIG_REQUIRES@ From 400de684b317277f8c23ed98344472969a873627 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:07:40 +0300 Subject: [PATCH 12/46] CMakeLists blosc2 Signed-off-by: Todica Ionut --- src/lib/OpenEXR/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/OpenEXR/CMakeLists.txt b/src/lib/OpenEXR/CMakeLists.txt index 4f3d4be16b..c5694cc2fa 100644 --- a/src/lib/OpenEXR/CMakeLists.txt +++ b/src/lib/OpenEXR/CMakeLists.txt @@ -125,6 +125,8 @@ openexr_define_library(OpenEXR ImfZip.h ImfZipCompressor.cpp ImfZipCompressor.h + ImfZstdCompressor.cpp + ImfZstdCompressor.h HEADERS ImfAcesFile.h ImfArray.h @@ -222,6 +224,7 @@ openexr_define_library(OpenEXR ImfXdr.h DEPENDENCIES Imath::Imath + Blosc2::blosc2_static OpenEXR::Config OpenEXR::Iex OpenEXR::IlmThread From e9d09ee12702a71aa1996f07b7bec1ce05e14d49 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:11:33 +0300 Subject: [PATCH 13/46] CMakeLists blosc2 Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/OpenEXRCore/CMakeLists.txt b/src/lib/OpenEXRCore/CMakeLists.txt index 77b3f404f6..52a4eed2b7 100644 --- a/src/lib/OpenEXRCore/CMakeLists.txt +++ b/src/lib/OpenEXRCore/CMakeLists.txt @@ -40,6 +40,7 @@ openexr_define_library(OpenEXRCore internal_rle.c internal_zip.c + internal_zstd.c internal_pxr24.c internal_b44.c internal_b44_table.c @@ -104,6 +105,7 @@ openexr_define_library(OpenEXRCore DEPENDENCIES Imath::Imath + Blosc2::blosc2_static ) if (DEFINED EXR_DEFLATE_LIB) From 7e49cd1e81feefa242a5105f45a71bf7689d4fe8 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:26:37 +0300 Subject: [PATCH 14/46] zstd compression level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/internal_structs.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/OpenEXRCore/internal_structs.h b/src/lib/OpenEXRCore/internal_structs.h index 888aadbad1..673fd1ad3f 100644 --- a/src/lib/OpenEXRCore/internal_structs.h +++ b/src/lib/OpenEXRCore/internal_structs.h @@ -125,6 +125,7 @@ struct _priv_exr_part_t int32_t zip_compression_level; float dwa_compression_level; + int32_t zstd_compression_level; int32_t num_tile_levels_x; int32_t num_tile_levels_y; @@ -207,6 +208,7 @@ struct _priv_exr_context_t int default_zip_level; float default_dwa_quality; + int default_zstd_level; void* real_user_data; void* user_data; From de5d0ae0d8db8a62abbf92ae1311490993df2044 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:29:18 +0300 Subject: [PATCH 15/46] Add ZSTD Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfCRgbaFile.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/OpenEXR/ImfCRgbaFile.h b/src/lib/OpenEXR/ImfCRgbaFile.h index f94eba7399..3c23dae92a 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_ZSTD_COMPRESSION 12 +#define IMF_NUM_COMPRESSION_METHODS 13 /* ** Channels; values must be the same as in Imf::RgbaChannels. From 7f481ff26313bcd817677959f71f1e68b836fd53 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:32:21 +0300 Subject: [PATCH 16/46] Compression ZSTD Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfCompression.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/OpenEXR/ImfCompression.cpp b/src/lib/OpenEXR/ImfCompression.cpp index cc18fd939b..ff9a20d542 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 ( + "zstd", + "blosc zstd lossless compression, one scan line at a time.", + 1, + false, + true), }; // clang-format on @@ -206,6 +212,7 @@ static const std::map CompressionNameToId = { {"dwab", Compression::DWAB_COMPRESSION}, {"htj2k256", Compression::HTJ2K256_COMPRESSION}, {"htj2k32", Compression::HTJ2K32_COMPRESSION}, + {"zstd", Compression::ZSTD_COMPRESSION}, }; #define UNKNOWN_COMPRESSION_ID_MSG "INVALID COMPRESSION ID" From 9eb941831430c59860e92fd6bfc2ab263e9a3fd1 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:36:21 +0300 Subject: [PATCH 17/46] blosc zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfCompression.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/OpenEXR/ImfCompression.h b/src/lib/OpenEXR/ImfCompression.h index a25c202a2a..c18e2b67e6 100644 --- a/src/lib/OpenEXR/ImfCompression.h +++ b/src/lib/OpenEXR/ImfCompression.h @@ -55,6 +55,9 @@ enum IMF_EXPORT_ENUM Compression HTJ2K32_COMPRESSION = 11, // High-Throughput JPEG2000 (HTJ2K), 32 scanlines + ZSTD_COMPRESSION = 12, // blosc zstd lossless compression, one scan line + // at a time. + NUM_COMPRESSION_METHODS // number of different compression methods }; @@ -92,6 +95,9 @@ IMF_EXPORT void setDefaultZipCompressionLevel (int level); /// Controls the default quality level for the DWA lossy compression IMF_EXPORT void setDefaultDwaCompressionLevel (float level); +/// Controls the default zstd compression level used. Zstd is used for +IMF_EXPORT void setDefaultZstdCompressionLevel (int level); + OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT #endif From ea09170195903665e9d3db32e9a2966202d09ae6 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:39:50 +0300 Subject: [PATCH 18/46] zstd Compression Level Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfHeader.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/OpenEXR/ImfHeader.cpp b/src/lib/OpenEXR/ImfHeader.cpp index ded1250207..3006483fd5 100644 --- a/src/lib/OpenEXR/ImfHeader.cpp +++ b/src/lib/OpenEXR/ImfHeader.cpp @@ -71,9 +71,11 @@ struct CompressionRecord { exr_get_default_zip_compression_level (&zip_level); exr_get_default_dwa_compression_quality (&dwa_level); + exr_get_default_zstd_compression_level (&zstd_level); } int zip_level; float dwa_level; + int zstd_level; }; // NB: This is extra complicated than one would normally write to // handle scenario that seems to happen on MacOS/Windows (probably @@ -693,6 +695,18 @@ Header::zipCompressionLevel () const return retrieveCompressionRecord (this).zip_level; } +int& +Header::zstdCompressionLevel () +{ + return retrieveCompressionRecord (this).zstd_level; +} + +int +Header::zstdCompressionLevel () const +{ + return retrieveCompressionRecord (this).zstd_level; +} + float& Header::dwaCompressionLevel () { From bca495436c8c94a699d805392a45c8cdc14377e0 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Tue, 14 Oct 2025 20:42:11 +0300 Subject: [PATCH 19/46] zstd Compression Level Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfHeader.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/OpenEXR/ImfHeader.h b/src/lib/OpenEXR/ImfHeader.h index e965bffbc9..89345216e3 100644 --- a/src/lib/OpenEXR/ImfHeader.h +++ b/src/lib/OpenEXR/ImfHeader.h @@ -285,6 +285,10 @@ class IMF_EXPORT_TYPE Header float& dwaCompressionLevel (); IMF_EXPORT float dwaCompressionLevel () const; + IMF_EXPORT + int& zstdCompressionLevel (); + IMF_EXPORT + int zstdCompressionLevel () const; //----------------------------------------------------- // Access to required attributes for multipart files From 92594570886b205af52ec45a0a0bf64fe01165e0 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Wed, 15 Oct 2025 08:41:52 +0300 Subject: [PATCH 20/46] zstd Compression Level Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfCompressor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/OpenEXR/ImfCompressor.cpp b/src/lib/OpenEXR/ImfCompressor.cpp index 5ba51ed028..e741ec9953 100644 --- a/src/lib/OpenEXR/ImfCompressor.cpp +++ b/src/lib/OpenEXR/ImfCompressor.cpp @@ -17,6 +17,7 @@ #include "ImfPizCompressor.h" #include "ImfPxr24Compressor.h" #include "ImfRleCompressor.h" +#include "ImfZstdCompressor.h" #include "ImfZipCompressor.h" #include "ImfZip.h" @@ -54,6 +55,7 @@ Compressor::Compressor ( exr_set_zip_compression_level (_ctxt, 0, hdr.zipCompressionLevel ()); exr_set_dwa_compression_level (_ctxt, 0, hdr.dwaCompressionLevel ()); + exr_set_zstd_compression_level (_ctxt, 0, hdr.zstdCompressionLevel ()); exr_compression_t hdrcomp; if (EXR_ERR_SUCCESS != exr_get_compression (_ctxt, 0, &hdrcomp)) From 29e33e82ae66cef7de2093e73f33048a186ff1df Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Wed, 15 Oct 2025 08:44:19 +0300 Subject: [PATCH 21/46] set Zstd Level Signed-off-by: Todica Ionut --- src/lib/OpenEXR/ImfContextInit.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/OpenEXR/ImfContextInit.h b/src/lib/OpenEXR/ImfContextInit.h index ba7abff4c5..e9634a7d97 100644 --- a/src/lib/OpenEXR/ImfContextInit.h +++ b/src/lib/OpenEXR/ImfContextInit.h @@ -105,6 +105,12 @@ class IMF_EXPORT_TYPE ContextInitializer return *this; } + ContextInitializer& setZstdLevel (int zl) noexcept + { + _initializer.zstd_level = zl; + return *this; + } + ContextInitializer& strictHeaderValidation (bool onoff) noexcept { setFlag (EXR_CONTEXT_FLAG_STRICT_HEADER, onoff); From 2eecde05dad41b2ea7898dd549d9b3e813abc016 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Wed, 15 Oct 2025 08:45:41 +0300 Subject: [PATCH 22/46] zstd level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/context.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/OpenEXRCore/context.c b/src/lib/OpenEXRCore/context.c index ae0e6a563a..ac2e6b056c 100644 --- a/src/lib/OpenEXRCore/context.c +++ b/src/lib/OpenEXRCore/context.c @@ -129,6 +129,7 @@ fill_context_data (const exr_context_initializer_t* ctxtdata) { inits.zip_level = ctxtdata->zip_level; inits.dwa_quality = ctxtdata->dwa_quality; + inits.zstd_level = ctxtdata->zstd_level; } if (ctxtdata->size >= sizeof (struct _exr_context_initializer_v3)) { From a4b8174bb80c78df86052cac27c619c14b0996a0 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Wed, 15 Oct 2025 08:49:49 +0300 Subject: [PATCH 23/46] zstd level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/internal_structs.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/OpenEXRCore/internal_structs.c b/src/lib/OpenEXRCore/internal_structs.c index a84bc12277..ffe37dde4c 100644 --- a/src/lib/OpenEXRCore/internal_structs.c +++ b/src/lib/OpenEXRCore/internal_structs.c @@ -232,6 +232,7 @@ internal_exr_add_part ( part->zip_compression_level = f->default_zip_level; part->dwa_compression_level = f->default_dwa_quality; + part->zstd_compression_level = f->default_zstd_level; /* put it into the part table */ if (ncount > 1) @@ -382,10 +383,14 @@ internal_exr_alloc_context ( exr_get_default_zip_compression_level (&ret->default_zip_level); exr_get_default_dwa_compression_quality (&ret->default_dwa_quality); + exr_get_default_zstd_compression_level (&ret->default_zstd_level); + if (initializers->zip_level >= 0) ret->default_zip_level = initializers->zip_level; if (initializers->dwa_quality >= 0.f) ret->default_dwa_quality = initializers->dwa_quality; + if (initializers->zstd_level >= 0) + ret->default_zstd_level = initializers->zstd_level; if (initializers->flags & EXR_CONTEXT_FLAG_STRICT_HEADER) ret->strict_header = 1; From 500321c3528db2e9b7d22de2a3504c79ed5d10a9 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 08:44:10 +0300 Subject: [PATCH 24/46] context zstd level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/openexr_context.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/OpenEXRCore/openexr_context.h b/src/lib/OpenEXRCore/openexr_context.h index d2dcdf0c87..b18845aec6 100644 --- a/src/lib/OpenEXRCore/openexr_context.h +++ b/src/lib/OpenEXRCore/openexr_context.h @@ -319,6 +319,12 @@ typedef struct _exr_context_initializer_v3 */ float dwa_quality; + /** Initialize a field specifying what the default zstd compression level + * should be for this context. See exr_set_default_zstd_compresion_level() + * to set it for all contexts. + */ + int zstd_level; + /** Initialize with a bitwise or of the various context flags */ int flags; @@ -353,7 +359,7 @@ typedef struct _exr_context_initializer_v3 /* clang-format off */ /** @brief Simple macro to initialize the context initializer with default values. */ #define EXR_DEFAULT_CONTEXT_INITIALIZER \ - { sizeof (exr_context_initializer_t), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -1.f, 0, { 0, 0, 0, 0 } } + { sizeof (exr_context_initializer_t), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -1.f, -1, 0, { 0, 0, 0, 0 } } /* clang-format on */ /** @} */ /* context function pointer declarations */ From ad2165d645b430800d3f29e1d11ce81087cca4bb Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 08:45:53 +0300 Subject: [PATCH 25/46] zstd compression level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/openexr_part.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib/OpenEXRCore/openexr_part.h b/src/lib/OpenEXRCore/openexr_part.h index 3df0d73251..45b831bd1a 100644 --- a/src/lib/OpenEXRCore/openexr_part.h +++ b/src/lib/OpenEXRCore/openexr_part.h @@ -234,6 +234,28 @@ EXR_EXPORT exr_result_t exr_get_dwa_compression_level ( EXR_EXPORT exr_result_t exr_set_dwa_compression_level (exr_context_t ctxt, int part_index, float level); +/** @brief Retrieve the zstd compression level used for the specified part. + * + * This only applies when the compression method is zstd. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so will be at the default value when just + * reading a file. + */ +EXR_EXPORT exr_result_t exr_get_zstd_compression_level ( + exr_const_context_t ctxt, int part_index, int* level); + +/** @brief Set the zstd compression method used for the specified part. + * + * This only applies when the compression method is zstd. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so this value will be ignored when + * reading a file. + */ +EXR_EXPORT exr_result_t +exr_set_zstd_compression_level (exr_context_t ctxt, int part_index, int level); + /**************************************/ /** @defgroup PartMetadata Functions to get and set metadata for a particular part. From c4a5f20ac6ed7462cfd8092e1cc7df1c1acc2d42 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 08:51:37 +0300 Subject: [PATCH 26/46] get zstd compression level test Signed-off-by: Todica Ionut --- src/test/OpenEXRCoreTest/write.cpp | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/OpenEXRCoreTest/write.cpp b/src/test/OpenEXRCoreTest/write.cpp index 0cfad5af72..304d003932 100644 --- a/src/test/OpenEXRCoreTest/write.cpp +++ b/src/test/OpenEXRCoreTest/write.cpp @@ -446,6 +446,41 @@ testWriteBaseHeader (const std::string& tempdir) EXRCORE_TEST_RVAL (exr_get_dwa_compression_level (outf, 0, &dlev)); EXRCORE_TEST (dlev == 420.f); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_MISSING_CONTEXT_ARG, + exr_get_zstd_compression_level (NULL, 0, NULL)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_ARGUMENT_OUT_OF_RANGE, + exr_get_zstd_compression_level (outf, -1, NULL)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_ARGUMENT_OUT_OF_RANGE, + exr_get_zstd_compression_level (outf, 5, NULL)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_INVALID_ARGUMENT, + exr_get_zstd_compression_level (outf, 0, NULL)); + int slev = -1; + EXRCORE_TEST_RVAL (exr_get_zstd_compression_level (outf, 0, &slev)); + EXRCORE_TEST (slev == 5); + + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_MISSING_CONTEXT_ARG, + exr_set_zstd_compression_level (NULL, 0, 5)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_ARGUMENT_OUT_OF_RANGE, + exr_set_zstd_compression_level (outf, -1, 5)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_ARGUMENT_OUT_OF_RANGE, + exr_set_zstd_compression_level (outf, 5, 5)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_INVALID_ARGUMENT, + exr_set_zstd_compression_level (outf, 0, -1)); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_INVALID_ARGUMENT, + exr_set_zstd_compression_level (outf, 0, 10)); + EXRCORE_TEST_RVAL (exr_set_zstd_compression_level (outf, 0, 1)); + EXRCORE_TEST_RVAL (exr_get_zstd_compression_level (outf, 0, &slev)); + EXRCORE_TEST (slev == 1); + EXRCORE_TEST_RVAL (exr_finish (&outf)); remove (outfn.c_str ()); From d5c8494568062ac314381bb952f0e89a2865237d Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 08:53:42 +0300 Subject: [PATCH 27/46] zstd level test Signed-off-by: Todica Ionut --- src/test/OpenEXRCoreTest/read.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/OpenEXRCoreTest/read.cpp b/src/test/OpenEXRCoreTest/read.cpp index 946fc40024..c0f7299c4c 100644 --- a/src/test/OpenEXRCoreTest/read.cpp +++ b/src/test/OpenEXRCoreTest/read.cpp @@ -193,6 +193,12 @@ testReadMeta (const std::string& tempdir) EXRCORE_TEST_RVAL_FAIL ( EXR_ERR_NOT_OPEN_WRITE, exr_set_dwa_compression_level (f, 0, 42.f)); + int slev = -1; + EXRCORE_TEST_RVAL (exr_get_zstd_compression_level (f, 0, &slev)); + EXRCORE_TEST (slev == 5); + EXRCORE_TEST_RVAL_FAIL ( + EXR_ERR_NOT_OPEN_WRITE, exr_set_zstd_compression_level (f, 0, 5)); + exr_finish (&f); } From 9f63ad45b99b3ecefae21f3adffb253c11df53d9 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 09:00:10 +0300 Subject: [PATCH 28/46] Deep ScanLine test zstd Signed-off-by: Todica Ionut --- src/test/OpenEXRTest/testDeepScanLineBasic.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/OpenEXRTest/testDeepScanLineBasic.cpp b/src/test/OpenEXRTest/testDeepScanLineBasic.cpp index f502cf14a2..4b41185355 100644 --- a/src/test/OpenEXRTest/testDeepScanLineBasic.cpp +++ b/src/test/OpenEXRTest/testDeepScanLineBasic.cpp @@ -554,13 +554,14 @@ readWriteTest ( for (int i = 0; i < testTimes; i++) { - int compressionIndex = i % 3; + int compressionIndex = i % 4; Compression compression; switch (compressionIndex) { case 0: compression = NO_COMPRESSION; break; case 1: compression = RLE_COMPRESSION; break; case 2: compression = ZIPS_COMPRESSION; break; + case 3: compression = ZSTD_COMPRESSION; break; } generateRandomFile ( @@ -603,6 +604,8 @@ testCompressionTypeChecks () h.sanityCheck (); h.compression () = RLE_COMPRESSION; h.sanityCheck (); + h.compression () = ZSTD_COMPRESSION; + h.sanityCheck (); cout << "accepted valid compression types\n"; // From 6c3183385a6786183bcc6a6fb5d1185791032cc8 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 09:04:47 +0300 Subject: [PATCH 29/46] test Large ZSTD Signed-off-by: Todica Ionut --- src/test/OpenEXRTest/testLargeDataWindowOffsets.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/OpenEXRTest/testLargeDataWindowOffsets.cpp b/src/test/OpenEXRTest/testLargeDataWindowOffsets.cpp index 0df8a92ee8..60d6b09338 100644 --- a/src/test/OpenEXRTest/testLargeDataWindowOffsets.cpp +++ b/src/test/OpenEXRTest/testLargeDataWindowOffsets.cpp @@ -244,8 +244,13 @@ writefile ( pixelCount / ((long long) (hdr.dataWindow ().max.y) - (long long) (hdr.dataWindow ().min.y)); +// FIXME ZSTD ! +#if (1) + hdr.compression () = ZSTD_COMPRESSION; +#else hdr.compression () = Compression (random_int (static_cast (NUM_COMPRESSION_METHODS))); +#endif hdr.channels () = setupBuffer (hdr, channels, pt, buf, true); remove (filename.c_str ()); @@ -356,7 +361,8 @@ test (int testCount) Header hdr = writefile (writeFrameBuf, channels, writetypes); Box2i dw = hdr.dataWindow (); - cout << "dataWindow: " << dw.min << ' ' << dw.max << ' '; + cout << "dataWindow: " << dw.min << ' ' << dw.max << ' ' + << "comp: " << hdr.compression () << ' '; cout.flush (); FrameBuffer readFrameBuf; readfile (readFrameBuf, channels, readTypes); @@ -365,7 +371,7 @@ test (int testCount) // only the first 5 compression methods are guaranteed lossless on both half and float. // skip comparison for other types // - if (hdr.compression () < 5) + if (hdr.compression () < 5 || hdr.compression () == ZSTD_COMPRESSION) { if (compare (readFrameBuf, writeFrameBuf, dw)) { cout << " OK "; } else { cout << " FAIL" << endl; } From 91db8f22edd209e9cae0b3037cb997f069b5ea5a Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 09:09:16 +0300 Subject: [PATCH 30/46] zstd Compression Level Signed-off-by: Todica Ionut --- src/bin/exrmetrics/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/exrmetrics/main.cpp b/src/bin/exrmetrics/main.cpp index e066d6f366..8ef31f4755 100644 --- a/src/bin/exrmetrics/main.cpp +++ b/src/bin/exrmetrics/main.cpp @@ -64,7 +64,7 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) " -t n Use a pool of n worker threads for processing files.\n" " Default is single threaded (no thread pool)\n" "\n" - " -l level set DWA or ZIP compression level\n" + " -l level set DWA, ZIP or ZSTD compression level\n" "\n" " -z,--compression list list of compression methods to test\n" " (" From 661cda2bfc097a1b776beb3d15d83b664e600393 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 16 Oct 2025 09:15:17 +0300 Subject: [PATCH 31/46] exrmetrics zstd Signed-off-by: Todica Ionut --- src/bin/exrmetrics/exrmetrics.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bin/exrmetrics/exrmetrics.cpp b/src/bin/exrmetrics/exrmetrics.cpp index 7ea81c9f6e..49318e95f6 100644 --- a/src/bin/exrmetrics/exrmetrics.cpp +++ b/src/bin/exrmetrics/exrmetrics.cpp @@ -1022,9 +1022,10 @@ exrmetrics ( outHeaders[p].zipCompressionLevel () = level; compressionSet = true; break; - // case ZSTD_COMPRESSION : - // outHeader.zstdCompressionLevel()=level; - // break; + case ZSTD_COMPRESSION : + outHeader[p].zstdCompressionLevel () = level; + compressionSet = true; + break; default: break; } } From f9b2efcc8ec7fb0efc33e0d64a40d1a2665fd821 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:27:00 +0300 Subject: [PATCH 32/46] zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/internal_compress.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/OpenEXRCore/internal_compress.h b/src/lib/OpenEXRCore/internal_compress.h index 1cdc484a85..ab0b4abee5 100644 --- a/src/lib/OpenEXRCore/internal_compress.h +++ b/src/lib/OpenEXRCore/internal_compress.h @@ -33,6 +33,8 @@ exr_result_t internal_exr_apply_dwaa (exr_encode_pipeline_t* encode); exr_result_t internal_exr_apply_dwab (exr_encode_pipeline_t* encode); +exr_result_t internal_exr_apply_zstd (exr_encode_pipeline_t* encode); + #ifdef __cplusplus extern "C" { #endif From 465df394178a9927c6d077b002a56625d8c35358 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:27:50 +0300 Subject: [PATCH 33/46] zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/internal_decompress.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/OpenEXRCore/internal_decompress.h b/src/lib/OpenEXRCore/internal_decompress.h index 6251c2d9c1..c86de66ffc 100644 --- a/src/lib/OpenEXRCore/internal_decompress.h +++ b/src/lib/OpenEXRCore/internal_decompress.h @@ -73,6 +73,13 @@ exr_result_t internal_exr_undo_dwab ( void* uncompressed_data, uint64_t uncompressed_size); +exr_result_t internal_exr_undo_zstd ( + exr_decode_pipeline_t* decode, + const void* compressed_data, + uint64_t comp_buf_size, + void* uncompressed_data, + uint64_t uncompressed_size); + #ifdef __cplusplus extern "C" { #endif From 8d12714402d857ee360a058a53ed9f22d9ee869a Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:29:02 +0300 Subject: [PATCH 34/46] zstd Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/openexr_attr.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/OpenEXRCore/openexr_attr.h b/src/lib/OpenEXRCore/openexr_attr.h index 8c51f96c43..dc056106a0 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_ZSTD = 12, EXR_COMPRESSION_LAST_TYPE /**< Invalid value, provided for range checking. */ } exr_compression_t; From c37655490296ef4c4207dfecf9878613dffb2b22 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:30:38 +0300 Subject: [PATCH 35/46] zstd compression level Signed-off-by: Todica Ionut --- src/lib/OpenEXRCore/openexr_base.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/OpenEXRCore/openexr_base.h b/src/lib/OpenEXRCore/openexr_base.h index 8df304235b..a145323aa2 100644 --- a/src/lib/OpenEXRCore/openexr_base.h +++ b/src/lib/OpenEXRCore/openexr_base.h @@ -136,6 +136,17 @@ EXR_EXPORT void exr_set_default_dwa_compression_quality (float q); */ EXR_EXPORT void exr_get_default_dwa_compression_quality (float* q); +/** @brief Assigns a default zstd compression level. + * + * This value may be controlled separately on each part, but this + * global control determines the initial value. + */ +EXR_EXPORT void exr_set_default_zstd_compression_level (int l); + +/** @brief Retrieve the global default zstd compression value + */ +EXR_EXPORT void exr_get_default_zstd_compression_level (int* l); + /** @} */ /** From 23226f87ec4f79632e4628bf1c56e9373a2f3931 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:37:02 +0300 Subject: [PATCH 36/46] test ZSTD Signed-off-by: Todica Ionut --- src/test/OpenEXRCoreTest/compression.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/test/OpenEXRCoreTest/compression.cpp b/src/test/OpenEXRCoreTest/compression.cpp index 58e1cc632c..5d2c33ff4e 100644 --- a/src/test/OpenEXRCoreTest/compression.cpp +++ b/src/test/OpenEXRCoreTest/compression.cpp @@ -1433,6 +1433,7 @@ doWriteRead ( case EXR_COMPRESSION_RLE: case EXR_COMPRESSION_ZIP: case EXR_COMPRESSION_ZIPS: + case EXR_COMPRESSION_ZSTD: restore.compareExact (p, "orig", "C loaded C"); break; case EXR_COMPRESSION_PIZ: @@ -1696,6 +1697,12 @@ testDWABCompression (const std::string& tempdir) testComp (tempdir, EXR_COMPRESSION_DWAB); } +void +testZstdCompression (const std::string& tempdir) +{ + testComp (tempdir, EXR_COMPRESSION_ZSTD); +} + struct ht_channel_map_tests { exr_coding_channel_info_t channels[6]; int channel_count; @@ -1761,3 +1768,7 @@ testDeepZIPCompression (const std::string& tempdir) void testDeepZIPSCompression (const std::string& tempdir) {} + +void +testDeepZstdCompression (const std::string& tempdir) +{} From 9471de731c30837be0c45b5252a11217cf28c275 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:39:06 +0300 Subject: [PATCH 37/46] test Deep Zstd Signed-off-by: Todica Ionut --- src/test/OpenEXRCoreTest/compression.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/OpenEXRCoreTest/compression.h b/src/test/OpenEXRCoreTest/compression.h index d85f326c10..2407364887 100644 --- a/src/test/OpenEXRCoreTest/compression.h +++ b/src/test/OpenEXRCoreTest/compression.h @@ -22,10 +22,12 @@ void testB44Compression (const std::string& tempdir); void testB44ACompression (const std::string& tempdir); void testDWAACompression (const std::string& tempdir); void testDWABCompression (const std::string& tempdir); +void testZstdCompression (const std::string& tempdir); void testHTChannelMap (const std::string& tempdir); void testDeepNoCompression (const std::string& tempdir); void testDeepZIPCompression (const std::string& tempdir); void testDeepZIPSCompression (const std::string& tempdir); +void testDeepZstdCompression (const std::string& tempdir); #endif // OPENEXR_CORE_TEST_COMPRESSION_H From 84aafd7eb263b0ba4a35776c847a75167387a7d7 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:41:06 +0300 Subject: [PATCH 38/46] test Deep Zstd Signed-off-by: Todica Ionut --- src/test/OpenEXRCoreTest/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/OpenEXRCoreTest/main.cpp b/src/test/OpenEXRCoreTest/main.cpp index 33a13bb7b9..ca4b7d0843 100644 --- a/src/test/OpenEXRCoreTest/main.cpp +++ b/src/test/OpenEXRCoreTest/main.cpp @@ -208,11 +208,13 @@ main (int argc, char* argv[]) TEST (testB44ACompression, "core_compression"); TEST (testDWAACompression, "core_compression"); TEST (testDWABCompression, "core_compression"); + TEST (testZstdCompression, "core_compression"); TEST (testHTChannelMap, "core_compression"); TEST (testDeepNoCompression, "core_compression"); TEST (testDeepZIPCompression, "core_compression"); TEST (testDeepZIPSCompression, "core_compression"); + TEST (testDeepZstdCompression, "core_compression"); // empty dummy test if (helpMode) { From 9a4d8a6f8ee2b037f9bb3fef108c20921beb9863 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:44:54 +0300 Subject: [PATCH 39/46] test ZSTD Signed-off-by: Todica Ionut --- src/test/OpenEXRTest/testCompressionApi.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/OpenEXRTest/testCompressionApi.cpp b/src/test/OpenEXRTest/testCompressionApi.cpp index 85a6993074..cca05b7c94 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/zstd"; 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 ZSTD_COMPRESSION: assert (isLossyCompression (c) == false); break; @@ -76,6 +77,7 @@ testCompressionApi (const string& tempDir) case NO_COMPRESSION: case RLE_COMPRESSION: case ZIPS_COMPRESSION: + case ZSTD_COMPRESSION: assert (isValidDeepCompression (c) == true); break; From edfa8d24e26f7a1d13e9d0b26c518972be2280d9 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 08:51:26 +0300 Subject: [PATCH 40/46] blosc2 Signed-off-by: Todica Ionut --- cmake/OpenEXRSetup.cmake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index eac10539d6..38f3d194dc 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -311,10 +311,6 @@ if(NOT TARGET Blosc2::blosc2_static AND NOT Blosc2_FOUND) get_target_property(blosc2libdir Blosc2::blosc2_static BINARY_DIR) set(BLOSC2_LIB_DIR ${blosc2libdir}) - - if(OPENEXR_RUN_FUZZ_TESTS) - target_compile_options(blosc2_static PUBLIC "-gdwarf-4") - endif() endif() else() message(STATUS "Blosc2: Using installed Blosc2 ${Blosc2_VERSION} from ${Blosc2_DIR}") From b9feaa3f9ca5877c4004b631b0c49515df2ad010 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Fri, 17 Oct 2025 09:17:47 +0300 Subject: [PATCH 41/46] c-blosc2 Signed-off-by: Todica Ionut --- MODULE.bazel | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index ba104373de..7ed642776f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,10 +6,19 @@ module( compatibility_level = 1, ) +archive_override( + module_name = "c-blosc2", + patches = [ + "//bazel:c-blosc2_add_build_file.patch", + "//bazel:c-blosc2_module_dot_bazel.patch", + ], + strip_prefix = "c-blosc2-main", + urls = ["https://github.com/Blosc/c-blosc2/archive/refs/heads/main.zip"], +) + bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "imath", version = "3.2.1") bazel_dep(name = "libdeflate", version = "1.24") bazel_dep(name = "openjph", version = "0.24.1") -bazel_dep(name = "c-blosc2", version = "2.21.3") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.9") From b3906bf2479e9853ae1294f0b1dedbc3aabff623 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Mon, 20 Oct 2025 20:17:33 +0300 Subject: [PATCH 42/46] Fix Blosc2 Signed-off-by: Todica Ionut --- cmake/OpenEXRSetup.cmake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index 38f3d194dc..2b1549c9eb 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -1,6 +1,20 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) Contributors to the OpenEXR Project. +function(_error_if_not_found prop var fallback) + message(STATUS "Blosc2: ${prop} ${var} '${fallback}'") + string(FIND "${var}" "-NOTFOUND" pos) + if(NOT pos EQUAL -1) + if(fallback STREQUAL "") + message(FATAL_ERROR "Blosc2: Property ${prop} not found: ${var}") + else() + string(SUBSTRING "${var}" 0 ${pos} var_name) + message(STATUS "Blosc2: Property ${prop} not found: ${var_name} falling back to '${fallback}'") + set(${var_name} "${fallback}" PARENT_SCOPE) + endif() + endif() +endfunction(_error_if_not_found) + include(GNUInstallDirs) if(NOT "${CMAKE_PROJECT_NAME}" STREQUAL "${PROJECT_NAME}") From 057703a1d63ffa81fc7b8b615687b5668882ba88 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 23 Oct 2025 16:13:54 +0300 Subject: [PATCH 43/46] Fix bulid Signed-off-by: Todica Ionut --- cmake/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 9fbfad1d04..895b0e8b98 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -192,6 +192,7 @@ if(OPENEXR_INSTALL_PKG_CONFIG) function(openexr_pkg_config_help pcinfile) string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) set(LIB_SUFFIX_DASH ${OPENEXR_LIB_SUFFIX}${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}) + set(LIB_BUILD_SUFFIX ${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}) if(OPENEXR_ENABLE_THREADING AND TARGET Threads::Threads) # hrm, can't use properties as they end up as generator expressions # which don't seem to evaluate From ea6fdef0c1b19a56806deb89d0c5fbb11e4b5925 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Sun, 26 Oct 2025 19:09:07 +0200 Subject: [PATCH 44/46] Fix c-blosc2 Signed-off-by: Todica Ionut --- MODULE.bazel | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 7ed642776f..32f6346315 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,19 +6,10 @@ module( compatibility_level = 1, ) -archive_override( - module_name = "c-blosc2", - patches = [ - "//bazel:c-blosc2_add_build_file.patch", - "//bazel:c-blosc2_module_dot_bazel.patch", - ], - strip_prefix = "c-blosc2-main", - urls = ["https://github.com/Blosc/c-blosc2/archive/refs/heads/main.zip"], -) - bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "imath", version = "3.2.1") bazel_dep(name = "libdeflate", version = "1.24") bazel_dep(name = "openjph", version = "0.24.1") +bazel_dep(name = "c-blosc2", version = "2.12.0.bcr.2") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.9") From f6a10975254255ccfdd2956d297fb4fcaeea1114 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Mon, 27 Oct 2025 15:27:25 +0200 Subject: [PATCH 45/46] thread blosc2 Signed-off-by: Todica Ionut --- cmake/OpenEXRConfig.cmake.in | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmake/OpenEXRConfig.cmake.in b/cmake/OpenEXRConfig.cmake.in index 8766472a2e..cd49289188 100644 --- a/cmake/OpenEXRConfig.cmake.in +++ b/cmake/OpenEXRConfig.cmake.in @@ -5,12 +5,10 @@ include(CMakeFindDependencyMacro) -set(openexr_needthreads @OPENEXR_ENABLE_THREADING@) -if (openexr_needthreads) - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_dependency(Threads) -endif() -unset(openexr_needthreads) +# blosc2 needs threads, so we set it irrespective of OPENEXR_ENABLE_THREADING +# which enables threaded processing of requests. +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_dependency(Threads) find_dependency(Imath) @@ -22,5 +20,9 @@ if (@openjph_FOUND@) find_dependency(openjph) endif() +if (@c-blosc2_FOUND@) + find_dependency(c-blosc2) +endif() + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From fd76d605d1dcaf47bdcd58a4a5e9f79988fbce80 Mon Sep 17 00:00:00 2001 From: Todica Ionut Date: Thu, 13 Nov 2025 09:10:26 +0200 Subject: [PATCH 46/46] c-blosc2 2.22 Signed-off-by: Todica Ionut --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 32f6346315..d19c15dcd4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -10,6 +10,6 @@ bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "imath", version = "3.2.1") bazel_dep(name = "libdeflate", version = "1.24") bazel_dep(name = "openjph", version = "0.24.1") -bazel_dep(name = "c-blosc2", version = "2.12.0.bcr.2") +bazel_dep(name = "c-blosc2", version = "2.22.0") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "rules_cc", version = "0.2.9")