Skip to content

Commit

Permalink
Color management nomenclature improvements (#4479)
Browse files Browse the repository at this point in the history
A round of color management improvements (I hope).

We were pretty squirrelly about what "linear" meant. Get more exact. Now
we will treat "linear" as a legacy synonym for "lin_rec709" (sRGB
primaries, but linear response). Change file format readers to say
"lin_rec709" when they mean it, and not use the more generic alias
"linear".

"scene_linear", on the other hand, is the internal working color space
for math. Which concrete color space it is an alias for is determined by
the OCIO config in use.

Use "g18_rec709" and "g22_rec709" for rec709 + gamma 1.8 or 2.2,
respectvely.

A few new API calls help out: equivalent_colorspace(),
set_colorspace(spec,name), set_colorspace_rec709_gamma().

OpenEXR always tagged files as linear by default. It was too aggressive
about this, so with this PR, now it defaults to lin_rec709 only for
files that appear to be RGB and not have any other clues about the color
space.

Similarly, the raw reader was too eager to set the Exif:ColorSpace to
look like sRGB even when it wasn't.

Lots of reference outputs needed to be updated.

This is a potentially big behavior change. But I think it's for the
better.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
  • Loading branch information
lgritz authored Oct 8, 2024
1 parent ff20241 commit a946288
Show file tree
Hide file tree
Showing 75 changed files with 542 additions and 445 deletions.
9 changes: 4 additions & 5 deletions src/cineon.imageio/cineoninput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,27 +169,26 @@ CineonInput::open(const std::string& name, ImageSpec& newspec)
// This is not very smart, but it seems that as a practical matter,
// all Cineon files are log. So ignore the gamma field and just set
// the color space to KodakLog.
m_spec.attribute("oiio:ColorSpace", "KodakLog");
m_spec.set_colorspace("KodakLog");
#else
// image linearity
// FIXME: making this more robust would require the per-channel transfer
// function functionality which isn't yet in OIIO
switch (m_cin.header.ImageDescriptor(0)) {
case cineon::kRec709Red:
case cineon::kRec709Green:
case cineon::kRec709Blue: m_spec.attribute("oiio:ColorSpace", "Rec709");
case cineon::kRec709Blue: m_spec.set_colorspace("Rec709");
default:
// either grayscale or printing density
if (!std::isinf(m_cin.header.Gamma()) && m_cin.header.Gamma() != 0.0f)
// actual gamma value is read later on
m_spec.attribute("oiio:ColorSpace",
Strutil::fmt::format("Gamma{:.2g}", g));
set_colorspace_rec709_gamma(m_spec, float(m_cin.header.Gamma()));
break;
}
// gamma exponent
if (!std::isinf(m_cin.header.Gamma()) && m_cin.header.Gamma() != 0.0f)
m_spec.attribute("oiio:Gamma", (float)m_cin.header.Gamma());
set_colorspace_rec709_gamma(m_spec, float(m_cin.header.Gamma()));
#endif

// general metadata
Expand Down
2 changes: 1 addition & 1 deletion src/dds.imageio/ddsinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ DDSInput::seek_subimage(int subimage, int miplevel)
// linear color space for HDR-ish images
if (colorspace == nullptr
&& (basetype == TypeDesc::HALF || basetype == TypeDesc::FLOAT))
colorspace = "linear";
colorspace = "lin_rec709";

m_spec.set_colorspace(colorspace);

Expand Down
6 changes: 6 additions & 0 deletions src/doc/imageioapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ just exist in the OIIO namespace as general utilities. (See

.. doxygenfunction:: get_extension_map

.. doxygenfunction:: OIIO::set_colorspace

.. doxygenfunction:: OIIO::set_colorspace_rec709_gamma

.. doxygenfunction:: OIIO::equivalent_colorspace

|
.. _sec-startupshutdown:
Expand Down
4 changes: 2 additions & 2 deletions src/doc/imageoutput.rst
Original file line number Diff line number Diff line change
Expand Up @@ -888,12 +888,12 @@ color space:
.. code-tab:: c++

ImageSpec spec (width, length, channels, format);
spec.attribute ("oiio:ColorSpace", "scene_linear");
spec.set_colorspace("scene_linear");

.. code-tab:: py

spec = ImageSpec(width, length, channels, format)
spec.attribute ("oiio:ColorSpace", "scene_linear")
spec.set_colorspace("scene_linear")

If a particular ``ImageOutput`` implementation is required (by the rules of
the file format it writes) to have pixels in a fixed color space,
Expand Down
48 changes: 47 additions & 1 deletion src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,6 @@ Section :ref:`sec-ImageSpec`, is replicated for Python.
spec.set_colorspace ("sRGB")
.. py:method:: ImageSpec.undefined ()
Returns `True` for a newly initialized (undefined) ImageSpec.
Expand Down Expand Up @@ -3885,6 +3884,53 @@ details.
formats = oiio.get_string_attribute ("format_list")
.. py:method:: set_colorspace (spec, name)
Set the metadata of the `spec` to presume that color space is `name` (or
to assume nothing about the color space if `name` is empty).
Example:
.. code-block:: python
spec = oiio.ImageSpec()
oiio.set_colorspace (spec, "lin_rec709")
This function was added in OpenImageIO 3.0.
.. py:method:: set_colorspace_rec709_gamma (spec, name)
Set the metadata of the `spec` to reflect Rec709 color primaries and the
given gamma.
Example:
.. code-block:: python
spec = oiio.ImageSpec()
oiio.set_colorspace_rec709_gamma (spec, 2.2)
This function was added in OpenImageIO 3.0.
.. py:method:: equivalent_colorspace (a, b)
Return `True` if the color spaces `a` and `b` are equivalent in the
default active color config.
Example:
.. code-block:: python
# ib is an ImageBuf
cs = ib.spec().get_string_attribute("oiio:ColorSpace")
if oiio.equivalent_colorspace(cs, "sRGB") :
print ("The image is sRGB")
This function was added in OpenImageIO 3.0.
.. py:method:: is_imageio_format_name (name)
Returns True if `name` is the name of a known and supported file format,
Expand Down
17 changes: 9 additions & 8 deletions src/doc/stdmetadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,18 +143,19 @@ Color information

- `"scene_linear"` : Color pixel values are known to be scene-linear and
using facility-default color primaries as defined by the OpenColorIO
configuration. Note that `"linear"` is treated as a synonym. (Note: when
no color config is found, this are presumed to use sRGB/Rec709 color
primaries when built against OpenColorIO 2.1 or earlier, or when no OCIO
support is available, but is presumed to be ACEScg when built against
OCIO 2.2 or higher and using its built-in config.)
- `"lin_srgb"` : Color pixel values are known to be linear and
using sRGB/Rec709 color primaries.
configuration.
- `"lin_srgb"`, `"lin_rec709"` : Color pixel values are known to be
linear and using sRGB/Rec709 color primaries. Note that `"linear"` is
treated as a synonym.
- `"sRGB"` : Using standard sRGB response and primaries.
- `"Rec709"` : Using standard Rec709 response and primaries.
- `"ACEScg"` : ACEScg color space encoding.
- `"AdobeRGB"` : Adobe RGB color space.
- `"KodakLog"` : Kodak logarithmic color space.
- `"g22_rec709"` : Rec709/sRGB primaries, but using a response curve
corresponding to gamma 2.2.
- `"g18_rec709"` : Rec709/sRGB primaries, but using a response curve
corresponding to gamma 1.8.
- `"GammaX.Y"` : Color values have been gamma corrected
(raised to the power :math:`1/\gamma`). The `X.Y` is the numeric value
of the gamma exponent.
Expand Down Expand Up @@ -230,7 +231,7 @@ Disk file format info/hints
`piz`, `pxr24`, `b44`, `b44a`, `dwaa`, or `dwab`.

he compression name is permitted to have a quality value to be appended
fter a colon, for example `dwaa:60`. The exact meaning and range of
after a colon, for example `dwaa:60`. The exact meaning and range of
he quality value can vary between different file formats and compression
odes, and some don't support quality values at all (it will be ignored if
ot supported, or if out of range).
Expand Down
13 changes: 4 additions & 9 deletions src/dpx.imageio/dpxinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,17 +312,12 @@ DPXInput::seek_subimage(int subimage, int miplevel)

// image linearity
switch (m_dpx.header.Transfer(subimage)) {
case dpx::kLinear: m_spec.attribute("oiio:ColorSpace", "Linear"); break;
case dpx::kLogarithmic:
m_spec.attribute("oiio:ColorSpace", "KodakLog");
break;
case dpx::kITUR709: m_spec.attribute("oiio:ColorSpace", "Rec709"); break;
case dpx::kLinear: m_spec.set_colorspace("Linear"); break;
case dpx::kLogarithmic: m_spec.set_colorspace("KodakLog"); break;
case dpx::kITUR709: m_spec.set_colorspace("Rec709"); break;
case dpx::kUserDefined:
if (!std::isnan(m_dpx.header.Gamma()) && m_dpx.header.Gamma() != 0) {
float g = float(m_dpx.header.Gamma());
m_spec.attribute("oiio:ColorSpace",
Strutil::fmt::format("Gamma{:.2}", g));
m_spec.attribute("oiio:Gamma", g);
set_colorspace_rec709_gamma(m_spec, float(m_dpx.header.Gamma()));
break;
}
// intentional fall-through
Expand Down
2 changes: 1 addition & 1 deletion src/gif.imageio/gifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ GIFInput::read_subimage_metadata(ImageSpec& newspec)
newspec.nchannels = 4;
newspec.default_channel_names();
newspec.alpha_channel = 4;
newspec.attribute("oiio:ColorSpace", "sRGB");
newspec.set_colorspace("sRGB");

m_previous_disposal_method = m_disposal_method;
m_disposal_method = DISPOSAL_UNSPECIFIED;
Expand Down
10 changes: 2 additions & 8 deletions src/hdr.imageio/hdrinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ HdrInput::RGBE_ReadHeader()
if (!line.size())
return false;

m_spec.attribute("oiio:ColorSpace", "lin_srgb");
m_spec.set_colorspace("lin_rec709");
// presume linear w/ srgb primaries -- seems like the safest assumption
// for this old file format.

Expand All @@ -310,13 +310,7 @@ HdrInput::RGBE_ReadHeader()
// 2.2, not 2.19998.
float g = float(1.0 / tempf);
g = roundf(100.0 * g) / 100.0f;
m_spec.attribute("oiio:Gamma", g);
if (g == 1.0f)
m_spec.attribute("oiio:ColorSpace", "linear");
else
m_spec.attribute("oiio:ColorSpace",
Strutil::fmt::format("Gamma{:.2g}", g));

set_colorspace_rec709_gamma(m_spec, g);
} else if (Strutil::parse_values(line,
"EXPOSURE=", span<float>(tempf))) {
m_spec.attribute("hdr:exposure", tempf);
Expand Down
2 changes: 1 addition & 1 deletion src/heif.imageio/heifinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ HeifInput::seek_subimage(int subimage, int miplevel)
m_himage.get_height(heif_channel_interleaved), bits / 8,
TypeUInt8);

m_spec.attribute("oiio:ColorSpace", "sRGB");
m_spec.set_colorspace("sRGB");

#if LIBHEIF_HAVE_VERSION(1, 12, 0)
// Libheif >= 1.12 added API call to find out if the image is associated
Expand Down
2 changes: 1 addition & 1 deletion src/iconvert/iconvert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ adjust_spec(ImageInput* in, ImageOutput* out, const ImageSpec& inspec,

outspec.attribute("oiio:Gamma", gammaval);
if (sRGB) {
outspec.attribute("oiio:ColorSpace", "sRGB");
outspec.set_colorspace("sRGB");
if (!strcmp(in->format_name(), "jpeg")
|| outspec.find_attribute("Exif:ColorSpace"))
outspec.attribute("Exif:ColorSpace", 1);
Expand Down
17 changes: 17 additions & 0 deletions src/include/OpenImageIO/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,23 @@ class OIIO_API ColorConfig {
/// Return a filename or other identifier for the config we're using.
std::string configname() const;

/// Set the spec's metadata to presume that color space is `name` (or to
/// assume nothing about the color space if `name` is empty). The core
/// operation is to set the "oiio:ColorSpace" attribute, but it also removes
/// or alters several other attributes that may hint color space in ways that
/// might be contradictory or no longer true.
///
/// @version 3.0
void set_colorspace(ImageSpec& spec, string_view name) const;

/// Set the spec's metadata to reflect Rec709 color primaries and the given
/// gamma. The core operation is to set the "oiio:ColorSpace" attribute, but
/// it also removes or alters several other attributes that may hint color
/// space in ways that might be contradictory or no longer true.
///
/// @version 3.0
void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const;

/// Return if OpenImageIO was built with OCIO support
static bool supportsOpenColorIO();

Expand Down
12 changes: 5 additions & 7 deletions src/include/OpenImageIO/imagebufalgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1817,12 +1817,9 @@ bool OIIO_API erode (ImageBuf &dst, const ImageBuf &src,
/// transformed, and the fourth channel (if it exists) is presumed to be
/// alpha. Any additional channels will be simply copied unaltered.
///
/// If OIIO was built with OpenColorIO support enabled, then the
/// transformation may be between any two spaces supported by the active
/// The transformation may be between any two spaces supported by the active
/// OCIO configuration, or may be a "look" transformation created by
/// `ColorConfig::createLookTransform`. If OIIO was not built with
/// OpenColorIO support enabled, then the only transformations available are
/// from "sRGB" to "linear" and vice versa.
/// `ColorConfig::createLookTransform`.
///
/// @param fromspace/tospace
/// For the varieties of `colorconvert()` that use named color
Expand Down Expand Up @@ -1927,7 +1924,8 @@ bool OIIO_API colormatrixtransform (ImageBuf &dst, const ImageBuf &src,
/// The looks to apply (comma-separated).
/// @param fromspace/tospace
/// For the varieties of `colorconvert()` that use named color
/// spaces, these specify the color spaces by name.
/// spaces, these specify the color spaces by name. If either
/// is the empty string, it will use `"scene_linear"`.
/// @param unpremult
/// If true, unpremultiply the image (divide the RGB channels by
/// alpha if it exists and is nonzero) before color conversion,
Expand Down Expand Up @@ -1979,7 +1977,7 @@ bool OIIO_API ociolook (ImageBuf &dst, const ImageBuf &src, string_view looks,
/// If `fromspace` is not supplied, it will assume that the
/// source color space is whatever is indicated by the source
/// image's metadata or filename, and if that cannot be deduced,
/// it will be assumed to be scene linear.
/// it will be assumed to be `"scene_linear"`.
/// @param looks
/// The looks to apply (comma-separated). This may be empty,
/// in which case no "look" is used. Note: this parameter value
Expand Down
27 changes: 27 additions & 0 deletions src/include/OpenImageIO/imageio.h
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,33 @@ inline string_view get_string_attribute (string_view name,
}


/// Set the metadata of the `spec` to presume that color space is `name` (or
/// to assume nothing about the color space if `name` is empty). The core
/// operation is to set the "oiio:ColorSpace" attribute, but it also removes
/// or alters several other attributes that may hint color space in ways that
/// might be contradictory or no longer true. This uses the current default
/// color config to adjudicate color space name equivalencies.
///
/// @version 3.0
OIIO_API void set_colorspace(ImageSpec& spec, string_view name);

/// Set the metadata of the `spec` to reflect Rec709 color primaries and the
/// given gamma. The core operation is to set the "oiio:ColorSpace" attribute,
/// but it also removes or alters several other attributes that may hint color
/// space in ways that might be contradictory or no longer true. This uses the
/// current default color config to adjudicate color space name equivalencies.
///
/// @version 3.0
OIIO_API void set_colorspace_rec709_gamma(ImageSpec& spec, float gamma);


/// Are the two named color spaces equivalent, based on the default color
/// config in effect?
///
/// @version 3.0
OIIO_API bool equivalent_colorspace(string_view a, string_view b);


/// Register the input and output 'create' routines and list of file
/// extensions for a particular format.
OIIO_API void declare_imageio_format (const std::string &format_name,
Expand Down
2 changes: 1 addition & 1 deletion src/jpeg.imageio/jpeginput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ JpgInput::open(const std::string& name, ImageSpec& newspec)
return false;

// Assume JPEG is in sRGB unless the Exif or XMP tags say otherwise.
m_spec.attribute("oiio:ColorSpace", "sRGB");
m_spec.set_colorspace("sRGB");

if (m_cinfo.jpeg_color_space == JCS_CMYK)
m_spec.attribute("jpeg:ColorSpace", "CMYK");
Expand Down
3 changes: 2 additions & 1 deletion src/jpeg.imageio/jpegoutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec,
comment.size() + 1);
}

if (Strutil::iequals(m_spec.get_string_attribute("oiio:ColorSpace"), "sRGB"))
if (equivalent_colorspace(m_spec.get_string_attribute("oiio:ColorSpace"),
"sRGB"))
m_spec.attribute("Exif:ColorSpace", 1);

// Write EXIF info
Expand Down
2 changes: 1 addition & 1 deletion src/jpeg2000.imageio/jpeg2000input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ Jpeg2000Input::open(const std::string& name, ImageSpec& p_spec)
m_spec.full_height = m_image->y1;

m_spec.attribute("oiio:BitsPerSample", maxPrecision);
m_spec.attribute("oiio:ColorSpace", "sRGB");
m_spec.set_colorspace("sRGB");

if (m_image->icc_profile_len && m_image->icc_profile_buf) {
m_spec.attribute("ICCProfile",
Expand Down
Loading

0 comments on commit a946288

Please sign in to comment.