Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3888,6 +3888,73 @@ sections) work with deep inputs::

|

.. _sec-pythoncolorconfig:


ColorConfig
===========

The `ColorConfig` class that represents the set of color transformations that
are allowed.

If OpenColorIO is enabled at build time, this configuration is loaded at
runtime, allowing the user to have complete control of all color transformation
math. See the
`OpenColorIO documentation <https://opencolorio.readthedocs.io>`_ for details.

If OpenColorIO is not enabled at build time, a generic color configuration
is provided for minimal color support.

..
TODO: The documentation for this class is incomplete.

.. py:method:: get_cicp (colorspace)

Find CICP code corresponding to the colorspace.
Return a sequence of 4 ints, or None if not found.

Example:

.. code-block:: python

colorconfig = oiio.ColorConfig()
cicp = colorconfig.get_cicp("pq_rec2020_display")
if cicp:
primaries, transfer, matrix, color_range = cicp

This function was added in OpenImageIO 3.1.


.. py:method:: get_color_interop_id (colorspace)

Find color interop ID for the given colorspace.
Returns empty string if not found.

Example:

.. code-block:: python

colorconfig = oiio.ColorConfig()
interop_id = colorconfig.get_color_interop_id("Rec.2100-PQ - Display")

This function was added in OpenImageIO 3.1.


.. py:method:: get_color_interop_id (cicp)

Find color interop ID corresponding to the CICP code.
Returns empty string if not found.

Example:

.. code-block:: python

colorconfig = oiio.ColorConfig()
interop_id = colorconfig.get_color_interop_id([9, 16, 9, 1])

This function was added in OpenImageIO 3.1.


.. _sec-pythonmiscapi:

Miscellaneous Utilities
Expand Down
6 changes: 6 additions & 0 deletions src/ffmpeg.imageio/ffmpeginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ receive_frame(AVCodecContext* avctx, AVFrame* picture, AVPacket* avpkt)



#include <OpenImageIO/color.h>
#include <OpenImageIO/imageio.h>
#include <iostream>
#include <mutex>
Expand Down Expand Up @@ -549,6 +550,11 @@ FFmpegInput::open(const std::string& name, ImageSpec& spec)
m_codec_context->colorspace,
m_codec_context->color_range == AVCOL_RANGE_MPEG ? 0 : 1 };
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
string_view interop_id = colorconfig.get_color_interop_id(cicp);
if (!interop_id.empty())
m_spec.attribute("oiio:ColorSpace", interop_id);

m_nsubimages = m_frames;
spec = m_spec;
m_filename = name;
Expand Down
6 changes: 6 additions & 0 deletions src/heif.imageio/heifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/AcademySoftwareFoundation/OpenImageIO

#include <OpenImageIO/color.h>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/fmath.h>
#include <OpenImageIO/imageio.h>
Expand Down Expand Up @@ -292,6 +293,11 @@ HeifInput::seek_subimage(int subimage, int miplevel)
int(nclx->matrix_coefficients),
int(nclx->full_range_flag ? 1 : 0) };
m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp);
const ColorConfig& colorconfig(
ColorConfig::default_colorconfig());
string_view interop_id = colorconfig.get_color_interop_id(cicp);
if (!interop_id.empty())
m_spec.attribute("oiio:ColorSpace", interop_id);
}
heif_nclx_color_profile_free(nclx);
}
Expand Down
12 changes: 8 additions & 4 deletions src/heif.imageio/heifoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://github.com/AcademySoftwareFoundation/OpenImageIO


