Skip to content

Commit dfe9ccd

Browse files
committed
feat: Auto convert between oiio:ColorSpace and CICP attributes in I/O
When reading image files with CICP metadata, automatically set the corresponding "oiio:ColorSpace". When writing files that support CICP and no other colorspace metadata can represent "oiio:ColorSpace", automatically write CICP metadata. Setting "oiio:ColorSpace" on read prefers scene referred over display referred color spaces, changing existing behavior as little as possible. The alternative would have been to interpret the presence of CICP metadata as an indication that the image is likely display referred, which might be reasonable too. Also add new ColorConfig set_colorspace_cicp and get_colorspace_cicp API functions to share logic between file formats.
1 parent 5f72880 commit dfe9ccd

File tree

14 files changed

+253
-26
lines changed

14 files changed

+253
-26
lines changed

src/ffmpeg.imageio/ffmpeginput.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ receive_frame(AVCodecContext* avctx, AVFrame* picture, AVPacket* avpkt)
7171

7272

7373

74+
#include <OpenImageIO/color.h>
7475
#include <OpenImageIO/imageio.h>
7576
#include <iostream>
7677
#include <mutex>
@@ -548,7 +549,8 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec)
548549
= { m_codec_context->color_primaries, m_codec_context->color_trc,
549550
m_codec_context->colorspace,
550551
m_codec_context->color_range == AVCOL_RANGE_MPEG ? 0 : 1 };
551-
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
552+
ColorConfig::default_colorconfig().set_colorspace_cicp(m_spec, cicp);
553+
552554
m_nsubimages = m_frames;
553555
spec = m_spec;
554556
m_filename = name;

src/heif.imageio/heifinput.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// https://github.com/AcademySoftwareFoundation/OpenImageIO
44

5+
#include <OpenImageIO/color.h>
56
#include <OpenImageIO/filesystem.h>
67
#include <OpenImageIO/fmath.h>
78
#include <OpenImageIO/imageio.h>
@@ -291,7 +292,9 @@ HeifInput::seek_subimage(int subimage, int miplevel)
291292
int(nclx->transfer_characteristics),
292293
int(nclx->matrix_coefficients),
293294
int(nclx->full_range_flag ? 1 : 0) };
294-
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
295+
const ColorConfig& colorconfig(
296+
ColorConfig::default_colorconfig());
297+
colorconfig.set_colorspace_cicp(m_spec, cicp);
295298
}
296299
heif_nclx_color_profile_free(nclx);
297300
}

src/heif.imageio/heifoutput.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// https://github.com/AcademySoftwareFoundation/OpenImageIO
44

55

