From 04a9d30e7dd6dd2f0bab9172d9cfb03bd6b7f8d7 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 25 Aug 2026 00:00:12 +0200 Subject: [PATCH 1/2] feat(openexr): Follow CIF for color interop ID in multi-part files On read, a part without a colorInteropID now gets it from the first part, if there is any. There is no validation, EXR files that do not follow the CIF recommendation are still accepted. On write, give an error when the colorInteropID in later parts is different than the first part, except if is missing or set to "data". Upcoming versions of OpenEXR will throw an exception in this case, this applies the same logic for existing versions of OpenEXR already. Note this also means writing parts in order like this is now an error: * "lin_ap0_scene", "lin_ap1_scene" * "data", "lin_ap0_scene" * missing, "lin_ap0_scene" It also means "data" becomes "data" "data" on read. I considered automatically reordering headers to accept cases where a "data" part happens to come first. However attributes in the first header have a special meaning, and reordering attributes other than colorInteropID to later headers would be problematic. Ref AcademySoftwareFoundation/openexr#2560 Signed-off-by: Brecht Van Lommel --- src/cmake/testing.cmake | 3 + src/openexr.imageio/exr_pvt.h | 2 + src/openexr.imageio/exrinput.cpp | 24 +- src/openexr.imageio/exrinput_c.cpp | 30 +- src/openexr.imageio/exroutput.cpp | 39 +++ .../openexr-multipart-colorspace/ref/out.txt | 316 ++++++++++++++++++ testsuite/openexr-multipart-colorspace/run.py | 77 +++++ .../src/multipart_colorspace_data_first.exr | Bin 0 -> 2217 bytes .../src/multipart_data.exr | Bin 0 -> 1108 bytes 9 files changed, 487 insertions(+), 4 deletions(-) create mode 100644 testsuite/openexr-multipart-colorspace/ref/out.txt create mode 100644 testsuite/openexr-multipart-colorspace/run.py create mode 100644 testsuite/openexr-multipart-colorspace/src/multipart_colorspace_data_first.exr create mode 100644 testsuite/openexr-multipart-colorspace/src/multipart_data.exr diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 0db17cc7d4..8aaa3bb900 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -469,6 +469,9 @@ macro (oiio_add_all_tests) # Self-contained decompression-bomb regression (ships its own tiny fixture); # exercises both the C++ and C-API readers via the openexr:core attribute. oiio_add_tests (openexr-bomb) + # Self-contained multi-part colorInteropID inheritance test (ships its own + # tiny fixture); exercises both the C++ and C-API readers. + oiio_add_tests (openexr-multipart-colorspace) # if (NOT DEFINED ENV{${PROJECT_NAME}_CI}) # oiio_add_tests (openexr-damaged # IMAGEDIR openexr-images diff --git a/src/openexr.imageio/exr_pvt.h b/src/openexr.imageio/exr_pvt.h index 250639b74f..3cf7c14336 100644 --- a/src/openexr.imageio/exr_pvt.h +++ b/src/openexr.imageio/exr_pvt.h @@ -232,6 +232,7 @@ class OpenEXRInput final : public ImageInput { int m_miplevel; ///< What MIP level are we looking at? std::vector m_missingcolor; ///< Color for missing tile/scanline std::string m_filename; // filename, if known + std::string m_file_color_interop_id; void init() { @@ -248,6 +249,7 @@ class OpenEXRInput final : public ImageInput { m_local_io.reset(); m_missingcolor.clear(); m_filename.clear(); + m_file_color_interop_id.clear(); } bool read_native_scanlines_individually(int subimage, int miplevel, diff --git a/src/openexr.imageio/exrinput.cpp b/src/openexr.imageio/exrinput.cpp index ac770dafa2..72ea97287c 100644 --- a/src/openexr.imageio/exrinput.cpp +++ b/src/openexr.imageio/exrinput.cpp @@ -218,6 +218,17 @@ OpenEXRInput::valid_file(Filesystem::IOProxy* ioproxy) const +// Color space shared by all parts of the file, taken from the first part. +static std::string +file_color_interop_id(const Imf::MultiPartInputFile* multipart) +{ + const Imf::StringAttribute* attr + = multipart->header(0).findTypedAttribute( + "colorInteropID"); + return attr ? attr->value() : std::string(); +} + + bool OpenEXRInput::open(const std::string& name, ImageSpec& newspec, const ImageSpec& config) @@ -326,6 +337,8 @@ OpenEXRInput::open(const std::string& name, ImageSpec& newspec, m_subimage = -1; m_miplevel = -1; + m_file_color_interop_id = file_color_interop_id(m_input_multipart); + // Set up for the first subimage ("part"). This will trigger reading // information about all the parts. bool ok = seek_subimage(0, 0); @@ -731,8 +744,15 @@ OpenEXRInput::PartInfo::parse_header(OpenEXRInput* in, // Try to figure out the color space for some unambiguous cases if (spec.get_int_attribute("acesImageContainerFlag") == 1) { spec.set_colorspace("lin_ap0_scene"); - } else if (auto c = spec.find_attribute("colorInteropID", TypeString)) { - spec.set_colorspace(c->get_ustring()); + } else { + // Follow the color interop forum recommendation for OpenEXR files, + // inheriting the colorInteropID from the first part. + string_view interop_id = spec.get_string_attribute("colorInteropID"); + if (!interop_id.empty()) { + spec.set_colorspace(interop_id); + } else if (!in->m_file_color_interop_id.empty()) { + spec.set_colorspace(in->m_file_color_interop_id); + } } // Squash some problematic texture metadata if we suspect it's wrong diff --git a/src/openexr.imageio/exrinput_c.cpp b/src/openexr.imageio/exrinput_c.cpp index 3c13b07f7c..88f7c68865 100644 --- a/src/openexr.imageio/exrinput_c.cpp +++ b/src/openexr.imageio/exrinput_c.cpp @@ -212,6 +212,7 @@ class OpenEXRCoreInput final : public ImageInput { int m_nsubimages; ///< How many subimages are there? std::vector m_missingcolor; ///< Color for missing tile/scanline std::string m_filename; // filename, if known + std::string m_file_color_interop_id; void init() { @@ -221,6 +222,7 @@ class OpenEXRCoreInput final : public ImageInput { m_local_io.reset(); m_missingcolor.clear(); m_filename.clear(); + m_file_color_interop_id.clear(); } bool valid_file_or_proxy(const std::string& filename, @@ -340,6 +342,21 @@ OpenEXRCoreInput::valid_file_or_proxy(const std::string& filename, +// Color space shared by all parts of the file, taken from the first part. +static std::string +file_color_interop_id(exr_context_t ctxt) +{ + int32_t length = 0; + const char* interop = nullptr; + if (exr_attr_get_string(ctxt, 0, "colorInteropID", &length, &interop) + != EXR_ERR_SUCCESS + || !interop) + return std::string(); + + return std::string(interop, size_t(length)); +} + + bool OpenEXRCoreInput::open(const std::string& name, ImageSpec& newspec, const ImageSpec& config) @@ -439,6 +456,8 @@ OpenEXRCoreInput::open(const std::string& name, ImageSpec& newspec, m_subimage = -1; m_miplevel = -1; + m_file_color_interop_id = file_color_interop_id(m_exr_context); + // Set up for the first subimage ("part"). This will trigger reading // information about all the parts. bool ok = seek_subimage(0, 0); @@ -825,8 +844,15 @@ OpenEXRCoreInput::PartInfo::parse_header(OpenEXRCoreInput* in, // Try to figure out the color space for some unambiguous cases if (spec.get_int_attribute("acesImageContainerFlag") == 1) { spec.set_colorspace("lin_ap0_scene"); - } else if (auto c = spec.find_attribute("colorInteropID", TypeString)) { - spec.set_colorspace(c->get_ustring()); + } else { + // Follow the color interop forum recommendation for OpenEXR files, + // inheriting the colorInteropID from the first part. + string_view interop_id = spec.get_string_attribute("colorInteropID"); + if (!interop_id.empty()) { + spec.set_colorspace(interop_id); + } else if (!in->m_file_color_interop_id.empty()) { + spec.set_colorspace(in->m_file_color_interop_id); + } } // Squash some problematic texture metadata if we suspect it's wrong diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 3b3c29ffaa..15beb20211 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -710,6 +710,39 @@ OpenEXROutput::open(const std::string& name, const ImageSpec& userspec, } +// Follow the color interop forum recommendation for OpenEXR files, +// where the colorInteropID in later parts must match the first part, +// except when "data" or missing. +static std::string +validate_color_interop_ids(const std::vector& headers) +{ + string_view file_interop_id; + + for (size_t s = 0; s < headers.size(); ++s) { + const Imf::StringAttribute* attr + = headers[s].findTypedAttribute( + "colorInteropID"); + string_view interop_id = attr ? string_view(attr->value()) + : string_view(); + + if (s == 0) { + file_interop_id = interop_id; + continue; + } + + if (interop_id.empty() || interop_id == "data" + || interop_id == file_interop_id) + continue; + + return Strutil::fmt::format( + "OpenEXR subimage {} has color space \"{}\", different from \"{}\" in the first subimage", + s, interop_id, file_interop_id); + } + + return ""; +} + + bool OpenEXROutput::open(const std::string& name, int subimages, @@ -758,6 +791,12 @@ OpenEXROutput::open(const std::string& name, int subimages, } } + std::string interop_id_error = validate_color_interop_ids(m_headers); + if (!interop_id_error.empty()) { + errorfmt("{}", interop_id_error); + return false; + } + m_spec = m_subimagespecs[0]; sanity_check_channelnames(); compute_pixeltypes(m_spec); diff --git a/testsuite/openexr-multipart-colorspace/ref/out.txt b/testsuite/openexr-multipart-colorspace/ref/out.txt new file mode 100644 index 0000000000..70aab0ea85 --- /dev/null +++ b/testsuite/openexr-multipart-colorspace/ref/out.txt @@ -0,0 +1,316 @@ +Reading typical.exr +typical.exr : 4 x 4, 3 channel, half openexr + 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 + channel list: R, G, B + colorInteropID: "lin_ap1_scene" + compression: "zip" + name: "beauty" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "beauty" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: 2C000460D7DBE3E3F8F015B585D48396D79A953C + channel list: R, G, B + compression: "zip" + name: "diffuse" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "diffuse" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: 09130898C63D0749C555654FBF1EF5CA59A79D07 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "depth" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "depth" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 3: 4 x 4, 3 channel, half openexr + SHA-1: 5497DD3A644B9A44B5259A409346F9184A58891A + channel list: R, G, B + compression: "zip" + name: "specular" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "specular" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +Reading typical.exr +typical.exr : 4 x 4, 3 channel, half openexr + 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 + channel list: R, G, B + colorInteropID: "lin_ap1_scene" + compression: "zip" + name: "beauty" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "beauty" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: 2C000460D7DBE3E3F8F015B585D48396D79A953C + channel list: R, G, B + compression: "zip" + name: "diffuse" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "diffuse" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: 09130898C63D0749C555654FBF1EF5CA59A79D07 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "depth" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "depth" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 3: 4 x 4, 3 channel, half openexr + SHA-1: 5497DD3A644B9A44B5259A409346F9184A58891A + channel list: R, G, B + compression: "zip" + name: "specular" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "specular" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +Reading typical_nocolorspace.exr +typical_nocolorspace.exr : 4 x 4, 3 channel, half openexr + 3 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 + channel list: R, G, B + compression: "zip" + name: "beauty" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:subimagename: "beauty" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: 09130898C63D0749C555654FBF1EF5CA59A79D07 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "depth" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "depth" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: 5497DD3A644B9A44B5259A409346F9184A58891A + channel list: R, G, B + compression: "zip" + name: "specular" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:subimagename: "specular" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +Reading typical_nocolorspace.exr +typical_nocolorspace.exr : 4 x 4, 3 channel, half openexr + 3 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 + channel list: R, G, B + compression: "zip" + name: "beauty" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:subimagename: "beauty" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: 09130898C63D0749C555654FBF1EF5CA59A79D07 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "depth" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "depth" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: 5497DD3A644B9A44B5259A409346F9184A58891A + channel list: R, G, B + compression: "zip" + name: "specular" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:subimagename: "specular" + oiio:subimages: 3 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +Reading src/multipart_colorspace_data_first.exr +src/multipart_colorspace_data_first.exr : 4 x 4, 3 channel, half openexr + 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "data0" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "data0" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + compression: "zip" + name: "missing1" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "missing1" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + colorInteropID: "lin_ap1_scene" + compression: "zip" + name: "color2" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "color2" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 3: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + compression: "zip" + name: "missing3" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "missing3" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +Reading src/multipart_colorspace_data_first.exr +src/multipart_colorspace_data_first.exr : 4 x 4, 3 channel, half openexr + 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] + subimage 0: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + colorInteropID: "data" + compression: "zip" + name: "data0" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "data0" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 1: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + compression: "zip" + name: "missing1" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "missing1" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 2: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + colorInteropID: "lin_ap1_scene" + compression: "zip" + name: "color2" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "lin_ap1_scene" + oiio:subimagename: "color2" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" + subimage 3: 4 x 4, 3 channel, half openexr + SHA-1: C49A9785B2243F2F080DAAD1747F119ACCECCFA5 + channel list: R, G, B + compression: "zip" + name: "missing3" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "data" + oiio:subimagename: "missing3" + oiio:subimages: 4 + openexr:chunkCount: 1 + openexr:lineOrder: "increasingY" +oiiotool ERROR: -o : OpenEXR subimage 1 has color space "lin_rec709_scene", different from "lin_ap1_scene" in the first subimage +Full command line was: +> oiiotool --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular --siappendall -o datafirst.exr +oiiotool ERROR: -o : OpenEXR subimage 1 has color space "lin_ap1_scene", different from "data" in the first subimage +Full command line was: +> oiiotool --pattern constant:color=0.25,0.25,0.25 4x4 3 -d half --attrib oiio:ColorSpace data --attrib oiio:subimagename depth --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular --siappendall -o datafirst.exr diff --git a/testsuite/openexr-multipart-colorspace/run.py b/testsuite/openexr-multipart-colorspace/run.py new file mode 100644 index 0000000000..01aadebeb6 --- /dev/null +++ b/testsuite/openexr-multipart-colorspace/run.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + + +redirect = ' >> out.txt 2>&1 ' + +# Test handling of colorInteropID in multi-part files. + +# Parts: "lin_ap1_scene", missing, "data", missing +command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "--pattern constant:color=0,1,0 4x4 3 -d half " + "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename diffuse " + "--pattern constant:color=0.25,0.25,0.25 4x4 3 -d half " + "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " + "--pattern constant:color=0,0,1 4x4 3 -d half " + "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename specular " + "--siappendall -o typical.exr") +command += info_command("typical.exr", + extraargs="-oiioattrib openexr:core 0", safematch=True) +command += info_command("typical.exr", + extraargs="-oiioattrib openexr:core 1", safematch=True) + +# Parts: missing, "data", missing +command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " + "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename beauty " + "--pattern constant:color=0.25,0.25,0.25 4x4 3 -d half " + "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " + "--pattern constant:color=0,0,1 4x4 3 -d half " + "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename specular " + "--siappendall -o typical_nocolorspace.exr") +command += info_command("typical_nocolorspace.exr", + extraargs="-oiioattrib openexr:core 0", safematch=True) +command += info_command("typical_nocolorspace.exr", + extraargs="-oiioattrib openexr:core 1", safematch=True) + +# Parts: "data", missing, "lin_ap1_scene", missing +# Not valid according to the CIF recommendation, but can be read anyway. +command += info_command("src/multipart_colorspace_data_first.exr", + extraargs="-oiioattrib openexr:core 0", safematch=True) +command += info_command("src/multipart_colorspace_data_first.exr", + extraargs="-oiioattrib openexr:core 1", safematch=True) + +# Parts: "lin_ap1_scene", "lin_ap1_scene" +command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "--pattern constant:color=0,1,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular " + "--siappendall -o datafirst.exr", failureok=True) + +# Parts: "lin_ap1_scene", missing +command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "--pattern constant:color=0,1,0 4x4 3 -d half " + "--attrib oiio:subimagename specular " + "--siappendall -o datafirst.exr", failureok=True) + +# Parts: "lin_ap1_scene", "lin_ap1_scene" +# Not valid according to the CIF recommendation, error on write. +command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "--pattern constant:color=0,1,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular " + "--siappendall -o datafirst.exr", failureok=True) + +# Parts: "data", "lin_ap1_scene", "lin_ap1_scene" +# Not valid according to the CIF recommendation, error on write. +command += oiiotool("--pattern constant:color=0.25,0.25,0.25 4x4 3 -d half " + "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " + "--pattern constant:color=1,0,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "--pattern constant:color=0,1,0 4x4 3 -d half " + "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular " + "--siappendall -o datafirst.exr", failureok=True) diff --git a/testsuite/openexr-multipart-colorspace/src/multipart_colorspace_data_first.exr b/testsuite/openexr-multipart-colorspace/src/multipart_colorspace_data_first.exr new file mode 100644 index 0000000000000000000000000000000000000000..b6769d4b90e31eb0ef6e7fe4e3ba2504a3c37cc4 GIT binary patch literal 2217 zcmeHI%}xR_5FYR+9yBrW1$Y5rx3IwO#jO08J(1|ao2GUPG%jtkt^C~d5qtw5%SZ4r zY++pq96Tw+FiAVj^y^Hg^JO}&xP{^=z>VnZ2~H^}oiaXv5CD3T@@RwyH0qVAWmI;` zD5m35P-^iM%e_8jsZO^Z{PUzRej#qU&^j8EjCdf+qz zR4bus;57oLL57%fnkXQ{geeUT1G|KRnXOoD$JLtUdd*@yJn_etc;XtE8T;8;A`+2C zT+>v@sQs0q-HoxvM2uugm12U!^4dn-vw&O1V=;jTF+~i_TjjE65oW|p$pp`S#3hW= zUK-OBzE|@+IUeP&bYLBtsE%Y)nuJPHqOY-L0{V%-i=+UaFBC~B<%^gtYLkO8>cdff zJAh@#EsOPV#f6^9JnV@%rwHcuX3Q4-zf)qXif*emVupi!;O^@F=dHk!-w^*EOZQ$8 z%IR_+SYB_aCmfpl&6^n7k>gj%v8_#^y6L(@lzl8X)3Z#!X4)`xF2`n`-jU(kIl#wz W)Ew_Ohb^|4J zZUt5tMSizsd%kz)3_7Uacae+wZs@m>-m=|p+igW1FCw@YU{!HmYEEP$FR=XpwJqBT zkRCosG3PXsK+=p!1sx6RLrs5;nAzz4foF8!(F`;G9EurNz_cE3&k~V|GUkewqKF4y zDVp5`D@;UQlvGM4IPBUcYTW?Fj3;6NPhyD}=(nn*ZV*}%CW{QOe#B*r(_xv=627f< zo*d`ZE1eiZ9n~(_l4dWN z_bAk=sKTD>bCO_gZpKW}|9eaPsiK>z Date: Tue, 25 Aug 2026 18:14:47 +0200 Subject: [PATCH 2/2] Improve test naming and failureok usage Signed-off-by: Brecht Van Lommel --- .../openexr-multipart-colorspace/ref/out.txt | 20 ++++++------- testsuite/openexr-multipart-colorspace/run.py | 30 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/testsuite/openexr-multipart-colorspace/ref/out.txt b/testsuite/openexr-multipart-colorspace/ref/out.txt index 70aab0ea85..aee7b35c1d 100644 --- a/testsuite/openexr-multipart-colorspace/ref/out.txt +++ b/testsuite/openexr-multipart-colorspace/ref/out.txt @@ -1,5 +1,5 @@ -Reading typical.exr -typical.exr : 4 x 4, 3 channel, half openexr +Reading copy_from_first.exr +copy_from_first.exr : 4 x 4, 3 channel, half openexr 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] subimage 0: 4 x 4, 3 channel, half openexr SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 @@ -55,8 +55,8 @@ typical.exr : 4 x 4, 3 channel, half openexr oiio:subimages: 4 openexr:chunkCount: 1 openexr:lineOrder: "increasingY" -Reading typical.exr -typical.exr : 4 x 4, 3 channel, half openexr +Reading copy_from_first.exr +copy_from_first.exr : 4 x 4, 3 channel, half openexr 4 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] subimage 0: 4 x 4, 3 channel, half openexr SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 @@ -112,8 +112,8 @@ typical.exr : 4 x 4, 3 channel, half openexr oiio:subimages: 4 openexr:chunkCount: 1 openexr:lineOrder: "increasingY" -Reading typical_nocolorspace.exr -typical_nocolorspace.exr : 4 x 4, 3 channel, half openexr +Reading missing_first.exr +missing_first.exr : 4 x 4, 3 channel, half openexr 3 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] subimage 0: 4 x 4, 3 channel, half openexr SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 @@ -153,8 +153,8 @@ typical_nocolorspace.exr : 4 x 4, 3 channel, half openexr oiio:subimages: 3 openexr:chunkCount: 1 openexr:lineOrder: "increasingY" -Reading typical_nocolorspace.exr -typical_nocolorspace.exr : 4 x 4, 3 channel, half openexr +Reading missing_first.exr +missing_first.exr : 4 x 4, 3 channel, half openexr 3 subimages: 4x4 [h,h,h], 4x4 [h,h,h], 4x4 [h,h,h] subimage 0: 4 x 4, 3 channel, half openexr SHA-1: 2888D7692B43824B1F6B6AFEE923705DC3BE7B32 @@ -310,7 +310,7 @@ src/multipart_colorspace_data_first.exr : 4 x 4, 3 channel, half openexr openexr:lineOrder: "increasingY" oiiotool ERROR: -o : OpenEXR subimage 1 has color space "lin_rec709_scene", different from "lin_ap1_scene" in the first subimage Full command line was: -> oiiotool --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular --siappendall -o datafirst.exr +> oiiotool --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular --siappendall -o mismatched.exr oiiotool ERROR: -o : OpenEXR subimage 1 has color space "lin_ap1_scene", different from "data" in the first subimage Full command line was: -> oiiotool --pattern constant:color=0.25,0.25,0.25 4x4 3 -d half --attrib oiio:ColorSpace data --attrib oiio:subimagename depth --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular --siappendall -o datafirst.exr +> oiiotool --pattern constant:color=0.25,0.25,0.25 4x4 3 -d half --attrib oiio:ColorSpace data --attrib oiio:subimagename depth --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty --pattern constant:color=0,1,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular --siappendall -o data_first.exr diff --git a/testsuite/openexr-multipart-colorspace/run.py b/testsuite/openexr-multipart-colorspace/run.py index 01aadebeb6..afaafc1631 100644 --- a/testsuite/openexr-multipart-colorspace/run.py +++ b/testsuite/openexr-multipart-colorspace/run.py @@ -18,10 +18,10 @@ "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " "--pattern constant:color=0,0,1 4x4 3 -d half " "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename specular " - "--siappendall -o typical.exr") -command += info_command("typical.exr", + "--siappendall -o copy_from_first.exr") +command += info_command("copy_from_first.exr", extraargs="-oiioattrib openexr:core 0", safematch=True) -command += info_command("typical.exr", +command += info_command("copy_from_first.exr", extraargs="-oiioattrib openexr:core 1", safematch=True) # Parts: missing, "data", missing @@ -31,10 +31,10 @@ "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " "--pattern constant:color=0,0,1 4x4 3 -d half " "--eraseattrib oiio:ColorSpace --attrib oiio:subimagename specular " - "--siappendall -o typical_nocolorspace.exr") -command += info_command("typical_nocolorspace.exr", + "--siappendall -o missing_first.exr") +command += info_command("missing_first.exr", extraargs="-oiioattrib openexr:core 0", safematch=True) -command += info_command("typical_nocolorspace.exr", +command += info_command("missing_first.exr", extraargs="-oiioattrib openexr:core 1", safematch=True) # Parts: "data", missing, "lin_ap1_scene", missing @@ -49,22 +49,22 @@ "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " "--pattern constant:color=0,1,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular " - "--siappendall -o datafirst.exr", failureok=True) + "--siappendall -o matched.exr") -# Parts: "lin_ap1_scene", missing +# Parts: "lin_ap1_scene", "lin_rec709_scene" +# Not valid according to the CIF recommendation, error on write. command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " "--pattern constant:color=0,1,0 4x4 3 -d half " - "--attrib oiio:subimagename specular " - "--siappendall -o datafirst.exr", failureok=True) + "--attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular " + "--siappendall -o mismatched.exr", failureok=True) -# Parts: "lin_ap1_scene", "lin_ap1_scene" -# Not valid according to the CIF recommendation, error on write. +# Parts: "lin_ap1_scene", missing command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " "--pattern constant:color=0,1,0 4x4 3 -d half " - "--attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular " - "--siappendall -o datafirst.exr", failureok=True) + "--attrib oiio:subimagename specular " + "--siappendall -o missing_second.exr") # Parts: "data", "lin_ap1_scene", "lin_ap1_scene" # Not valid according to the CIF recommendation, error on write. @@ -74,4 +74,4 @@ "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " "--pattern constant:color=0,1,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular " - "--siappendall -o datafirst.exr", failureok=True) + "--siappendall -o data_first.exr", failureok=True)