Skip to content

Commit 10c758a

Browse files
committed
feat(exr): Write OpenEXR colorInteropID metadata based on oiio:ColorSpace
If the colorspace exists and has an interop ID in an OCIO 2.5 config, use that. Otherwise check if the colorspace is equivalent to a known color interop ID. Adds a new get_color_interop_id(colorspace) API function. Signed-off-by: Brecht Van Lommel <[email protected]>
1 parent 3d377bd commit 10c758a

File tree

10 files changed

+124
-11
lines changed

10 files changed

+124
-11
lines changed

src/include/OpenImageIO/color.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ class OIIO_API ColorConfig {
408408
/// @version 3.1
409409
cspan<int> get_cicp(string_view colorspace) const;
410410

411+
/// Find color interop ID for the given colorspace.
412+
/// Returns empty string if not found.
413+
///
414+
/// @version 3.1
415+
string_view get_color_interop_id(string_view colorspace) const;
416+
411417
/// Find color interop ID corresponding to the CICP code.
412418
/// Returns empty string if not found.
413419
///

src/libOpenImageIO/color_ocio.cpp

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,38 +2033,56 @@ enum class CICPRange : int {
20332033
};
20342034

20352035
struct ColorInteropID {
2036+
constexpr ColorInteropID(const char* interop_id)
2037+
: interop_id(interop_id)
2038+
, cicp({ 0, 0, 0, 0 })
2039+
, have_cicp(false)
2040+
{
2041+
}
2042+
20362043
constexpr ColorInteropID(const char* interop_id, CICPPrimaries primaries,
20372044
CICPTransfer transfer, CICPMatrix matrix)
20382045
: interop_id(interop_id)
20392046
, cicp({ int(primaries), int(transfer), int(matrix),
20402047
int(CICPRange::Full) })
2048+
, have_cicp(true)
20412049
{
20422050
}
20432051

20442052
const char* interop_id;
20452053
std::array<int, 4> cicp;
2054+
bool have_cicp;
20462055
};
20472056

20482057
// Mapping between color interop ID and CICP, based on Color Interop Forum
20492058
// recommendations.
20502059
constexpr ColorInteropID color_interop_ids[] = {
20512060
// Scene referred interop IDs first so they are the default in automatic
2052-
// conversion from CICP to interop ID.
2053-
{ "srgb_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::sRGB,
2054-
CICPMatrix::BT709 },
2055-
{ "srgb_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::sRGB,
2056-
CICPMatrix::BT709 },
2057-
{ "g22_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Gamma22,
2058-
CICPMatrix::BT709 },
2059-
// These are not display color spaces at all, but can be represented by CICP.
2061+
// conversion from CICP to interop ID. Some are not display color spaces
2062+
// at all, but can be represented by CICP anyway.
2063+
{ "lin_ap1_scene" },
2064+
{ "lin_ap0_scene" },
20602065
{ "lin_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Linear,
20612066
CICPMatrix::BT709 },
20622067
{ "lin_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::Linear,
20632068
CICPMatrix::BT709 },
20642069
{ "lin_rec2020_scene", CICPPrimaries::Rec2020, CICPTransfer::Linear,
20652070
CICPMatrix::Rec2020_CL },
2071+
{ "lin_adobergb_scene" },
20662072
{ "lin_ciexyzd65_scene", CICPPrimaries::XYZD65, CICPTransfer::Linear,
20672073
CICPMatrix::Unspecified },
2074+
{ "srgb_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::sRGB,
2075+
CICPMatrix::BT709 },
2076+
{ "g22_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Gamma22,
2077+
CICPMatrix::BT709 },
2078+
{ "g18_rec709_scene" },
2079+
{ "srgb_ap1_scene" },
2080+
{ "g22_ap1_scene" },
2081+
{ "srgb_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::sRGB,
2082+
CICPMatrix::BT709 },
2083+
{ "g22_adobergb_scene" },
2084+
{ "data" },
2085+
{ "unknown" },
20682086

20692087
// Display referred interop IDs.
20702088
{ "srgb_rec709_display", CICPPrimaries::Rec709, CICPTransfer::sRGB,
@@ -2084,7 +2102,7 @@ constexpr ColorInteropID color_interop_ids[] = {
20842102
{ "g22_rec709_display", CICPPrimaries::Rec709, CICPTransfer::Gamma22,
20852103
CICPMatrix::BT709 },
20862104
// No CICP code for Adobe RGB primaries.
2087-
// { "g22_adobergb_display" }
2105+
{ "g22_adobergb_display" },
20882106
{ "g26_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::Gamma26,
20892107
CICPMatrix::BT709 },
20902108
{ "g26_xyzd65_display", CICPPrimaries::XYZD65, CICPTransfer::Gamma26,
@@ -2094,11 +2112,35 @@ constexpr ColorInteropID color_interop_ids[] = {
20942112
};
20952113
} // namespace
20962114

2115+
string_view
2116+
ColorConfig::get_color_interop_id(string_view colorspace) const
2117+
{
2118+
if (colorspace.empty())
2119+
return "";
2120+
#if OCIO_VERSION_HEX >= MAKE_OCIO_VERSION_HEX(2, 5, 0)
2121+
if (getImpl()->config_ && !disable_ocio) {
2122+
OCIO::ConstColorSpaceRcPtr c = getImpl()->config_->getColorSpace(
2123+
std::string(resolve(colorspace)).c_str());
2124+
const char* interop_id = (c) ? c->getInteropID() : nullptr;
2125+
if (interop_id) {
2126+
return interop_id;
2127+
}
2128+
}
2129+
#endif
2130+
for (const ColorInteropID& interop : color_interop_ids) {
2131+
if (equivalent(colorspace, interop.interop_id)) {
2132+
return interop.interop_id;
2133+
}
2134+
}
2135+
return "";
2136+
}
2137+
20972138
string_view
20982139
ColorConfig::get_color_interop_id(const int cicp[4]) const
20992140
{
21002141
for (const ColorInteropID& interop : color_interop_ids) {
2101-
if (interop.cicp[0] == cicp[0] && interop.cicp[1] == cicp[1]) {
2142+
if (interop.have_cicp && interop.cicp[0] == cicp[0]
2143+
&& interop.cicp[1] == cicp[1]) {
21022144
return interop.interop_id;
21032145
}
21042146
}
@@ -2110,7 +2152,8 @@ ColorConfig::get_cicp(string_view colorspace) const
21102152
{
21112153
if (!colorspace.empty()) {
21122154
for (const ColorInteropID& interop : color_interop_ids) {
2113-
if (equivalent(colorspace, interop.interop_id)) {
2155+
if (interop.have_cicp
2156+
&& equivalent(colorspace, interop.interop_id)) {
21142157
return interop.cicp;
21152158
}
21162159
}

src/openexr.imageio/exroutput.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <numeric>
1414

1515
#include <OpenImageIO/Imath.h>
16+
#include <OpenImageIO/color.h>
1617
#include <OpenImageIO/platform.h>
1718

1819
#include <OpenEXR/IlmThreadPool.h>
@@ -1026,6 +1027,15 @@ OpenEXROutput::spec_to_header(ImageSpec& spec, int subimage,
10261027
}
10271028
}
10281029

1030+
// Set color interop ID from colorspace
1031+
if (spec.get_string_attribute("colorInteropID").empty()) {
1032+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
1033+
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace");
1034+
string_view interop_id = colorconfig.get_color_interop_id(colorspace);
1035+
if (!interop_id.empty())
1036+
spec.attribute("colorInteropID", interop_id);
1037+
}
1038+
10291039
// Deal with all other params
10301040
for (const auto& p : spec.extra_attribs)
10311041
put_parameter(p.name().string(), p.type(), p.data(), header);

src/python/py_colorconfig.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ declare_colorconfig(py::module& m)
161161
return self.equivalent(color_space, other_color_space);
162162
},
163163
"color_space"_a, "other_color_space"_a)
164+
.def("get_color_interop_id",
165+
[](const ColorConfig& self, const std::string& colorspace) {
166+
return std::string(self.get_color_interop_id(colorspace));
167+
})
164168
.def("get_color_interop_id",
165169
[](const ColorConfig& self, const std::array<int, 4> cicp) {
166170
return std::string(self.get_color_interop_id(cicp.data()));

testsuite/openexr-suite/ref/out.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,3 +369,37 @@ Full command line was:
369369
oiiotool ERROR: -o : Cannot output non-compliant ACES Container in 'strict' mode. REASON: EXR data type is not 'HALF' as required for an ACES Container.
370370
Full command line was:
371371
> oiiotool --create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr
372+
Reading color_interop_id_scene_linear.exr
373+
color_interop_id_scene_linear.exr : 4 x 4, 3 channel, float openexr
374+
SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339
375+
channel list: R, G, B
376+
colorInteropID: "lin_ap1_scene"
377+
compression: "zip"
378+
PixelAspectRatio: 1
379+
screenWindowCenter: 0, 0
380+
screenWindowWidth: 1
381+
oiio:ColorSpace: "lin_ap1_scene"
382+
oiio:subimages: 1
383+
openexr:lineOrder: "increasingY"
384+
Reading color_interop_id_linear_adobergb.exr
385+
color_interop_id_linear_adobergb.exr : 4 x 4, 3 channel, float openexr
386+
SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339
387+
channel list: R, G, B
388+
colorInteropID: "lin_adobergb_scene"
389+
compression: "zip"
390+
PixelAspectRatio: 1
391+
screenWindowCenter: 0, 0
392+
screenWindowWidth: 1
393+
oiio:ColorSpace: "lin_adobergb_scene"
394+
oiio:subimages: 1
395+
openexr:lineOrder: "increasingY"
396+
Reading color_interop_id_unknown.exr
397+
color_interop_id_unknown.exr : 4 x 4, 3 channel, float openexr
398+
SHA-1: D7699308C38CD04EEB732577A82D31D04E05A339
399+
channel list: R, G, B
400+
compression: "zip"
401+
PixelAspectRatio: 1
402+
screenWindowCenter: 0, 0
403+
screenWindowWidth: 1
404+
oiio:subimages: 1
405+
openexr:lineOrder: "increasingY"

testsuite/openexr-suite/run.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,11 @@
8383

8484
# Invalid data type
8585
command += oiiotool("--create 4x4 3 -d float --compression none -sattrib openexr:ACESContainerPolicy strict -o strict-fail.exr", failureok=True)
86+
87+
# Check color interop ID output
88+
command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace scene_linear -o color_interop_id_scene_linear.exr")
89+
command += info_command("color_interop_id_scene_linear.exr", safematch=True)
90+
command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace lin_adobergb_scene -o color_interop_id_linear_adobergb.exr")
91+
command += info_command("color_interop_id_linear_adobergb.exr", safematch=True)
92+
command += oiiotool("--create 4x4 3 --attrib oiio:ColorSpace unknown_interop_id -o color_interop_id_unknown.exr")
93+
command += info_command("color_interop_id_unknown.exr", safematch=True)

testsuite/python-colorconfig/ref/out-ocio23.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ equivalent('linear', 'lin_srgb'): False
2626
equivalent('scene_linear', 'lin_srgb'): False
2727
equivalent('ACEScg', 'scene_linear'): True
2828
equivalent('lnf', 'scene_linear'): False
29+
get_color_interop_id('ACEScg') = lin_ap1_scene
30+
get_color_interop_id('lin_srgb') = lin_rec709_scene
2931
get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene
3032
get_cicp('pq_rec2020_display') = [9, 16, 9, 1]
3133
get_cicp('unknown_interop_id') = None

testsuite/python-colorconfig/ref/out-ocio24.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ equivalent('linear', 'lin_srgb'): False
2626
equivalent('scene_linear', 'lin_srgb'): False
2727
equivalent('ACEScg', 'scene_linear'): True
2828
equivalent('lnf', 'scene_linear'): False
29+
get_color_interop_id('ACEScg') = lin_ap1_scene
30+
get_color_interop_id('lin_srgb') = lin_rec709_scene
2931
get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene
3032
get_cicp('pq_rec2020_display') = [9, 16, 9, 1]
3133
get_cicp('unknown_interop_id') = None

testsuite/python-colorconfig/ref/out-ocio25.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ equivalent('linear', 'lin_srgb'): False
2626
equivalent('scene_linear', 'lin_srgb'): False
2727
equivalent('ACEScg', 'scene_linear'): True
2828
equivalent('lnf', 'scene_linear'): False
29+
get_color_interop_id('ACEScg') = lin_ap1_scene
30+
get_color_interop_id('lin_srgb') = lin_rec709_scene
2931
get_color_interop_id([1, 13, 1, 1]) = srgb_rec709_scene
3032
get_cicp('pq_rec2020_display') = [9, 16, 9, 1]
3133
get_cicp('unknown_interop_id') = None

testsuite/python-colorconfig/src/test_colorconfig.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
print ("equivalent('scene_linear', 'lin_srgb'):", config.equivalent("scene_linear", "lin_srgb"))
4949
print ("equivalent('ACEScg', 'scene_linear'):", config.equivalent("ACEScg", "scene_linear"))
5050
print ("equivalent('lnf', 'scene_linear'):", config.equivalent("lnf", "scene_linear"))
51+
print ("get_color_interop_id('ACEScg') = ", config.get_color_interop_id("ACEScg"))
52+
print ("get_color_interop_id('lin_srgb') = ", config.get_color_interop_id("lin_srgb"))
5153
print ("get_color_interop_id([1, 13, 1, 1]) = ", config.get_color_interop_id([1, 13, 1, 1]))
5254
print ("get_cicp('pq_rec2020_display') = ", config.get_cicp("pq_rec2020_display"))
5355
print ("get_cicp('unknown_interop_id') = ", config.get_cicp("unknown_interop_id"))

0 commit comments

Comments
 (0)