6+
#include <OpenImageIO/color.h>
67
#include <OpenImageIO/filesystem.h>
78
#include <OpenImageIO/fmath.h>
89
#include <OpenImageIO/imageio.h>
@@ -249,10 +250,10 @@ HeifOutput::close()
249250
std::unique_ptr<heif_color_profile_nclx,
250251
void (*)(heif_color_profile_nclx*)>
251252
nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free);
252-
const ParamValue* p = m_spec.find_attribute("CICP",
253-
TypeDesc(TypeDesc::INT, 4));
254-
if (p) {
255-
const int* cicp = static_cast<const int*>(p->data());
253+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
254+
const bool auto_cicp = true;
255+
int cicp[4];
256+
if (colorconfig.get_colorspace_cicp(m_spec, auto_cicp, cicp)) {
256257
nclx->color_primaries = heif_color_primaries(cicp[0]);
257258
nclx->transfer_characteristics = heif_transfer_characteristics(
258259
cicp[1]);

src/include/OpenImageIO/color.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,23 @@ class OIIO_API ColorConfig {
422422
/// @version 3.0
423423
void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const;
424424

425+
/// Set the "CICP" attribute in the spec. If a corresponding colorspace
426+
/// is found, automatically set "oiio:ColorSpace" as well. It also removes
427+
/// or alters several other attributes that may hint color space in ways
428+
/// that might be contradictory or no longer true.
429+
///
430+
/// @version 3.1
431+
void set_colorspace_cicp(ImageSpec& spec, const int cicp[4]) const;
432+
433+
/// Get the CICP code corresponding from the "CICP" attribute. If there
434+
/// is not such attribute and auto_cicp is true, atempt to determine a CICP
435+
/// code from the "oiio:ColorSpace" attribute.
436+
/// Returns false if no CICP code could be determined.
437+
///
438+
/// @version 3.1
439+
bool get_colorspace_cicp(ImageSpec& spec, bool auto_colorspace,
440+
int cicp[4]) const;
441+
425442
/// Return if OpenImageIO was built with OCIO support
426443
static bool supportsOpenColorIO();
427444

src/libOpenImageIO/color_ocio.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2655,6 +2655,140 @@ ColorConfig::set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const
26552655
}
26562656
}
26572657

2658+
namespace {
2659+
// Primaries
2660+
static const int cicp_primaries_rec709 = 1;
2661+
static const int cicp_primaries_rec2020 = 9;
2662+
static const int cicp_primaries_xyzd65 = 10;
2663+
static const int cicp_primaries_p3d65 = 12;
2664+
// Transfer functions
2665+
static const int cicp_transfer_bt709 = 1;
2666+
static const int cicp_transfer_g22 = 4;
2667+
static const int cicp_transfer_linear = 8;
2668+
static const int cicp_transfer_srgb = 13;
2669+
static const int cicp_transfer_pq = 16;
2670+
static const int cicp_transfer_g26 = 17;
2671+
static const int cicp_transfer_hlg = 18;
2672+
// Matrix
2673+
static const int cicp_matrix_bt709 = 1;
2674+
static const int cicp_matrix_unspecified = 2;
2675+
static const int cicp_matrix_rec2020_ncl = 9;
2676+
static const int cicp_matrix_rec2020_cl = 10;
2677+
// Range
2678+
static const int cicp_range_full = 1;
2679+
2680+
// Mapping between color interop ID and CICP, based on Color Interop Forum
2681+
// recommendations.
2682+
struct ColorSpaceCICP {
2683+
const char* interop_id;
2684+
int cicp[4];
2685+
};
2686+
2687+
static const ColorSpaceCICP color_space_cicp[] = {
2688+
// Scene referred interop IDs first so they are the default in automatic
2689+
// conversion from CICP to interop ID.
2690+
{ "srgb_rec709_scene",
2691+
{ cicp_primaries_rec709, cicp_transfer_srgb, cicp_matrix_bt709,
2692+
cicp_range_full } },
2693+
{ "srgb_rec709_scene",
2694+
{ cicp_primaries_rec709, cicp_transfer_srgb, cicp_matrix_bt709,
2695+
cicp_range_full } },
2696+
{ "srgb_p3d65_scene",
2697+
{ cicp_primaries_p3d65, cicp_transfer_srgb, cicp_matrix_bt709,
2698+
cicp_range_full } },
2699+
// These are not display color spaces at all, but can be represented by CICP.
2700+
{ "lin_rec709_scene",
2701+
{ cicp_primaries_rec709, cicp_transfer_linear, cicp_matrix_bt709,
2702+
cicp_range_full } },
2703+
{ "lin_p3d65_scene",
2704+
{ cicp_primaries_p3d65, cicp_transfer_linear, cicp_matrix_bt709,
2705+
cicp_range_full } },
2706+
{ "lin_rec2020_scene",
2707+
{ cicp_primaries_rec2020, cicp_transfer_linear, cicp_matrix_rec2020_cl,
2708+
cicp_range_full } },
2709+
{ "lin_ciexyzd65_scene",
2710+
{ cicp_primaries_xyzd65, cicp_transfer_linear, cicp_matrix_unspecified,
2711+
cicp_range_full } },
2712+
2713+
// Display referred interop IDs.
2714+
{ "srgb_rec709_display",
2715+
{ cicp_primaries_rec709, cicp_transfer_srgb, cicp_matrix_bt709,
2716+
cicp_range_full } },
2717+
{ "g24_rec709_display",
2718+
{ cicp_primaries_rec709, cicp_transfer_bt709, cicp_matrix_bt709,
2719+
cicp_range_full } },
2720+
{ "srgb_p3d65_display",
2721+
{ cicp_primaries_p3d65, cicp_transfer_srgb, cicp_matrix_bt709,
2722+
cicp_range_full } },
2723+
{ "srgbe_p3d65_display",
2724+
{ cicp_primaries_p3d65, cicp_transfer_srgb, cicp_matrix_bt709,
2725+
cicp_range_full } },
2726+
{ "pq_p3d65_display",
2727+
{ cicp_primaries_p3d65, cicp_transfer_pq, cicp_matrix_rec2020_ncl,
2728+
cicp_range_full } },
2729+
{ "pq_rec2020_display",
2730+
{ cicp_primaries_rec2020, cicp_transfer_pq, cicp_matrix_rec2020_ncl,
2731+
cicp_range_full } },
2732+
{ "hlg_rec2020_display",
2733+
{ cicp_primaries_rec2020, cicp_transfer_hlg, cicp_matrix_rec2020_ncl,
2734+
cicp_range_full } },
2735+
{ "g22_rec709_display",
2736+
{ cicp_primaries_rec709, cicp_transfer_g22, cicp_matrix_bt709,
2737+
cicp_range_full } },
2738+
// No CICP code for Adobe RGB primaries.
2739+
// { "g22_adobergb_display" }
2740+
{ "g26_p3d65_display",
2741+
{ cicp_primaries_p3d65, cicp_transfer_g26, cicp_matrix_bt709,
2742+
cicp_range_full } },
2743+
{ "g26_xyzd65_display",
2744+
{ cicp_primaries_xyzd65, cicp_transfer_g26, cicp_matrix_unspecified,
2745+
cicp_range_full } },
2746+
{ "pq_xyzd65_display",
2747+
{ cicp_primaries_xyzd65, cicp_transfer_pq, cicp_matrix_unspecified,
2748+
cicp_range_full } },
2749+
};
2750+
} // namespace
2751+
2752+
void
2753+
ColorConfig::set_colorspace_cicp(ImageSpec& spec, const int cicp[4]) const
2754+
{
2755+
spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
2756+
2757+
for (const ColorSpaceCICP& space : color_space_cicp) {
2758+
if (space.cicp[0] == cicp[0] && space.cicp[1] == cicp[1]) {
2759+
set_colorspace(spec, space.interop_id);
2760+
return;
2761+
}
2762+
}
2763+
}
2764+
2765+
bool
2766+
ColorConfig::get_colorspace_cicp(ImageSpec& spec, bool auto_cicp,
2767+
int cicp[4]) const
2768+
{
2769+
const ParamValue* p = spec.find_attribute("CICP",
2770+
TypeDesc(TypeDesc::INT, 4));
2771+
if (p) {
2772+
std::copy_n(static_cast<const int*>(p->data()), 4, cicp);
2773+
return true;
2774+
}
2775+
2776+
if (!auto_cicp) {
2777+
return false;
2778+
}
2779+
2780+
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace");
2781+
if (!colorspace.empty()) {
2782+
for (const ColorSpaceCICP& space : color_space_cicp) {
2783+
if (equivalent(colorspace, space.interop_id)) {
2784+
std::copy_n(space.cicp, 4, cicp);
2785+
return true;
2786+
}
2787+
}
2788+
}
2789+
2790+
return false;
2791+
}
26582792

26592793
void
26602794
set_colorspace(ImageSpec& spec, string_view colorspace)

src/png.imageio/png_pvt.h

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ For further information see the following mailing list threads:
4040
OIIO_PLUGIN_NAMESPACE_BEGIN
4141

4242
#define ICC_PROFILE_ATTR "ICCProfile"
43-
#define CICP_ATTR "CICP"
4443

4544
namespace PNG_pvt {
4645

@@ -330,8 +329,9 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type,
330329
{
331330
png_byte pri = 0, trc = 0, mtx = 0, vfr = 0;
332331
if (png_get_cICP(sp, ip, &pri, &trc, &mtx, &vfr)) {
333-
int cicp[4] = { pri, trc, mtx, vfr };
334-
spec.attribute(CICP_ATTR, TypeDesc(TypeDesc::INT, 4), cicp);
332+
const int cicp[4] = { pri, trc, mtx, vfr };
333+
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
334+
colorconfig.set_colorspace_cicp(spec, cicp);
335335
}
336336
}
337337
#endif
@@ -608,7 +608,8 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
608608
string_view colorspace = spec.get_string_attribute("oiio:ColorSpace",
609609
"srgb_rec709_scene");
610610
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
611-
srgb = false;
611+
bool wrote_colorspace = false;
612+
srgb = false;
612613
if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) {
613614
srgb = true;
614615
gamma = 1.0f;
@@ -628,7 +629,8 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
628629
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
629630
return "Could not set PNG gAMA chunk";
630631
png_set_gAMA(sp, ip, 1.0);
631-
srgb = false;
632+
srgb = false;
633+
wrote_colorspace = true;
632634
} else if (Strutil::istarts_with(colorspace, "Gamma")) {
633635
// Back compatible, this is DEPRECATED(3.1)
634636
Strutil::parse_word(colorspace);
@@ -638,24 +640,28 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
638640
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
639641
return "Could not set PNG gAMA chunk";
640642
png_set_gAMA(sp, ip, 1.0f / gamma);
641-
srgb = false;
643+
srgb = false;
644+
wrote_colorspace = true;
642645
} else if (colorconfig.equivalent(colorspace, "g22_rec709_scene")) {
643646
gamma = 2.2f;
644647
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
645648
return "Could not set PNG gAMA chunk";
646649
png_set_gAMA(sp, ip, 1.0f / gamma);
647-
srgb = false;
650+
srgb = false;
651+
wrote_colorspace = true;
648652
} else if (colorconfig.equivalent(colorspace, "g18_rec709_scene")) {
649653
gamma = 1.8f;
650654
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
651655
return "Could not set PNG gAMA chunk";
652656
png_set_gAMA(sp, ip, 1.0f / gamma);
653-
srgb = false;
657+
srgb = false;
658+
wrote_colorspace = true;
654659
} else if (colorconfig.equivalent(colorspace, "srgb_rec709_scene")) {
655660
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
656661
return "Could not set PNG gAMA and cHRM chunk";
657662
png_set_sRGB_gAMA_and_cHRM(sp, ip, PNG_sRGB_INTENT_ABSOLUTE);
658-
srgb = true;
663+
srgb = true;
664+
wrote_colorspace = true;
659665
}
660666

661667
// Write ICC profile, if we have anything
@@ -667,8 +673,10 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
667673
return "Could not set PNG iCCP chunk";
668674
unsigned char* icc_profile
669675
= (unsigned char*)icc_profile_parameter->data();
670-
if (icc_profile && length)
676+
if (icc_profile && length) {
671677
png_set_iCCP(sp, ip, "Embedded Profile", 0, icc_profile, length);
678+
wrote_colorspace = true;
679+
}
672680
}
673681

