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/3] 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/3] 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) From 6f87cc09f6c1e709674edc528d576f183d38f625 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 5 Sep 2026 20:24:42 +0200 Subject: [PATCH 3/3] Make write validation optional with openexr:ColorInteropIDPolicy Signed-off-by: Brecht Van Lommel --- src/doc/builtinplugins.rst | 8 ++++ src/openexr.imageio/exroutput.cpp | 40 ++++++++++++------ .../openexr-multipart-colorspace/ref/out.txt | 4 +- testsuite/openexr-multipart-colorspace/run.py | 8 +++- .../src/multipart_data.exr | Bin 1108 -> 0 bytes 5 files changed, 42 insertions(+), 18 deletions(-) delete mode 100644 testsuite/openexr-multipart-colorspace/src/multipart_data.exr diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 833346310c..20c2a09727 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1741,6 +1741,14 @@ control aspects of the writing itself: While in `relaxed` mode, if the spec is non-compliant, `chromaticities` and `colorInteropID` will be set, but `acesImageContainerFlag` will NOT. + * - ``openexr:ColorInteropIDPolicy`` + - string + - One of `none` (default) or `strict`, taken from the first subimage. + In `strict` mode, if the spec is non-compliant with the ASWF Color + Interop Forum Recommendation for OpenEXR files, the output will + throw an error and avoid writing the image. For compliance, the + `colorInteropID` in all subimages must be either equal to the first + subimage, or be unspecified or set to `data`. * - ``oiio:RawColor`` - int - If nonzero, writing images with non-RGB color models (such as YCbCr) diff --git a/src/openexr.imageio/exroutput.cpp b/src/openexr.imageio/exroutput.cpp index 15beb20211..fd9059fad8 100644 --- a/src/openexr.imageio/exroutput.cpp +++ b/src/openexr.imageio/exroutput.cpp @@ -187,6 +187,9 @@ class OpenEXROutput final : public ImageOutput { // spec a bit. bool spec_to_header(ImageSpec& spec, int subimage, Imf::Header& header); + // Validate color interop IDs if openexr:ColorInteropIDPolicy is set. + bool validate_color_interop_ids(); + // Compute an OpenEXR PixelType from an OIIO TypeDesc Imf::PixelType imfpixeltype(TypeDesc type); @@ -710,17 +713,28 @@ 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) +bool +OpenEXROutput::validate_color_interop_ids() { + string_view policy = m_subimagespecs[0].get_string_attribute( + "openexr:ColorInteropIDPolicy", "none"); + if (policy == "none") + return true; + + if (policy != "strict") { + errorfmt("Unknown openexr:ColorInteropIDPolicy \"{}\"", policy); + return false; + } + + // Follow the color interop forum recommendation, where the colorInteropID + // of later parts must match the first part, except when "data" or missing. + // + // In the future, checkColorMetadata added in OpenEXR 3.5 can replace this. string_view file_interop_id; - for (size_t s = 0; s < headers.size(); ++s) { + for (size_t s = 0; s < m_headers.size(); ++s) { const Imf::StringAttribute* attr - = headers[s].findTypedAttribute( + = m_headers[s].findTypedAttribute( "colorInteropID"); string_view interop_id = attr ? string_view(attr->value()) : string_view(); @@ -734,12 +748,13 @@ validate_color_interop_ids(const std::vector& headers) || interop_id == file_interop_id) continue; - return Strutil::fmt::format( + errorfmt( "OpenEXR subimage {} has color space \"{}\", different from \"{}\" in the first subimage", s, interop_id, file_interop_id); + return false; } - return ""; + return true; } @@ -791,11 +806,8 @@ 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); + if (!validate_color_interop_ids()) return false; - } m_spec = m_subimagespecs[0]; sanity_check_channelnames(); @@ -1184,7 +1196,7 @@ static ExrMeta exr_meta_translation[] = { // user or from a file we read. ExrMeta("YResolution"), ExrMeta("planarconfig"), ExrMeta("type"), ExrMeta("tiles"), ExrMeta("chunkCount"), ExrMeta("maxSamplesPerPixel"), - ExrMeta("openexr:roundingmode") + ExrMeta("openexr:roundingmode"), ExrMeta("openexr:ColorInteropIDPolicy") }; diff --git a/testsuite/openexr-multipart-colorspace/ref/out.txt b/testsuite/openexr-multipart-colorspace/ref/out.txt index aee7b35c1d..6859cb2691 100644 --- a/testsuite/openexr-multipart-colorspace/ref/out.txt +++ b/testsuite/openexr-multipart-colorspace/ref/out.txt @@ -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 mismatched.exr +> oiiotool --pattern constant:color=1,0,0 4x4 3 -d half --attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty -sattrib openexr:ColorInteropIDPolicy strict --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 data_first.exr +> oiiotool --pattern constant:color=0.25,0.25,0.25 4x4 3 -d half --attrib oiio:ColorSpace data --attrib oiio:subimagename depth -sattrib openexr:ColorInteropIDPolicy strict --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 afaafc1631..9bc082c7d0 100644 --- a/testsuite/openexr-multipart-colorspace/run.py +++ b/testsuite/openexr-multipart-colorspace/run.py @@ -47,14 +47,16 @@ # 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 " + "-sattrib openexr:ColorInteropIDPolicy strict " "--pattern constant:color=0,1,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename specular " "--siappendall -o matched.exr") # Parts: "lin_ap1_scene", "lin_rec709_scene" -# Not valid according to the CIF recommendation, error on write. +# Not valid according to the CIF recommendation, error with strict policy. command += oiiotool("--pattern constant:color=1,0,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_ap1_scene --attrib oiio:subimagename beauty " + "-sattrib openexr:ColorInteropIDPolicy strict " "--pattern constant:color=0,1,0 4x4 3 -d half " "--attrib oiio:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular " "--siappendall -o mismatched.exr", failureok=True) @@ -62,14 +64,16 @@ # 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 " + "-sattrib openexr:ColorInteropIDPolicy strict " "--pattern constant:color=0,1,0 4x4 3 -d half " "--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. +# Not valid according to the CIF recommendation, error with strict policy. command += oiiotool("--pattern constant:color=0.25,0.25,0.25 4x4 3 -d half " "--attrib oiio:ColorSpace data --attrib oiio:subimagename depth " + "-sattrib openexr:ColorInteropIDPolicy strict " "--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 " diff --git a/testsuite/openexr-multipart-colorspace/src/multipart_data.exr b/testsuite/openexr-multipart-colorspace/src/multipart_data.exr deleted file mode 100644 index 7941a06a279e4c858cf86bf30fba081961852394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1108 zcmds0y-ve05WZ577(hbe1v0SGCQh5C5fW)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