Skip to content

Commit

Permalink
feat: implement support for OCIO NamedTransforms
Browse files Browse the repository at this point in the history
(WIP)
  • Loading branch information
zachlewis committed Aug 13, 2024
1 parent 80545a9 commit da51b32
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 11 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
186 changes: 176 additions & 10 deletions src/libOpenImageIO/color_ocio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,45 +76,48 @@ class ColorProcCacheKey {
ColorProcCacheKey(ustring in, ustring out, ustring key = ustring(),
ustring val = ustring(), ustring looks = ustring(),
ustring display = ustring(), ustring view = ustring(),
ustring file = ustring(), bool inverse = false)
ustring file = ustring(),
ustring namedtransform = ustring(),
bool inverse = false),
: inputColorSpace(in)
, outputColorSpace(out)
, context_key(key)
, context_value(val)
, looks(looks)
, file(file)
, namedtransform(namedtransform)
, inverse(inverse)
{
hash = inputColorSpace.hash() + 14033ul * outputColorSpace.hash()
+ 823ul * context_key.hash() + 28411ul * context_value.hash()
+ 1741ul
* (looks.hash() + display.hash() + view.hash()
+ file.hash())
+ file.hash() + namedtransform.hash())
+ (inverse ? 6421 : 0);
// N.B. no separate multipliers for looks, display, view, file
// because they're never used for the same lookup.
// N.B. no separate multipliers for looks, display, view, file,
// namedtransform, because they're never used for the same lookup.
}

friend bool operator<(const ColorProcCacheKey& a,
const ColorProcCacheKey& b)
{
return std::tie(a.hash, a.inputColorSpace, a.outputColorSpace,
a.context_key, a.context_value, a.looks, a.display,
a.view, a.file, a.inverse)
a.view, a.file, a.namedtransform a.inverse)
< std::tie(b.hash, b.inputColorSpace, b.outputColorSpace,
b.context_key, b.context_value, b.looks, b.display,
b.view, b.file, b.inverse);
b.view, b.file, a.namedtransform, b.inverse);
}