674682
if (false && !spec.find_attribute("DateTime")) {
@@ -724,13 +732,16 @@ write_info(png_structp& sp, png_infop& ip, int& color_type, ImageSpec& spec,
724732
}
725733

726734
#ifdef PNG_cICP_SUPPORTED
727-
const ParamValue* p = spec.find_attribute(CICP_ATTR,
728-
TypeDesc(TypeDesc::INT, 4));
729-
if (p) {
730-
const int* int_vals = static_cast<const int*>(p->data());
735+
// Only automatically determine CICP from oiio::ColorSpace if we didn't
736+
// write colorspace metadata yet.
737+
const bool auto_cicp = !wrote_colorspace;
738+
int cicp[4];
739+
if (colorconfig.get_colorspace_cicp(spec, auto_cicp, cicp)) {
740+
// Matrix must be RGB according to PNG spec v3
741+
cicp[2] = 0;
731742
png_byte vals[4];
732743
for (int i = 0; i < 4; ++i)
733-
vals[i] = static_cast<png_byte>(int_vals[i]);
744+
vals[i] = static_cast<png_byte>(cicp[i]);
734745
if (setjmp(png_jmpbuf(sp))) // NOLINT(cert-err52-cpp)
735746
return "Could not set PNG cICP chunk";
736747
// libpng will only write the chunk if the third byte is 0

testsuite/ffmpeg/ref/out-ffmpeg6.1.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
9696
FramesPerSecond: 24/1 (24)
9797
ffmpeg:codec_name: "Google VP9"
9898
oiio:BitsPerSample: 10
99+
oiio:ColorSpace: "pq_rec2020_display"
99100
oiio:Movie: 1
100101
oiio:subimages: 2
101102
subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie
@@ -106,5 +107,6 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
106107
FramesPerSecond: 24/1 (24)
107108
ffmpeg:codec_name: "Google VP9"
108109
oiio:BitsPerSample: 10
110+
oiio:ColorSpace: "pq_rec2020_display"
109111
oiio:Movie: 1
110112
oiio:subimages: 2

testsuite/ffmpeg/ref/out-ffmpeg8.0.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
9696
FramesPerSecond: 24/1 (24)
9797
ffmpeg:codec_name: "Google VP9"
9898
oiio:BitsPerSample: 10
99+
oiio:ColorSpace: "pq_rec2020_display"
99100
oiio:Movie: 1
100101
oiio:subimages: 2
101102
subimage 1: 384 x 216, 3 channel, uint10 FFmpeg movie
@@ -106,5 +107,6 @@ ref/vp9_rec2100_pq.mkv : 384 x 216, 3 channel, uint10 FFmpeg movie
106107
FramesPerSecond: 24/1 (24)
107108
ffmpeg:codec_name: "Google VP9"
108109
oiio:BitsPerSample: 10
110+
oiio:ColorSpace: "pq_rec2020_display"
109111
oiio:Movie: 1
110112
oiio:subimages: 2

testsuite/heif/ref/out-libheif1.12-orient.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
6464
Exif:FlashPixVersion: "0100"
6565
heif:UnassociatedAlpha: 1
6666
oiio:BitsPerSample: 10
67-
oiio:ColorSpace: "srgb_rec709_scene"
67+
oiio:ColorSpace: "pq_rec2020_display"
68+
Reading colorspace_hlg.avif
69+
colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif
70+
SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540
71+
channel list: R, G, B, A
72+
CICP: 9, 18, 9, 1
73+
Exif:ExifVersion: "0230"
74+
Exif:FlashPixVersion: "0100"
75+
heif:UnassociatedAlpha: 1
76+
oiio:BitsPerSample: 10
77+
oiio:ColorSpace: "hlg_rec2020_display"
6878
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
6979
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
7080
SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E

testsuite/heif/ref/out-libheif1.4.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,17 @@ cicp_pq.avif : 16 x 16, 4 channel, uint10 heif
6464
Exif:FlashPixVersion: "0100"
6565
heif:UnassociatedAlpha: 1
6666
oiio:BitsPerSample: 10
67-
oiio:ColorSpace: "srgb_rec709_scene"
67+
oiio:ColorSpace: "pq_rec2020_display"
68+
Reading colorspace_hlg.avif
69+
colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif
70+
SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540
71+
channel list: R, G, B, A
72+
CICP: 9, 18, 9, 1
73+
Exif:ExifVersion: "0230"
74+
Exif:FlashPixVersion: "0100"
75+
heif:UnassociatedAlpha: 1
76+
oiio:BitsPerSample: 10
77+
oiio:ColorSpace: "hlg_rec2020_display"
6878
Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic
6979
../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif
7080
SHA-1: 8211F56BBABDC7615CCAF67CBF49741D1A292D2E

0 commit comments

Comments
 (0)