Skip to content

Commit 647d175

Browse files
committed
feat: Support display color space interop IDs in I/O
Previously only srgb_rec709_scene was recognized as sRGB for file metadata and display, now srgb_rec709_display and g22_rec709_display are treated as sRGB as well. g24_rec709_display is now recognized as gamma 2.4. The reason for g22_rec709_display behavior is that this type of display is often used to correct for the discrepancy where images are encoded as sRGB but usually decoded as gamma 2.2 by the physical display. This adds new is_colorspace_interop_srgb and get_colorspace_rec709_gamma, functions and uses them to make logic between file formats consistent, and fixing some existing issues with gamma metadata. There is existing consistency in that some file formats assume an empty oiio:ColorSpace to mean sRGB, and some don't. This inconsistency is preserved using the default_to_rgb argument. Signed-off-by: Brecht Van Lommel <brecht@blender.org>
1 parent 200539d commit 647d175

File tree

16 files changed

+185
-143
lines changed

16 files changed

+185
-143
lines changed

src/dpx.imageio/dpxoutput.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -436,18 +436,14 @@ DPXOutput::prep_subimage(int s, bool allocate)
436436
m_desc = get_image_descriptor();
437437

438438
// transfer function
439-
const ColorConfig& colorconfig = ColorConfig::default_colorconfig();
440-
std::string colorspace = spec_s.get_string_attribute("oiio:ColorSpace", "");
441-
if (colorconfig.equivalent(colorspace, "lin_rec709_scene"))
442-
m_transfer = dpx::kLinear;
443-
else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene"))
439+
const float gamma = get_colorspace_rec709_gamma(spec_s);
440+
if (is_colorspace_interop_srgb(spec_s, false))
444441
m_transfer = dpx::kITUR709;
445-
else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")
446-
|| colorconfig.equivalent(colorspace, "g24_rec709_scene")
447-
|| colorconfig.equivalent(colorspace, "g18_rec709_scene")
448-
|| Strutil::istarts_with(colorspace, "Gamma"))
442+
else if (gamma == 1.0f)
443+
m_transfer = dpx::kLinear;
444+
else if (gamma != 0.0f)
449445
m_transfer = dpx::kUserDefined;
450-
else if (colorconfig.equivalent(colorspace, "KodakLog"))
446+
else if (spec_s.get_string_attribute("oiio:ColorSpace") == "KodakLog")
451447
m_transfer = dpx::kLogarithmic;
452448
else {
453449
std::string dpxtransfer = spec_s.get_string_attribute("dpx:Transfer",

src/include/OpenImageIO/color.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,22 @@ class OIIO_API ColorConfig {
440440
/// @version 3.0
441441
void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const;
442442

443+
// If the spec's metadata specifies a color space with Rec709 primaries and
444+
// gamma transfer function, return the gamma value. If not, return zero.
445+
//
446+
// @version 3.1
447+
float get_colorspace_rec709_gamma(const ImageSpec& spec) const;
448+
449+
/// Returns true if for the purpose of interop, the spec's metadata
450+
/// specifies a color space that should be encoded as sRGB.
451+
///
452+
/// If default_to_srgb is true, the colorspace will be assumed to
453+
/// be sRGB if no colorspace was specified in the spec.
454+
///
455+
/// @version 3.1
456+
bool is_colorspace_interop_srgb(const ImageSpec& spec,
457+
bool default_to_srgb = true) const;
458+
443459
/// Return if OpenImageIO was built with OCIO support
444460
static bool supportsOpenColorIO();
445461

src/include/OpenImageIO/imageio.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4198,6 +4198,21 @@ OIIO_API void set_colorspace(ImageSpec& spec, string_view name);
41984198
/// @version 3.0
41994199
OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma);
42004200

4201+
/// If the spec's metadata specifies a color space with Rec709 primaries and
4202+
/// gamma transfer function, return the gamma value. If not, return zero.
4203+
///
4204+
/// @version 3.1
4205+
OIIO_API float get_colorspace_rec709_gamma(const ImageSpec& spec);
4206+
4207+
/// Returns true if for the purpose of interop, the spec's metadata
4208+
/// specifies a color space that should be encoded as sRGB.
4209+
///
4210+
/// If default_to_srgb is true, the colorspace will be assumed to
4211+
/// be sRGB if no colorspace was specified in the spec.
4212+
///
4213+
/// @version 3.1
4214+
OIIO_API bool is_colorspace_interop_srgb (const ImageSpec& spec,
4215+
bool default_to_srgb = true);
42014216

42024217
/// Are the two named color spaces equivalent, based on the default color
42034218
/// config in effect?

src/iv/imageviewer.cpp

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,6 @@
4848
#include "ivutils.h"
4949

5050

51-
namespace {
52-
53-
inline bool
54-
IsSpecSrgb(const ImageSpec& spec)
55-
{
56-
return equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"),
57-
"srgb_rec709_scene");
58-
}
59-
60-
} // namespace
61-
62-
6351
// clang-format off
6452
static const char *s_file_filters = ""
6553
"Image Files (*.bmp *.cin *.dcm *.dds *.dpx *.fits *.gif *.hdr *.ico *.iff "
@@ -1242,7 +1230,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel)
12421230
//std::cerr << "Loading HALF-FLOAT as FLOAT\n";
12431231
read_format = TypeDesc::FLOAT;
12441232
}
1245-
if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable()) {
1233+
if (is_colorspace_interop_srgb(image_spec)
1234+
&& !glwin->is_srgb_capable()) {
12461235
// If the image is in sRGB, but OpenGL can't load sRGB textures then
12471236
// we'll need to do the transformation on the CPU after loading the
12481237
// image. We (so far) can only do this with UINT8 images, so make
@@ -1257,7 +1246,8 @@ ImageViewer::loadCurrentImage(int subimage, int miplevel)
12571246
read_format = TypeDesc::UINT8;
12581247
allow_transforms = true;
12591248

1260-
if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable())
1249+
if (is_colorspace_interop_srgb(image_spec)
1250+
&& !glwin->is_srgb_capable())
12611251
srgb_transform = true;
12621252
}
12631253