#include <OpenImageIO/color.h>
#include <OpenImageIO/filesystem.h>
#include <OpenImageIO/fmath.h>
#include <OpenImageIO/imageio.h>
Expand Down Expand Up @@ -249,10 +250,13 @@ HeifOutput::close()
std::unique_ptr<heif_color_profile_nclx,
void (*)(heif_color_profile_nclx*)>
nclx(heif_nclx_color_profile_alloc(), heif_nclx_color_profile_free);
const ParamValue* p = m_spec.find_attribute("CICP",
TypeDesc(TypeDesc::INT, 4));
if (p) {
const int* cicp = static_cast<const int*>(p->data());
const ColorConfig& colorconfig(ColorConfig::default_colorconfig());
const ParamValue* p = m_spec.find_attribute("CICP",
TypeDesc(TypeDesc::INT, 4));
string_view colorspace = m_spec.get_string_attribute("oiio:ColorSpace");
cspan<int> cicp = (p) ? p->as_cspan<int>()
: colorconfig.get_cicp(colorspace);
if (!cicp.empty()) {
nclx->color_primaries = heif_color_primaries(cicp[0]);
nclx->transfer_characteristics = heif_transfer_characteristics(
cicp[1]);
Expand Down
18 changes: 18 additions & 0 deletions src/include/OpenImageIO/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,24 @@ class OIIO_API ColorConfig {
bool equivalent(string_view color_space,
string_view other_color_space) const;

/// Find CICP code corresponding to the colorspace.
/// Return a cspan of 4 ints, or an empty span if not found.
///
/// @version 3.1
cspan<int> get_cicp(string_view colorspace) const;

/// Find color interop ID for the given colorspace.
/// Returns empty string if not found.
///
/// @version 3.1
string_view get_color_interop_id(string_view colorspace) const;

/// Find color interop ID corresponding to the CICP code.
/// Returns empty string if not found.
///
/// @version 3.1
string_view get_color_interop_id(const int cicp[4]) const;

/// Return a filename or other identifier for the config we're using.
std::string configname() const;

Expand Down
167 changes: 167 additions & 0 deletions src/libOpenImageIO/color_ocio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1997,6 +1997,173 @@ ColorConfig::parseColorSpaceFromString(string_view str) const
}


//////////////////////////////////////////////////////////////////////////
//
// Color Interop ID

namespace {
enum class CICPPrimaries : int {
Rec709 = 1,
Rec2020 = 9,
XYZD65 = 10,
P3D65 = 12,
};

enum class CICPTransfer : int {
BT709 = 1,
Gamma22 = 4,
Linear = 8,
sRGB = 13,
PQ = 16,
Gamma26 = 17,
HLG = 18,
};

enum class CICPMatrix : int {
RGB = 0,
BT709 = 1,
Unspecified = 2,
Rec2020_NCL = 9,
Rec2020_CL = 10,
};

enum class CICPRange : int {
Narrow = 0,
Full = 1,
};

struct ColorInteropID {
constexpr ColorInteropID(const char* interop_id)
: interop_id(interop_id)
, cicp({ 0, 0, 0, 0 })
, has_cicp(false)
{
}

constexpr ColorInteropID(const char* interop_id, CICPPrimaries primaries,
CICPTransfer transfer, CICPMatrix matrix)
: interop_id(interop_id)
, cicp({ int(primaries), int(transfer), int(matrix),
int(CICPRange::Full) })
, has_cicp(true)
{
}

const char* interop_id;
std::array<int, 4> cicp;
bool has_cicp;
};

// Mapping between color interop ID and CICP, based on Color Interop Forum
// recommendations.
constexpr ColorInteropID color_interop_ids[] = {
// Scene referred interop IDs first so they are the default in automatic
// conversion from CICP to interop ID. Some are not display color spaces
// at all, but can be represented by CICP anyway.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since CICP is primarily used in video files, I think it would be more appropriate to prefer the display-referred mapping. The one exception might be for CICPTransfer::Linear, but what file formats would contain that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as mentioned in the description I think that's reasonable too.

There are some potentially negative consequences:

  • OIIO currently reads sRGB images as srgb_rec709_scene. If the presence of CICP would change that to srgb_rec709_display, some applications integrating OIIO would need to be updated (again) to handle both cases.
  • If you use srgb_p3d65_scene for textures, then it would help to write CICP to make them open and display correctly in other software. But then opening them again in OIIO they would be srgb_p3d65_display.
  • Other software for painting and editing textures may write CICP (maybe more so in the future if AVIF, JPEG XL gain more adoption).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welp, you both make very good points.

I've given this a lot of thought, and I've come up with a potential way forward that I believe addresses Doug's concerns about preferring to interpret CICP values as display-referred color spaces, as well as Brecht's concerns about reading and writing metadata that OIIO can interpret reliably.

Upshot: by default, interpret CICP as display-referred; and leverage other color metadata to convey scene-referred encodings.

#4787 (comment)

This said, I think, if you guys are okay with it, for the sake of the other dependent PRs, we should leave this behavior as-is for the immediate future, and continue with implementing a more robust strategy in a forthcoming PR, after the rest of Brecht's related work has made its way in safely.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should leave this behavior as-is for the immediate future, and continue with implementing a more robust strategy in a forthcoming PR

Fine by me. We have two gates: one on individual PRs, but another on when we backport a batch of PRs to 3.1. It's ok to noodle with the specifics from PR to PR as long as we have things reasonably settled down for the backporting steps.

{ "lin_ap1_scene" },
{ "lin_ap0_scene" },
{ "lin_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Linear,
CICPMatrix::BT709 },
{ "lin_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::Linear,
CICPMatrix::BT709 },
{ "lin_rec2020_scene", CICPPrimaries::Rec2020, CICPTransfer::Linear,
CICPMatrix::Rec2020_CL },
{ "lin_adobergb_scene" },
{ "lin_ciexyzd65_scene", CICPPrimaries::XYZD65, CICPTransfer::Linear,
CICPMatrix::Unspecified },
{ "srgb_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::sRGB,
CICPMatrix::BT709 },
{ "g22_rec709_scene", CICPPrimaries::Rec709, CICPTransfer::Gamma22,
CICPMatrix::BT709 },
{ "g18_rec709_scene" },
{ "srgb_ap1_scene" },
{ "g22_ap1_scene" },
{ "srgb_p3d65_scene", CICPPrimaries::P3D65, CICPTransfer::sRGB,
CICPMatrix::BT709 },
{ "g22_adobergb_scene" },
{ "data" },
{ "unknown" },

// Display referred interop IDs.
{ "srgb_rec709_display", CICPPrimaries::Rec709, CICPTransfer::sRGB,
CICPMatrix::BT709 },
{ "g24_rec709_display", CICPPrimaries::Rec709, CICPTransfer::BT709,
CICPMatrix::BT709 },
{ "srgb_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::sRGB,
CICPMatrix::BT709 },
{ "srgbe_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::sRGB,
CICPMatrix::BT709 },
{ "pq_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::PQ,
CICPMatrix::Rec2020_NCL },
{ "pq_rec2020_display", CICPPrimaries::Rec2020, CICPTransfer::PQ,
CICPMatrix::Rec2020_NCL },
{ "hlg_rec2020_display", CICPPrimaries::Rec2020, CICPTransfer::HLG,
CICPMatrix::Rec2020_NCL },
// No CICP mapping to keep previous behavior unchanged, as Gamma 2.2
// display is more likely meant to be written as sRGB. On read the
// scene referred interop ID will be used.
{ "g22_rec709_display",
/* CICPPrimaries::Rec709, CICPTransfer::Gamma22, CICPMatrix::BT709 */ },
// No CICP code for Adobe RGB primaries.
{ "g22_adobergb_display" },
{ "g26_p3d65_display", CICPPrimaries::P3D65, CICPTransfer::Gamma26,
CICPMatrix::BT709 },
{ "g26_xyzd65_display", CICPPrimaries::XYZD65, CICPTransfer::Gamma26,
CICPMatrix::Unspecified },
{ "pq_xyzd65_display", CICPPrimaries::XYZD65, CICPTransfer::PQ,
CICPMatrix::Unspecified },
};
} // namespace

string_view
ColorConfig::get_color_interop_id(string_view colorspace) const
{
if (colorspace.empty())
return "";
#if OCIO_VERSION_HEX >= MAKE_OCIO_VERSION_HEX(2, 5, 0)
if (getImpl()->config_ && !disable_ocio) {
OCIO::ConstColorSpaceRcPtr c = getImpl()->config_->getColorSpace(
std::string(resolve(colorspace)).c_str());
const char* interop_id = (c) ? c->getInteropID() : nullptr;
if (interop_id) {
return interop_id;
}
}
#endif
for (const ColorInteropID& interop : color_interop_ids) {
if (equivalent(colorspace, interop.interop_id)) {
return interop.interop_id;
}
}
return "";
}

string_view
ColorConfig::get_color_interop_id(const int cicp[4]) const
{
for (const ColorInteropID& interop : color_interop_ids) {
if (interop.has_cicp && interop.cicp[0] == cicp[0]
&& interop.cicp[1] == cicp[1]) {
return interop.interop_id;
}
}
return "";
}

cspan<int>
ColorConfig::get_cicp(string_view colorspace) const
{
string_view interop_id = get_color_interop_id(colorspace);
if (!interop_id.empty()) {
for (const ColorInteropID& interop : color_interop_ids) {
if (interop.has_cicp && interop_id == interop.interop_id) {
return interop.cicp;
}
}
}
return cspan<int>();
}


//////////////////////////////////////////////////////////////////////////
//
Expand Down
Loading
Loading