Skip to content

Commit

Permalink
feat: implement support for OCIO NamedTransforms (#4393)
Browse files Browse the repository at this point in the history
- ColorProcCacheKey: Update definition, signature, and usage to support
caching NamedTransform processor handles.
(Note: **ABI-breaking**)

- ColorConfig: add NamedTransform convenience functions:
`getNumNamedTransforms`, `getNamedTransformNameByIndex`,
`getNamedTransformNames`, `getNamedTransformAliases`

- ColorConfig: add `createNamedTransform` function returning new
ColorProcessorHandle

- Add `ImageBufAlgo::ocionamedtransform` functions

- oiiotool: add `--ocionamedtransform` operator

- python: add NamedTransform convenience function bindings

- python: add `ImageBufAlgo.ocionamedtransform(...)` bindings

- Update `oiiotool` and `python` documentation with examples


## Description

OCIO-2 config authors may define a set of stand-alone "source-agnostic"
`NamedTransforms`. Unlike the `colorconvert`, `ociodisplay`, and
`ociolook` IBAs, which explicitly convert _from_ a color space to
something else, the `ocionamedtransform` IBA behaves more like the
`ociofiletransform` IBA, where the transform itself can be applied
either in the forwards or inverse direction, and does not take into
consideration the input encoding or image state.

Quoting from the OCIO [NamedTransform
documentation](https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#named-transforms):

> Sometimes it is helpful to include one or more transforms in a config
that are essentially stand-alone transforms that do not have a fixed
relationship to a reference space or a process space. An example would
be a “utility curve” transform where the intent is to simply apply a
LUT1D without any conversion to a reference space. In these cases, a
named_transforms section may be added to the config with one or more
named transforms. [...] This feature may be used to emulate older
methods of color management that ignored the RGB primaries and simply
applied one-dimensional transformations.

Notably, the built-in OCIO-2 Studio config
(`ocio://studio-config-latest`) contains NamedTransforms "sRGB - Curve"
and "Rec709 - Curve", which we could implement as drop-in replacements
for the existing `linear_to_sRGB`, `sRGB_to_linear`, `linear_to_Rec709`,
and `Rec709_to_linear` functions in `color.h` (unless those functions
are deprecated...).

There are several other applications for NamedTransforms as well. They
can be extremely useful as workflow-oriented transforms implemented at
various points in facility pipelines (e.g., for applying shot-specific
conversions, grades, gamut-compression, etc.); they can be used to
provide diagnostic transforms for visualizing "exposure zones", clipped
areas, NaNs, etc; for visually "tagging" problematic clips; for applying
parts of ACES Metadata Files parsed by OCIO's forthcoming AMF parser;
for conversion to non-RGB color models... all sorts of things.

## Tests

Tests are forthcoming. I've tested everything locally, but have not yet
implemented proper tests in the testsuite.

---------

Signed-off-by: Zach Lewis <[email protected]>
  • Loading branch information
zachlewis authored Sep 2, 2024
1 parent 482f165 commit 1d599d2
Show file tree
Hide file tree
Showing 8 changed files with 413 additions and 13 deletions.
33 changes: 33 additions & 0 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4439,6 +4439,39 @@ will be printed with the command `oiiotool --colorconfiginfo`.

oiiotool in.jpg --ociofiletransform footransform.csp -o out.jpg


.. option:: --ocionamedtransform <name>

Replace the current image with a new image whose pixels are transformed
using the named OpenColorIO named transform. Optional appended arguments
include:

- `key=` *name*, `value=` *str*

Adds a key/value pair to the "context" that OpenColorIO will used
when applying the look. Multiple key/value pairs may be specified by
making each one a comma-separated list.

- `inverse=` *val* :

If *val* is nonzero, inverts the color transformation.

- `unpremult=` *val* :

If the numeric *val* is nonzero, the pixel values will be
"un-premultipled" (divided by alpha) prior to the actual color
conversion, and then re-multipled by alpha afterwards. The default is
0, meaning the color transformation not will be automatically
bracketed by divide-by-alpha / mult-by-alpha operations.

`:subimages=` *indices-or-names*
Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`).

Examples::

oiiotool in.exr --ocionamedtransform:inverse=1 srgb_crv -o out.jpg


.. option:: --unpremult

Divide all color channels (those not alpha or z) of the current image by
Expand Down
13 changes: 13 additions & 0 deletions src/doc/pythonbindings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3623,6 +3623,19 @@ Color manipulation
Dst = ImageBufAlgo.ociofiletransform (Src, "foottransform.csp")
.. py:method:: ImageBuf ImageBufAlgo.ocionamedtransform (src, name, unpremult=True, inverse=False, context_key="", context_value="", colorconfig="", roi=ROI.All, nthreads=0)
bool ImageBufAlgo.ocionamedtransform (dst, src, name, unpremult=True, inverse=False, context_key="", context_value="", colorconfig="", roi=ROI.All, nthreads=0)
Apply an OpenColorIO "named" transform to the pixel values.
Example:
.. code-block:: python
Src = ImageBuf ("tahoe.dpx")
Dst = ImageBufAlgo.ocionamedtransform (Src, "log_to_lin",
context_key="SHOT", context_value="pe0012")
.. py:method:: ImageBuf ImageBufAlgo.unpremult (src, roi=ROI.All, nthreads=0)
bool ImageBufAlgo.unpremult (dst, src, roi=ROI.All, nthreads=0)
Expand Down
32 changes: 32 additions & 0 deletions src/include/OpenImageIO/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ class OIIO_API ColorConfig {
/// Retrieve the full list of known look names, as a vector of strings.
std::vector<std::string> getLookNames() const;

/// Get the number of NamedTransforms defined in this configuration
int getNumNamedTransforms() const;

/// Query the name of the specified NamedTransform.
const char* getNamedTransformNameByIndex(int index) const;

/// Retrieve the full list of known NamedTransforms, as a vector of strings
std::vector<std::string> getNamedTransformNames() const;

/// Retrieve the full list of aliases for the named NamedTransform.
std::vector<std::string>
getNamedTransformAliases(string_view named_transform) const;

/// Is the color space known to be linear? This is very conservative, and
/// will return false if it's not sure.
bool isColorSpaceLinear(string_view name) const;
Expand Down Expand Up @@ -305,6 +318,25 @@ class OIIO_API ColorConfig {
ColorProcessorHandle createFileTransform(ustring name,
bool inverse = false) const;

/// Construct a processor to perform color transforms determined by an
/// OpenColorIO NamedTransform. It is possible that this will return an
/// empty handle if the NamedTransform doesn't exist or is not allowed.
///
/// The handle is actually a shared_ptr, so when you're done with a
/// ColorProcess, just discard it. ColorProcessor(s) remain valid even
/// if the ColorConfig that created them no longer exists.
///
/// Created ColorProcessors are cached, so asking for the same color
/// space transformation multiple times shouldn't be very expensive.
ColorProcessorHandle
createNamedTransform(string_view name, bool inverse = false,
string_view context_key = "",
string_view context_value = "") const;
ColorProcessorHandle
createNamedTransform(ustring name, bool inverse = false,
ustring context_key = ustring(),
ustring context_value = ustring()) const;

/// Construct a processor to perform color transforms specified by a
/// 4x4 matrix.
///
Expand Down
44 changes: 44 additions & 0 deletions src/include/OpenImageIO/imagebufalgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,50 @@ bool OIIO_API ociofiletransform (ImageBuf &dst, const ImageBuf &src,
ROI roi={}, int nthreads=0);


/// Return the pixels of `src` within the ROI, applying an OpenColorIO
/// "named" transform to the pixel values. In-place operations
/// (`dst` == `src`) are supported.
///
/// The first three channels are presumed to be the color to be
/// transformed, and the fourth channel (if it exists) is presumed to be
/// alpha. Any additional channels will be simply copied unaltered.
///
/// @param name
/// The name of the OCIO NamedTransform to apply.
/// @param unpremult
/// If true, unpremultiply the image (divide the RGB channels by
/// alpha if it exists and is nonzero) before color conversion,
/// then repremult after the after the color conversion. Passing
/// unpremult=false skips this step, which may be desirable if
/// you know that the image is "unassociated alpha" (a.k.a.,
/// "not pre-multiplied colors").
/// @param inverse
/// If `true`, it will apply the NamedTransform in the inverse
/// direction.
/// @param context_key/context_value
/// Optional key/value to establish a context (for example, a
/// shot-specific transform).
/// @param colorconfig
/// An optional `ColorConfig*` specifying an OpenColorIO
/// configuration. If not supplied, the default OpenColorIO
/// color configuration found by examining the `$OCIO`
/// environment variable will be used instead.
ImageBuf OIIO_API ocionamedtransform (const ImageBuf &src, string_view name,
bool unpremult=true, bool inverse=false,
string_view context_key="",
string_view context_value="",
const ColorConfig* colorconfig = nullptr,
ROI roi={}, int nthreads=0);
/// Write to an existing image `dst` (allocating if it is uninitialized).
bool OIIO_API ocionamedtransform (ImageBuf &dst, const ImageBuf &src,
string_view name, bool unpremult=true,
bool inverse=false,
string_view context_key="",
string_view context_value="",
const ColorConfig* colorconfig = nullptr,
ROI roi={}, int nthreads=0);


/// @defgroup premult (Premultiply or un-premultiply color by alpha)
/// @{
///
Expand Down
Loading

0 comments on commit 1d599d2

Please sign in to comment.