friend bool operator==(const ColorProcCacheKey& a,
const ColorProcCacheKey& b)
{
return std::tie(a.hash, a.inputColorSpace, a.outputColorSpace,
a.context_key, a.context_value, a.looks, a.display,
a.view, a.file, a.inverse)
a.view, a.file, a.namedtransform, a.inverse)
== std::tie(b.hash, b.inputColorSpace, b.outputColorSpace,
b.context_key, b.context_value, b.looks, b.display,
b.view, b.file, b.inverse);
b.view, b.file, a.namedtransform, b.inverse);
}
ustring inputColorSpace;
ustring outputColorSpace;
Expand All @@ -124,6 +127,7 @@ class ColorProcCacheKey {
ustring display;
ustring view;
ustring file;
ustring namedtransform;
bool inverse;
size_t hash;
};
Expand Down Expand Up @@ -1238,6 +1242,54 @@ ColorConfig::getDisplayViewLooks(const std::string& display,



int
ColorConfig::getNumNamedTransforms() const
{
if (getImpl()->config_ && !disable_ocio)
return getImpl()->config_->getNumNamedTransforms();
return 0;
}



const char*
ColorConfig::getNamedTransformNameByIndex(int index) const
{
if (getImpl()->config_ && !disable_ocio)
return getImpl()->config_->getNamedTransformNameByIndex(index);
return nullptr;
}



std::vector<std::string>
ColorConfig::getNamedTransformNames() const
{
std::vector<std::string> result;
for (int i = 0, e = getNumLooks(); i != e; ++i)
result.emplace_back(getNamedTransformNameByIndex(i));
return result;
}



std::vector<std::string>
ColorConfig::getNamedTransformAliases(string_view named_transform) const
{
std::vector<std::string> result;
auto config = getImpl()->config_;
if (config) {
auto nt = config->getNamedTransform(c_str(named_transform));
if (nt) {
for (int i = 0, e = nt->getNumAliases(); i < e; ++i)
result.emplace_back(nt->getAlias(i));
}
}
return result;
}



std::string
ColorConfig::configname() const
{
Expand Down Expand Up @@ -1804,7 +1856,8 @@ ColorConfig::createLookTransform(ustring looks, ustring inputColorSpace,
// exists, just return it.
ColorProcCacheKey prockey(inputColorSpace, outputColorSpace, context_key,
context_value, looks, ustring() /*display*/,
ustring() /*view*/, ustring() /*file*/, inverse);
ustring() /*view*/, ustring() /*file*/,
ustring() /*namedtransform*/, inverse);
ColorProcessorHandle handle = getImpl()->findproc(prockey);
if (handle)
return handle;
Expand Down Expand Up @@ -1888,7 +1941,8 @@ ColorConfig::createDisplayTransform(ustring display, ustring view,
// exists, just return it.
ColorProcCacheKey prockey(inputColorSpace, ustring() /*outputColorSpace*/,
context_key, context_value, looks, display, view,
ustring() /*file*/, inverse);
ustring() /*file*/, ustring() /*namedtransform*/,
inverse);
ColorProcessorHandle handle = getImpl()->findproc(prockey);
if (handle)
return handle;
Expand Down Expand Up @@ -1993,6 +2047,69 @@ ColorConfig::createFileTransform(ustring name, bool inverse) const



ColorProcessorHandle
ColorConfig::createNamedTransform(string_view name, bool inverse,
string_view context_key,
string_view context_value) const
{
return createNamedTransform(ustring(name), inverse, ustring(context_key),
ustring(context_value));
}



ColorProcessorHandle
ColorConfig::createNamedTransform(ustring name, bool inverse,
ustring context_key,
ustring context_value) const
{
// First, look up the requested processor in the cache. If it already
// exists, just return it.
ColorProcCacheKey prockey(ustring() /*inputColorSpace*/,
ustring() /*outputColorSpace*/, context_key,
context_value, ustring() /*looks*/,
ustring() /*display*/, ustring() /*view*/,
ustring() /*file*/, name, inverse);
ColorProcessorHandle handle = getImpl()->findproc(prockey);
if (handle)
return handle;

// Ask OCIO to make a Processor that can handle the requested
// transformation.
if (getImpl()->config_ && !disable_ocio) {
OCIO::ConstConfigRcPtr config = getImpl()->config_;
auto transform = config->getNamedTransform(name.c_str());
OCIO::TransformDirection dir = inverse ? OCIO::TRANSFORM_DIR_INVERSE
: OCIO::TRANSFORM_DIR_FORWARD;
auto context = config->getCurrentContext();
auto keys = Strutil::splits(context_key, ",");
auto values = Strutil::splits(context_value, ",");
if (keys.size() && values.size() && keys.size() == values.size()) {
OCIO::ContextRcPtr ctx = context->createEditableCopy();
for (size_t i = 0; i < keys.size(); ++i)
ctx->setStringVar(keys[i].c_str(), values[i].c_str());
context = ctx;
}

OCIO::ConstProcessorRcPtr p;
try {
// Get the processor corresponding to this transform.
p = config->getProcessor(context, transform, dir);
getImpl()->clear_error();
handle = ColorProcessorHandle(new ColorProcessor_OCIO(p));
} catch (OCIO::Exception& e) {
getImpl()->error(e.what());
} catch (...) {
getImpl()->error(
"An unknown error occurred in OpenColorIO, getProcessor");
}
}

return getImpl()->addproc(prockey, handle);
}



ColorProcessorHandle
ColorConfig::createMatrixTransform(M44fParam M, bool inverse) const
{
Expand Down Expand Up @@ -2547,6 +2664,55 @@ ImageBufAlgo::ociofiletransform(const ImageBuf& src, string_view name,



bool
ImageBufAlgo::ocionamedtransform(ImageBuf& dst, const ImageBuf& src,
string_view name, bool unpremult,
bool inverse, string_view key,
string_view value,
const ColorConfig* colorconfig, ROI roi,
int nthreads)
{
pvt::LoggedTimer logtime("IBA::ocionamedtransform");
ColorProcessorHandle processor;
{
if (!colorconfig)
colorconfig = &ColorConfig::default_colorconfig();
processor
= colorconfig->createNamedTransform(name, inverse, key, value);
if (!processor) {
if (colorconfig->has_error())
dst.errorfmt("{}", colorconfig->geterror());
else
dst.errorfmt(
"Could not construct the color transform (unknown error)");
return false;
}
}

logtime.stop(); // transition to colorconvert
bool ok = colorconvert(dst, src, processor.get(), unpremult, roi, nthreads);
return ok;
}



ImageBuf
ImageBufAlgo::ocionamedtransform(const ImageBuf& src, string_view name,
bool unpremult, bool inverse, string_view key,
string_view value,
const ColorConfig* colorconfig, ROI roi,
int nthreads)
{
ImageBuf result;
bool ok = ocionamedtransform(result, src, name, unpremult, inverse,
key, value, colorconfig, roi, nthreads);
if (!ok && !result.has_error())
result.errorfmt("ImageBufAlgo::ocionamedtransform() error");
return result;
}



bool
ImageBufAlgo::colorconvert(span<float> color, const ColorProcessor* processor,
bool unpremult)
Expand Down
26 changes: 26 additions & 0 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2385,6 +2385,20 @@ OIIOTOOL_OP(ociofiletransform, 1, [&](OiiotoolOp& op, span<ImageBuf*> img) {



// --ocionamedtransform
OIIOTOOL_OP(ocionamedtransform, 1, [&](OiiotoolOp& op, span<ImageBuf*> img) {
string_view name = op.args(1);
std::string contextkey = op.options()["key"];
std::string contextvalue = op.options()["value"];
bool unpremult = op.options().get_int("unpremult");
bool inverse = op.options().get_int("inverse");
return ImageBufAlgo::ocionamedtransform(*img[0], *img[1], name, unpremult,
inverse, contextkey, contextvalue,
&ot.colorconfig);
});



static void
output_tiles(Oiiotool& ot, cspan<const char*>)
{
Expand Down Expand Up @@ -6015,6 +6029,15 @@ print_ocio_info(Oiiotool& ot, std::ostream& out)
out << "\n";
}
}

int nnamed_transforms = ot.colorconfig.getNumNamedTransforms();
if (nnamed_transforms) {
out << "Named transforms:\n";
for (int i = 0; i < nnamed_transforms; ++i) {
const char* x = ot.colorconfig.getNamedTransformNameByIndex(i);
out << " - " << quote_if_spaces(x) << "\n";
}
}
if (!ot.colorconfig.supportsOpenColorIO())
out << "No OpenColorIO support was enabled at build time.\n";
}
Expand Down Expand Up @@ -6848,6 +6871,9 @@ Oiiotool::getargs(int argc, char* argv[])
ap.arg("--ociofiletransform %s:FILENAME")
.help("Apply the named OCIO filetransform (options: inverse=, unpremult=)")
.OTACTION(action_ociofiletransform);
ap.arg("--ocionamedtransform %s:NAME")
.help("Apply the named OCIO namedtransform (options: inverse=, key=, value=, unpremult=)")
.OTACTION(action_ocionamedtransform);
ap.arg("--unpremult")
.help("Divide all color channels of the current image by the alpha to \"un-premultiply\"")
.OTACTION(action_unpremult);
Expand Down
8 changes: 7 additions & 1 deletion src/python/py_colorconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,13 @@ declare_colorconfig(py::module& m)
[](const ColorConfig& self, const std::string& color_space) {
return self.getAliases(color_space);
})

.def("getNumNamedTransforms", &ColorConfig::getNumNamedTransforms)
.def("getNamedTransformNameByIndex", &ColorConfig::getNamedTransformNameByIndex)
.def("getNamedTransformNames", &ColorConfig::getNamedTransformNames)
.def("getNamedTransformAliases",
[](const ColorConfig& self, const std::string& named_transform) {
return self.getNamedTransformAliases(named_transform);
})
.def("getColorSpaceFromFilepath",
[](const ColorConfig& self, const std::string& str) {
return std::string(self.getColorSpaceFromFilepath(str));
Expand Down

0 comments on commit da51b32

Please sign in to comment.