@@ -1443,7 +1433,7 @@ ImageViewer::exposureMinusOneTenthStop()
14431433
img->exposure(img->exposure() - 0.1);
14441434
if (!glwin->is_glsl_capable()) {
14451435
bool srgb_transform = (!glwin->is_srgb_capable()
1446-
&& IsSpecSrgb(img->spec()));
1436+
&& is_colorspace_interop_srgb(img->spec()));
14471437
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14481438
current_channel());
14491439
displayCurrentImage();
@@ -1462,7 +1452,7 @@ ImageViewer::exposureMinusOneHalfStop()
14621452
img->exposure(img->exposure() - 0.5);
14631453
if (!glwin->is_glsl_capable()) {
14641454
bool srgb_transform = (!glwin->is_srgb_capable()
1465-
&& IsSpecSrgb(img->spec()));
1455+
&& is_colorspace_interop_srgb(img->spec()));
14661456
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14671457
current_channel());
14681458
displayCurrentImage();
@@ -1481,7 +1471,7 @@ ImageViewer::exposurePlusOneTenthStop()
14811471
img->exposure(img->exposure() + 0.1);
14821472
if (!glwin->is_glsl_capable()) {
14831473
bool srgb_transform = (!glwin->is_srgb_capable()
1484-
&& IsSpecSrgb(img->spec()));
1474+
&& is_colorspace_interop_srgb(img->spec()));
14851475
img->pixel_transform(srgb_transform, (int)current_color_mode(),
14861476
current_channel());
14871477
displayCurrentImage();
@@ -1500,7 +1490,7 @@ ImageViewer::exposurePlusOneHalfStop()
15001490
img->exposure(img->exposure() + 0.5);
15011491
if (!glwin->is_glsl_capable()) {
15021492
bool srgb_transform = (!glwin->is_srgb_capable()
1503-
&& IsSpecSrgb(img->spec()));
1493+
&& is_colorspace_interop_srgb(img->spec()));
15041494
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15051495
current_channel());
15061496
displayCurrentImage();
@@ -1520,7 +1510,7 @@ ImageViewer::gammaMinus()
15201510
img->gamma(img->gamma() - 0.05);
15211511
if (!glwin->is_glsl_capable()) {
15221512
bool srgb_transform = (!glwin->is_srgb_capable()
1523-
&& IsSpecSrgb(img->spec()));
1513+
&& is_colorspace_interop_srgb(img->spec()));
15241514
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15251515
current_channel());
15261516
displayCurrentImage();
@@ -1539,7 +1529,7 @@ ImageViewer::gammaPlus()
15391529
img->gamma(img->gamma() + 0.05);
15401530
if (!glwin->is_glsl_capable()) {
15411531
bool srgb_transform = (!glwin->is_srgb_capable()
1542-
&& IsSpecSrgb(img->spec()));
1532+
&& is_colorspace_interop_srgb(img->spec()));
15431533
img->pixel_transform(srgb_transform, (int)current_color_mode(),
15441534
current_channel());
15451535
displayCurrentImage();
@@ -1571,8 +1561,9 @@ ImageViewer::viewChannel(int c, COLOR_MODE colormode)
15711561
if (!glwin->is_glsl_capable()) {
15721562
IvImage* img = cur();
15731563
if (img) {
1574-
bool srgb_transform = (!glwin->is_srgb_capable()
1575-
&& IsSpecSrgb(img->spec()));
1564+
bool srgb_transform
1565+
= (!glwin->is_srgb_capable()
1566+
&& is_colorspace_interop_srgb(img->spec()));
15761567
img->pixel_transform(srgb_transform, (int)colormode, c);
15771568
}
15781569
} else {

src/iv/ivgl.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2198,9 +2198,7 @@ IvGL::typespec_to_opengl(const ImageSpec& spec, int nchannels, GLenum& gltype,
21982198
break;
21992199
}
22002200

2201-
bool issrgb
2202-
= equivalent_colorspace(spec.get_string_attribute("oiio:ColorSpace"),
2203-
"srgb_rec709_scene");
2201+
bool issrgb = is_colorspace_interop_srgb(spec);
22042202

22052203
glinternalformat = nchannels;
22062204
if (nchannels == 1) {

src/jpeg.imageio/jpegoutput.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,7 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec,
268268
}
269269
}
270270

