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..aee7b35c1d --- /dev/null +++ b/testsuite/openexr-multipart-colorspace/ref/out.txt @@ -0,0 +1,316 @@ +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 + 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 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 + 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 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 + 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 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 + 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 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 diff --git a/testsuite/openexr-multipart-colorspace/run.py b/testsuite/openexr-multipart-colorspace/run.py new file mode 100644 index 0000000000..afaafc1631 --- /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 copy_from_first.exr") +command += info_command("copy_from_first.exr", + extraargs="-oiioattrib openexr:core 0", safematch=True) +command += info_command("copy_from_first.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 missing_first.exr") +command += info_command("missing_first.exr", + extraargs="-oiioattrib openexr:core 0", safematch=True) +command += info_command("missing_first.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 matched.exr") + +# 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:ColorSpace lin_rec709_scene --attrib oiio:subimagename specular " + "--siappendall -o mismatched.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 missing_second.exr") + +# 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 data_first.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 0000000000..b6769d4b90 Binary files /dev/null and b/testsuite/openexr-multipart-colorspace/src/multipart_colorspace_data_first.exr differ diff --git a/testsuite/openexr-multipart-colorspace/src/multipart_data.exr b/testsuite/openexr-multipart-colorspace/src/multipart_data.exr new file mode 100644 index 0000000000..7941a06a27 Binary files /dev/null and b/testsuite/openexr-multipart-colorspace/src/multipart_data.exr differ