From 6578b5cdf960601d158d71b71df34dec88f5cb9e Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Tue, 16 Apr 2024 20:09:12 +1200 Subject: [PATCH 01/12] Add api for C++ integration Add additional API for querying information from the context for integration into the C++ code base. These should be reviewed prior to final release whether they need to persist or are only temporary Signed-off-by: Kimball Thurston --- src/lib/OpenEXRCore/base.c | 2 + src/lib/OpenEXRCore/chunk.c | 17 +++++++- src/lib/OpenEXRCore/context.c | 34 +++++++++++++++- src/lib/OpenEXRCore/internal_attr.h | 2 +- .../OpenEXRCore/internal_win32_file_impl.h | 20 ++-------- src/lib/OpenEXRCore/openexr_chunkio.h | 5 +++ src/lib/OpenEXRCore/openexr_context.h | 15 +++++-- src/lib/OpenEXRCore/openexr_errors.h | 3 +- src/lib/OpenEXRCore/openexr_part.h | 18 +++++++-- src/lib/OpenEXRCore/part.c | 40 +++++++++++++++++++ website/OpenEXRCoreAPI.rst | 4 +- 11 files changed, 129 insertions(+), 31 deletions(-) diff --git a/src/lib/OpenEXRCore/base.c b/src/lib/OpenEXRCore/base.c index 513e73ea2c..dda032bf66 100644 --- a/src/lib/OpenEXRCore/base.c +++ b/src/lib/OpenEXRCore/base.c @@ -49,6 +49,7 @@ static const char* the_error_code_names[] = { "EXR_ERR_ALREADY_WROTE_ATTRS", "EXR_ERR_BAD_CHUNK_LEADER", "EXR_ERR_CORRUPT_CHUNK", + "EXR_ERR_INCOMPLETE_CHUNK_TABLE", "EXR_ERR_INCORRECT_PART", "EXR_ERR_INCORRECT_CHUNK", "EXR_ERR_USE_SCAN_DEEP_WRITE", @@ -88,6 +89,7 @@ static const char* the_default_errors[] = { "File in write mode, but header already written, can no longer edit attributes", "Unexpected or corrupt values in data block leader vs computed value", "Corrupt data block data, unable to decode", + "Chunk offsets table not completely finished writing (incomplete file)", "Previous part not yet finished writing", "Invalid data block to write at this point", "Use deep scanline write with the sample count table arguments", diff --git a/src/lib/OpenEXRCore/chunk.c b/src/lib/OpenEXRCore/chunk.c index b7130f3062..d326d9be73 100644 --- a/src/lib/OpenEXRCore/chunk.c +++ b/src/lib/OpenEXRCore/chunk.c @@ -51,7 +51,7 @@ atomic_compare_exchange_strong ( /**************************************/ -static exr_result_t extract_chunk_table ( +exr_result_t extract_chunk_table ( exr_const_context_t ctxt, exr_const_priv_part_t part, uint64_t** chunktable, @@ -540,7 +540,20 @@ reconstruct_chunk_table ( return firstfailrv; } -static exr_result_t +exr_result_t +exr_get_chunk_table_offset ( + exr_const_context_t ctxt, int part_index, uint64_t* chunk_offset_out) +{ + EXR_READONLY_AND_DEFINE_PART (part_index); + + if (!chunk_offset_out) + return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); + + *chunk_offset_out = part->chunk_table_offset; + return EXR_ERR_SUCCESS; +} + +exr_result_t extract_chunk_table ( exr_const_context_t ctxt, exr_const_priv_part_t part, diff --git a/src/lib/OpenEXRCore/context.c b/src/lib/OpenEXRCore/context.c index 5222e98d3c..6b2c18818c 100644 --- a/src/lib/OpenEXRCore/context.c +++ b/src/lib/OpenEXRCore/context.c @@ -373,7 +373,6 @@ exr_result_t exr_get_file_name (exr_const_context_t ctxt, const char** name) { if (!ctxt) return EXR_ERR_MISSING_CONTEXT_ARG; - if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_lock (ctxt); /* not changeable after construction, no locking needed */ if (name) @@ -383,6 +382,32 @@ exr_get_file_name (exr_const_context_t ctxt, const char** name) return EXR_ERR_SUCCESS; } + return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); +} + +/**************************************/ + +exr_result_t +exr_get_file_version_and_flags (exr_const_context_t ctxt, uint32_t* ver) +{ + if (!ctxt) return EXR_ERR_MISSING_CONTEXT_ARG; + if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_lock (ctxt); + + if (ver) + { + uint32_t flags = ctxt->version; + + if (ctxt->is_multipart) flags |= EXR_MULTI_PART_FLAG; + if (ctxt->max_name_length > EXR_SHORTNAME_MAXLEN) + flags |= EXR_LONG_NAMES_FLAG; + if (ctxt->has_nonimage_data) flags |= EXR_NON_IMAGE_FLAG; + if (ctxt->is_singlepart_tiled) flags |= EXR_TILED_FLAG; + + *ver = flags; + if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); + return EXR_ERR_SUCCESS; + } + if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); } @@ -523,7 +548,12 @@ exr_set_longname_support (exr_context_t ctxt, int onoff) oldval = ctxt->max_name_length; newval = EXR_SHORTNAME_MAXLEN; - if (onoff) newval = EXR_LONGNAME_MAXLEN; + if (onoff) + { + newval = EXR_LONGNAME_MAXLEN; + ctxt->version = 2; + } + else { ctxt->version = 1; } if (oldval > newval) { diff --git a/src/lib/OpenEXRCore/internal_attr.h b/src/lib/OpenEXRCore/internal_attr.h index 71ccd0add7..04eee725e6 100644 --- a/src/lib/OpenEXRCore/internal_attr.h +++ b/src/lib/OpenEXRCore/internal_attr.h @@ -9,7 +9,7 @@ #include "openexr_attr.h" #include "openexr_context.h" -/** +/** * @defgroup InternalAttributeFunctions Functions for manipulating attributes * * The functions are currently internal to the library and are not diff --git a/src/lib/OpenEXRCore/internal_win32_file_impl.h b/src/lib/OpenEXRCore/internal_win32_file_impl.h index ad8c3ce240..7e1e29cc52 100644 --- a/src/lib/OpenEXRCore/internal_win32_file_impl.h +++ b/src/lib/OpenEXRCore/internal_win32_file_impl.h @@ -21,7 +21,7 @@ static exr_result_t print_error_helper ( - exr_context_t pf, + exr_const_context_t pf, exr_result_t errcode, DWORD dw, exr_stream_error_func_ptr_t error_cb, @@ -60,11 +60,7 @@ print_error_helper ( } if (error_cb) - error_cb ( - (exr_const_context_t) pf, - errcode, - "%s", - (const char*) lpDisplayBuf); + error_cb (pf, errcode, "%s", (const char*) lpDisplayBuf); else pf->print_error (pf, errcode, "%s", (const char*) lpDisplayBuf); @@ -75,21 +71,11 @@ print_error_helper ( } static exr_result_t -print_error (exr_context_t pf, exr_result_t errcode, const char* msg) +print_error (exr_const_context_t pf, exr_result_t errcode, const char* msg) { return print_error_helper (pf, errcode, GetLastError (), NULL, msg); } -static exr_result_t -send_error ( - exr_context_t pf, - exr_result_t errcode, - exr_stream_error_func_ptr_t error_cb, - const char* msg) -{ - return print_error_helper (pf, errcode, GetLastError (), error_cb, msg); -} - static wchar_t* widen_filename (exr_context_t file, const char* fn) { diff --git a/src/lib/OpenEXRCore/openexr_chunkio.h b/src/lib/OpenEXRCore/openexr_chunkio.h index 35ed11b966..87bf15a318 100644 --- a/src/lib/OpenEXRCore/openexr_chunkio.h +++ b/src/lib/OpenEXRCore/openexr_chunkio.h @@ -16,6 +16,11 @@ extern "C" { /** @file */ +/** @brief Retrieve the chunk table offset for the part in question. + */ +EXR_EXPORT exr_result_t exr_get_chunk_table_offset ( + exr_const_context_t ctxt, int part_index, uint64_t* chunk_offset_out); + /** * Struct describing raw data information about a chunk. * diff --git a/src/lib/OpenEXRCore/openexr_context.h b/src/lib/OpenEXRCore/openexr_context.h index 195b99cf34..1a8b3b79ec 100644 --- a/src/lib/OpenEXRCore/openexr_context.h +++ b/src/lib/OpenEXRCore/openexr_context.h @@ -19,7 +19,7 @@ extern "C" { /** @file */ -/** +/** * @defgroup Context Context related definitions * * A context is a single instance of an OpenEXR file or stream. Beyond @@ -42,7 +42,7 @@ extern "C" { typedef struct _priv_exr_context_t* exr_context_t; typedef const struct _priv_exr_context_t* exr_const_context_t; -/** +/** * @defgroup ContextFunctions OpenEXR Context Stream/File Functions * * @brief These are a group of function interfaces used to customize @@ -125,6 +125,9 @@ typedef int64_t (*exr_query_size_func_ptr_t) ( * truly a stream, it is up to the provider to implement appropriate * caching of data to give the appearance of being able to seek/read * atomically. + * + * TODO: This does not handle the ability to mmap a file and get to + * zero copy */ typedef int64_t (*exr_read_func_ptr_t) ( exr_const_context_t ctxt, @@ -471,6 +474,12 @@ EXR_EXPORT exr_result_t exr_start_inplace_header_update ( EXR_EXPORT exr_result_t exr_get_file_name (exr_const_context_t ctxt, const char** name); +/** @brief Retrieve the file version and flags the context is for as + * parsed during the start routine. + */ +EXR_EXPORT exr_result_t +exr_get_file_version_and_flags (exr_const_context_t ctxt, uint32_t* ver); + /** @brief Query the user data the context was constructed with. This * is perhaps useful in the error handler callback to jump back into * an object the user controls. @@ -524,7 +533,7 @@ exr_set_longname_support (exr_context_t ctxt, int onoff); * metadata up front, prior to calling the above exr_start_write(), * allow the data to be set, then once this is called, it switches * into a mode where the library assumes the data is now valid. - * + * * It will recompute the number of chunks that will be written, and * reset the chunk offsets. If you modify file attributes or part * information after a call to this, it will error. diff --git a/src/lib/OpenEXRCore/openexr_errors.h b/src/lib/OpenEXRCore/openexr_errors.h index 2092a20f45..5d74b01f2c 100644 --- a/src/lib/OpenEXRCore/openexr_errors.h +++ b/src/lib/OpenEXRCore/openexr_errors.h @@ -16,7 +16,7 @@ extern "C" { /** @file */ -/** +/** * @defgroup ErrorCodes Error Handling * @brief These are a group of definitions related to error handling. * @@ -54,6 +54,7 @@ typedef enum EXR_ERR_ALREADY_WROTE_ATTRS, EXR_ERR_BAD_CHUNK_LEADER, EXR_ERR_CORRUPT_CHUNK, + EXR_ERR_INCOMPLETE_CHUNK_TABLE, EXR_ERR_INCORRECT_PART, EXR_ERR_INCORRECT_CHUNK, EXR_ERR_USE_SCAN_DEEP_WRITE, diff --git a/src/lib/OpenEXRCore/openexr_part.h b/src/lib/OpenEXRCore/openexr_part.h index 3a7b1196db..ca4d59b2e7 100644 --- a/src/lib/OpenEXRCore/openexr_part.h +++ b/src/lib/OpenEXRCore/openexr_part.h @@ -16,7 +16,7 @@ extern "C" { /** @file */ -/** +/** * @defgroup PartInfo Part related definitions. * * A part is a separate entity in the OpenEXR file. This was @@ -123,7 +123,7 @@ EXR_EXPORT exr_result_t exr_get_level_sizes ( * As in the technical documentation for OpenEXR, the chunk is the * generic term for a pixel data block. This is the atomic unit that * this library uses to negotiate data to and from a context. - * + * * This should be used as a basis for splitting up how a file is * processed. Depending on the compression, a different number of * scanlines are encoded in each chunk, and since those need to be @@ -133,6 +133,16 @@ EXR_EXPORT exr_result_t exr_get_level_sizes ( EXR_EXPORT exr_result_t exr_get_chunk_count (exr_const_context_t ctxt, int part_index, int32_t* out); +/** Return whether the chunk table for this part is completely written. + * + * This only validates that all the offsets are valid. + * + * return EXR_ERR_INCOMPLETE_CHUNK_TABLE when incomplete, EXR_ERR_SUCCESS + * if it appears ok, or another error if otherwise problematic + */ +EXR_EXPORT exr_result_t +exr_validate_chunk_table (exr_context_t ctxt, int part_index); + /** Return the number of scanlines chunks for this file part. * * When iterating over a scanline file, this may be an easier metric @@ -266,7 +276,7 @@ EXR_EXPORT exr_result_t exr_attr_declare ( exr_attribute_type_t type, exr_attribute_t** newattr); -/** +/** * @defgroup RequiredAttributeHelpers Required Attribute Utililities * * @brief These are a group of functions for attributes that are @@ -436,7 +446,7 @@ exr_set_chunk_count (exr_context_t ctxt, int part_index, int32_t val); /** @} */ /* required attr group. */ -/** +/** * @defgroup BuiltinAttributeHelpers Attribute utilities for builtin types * * @brief These are a group of functions for attributes that use the builtin types. diff --git a/src/lib/OpenEXRCore/part.c b/src/lib/OpenEXRCore/part.c index 07172b44a2..59ea9874eb 100644 --- a/src/lib/OpenEXRCore/part.c +++ b/src/lib/OpenEXRCore/part.c @@ -365,6 +365,46 @@ exr_get_chunk_count (exr_const_context_t ctxt, int part_index, int32_t* out) /**************************************/ +exr_result_t extract_chunk_table ( + exr_const_context_t ctxt, + exr_const_priv_part_t part, + uint64_t** chunktable, + uint64_t* chunkminoffset); + +exr_result_t +exr_validate_chunk_table (exr_context_t ctxt, int part_index) +{ + exr_result_t rv; + uint64_t chunkmin, maxoff = ((uint64_t) -1); + uint64_t* ctable; + int complete; + EXR_LOCK_WRITE_AND_DEFINE_PART (part_index); + + /* need to read from the file to get the packed chunk size */ + rv = extract_chunk_table (ctxt, part, &ctable, &chunkmin); + + if (rv != EXR_ERR_SUCCESS) return rv; + + if (ctxt->file_size > 0) maxoff = (uint64_t) ctxt->file_size; + complete = 1; + + for (int ci = 0; ci < part->chunk_count; ++ci) + { + uint64_t cchunk = ctable[ci]; + if (cchunk < chunkmin || cchunk >= maxoff) + { + complete = 0; + break; + } + } + + if (!complete) return EXR_ERR_INCOMPLETE_CHUNK_TABLE; + + return EXR_ERR_SUCCESS; +} + +/**************************************/ + exr_result_t exr_get_scanlines_per_chunk ( exr_const_context_t ctxt, int part_index, int32_t* out) diff --git a/website/OpenEXRCoreAPI.rst b/website/OpenEXRCoreAPI.rst index 90aaf856dd..7cb5bca711 100644 --- a/website/OpenEXRCoreAPI.rst +++ b/website/OpenEXRCoreAPI.rst @@ -221,6 +221,7 @@ Chunk Reading Chunks ^^^^^^ +.. doxygenfunction:: exr_get_chunk_table_offset .. doxygenstruct:: exr_chunk_info_t Chunk Writing @@ -260,12 +261,13 @@ Context .. doxygentypedef:: exr_context_t .. doxygentypedef:: exr_const_context_t - + .. doxygenstruct:: _exr_context_initializer_v3 :members: .. doxygentypedef:: exr_context_initializer_t .. doxygenfunction:: exr_get_file_name +.. doxygenfunction:: exr_get_file_version_and_flags .. doxygenfunction:: exr_get_user_data .. doxygenfunction:: exr_register_attr_type_handler From b83043408842354cdf9bc9ceb197c8bd250db447 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Tue, 16 Apr 2024 20:12:52 +1200 Subject: [PATCH 02/12] Add initial context objects These are not final, but should represent concepts we will use in new world of using the core to replace the internals of the C++ Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/CMakeLists.txt | 4 + src/lib/OpenEXR/ImfContext.cpp | 598 +++++++++++++++++++++++++++++ src/lib/OpenEXR/ImfContext.h | 78 ++++ src/lib/OpenEXR/ImfContextInit.cpp | 208 ++++++++++ src/lib/OpenEXR/ImfContextInit.h | 148 +++++++ 5 files changed, 1036 insertions(+) create mode 100644 src/lib/OpenEXR/ImfContext.cpp create mode 100644 src/lib/OpenEXR/ImfContext.h create mode 100644 src/lib/OpenEXR/ImfContextInit.cpp create mode 100644 src/lib/OpenEXR/ImfContextInit.h diff --git a/src/lib/OpenEXR/CMakeLists.txt b/src/lib/OpenEXR/CMakeLists.txt index fe86e87cba..bbeed962c5 100644 --- a/src/lib/OpenEXR/CMakeLists.txt +++ b/src/lib/OpenEXR/CMakeLists.txt @@ -44,6 +44,8 @@ openexr_define_library(OpenEXR ImfCompressionAttribute.cpp ImfCompressor.cpp ImfCompression.cpp + ImfContext.cpp + ImfContextInit.cpp ImfConvert.cpp ImfCRgbaFile.cpp ImfDeepCompositing.cpp @@ -137,6 +139,8 @@ openexr_define_library(OpenEXR ImfCompression.h ImfCompressionAttribute.h ImfCompressor.h + ImfContext.h + ImfContextInit.h ImfConvert.h ImfCRgbaFile.h ImfDeepCompositing.h diff --git a/src/lib/OpenEXR/ImfContext.cpp b/src/lib/OpenEXR/ImfContext.cpp new file mode 100644 index 0000000000..b8f77b9cfd --- /dev/null +++ b/src/lib/OpenEXR/ImfContext.cpp @@ -0,0 +1,598 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#include "ImfContext.h" + +#include "Iex.h" + +// TODO: remove these once we've cleared the legacy stream need +#include "ImfIO.h" +#include "ImfStdIO.h" +#include + +#include +#include + +#include "openexr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER + +namespace +{ + +/// TODO: delete this after porting of other classes has finished +class LegacyStream final : public IStream +{ +public: + LegacyStream ( + const char* fileName, + exr_context_t ctxt, + const exr_context_initializer_t& ctxtinit) + : IStream (fileName) + , _curpos (0) + , _ctxt (ctxt) + , _user_data (ctxtinit.user_data) + , _read_fn (ctxtinit.read_fn) + { + if (!_read_fn) _stdstream = std::make_unique (fileName); + } + ~LegacyStream () override {} + + bool read (char c[/*n*/], int n) override + { + std::lock_guard lk (_mx); + if (_stdstream) return _stdstream->read (c, n); + + int64_t nr = _read_fn (_ctxt, _user_data, c, n, _curpos, nullptr); + if (nr > 0) _curpos += nr; + return nr == n; + } + + uint64_t tellg () override + { + std::lock_guard lk (_mx); + if (_stdstream) return _stdstream->tellg (); + return _curpos; + } + + void seekg (uint64_t pos) override + { + std::lock_guard lk (_mx); + if (_stdstream) return _stdstream->seekg (pos); + _curpos = pos; + } + + void clear () override + { + std::lock_guard lk (_mx); + if (_stdstream) return _stdstream->clear (); + } + +private: + std::mutex _mx; + uint64_t _curpos = 0; + + std::unique_ptr _stdstream; + + exr_context_t _ctxt = nullptr; + void* _user_data = nullptr; + exr_read_func_ptr_t _read_fn = nullptr; +}; // class LegacyStream + +} // namespace +//////////////////////////////////////// + +Context::Context () + : _ctxt (new exr_context_t, [] (exr_context_t* todel) { + exr_finish (todel); + delete todel; + }) +{ + *_ctxt = nullptr; +} + +//////////////////////////////////////// + +void +Context::setLongNameSupport (bool onoff) +{ + if (EXR_ERR_SUCCESS != exr_set_longname_support (*_ctxt, onoff ? 1 : 0)) + { + THROW (IEX_NAMESPACE::ArgExc, "Unable to set long name support flag"); + } +} + +//////////////////////////////////////// + +void +Context::startRead (const char* filename, const ContextInitializer& ctxtinit) +{ + if (*_ctxt) + { + THROW ( + IEX_NAMESPACE::ArgExc, "Context already started, only start once"); + } + + if (EXR_ERR_SUCCESS != + exr_start_read (_ctxt.get (), filename, &(ctxtinit._initializer))) + { + THROW ( + IEX_NAMESPACE::InputExc, + "Unable to open '" << filename << "' for read"); + } + + _prov_stream = ctxtinit._prov_stream; + if (_prov_stream) + { + _prov_stream->seekg (0); + _prov_stream->clear (); + } + else + { + _legacy = std::make_shared ( + filename, *_ctxt, ctxtinit._initializer); + } +} + +//////////////////////////////////////// + +void +Context::startWrite (const char* filename, const ContextInitializer& ctxtinit) +{ + if (*_ctxt) + { + THROW ( + IEX_NAMESPACE::ArgExc, "Context already started, only start once"); + } + + _legacy.reset (); + + if (EXR_ERR_SUCCESS != exr_start_write ( + _ctxt.get (), + filename, + EXR_WRITE_FILE_DIRECTLY, + &(ctxtinit._initializer))) + { + THROW ( + IEX_NAMESPACE::InputExc, + "Unable to open '" << filename << "' for write"); + } +} + +//////////////////////////////////////// + +const char* +Context::fileName () const +{ + const char* filename = nullptr; + + if (EXR_ERR_SUCCESS != exr_get_file_name (*_ctxt, &filename)) + { + THROW (IEX_NAMESPACE::ArgExc, "Unable to get filename from context"); + } + + return filename; +} + +//////////////////////////////////////// + +int +Context::version () const +{ + uint32_t ver = 0; + + if (EXR_ERR_SUCCESS != exr_get_file_version_and_flags (*_ctxt, &ver)) + { + THROW ( + IEX_NAMESPACE::ArgExc, "Unable to get file version from context"); + } + + return static_cast (ver); +} + +//////////////////////////////////////// + +int +Context::partCount () const +{ + int count = 0; + + if (EXR_ERR_SUCCESS != exr_get_count (*_ctxt, &count)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get part count for file '" << fileName () << "'"); + } + + return count; +} + +//////////////////////////////////////// + +exr_storage_t +Context::storage (int partidx) const +{ + exr_storage_t storage = EXR_STORAGE_LAST_TYPE; + + if (EXR_ERR_SUCCESS != exr_get_storage (*_ctxt, partidx, &storage)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get storage type for part " << partidx << " in file '" + << fileName () << "'"); + } + + return storage; +} + +//////////////////////////////////////// + +exr_attr_box2i_t +Context::dataWindow (int partidx) const +{ + exr_attr_box2i_t dw; + + if (EXR_ERR_SUCCESS != exr_get_data_window (*_ctxt, partidx, &dw)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get the data window for part " << partidx << " in file '" + << fileName () << "'"); + } + + return dw; +} + +//////////////////////////////////////// + +const exr_attr_chlist_t* +Context::channels (int partidx) const +{ + const exr_attr_chlist_t* cl; + + if (EXR_ERR_SUCCESS != exr_get_channels (*_ctxt, partidx, &cl)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get the channel list for part " + << partidx << " in file '" << fileName () << "'"); + } + + return cl; +} + +//////////////////////////////////////// + +bool +Context::hasChannel (int partidx, const char* name) const +{ + const exr_attr_chlist_t* cl = channels (partidx); + int32_t len = strlen (name); + + for (int i = 0; i < cl->num_channels; ++i) + { + const exr_attr_chlist_entry_t* curc = cl->entries + i; + if (curc->name.length == len && 0 == memcmp (name, curc->name.str, len)) + { + return true; + } + } + return false; +} + +//////////////////////////////////////// + +exr_lineorder_t +Context::lineOrder (int partidx) const +{ + exr_lineorder_t lo; + + if (EXR_ERR_SUCCESS != exr_get_lineorder (*_ctxt, partidx, &lo)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get the line order for part " << partidx << " in file '" + << fileName () << "'"); + } + + return lo; +} + +//////////////////////////////////////// + +int +Context::attrCount (int partidx) const +{ + int32_t attrcnt = 0; + + if (EXR_ERR_SUCCESS != exr_get_attribute_count (*_ctxt, partidx, &attrcnt)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get attribute for part " << partidx << " in file '" + << fileName () << "'"); + } + + return 0; +} + +//////////////////////////////////////// + +const exr_attribute_t* +Context::getAttr (int partidx, int attridx) const +{ + const exr_attribute_t* attr = nullptr; + + if (EXR_ERR_SUCCESS != + exr_get_attribute_by_index ( + *_ctxt, partidx, EXR_ATTR_LIST_FILE_ORDER, attridx, &attr)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get attribute index " << attridx << " for part " + << partidx << " in file '" + << fileName () << "'"); + } + + return attr; +} + +//////////////////////////////////////// + +const exr_attribute_t* +Context::getAttr (int partidx, const char* name) const +{ + const exr_attribute_t* attr = nullptr; + exr_result_t res; + + res = exr_get_attribute_by_name (*_ctxt, partidx, name, &attr); + + if (res == EXR_ERR_SUCCESS || res == EXR_ERR_NO_ATTR_BY_NAME) return attr; + + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to find attribute '" << name << "' for part " << partidx + << " in file '" << fileName () << "'"); +} + +//////////////////////////////////////// + +bool +Context::chunkTableValid (int partidx) const +{ + return exr_validate_chunk_table (*_ctxt, partidx) == EXR_ERR_SUCCESS; +} + +//////////////////////////////////////// + +Header +Context::header (int partidx) const +{ + Header hdr; + int32_t attrcnt = 0; + const exr_attribute_t* cur = nullptr; + + if (EXR_ERR_SUCCESS != exr_get_attribute_count (*_ctxt, partidx, &attrcnt)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get attribute for part " << partidx << " in file '" + << fileName () << "'"); + } + + for (int32_t idx = 0; idx < attrcnt; ++idx) + { + if (EXR_ERR_SUCCESS != + exr_get_attribute_by_index ( + *_ctxt, partidx, EXR_ATTR_LIST_FILE_ORDER, idx, &cur)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get attribute index " << idx << " for part " + << partidx << " in file '" + << fileName () << "'"); + } + + switch (cur->type) + { + case EXR_ATTR_INT: + hdr.insert (cur->name, IntAttribute (cur->i)); + break; + case EXR_ATTR_DOUBLE: + hdr.insert (cur->name, DoubleAttribute (cur->d)); + break; + case EXR_ATTR_FLOAT: + hdr.insert (cur->name, FloatAttribute (cur->f)); + break; + + case EXR_ATTR_BOX2I: + hdr.insert ( + cur->name, + Box2iAttribute (IMATH_NAMESPACE::Box2i ( + IMATH_NAMESPACE::V2i (cur->box2i->min), + IMATH_NAMESPACE::V2i (cur->box2i->max)))); + break; + case EXR_ATTR_BOX2F: + hdr.insert ( + cur->name, + Box2fAttribute (IMATH_NAMESPACE::Box2f ( + IMATH_NAMESPACE::V2f (cur->box2f->min), + IMATH_NAMESPACE::V2f (cur->box2f->max)))); + break; + + case EXR_ATTR_V2I: + hdr.insert ( + cur->name, + V2iAttribute (IMATH_NAMESPACE::V2i (*(cur->v2i)))); + break; + case EXR_ATTR_V2F: + hdr.insert ( + cur->name, + V2fAttribute (IMATH_NAMESPACE::V2f (*(cur->v2f)))); + break; + case EXR_ATTR_V2D: + hdr.insert ( + cur->name, + V2dAttribute (IMATH_NAMESPACE::V2d (*(cur->v2d)))); + break; + case EXR_ATTR_V3I: + hdr.insert ( + cur->name, + V3iAttribute (IMATH_NAMESPACE::V3i (*(cur->v3i)))); + break; + case EXR_ATTR_V3F: + hdr.insert ( + cur->name, + V3fAttribute (IMATH_NAMESPACE::V3f (*(cur->v3f)))); + break; + case EXR_ATTR_V3D: + hdr.insert ( + cur->name, + V3dAttribute (IMATH_NAMESPACE::V3d (*(cur->v3d)))); + break; + + case EXR_ATTR_STRING: + hdr.insert ( + cur->name, + StringAttribute ( + std::string (cur->string->str, cur->string->length))); + break; + + case EXR_ATTR_COMPRESSION: + hdr.insert ( + cur->name, CompressionAttribute (Compression (cur->uc))); + break; + case EXR_ATTR_ENVMAP: + hdr.insert (cur->name, EnvmapAttribute (Envmap (cur->uc))); + break; + case EXR_ATTR_LINEORDER: + hdr.insert ( + cur->name, LineOrderAttribute (LineOrder (cur->uc))); + break; + + case EXR_ATTR_CHLIST: { + ChannelList& chans = hdr.channels (); + + for (int c = 0; c < cur->chlist->num_channels; ++c) + { + const exr_attr_chlist_entry_t& curc = + cur->chlist->entries[c]; + chans.insert ( + curc.name.str, + Channel ( + PixelType (curc.pixel_type), + curc.x_sampling, + curc.y_sampling, + curc.p_linear != 0)); + } + break; + } + + default: { + THROW (IEX_NAMESPACE::LogicExc, "Not yet implemented"); + } +#if 0 + case EXR_ATTR_CHROMATICITIES: + retval += sizeof (*(cur->chromaticities)); + break; + case EXR_ATTR_FLOAT_VECTOR: + retval += sizeof (float) * (size_t) (cur->floatvector->length); + break; + case EXR_ATTR_KEYCODE: retval += sizeof (*(cur->keycode)); break; + case EXR_ATTR_M33F: retval += sizeof (*(cur->m33f)); break; + case EXR_ATTR_M33D: retval += sizeof (*(cur->m33d)); break; + case EXR_ATTR_M44F: retval += sizeof (*(cur->m44f)); break; + case EXR_ATTR_M44D: retval += sizeof (*(cur->m44d)); break; + case EXR_ATTR_PREVIEW: + retval += (size_t) cur->preview->width * + (size_t) cur->preview->height * (size_t) 4; + break; + case EXR_ATTR_RATIONAL: retval += sizeof (*(cur->rational)); break; + case EXR_ATTR_STRING_VECTOR: + for (int s = 0; s < cur->stringvector->n_strings; ++s) + { + retval += (size_t) cur->stringvector->strings[s].length; + retval += sizeof (int32_t); + } + break; + case EXR_ATTR_TILEDESC: retval += sizeof (*(cur->tiledesc)); break; + case EXR_ATTR_TIMECODE: retval += sizeof (*(cur->timecode)); break; + case EXR_ATTR_OPAQUE: + if (cur->opaque->packed_data) + retval += (size_t) cur->opaque->size; + else if (cur->opaque->unpacked_data) + { + int32_t sz = 0; + rv = + exr_attr_opaquedata_pack (ctxt, cur->opaque, &sz, NULL); + if (rv != EXR_ERR_SUCCESS) return rv; + + retval += (size_t) sz; + } + break; + case EXR_ATTR_UNKNOWN: + case EXR_ATTR_LAST_KNOWN_TYPE: + default: + return ctxt->print_error ( + ctxt, + EXR_ERR_INVALID_ARGUMENT, + "Invalid / unhandled type '%s' for attribute '%s', unable to compute size", + cur->type_name, + cur->name); +#endif + } + } + + return hdr; +} + +IStream* +Context::legacyIStream (int partnum) const +{ + IStream* ret; + uint64_t coff = 0; + + if (EXR_ERR_SUCCESS != exr_get_chunk_table_offset (*_ctxt, partnum, &coff)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to get the chunk table offset for part " + << partnum << " in file '" << fileName () << "'"); + } + // The legacy system was built on a presumption of stateful + // position within a stream, not arbitrary pread semantics + // so we need to seek to the beginning of the chunk table + // (aka end of header) + ret = _prov_stream ? _prov_stream : _legacy.get (); + ret->seekg (coff); + return ret; +} + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT diff --git a/src/lib/OpenEXR/ImfContext.h b/src/lib/OpenEXR/ImfContext.h new file mode 100644 index 0000000000..d17b5095b4 --- /dev/null +++ b/src/lib/OpenEXR/ImfContext.h @@ -0,0 +1,78 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#ifndef INCLUDED_IMF_CONTEXT_H +#define INCLUDED_IMF_CONTEXT_H + +#include "ImfContextInit.h" + +#include "ImfHeader.h" + +#include + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER + +/// @brief Context provides +class IMF_EXPORT_TYPE Context +{ +public: + Context (); + + operator exr_context_t () const noexcept { return *(_ctxt); } + + IMF_EXPORT void + startRead (const char* filename, const ContextInitializer& ctxtinit); + IMF_EXPORT void + startWrite (const char* filename, const ContextInitializer& ctxtinit); + + IMF_EXPORT void setLongNameSupport (bool onoff); + + // generic file values + + IMF_EXPORT const char* fileName () const; + + IMF_EXPORT int version () const; + + IMF_EXPORT int partCount () const; + + IMF_EXPORT exr_storage_t storage (int partidx) const; + + // access to commonly used attributes + + IMF_EXPORT exr_attr_box2i_t dataWindow (int partidx) const; + + IMF_EXPORT const exr_attr_chlist_t* channels (int partidx) const; + IMF_EXPORT bool hasChannel (int partidx, const char* name) const; + + IMF_EXPORT exr_lineorder_t lineOrder (int partidx) const; + + // access to generic attributes + + IMF_EXPORT int attrCount (int partidx) const; + + IMF_EXPORT const exr_attribute_t* getAttr (int partidx, int attridx) const; + IMF_EXPORT const exr_attribute_t* + getAttr (int partidx, const char* name) const; + + // validation and legacy things + + IMF_EXPORT bool chunkTableValid (int partidx) const; + + IMF_EXPORT Header header (int partnum) const; + + // TODO: remove once the rest has been ported + IMF_EXPORT IStream* legacyIStream (int partnum) const; + +private: + std::shared_ptr _ctxt; + + // TODO: remove both these + std::shared_ptr _legacy; + IStream* _prov_stream = nullptr; +}; // class Context + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT + +#endif // INCLUDED_IMF_CONTEXT_H diff --git a/src/lib/OpenEXR/ImfContextInit.cpp b/src/lib/OpenEXR/ImfContextInit.cpp new file mode 100644 index 0000000000..42877eb8d5 --- /dev/null +++ b/src/lib/OpenEXR/ImfContextInit.cpp @@ -0,0 +1,208 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#include "ImfContextInit.h" +#include "ImfIO.h" + +#include +#include +#include +#include + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER + +struct istream_holder +{ + istream_holder (IStream* s) : _stream (s) + { + if (_stream) _cur_offset = _stream->tellg (); + } + +#if ILMTHREAD_THREADING_ENABLED + std::mutex _mx; +#endif + uint64_t _cur_offset = 0; + IStream* _stream; +}; + +static int64_t +istream_read ( + exr_const_context_t ctxt, + void* userdata, + void* buffer, + uint64_t sz, + uint64_t offset, + exr_stream_error_func_ptr_t error_cb) +{ + istream_holder* ih = static_cast (userdata); + + if (sz > INT_MAX) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Stream interface request to read block too large"); + return -1; + } + +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lk{ih->_mx}; +#endif + + if (offset != ih->_cur_offset) + { + ih->_stream->seekg (offset); + ih->_cur_offset = ih->_stream->tellg (); + if (offset != ih->_cur_offset) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Unable to seek to desired offset %" PRIu64, + offset); + return -1; + } + } + + int64_t nread = ih->_cur_offset; + try + { + if (ih->_stream->isMemoryMapped ()) + memcpy ( + buffer, + ih->_stream->readMemoryMapped (static_cast (sz)), + sz); + else + ih->_stream->read ( + static_cast (buffer), static_cast (sz)); + ih->_cur_offset = ih->_stream->tellg (); + nread = ih->_cur_offset - nread; + } + catch (...) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Unable to seek to desired offset %" PRIu64, + offset); + nread = -1; + } + return nread; +} + +static void +istream_destroy (exr_const_context_t ctxt, void* userdata, int failed) +{ + istream_holder* ih = static_cast (userdata); + delete ih; +} + +ContextInitializer& +ContextInitializer::setInputStream (IStream* istr) +{ + _initializer.user_data = new istream_holder{istr}; + _initializer.read_fn = istream_read; + _initializer.size_fn = nullptr; + _initializer.write_fn = nullptr; + _initializer.destroy_fn = istream_destroy; + _ctxt_type = ContextFileType::READ; + _prov_stream = istr; + return *this; +} + +struct ostream_holder +{ + ostream_holder (OStream* s) : _stream (s) + { + if (_stream) _cur_offset = _stream->tellp (); + } + +#if ILMTHREAD_THREADING_ENABLED + std::mutex _mx; +#endif + uint64_t _cur_offset = 0; + OStream* _stream; +}; + +static int64_t +ostream_write ( + exr_const_context_t ctxt, + void* userdata, + const void* buffer, + uint64_t sz, + uint64_t offset, + exr_stream_error_func_ptr_t error_cb) +{ + ostream_holder* oh = static_cast (userdata); + + if (sz > INT_MAX) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Stream interface request to write block too large"); + return -1; + } + +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lk{oh->_mx}; +#endif + + if (offset != oh->_cur_offset) + { + oh->_stream->seekp (offset); + oh->_cur_offset = oh->_stream->tellp (); + if (offset != oh->_cur_offset) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Unable to seek to desired offset %" PRIu64, + offset); + return -1; + } + } + + int64_t nwrite = oh->_cur_offset; + try + { + oh->_stream->write ( + static_cast (buffer), static_cast (sz)); + oh->_cur_offset = oh->_stream->tellp (); + nwrite = oh->_cur_offset - nwrite; + } + catch (...) + { + error_cb ( + ctxt, + EXR_ERR_READ_IO, + "Unable to seek to desired offset %" PRIu64, + offset); + nwrite = -1; + } + return nwrite; +} + +static void +ostream_destroy (exr_const_context_t ctxt, void* userdata, int failed) +{ + ostream_holder* oh = static_cast (userdata); + + delete oh; +} + +ContextInitializer& +ContextInitializer::setOutputStream (OStream* ostr) +{ + _initializer.user_data = new ostream_holder{ostr}; + _initializer.read_fn = nullptr; + _initializer.size_fn = nullptr; + _initializer.write_fn = ostream_write; + _initializer.destroy_fn = ostream_destroy; + _ctxt_type = ContextFileType::WRITE; + return *this; +} + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT diff --git a/src/lib/OpenEXR/ImfContextInit.h b/src/lib/OpenEXR/ImfContextInit.h new file mode 100644 index 0000000000..4a3dc26bfb --- /dev/null +++ b/src/lib/OpenEXR/ImfContextInit.h @@ -0,0 +1,148 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#ifndef INCLUDED_IMF_CONTEXT_INIT_H +#define INCLUDED_IMF_CONTEXT_INIT_H + +#include "ImfForward.h" + +#include "openexr_context.h" + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER + +/// @brief ContextInitializer provides a basic type +/// to initialize a Context with. +/// +/// A context fundamentally represents an EXR file of some sort +/// (reading a file, reading a stream, etc.) +class IMF_EXPORT_TYPE ContextInitializer +{ + enum class ContextFileType + { + READ, + WRITE, + READ_WRITE, + TEMP + }; + +public: + ContextInitializer& setErrorHandler (exr_error_handler_cb_t errfn) noexcept + { + _initializer.error_handler_fn = errfn; + return *this; + } + + ContextInitializer& setAllocationFunctions ( + exr_memory_allocation_func_t allocfn, + exr_memory_free_func_t freefn) noexcept + { + _initializer.alloc_fn = allocfn; + _initializer.free_fn = freefn; + return *this; + } + + IMF_EXPORT + ContextInitializer& setInputStream (IStream* istr); + IMF_EXPORT + ContextInitializer& setOutputStream (OStream* ostr); + + ContextInitializer& setCustomInputIO ( + void* user, + exr_read_func_ptr_t readfn, + exr_query_size_func_ptr_t sizefn, + exr_destroy_stream_func_ptr_t destroyfn) noexcept + { + _initializer.user_data = user; + _initializer.read_fn = readfn; + _initializer.size_fn = sizefn; + _initializer.destroy_fn = destroyfn; + _ctxt_type = ContextFileType::READ; + return *this; + } + + ContextInitializer& setCustomOutputIO ( + void* user, + exr_write_func_ptr_t writefn, + exr_destroy_stream_func_ptr_t destroyfn, + exr_read_func_ptr_t readfn = nullptr, + exr_query_size_func_ptr_t sizefn = nullptr) noexcept + { + _initializer.user_data = user; + _initializer.read_fn = readfn; + _initializer.size_fn = sizefn; + _initializer.write_fn = writefn; + _initializer.destroy_fn = destroyfn; + _ctxt_type = (readfn) ? ContextFileType::READ_WRITE + : ContextFileType::WRITE; + return *this; + } + + ContextInitializer& setMaxImageSize (int w, int h) noexcept + { + _initializer.max_image_width = w; + _initializer.max_image_height = h; + return *this; + } + + ContextInitializer& setMaxTileSize (int w, int h) noexcept + { + _initializer.max_tile_width = w; + _initializer.max_tile_height = h; + return *this; + } + + ContextInitializer& setZipLevel (int zl) noexcept + { + _initializer.zip_level = zl; + return *this; + } + + ContextInitializer& setDWAQuality (float dq) noexcept + { + _initializer.dwa_quality = dq; + return *this; + } + + ContextInitializer& strictHeaderValidation (bool onoff) noexcept + { + setFlag (EXR_CONTEXT_FLAG_STRICT_HEADER, onoff); + return *this; + } + + ContextInitializer& slientHeaderParse (bool onoff) noexcept + { + setFlag (EXR_CONTEXT_FLAG_SILENT_HEADER_PARSE, onoff); + return *this; + } + + ContextInitializer& disableChunkReconstruction (bool onoff) noexcept + { + setFlag (EXR_CONTEXT_FLAG_DISABLE_CHUNK_RECONSTRUCTION, onoff); + return *this; + } + + ContextInitializer& writeLegacyHeader (bool onoff) noexcept + { + setFlag (EXR_CONTEXT_FLAG_WRITE_LEGACY_HEADER, onoff); + return *this; + } + +private: + void setFlag (const int flag, bool onoff) + { + _initializer.flags = + (_initializer.flags & ~(flag)) | (onoff ? flag : 0); + } + + friend class Context; + + exr_context_initializer_t _initializer = EXR_DEFAULT_CONTEXT_INITIALIZER; + ContextFileType _ctxt_type = ContextFileType::TEMP; + IStream* _prov_stream = nullptr; +}; // class ContextInitializer + +OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT + +#endif // INCLUDED_IMF_CONTEXT_INIT_H From e2d501e65cea079471476c1ac2bd2b0d3228d19f Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Tue, 16 Apr 2024 20:13:53 +1200 Subject: [PATCH 03/12] Initial conversion of input file to use core This is not complete (some of the dependencies are circular, so depends on other later conversions to finish), so represents a WIP state Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfInputFile.cpp | 1369 ++++++++++-------------- src/lib/OpenEXR/ImfInputFile.h | 80 +- src/lib/OpenEXR/ImfTiledOutputFile.cpp | 2 +- 3 files changed, 628 insertions(+), 823 deletions(-) diff --git a/src/lib/OpenEXR/ImfInputFile.cpp b/src/lib/OpenEXR/ImfInputFile.cpp index cf401f96b2..01b84a7064 100644 --- a/src/lib/OpenEXR/ImfInputFile.cpp +++ b/src/lib/OpenEXR/ImfInputFile.cpp @@ -30,256 +30,638 @@ #include "Iex.h" #include #include +#include +#include #include #include +#include +#include OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER -using IMATH_NAMESPACE::Box2i; -using IMATH_NAMESPACE::divp; -using IMATH_NAMESPACE::modp; - -// -// Struct InputFile::Data stores things that will be -// needed between calls to readPixels -// - struct InputFile::Data -#if ILMTHREAD_THREADING_ENABLED - : public std::mutex -#endif { - Header header; - int version; - bool isTiled; + static constexpr int kDefaultPart = 0; - TiledInputFile* tFile; - ScanLineInputFile* sFile; - DeepScanLineInputFile* dsFile; + Data (Context* ctxt, int t) : _ctxt (ctxt), _numThreads (t) {} - LineOrder lineOrder; // the file's lineorder - int minY; // data window's min y coord - int maxY; // data window's max x coord + const Header& getHeader (int part) + { + int pc = _ctxt->partCount (); +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lk (_mx); +#endif + if (part < pc) + { + int cursz = static_cast (_lazy_header_cache.size ()); + if (part >= cursz) + { + _lazy_header_cache.resize (static_cast (part) + 1); + for (int i = cursz; i <= part; ++i) + _lazy_header_cache[i] = _ctxt->header (i); + } + return _lazy_header_cache[part]; + } + else + { + THROW ( + Iex::ArgExc, + "Invalid out of bounds part number " << part << ", only " << pc + << " parts in " + << _ctxt->fileName ()); + } + } - FrameBuffer tFileBuffer; - FrameBuffer* cachedBuffer; - CompositeDeepScanLine* compositor; // for loading deep files + void setFrameBuffer (const FrameBuffer& frameBuffer); - int cachedTileY; - int offset; + void readPixels (int scanline1, int scanline2); + void bufferedReadPixels (int scanline1, int scanline2); - int numThreads; + void deleteCachedBuffer (void); - int partNumber; - InputPartData* part; + IStream* getCompatStream () { return _ctxt->legacyIStream (getPartIdx ()); } - bool multiPartBackwardSupport; - MultiPartInputFile* multiPartFile; - InputStreamMutex* _streamData; - bool _deleteStream; + int getPartIdx () const { return _part ? _part->partNumber : kDefaultPart; } +#if ILMTHREAD_THREADING_ENABLED + // TODO: remove once we can do everything init in the constructor + // and just use the C interface + std::mutex _mx; +#endif + Context* _ctxt; + int _numThreads = 0; + exr_storage_t _storage; + + // TODO: remove + InputPartData* _part = nullptr; + std::unique_ptr _tFile; + std::unique_ptr _sFile; + std::unique_ptr _dsFile; + std::unique_ptr + _compositor; // for loading deep files + + // TODO: remove once we can remove deprecated API + std::vector _pixel_data_scratch; + + // TODO: remove once we can remove deprecated API or to make it slow so + // people switch to the new UI + std::vector
_lazy_header_cache; + + FrameBuffer _cacheFrameBuffer; + int _cachedTileY = -1; + int _cachedOffset = 0; + std::unique_ptr _cachedBuffer; +}; - Data (int numThreads); - ~Data (); +InputFile::InputFile ( + const char* filename, const ContextInitializer& ctxtinit, int numThreads) + : _data (std::make_shared (&_ctxt, numThreads)) +{ + _ctxt.startRead (filename, ctxtinit); + initialize (); +} - Data (const Data& other) = delete; - Data& operator= (const Data& other) = delete; - Data (Data&& other) = delete; - Data& operator= (Data&& other) = delete; +InputFile::InputFile (const char filename[], int numThreads) + : InputFile (filename, ContextInitializer (), numThreads) +{} - void deleteCachedBuffer (); -}; +InputFile::InputFile ( + OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int numThreads) + : InputFile ( + is.fileName (), + ContextInitializer ().setInputStream (&is), + numThreads) +{} + +InputFile::InputFile (InputPartData* part) + : _data (std::make_shared (&_ctxt, part->numThreads)) +{ + _ctxt.startRead ( + part->mutex->is->fileName (), + ContextInitializer ().setInputStream (part->mutex->is)); -InputFile::Data::Data (int numThreads) - : isTiled (false) - , tFile (0) - , sFile (0) - , dsFile (0) - , cachedBuffer (0) - , compositor (0) - , cachedTileY (-1) - , numThreads (numThreads) - , partNumber (-1) - , part (NULL) - , multiPartBackwardSupport (false) - , multiPartFile (0) - , _streamData (0) - , _deleteStream (false) + _data->_part = part; + initialize (); +} +const char* +InputFile::fileName () const { - // empty + return _ctxt.fileName (); } -InputFile::Data::~Data () +const Header& +InputFile::header () const { - if (tFile) delete tFile; - if (sFile) delete sFile; - if (dsFile) delete dsFile; - if (compositor) delete compositor; - - deleteCachedBuffer (); + if (_data->_part) return _data->_part->header; + return _data->getHeader (Data::kDefaultPart); +} - if (multiPartBackwardSupport && multiPartFile) delete multiPartFile; +int +InputFile::version () const +{ + if (_data->_part) return _data->_part->version; + return _ctxt.version (); } void -InputFile::Data::deleteCachedBuffer () +InputFile::setFrameBuffer (const FrameBuffer& frameBuffer) { - // - // Delete the cached frame buffer, and all memory - // allocated for the slices in the cached frameBuffer. - // - - if (cachedBuffer) - { - for (FrameBuffer::Iterator k = cachedBuffer->begin (); - k != cachedBuffer->end (); - ++k) - { - Slice& s = k.slice (); - - switch (s.type) - { - case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: - - delete[] (((unsigned int*) s.base) + offset); - break; - - case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: - - delete[] ((half*) s.base + offset); - break; + _data->setFrameBuffer (frameBuffer); +} - case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: +const FrameBuffer& +InputFile::frameBuffer () const +{ + // Really not actual protection once this returns... +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lock (_data->_mx); +#endif - delete[] (((float*) s.base) + offset); - break; - case NUM_PIXELTYPES: - throw (IEX_NAMESPACE::ArgExc ("Invalid pixel type")); - } - } + if (_data->_compositor) { return _data->_compositor->frameBuffer (); } - // - // delete the cached frame buffer - // + return _data->_cacheFrameBuffer; +} - delete cachedBuffer; - cachedBuffer = 0; - } +bool +InputFile::isComplete () const +{ + return _ctxt.chunkTableValid (_data->getPartIdx ()); } -namespace +bool +InputFile::isOptimizationEnabled () const { + return true; +} void -bufferedReadPixels (InputFile::Data* ifd, int scanLine1, int scanLine2) +InputFile::readPixels (int scanLine1, int scanLine2) { - // - // bufferedReadPixels reads each row of tiles that intersect the - // scan-line range (scanLine1 to scanLine2). The previous row of - // tiles is cached in order to prevent redundant tile reads when - // accessing scanlines sequentially. - // + _data->readPixels (scanLine1, scanLine2); +} - int minY = std::min (scanLine1, scanLine2); - int maxY = std::max (scanLine1, scanLine2); +void +InputFile::readPixels (int scanLine) +{ + _data->readPixels (scanLine, scanLine); +} - if (minY < ifd->minY || maxY > ifd->maxY) +void +InputFile::rawPixelData ( + int firstScanLine, const char*& pixelData, int& pixelDataSize) +{ + uint64_t maxsize = 0; + if (EXR_ERR_SUCCESS != + exr_get_chunk_unpacked_size (_ctxt, _data->getPartIdx (), &maxsize)) { - throw IEX_NAMESPACE::ArgExc ("Tried to read scan line outside " - "the image file's data window."); + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to query data size of chunk in file '" << fileName () + << "'"); } - // - // The minimum and maximum y tile coordinates that intersect this - // scanline range - // + // again, doesn't actually provide any safety given we're handing + // back a pointer... but will at least prevent two threads + // allocating at the same time and getting sliced +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lock (_data->_mx); +#endif - int minDy = (minY - ifd->minY) / ifd->tFile->tileYSize (); - int maxDy = (maxY - ifd->minY) / ifd->tFile->tileYSize (); + _data->_pixel_data_scratch.resize (maxsize); - // - // Figure out which one is first in the file so we can read without seeking - // + pixelData = _data->_pixel_data_scratch.data (); + pixelDataSize = static_cast (maxsize); - int yStart, yEnd, yStep; + rawPixelDataToBuffer ( + firstScanLine, _data->_pixel_data_scratch.data (), pixelDataSize); +} - if (ifd->lineOrder == DECREASING_Y) +void +InputFile::rawPixelDataToBuffer ( + int scanLine, char* pixelData, int& pixelDataSize) const +{ + exr_chunk_info_t cinfo; + if (EXR_ERR_SUCCESS == exr_read_scanline_chunk_info ( + _ctxt, _data->getPartIdx (), scanLine, &cinfo)) { - yStart = maxDy; - yEnd = minDy - 1; - yStep = -1; + if (cinfo.packed_size > static_cast (pixelDataSize)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Error reading pixel data from image " + "file \"" + << fileName () + << "\". Provided buffer is too small to read raw pixel data:" + << pixelDataSize << " bytes."); + } + + if (EXR_ERR_SUCCESS != + exr_read_chunk (_ctxt, _data->getPartIdx (), &cinfo, pixelData)) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Error reading pixel data from image " + "file \"" + << fileName () << "\". Unable to read raw pixel data of " + << pixelDataSize << " bytes."); + } } else { - yStart = minDy; - yEnd = maxDy + 1; - yStep = 1; + if (_data->_storage == EXR_STORAGE_TILED) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Error reading pixel data from image " + "file \"" + << fileName () + << "\". Tried to read a raw scanline from a tiled image."); + } + else + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Error reading pixel data from image " + "file \"" + << fileName () + << "\". Unable to query data block information."); + } } +} - // - // the number of pixels in a row of tiles - // - - Box2i levelRange = ifd->tFile->dataWindowForLevel (0); - - // - // Read the tiles into our temporary framebuffer and copy them into - // the user's buffer - // +void +InputFile::rawTileData ( + int& dx, + int& dy, + int& lx, + int& ly, + const char*& pixelData, + int& pixelDataSize) +{ + try + { + if (_data->_storage != EXR_STORAGE_TILED && + _data->_storage != EXR_STORAGE_DEEP_TILED) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Tried to read a raw tile " + "from a scanline-based image."); + } - for (int j = yStart; j != yEnd; j += yStep) + _data->_tFile->rawTileData (dx, dy, lx, ly, pixelData, pixelDataSize); + } + catch (IEX_NAMESPACE::BaseExc& e) { - Box2i tileRange = ifd->tFile->dataWindowForTile (0, j, 0); + REPLACE_EXC ( + e, + "Error reading tile data from image " + "file \"" + << fileName () << "\". " << e.what ()); + throw; + } +} - int minYThisRow = std::max (minY, tileRange.min.y); - int maxYThisRow = std::min (maxY, tileRange.max.y); +TiledInputFile& +InputFile::asTiledInput (void) const +{ + if (_data->_part) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Tried to initialize a copy tile input file " + "from a multi-part-converted input file."); + } - if (j != ifd->cachedTileY) - { - // - // We don't have any valid buffered info, so we need to read in - // from the file. - // if no channels are being read that are present in file, cachedBuffer will be empty - // + return *(_data->_tFile); +} - if (ifd->cachedBuffer && - ifd->cachedBuffer->begin () != ifd->cachedBuffer->end ()) - { - ifd->tFile->readTiles (0, ifd->tFile->numXTiles (0) - 1, j, j); - } +void +InputFile::initialize (void) +{ + int partidx = _data->getPartIdx (); - ifd->cachedTileY = j; + _data->_storage = _ctxt.storage (partidx); + // silly protection rules mean make_unique can't be used here + if (_data->_storage == EXR_STORAGE_DEEP_SCANLINE) + { + if (_data->_part) + { + _data->_dsFile.reset (new DeepScanLineInputFile (_data->_part)); + } + else + { + _data->_dsFile.reset (new DeepScanLineInputFile ( + _data->getHeader (partidx), + _data->getCompatStream (), + _ctxt.version (), + _data->_numThreads)); + } + _data->_compositor = std::make_unique (); + _data->_compositor->addSource (_data->_dsFile.get ()); + } + else if ( + _data->_storage == EXR_STORAGE_DEEP_TILED || + _data->_storage == EXR_STORAGE_TILED) + { + if (_data->_part) + { + _data->_tFile.reset (new TiledInputFile (_data->_part)); + } + else + { + _data->_tFile.reset (new TiledInputFile ( + _data->getHeader (partidx), + _data->getCompatStream (), + _ctxt.version (), + _data->_numThreads)); } + } + else + { + _data->_sFile = std::make_unique ( + _data->getHeader (partidx), + _data->getCompatStream (), + _data->_numThreads); + } +} + +void +InputFile::Data::setFrameBuffer (const FrameBuffer& frameBuffer) +{ +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lk (_mx); +#endif + if (_storage == EXR_STORAGE_TILED) + { // - // Copy the data from our cached framebuffer into the user's - // framebuffer. + // We must invalidate the cached buffer if the new frame + // buffer has a different set of channels than the old + // frame buffer, or if the type of a channel has changed. // - for (FrameBuffer::ConstIterator k = ifd->tFileBuffer.begin (); - k != ifd->tFileBuffer.end (); - ++k) + const FrameBuffer& oldFrameBuffer = _cacheFrameBuffer; + + FrameBuffer::ConstIterator i = oldFrameBuffer.begin (); + FrameBuffer::ConstIterator j = frameBuffer.begin (); + + while (i != oldFrameBuffer.end () && j != frameBuffer.end ()) { + if (strcmp (i.name (), j.name ()) || + i.slice ().type != j.slice ().type) + break; - Slice toSlice = k.slice (); // slice to read from - char* toPtr; + ++i; + ++j; + } - int xStart = levelRange.min.x; - int yStart = minYThisRow; + if (i != oldFrameBuffer.end () || j != frameBuffer.end ()) + { + // + // Invalidate the cached buffer. + // + deleteCachedBuffer (); - while (modp (xStart, toSlice.xSampling) != 0) - ++xStart; + // + // Create new a cached frame buffer. It can hold a single + // row of tiles. The cached buffer can be reused for each + // row of tiles because we set the yTileCoords parameter of + // each Slice to true. + // - while (modp (yStart, toSlice.ySampling) != 0) - ++yStart; + _cachedBuffer = std::make_unique (); + int partidx = getPartIdx (); + exr_attr_box2i_t dataWindow = _ctxt->dataWindow (partidx); + _cachedOffset = dataWindow.min.x; - FrameBuffer::ConstIterator c = ifd->cachedBuffer->find (k.name ()); - intptr_t toBase = reinterpret_cast (toSlice.base); + uint64_t tileRowSize = + uint64_t (_tFile->tileYSize ()) * + (static_cast (dataWindow.max.x - dataWindow.min.x) + + 1U); - if (c != ifd->cachedBuffer->end ()) + for (FrameBuffer::ConstIterator k = frameBuffer.begin (); + k != frameBuffer.end (); + ++k) { - // + Slice s = k.slice (); + + // + // omit adding channels that are not listed - 'fill' channels are added later + // + if (_ctxt->hasChannel (partidx, k.name ())) + { + switch (s.type) + { + case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: + _cachedBuffer->insert ( + k.name (), + Slice ( + UINT, + (char*) (new unsigned int[tileRowSize] - + _cachedOffset), + sizeof (unsigned int), + sizeof (unsigned int) * + _tFile->levelWidth (0), + 1, + 1, + s.fillValue, + false, + true)); + break; + + case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: + + _cachedBuffer->insert ( + k.name (), + Slice ( + HALF, + (char*) (new half[tileRowSize] - + _cachedOffset), + sizeof (half), + sizeof (half) * _tFile->levelWidth (0), + 1, + 1, + s.fillValue, + false, + true)); + break; + + case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: + + _cachedBuffer->insert ( + k.name (), + Slice ( + OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT, + (char*) (new float[tileRowSize] - + _cachedOffset), + sizeof (float), + sizeof (float) * _tFile->levelWidth (0), + 1, + 1, + s.fillValue, + false, + true)); + break; + + default: + + throw IEX_NAMESPACE::ArgExc ( + "Unknown pixel data type."); + } + } + } + _tFile->setFrameBuffer (*_cachedBuffer); + } + + _cacheFrameBuffer = frameBuffer; + } + else if ( + _storage == EXR_STORAGE_DEEP_SCANLINE || + _storage == EXR_STORAGE_DEEP_TILED) + { + if (!_compositor) + _compositor = std::make_unique (); + _compositor->setFrameBuffer (frameBuffer); + } + else + { + _sFile->setFrameBuffer (frameBuffer); + _cacheFrameBuffer = frameBuffer; + } +} + +void +InputFile::Data::readPixels (int scanLine1, int scanLine2) +{ +#if ILMTHREAD_THREADING_ENABLED + std::lock_guard lock (_mx); +#endif + + if (_compositor) { _compositor->readPixels (scanLine1, scanLine2); } + else if (_storage == EXR_STORAGE_TILED) + { + bufferedReadPixels (scanLine1, scanLine2); + } + else { _sFile->readPixels (scanLine1, scanLine2); } +} + +void +InputFile::Data::bufferedReadPixels (int scanLine1, int scanLine2) +{ + exr_attr_box2i_t dataWindow = _ctxt->dataWindow (getPartIdx ()); + + using IMATH_NAMESPACE::Box2i; + using IMATH_NAMESPACE::divp; + using IMATH_NAMESPACE::modp; + + // + // bufferedReadPixels reads each row of tiles that intersect the + // scan-line range (scanLine1 to scanLine2). The previous row of + // tiles is cached in order to prevent redundant tile reads when + // accessing scanlines sequentially. + // + + int minY = std::min (scanLine1, scanLine2); + int maxY = std::max (scanLine1, scanLine2); + + if (minY < dataWindow.min.y || maxY > dataWindow.max.y) + { + throw IEX_NAMESPACE::ArgExc ("Tried to read scan line outside " + "the image file's data window."); + } + + // + // The minimum and maximum y tile coordinates that intersect this + // scanline range + // + + int minDy = (minY - dataWindow.min.y) / _tFile->tileYSize (); + int maxDy = (maxY - dataWindow.min.y) / _tFile->tileYSize (); + + // + // Figure out which one is first in the file so we can read without seeking + // + + int yStart, yEnd, yStep; + + if (_ctxt->lineOrder (getPartIdx ()) == EXR_LINEORDER_DECREASING_Y) + { + yStart = maxDy; + yEnd = minDy - 1; + yStep = -1; + } + else + { + yStart = minDy; + yEnd = maxDy + 1; + yStep = 1; + } + + // + // the number of pixels in a row of tiles + // + + Box2i levelRange = _tFile->dataWindowForLevel (0); + + // + // Read the tiles into our temporary framebuffer and copy them into + // the user's buffer + // + + for (int j = yStart; j != yEnd; j += yStep) + { + Box2i tileRange = _tFile->dataWindowForTile (0, j, 0); + + int minYThisRow = std::max (minY, tileRange.min.y); + int maxYThisRow = std::min (maxY, tileRange.max.y); + + if (j != _cachedTileY) + { + // + // We don't have any valid buffered info, so we need to read in + // from the file. + // if no channels are being read that are present in file, cachedBuffer will be empty + // + + if (_cachedBuffer && + _cachedBuffer->begin () != _cachedBuffer->end ()) + { + _tFile->readTiles (0, _tFile->numXTiles (0) - 1, j, j); + } + + _cachedTileY = j; + } + + // + // Copy the data from our cached framebuffer into the user's + // framebuffer. + // + + for (FrameBuffer::ConstIterator k = _cacheFrameBuffer.begin (); + k != _cacheFrameBuffer.end (); + ++k) + { + + Slice toSlice = k.slice (); // slice to read from + char* toPtr; + + int xStart = levelRange.min.x; + int yStart = minYThisRow; + + while (modp (xStart, toSlice.xSampling) != 0) + ++xStart; + + while (modp (yStart, toSlice.ySampling) != 0) + ++yStart; + + FrameBuffer::ConstIterator c = _cachedBuffer->find (k.name ()); + intptr_t toBase = reinterpret_cast (toSlice.base); + + if (c != _cachedBuffer->end ()) + { + // // output channel was read from source image: copy to output slice // Slice fromSlice = c.slice (); // slice to write to @@ -378,632 +760,41 @@ bufferedReadPixels (InputFile::Data* ifd, int scanLine1, int scanLine2) } } -} // namespace - -InputFile::InputFile (const char fileName[], int numThreads) - : _data (new Data (numThreads)) +void +InputFile::Data::deleteCachedBuffer (void) { - _data->_streamData = NULL; - _data->_deleteStream = true; - - OPENEXR_IMF_INTERNAL_NAMESPACE::IStream* is = 0; - try + if (_cachedBuffer) { - is = new StdIFStream (fileName); - readMagicNumberAndVersionField (*is, _data->version); - - // - // compatibility to read multipart file. - // - if (isMultiPart (_data->version)) { compatibilityInitialize (*is); } - else + for (FrameBuffer::Iterator k = _cachedBuffer->begin (); + k != _cachedBuffer->end (); + ++k) { - _data->_streamData = new InputStreamMutex (); - _data->_streamData->is = is; - _data->header.readFrom (*_data->_streamData->is, _data->version); - - if (isNonImage (_data->version)) - { - if (!_data->header.hasType ()) - { - throw (IEX_NAMESPACE::InputExc ( - "Non-image files must have a 'type' attribute")); - } - } + Slice& s = k.slice (); - // fix type attribute in single part regular image types - // (may be wrong if an old version of OpenEXR converts - // a tiled image to scanline or vice versa) - if (!isNonImage (_data->version) && !isMultiPart (_data->version) && - _data->header.hasType ()) + switch (s.type) { - _data->header.setType ( - isTiled (_data->version) ? TILEDIMAGE : SCANLINEIMAGE); - } - - _data->header.sanityCheck (isTiled (_data->version)); - - initialize (); - } - } - catch (IEX_NAMESPACE::BaseExc& e) - { - if (is) delete is; - - if (_data && !_data->multiPartBackwardSupport && _data->_streamData) - { - delete _data->_streamData; - _data->_streamData = NULL; - } - - if (_data) delete _data; - _data = NULL; - - REPLACE_EXC ( - e, - "Cannot read image file " - "\"" << fileName - << "\". " << e.what ()); - throw; - } - catch (...) - { - if (is) delete is; - if (_data && !_data->multiPartBackwardSupport && _data->_streamData) - { - delete _data->_streamData; - } - if (_data) delete _data; + case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: - throw; - } -} + delete[] (((unsigned int*) s.base) + _cachedOffset); + break; -InputFile::InputFile ( - OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int numThreads) - : _data (new Data (numThreads)) -{ - _data->_streamData = NULL; - _data->_deleteStream = false; - try - { - readMagicNumberAndVersionField (is, _data->version); + case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: - // - // Backward compatibility to read multpart file. - // - if (isMultiPart (_data->version)) { compatibilityInitialize (is); } - else - { - _data->_streamData = new InputStreamMutex (); - _data->_streamData->is = &is; - _data->header.readFrom (*_data->_streamData->is, _data->version); + delete[] ((half*) s.base + _cachedOffset); + break; - if (isNonImage (_data->version)) - { - if (!_data->header.hasType ()) - { - throw (IEX_NAMESPACE::InputExc ( - "Non-image files must have a 'type' attribute")); - } - } + case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: - // fix type attribute in single part regular image types - // (may be wrong if an old version of OpenEXR converts - // a tiled image to scanline or vice versa) - if (!isNonImage (_data->version) && !isMultiPart (_data->version) && - _data->header.hasType ()) - { - _data->header.setType ( - isTiled (_data->version) ? TILEDIMAGE : SCANLINEIMAGE); + delete[] (((float*) s.base) + _cachedOffset); + break; + case NUM_PIXELTYPES: + throw (IEX_NAMESPACE::ArgExc ("Invalid pixel type")); } - - _data->header.sanityCheck (isTiled (_data->version)); - - initialize (); } - } - catch (IEX_NAMESPACE::BaseExc& e) - { - if (_data && !_data->multiPartBackwardSupport && _data->_streamData) - delete _data->_streamData; - if (_data) delete _data; - _data = NULL; - - REPLACE_EXC ( - e, - "Cannot read image file " - "\"" << is.fileName () - << "\". " << e.what ()); - throw; - } - catch (...) - { - if (_data && !_data->multiPartBackwardSupport && _data->_streamData) - delete _data->_streamData; - if (_data) delete _data; - _data = NULL; - throw; - } -} -InputFile::InputFile (InputPartData* part) : _data (new Data (part->numThreads)) -{ - _data->_deleteStream = false; - try - { - multiPartInitialize (part); + _cachedBuffer.reset (); } - catch (...) - { - delete _data; - throw; - } -} - -void -InputFile::compatibilityInitialize (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is) -{ - is.seekg (0); - - // - // Construct a MultiPartInputFile, initialize InputFile - // with the part 0 data. - // (TODO) may want to have a way to set the reconstruction flag. - // - _data->multiPartBackwardSupport = true; - _data->multiPartFile = new MultiPartInputFile (is, _data->numThreads); - InputPartData* part = _data->multiPartFile->getPart (0); - - multiPartInitialize (part); -} - -void -InputFile::multiPartInitialize (InputPartData* part) -{ - _data->_streamData = part->mutex; - _data->version = part->version; - _data->header = part->header; - _data->partNumber = part->partNumber; - _data->part = part; - - initialize (); -} - -void -InputFile::initialize () -{ - if (!_data->part) - { - if (_data->header.hasType () && _data->header.type () == DEEPSCANLINE) - { - _data->isTiled = false; - const Box2i& dataWindow = _data->header.dataWindow (); - _data->minY = dataWindow.min.y; - _data->maxY = dataWindow.max.y; - - _data->dsFile = new DeepScanLineInputFile ( - _data->header, - _data->_streamData->is, - _data->version, - _data->numThreads); - _data->compositor = new CompositeDeepScanLine; - _data->compositor->addSource (_data->dsFile); - } - - else if (isTiled (_data->version) && !isNonImage (_data->version)) - { - _data->isTiled = true; - _data->lineOrder = _data->header.lineOrder (); - - // - // Save the dataWindow information - // - - const Box2i& dataWindow = _data->header.dataWindow (); - _data->minY = dataWindow.min.y; - _data->maxY = dataWindow.max.y; - - _data->tFile = new TiledInputFile ( - _data->header, - _data->_streamData->is, - _data->version, - _data->numThreads); - } - - else if ( - !_data->header.hasType () || _data->header.type () == SCANLINEIMAGE) - { - _data->sFile = new ScanLineInputFile ( - _data->header, _data->_streamData->is, _data->numThreads); - } - else - { - // type set but not recognised - - THROW ( - IEX_NAMESPACE::ArgExc, - "InputFile cannot handle parts of type " - << _data->header.type ()); - } - } - else - { - if (_data->header.hasType () && _data->header.type () == DEEPSCANLINE) - { - _data->isTiled = false; - const Box2i& dataWindow = _data->header.dataWindow (); - _data->minY = dataWindow.min.y; - _data->maxY = dataWindow.max.y; - - _data->dsFile = new DeepScanLineInputFile (_data->part); - _data->compositor = new CompositeDeepScanLine; - _data->compositor->addSource (_data->dsFile); - } - else if ( - _data->header.hasType () && _data->header.type () == TILEDIMAGE) - { - _data->isTiled = true; - _data->lineOrder = _data->header.lineOrder (); - - // - // Save the dataWindow information - // - - const Box2i& dataWindow = _data->header.dataWindow (); - _data->minY = dataWindow.min.y; - _data->maxY = dataWindow.max.y; - - _data->tFile = new TiledInputFile (_data->part); - } - else if ( - !_data->header.hasType () || _data->header.type () == SCANLINEIMAGE) - { - _data->sFile = new ScanLineInputFile (_data->part); - } - else - { - THROW ( - IEX_NAMESPACE::ArgExc, - "InputFile cannot handle parts of type " - << _data->header.type ()); - } - } -} - -#include -InputFile::~InputFile () -{ - if (_data->_deleteStream) delete _data->_streamData->is; - - // unless this file was opened via the multipart API, - // delete the streamData object too - if (_data->partNumber == -1 && _data->_streamData) - delete _data->_streamData; - - if (_data) delete _data; -} - -const char* -InputFile::fileName () const -{ - return _data->_streamData->is->fileName (); -} - -const Header& -InputFile::header () const -{ - return _data->header; -} - -int -InputFile::version () const -{ - return _data->version; -} - -void -InputFile::setFrameBuffer (const FrameBuffer& frameBuffer) -{ - if (_data->isTiled) - { -#if ILMTHREAD_THREADING_ENABLED - std::lock_guard lock (*_data); -#endif - // - // We must invalidate the cached buffer if the new frame - // buffer has a different set of channels than the old - // frame buffer, or if the type of a channel has changed. - // - - const FrameBuffer& oldFrameBuffer = _data->tFileBuffer; - - FrameBuffer::ConstIterator i = oldFrameBuffer.begin (); - FrameBuffer::ConstIterator j = frameBuffer.begin (); - - while (i != oldFrameBuffer.end () && j != frameBuffer.end ()) - { - if (strcmp (i.name (), j.name ()) || - i.slice ().type != j.slice ().type) - break; - - ++i; - ++j; - } - - if (i != oldFrameBuffer.end () || j != frameBuffer.end ()) - { - // - // Invalidate the cached buffer. - // - - _data->deleteCachedBuffer (); - _data->cachedTileY = -1; - - // - // Create new a cached frame buffer. It can hold a single - // row of tiles. The cached buffer can be reused for each - // row of tiles because we set the yTileCoords parameter of - // each Slice to true. - // - - const Box2i& dataWindow = _data->header.dataWindow (); - _data->cachedBuffer = new FrameBuffer (); - _data->offset = dataWindow.min.x; - - unsigned int tileRowSize = uiMult ( - dataWindow.max.x - dataWindow.min.x + 1U, - _data->tFile->tileYSize ()); - - for (FrameBuffer::ConstIterator k = frameBuffer.begin (); - k != frameBuffer.end (); - ++k) - { - Slice s = k.slice (); - - // - // omit adding channels that are not listed - 'fill' channels are added later - // - if (_data->header.channels ().find (k.name ()) != - _data->header.channels ().end ()) - { - switch (s.type) - { - case OPENEXR_IMF_INTERNAL_NAMESPACE::UINT: - - _data->cachedBuffer->insert ( - k.name (), - Slice ( - UINT, - (char*) (new unsigned int[tileRowSize] - - _data->offset), - sizeof (unsigned int), - sizeof (unsigned int) * - _data->tFile->levelWidth (0), - 1, - 1, - s.fillValue, - false, - true)); - break; - - case OPENEXR_IMF_INTERNAL_NAMESPACE::HALF: - - _data->cachedBuffer->insert ( - k.name (), - Slice ( - HALF, - (char*) (new half[tileRowSize] - - _data->offset), - sizeof (half), - sizeof (half) * - _data->tFile->levelWidth (0), - 1, - 1, - s.fillValue, - false, - true)); - break; - - case OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT: - - _data->cachedBuffer->insert ( - k.name (), - Slice ( - OPENEXR_IMF_INTERNAL_NAMESPACE::FLOAT, - (char*) (new float[tileRowSize] - - _data->offset), - sizeof (float), - sizeof (float) * - _data->tFile->levelWidth (0), - 1, - 1, - s.fillValue, - false, - true)); - break; - - default: - - throw IEX_NAMESPACE::ArgExc ( - "Unknown pixel data type."); - } - } - } - - _data->tFile->setFrameBuffer (*_data->cachedBuffer); - } - - _data->tFileBuffer = frameBuffer; - } - else if (_data->compositor) - { - _data->compositor->setFrameBuffer (frameBuffer); - } - else - { - _data->sFile->setFrameBuffer (frameBuffer); - _data->tFileBuffer = frameBuffer; - } -} - -const FrameBuffer& -InputFile::frameBuffer () const -{ - if (_data->compositor) { return _data->compositor->frameBuffer (); } - else if (_data->isTiled) - { -#if ILMTHREAD_THREADING_ENABLED - std::lock_guard lock (*_data); -#endif - return _data->tFileBuffer; - } - else { return _data->sFile->frameBuffer (); } -} - -bool -InputFile::isComplete () const -{ - if (_data->dsFile) - return _data->dsFile->isComplete (); - else if (_data->isTiled) - return _data->tFile->isComplete (); - else - return _data->sFile->isComplete (); -} - -bool -InputFile::isOptimizationEnabled () const -{ - if (_data->sFile) { return _data->sFile->isOptimizationEnabled (); } - else { return false; } -} - -void -InputFile::readPixels (int scanLine1, int scanLine2) -{ - if (_data->compositor) - { - _data->compositor->readPixels (scanLine1, scanLine2); - } - else if (_data->isTiled) - { -#if ILMTHREAD_THREADING_ENABLED - std::lock_guard lock (*_data); -#endif - bufferedReadPixels (_data, scanLine1, scanLine2); - } - else { _data->sFile->readPixels (scanLine1, scanLine2); } -} - -void -InputFile::readPixels (int scanLine) -{ - readPixels (scanLine, scanLine); -} - -void -InputFile::rawPixelData ( - int firstScanLine, const char*& pixelData, int& pixelDataSize) -{ - try - { - if (_data->dsFile) - { - throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " - "from a deep image."); - } - - else if (_data->isTiled) - { - throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " - "from a tiled image."); - } - - _data->sFile->rawPixelData (firstScanLine, pixelData, pixelDataSize); - } - catch (IEX_NAMESPACE::BaseExc& e) - { - REPLACE_EXC ( - e, - "Error reading pixel data from image " - "file \"" - << fileName () << "\". " << e.what ()); - throw; - } -} - -void -InputFile::rawPixelDataToBuffer ( - int scanLine, char* pixelData, int& pixelDataSize) const -{ - try - { - if (_data->dsFile) - { - throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " - "from a deep image."); - } - - else if (_data->isTiled) - { - throw IEX_NAMESPACE::ArgExc ("Tried to read a raw scanline " - "from a tiled image."); - } - - _data->sFile->rawPixelDataToBuffer (scanLine, pixelData, pixelDataSize); - } - catch (IEX_NAMESPACE::BaseExc& e) - { - REPLACE_EXC ( - e, - "Error reading pixel data from image " - "file \"" - << fileName () << "\". " << e.what ()); - throw; - } -} - -void -InputFile::rawTileData ( - int& dx, - int& dy, - int& lx, - int& ly, - const char*& pixelData, - int& pixelDataSize) -{ - try - { - if (!_data->isTiled) - { - throw IEX_NAMESPACE::ArgExc ("Tried to read a raw tile " - "from a scanline-based image."); - } - - _data->tFile->rawTileData (dx, dy, lx, ly, pixelData, pixelDataSize); - } - catch (IEX_NAMESPACE::BaseExc& e) - { - REPLACE_EXC ( - e, - "Error reading tile data from image " - "file \"" - << fileName () << "\". " << e.what ()); - throw; - } -} - -TiledInputFile* -InputFile::tFile () -{ - if (!_data->isTiled) - { - throw IEX_NAMESPACE::ArgExc ("Cannot get a TiledInputFile pointer " - "from an InputFile that is not tiled."); - } - - return _data->tFile; + _cachedTileY = -1; } OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT diff --git a/src/lib/OpenEXR/ImfInputFile.h b/src/lib/OpenEXR/ImfInputFile.h index ecda904e4e..5716be51f9 100644 --- a/src/lib/OpenEXR/ImfInputFile.h +++ b/src/lib/OpenEXR/ImfInputFile.h @@ -15,25 +15,20 @@ #include "ImfForward.h" -#include "ImfGenericInputFile.h" #include "ImfThreading.h" +#include "ImfContext.h" + +// TODO: remove this once multipart file is cleaned up +#include "ImfGenericInputFile.h" + OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER +class TiledInputFile; + class IMF_EXPORT_TYPE InputFile : public GenericInputFile { public: - //----------------------------------------------------------- - // A constructor that opens the file with the specified name. - // Destroying the InputFile object will close the file. - // - // numThreads determines the number of threads that will be - // used to read the file (see ImfThreading.h). - //----------------------------------------------------------- - - IMF_EXPORT - InputFile (const char fileName[], int numThreads = globalThreadCount ()); - //------------------------------------------------------------- // A constructor that attaches the new InputFile object to a // file that has already been opened. Destroying the InputFile @@ -48,12 +43,26 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is, int numThreads = globalThreadCount ()); - //----------- - // Destructor - //----------- + //----------------------------------------------------------- + // A constructor that opens the file with the specified name. + // Destroying the InputFile object will close the file. + // + // numThreads determines the number of threads that will be + // used to read the file (see ImfThreading.h). + //----------------------------------------------------------- + IMF_EXPORT + InputFile (const char filename[], int numThreads = globalThreadCount ()); + //----------------------------------------------------------- + // A constructor that opens the file with the specified name + // and context initialization routines + // Destroying the InputFile object will close the file. + //----------------------------------------------------------- IMF_EXPORT - virtual ~InputFile (); + InputFile ( + const char* filename, + const ContextInitializer& ctxtinit, + int numThreads = globalThreadCount ()); //------------------------ // Access to the file name @@ -67,6 +76,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //-------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ( + "Use context-based attribute access for faster retrieval") const Header& header () const; //---------------------------------- @@ -88,6 +99,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //----------------------------------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ( + "Use stateless API to pass framebuffer with read request") void setFrameBuffer (const FrameBuffer& frameBuffer); //----------------------------------- @@ -95,6 +108,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //----------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ( + "Use stateless API to pass framebuffer with read request") const FrameBuffer& frameBuffer () const; //--------------------------------------------------------------- @@ -128,6 +143,7 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //--------------------------------------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ("No longer meaningful") bool isOptimizationEnabled () const; //--------------------------------------------------------------- @@ -150,8 +166,12 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //--------------------------------------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ( + "Use stateless API to pass framebuffer with read request") void readPixels (int scanLine1, int scanLine2); IMF_EXPORT + OPENEXR_DEPRECATED ( + "Use stateless API to pass framebuffer with read request") void readPixels (int scanLine); //---------------------------------------------- @@ -161,6 +181,7 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //---------------------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ("Prefer using externally managed buffer") void rawPixelData ( int firstScanLine, const char*& pixelData, int& pixelDataSize); @@ -191,6 +212,7 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile //-------------------------------------------------- IMF_EXPORT + OPENEXR_DEPRECATED ("Prefer using externally managed buffer") void rawTileData ( int& dx, int& dy, @@ -199,28 +221,20 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile const char*& pixelData, int& pixelDataSize); - struct IMF_HIDDEN Data; - private: - IMF_HIDDEN InputFile (InputPartData* part); - - InputFile (const InputFile&) = delete; - InputFile& operator= (const InputFile&) = delete; - InputFile (InputFile&&) = delete; - InputFile& operator= (InputFile&&) = delete; + IMF_HIDDEN void initialize (void); - IMF_HIDDEN void initialize (); - IMF_HIDDEN void multiPartInitialize (InputPartData* part); - IMF_HIDDEN void - compatibilityInitialize (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream& is); - IMF_HIDDEN TiledInputFile* tFile (); + // TODO: Remove these once MultiPartInputFile is converted + IMF_HIDDEN InputFile (InputPartData* part); + friend class MultiPartInputFile; - // for copyPixels + // TODO: Remove these once TiledOutputFile is converted + IMF_HIDDEN TiledInputFile& asTiledInput (void) const; friend class TiledOutputFile; - Data* _data; - - friend class MultiPartInputFile; + Context _ctxt; + struct Data; + std::shared_ptr _data; }; OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_EXIT diff --git a/src/lib/OpenEXR/ImfTiledOutputFile.cpp b/src/lib/OpenEXR/ImfTiledOutputFile.cpp index 2990516e42..dbfcd46923 100644 --- a/src/lib/OpenEXR/ImfTiledOutputFile.cpp +++ b/src/lib/OpenEXR/ImfTiledOutputFile.cpp @@ -1593,7 +1593,7 @@ TiledOutputFile::copyPixels (TiledInputFile& in) void TiledOutputFile::copyPixels (InputFile& in) { - copyPixels (*in.tFile ()); + copyPixels (in.asTiledInput ()); } void From f185741d0aee9ff5a0583dcfe247103a93e9fb90 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Tue, 16 Apr 2024 20:22:41 +1200 Subject: [PATCH 04/12] Add new files to fix bazel build check Signed-off-by: Kimball Thurston --- BUILD.bazel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index e233492d4b..1373af5afa 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -279,6 +279,8 @@ cc_library( "src/lib/OpenEXR/ImfCompressionAttribute.cpp", "src/lib/OpenEXR/ImfCompressor.cpp", "src/lib/OpenEXR/ImfConvert.cpp", + "src/lib/OpenEXR/ImfContext.cpp", + "src/lib/OpenEXR/ImfContextInit.cpp", "src/lib/OpenEXR/ImfDeepCompositing.cpp", "src/lib/OpenEXR/ImfDeepFrameBuffer.cpp", "src/lib/OpenEXR/ImfDeepImageStateAttribute.cpp", @@ -380,6 +382,8 @@ cc_library( "src/lib/OpenEXR/ImfCompression.h", "src/lib/OpenEXR/ImfCompressionAttribute.h", "src/lib/OpenEXR/ImfCompressor.h", + "src/lib/OpenEXR/ImfContext.h", + "src/lib/OpenEXR/ImfContextInit.h", "src/lib/OpenEXR/ImfConvert.h", "src/lib/OpenEXR/ImfDeepCompositing.h", "src/lib/OpenEXR/ImfDeepFrameBuffer.h", From bc999862f2b6cf22020c64daf5951a7e603d3b55 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Wed, 17 Apr 2024 00:47:50 +1200 Subject: [PATCH 05/12] Add missing deep image state attribute Not sure when that got added to the C++ library, but wasn't in the core Signed-off-by: Kimball Thurston --- src/lib/OpenEXRCore/attributes.c | 8 ++++++-- src/lib/OpenEXRCore/openexr_attr.h | 11 +++++++++++ src/lib/OpenEXRCore/parse_header.c | 10 ++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/lib/OpenEXRCore/attributes.c b/src/lib/OpenEXRCore/attributes.c index 4c740354e5..4d59e13438 100644 --- a/src/lib/OpenEXRCore/attributes.c +++ b/src/lib/OpenEXRCore/attributes.c @@ -55,7 +55,8 @@ static struct _internal_exr_attr_map the_predefined_attr_typenames[] = { {"v2d", 3, EXR_ATTR_V2D, sizeof (exr_attr_v2d_t)}, {"v3i", 3, EXR_ATTR_V3I, sizeof (exr_attr_v3i_t)}, {"v3f", 3, EXR_ATTR_V3F, sizeof (exr_attr_v3f_t)}, - {"v3d", 3, EXR_ATTR_V3D, sizeof (exr_attr_v3d_t)}}; + {"v3d", 3, EXR_ATTR_V3D, sizeof (exr_attr_v3d_t)}, + {"deepImageState", 14, EXR_ATTR_DEEP_IMAGE_STATE, 0}}; static int the_predefined_attr_count = sizeof (the_predefined_attr_typenames) / sizeof (struct _internal_exr_attr_map); @@ -88,7 +89,8 @@ attr_init (exr_context_t ctxt, exr_attribute_t* nattr) } case EXR_ATTR_COMPRESSION: case EXR_ATTR_ENVMAP: - case EXR_ATTR_LINEORDER: nattr->uc = 0; break; + case EXR_ATTR_LINEORDER: + case EXR_ATTR_DEEP_IMAGE_STATE: nattr->uc = 0; break; case EXR_ATTR_DOUBLE: nattr->d = 0.0; break; case EXR_ATTR_FLOAT: nattr->f = 0.0f; break; case EXR_ATTR_FLOAT_VECTOR: { @@ -250,6 +252,7 @@ attr_destroy (exr_context_t ctxt, exr_attribute_t* attr) case EXR_ATTR_V3I: case EXR_ATTR_V3F: case EXR_ATTR_V3D: + case EXR_ATTR_DEEP_IMAGE_STATE: case EXR_ATTR_UNKNOWN: case EXR_ATTR_LAST_KNOWN_TYPE: default: break; @@ -375,6 +378,7 @@ exr_attr_list_compute_size ( case EXR_ATTR_V3I: retval += sizeof (*(cur->v3i)); break; case EXR_ATTR_V3F: retval += sizeof (*(cur->v3f)); break; case EXR_ATTR_V3D: retval += sizeof (*(cur->v3d)); break; + case EXR_ATTR_DEEP_IMAGE_STATE: retval += sizeof (uint8_t); break; case EXR_ATTR_OPAQUE: if (cur->opaque->packed_data) retval += (size_t) cur->opaque->size; diff --git a/src/lib/OpenEXRCore/openexr_attr.h b/src/lib/OpenEXRCore/openexr_attr.h index 7b01e4b350..bcc3c1c74b 100644 --- a/src/lib/OpenEXRCore/openexr_attr.h +++ b/src/lib/OpenEXRCore/openexr_attr.h @@ -101,6 +101,16 @@ typedef enum EXR_PIXEL_LAST_TYPE } exr_pixel_type_t; +/** Enum declaring allowed values for \c uint8_t value stored in \c deepImageState type. */ +typedef enum +{ + EXR_DIS_MESSY = 0, + EXR_DIS_SORTED = 1, + EXR_DIS_NON_OVERLAPPING = 2, + EXR_DIS_TIDY = 3, + EXR_DIS_LAST_TYPE /**< Invalid value, provided for range checking. */ +} exr_deep_image_state_t; + /* /////////////////////////////////////// */ /* First set of structs are data where we can read directly with no allocation needed... */ @@ -450,6 +460,7 @@ typedef enum EXR_ATTR_V3I, /**< Set of 3 32-bit integers. */ EXR_ATTR_V3F, /**< Set of 3 32-bit floats. */ EXR_ATTR_V3D, /**< Set of 3 64-bit floats. */ + EXR_ATTR_DEEP_IMAGE_STATE, /**< ``uint8_t`` declaring deep image state. */ EXR_ATTR_OPAQUE, /**< User/unknown provided type. */ EXR_ATTR_LAST_KNOWN_TYPE } exr_attribute_type_t; diff --git a/src/lib/OpenEXRCore/parse_header.c b/src/lib/OpenEXRCore/parse_header.c index 5eb78c69c9..3c75f5a8f4 100644 --- a/src/lib/OpenEXRCore/parse_header.c +++ b/src/lib/OpenEXRCore/parse_header.c @@ -2044,6 +2044,16 @@ pull_attr ( rv = extract_attr_64bit ( ctxt, scratch, nattr->v3d->arr, name, type, attrsz, 3); break; + case EXR_ATTR_DEEP_IMAGE_STATE: + rv = extract_attr_uint8 ( + ctxt, + scratch, + &(nattr->uc), + name, + type, + attrsz, + (uint8_t) EXR_DIS_LAST_TYPE); + break; case EXR_ATTR_OPAQUE: rv = extract_attr_opaque ( ctxt, scratch, nattr->opaque, name, type, attrsz); From 882dba2f5931a300bb5d2ca1dc334be4662ca394 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Wed, 17 Apr 2024 00:49:32 +1200 Subject: [PATCH 06/12] Complete conversion of header attributes to header class Add additional constructor to opaque attribute to enable this. Registered custom attributes still TODO Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfContext.cpp | 180 ++++++++++++++++++------- src/lib/OpenEXR/ImfContextInit.h | 2 +- src/lib/OpenEXR/ImfOpaqueAttribute.cpp | 9 ++ src/lib/OpenEXR/ImfOpaqueAttribute.h | 3 + 4 files changed, 148 insertions(+), 46 deletions(-) diff --git a/src/lib/OpenEXR/ImfContext.cpp b/src/lib/OpenEXR/ImfContext.cpp index b8f77b9cfd..580c9c5096 100644 --- a/src/lib/OpenEXR/ImfContext.cpp +++ b/src/lib/OpenEXR/ImfContext.cpp @@ -5,6 +5,8 @@ #include "ImfContext.h" +#include "openexr.h" + #include "Iex.h" // TODO: remove these once we've cleared the legacy stream need @@ -15,8 +17,6 @@ #include #include -#include "openexr.h" - #include #include #include @@ -134,18 +134,29 @@ Context::setLongNameSupport (bool onoff) void Context::startRead (const char* filename, const ContextInitializer& ctxtinit) { + exr_result_t rv; if (*_ctxt) { THROW ( IEX_NAMESPACE::ArgExc, "Context already started, only start once"); } - if (EXR_ERR_SUCCESS != - exr_start_read (_ctxt.get (), filename, &(ctxtinit._initializer))) + rv = exr_start_read (_ctxt.get (), filename, &(ctxtinit._initializer)); + if (EXR_ERR_SUCCESS != rv) { - THROW ( - IEX_NAMESPACE::InputExc, - "Unable to open '" << filename << "' for read"); + if (rv == EXR_ERR_MISSING_REQ_ATTR) + { + THROW ( + IEX_NAMESPACE::ArgExc, + "Invalid or missing attribute when attempting to open '" + << filename << "' for read"); + } + else + { + THROW ( + IEX_NAMESPACE::InputExc, + "Unable to open '" << filename << "' for read"); + } } _prov_stream = ctxtinit._prov_stream; @@ -515,58 +526,137 @@ Context::header (int partidx) const break; } - default: { - THROW (IEX_NAMESPACE::LogicExc, "Not yet implemented"); - } -#if 0 - case EXR_ATTR_CHROMATICITIES: - retval += sizeof (*(cur->chromaticities)); + case EXR_ATTR_TILEDESC: + hdr.insert ( + cur->name, TileDescriptionAttribute ( + TileDescription ( + cur->tiledesc->x_size, + cur->tiledesc->y_size, + (LevelMode)(EXR_GET_TILE_LEVEL_MODE (*cur->tiledesc)), + (LevelRoundingMode)(EXR_GET_TILE_ROUND_MODE (*cur->tiledesc))))); break; + case EXR_ATTR_FLOAT_VECTOR: - retval += sizeof (float) * (size_t) (cur->floatvector->length); + hdr.insert ( + cur->name, FloatVectorAttribute ( + FloatVector ( + cur->floatvector->arr, + cur->floatvector->arr + cur->floatvector->length))); + break; + + case EXR_ATTR_M33F: + hdr.insert ( + cur->name, M33fAttribute ( + IMATH_NAMESPACE::M33f ( + *reinterpret_cast (cur->m33f->m)))); + break; + case EXR_ATTR_M33D: + hdr.insert ( + cur->name, M33dAttribute ( + IMATH_NAMESPACE::M33d ( + *reinterpret_cast (cur->m33d->m)))); + break; + case EXR_ATTR_M44F: + hdr.insert ( + cur->name, M44fAttribute ( + IMATH_NAMESPACE::M44f ( + *reinterpret_cast (cur->m44f->m)))); + break; + case EXR_ATTR_M44D: + hdr.insert ( + cur->name, M44dAttribute ( + IMATH_NAMESPACE::M44d ( + *reinterpret_cast (cur->m44d->m)))); + break; + + case EXR_ATTR_CHROMATICITIES: + hdr.insert ( + cur->name, ChromaticitiesAttribute ( + Chromaticities ( + IMATH_NAMESPACE::V2f ( + cur->chromaticities->red_x, + cur->chromaticities->red_y), + IMATH_NAMESPACE::V2f ( + cur->chromaticities->green_x, + cur->chromaticities->green_y), + IMATH_NAMESPACE::V2f ( + cur->chromaticities->blue_x, + cur->chromaticities->blue_y), + IMATH_NAMESPACE::V2f ( + cur->chromaticities->white_x, + cur->chromaticities->white_y)))); + break; + case EXR_ATTR_KEYCODE: + hdr.insert ( + cur->name, KeyCodeAttribute ( + KeyCode ( + cur->keycode->film_mfc_code, + cur->keycode->film_type, + cur->keycode->prefix, + cur->keycode->count, + cur->keycode->perf_offset, + cur->keycode->perfs_per_frame, + cur->keycode->perfs_per_count))); + break; + case EXR_ATTR_RATIONAL: + hdr.insert ( + cur->name, RationalAttribute ( + Rational ( + cur->rational->num, + cur->rational->denom))); + break; + case EXR_ATTR_TIMECODE: + hdr.insert ( + cur->name, TimeCodeAttribute ( + TimeCode ( + cur->timecode->time_and_flags, + cur->timecode->user_data))); break; - case EXR_ATTR_KEYCODE: retval += sizeof (*(cur->keycode)); break; - case EXR_ATTR_M33F: retval += sizeof (*(cur->m33f)); break; - case EXR_ATTR_M33D: retval += sizeof (*(cur->m33d)); break; - case EXR_ATTR_M44F: retval += sizeof (*(cur->m44f)); break; - case EXR_ATTR_M44D: retval += sizeof (*(cur->m44d)); break; case EXR_ATTR_PREVIEW: - retval += (size_t) cur->preview->width * - (size_t) cur->preview->height * (size_t) 4; + hdr.insert ( + cur->name, PreviewImageAttribute ( + PreviewImage ( + cur->preview->width, + cur->preview->height, + reinterpret_cast (cur->preview->rgba)))); break; - case EXR_ATTR_RATIONAL: retval += sizeof (*(cur->rational)); break; case EXR_ATTR_STRING_VECTOR: + { + std::vector svec; + svec.resize (cur->stringvector->n_strings); for (int s = 0; s < cur->stringvector->n_strings; ++s) { - retval += (size_t) cur->stringvector->strings[s].length; - retval += sizeof (int32_t); + svec[s] = std::string ( + cur->stringvector->strings[s].str, + cur->stringvector->strings[s].length); } + hdr.insert ( + cur->name, StringVectorAttribute (svec)); break; - case EXR_ATTR_TILEDESC: retval += sizeof (*(cur->tiledesc)); break; - case EXR_ATTR_TIMECODE: retval += sizeof (*(cur->timecode)); break; - case EXR_ATTR_OPAQUE: - if (cur->opaque->packed_data) - retval += (size_t) cur->opaque->size; - else if (cur->opaque->unpacked_data) - { - int32_t sz = 0; - rv = - exr_attr_opaquedata_pack (ctxt, cur->opaque, &sz, NULL); - if (rv != EXR_ERR_SUCCESS) return rv; + } - retval += (size_t) sz; - } + case EXR_ATTR_DEEP_IMAGE_STATE: + hdr.insert ( + cur->name, DeepImageStateAttribute (DeepImageState (cur->uc))); break; + + case EXR_ATTR_OPAQUE: + hdr.insert ( + cur->name, OpaqueAttribute ( + cur->type_name, + cur->opaque->size, + cur->opaque->packed_data)); + break; + case EXR_ATTR_UNKNOWN: case EXR_ATTR_LAST_KNOWN_TYPE: - default: - return ctxt->print_error ( - ctxt, - EXR_ERR_INVALID_ARGUMENT, - "Invalid / unhandled type '%s' for attribute '%s', unable to compute size", - cur->type_name, - cur->name); -#endif + default: { + THROW (IEX_NAMESPACE::LogicExc, + "Unknown attribute '" + << cur->name + << "' of type '" + << cur->type << "', conversion to legacy header not yet implemented"); + } } } diff --git a/src/lib/OpenEXR/ImfContextInit.h b/src/lib/OpenEXR/ImfContextInit.h index 4a3dc26bfb..ee54e1e461 100644 --- a/src/lib/OpenEXR/ImfContextInit.h +++ b/src/lib/OpenEXR/ImfContextInit.h @@ -8,7 +8,7 @@ #include "ImfForward.h" -#include "openexr_context.h" +#include "openexr.h" OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER diff --git a/src/lib/OpenEXR/ImfOpaqueAttribute.cpp b/src/lib/OpenEXR/ImfOpaqueAttribute.cpp index c1027bc1ce..716e9c8649 100644 --- a/src/lib/OpenEXR/ImfOpaqueAttribute.cpp +++ b/src/lib/OpenEXR/ImfOpaqueAttribute.cpp @@ -26,6 +26,15 @@ OpaqueAttribute::OpaqueAttribute (const char typeName[]) : _typeName (typeName), _dataSize (0) {} +OpaqueAttribute::OpaqueAttribute (const char typeName[], + long dataSize, + const void* data) + : _typeName (typeName), _dataSize (dataSize) +{ + _data.resizeErase (dataSize); + memcpy ((char*) _data, (const char*) data, dataSize); +} + OpaqueAttribute::OpaqueAttribute (const OpaqueAttribute& other) : _typeName (other._typeName) , _dataSize (other._dataSize) diff --git a/src/lib/OpenEXR/ImfOpaqueAttribute.h b/src/lib/OpenEXR/ImfOpaqueAttribute.h index 38c01595c0..c24277dd5b 100644 --- a/src/lib/OpenEXR/ImfOpaqueAttribute.h +++ b/src/lib/OpenEXR/ImfOpaqueAttribute.h @@ -34,6 +34,9 @@ class IMF_EXPORT_TYPE OpaqueAttribute : public Attribute //---------------------------- IMF_EXPORT OpaqueAttribute (const char typeName[]); + IMF_EXPORT OpaqueAttribute (const char typeName[], + long dataSize, + const void* data); IMF_EXPORT OpaqueAttribute (const OpaqueAttribute& other); IMF_EXPORT virtual ~OpaqueAttribute (); From ac0882f9eb4298b6d05298528f99b62a87e231b5 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Wed, 17 Apr 2024 00:53:18 +1200 Subject: [PATCH 07/12] Fix flag handling and storage type validation to match Long name attribute flags was not being set correctly and the rules around when there is a mismatch between the bit flags in the header and the type attribute were not being correctly handled Signed-off-by: Kimball Thurston --- src/lib/OpenEXRCore/context.c | 14 ++--- src/lib/OpenEXRCore/internal_file.h | 1 + src/lib/OpenEXRCore/internal_structs.h | 3 +- src/lib/OpenEXRCore/parse_header.c | 58 +++++++++++-------- src/lib/OpenEXRCore/validation.c | 79 +++++++++++++++++++------- src/lib/OpenEXRCore/write_header.c | 68 ++++++++++++++++++++-- 6 files changed, 166 insertions(+), 57 deletions(-) diff --git a/src/lib/OpenEXRCore/context.c b/src/lib/OpenEXRCore/context.c index 6b2c18818c..9519f8a1ee 100644 --- a/src/lib/OpenEXRCore/context.c +++ b/src/lib/OpenEXRCore/context.c @@ -395,17 +395,15 @@ exr_get_file_version_and_flags (exr_const_context_t ctxt, uint32_t* ver) if (ver) { - uint32_t flags = ctxt->version; + exr_result_t ret = EXR_ERR_SUCCESS; - if (ctxt->is_multipart) flags |= EXR_MULTI_PART_FLAG; - if (ctxt->max_name_length > EXR_SHORTNAME_MAXLEN) - flags |= EXR_LONG_NAMES_FLAG; - if (ctxt->has_nonimage_data) flags |= EXR_NON_IMAGE_FLAG; - if (ctxt->is_singlepart_tiled) flags |= EXR_TILED_FLAG; + if (ctxt->orig_version_and_flags != 0) + *ver = ctxt->orig_version_and_flags; + else + ret = internal_exr_calc_header_version_flags (ctxt, ver); - *ver = flags; if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); - return EXR_ERR_SUCCESS; + return ret; } if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); diff --git a/src/lib/OpenEXRCore/internal_file.h b/src/lib/OpenEXRCore/internal_file.h index 504abe64e0..2b3d9ab280 100644 --- a/src/lib/OpenEXRCore/internal_file.h +++ b/src/lib/OpenEXRCore/internal_file.h @@ -26,6 +26,7 @@ exr_result_t internal_exr_compute_tile_information ( exr_context_t ctxt, exr_priv_part_t curpart, int rebuild); int32_t internal_exr_compute_chunk_offset_size (exr_priv_part_t curpart); +exr_result_t internal_exr_calc_header_version_flags (exr_const_context_t ctxt, uint32_t *flags); exr_result_t internal_exr_write_header (exr_context_t ctxt); /* in openexr_validate.c, functions to validate the header during read / pre-write */ diff --git a/src/lib/OpenEXRCore/internal_structs.h b/src/lib/OpenEXRCore/internal_structs.h index fcf7e1297b..cda21ea909 100644 --- a/src/lib/OpenEXRCore/internal_structs.h +++ b/src/lib/OpenEXRCore/internal_structs.h @@ -216,7 +216,8 @@ struct _priv_exr_context_t #endif uint8_t disable_chunk_reconstruct; uint8_t legacy_header; - uint8_t _pad[6]; + uint8_t _pad[2]; + uint32_t orig_version_and_flags; }; #define EXR_CONST_CAST(t, v) ((t) (uintptr_t) v) diff --git a/src/lib/OpenEXRCore/parse_header.c b/src/lib/OpenEXRCore/parse_header.c index 3c75f5a8f4..1018c61bdf 100644 --- a/src/lib/OpenEXRCore/parse_header.c +++ b/src/lib/OpenEXRCore/parse_header.c @@ -1596,13 +1596,25 @@ check_populate_type ( } if (strcmp ((const char*) outstr, "scanlineimage") == 0) - curpart->storage_mode = EXR_STORAGE_SCANLINE; + { + if (ctxt->has_nonimage_data || ctxt->is_multipart) + curpart->storage_mode = EXR_STORAGE_SCANLINE; + } else if (strcmp ((const char*) outstr, "tiledimage") == 0) - curpart->storage_mode = EXR_STORAGE_TILED; + { + if (ctxt->has_nonimage_data || ctxt->is_multipart) + curpart->storage_mode = EXR_STORAGE_TILED; + } else if (strcmp ((const char*) outstr, "deepscanline") == 0) - curpart->storage_mode = EXR_STORAGE_DEEP_SCANLINE; + { + if (ctxt->has_nonimage_data || ctxt->is_multipart) + curpart->storage_mode = EXR_STORAGE_DEEP_SCANLINE; + } else if (strcmp ((const char*) outstr, "deeptile") == 0) - curpart->storage_mode = EXR_STORAGE_DEEP_TILED; + { + if (ctxt->has_nonimage_data || ctxt->is_multipart) + curpart->storage_mode = EXR_STORAGE_DEEP_TILED; + } else { rv = ctxt->print_error ( @@ -2478,6 +2490,7 @@ read_magic_and_flags (exr_context_t ctxt, uint32_t* outflags, uint64_t* initpos) flags = magic_and_version[1]; + ctxt->orig_version_and_flags = flags; ctxt->version = flags & EXR_FILE_VERSION_MASK; if (ctxt->version != 2) { @@ -2571,27 +2584,26 @@ internal_exr_parse_header (exr_context_t ctxt) { if (ctxt->has_nonimage_data || ctxt->is_multipart) { - if (ctxt->strict_header) - { - rv = ctxt->print_error ( - ctxt, - EXR_ERR_FILE_BAD_HEADER, - "Invalid combination of version flags: single part found, but also marked as deep (%d) or multipart (%d)", - (int) ctxt->has_nonimage_data, - (int) ctxt->is_multipart); - priv_destroy_scratch (&scratch); - return internal_exr_context_restore_handlers (ctxt, rv); - } - else - { - // assume multipart for now - ctxt->is_singlepart_tiled = 0; - } + // this appears to always be fatal, so do not check strict / not + rv = ctxt->print_error ( + ctxt, + EXR_ERR_FILE_BAD_HEADER, + "Invalid combination of version flags: single part flag found, but also marked as deep (%d) or multipart (%d)", + (int) ctxt->has_nonimage_data, + (int) ctxt->is_multipart); + priv_destroy_scratch (&scratch); + return internal_exr_context_restore_handlers (ctxt, rv); } - curpart->storage_mode = EXR_STORAGE_TILED; } - else - curpart->storage_mode = EXR_STORAGE_SCANLINE; + + /* leave storage mode uninitialized until we encounter the type */ + if (!ctxt->has_nonimage_data && !ctxt->is_multipart) + { + if (ctxt->is_singlepart_tiled) + curpart->storage_mode = EXR_STORAGE_TILED; + else + curpart->storage_mode = EXR_STORAGE_SCANLINE; + } do { diff --git a/src/lib/OpenEXRCore/validation.c b/src/lib/OpenEXRCore/validation.c index 01fa0b33cf..8c08dd39a9 100644 --- a/src/lib/OpenEXRCore/validation.c +++ b/src/lib/OpenEXRCore/validation.c @@ -8,6 +8,7 @@ #include #include #include +#include /**************************************/ @@ -407,37 +408,77 @@ validate_part_type (exr_context_t f, exr_priv_part_t curpart) // TODO: there are probably more tests to add here... if (curpart->type) { - int rv; + const char *expectedtype = NULL; + exr_result_t rv; // see if the type overwrote the storage mode - if (f->is_singlepart_tiled && - curpart->storage_mode != EXR_STORAGE_TILED) + if (f->is_singlepart_tiled) { - // mismatch between type attr and file flag. c++ believed the - // flag first and foremost - curpart->storage_mode = EXR_STORAGE_TILED; - - // TODO: define how strict we should be - //exr_attr_list_remove( f, &(curpart->attributes), curpart->type ); - //curpart->type = NULL; - f->print_error ( - f, - EXR_ERR_INVALID_ATTR, - "attribute 'type': Mismatch between file flags and type string '%s', believing file flags", - curpart->type->string->str); + if (f->is_multipart || f->num_parts > 1) + return f->print_error ( + f, + EXR_ERR_INVALID_ATTR, + "Multipart files cannot have the tiled bit set"); + + if (curpart->storage_mode != EXR_STORAGE_TILED) + { + curpart->storage_mode = EXR_STORAGE_TILED; + + if (f->strict_header) + { + return f->print_error ( + f, + EXR_ERR_INVALID_ATTR, + "attribute 'type': Single part tiled flag set but not marked as tiled storage type"); + } + } + } + + if (curpart->storage_mode == EXR_STORAGE_SCANLINE) + expectedtype = "scanlineimage"; + else if (curpart->storage_mode == EXR_STORAGE_TILED) + expectedtype = "tiledimage"; + else if (curpart->storage_mode == EXR_STORAGE_DEEP_SCANLINE) + expectedtype = "deepscanline"; + else if (curpart->storage_mode == EXR_STORAGE_DEEP_TILED) + expectedtype = "deeptile"; + if (expectedtype && 0 != strcmp (curpart->type->string->str, expectedtype)) + { if (f->mode == EXR_CONTEXT_WRITE) return EXR_ERR_INVALID_ATTR; - rv = exr_attr_string_set_with_length ( - (exr_context_t) f, curpart->type->string, "tiledimage", 10); - if (rv != EXR_ERR_SUCCESS) + if (f->strict_header) + { return f->print_error ( f, EXR_ERR_INVALID_ATTR, - "attribute 'type': Mismatch between file flags and type attribute, unable to fix"); + "attribute 'type': Type should be '%s' but set to '%s', believing file flags", + expectedtype, + curpart->type->string->str); + } + else + { + /* C++ silently changed this */ + rv = exr_attr_string_set ( + f, curpart->type->string, expectedtype); + + if (rv != EXR_ERR_SUCCESS) + return f->print_error ( + f, + EXR_ERR_INVALID_ATTR, + "attribute 'type': Mismatch between file flags and type attribute, unable to fix"); + } } } + if (curpart->storage_mode == EXR_STORAGE_LAST_TYPE) + { + return f->print_error ( + f, + EXR_ERR_INVALID_ATTR, + "Unable to determine data storage type for part"); + } + return EXR_ERR_SUCCESS; } diff --git a/src/lib/OpenEXRCore/write_header.c b/src/lib/OpenEXRCore/write_header.c index f53aef35ad..bb0082eb9a 100644 --- a/src/lib/OpenEXRCore/write_header.c +++ b/src/lib/OpenEXRCore/write_header.c @@ -596,6 +596,67 @@ save_attr (exr_context_t ctxt, const exr_attribute_t* a) return rv; } +/**************************************/ + +exr_result_t internal_exr_calc_header_version_flags (exr_const_context_t ctxt, uint32_t *flags) +{ + *flags = 2; // EXR_VERSION + + if (ctxt->is_multipart) *flags |= EXR_MULTI_PART_FLAG; + + if (ctxt->max_name_length > EXR_SHORTNAME_MAXLEN) + { + int longnamefound = 0; + + for ( int p = 0; p < ctxt->num_parts; ++p ) + { + exr_priv_part_t curpart = ctxt->parts[p]; + for ( int a = 0; a < curpart->attributes.num_attributes; ++a ) + { + exr_attribute_t *cura = curpart->attributes.entries[a]; + if (cura->name_length > EXR_SHORTNAME_MAXLEN || + cura->type_name_length > EXR_SHORTNAME_MAXLEN) + { + longnamefound = 1; + break; + } + + // the original C++ side assumes there was + // only one channel list (who would have multiple) + // but let's not make that same assertion and check names + // if we encounter any channel list named anything + if (cura->type == EXR_ATTR_CHLIST) + { + const exr_attr_chlist_t* chlist = cura->chlist; + int nc = chlist->num_channels; + + for ( int c = 0; c < nc; ++c ) + { + const exr_attr_chlist_entry_t *ce = chlist->entries + c; + if (ce->name.length > EXR_SHORTNAME_MAXLEN) + { + longnamefound = 1; + break; + } + } + } + } + + if (longnamefound) + break; + } + + if (longnamefound) + *flags |= EXR_LONG_NAMES_FLAG; + } + + if (ctxt->has_nonimage_data) *flags |= EXR_NON_IMAGE_FLAG; + if (ctxt->is_singlepart_tiled) *flags |= EXR_TILED_FLAG; + + return EXR_ERR_SUCCESS; +} + + /**************************************/ exr_result_t @@ -606,12 +667,7 @@ internal_exr_write_header (exr_context_t ctxt) uint32_t flags; uint8_t next_byte; - flags = 2; // EXR_VERSION - if (ctxt->is_multipart) flags |= EXR_MULTI_PART_FLAG; - if (ctxt->max_name_length > EXR_SHORTNAME_MAXLEN) - flags |= EXR_LONG_NAMES_FLAG; - if (ctxt->has_nonimage_data) flags |= EXR_NON_IMAGE_FLAG; - if (ctxt->is_singlepart_tiled) flags |= EXR_TILED_FLAG; + rv = internal_exr_calc_header_version_flags (ctxt, &flags); magic_and_version[0] = 20000630; magic_and_version[1] = flags; From 71bac2a40e08891f6302c615ef9ec680cafe7573 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Thu, 18 Apr 2024 01:19:11 +1200 Subject: [PATCH 08/12] Add unknown storage mode for future proofing, fix logic Signed-off-by: Kimball Thurston --- src/lib/OpenEXRCore/chunk.c | 8 ++++---- src/lib/OpenEXRCore/openexr_attr.h | 3 ++- src/lib/OpenEXRCore/parse_header.c | 21 ++++++++++++++++---- src/lib/OpenEXRCore/validation.c | 31 ++++++++++++++++++++++++++---- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/lib/OpenEXRCore/chunk.c b/src/lib/OpenEXRCore/chunk.c index d326d9be73..e2db0e070e 100644 --- a/src/lib/OpenEXRCore/chunk.c +++ b/src/lib/OpenEXRCore/chunk.c @@ -742,8 +742,8 @@ exr_read_scanline_chunk_info ( if (!cinfo) return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); - if (part->storage_mode == EXR_STORAGE_TILED || - part->storage_mode == EXR_STORAGE_DEEP_TILED) + if (part->storage_mode != EXR_STORAGE_SCANLINE && + part->storage_mode != EXR_STORAGE_DEEP_SCANLINE) { return ctxt->standard_error (ctxt, EXR_ERR_SCAN_TILE_MIXEDAPI); } @@ -1002,8 +1002,8 @@ exr_read_tile_chunk_info ( if (!cinfo) return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); - if (part->storage_mode == EXR_STORAGE_SCANLINE || - part->storage_mode == EXR_STORAGE_DEEP_SCANLINE) + if (part->storage_mode != EXR_STORAGE_TILED && + part->storage_mode != EXR_STORAGE_DEEP_TILED) { return ctxt->standard_error (ctxt, EXR_ERR_TILE_SCAN_MIXEDAPI); } diff --git a/src/lib/OpenEXRCore/openexr_attr.h b/src/lib/OpenEXRCore/openexr_attr.h index bcc3c1c74b..407ab6dd7f 100644 --- a/src/lib/OpenEXRCore/openexr_attr.h +++ b/src/lib/OpenEXRCore/openexr_attr.h @@ -72,7 +72,8 @@ typedef enum EXR_STORAGE_TILED, /**< Corresponds to type of \c tiledimage. */ EXR_STORAGE_DEEP_SCANLINE, /**< Corresponds to type of \c deepscanline. */ EXR_STORAGE_DEEP_TILED, /**< Corresponds to type of \c deeptile. */ - EXR_STORAGE_LAST_TYPE /**< Invalid value, provided for range checking. */ + EXR_STORAGE_LAST_TYPE, /**< Invalid value, provided for range checking. */ + EXR_STORAGE_UNKNOWN /**< An unknown type, provided for future proofing. */ } exr_storage_t; /** @brief Enum representing what type of tile information is contained. */ diff --git a/src/lib/OpenEXRCore/parse_header.c b/src/lib/OpenEXRCore/parse_header.c index 1018c61bdf..ebc79e7b51 100644 --- a/src/lib/OpenEXRCore/parse_header.c +++ b/src/lib/OpenEXRCore/parse_header.c @@ -1615,7 +1615,7 @@ check_populate_type ( if (ctxt->has_nonimage_data || ctxt->is_multipart) curpart->storage_mode = EXR_STORAGE_DEEP_TILED; } - else + else if (ctxt->strict_header) { rv = ctxt->print_error ( ctxt, @@ -1624,6 +1624,13 @@ check_populate_type ( outstr); exr_attr_list_remove (ctxt, &(curpart->attributes), curpart->type); curpart->type = NULL; + if (curpart->storage_mode == EXR_STORAGE_LAST_TYPE) + curpart->storage_mode = EXR_STORAGE_UNKNOWN; + } + else + { + if (curpart->storage_mode == EXR_STORAGE_LAST_TYPE) + curpart->storage_mode = EXR_STORAGE_UNKNOWN; } return rv; @@ -1676,8 +1683,13 @@ check_populate_version ( attrsz = (int32_t) one_to_native32 ((uint32_t) attrsz); if (attrsz != 1) - return ctxt->print_error ( - ctxt, EXR_ERR_INVALID_ATTR, "Invalid version %d: expect 1", attrsz); + { + if (ctxt->strict_header) + { + return ctxt->print_error ( + ctxt, EXR_ERR_INVALID_ATTR, "Invalid version %d: expect 1", attrsz); + } + } rv = exr_attr_list_add_static_name ( ctxt, @@ -2144,7 +2156,8 @@ internal_exr_compute_tile_information ( { exr_result_t rv = EXR_ERR_SUCCESS; if (curpart->storage_mode == EXR_STORAGE_SCANLINE || - curpart->storage_mode == EXR_STORAGE_DEEP_SCANLINE) + curpart->storage_mode == EXR_STORAGE_DEEP_SCANLINE || + curpart->storage_mode == EXR_STORAGE_UNKNOWN) return EXR_ERR_SUCCESS; if (rebuild && (!curpart->dataWindow || !curpart->tiles)) diff --git a/src/lib/OpenEXRCore/validation.c b/src/lib/OpenEXRCore/validation.c index 8c08dd39a9..202a204af6 100644 --- a/src/lib/OpenEXRCore/validation.c +++ b/src/lib/OpenEXRCore/validation.c @@ -5,6 +5,7 @@ #include "internal_file.h" +#include "internal_constants.h" #include #include #include @@ -202,15 +203,35 @@ validate_req_attr (exr_context_t f, exr_priv_part_t curpart, int adddefault) EXR_ERR_MISSING_REQ_ATTR, "'name' attribute for multipart file not found"); if (!curpart->type) + { return f->print_error ( f, EXR_ERR_MISSING_REQ_ATTR, "'type' attribute for v2+ file not found"); + } if (f->has_nonimage_data && !curpart->version) - return f->print_error ( - f, - EXR_ERR_MISSING_REQ_ATTR, - "'version' attribute for deep file not found"); + { + /* TODO: C++ goes ahead and just assumes there's a version of 1... */ + if (adddefault) + { + rv = exr_attr_list_add_static_name ( + f, + &(curpart->attributes), + EXR_REQ_VERSION_STR, + EXR_ATTR_INT, + 0, + NULL, + &(curpart->version)); + curpart->version->i = 1; + } + else + { + return f->print_error ( + f, + EXR_ERR_MISSING_REQ_ATTR, + "'version' attribute for deep file not found"); + } + } if (!curpart->chunkCount) return f->print_error ( f, @@ -471,6 +492,8 @@ validate_part_type (exr_context_t f, exr_priv_part_t curpart) } } + /* NB: we allow an 'unknown' storage type of EXR_STORAGE_UNKNOWN + * for future proofing */ if (curpart->storage_mode == EXR_STORAGE_LAST_TYPE) { return f->print_error ( From c875e9825e35154405f260a3913d9946723ea544 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Thu, 18 Apr 2024 01:20:05 +1200 Subject: [PATCH 09/12] Properly bridge to multipart file for now Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfInputFile.cpp | 52 ++++++++++++++++++++++++++------ src/lib/OpenEXR/ImfInputFile.h | 3 ++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/lib/OpenEXR/ImfInputFile.cpp b/src/lib/OpenEXR/ImfInputFile.cpp index 01b84a7064..65154c2f50 100644 --- a/src/lib/OpenEXR/ImfInputFile.cpp +++ b/src/lib/OpenEXR/ImfInputFile.cpp @@ -30,8 +30,6 @@ #include "Iex.h" #include #include -#include -#include #include #include @@ -94,6 +92,7 @@ struct InputFile::Data // TODO: remove InputPartData* _part = nullptr; + std::unique_ptr _mFile; std::unique_ptr _tFile; std::unique_ptr _sFile; std::unique_ptr _dsFile; @@ -136,6 +135,8 @@ InputFile::InputFile ( InputFile::InputFile (InputPartData* part) : _data (std::make_shared (&_ctxt, part->numThreads)) { + // TODO: janky, this will reread things for now (should share + // context between objects eventually) _ctxt.startRead ( part->mutex->is->fileName (), ContextInitializer ().setInputStream (part->mutex->is)); @@ -144,6 +145,10 @@ InputFile::InputFile (InputPartData* part) initialize (); } +InputFile::~InputFile () +{ +} + const char* InputFile::fileName () const { @@ -192,7 +197,10 @@ InputFile::isComplete () const bool InputFile::isOptimizationEnabled () const { - return true; + // TODO: the core library has a number of special cased patterns, + // this is all kind of ... not useful? for now, return a pattern + // similar to legacy version + return _ctxt.channels (_data->getPartIdx ())->num_channels != 2; } void @@ -227,7 +235,6 @@ InputFile::rawPixelData ( #if ILMTHREAD_THREADING_ENABLED std::lock_guard lock (_data->_mx); #endif - _data->_pixel_data_scratch.resize (maxsize); pixelData = _data->_pixel_data_scratch.data (); @@ -256,6 +263,8 @@ InputFile::rawPixelDataToBuffer ( << pixelDataSize << " bytes."); } + pixelDataSize = static_cast (cinfo.packed_size); + if (EXR_ERR_SUCCESS != exr_read_chunk (_ctxt, _data->getPartIdx (), &cinfo, pixelData)) { @@ -340,9 +349,20 @@ InputFile::asTiledInput (void) const void InputFile::initialize (void) { - int partidx = _data->getPartIdx (); + int partidx; + + if (!_data->_part && _ctxt.partCount() > 1) + { + IStream *s = _data->getCompatStream (); + + s->seekg (0); + _data->_mFile.reset (new MultiPartInputFile (*s, _data->_numThreads)); + _data->_part = _data->_mFile->getPart (0); + } + partidx = _data->getPartIdx (); _data->_storage = _ctxt.storage (partidx); + // silly protection rules mean make_unique can't be used here if (_data->_storage == EXR_STORAGE_DEEP_SCANLINE) { @@ -378,12 +398,26 @@ InputFile::initialize (void) _data->_numThreads)); } } + else if (_data->_storage == EXR_STORAGE_SCANLINE) + { + if (_data->_part) + { + _data->_sFile.reset (new ScanLineInputFile (_data->_part)); + } + else + { + _data->_sFile.reset (new ScanLineInputFile ( + _data->getHeader (partidx), + _data->getCompatStream (), + _data->_numThreads)); + } + } else { - _data->_sFile = std::make_unique ( - _data->getHeader (partidx), - _data->getCompatStream (), - _data->_numThreads); + THROW ( + IEX_NAMESPACE::ArgExc, + "Unable to handle data storage type in file '" << fileName () + << "'"); } } diff --git a/src/lib/OpenEXR/ImfInputFile.h b/src/lib/OpenEXR/ImfInputFile.h index 5716be51f9..5c4b7707ce 100644 --- a/src/lib/OpenEXR/ImfInputFile.h +++ b/src/lib/OpenEXR/ImfInputFile.h @@ -64,6 +64,9 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile const ContextInitializer& ctxtinit, int numThreads = globalThreadCount ()); + // TODO: remove once multipart is cleaned up + IMF_EXPORT ~InputFile () override; + //------------------------ // Access to the file name //------------------------ From 66aceb00011b5f5537c31d7c5be84435c9c53ca3 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Thu, 18 Apr 2024 01:20:56 +1200 Subject: [PATCH 10/12] Better handle streams and attempts to read past end Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfContextInit.cpp | 31 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/lib/OpenEXR/ImfContextInit.cpp b/src/lib/OpenEXR/ImfContextInit.cpp index 42877eb8d5..6187c07083 100644 --- a/src/lib/OpenEXR/ImfContextInit.cpp +++ b/src/lib/OpenEXR/ImfContextInit.cpp @@ -69,26 +69,25 @@ istream_read ( int64_t nread = ih->_cur_offset; try { - if (ih->_stream->isMemoryMapped ()) - memcpy ( - buffer, - ih->_stream->readMemoryMapped (static_cast (sz)), - sz); - else - ih->_stream->read ( - static_cast (buffer), static_cast (sz)); - ih->_cur_offset = ih->_stream->tellg (); - nread = ih->_cur_offset - nread; + ih->_stream->read ( + static_cast (buffer), static_cast (sz)); } catch (...) { - error_cb ( - ctxt, - EXR_ERR_READ_IO, - "Unable to seek to desired offset %" PRIu64, - offset); - nread = -1; + // bah, there could be two reasons for this, one is a + // legitimate error, the other is a read past the end of file + // (i.e. the core library tries to read a 4k block when + // parsing the header), let's let the core deal with that and + // clear errors + ih->_stream->clear (); + //error_cb ( + // ctxt, + // EXR_ERR_READ_IO, + // "Unable to read requested bytes: %" PRIu64, + // sz); } + ih->_cur_offset = ih->_stream->tellg (); + nread = ih->_cur_offset - nread; return nread; } From 2297c102a89854cc9df524a21d886cd27c50ca57 Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Thu, 18 Apr 2024 01:21:15 +1200 Subject: [PATCH 11/12] handle custom attributes Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfContext.cpp | 79 +++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/src/lib/OpenEXR/ImfContext.cpp b/src/lib/OpenEXR/ImfContext.cpp index 580c9c5096..0be5ea55df 100644 --- a/src/lib/OpenEXR/ImfContext.cpp +++ b/src/lib/OpenEXR/ImfContext.cpp @@ -106,6 +106,61 @@ class LegacyStream final : public IStream exr_read_func_ptr_t _read_fn = nullptr; }; // class LegacyStream +class MemAttrStream : public OPENEXR_IMF_NAMESPACE::IStream +{ +public: + MemAttrStream (const exr_attr_opaquedata_t *opaque) + : IStream (""), + _data (static_cast (opaque->packed_data)), + _sz (static_cast (opaque->size)), + _pos (0) + {} + + ~MemAttrStream () override {} + + bool isMemoryMapped () const override { return true; } + + bool read (char c[/*n*/], int n) override + { + if (_pos >= _sz && n != 0) + throw IEX_NAMESPACE::InputExc ("Unexpected end of file."); + + uint64_t n2 = n; + bool retVal = true; + + if (_sz - _pos <= n2) + { + n2 = _sz - _pos; + retVal = false; + } + + memcpy (c, _data + _pos, n2); + _pos += n2; + return retVal; + } + + char* readMemoryMapped (int n) override + { + if (_pos >= _sz) + throw IEX_NAMESPACE::InputExc ("Unexpected end of file."); + + if (_pos + n > _sz) + throw IEX_NAMESPACE::InputExc ("Reading past end of file."); + + char* retVal = _data + _pos; + _pos += n; + return retVal; + } + uint64_t tellg () override { return _pos; } + void seekg (uint64_t pos) override { _pos = pos; } + void clear () override {} + +private: + char* _data; + uint64_t _sz; + uint64_t _pos; +}; + } // namespace //////////////////////////////////////// @@ -641,11 +696,25 @@ Context::header (int partidx) const break; case EXR_ATTR_OPAQUE: - hdr.insert ( - cur->name, OpaqueAttribute ( - cur->type_name, - cur->opaque->size, - cur->opaque->packed_data)); + if (Attribute::knownType (cur->type_name)) + { + MemAttrStream mas {cur->opaque}; + + std::unique_ptr attr; + attr.reset (Attribute::newAttribute (cur->type_name)); + + attr->readValueFrom (mas, cur->opaque->size, version ()); + // TODO: can we avoid a double copy? + hdr.insert (cur->name, *attr); + } + else + { + hdr.insert ( + cur->name, OpaqueAttribute ( + cur->type_name, + cur->opaque->size, + cur->opaque->packed_data)); + } break; case EXR_ATTR_UNKNOWN: From 43155c0659f2b858cd577719feb0b01b79fee3ae Mon Sep 17 00:00:00 2001 From: Kimball Thurston Date: Thu, 18 Apr 2024 19:17:46 +1200 Subject: [PATCH 12/12] Remove unused header, fix attribute spec order to work with clang Signed-off-by: Kimball Thurston --- src/lib/OpenEXR/ImfContext.cpp | 1 - src/lib/OpenEXR/ImfInputFile.h | 17 +++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/OpenEXR/ImfContext.cpp b/src/lib/OpenEXR/ImfContext.cpp index 0be5ea55df..ab1b92f5ed 100644 --- a/src/lib/OpenEXR/ImfContext.cpp +++ b/src/lib/OpenEXR/ImfContext.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include diff --git a/src/lib/OpenEXR/ImfInputFile.h b/src/lib/OpenEXR/ImfInputFile.h index 5c4b7707ce..e89201faf8 100644 --- a/src/lib/OpenEXR/ImfInputFile.h +++ b/src/lib/OpenEXR/ImfInputFile.h @@ -78,9 +78,9 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // Access to the file header //-------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ( "Use context-based attribute access for faster retrieval") + IMF_EXPORT const Header& header () const; //---------------------------------- @@ -101,18 +101,18 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // to readPixels(). //----------------------------------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ( "Use stateless API to pass framebuffer with read request") + IMF_EXPORT void setFrameBuffer (const FrameBuffer& frameBuffer); //----------------------------------- // Access to the current frame buffer //----------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ( "Use stateless API to pass framebuffer with read request") + IMF_EXPORT const FrameBuffer& frameBuffer () const; //--------------------------------------------------------------- @@ -145,8 +145,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // //--------------------------------------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ("No longer meaningful") + IMF_EXPORT bool isOptimizationEnabled () const; //--------------------------------------------------------------- @@ -168,13 +168,14 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // //--------------------------------------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ( "Use stateless API to pass framebuffer with read request") - void readPixels (int scanLine1, int scanLine2); IMF_EXPORT + void readPixels (int scanLine1, int scanLine2); + OPENEXR_DEPRECATED ( "Use stateless API to pass framebuffer with read request") + IMF_EXPORT void readPixels (int scanLine); //---------------------------------------------- @@ -183,8 +184,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // used to implement OutputFile::copyPixels()). //---------------------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ("Prefer using externally managed buffer") + IMF_EXPORT void rawPixelData ( int firstScanLine, const char*& pixelData, int& pixelDataSize); @@ -214,8 +215,8 @@ class IMF_EXPORT_TYPE InputFile : public GenericInputFile // used to implement TiledOutputFile::copyPixels()). //-------------------------------------------------- - IMF_EXPORT OPENEXR_DEPRECATED ("Prefer using externally managed buffer") + IMF_EXPORT void rawTileData ( int& dx, int& dy,