271-
if (equivalent_colorspace(m_spec.get_string_attribute("oiio:ColorSpace"),
272-
"srgb_rec709_scene"))
271+
if (is_colorspace_interop_srgb(m_spec, false))
273272
m_spec.attribute("Exif:ColorSpace", 1);
274273

275274
// Write EXIF info

src/libOpenImageIO/color_ocio.cpp

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,23 @@ equivalent_colorspace(string_view a, string_view b)
14201420

14211421

14221422

1423+
bool
1424+
ColorConfig::is_colorspace_interop_srgb(const ImageSpec& spec,
1425+
bool default_to_srgb) const
1426+
{
1427+
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace");
1428+
if (default_to_srgb && colorspace.empty()) {
1429+
return true;
1430+
}
1431+
1432+
// See the interop table above for why g22_rec709_display is treated as sRGB
1433+
string_view interop_id = get_color_interop_id(colorspace);
1434+
return (interop_id == "srgb_rec709_scene"
1435+
|| interop_id == "srgb_rec709_display"
1436+
|| interop_id == "g22_rec709_display");
1437+
}
1438+
1439+
14231440
inline OCIO::BitDepth
14241441
ocio_bitdepth(TypeDesc type)
14251442
{
@@ -2099,11 +2116,13 @@ constexpr ColorInteropID color_interop_ids[] = {
20992116
CICPMatrix::Rec2020_NCL },
21002117
{ "hlg_rec2020_display", CICPPrimaries::Rec2020, CICPTransfer::HLG,
21012118
CICPMatrix::Rec2020_NCL },
2102-
// No CICP mapping to keep previous behavior unchanged, as Gamma 2.2
2103-
// display is more likely meant to be written as sRGB. On read the
2104-
// scene referred interop ID will be used.
2105-
{ "g22_rec709_display",
2106-
/* CICPPrimaries::Rec709, CICPTransfer::Gamma22, CICPMatrix::BT709 */ },
2119+
// Mapped to sRGB as a gamma 2.2 display is more likely meant to be written
2120+
// as sRGB. This type of display is often used to correct for the discrepancy
2121+
// where images are encoded as sRGB but usually decoded as gamma 2.2 by the
2122+
// physical display.
2123+
// For read and write, g22_rec709_scene. still maps to Gamma 2.2.
2124+
{ "g22_rec709_display", CICPPrimaries::Rec709, CICPTransfer::sRGB,
2125+
CICPMatrix::BT709 },
21072126
// No CICP code for Adobe RGB primaries.
21082127
{ "g22_adobergb_display" },
21092128
{ "g26_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::Gamma26,
@@ -2823,6 +2842,44 @@ ColorConfig::set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const
28232842
}
28242843

28252844

2845+
float
2846+
ColorConfig::get_colorspace_rec709_gamma(const ImageSpec& spec) const
2847+
{
2848+
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace");
2849+
string_view interop_id = get_color_interop_id(colorspace);
2850+
2851+
// scene_linear is not guaranteed to be Rec709, here for back compatibility
2852+
if (equivalent(colorspace, "linear")
2853+
|| equivalent(colorspace, "scene_linear")
2854+
|| interop_id == "lin_rec709_scene") {
2855+
return 1.0f;
2856+
}
2857+
// See the interop table above for why g22_rec709_display is not treated as gamma
2858+
else if (interop_id == "g22_rec709_scene") {
2859+
return 2.2f;
2860+
}
2861+
// Note g24_rec709_scene is not a standard interop
2862+
else if (equivalent(colorspace, "g24_rec709_scene")
2863+
|| interop_id == "g24_rec709_display") {
2864+
return 2.4f;
2865+
}
2866+
// Note g18_rec709_display is not an interop ID
2867+
else if (interop_id == "g18_rec709_scene") {
2868+
return 1.8f;
2869+
}
2870+
// Back compatible, this is DEPRECATED(3.1)
2871+
else if (Strutil::istarts_with(colorspace, "Gamma")) {
2872+
Strutil::parse_word(colorspace);
2873+
float g = Strutil::from_string<float>(colorspace);
2874+
if (g >= 0.01f && g <= 10.0f /* sanity check */)
2875+
return g;
2876+
}
2877+
2878+
// Obsolete "oiio:Gamma" attrib for back compatibility
2879+
return spec.get_float_attribute("oiio:Gamma", 0.0f);
2880+
}
2881+
2882+
28262883
void
28272884
set_colorspace(ImageSpec& spec, string_view colorspace)
28282885
{
@@ -2835,5 +2892,17 @@ set_colorspace_rec709_gamma(ImageSpec& spec, float gamma)
28352892
ColorConfig::default_colorconfig().set_colorspace_rec709_gamma(spec, gamma);
28362893
}
28372894

2895+
float
2896+
get_colorspace_rec709_gamma(const ImageSpec& spec)
2897+
{
2898+
return ColorConfig::default_colorconfig().get_colorspace_rec709_gamma(spec);
2899+
}
2900+
2901+
bool
2902+
is_colorspace_interop_srgb(const ImageSpec& spec, bool default_to_srgb)
2903+
{
2904+
return ColorConfig::default_colorconfig().is_colorspace_interop_srgb(
2905+
spec, default_to_srgb);
2906+
}
28382907

28392908
OIIO_NAMESPACE_END

src/png.imageio/png_pvt.h

Lines changed: 18 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -609,63 +609,24 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
609609
convert_alpha = spec.alpha_channel != -1
610610
&& !spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
611611

612-
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace",
613-
"srgb_rec709_scene");
614-
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
615612
OIIO_MAYBE_UNUSED bool wrote_colorspace = false;
616613
srgb = false;
617-
if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) {
618-
srgb = true;
614+
if (is_colorspace_interop_srgb(spec)) {
619615
gamma = 1.0f;
620-
} else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) {
621-
gamma = 2.2f;
622-
} else if (colorconfig.equivalent(colorspace, "g24_rec709_scene")) {
623-
gamma = 2.4f;
624-
} else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) {
625-
gamma = 1.8f;
626-
} else {
627-
gamma = spec.get_float_attribute("oiio:Gamma", 1.0f);
628-
// obsolete "oiio:Gamma" attrib for back compatibility
629-
}
630-
631-
if (colorconfig.equivalent(colorspace, "scene_linear")
632-
|| colorconfig.equivalent(colorspace, "lin_rec709_scene")) {
633-
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
634-
return "Could not set PNG gAMA chunk";
635-
png_set_gAMA(sp, ip, 1.0);
636-
srgb = false;
637-
wrote_colorspace = true;
638-
} else if (Strutil::istarts_with(colorspace, "Gamma")) {
639-
// Back compatible, this is DEPRECATED(3.1)
640-
Strutil::parse_word(colorspace);
641-
float g = Strutil::from_string<float>(colorspace);
642-
if (g >= 0.01f && g <= 10.0f /* sanity check */)
643-
gamma = g;
644-
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
645-
return "Could not set PNG gAMA chunk";
646-
png_set_gAMA(sp, ip, 1.0f / gamma);
647-
srgb = false;
648-
wrote_colorspace = true;
649-
} else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) {
650-
gamma = 2.2f;
651-
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
652-
return "Could not set PNG gAMA chunk";
653-
png_set_gAMA(sp, ip, 1.0f / gamma);
654-
srgb = false;
655-
wrote_colorspace = true;
656-
} else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) {
657-
gamma = 1.8f;
658-
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
659-
return "Could not set PNG gAMA chunk";
660-
png_set_gAMA(sp, ip, 1.0f / gamma);
661-
srgb = false;
662-
wrote_colorspace = true;
663-
} else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) {
616+
srgb = true;
664617
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
665618
return "Could not set PNG gAMA and cHRM chunk";
666619
png_set_sRGB_gAMA_and_cHRM(sp, ip, PNG_sRGB_INTENT_ABSOLUTE);
667-
srgb = true;
668620
wrote_colorspace = true;
621+
} else {
622+
gamma = get_colorspace_rec709_gamma(spec);
623+
if (gamma != 0.0f) {
624+
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
625+
return "Could not set PNG gAMA chunk";
626+
png_set_gAMA(sp, ip, 1.0 / gamma);
627+
srgb = false;
628+
wrote_colorspace = true;
629+
}
669630
}
670631

671632
// Write ICC profile, if we have anything
@@ -738,11 +699,13 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
738699
#ifdef PNG_cICP_SUPPORTED
739700
// Only automatically determine CICP from oiio::ColorSpace if we didn't
740701
// write colorspace metadata yet.
741-
const ParamValue* p = spec.find_attribute(CICP_ATTR,
742-
TypeDesc(TypeDesc::INT, 4));
743-
cspan<int> cicp = (p) ? p->as_cspan<int>()
744-
: (!wrote_colorspace) ? colorconfig.get_cicp(colorspace)
745-
: cspan<int>();
702+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
703+
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace");
704+
const ParamValue* p = spec.find_attribute(CICP_ATTR,
705+
TypeDesc(TypeDesc::INT, 4));
706+
cspan<int> cicp = (p) ? p->as_cspan<int>()
707+
: (!wrote_colorspace) ? colorconfig.get_cicp(colorspace)
708+
: cspan<int>();
746709
if (!cicp.empty()) {
747710
png_byte vals[4];
748711
for (int i = 0; i < 4; ++i)

0 commit comments

Comments
 (0)