From a44084777ede4c24671a415afd1708eb857e36a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 23 Dec 2022 13:24:03 -0500 Subject: [PATCH 01/22] - Implementation of identifyInterchangeSpace, identifyBuiltinColorSpace and AreProcessorsEquivalent. - Remove some include of Processor.h where it wasn't needed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 33 + src/OpenColorIO/CMakeLists.txt | 1 + src/OpenColorIO/Config.cpp | 704 +++++++----------- src/OpenColorIO/ConfigUtils.cpp | 253 +++++++ src/OpenColorIO/ConfigUtils.h | 37 + src/OpenColorIO/Processor.cpp | 40 + src/OpenColorIO/Processor.h | 7 + src/OpenColorIO/Transform.cpp | 1 - src/OpenColorIO/fileformats/FileFormatICC.cpp | 1 + src/OpenColorIO/transforms/FileTransform.h | 1 - tests/cpu/CMakeLists.txt | 1 + tests/cpu/ColorSpace_tests.cpp | 102 +++ 12 files changed, 732 insertions(+), 449 deletions(-) create mode 100644 src/OpenColorIO/ConfigUtils.cpp create mode 100644 src/OpenColorIO/ConfigUtils.h diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 514fcc3db0..ab7159692e 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -679,6 +679,24 @@ class OCIOEXPORT Config */ bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const; + /** + * \brief Identify the interchange space of the source config and the default built-in config. + * + * \param srcInterchange Interchange space from the source config (output). + * \param builtinInterchange Interchange space from the default built-in config (output). + * + * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. + */ + void identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const; + /** + * \brief Find the name of the color space in the source config that is the same as + * a color space in the default built-in config. + * + * \param builtinColorSpaceName Color space name in the built-in config. + * \return const char* Matching color space from the source config. Empty if not found. + */ + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; + // // Roles // @@ -2424,6 +2442,21 @@ class OCIOEXPORT Processor ConstCPUProcessorRcPtr getOptimizedCPUProcessor(BitDepth inBitDepth, BitDepth outBitDepth, OptimizationFlags oFlags) const; + /** + * \brief Returns whether the two processors are equivalent. + * + * \param p1 First processor to compare. + * \param p2 Second processor to compare. + * \param rgbaValues RGBA values to use to test the processors. + * \param numValues Number of RGBA values. + * \param tolerance Tolerance of the comparaison. + * \return True or false depending on whether the two processors are equivalent. + */ + static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/CMakeLists.txt b/src/OpenColorIO/CMakeLists.txt index 1c4d774ddb..1ae08327fb 100755 --- a/src/OpenColorIO/CMakeLists.txt +++ b/src/OpenColorIO/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES ColorSpace.cpp ColorSpaceSet.cpp Config.cpp + ConfigUtils.cpp Context.cpp ContextVariableUtils.cpp CPUProcessor.cpp diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index bdf9994756..1483529f3e 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -15,6 +15,7 @@ #include #include "builtinconfigs/BuiltinConfigRegistry.h" +#include "ConfigUtils.h" #include "ContextVariableUtils.h" #include "Display.h" #include "fileformats/FileFormatICC.h" @@ -1095,302 +1096,14 @@ class Config::Impl return -1; } - std::string getRefSpace(ConstConfigRcPtr & cfg) const - { - // Find a color space where isData is false and it has neither a to_ref or from_ref - // transform. - auto nbCs = cfg->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = cfg->getColorSpace(cfg->getColorSpaceNameByIndex(i)); - if (cs->isData()) - { - continue; - } - - auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (t != nullptr) - { - continue; - } - - t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (t != nullptr) - { - continue; - } - - return cs->getName(); - } - return ""; - } - - bool containsSRGB(ConstColorSpaceRcPtr & cs) const - { - std::string name = StringUtils::Lower(cs->getName()); - if (StringUtils::Find(name, "srgb") != std::string::npos) - { - return true; - } - - size_t nbOfAliases = cs->getNumAliases(); - for (size_t i = 0; i < nbOfAliases; i++) - { - if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) - { - return true; - } - } - - return false; - } - - ConstProcessorRcPtr getRefToSRGBTransform(ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) const - { - // Build reference space of the given prims to sRGB transform. - std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(refColorSpaceName.c_str()); - csTransform->setDst(srgbColorSpaceName.c_str()); - - ConstProcessorRcPtr proc = getProcessorWithoutCaching(*builtinConfig, - csTransform, - TRANSFORM_DIR_FORWARD); - return proc; - } - - bool isIdentityTransform(ProcessorRcPtr & proc, std::vector & vals) const - { - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return false; - } - } - - return true; - } - - std::string getReferenceSpaceFromLinearSpace(ConstConfigRcPtr & srcConfig, - ConstColorSpaceRcPtr & cs, - ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) const - { - // If the color space is a recognized linear space, return the reference space used by - // the config. - std::string refSpace; - bool toRefDirection = true; - auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (!srcTransform) - { - srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (srcTransform) - { - toRefDirection = false; - } - else - { - return ""; - } - } - - // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is - // enough of an identity. - std::vector vals = { 0.7f, 0.4f, 0.02f, - 0.02f, 0.6f, 0.2f, - 0.3f, 0.02f, 0.5f, - 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f }; - - // Generate matrices between all combinations of the Built-in linear color spaces. - // Then combine these with the transform from the current color space to see if the result is - // an identity. If so, then it identifies the reference space being used by the source config. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) - { - for (size_t j = 0; j < builtinLinearSpaces.size(); j++) - { - if (i != j) - { - ConstProcessorRcPtr p1 = getProcessorWithoutCaching(*srcConfig, - srcTransform, - TRANSFORM_DIR_FORWARD); - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j].c_str()); - csTransform->setDst(builtinLinearSpaces[i].c_str()); - - ConstProcessorRcPtr p2 = getProcessorWithoutCaching(*builtinConfig, - csTransform, - TRANSFORM_DIR_FORWARD); - - ProcessorRcPtr proc = Processor::Create(); - proc->getImpl()->concatenate(p1, p2); - - if (isIdentityTransform(proc, vals)) - { - if (toRefDirection) - { - refSpace = builtinLinearSpaces[j]; - } - else - { - refSpace = builtinLinearSpaces[i]; - } - - return refSpace; - } - } - } - } - - return ""; - } - - std::string getReferenceSpaceFromSRGBSpace(ConstConfigRcPtr & config, - ConstColorSpaceRcPtr cs, - ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) const - { - // If the color space is an sRGB texture space, return the reference space used by the config. - - // Get a transform in the to-reference direction. - ConstTransformRcPtr toRefTransform; - ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (ctransform) - { - toRefTransform = ctransform; - } - else - { - ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (ctransform) - { - TransformRcPtr transform = ctransform->createEditableCopy(); - transform->setDirection(TRANSFORM_DIR_INVERSE); - toRefTransform = transform; - } - else - { - // Both directions missing. - return ""; - } - } - - // First check if it has the right non-linearity. The objective is to fail quickly on color - // spaces that are definitely not sRGB before proceeding to the longer test of guessing the - // reference space primaries. - - // Break point is at 0.039286, so include at least one value below this. - std::vector vals = - { - 0.5f, 0.5f, 0.5f, - 0.03f, 0.03f, 0.03f, - 0.25f, 0.25f, 0.25f, - 0.75f, 0.75f, 0.75f, - 0.f, 0.f, 0.f, - 1.f, 1.f , 1.f - }; - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstProcessorRcPtr proc = getProcessorWithoutCaching(*config, - toRefTransform, - TRANSFORM_DIR_FORWARD); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - // Apply the sRGB function (linear to non-lin). - // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. - if (out[i] <= 0.0030399346397784323f) - { - out[i] *= 12.923210180787857f; - } - else - { - out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; - } - - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return ""; - } - } - - // - // Then try the various primaries for the reference space. - // - - // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact - // converting sRGB texture values to the candidate reference space. It includes 0.02 which is - // on the sRGB linear segment, color values, and neutral values. - vals = { 0.7f, 0.4f, 0.02f, - 0.02f, 0.6f, 0.2f, - 0.3f, 0.02f, 0.5f, - 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f }; - std::string refSpace = ""; - ConstProcessorRcPtr fromRefProc; - if (toRefTransform) - { - // The color space has the sRGB non-linearity. Now try combining the transform with a - // transform from the Built-in config that goes from a variety of reference spaces to an - // sRGB texture space. If the result is an identity, then that tells what the source config - // reference space is. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) - { - fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); - - ConstProcessorRcPtr toRefProc = getProcessorWithoutCaching(*config, - toRefTransform, - TRANSFORM_DIR_FORWARD); - - ProcessorRcPtr proc = Processor::Create(); - proc->getImpl()->concatenate(toRefProc, fromRefProc); - - if (isIdentityTransform(proc, vals)) - { - refSpace = builtinLinearSpaces[i]; - } - } - } - - return refSpace; - } - - ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + static ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, const char * srcColorSpaceName, const char * builtinColorSpaceName, - TransformDirection direction) const + TransformDirection direction) { // Use the Default config as the Built-in config to interpret the known color space name. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) { std::ostringstream os; @@ -1420,79 +1133,32 @@ class Config::Impl } // otherwise, do nothing and continue. } - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(srcConfig); - if (refColorSpaceName.empty()) - { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); - } + char srcInterchange[255]; + char builtinInterchange[255]; - // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = srcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) - { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(srcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } + srcConfig->identifyInterchangeSpace(srcInterchange, builtinInterchange); - if (refColorSpacePrims.empty()) - { - // Check for a linear space with known primaries. - nbCs = srcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); - if (srcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(srcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - } - - if (!refColorSpacePrims.empty()) + if (builtinInterchange && builtinInterchange[0]) { - // Use the interchange spaces to get the processor. - std::string srcInterchange = refColorSpaceName; - std::string builtinInterchange = refColorSpacePrims; - ConstProcessorRcPtr proc; if (direction == TRANSFORM_DIR_FORWARD) { proc = Config::GetProcessorFromConfigs(srcConfig, srcColorSpaceName, - srcInterchange.c_str(), + srcInterchange, builtinConfig, builtinColorSpaceName, - builtinInterchange.c_str()); + builtinInterchange); } else if (direction == TRANSFORM_DIR_INVERSE) { proc = Config::GetProcessorFromConfigs(builtinConfig, builtinColorSpaceName, - builtinInterchange.c_str(), + builtinInterchange, srcConfig, srcColorSpaceName, - srcInterchange.c_str()); + srcInterchange); } return proc; } @@ -3230,6 +2896,151 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe return true; } +void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const +{ + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + std::vector builtinLinearSpaces = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Get the name of (one of) the reference spaces. + std::string refColorSpaceName = getRefSpace(*eSrcConfig); + if (refColorSpaceName.empty()) + { + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } + + // Check for an sRGB texture space. + std::string refColorSpacePrims = ""; + int nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) + { + refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + + if (refColorSpacePrims.empty()) + { + // Check for a linear space with known primaries. + nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + { + refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + } + + if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + { + // Copy interchange role from source config. + std::memcpy(srcInterchange, refColorSpaceName.c_str(), refColorSpaceName.size()); + // Copy interchange role from built-in config. + std::memcpy(builtinInterchange, refColorSpacePrims.c_str(), refColorSpacePrims.size()); + + // Terminate the string. + srcInterchange[refColorSpaceName.size()] = '\0'; + builtinInterchange[refColorSpacePrims.size()] = '\0'; + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } +} + +const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const +{ + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + char srcInterchange[255]; + char builtinInterchange[255]; + + // Identify interchange space. + identifyInterchangeSpace(srcInterchange, builtinInterchange); + + // Get processor from that space to the built-in color space. + ConstProcessorRcPtr builtinProc; + if (builtinInterchange && builtinInterchange[0]) + { + builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), + builtinInterchange, + builtinColorSpaceName); + } + + if (builtinProc && srcInterchange && srcInterchange[0]) + { + // Iterate over each color space in the source config. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + int nbCs = getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + // Get processor from that space to its reference and then use isProcessorEquivalent. + // If equivalent, return that color space name. + + ConstColorSpaceRcPtr cs = getColorSpace(getColorSpaceNameByIndex(i)); + const char * csName = cs->getName(); + + ConstProcessorRcPtr proc = getProcessor(getCurrentContext(), + csName, + srcInterchange); + + if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + { + return csName; + } + } + } + + return ""; +} + /////////////////////////////////////////////////////////////////////////// const char * Config::parseColorSpaceFromString(const char * str) const @@ -4944,20 +4755,20 @@ ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr src const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return srcConfig->getImpl()->getProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_FORWARD); + return Config::Impl::getProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_FORWARD); } ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return srcConfig->getImpl()->getProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_INVERSE); + return Config::Impl::getProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_INVERSE); } std::ostream& operator<< (std::ostream& os, const Config& config) @@ -5062,6 +4873,99 @@ void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept getImpl()->setProcessorCacheFlags(flags); } +void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) +{ + getImpl()->m_context->setConfigIOProxy(ciop); + + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +ConfigIOProxyRcPtr Config::getConfigIOProxy() const +{ + return getImpl()->m_context->getConfigIOProxy(); +} + +bool Config::isArchivable() const +{ + ConstContextRcPtr context = getCurrentContext(); + + // Current archive implementation needs a working directory to look for LUT files and + // working directory must be an absolute path. + const char * workingDirectory = getWorkingDir(); + if ((workingDirectory && !workingDirectory[0]) || !pystring::os::path::isabs(workingDirectory)) + { + return false; + } + + // Utility lambda to check the following criteria. + auto validatePathForArchiving = [](const std::string & path) + { + // Using the normalized path. + const std::string normPath = pystring::os::path::normpath(path); + if ( + // 1) Path may not be absolute. + pystring::os::path::isabs(normPath) || + // 2) Path may not start with double dot ".." (going above working directory). + pystring::startswith(normPath, "..") || + // 3) A context variable may not be located at the start of the path. + (ContainsContextVariables(path) && + (StringUtils::Find(path, "$") == 0 || + StringUtils::Find(path, "%") == 0))) + { + return false; + } + + return true; + }; + + /////////////////////////////// + // Search path verification. // + /////////////////////////////// + // Check that search paths are not absolute nor have context variables outside of config + // working directory. + int numSearchPaths = getNumSearchPaths(); + for (int i = 0; i < numSearchPaths; i++) + { + std::string currentPath = getSearchPath(i); + if (!validatePathForArchiving(currentPath)) + { + // Exit and return false. + return false; + } + } + + ///////////////////////////////// + // FileTransform verification. // + ///////////////////////////////// + ConstTransformVec allTransforms; + getImpl()->getAllInternalTransforms(allTransforms); + + std::set files; + for(const auto & transform : allTransforms) + { + GetFileReferences(files, transform); + } + + // Check that FileTransform sources are not absolute nor have context variables outside of + // config working directory. + for (const auto & path : files) + { + if (!validatePathForArchiving(path)) + { + // Exit and return false. + return false; + } + } + + return true; +} + +void Config::archive(std::ostream & ostream) const +{ + // Using utility functions in OCIOZArchive.cpp. + archiveConfig(ostream, *this, getCurrentContext()->getWorkingDir()); +} /////////////////////////////////////////////////////////////////////////// // Config::Impl @@ -5558,98 +5462,4 @@ void Config::Impl::checkVersionConsistency() const } } -void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) -{ - getImpl()->m_context->setConfigIOProxy(ciop); - - AutoMutex lock(getImpl()->m_cacheidMutex); - getImpl()->resetCacheIDs(); -} - -ConfigIOProxyRcPtr Config::getConfigIOProxy() const -{ - return getImpl()->m_context->getConfigIOProxy(); -} - -bool Config::isArchivable() const -{ - ConstContextRcPtr context = getCurrentContext(); - - // Current archive implementation needs a working directory to look for LUT files and - // working directory must be an absolute path. - const char * workingDirectory = getWorkingDir(); - if ((workingDirectory && !workingDirectory[0]) || !pystring::os::path::isabs(workingDirectory)) - { - return false; - } - - // Utility lambda to check the following criteria. - auto validatePathForArchiving = [](const std::string & path) - { - // Using the normalized path. - const std::string normPath = pystring::os::path::normpath(path); - if ( - // 1) Path may not be absolute. - pystring::os::path::isabs(normPath) || - // 2) Path may not start with double dot ".." (going above working directory). - pystring::startswith(normPath, "..") || - // 3) A context variable may not be located at the start of the path. - (ContainsContextVariables(path) && - (StringUtils::Find(path, "$") == 0 || - StringUtils::Find(path, "%") == 0))) - { - return false; - } - - return true; - }; - - /////////////////////////////// - // Search path verification. // - /////////////////////////////// - // Check that search paths are not absolute nor have context variables outside of config - // working directory. - int numSearchPaths = getNumSearchPaths(); - for (int i = 0; i < numSearchPaths; i++) - { - std::string currentPath = getSearchPath(i); - if (!validatePathForArchiving(currentPath)) - { - // Exit and return false. - return false; - } - } - - ///////////////////////////////// - // FileTransform verification. // - ///////////////////////////////// - ConstTransformVec allTransforms; - getImpl()->getAllInternalTransforms(allTransforms); - - std::set files; - for(const auto & transform : allTransforms) - { - GetFileReferences(files, transform); - } - - // Check that FileTransform sources are not absolute nor have context variables outside of - // config working directory. - for (const auto & path : files) - { - if (!validatePathForArchiving(path)) - { - // Exit and return false. - return false; - } - } - - return true; -} - -void Config::archive(std::ostream & ostream) const -{ - // Using utility functions in OCIOZArchive.cpp. - archiveConfig(ostream, *this, getCurrentContext()->getWorkingDir()); -} - } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp new file mode 100644 index 0000000000..a0038b7cd9 --- /dev/null +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#include "ConfigUtils.h" +#include "MathUtils.h" +#include "utils/StringUtils.h" + +namespace OCIO_NAMESPACE +{ + bool containsSRGB(ConstColorSpaceRcPtr & cs) + { + std::string name = StringUtils::Lower(cs->getName()); + if (StringUtils::Find(name, "srgb") != std::string::npos) + { + return true; + } + + size_t nbOfAliases = cs->getNumAliases(); + for (size_t i = 0; i < nbOfAliases; i++) + { + if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) + { + return true; + } + } + + return false; + } + + std::string getRefSpace(const Config & cfg) + { + // Find a color space where isData is false and it has neither a to_ref or from_ref + // transform. + auto nbCs = cfg.getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); + if (cs->isData()) + { + continue; + } + + auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (t != nullptr) + { + continue; + } + + t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (t != nullptr) + { + continue; + } + + return cs->getName(); + } + return ""; + } + + ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName) + { + // Build reference space of the given prims to sRGB transform. + std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; + + ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); + csTransform->setSrc(refColorSpaceName.c_str()); + csTransform->setDst(srgbColorSpaceName.c_str()); + + ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); + return proc; + } + + std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces) + { + // If the color space is a recognized linear space, return the reference space used by + // the config. + std::string refSpace; + bool toRefDirection = true; + auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (!srcTransform) + { + srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (srcTransform) + { + toRefDirection = false; + } + else + { + return ""; + } + } + + // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is + // enough of an identity. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + + // Generate matrices between all combinations of the Built-in linear color spaces. + // Then combine these with the transform from the current color space to see if the result is + // an identity. If so, then it identifies the reference space being used by the source config. + for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + { + for (size_t j = 0; j < builtinLinearSpaces.size(); j++) + { + if (i != j) + { + ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, + TRANSFORM_DIR_FORWARD); + + ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); + csTransform->setSrc(builtinLinearSpaces[j].c_str()); + csTransform->setDst(builtinLinearSpaces[i].c_str()); + + ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, + TRANSFORM_DIR_FORWARD); + + if (Processor::AreProcessorsEquivalent(p1, p2, &vals[0], 5, 1e-3f)) + { + if (toRefDirection) + { + refSpace = builtinLinearSpaces[j]; + } + else + { + refSpace = builtinLinearSpaces[i]; + } + + return refSpace; + } + } + } + } + + return ""; + } + + std::string getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces) + { + // If the color space is an sRGB texture space, return the reference space used by the config. + + // Get a transform in the to-reference direction. + ConstTransformRcPtr toRefTransform; + ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (ctransform) + { + toRefTransform = ctransform; + } + else + { + ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (ctransform) + { + TransformRcPtr transform = ctransform->createEditableCopy(); + transform->setDirection(TRANSFORM_DIR_INVERSE); + toRefTransform = transform; + } + else + { + // Both directions missing. + return ""; + } + } + + // First check if it has the right non-linearity. The objective is to fail quickly on color + // spaces that are definitely not sRGB before proceeding to the longer test of guessing the + // reference space primaries. + + // Break point is at 0.039286, so include at least one value below this. + std::vector vals = + { + 0.5f, 0.5f, 0.5f, + 0.03f, 0.03f, 0.03f, + 0.25f, 0.25f, 0.25f, + 0.75f, 0.75f, 0.75f, + 0.f, 0.f, 0.f, + 1.f, 1.f , 1.f + }; + std::vector out = vals; + + PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); + + ConstProcessorRcPtr proc = config.getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); + + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + // Apply the sRGB function (linear to non-lin). + // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. + if (out[i] <= 0.0030399346397784323f) + { + out[i] *= 12.923210180787857f; + } + else + { + out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; + } + + if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) + { + return ""; + } + } + + // + // Then try the various primaries for the reference space. + // + + // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact + // converting sRGB texture values to the candidate reference space. It includes 0.02 which is + // on the sRGB linear segment, color values, and neutral values. + vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f, }; + std::string refSpace = ""; + ConstProcessorRcPtr fromRefProc; + if (toRefTransform) + { + // The color space has the sRGB non-linearity. Now try combining the transform with a + // transform from the Built-in config that goes from a variety of reference spaces to an + // sRGB texture space. If the result is an identity, then that tells what the source config + // reference space is. + for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + { + fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); + + ConstProcessorRcPtr toRefProc = config.getProcessor(toRefTransform, + TRANSFORM_DIR_FORWARD); + + if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) + { + refSpace = builtinLinearSpaces[i]; + } + } + } + + return refSpace; + } +} \ No newline at end of file diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h new file mode 100644 index 0000000000..124fe1b316 --- /dev/null +++ b/src/OpenColorIO/ConfigUtils.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_COLORSPACE_UTILS_H +#define INCLUDED_OCIO_COLORSPACE_UTILS_H + +#include + +#include + +namespace OCIO_NAMESPACE +{ + // Return whether the color space contains SRGB or not. + bool containsSRGB(ConstColorSpaceRcPtr & cs); + + // Get color space where isData is false and it has neither a to_ref or from_ref transform. + std::string getRefSpace(const Config & cfg); + + // Get processor to a sRGB transform. + ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName); + + // Get reference space if the specified color space is a recognized linear space. + std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces); + + // Get reference space if the specified color space is an sRGB texture space. + std::string getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces); + +} // namespace OCIO_NAMESPACE + +#endif // INCLUDED_OCIO_BAKING_UTILS_H diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index dab0287dfb..72d24526de 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -15,6 +15,7 @@ #include "OpBuilders.h" #include "ops/noop/NoOps.h" #include "Processor.h" +#include "MathUtils.h" #include "TransformBuilder.h" #include "utils/StringUtils.h" @@ -218,6 +219,15 @@ ConstCPUProcessorRcPtr Processor::getOptimizedCPUProcessor(BitDepth inBitDepth, return getImpl()->getOptimizedCPUProcessor(inBitDepth, outBitDepth, oFlags); } +bool Processor::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance) +{ + return Processor::Impl::AreProcessorsEquivalent(p1, p2, rgbaValues, numValues, tolerance); +} + // Instantiate the cache with the right types. template class ProcessorCache; @@ -648,4 +658,34 @@ void Processor::Impl::computeMetadata() } } +bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance) +{ + ProcessorRcPtr proc = Processor::Create(); + + proc->getImpl()->concatenate(p1, p2); + + // RGBA values. + std::vector out(numValues*4); + + PackedImageDesc desc(rgbaValues, (long) numValues, 1, CHANNEL_ORDERING_RGBA); + PackedImageDesc descDst(&out[0], (long) numValues, 1, CHANNEL_ORDERING_RGBA); + + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + if (!EqualWithAbsError(rgbaValues[i], out[i], tolerance)) + { + return false; + } + } + + return true; +} + } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index 2bbddd9f2a..1835580c48 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -105,6 +105,13 @@ class Processor::Impl void computeMetadata(); + // Returns whether the specified processors are equivalents. + static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance); + protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, OptimizationFlags oFlags) const; diff --git a/src/OpenColorIO/Transform.cpp b/src/OpenColorIO/Transform.cpp index 7f4d7fddbc..0d723212e6 100755 --- a/src/OpenColorIO/Transform.cpp +++ b/src/OpenColorIO/Transform.cpp @@ -21,7 +21,6 @@ #include "ops/lut3d/Lut3DOp.h" #include "ops/matrix/MatrixOp.h" #include "ops/range/RangeOp.h" -#include "Processor.h" #include "TransformBuilder.h" diff --git a/src/OpenColorIO/fileformats/FileFormatICC.cpp b/src/OpenColorIO/fileformats/FileFormatICC.cpp index 786c8a5220..1fcfd9adcf 100755 --- a/src/OpenColorIO/fileformats/FileFormatICC.cpp +++ b/src/OpenColorIO/fileformats/FileFormatICC.cpp @@ -14,6 +14,7 @@ #include "ops/lut1d/Lut1DOp.h" #include "ops/matrix/MatrixOp.h" #include "ops/range/RangeOp.h" +#include "Platform.h" #include "pystring/pystring.h" #include "transforms/FileTransform.h" diff --git a/src/OpenColorIO/transforms/FileTransform.h b/src/OpenColorIO/transforms/FileTransform.h index 8754e77978..db737b8bd8 100644 --- a/src/OpenColorIO/transforms/FileTransform.h +++ b/src/OpenColorIO/transforms/FileTransform.h @@ -13,7 +13,6 @@ #include "Op.h" #include "ops/noop/NoOps.h" #include "PrivateTypes.h" -#include "Processor.h" #include "utils/StringUtils.h" diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index e78a86bede..ade3efe0b8 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -78,6 +78,7 @@ endfunction(add_ocio_test) set(SOURCES builtinconfigs/CGConfig.cpp builtinconfigs/StudioConfig.cpp + ConfigUtils.cpp fileformats/cdl/CDLParser.cpp fileformats/cdl/CDLReaderHelper.cpp fileformats/cdl/CDLWriter.cpp diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index fc3a6581a6..e5e13f23e3 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1175,4 +1175,106 @@ ocio_profile_version: 2 srcColorSpaceName.c_str()); checkProcessorInverse(proc); } + + OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); + { + const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + + } + + { + // Texture -- sRGB + const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + } +} + +OCIO_ADD_TEST(Processor, identify_colorspace) +{ + constexpr const char * CONFIG { R"( +ocio_profile_version: 2 + +roles: + default: raw + scene_linear: ref_cs + +colorspaces: + - ! + name: raw + description: A data colorspace (should not be used). + isdata: true + + - ! + name: ref_cs + description: The reference colorspace. + isdata: false + + - ! + name: not sRGB + description: A color space that misleadingly has sRGB in the name, even though it's not. + isdata: false + to_scene_reference: ! {style: ACEScct_to_ACES2065-1} + + - ! + name: ACES cg + description: An ACEScg space with an unusual spelling. + isdata: false + to_scene_reference: ! {style: ACEScg_to_ACES2065-1} + + - ! + name: Linear ITU-R BT.709 + description: A linear Rec.709 space with an unusual spelling. + isdata: false + from_scene_reference: ! + name: AP0 to Linear Rec.709 (sRGB) + children: + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + + - ! + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. + isdata: false + from_scene_reference: ! + name: AP0 to sRGB Rec.709 + children: + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + + - ! + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + isdata: false + from_scene_reference: ! + name: AP0 to sRGB Encoded AP1 - Texture + children: + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + +)" }; + + std::istringstream is; + is.str(CONFIG); + OCIO::ConstConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + + { + const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + + } + + { + const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + } + + { + char srcInterchange[255]; + char builtinInterchange[255]; + + cfg->identifyInterchangeSpace(srcInterchange, builtinInterchange); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); + } } \ No newline at end of file From c1c2c3ad12195f86556c378193018767e0823c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 11 Jan 2023 11:57:03 -0500 Subject: [PATCH 02/22] Moving code from Config.cpp to ConfigUtils.cpp and using wrapper in Config.cpp instead. Fixing identations issues. Fixing styling issues. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/Config.cpp | 258 ++----------------------------- src/OpenColorIO/ConfigUtils.cpp | 262 +++++++++++++++++++++++++++++++- src/OpenColorIO/ConfigUtils.h | 23 ++- src/OpenColorIO/Processor.cpp | 10 +- src/OpenColorIO/Processor.h | 2 +- tests/cpu/ColorSpace_tests.cpp | 72 +-------- 6 files changed, 298 insertions(+), 329 deletions(-) diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 1483529f3e..5c52c2d2e1 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -1096,10 +1096,10 @@ class Config::Impl return -1; } - static ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName, - TransformDirection direction) + static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName, + TransformDirection direction) { // Use the Default config as the Built-in config to interpret the known color space name. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); @@ -2791,254 +2791,17 @@ void Config::clearColorSpaces() bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const { - auto cs = getColorSpace(colorSpace); - - if (cs->isData()) - { - return false; - } - - // Colorspace is not linear if the types are opposite. - if (cs->getReferenceSpaceType() != referenceSpaceType) - { - return false; - } - - std::string encoding = cs->getEncoding(); - if (!encoding.empty()) - { - // Check the encoding value if it is set. - if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && - referenceSpaceType == REFERENCE_SPACE_SCENE) || - (StringUtils::Compare(cs->getEncoding(), "display-linear") && - referenceSpaceType == REFERENCE_SPACE_DISPLAY)) - { - return true; - } - else - { - return false; - } - } - - // We want to assess linearity over at least a reasonable range of values, so use a very dark - // value and a very bright value. Test neutral, red, green, and blue points to detect situations - // where the neutral may be linear but there is non-linearity off the neutral axis. - auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool - { - std::vector img = - { - 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, - 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, - 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, - 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f - }; - std::vector dst = - { - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f - }; - - PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); - - auto procToReference = config.getImpl()->getProcessorWithoutCaching( - config, t, TRANSFORM_DIR_FORWARD - ); - auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - optCPUProc->apply(desc, descDst); - - - float absError = 1e-5f; - float multiplier = 64.f; - bool ret = true; - - // Test the first RGB pair. - ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); - ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); - ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); - - // Test the second RGB pair. - ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); - ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); - ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); - - // Test the third RGB pair. - ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); - ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); - ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); - - // Test the fourth RGB pair. - ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); - ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); - ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); - - return ret; - }; - - ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if ((transformToReference && transformFromReference) || transformToReference) - { - // Color space has a transform for the to-reference direction, or both directions. - return evaluate(*this, transformToReference); - } - else if (transformFromReference) - { - // Color space only has a transform for the from-reference direction. - return evaluate(*this, transformFromReference); - } - - // Color space matches the desired reference space type, is not a data space, and has no - // transforms, so it is equivalent to the reference space and hence linear. - return true; + return ConfigUtils::isColorSpaceLinear(colorSpace, referenceSpaceType, *this); } void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(*eSrcConfig); - if (refColorSpaceName.empty()) - { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); - } - - // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = eSrcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) - { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - - if (refColorSpacePrims.empty()) - { - // Check for a linear space with known primaries. - nbCs = eSrcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - } - - if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) - { - // Copy interchange role from source config. - std::memcpy(srcInterchange, refColorSpaceName.c_str(), refColorSpaceName.size()); - // Copy interchange role from built-in config. - std::memcpy(builtinInterchange, refColorSpacePrims.c_str(), refColorSpacePrims.size()); - - // Terminate the string. - srcInterchange[refColorSpaceName.size()] = '\0'; - builtinInterchange[refColorSpacePrims.size()] = '\0'; - } - else - { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } + ConfigUtils::identifyInterchangeSpace(srcInterchange, builtinInterchange, *this); } const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } - - char srcInterchange[255]; - char builtinInterchange[255]; - - // Identify interchange space. - identifyInterchangeSpace(srcInterchange, builtinInterchange); - - // Get processor from that space to the built-in color space. - ConstProcessorRcPtr builtinProc; - if (builtinInterchange && builtinInterchange[0]) - { - builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinInterchange, - builtinColorSpaceName); - } - - if (builtinProc && srcInterchange && srcInterchange[0]) - { - // Iterate over each color space in the source config. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; - int nbCs = getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - // Get processor from that space to its reference and then use isProcessorEquivalent. - // If equivalent, return that color space name. - - ConstColorSpaceRcPtr cs = getColorSpace(getColorSpaceNameByIndex(i)); - const char * csName = cs->getName(); - - ConstProcessorRcPtr proc = getProcessor(getCurrentContext(), - csName, - srcInterchange); - - if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) - { - return csName; - } - } - } - - return ""; + return ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *this); } /////////////////////////////////////////////////////////////////////////// @@ -4755,7 +4518,7 @@ ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr src const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return Config::Impl::getProcessorToBuiltinCS(srcConfig, + return Config::Impl::GetProcessorToBuiltinCS(srcConfig, srcColorSpaceName, builtinColorSpaceName, TRANSFORM_DIR_FORWARD); @@ -4765,7 +4528,7 @@ ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * built ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return Config::Impl::getProcessorToBuiltinCS(srcConfig, + return Config::Impl::GetProcessorToBuiltinCS(srcConfig, srcColorSpaceName, builtinColorSpaceName, TRANSFORM_DIR_INVERSE); @@ -4873,6 +4636,9 @@ void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept getImpl()->setProcessorCacheFlags(flags); } +////////////////////////////////////////////////////////////////// +// ConfigIOProxy and Archiving + void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) { getImpl()->m_context->setConfigIOProxy(ciop); diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index a0038b7cd9..6029ac1d37 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -6,6 +6,8 @@ #include "utils/StringUtils.h" namespace OCIO_NAMESPACE +{ +namespace ConfigUtils { bool containsSRGB(ConstColorSpaceRcPtr & cs) { @@ -105,15 +107,13 @@ namespace OCIO_NAMESPACE // Generate matrices between all combinations of the Built-in linear color spaces. // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. + ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); for (size_t i = 0; i < builtinLinearSpaces.size(); i++) { for (size_t j = 0; j < builtinLinearSpaces.size(); j++) { if (i != j) { - ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, - TRANSFORM_DIR_FORWARD); - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); csTransform->setSrc(builtinLinearSpaces[j].c_str()); csTransform->setDst(builtinLinearSpaces[i].c_str()); @@ -250,4 +250,260 @@ namespace OCIO_NAMESPACE return refSpace; } + + bool isColorSpaceLinear(const char * colorSpace, + ReferenceSpaceType referenceSpaceType, + const Config & cfg) + { + auto cs = cfg.getColorSpace(colorSpace); + + if (cs->isData()) + { + return false; + } + + // Colorspace is not linear if the types are opposite. + if (cs->getReferenceSpaceType() != referenceSpaceType) + { + return false; + } + + std::string encoding = cs->getEncoding(); + if (!encoding.empty()) + { + // Check the encoding value if it is set. + if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && + referenceSpaceType == REFERENCE_SPACE_SCENE) || + (StringUtils::Compare(cs->getEncoding(), "display-linear") && + referenceSpaceType == REFERENCE_SPACE_DISPLAY)) + { + return true; + } + else + { + return false; + } + } + + // We want to assess linearity over at least a reasonable range of values, so use a very dark + // value and a very bright value. Test neutral, red, green, and blue points to detect situations + // where the neutral may be linear but there is non-linearity off the neutral axis. + auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool + { + std::vector img = + { + 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, + 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, + 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, + 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f + }; + std::vector dst = + { + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f + }; + + PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); + + // Create an editable copy to avoir filling the processor cache. + ConfigRcPtr eConfig = config.createEditableCopy(); + eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + auto procToReference = eConfig->getProcessor(t, TRANSFORM_DIR_FORWARD); + auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + optCPUProc->apply(desc, descDst); + + float absError = 1e-5f; + float multiplier = 64.f; + bool ret = true; + + // Test the first RGB pair. + ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); + ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); + ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); + + // Test the second RGB pair. + ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); + ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); + ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); + + // Test the third RGB pair. + ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); + ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); + ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); + + // Test the fourth RGB pair. + ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); + ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); + ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); + + return ret; + }; + + ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if ((transformToReference && transformFromReference) || transformToReference) + { + // Color space has a transform for the to-reference direction, or both directions. + return evaluate(cfg, transformToReference); + } + else if (transformFromReference) + { + // Color space only has a transform for the from-reference direction. + return evaluate(cfg, transformFromReference); + } + + // Color space matches the desired reference space type, is not a data space, and has no + // transforms, so it is equivalent to the reference space and hence linear. + return true; + } + + void identifyInterchangeSpace(char * srcInterchange, + char * builtinInterchange, + const Config & cfg) + { + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = cfg.createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + std::vector builtinLinearSpaces = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Get the name of (one of) the reference spaces. + std::string refColorSpaceName = getRefSpace(*eSrcConfig); + if (refColorSpaceName.empty()) + { + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } + + // Check for an sRGB texture space. + std::string refColorSpacePrims = ""; + int nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) + { + refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + + if (refColorSpacePrims.empty()) + { + // Check for a linear space with known primaries. + nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + { + refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + } + + if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + { + // Copy interchange role from source config. + snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); + // Copy interchange role from built-in config. + snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } + } + + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg) + { + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + char srcInterchange[255]; + char builtinInterchange[255]; + + // Identify interchange space. + identifyInterchangeSpace(srcInterchange, builtinInterchange, cfg); + + // Get processor from that space to the built-in color space. + ConstProcessorRcPtr builtinProc; + if (builtinInterchange && builtinInterchange[0]) + { + builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), + builtinInterchange, + builtinColorSpaceName); + } + + if (builtinProc && srcInterchange && srcInterchange[0]) + { + // Iterate over each color space in the source config. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + int nbCs = cfg.getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + // Get processor from that space to its reference and then use isProcessorEquivalent. + // If equivalent, return that color space name. + + ConstColorSpaceRcPtr cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); + const char * csName = cs->getName(); + + ConstProcessorRcPtr proc = cfg.getProcessor(cfg.getCurrentContext(), + csName, + srcInterchange); + + if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + { + return csName; + } + } + } + + return ""; + } +} + } \ No newline at end of file diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 124fe1b316..70a6e9b751 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -1,14 +1,17 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. -#ifndef INCLUDED_OCIO_COLORSPACE_UTILS_H -#define INCLUDED_OCIO_COLORSPACE_UTILS_H +#ifndef INCLUDED_OCIO_CONFIG_UTILS_H +#define INCLUDED_OCIO_CONFIG_UTILS_H #include #include namespace OCIO_NAMESPACE +{ + +namespace ConfigUtils { // Return whether the color space contains SRGB or not. bool containsSRGB(ConstColorSpaceRcPtr & cs); @@ -31,7 +34,21 @@ namespace OCIO_NAMESPACE const ConstColorSpaceRcPtr cs, const ConstConfigRcPtr & builtinConfig, std::vector & builtinLinearSpaces); + // Returns true if the specified color space is linear. + bool isColorSpaceLinear(const char * colorSpace, + ReferenceSpaceType referenceSpaceType, + const Config & cfg); + + // Identify the interchange space of the source config and the default built-in config. + void identifyInterchangeSpace(char * srcInterchange, + char * builtinInterchange, + const Config & cfg); + + // Find the name of the color space in the source config that is the same as + // a color space in the default built-in config. + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg); +} } // namespace OCIO_NAMESPACE -#endif // INCLUDED_OCIO_BAKING_UTILS_H +#endif // INCLUDED_OCIO_CONFIG_UTILS_H diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index 72d24526de..bfbe6d0f9a 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -659,10 +659,10 @@ void Processor::Impl::computeMetadata() } bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance) + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float absTolerance) { ProcessorRcPtr proc = Processor::Create(); @@ -679,7 +679,7 @@ bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, for (size_t i = 0; i < out.size(); i++) { - if (!EqualWithAbsError(rgbaValues[i], out[i], tolerance)) + if (!EqualWithAbsError(rgbaValues[i], out[i], absTolerance)) { return false; } diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index 1835580c48..cad0e6c577 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -110,7 +110,7 @@ class Processor::Impl ConstProcessorRcPtr & p2, float * rgbaValues, size_t numValues, - float tolerance); + float absTolerance); protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index e5e13f23e3..d4a60f036e 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1176,11 +1176,9 @@ ocio_profile_version: 2 checkProcessorInverse(proc); } - OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); - } { @@ -1188,76 +1186,8 @@ ocio_profile_version: 2 const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } -} - -OCIO_ADD_TEST(Processor, identify_colorspace) -{ - constexpr const char * CONFIG { R"( -ocio_profile_version: 2 - -roles: - default: raw - scene_linear: ref_cs - -colorspaces: - - ! - name: raw - description: A data colorspace (should not be used). - isdata: true - - - ! - name: ref_cs - description: The reference colorspace. - isdata: false - - - ! - name: not sRGB - description: A color space that misleadingly has sRGB in the name, even though it's not. - isdata: false - to_scene_reference: ! {style: ACEScct_to_ACES2065-1} - - - ! - name: ACES cg - description: An ACEScg space with an unusual spelling. - isdata: false - to_scene_reference: ! {style: ACEScg_to_ACES2065-1} - - - ! - name: Linear ITU-R BT.709 - description: A linear Rec.709 space with an unusual spelling. - isdata: false - from_scene_reference: ! - name: AP0 to Linear Rec.709 (sRGB) - children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - - - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. - isdata: false - from_scene_reference: ! - name: AP0 to sRGB Rec.709 - children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - - ! {gamma: 2.4, offset: 0.055, direction: inverse} - - - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. - isdata: false - from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture - children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - - ! {gamma: 2.4, offset: 0.055, direction: inverse} - -)" }; - - std::istringstream is; - is.str(CONFIG); - OCIO::ConstConfigRcPtr cfg; - OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + // identify_colorspace tests { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); From 5227973fc7cc80e5d86ac57f040de0fb1a6e798d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Thu, 12 Jan 2023 09:49:48 -0500 Subject: [PATCH 03/22] Typo and comments tweaks Moving the creation of the editable config into the wrapper of isColorSpaceLinear instead of doing it inside the function. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 6 ++-- src/OpenColorIO/Config.cpp | 6 +++- src/OpenColorIO/ConfigUtils.cpp | 52 +++++++++++++++++++++---------- src/OpenColorIO/ConfigUtils.h | 2 +- tests/cpu/ColorSpace_tests.cpp | 6 ++-- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index d62fadf859..e9c57b8b78 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -693,8 +693,8 @@ class OCIOEXPORT Config /** * \brief Identify the interchange space of the source config and the default built-in config. * - * \param srcInterchange Interchange space from the source config (output). - * \param builtinInterchange Interchange space from the default built-in config (output). + * \param[out] srcInterchange Interchange space from the source config (output). + * \param[out] builtinInterchange Interchange space from the default built-in config (output). * * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. */ @@ -704,7 +704,7 @@ class OCIOEXPORT Config * a color space in the default built-in config. * * \param builtinColorSpaceName Color space name in the built-in config. - * \return const char* Matching color space from the source config. Empty if not found. + * \return Matching color space from the source config. Empty if not found. */ const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 241e33402d..ce4fda6d13 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -2791,7 +2791,11 @@ void Config::clearColorSpaces() bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const { - return ConfigUtils::isColorSpaceLinear(colorSpace, referenceSpaceType, *this); + // Create an editable copy to avoir filling the processor cache. + ConfigRcPtr eConfig = createEditableCopy(); + eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + return ConfigUtils::isColorSpaceLinear(colorSpace, referenceSpaceType, *eConfig); } void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 6029ac1d37..295824d02c 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -253,9 +253,9 @@ namespace ConfigUtils bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType, - const Config & cfg) + Config & editableCfg { - auto cs = cfg.getColorSpace(colorSpace); + auto cs = editableCfg.getColorSpace(colorSpace); if (cs->isData()) { @@ -307,12 +307,8 @@ namespace ConfigUtils PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); - - // Create an editable copy to avoir filling the processor cache. - ConfigRcPtr eConfig = config.createEditableCopy(); - eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - auto procToReference = eConfig->getProcessor(t, TRANSFORM_DIR_FORWARD); + auto procToReference = config.getProcessor(t, TRANSFORM_DIR_FORWARD); auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); optCPUProc->apply(desc, descDst); @@ -348,12 +344,12 @@ namespace ConfigUtils if ((transformToReference && transformFromReference) || transformToReference) { // Color space has a transform for the to-reference direction, or both directions. - return evaluate(cfg, transformToReference); + return evaluate(editableCfg, transformToReference); } else if (transformFromReference) { // Color space only has a transform for the from-reference direction. - return evaluate(cfg, transformFromReference); + return evaluate(editableCfg, transformFromReference); } // Color space matches the desired reference space type, is not a data space, and has no @@ -421,9 +417,9 @@ namespace ConfigUtils if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. if (!refColorSpacePrims.empty()) break; } @@ -432,10 +428,34 @@ namespace ConfigUtils if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) { - // Copy interchange role from source config. - snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); - // Copy interchange role from built-in config. - snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + // Checking if the size of the role name + null terminated characters is small enough + // to fix into the char array. + + if (refColorSpaceName.size()+1 > 255) + { + // Copy interchange space from source config. + snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); + } + else + { + std::ostringstream os; + os << "The interchange space was found, but the name is too big: " + << refColorSpaceName; + throw Exception(os.str().c_str()); + } + + if (refColorSpacePrims.size()+1 > 255) + { + // Copy interchange space from built-in config. + snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + } + else + { + std::ostringstream os; + os << "The interchange space from the built-in config was found, but the name is " + << "too big: " << refColorSpacePrims; + throw Exception(os.str().c_str()); + } } else { diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 70a6e9b751..9ac7209463 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -37,7 +37,7 @@ namespace ConfigUtils // Returns true if the specified color space is linear. bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType, - const Config & cfg); + Config & cfg); // Identify the interchange space of the source config and the default built-in config. void identifyInterchangeSpace(char * srcInterchange, diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index c9bd511494..eb11d79a44 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1189,22 +1189,21 @@ ocio_profile_version: 2 checkProcessorInverse(proc); } + // identifyBuiltinColorSpace tests. { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } { - // Texture -- sRGB const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } - // identify_colorspace tests { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); - } { @@ -1212,6 +1211,7 @@ ocio_profile_version: 2 OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } + // identifyInterchangeSpace tests. { char srcInterchange[255]; char builtinInterchange[255]; From bd96bbb3d16d4528b829b238f3952bf541527b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Mon, 16 Jan 2023 09:46:43 -0500 Subject: [PATCH 04/22] Splitting identifyInterchangeSpace into two methods, one for source config and one for builtin. Adding back isColorSpaceLinear inside Config.cpp since we need access to getProcessorWithoutCaching. Refactor some of config utils function to return index instead of string. Creating editable config in the two wrappers to prevent processor caching. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 4 +- src/OpenColorIO/Config.cpp | 302 ++++++++++++++++++++--------- src/OpenColorIO/ConfigUtils.cpp | 311 ++++++++++-------------------- src/OpenColorIO/ConfigUtils.h | 30 ++- tests/cpu/ColorSpace_tests.cpp | 6 +- 5 files changed, 335 insertions(+), 318 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index e9c57b8b78..08a2108740 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -698,7 +698,9 @@ class OCIOEXPORT Config * * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. */ - void identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const; + const char * identifyInterchangeSpace() const; + const char * identifyBuiltinInterchangeSpace() const; + /** * \brief Find the name of the color space in the source config that is the same as * a color space in the default built-in config. diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index ce4fda6d13..24d6b51672 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -1095,79 +1095,6 @@ class Config::Impl // That should never happen. return -1; } - - static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName, - TransformDirection direction) - { - // Use the Default config as the Built-in config to interpret the known color space name. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } - - // If both configs have the interchange roles set, then it's easy. - try - { - ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - builtinConfig, - builtinColorSpaceName); - return proc; - } - catch(const Exception & e) - { - std::string str1 = "The role 'aces_interchange' is missing in the source config"; - std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; - - // Re-throw when the error is not about interchange roles. - if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) - { - throw Exception(e.what()); - } - // otherwise, do nothing and continue. - } - - char srcInterchange[255]; - char builtinInterchange[255]; - - srcConfig->identifyInterchangeSpace(srcInterchange, builtinInterchange); - - if (builtinInterchange && builtinInterchange[0]) - { - ConstProcessorRcPtr proc; - if (direction == TRANSFORM_DIR_FORWARD) - { - proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - srcInterchange, - builtinConfig, - builtinColorSpaceName, - builtinInterchange); - } - else if (direction == TRANSFORM_DIR_INVERSE) - { - proc = Config::GetProcessorFromConfigs(builtinConfig, - builtinColorSpaceName, - builtinInterchange, - srcConfig, - srcColorSpaceName, - srcInterchange); - } - return proc; - } - - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } }; @@ -2791,21 +2718,149 @@ void Config::clearColorSpaces() bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const { - // Create an editable copy to avoir filling the processor cache. - ConfigRcPtr eConfig = createEditableCopy(); - eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + auto cs = getColorSpace(colorSpace); + + if (cs == nullptr) + { + std::ostringstream os; + os << "Could not test colorspace linearity. Colorspace " << colorSpace << " does not exist."; + throw Exception(os.str().c_str()); + } + + if (cs->isData()) + { + return false; + } + + // Colorspace is not linear if the types are opposite. + if (cs->getReferenceSpaceType() != referenceSpaceType) + { + return false; + } + + std::string encoding = cs->getEncoding(); + if (!encoding.empty()) + { + // Check the encoding value if it is set. + if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && + referenceSpaceType == REFERENCE_SPACE_SCENE) || + (StringUtils::Compare(cs->getEncoding(), "display-linear") && + referenceSpaceType == REFERENCE_SPACE_DISPLAY)) + { + return true; + } + else + { + return false; + } + } + + // We want to assess linearity over at least a reasonable range of values, so use a very dark + // value and a very bright value. Test neutral, red, green, and blue points to detect situations + // where the neutral may be linear but there is non-linearity off the neutral axis. + auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool + { + std::vector img = + { + 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, + 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, + 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, + 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f + }; + std::vector dst = + { + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f + }; + + PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); + + auto procToReference = config.getImpl()->getProcessorWithoutCaching( + config, t, TRANSFORM_DIR_FORWARD + ); + auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + optCPUProc->apply(desc, descDst); + + float absError = 1e-5f; + float multiplier = 64.f; + bool ret = true; + + // Test the first RGB pair. + ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); + ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); + ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); + + // Test the second RGB pair. + ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); + ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); + ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); + + // Test the third RGB pair. + ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); + ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); + ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); + + // Test the fourth RGB pair. + ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); + ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); + ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); + + return ret; + }; + + ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if ((transformToReference && transformFromReference) || transformToReference) + { + // Color space has a transform for the to-reference direction, or both directions. + return evaluate(*this, transformToReference); + } + else if (transformFromReference) + { + // Color space only has a transform for the from-reference direction. + return evaluate(*this, transformFromReference); + } - return ConfigUtils::isColorSpaceLinear(colorSpace, referenceSpaceType, *eConfig); + // Color space matches the desired reference space type, is not a data space, and has no + // transforms, so it is equivalent to the reference space and hence linear. + return true; } -void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const +const char * Config::identifyInterchangeSpace() const +{ + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int srcInterchangeIndex = -1; + ConfigUtils::identifyInterchangeSpace(srcInterchangeIndex, *eSrcConfig); + + return getColorSpaceNameByIndex(srcInterchangeIndex); +} + +const char * Config::identifyBuiltinInterchangeSpace() const { - ConfigUtils::identifyInterchangeSpace(srcInterchange, builtinInterchange, *this); + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int builtinInterchangeIndex = -1; + ConfigUtils::identifyBuiltinInterchangeSpace(builtinInterchangeIndex, *eSrcConfig); + + return ConfigUtils::getBuiltinLinearSpaces(builtinInterchangeIndex); } const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const { - return ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *this); + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int csIndex = ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *eSrcConfig); + return getColorSpaceNameByIndex(csIndex); } /////////////////////////////////////////////////////////////////////////// @@ -4518,24 +4573,95 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr return processor; } +static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName, + TransformDirection direction) +{ + // Use the Default config as the Built-in config to interpret the known color space name. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + // If both configs have the interchange roles set, then it's easy. + try + { + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); + return proc; + } + catch(const Exception & e) + { + std::string str1 = "The role 'aces_interchange' is missing in the source config"; + std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; + + // Re-throw when the error is not about interchange roles. + if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) + { + throw Exception(e.what()); + } + // otherwise, do nothing and continue. + } + + const char * srcInterchange = srcConfig->identifyInterchangeSpace(); + const char * builtinInterchange = srcConfig->identifyBuiltinInterchangeSpace(); + + if (builtinInterchange && builtinInterchange[0]) + { + ConstProcessorRcPtr proc; + if (direction == TRANSFORM_DIR_FORWARD) + { + proc = Config::GetProcessorFromConfigs(srcConfig, + srcColorSpaceName, + srcInterchange, + builtinConfig, + builtinColorSpaceName, + builtinInterchange); + } + else if (direction == TRANSFORM_DIR_INVERSE) + { + proc = Config::GetProcessorFromConfigs(builtinConfig, + builtinColorSpaceName, + builtinInterchange, + srcConfig, + srcColorSpaceName, + srcInterchange); + } + return proc; + } + + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); +} + ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr srcConfig, const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return Config::Impl::GetProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_FORWARD); + return GetProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_FORWARD); } ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return Config::Impl::GetProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_INVERSE); + return GetProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_INVERSE); } std::ostream& operator<< (std::ostream& os, const Config& config) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 295824d02c..edd68b067b 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -7,8 +7,24 @@ namespace OCIO_NAMESPACE { + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + constexpr int numberOfbuiltinLinearSpaces = 5; + constexpr const char * builtinLinearSpaces[] = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + namespace ConfigUtils { + const char * getBuiltinLinearSpaces(int index) + { + return builtinLinearSpaces[index]; + } + bool containsSRGB(ConstColorSpaceRcPtr & cs) { std::string name = StringUtils::Lower(cs->getName()); @@ -29,7 +45,7 @@ namespace ConfigUtils return false; } - std::string getRefSpace(const Config & cfg) + int getRefSpace(const Config & cfg) { // Find a color space where isData is false and it has neither a to_ref or from_ref // transform. @@ -54,9 +70,9 @@ namespace ConfigUtils continue; } - return cs->getName(); + return i; } - return ""; + return -1; } ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, @@ -73,14 +89,14 @@ namespace ConfigUtils return proc; } - std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + int getReferenceSpaceFromLinearSpace(const Config & srcConfig, const ConstColorSpaceRcPtr & cs, const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) + const char * const * builtinLinearSpaces) { // If the color space is a recognized linear space, return the reference space used by // the config. - std::string refSpace; + int refSpaceIndex = -1; bool toRefDirection = true; auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); if (!srcTransform) @@ -92,7 +108,7 @@ namespace ConfigUtils } else { - return ""; + return -1; } } @@ -108,15 +124,15 @@ namespace ConfigUtils // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { - for (size_t j = 0; j < builtinLinearSpaces.size(); j++) + for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) { if (i != j) { ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j].c_str()); - csTransform->setDst(builtinLinearSpaces[i].c_str()); + csTransform->setSrc(builtinLinearSpaces[j]); + csTransform->setDst(builtinLinearSpaces[i]); ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); @@ -125,26 +141,26 @@ namespace ConfigUtils { if (toRefDirection) { - refSpace = builtinLinearSpaces[j]; + refSpaceIndex = (int) j; } else { - refSpace = builtinLinearSpaces[i]; + refSpaceIndex = (int) i; } - return refSpace; + return refSpaceIndex; } } } } - return ""; + return -1; } - std::string getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) + int getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + const char * const * builtinLinearSpaces) { // If the color space is an sRGB texture space, return the reference space used by the config. @@ -167,7 +183,7 @@ namespace ConfigUtils else { // Both directions missing. - return ""; + return -1; } } @@ -210,7 +226,7 @@ namespace ConfigUtils if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) { - return ""; + return -1; } } @@ -226,7 +242,7 @@ namespace ConfigUtils 0.3f, 0.02f, 0.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f, }; - std::string refSpace = ""; + int refSpaceIndex = -1; ConstProcessorRcPtr fromRefProc; if (toRefTransform) { @@ -234,7 +250,7 @@ namespace ConfigUtils // transform from the Built-in config that goes from a variety of reference spaces to an // sRGB texture space. If the result is an identity, then that tells what the source config // reference space is. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); @@ -243,219 +259,100 @@ namespace ConfigUtils if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) { - refSpace = builtinLinearSpaces[i]; + refSpaceIndex = (int) i; + break; } } } - return refSpace; + return refSpaceIndex; } - bool isColorSpaceLinear(const char * colorSpace, - ReferenceSpaceType referenceSpaceType, - Config & editableCfg + void identifyInterchangeSpace(int & srcInterchangeIndex, Config & eSrcConfig) { - auto cs = editableCfg.getColorSpace(colorSpace); - - if (cs->isData()) - { - return false; - } - - // Colorspace is not linear if the types are opposite. - if (cs->getReferenceSpaceType() != referenceSpaceType) + if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) { - return false; + std::ostringstream os; + os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; + throw Exception(os.str().c_str()); } - std::string encoding = cs->getEncoding(); - if (!encoding.empty()) + // Get the name of (one of) the reference spaces. + int refColorSpaceIndex = getRefSpace(eSrcConfig); + if (refColorSpaceIndex < 0) { - // Check the encoding value if it is set. - if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && - referenceSpaceType == REFERENCE_SPACE_SCENE) || - (StringUtils::Compare(cs->getEncoding(), "display-linear") && - referenceSpaceType == REFERENCE_SPACE_DISPLAY)) - { - return true; - } - else - { - return false; - } + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); } - // We want to assess linearity over at least a reasonable range of values, so use a very dark - // value and a very bright value. Test neutral, red, green, and blue points to detect situations - // where the neutral may be linear but there is non-linearity off the neutral axis. - auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool - { - std::vector img = - { - 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, - 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, - 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, - 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f - }; - std::vector dst = - { - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f - }; - - PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); - - auto procToReference = config.getProcessor(t, TRANSFORM_DIR_FORWARD); - auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - optCPUProc->apply(desc, descDst); - - float absError = 1e-5f; - float multiplier = 64.f; - bool ret = true; - - // Test the first RGB pair. - ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); - ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); - ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); - - // Test the second RGB pair. - ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); - ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); - ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); - - // Test the third RGB pair. - ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); - ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); - ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); - - // Test the fourth RGB pair. - ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); - ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); - ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); - - return ret; - }; - - ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if ((transformToReference && transformFromReference) || transformToReference) + if (refColorSpaceIndex > -1) { - // Color space has a transform for the to-reference direction, or both directions. - return evaluate(editableCfg, transformToReference); + srcInterchangeIndex = refColorSpaceIndex; } - else if (transformFromReference) + else { - // Color space only has a transform for the from-reference direction. - return evaluate(editableCfg, transformFromReference); + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); } - - // Color space matches the desired reference space type, is not a data space, and has no - // transforms, so it is equivalent to the reference space and hence linear. - return true; } - void identifyInterchangeSpace(char * srcInterchange, - char * builtinInterchange, - const Config & cfg) + void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig) { // Use the Default config as the Built-in config. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = cfg.createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(*eSrcConfig); - if (refColorSpaceName.empty()) + if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) { std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; + os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; throw Exception(os.str().c_str()); } + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = eSrcConfig->getNumColorSpaces(); + int refColorSpacePrimsIndex = -1; + int nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + ConstColorSpaceRcPtr cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); if (containsSRGB(cs)) { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; + if (refColorSpacePrimsIndex > -1) break; } } - if (refColorSpacePrims.empty()) + if (refColorSpacePrimsIndex < 0) { // Check for a linear space with known primaries. - nbCs = eSrcConfig->getNumColorSpaces(); + nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + auto cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); + if (eSrcConfig.isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; + if (refColorSpacePrimsIndex > -1) break; } } } - if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + if (refColorSpacePrimsIndex > -1) { - // Checking if the size of the role name + null terminated characters is small enough - // to fix into the char array. - - if (refColorSpaceName.size()+1 > 255) - { - // Copy interchange space from source config. - snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); - } - else - { - std::ostringstream os; - os << "The interchange space was found, but the name is too big: " - << refColorSpaceName; - throw Exception(os.str().c_str()); - } - - if (refColorSpacePrims.size()+1 > 255) - { - // Copy interchange space from built-in config. - snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); - } - else - { - std::ostringstream os; - os << "The interchange space from the built-in config was found, but the name is " - << "too big: " << refColorSpacePrims; - throw Exception(os.str().c_str()); - } + builtinInterchangeIndex = refColorSpacePrimsIndex; } else { @@ -466,7 +363,7 @@ namespace ConfigUtils } } - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg) + int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig) { // Use the Default config as the Built-in config. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); @@ -479,22 +376,22 @@ namespace ConfigUtils throw Exception(os.str().c_str()); } - char srcInterchange[255]; - char builtinInterchange[255]; - + int srcInterchangeIndex = -1; + int builtinInterchangeIndex = -1; // Identify interchange space. - identifyInterchangeSpace(srcInterchange, builtinInterchange, cfg); - + identifyInterchangeSpace(srcInterchangeIndex, eSrcConfig); + identifyBuiltinInterchangeSpace(builtinInterchangeIndex, eSrcConfig); + // Get processor from that space to the built-in color space. ConstProcessorRcPtr builtinProc; - if (builtinInterchange && builtinInterchange[0]) + if (builtinInterchangeIndex > -1) { builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinInterchange, - builtinColorSpaceName); + builtinConfig->getColorSpaceNameByIndex(builtinInterchangeIndex), + builtinColorSpaceName); } - - if (builtinProc && srcInterchange && srcInterchange[0]) + + if (builtinProc && srcInterchangeIndex > -1) { // Iterate over each color space in the source config. std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, @@ -502,27 +399,25 @@ namespace ConfigUtils 0.3f, 0.02f, 0.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f }; - int nbCs = cfg.getNumColorSpaces(); + int nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { // Get processor from that space to its reference and then use isProcessorEquivalent. // If equivalent, return that color space name. - ConstColorSpaceRcPtr cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); - const char * csName = cs->getName(); - - ConstProcessorRcPtr proc = cfg.getProcessor(cfg.getCurrentContext(), - csName, - srcInterchange); + const char * csName = eSrcConfig.getColorSpaceNameByIndex(i); + ConstProcessorRcPtr proc = eSrcConfig.getProcessor(eSrcConfig.getCurrentContext(), + csName, + eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) { - return csName; + return i; } } } - return ""; + return -1; } } diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 9ac7209463..6664f64a4e 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -13,40 +13,36 @@ namespace OCIO_NAMESPACE namespace ConfigUtils { + const char * getBuiltinLinearSpaces(int index); + // Return whether the color space contains SRGB or not. bool containsSRGB(ConstColorSpaceRcPtr & cs); // Get color space where isData is false and it has neither a to_ref or from_ref transform. - std::string getRefSpace(const Config & cfg); + int getRefSpace(const Config & cfg); // Get processor to a sRGB transform. ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, std::string refColorSpaceName); // Get reference space if the specified color space is a recognized linear space. - std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces); + int getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig); // Get reference space if the specified color space is an sRGB texture space. - std::string getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces); - // Returns true if the specified color space is linear. - bool isColorSpaceLinear(const char * colorSpace, - ReferenceSpaceType referenceSpaceType, - Config & cfg); + int getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig); // Identify the interchange space of the source config and the default built-in config. - void identifyInterchangeSpace(char * srcInterchange, - char * builtinInterchange, - const Config & cfg); + void identifyInterchangeSpace(int & srcInterchange, Config & eSrcConfig); + // Identify the interchange space the default built-in config. + void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig); // Find the name of the color space in the source config that is the same as // a color space in the default built-in config. - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg); + int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig); } } // namespace OCIO_NAMESPACE diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index eb11d79a44..3d8a8570d4 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1213,10 +1213,8 @@ ocio_profile_version: 2 // identifyInterchangeSpace tests. { - char srcInterchange[255]; - char builtinInterchange[255]; - - cfg->identifyInterchangeSpace(srcInterchange, builtinInterchange); + const char * srcInterchange = cfg->identifyInterchangeSpace(); + const char * builtinInterchange = cfg->identifyBuiltinInterchangeSpace(); OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); } From 0fe310d3009c8a35a3f6b6c385a6ac62b8221b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Mon, 16 Jan 2023 17:07:45 -0500 Subject: [PATCH 05/22] Tentative change to phase out isProcessorEquivalent. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/ConfigUtils.cpp | 69 ++++++++++++++++++++++++--------- src/OpenColorIO/ConfigUtils.h | 8 ++-- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index edd68b067b..9dec8051c8 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -75,8 +75,33 @@ namespace ConfigUtils return -1; } - ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) + bool isIdentityTransform(const Config & srcConfig, + GroupTransformRcPtr & tf, + std::vector & vals, + float absTolerance) + { + std::vector out = vals; + + PackedImageDesc desc(&vals[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&out[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + + ConstProcessorRcPtr proc = srcConfig.getProcessor(tf, TRANSFORM_DIR_FORWARD); + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + if (!EqualWithAbsError(vals[i], out[i], absTolerance)) + { + return false; + } + } + + return true; + } + + TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName) { // Build reference space of the given prims to sRGB transform. std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; @@ -85,14 +110,15 @@ namespace ConfigUtils csTransform->setSrc(refColorSpaceName.c_str()); csTransform->setDst(srgbColorSpaceName.c_str()); - ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); - return proc; + //ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); + //return proc; + return csTransform; } int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + const char * const * builtinLinearSpaces) { // If the color space is a recognized linear space, return the reference space used by // the config. @@ -123,7 +149,7 @@ namespace ConfigUtils // Generate matrices between all combinations of the Built-in linear color spaces. // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. - ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); + TransformRcPtr eSrcTransform = srcTransform->createEditableCopy(); for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) @@ -133,11 +159,12 @@ namespace ConfigUtils ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); csTransform->setSrc(builtinLinearSpaces[j]); csTransform->setDst(builtinLinearSpaces[i]); + + GroupTransformRcPtr grptf = GroupTransform::Create(); + grptf->appendTransform(eSrcTransform); + grptf->appendTransform(csTransform); - ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, - TRANSFORM_DIR_FORWARD); - - if (Processor::AreProcessorsEquivalent(p1, p2, &vals[0], 5, 1e-3f)) + if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) { if (toRefDirection) { @@ -243,7 +270,8 @@ namespace ConfigUtils 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f, }; int refSpaceIndex = -1; - ConstProcessorRcPtr fromRefProc; + TransformRcPtr fromRefTransform; + TransformRcPtr eToRefTransform = toRefTransform->createEditableCopy(); if (toRefTransform) { // The color space has the sRGB non-linearity. Now try combining the transform with a @@ -252,12 +280,13 @@ namespace ConfigUtils // reference space is. for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { - fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); + fromRefTransform = getTransformToSRGBSpace(builtinConfig, builtinLinearSpaces[i]); + + GroupTransformRcPtr grptf = GroupTransform::Create(); + grptf->appendTransform(eToRefTransform); + grptf->appendTransform(fromRefTransform); - ConstProcessorRcPtr toRefProc = config.getProcessor(toRefTransform, - TRANSFORM_DIR_FORWARD); - - if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) + if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) { refSpaceIndex = (int) i; break; @@ -410,7 +439,9 @@ namespace ConfigUtils csName, eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); - if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + auto grptf = builtinProc->createGroupTransform(); + grptf->appendTransform(proc->createGroupTransform()); + if (isIdentityTransform(eSrcConfig, grptf, vals, 1e-3f)) { return i; } diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 6664f64a4e..7fcc7bad0b 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -21,9 +21,11 @@ namespace ConfigUtils // Get color space where isData is false and it has neither a to_ref or from_ref transform. int getRefSpace(const Config & cfg); - // Get processor to a sRGB transform. - ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName); + bool isIdentityTransform(const Config & srcConfig, GroupTransformRcPtr & tf, std::vector & vals, float absTolerance); + + // Get reference to a sRGB transform. + TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName); // Get reference space if the specified color space is a recognized linear space. int getReferenceSpaceFromLinearSpace(const Config & srcConfig, From c05c4458c653851c08a74da97f3d0c44b0a3e9dc Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:27:04 -0400 Subject: [PATCH 06/22] Refactor code and add tests Signed-off-by: Doug Walker --- include/OpenColorIO/OpenColorIO.h | 129 +++-- src/OpenColorIO/Config.cpp | 201 +++---- src/OpenColorIO/ConfigUtils.cpp | 914 ++++++++++++++++++------------ src/OpenColorIO/ConfigUtils.h | 80 +-- src/OpenColorIO/Processor.cpp | 9 - tests/cpu/ColorSpace_tests.cpp | 658 ++++++++++++++++----- tests/cpu/Config_tests.cpp | 57 +- 7 files changed, 1348 insertions(+), 700 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 08a2108740..784140904e 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -690,32 +690,78 @@ class OCIOEXPORT Config */ bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const; - /** - * \brief Identify the interchange space of the source config and the default built-in config. - * - * \param[out] srcInterchange Interchange space from the source config (output). - * \param[out] builtinInterchange Interchange space from the default built-in config (output). - * - * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. - */ - const char * identifyInterchangeSpace() const; - const char * identifyBuiltinInterchangeSpace() const; - /** * \brief Find the name of the color space in the source config that is the same as - * a color space in the default built-in config. - * - * \param builtinColorSpaceName Color space name in the built-in config. - * \return Matching color space from the source config. Empty if not found. + * a color space in the default built-in config. For example, setting the + * builtinColorSpaceName to "sRGB - Texture" (a color space name from that + * config), would return the name for the corresponding sRGB texture space in + * the current config (or empty if it was not found). Note that this method + * relies on heuristics which may evolve over time and which may not work on + * all configs. + * + * The method only looks at active color spaces. If the interchange roles are + * missing and heuristics are used, only scene-referred color spaces are searched. + * + * \param srcConfig The config to search for the desired color space. + * \param builtinConfig The built-in config to use. See \ref Config::CreateFromBuiltinConfig. + * \param builtinColorSpaceName Color space name in the built-in default config. + * \return Matching color space name from the source config. Empty if not found. + * + * \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. + */ + static const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + + /** + * \brief Identify the two names of a common color space that exists in both the + * given config and the provided built-in config that may be used for converting + * color spaces between the two configs. If both configs have the interchange + * role set, than the color spaces set to that role will be returned. Otherwise, + * heuristics will be used to try and identify a known color space in the source + * config. These are the same heuristics that are used for other methods such as + * identifyBuiltinColorSpace and GetProcessorTo/FromBuiltinColorSpace. + * + * Using this method in connection with GetProcessorFromConfigs is more efficient + * if you need to call GetProcessorTo/FromBuiltinColorSpace multiple times since it + * is only necessary to run the heuristics once (to identify the interchange spaces). + * + * The srcColorSpaceName and builtinColorSpace name are used to decide which + * interchange role to use (scene- or display-referred). However, they are not + * used if the interchange roles are not present and the heuristics are used. + * It is actually only the ReferenceSpaceType of the color spaces that are used, + * so it is not necessary to call this function multiple times if all the spaces + * are of the same type. (These are the same arguments that would also be set if + * you were instead calling GetProcessorTo/FromBuiltinColorSpace.) + * + * \param[out] srcInterchangeName Color space name from the source config. + * \param[out] builtinInterchangeName Corresponding color space name from the built-in config. + * \param srcConfig The config to search for the desired color space. + * \param srcColorSpaceName Color space name in the given config to convert to/from. + * \param builtinConfig The built-in config to use. See \ref Config::CreateFromBuiltinConfig. + * \param builtinColorSpaceName Color space name in the default built-in config. + * + * \throw Exception if either the srcInterchange or builtinInterchange cannot be identified. + */ + static void IdentifyInterchangeSpace(const char ** srcInterchangeName, + const char ** builtinInterchangeName, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + + /** + * \defgroup Methods related to Roles. + * @{ + * + * A role allows a config author to indicate that a given color space should be used + * for a particular purpose. + * + * Role names may be passed to most functions that accept color space names, such as + * getColorSpace. So for example, you may find the name of the color space assigned + * to the scene_linear role by getting the color space object for "scene_linear" and + * then calling getName on the color space object. */ - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; - - // - // Roles - // - - // A role is like an alias for a colorspace. You can query the colorspace - // corresponding to a role using the normal getColorSpace fcn. /** * \brief @@ -1270,7 +1316,7 @@ class OCIOEXPORT Config * * If the source config defines the necessary Interchange Role (typically "aces_interchange"), * then the conversion will be well-defined and equivalent to calling GetProcessorFromConfigs - * with the source config and the Built-in config + * with the source config and the Built-in config. * * However, if the Interchange Roles are not present, heuristics will be used to try and * identify a common color space in the source config that may be used to allow the conversion @@ -1283,11 +1329,12 @@ class OCIOEXPORT Config * \param srcConfig The user's source config. * \param srcColorSpaceName The name of the color space in the source config. * \param builtinColorSpaceName The name of the color space in the current default Built-in config. + * + * \throw Exception if either the src or builtin interchange space cannot be identified. */ - static ConstProcessorRcPtr GetProcessorToBuiltinColorSpace( - ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName); + static ConstProcessorRcPtr GetProcessorToBuiltinColorSpace(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName); /** * \brief See description of GetProcessorToBuiltinColorSpace. * @@ -1295,14 +1342,12 @@ class OCIOEXPORT Config * \param srcConfig The user's source config. * \param srcColorSpaceName The name of the color space in the source config. */ - static ConstProcessorRcPtr GetProcessorFromBuiltinColorSpace( - const char * builtinColorSpaceName, - ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName); + static ConstProcessorRcPtr GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, + ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName); /** - * \brief Get a processor to convert between color spaces in two separate - * configs. + * \brief Get a processor to convert between color spaces in two separate configs. * * This relies on both configs having the aces_interchange role (when srcName * is scene-referred) or the role cie_xyz_d65_interchange (when srcName is @@ -1322,6 +1367,9 @@ class OCIOEXPORT Config /** * The srcInterchangeName and dstInterchangeName must refer to a pair of * color spaces in the two configs that are the same. A role name may also be used. + * + * Note: For all of the two-config GetProcessor functions, if either the source or + * destination color spaces are data spaces, the entire processor will be a no-op. */ static ConstProcessorRcPtr GetProcessorFromConfigs(const ConstConfigRcPtr & srcConfig, const char * srcColorSpaceName, @@ -1404,7 +1452,8 @@ class OCIOEXPORT Config /// Control the caching of processors in the config instance. By default, caching is on. /// The flags allow turning caching off entirely or only turning it off if dynamic /// properties are being used by the processor. - void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept; + ProcessorCacheFlags getProcessorCacheFlags() const noexcept; + void setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept; private: Config(); @@ -2465,11 +2514,11 @@ class OCIOEXPORT Processor * \param tolerance Tolerance of the comparaison. * \return True or false depending on whether the two processors are equivalent. */ - static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance); +// static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, +// ConstProcessorRcPtr & p2, +// float * rgbaValues, +// size_t numValues, +// float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 24d6b51672..1c02841567 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -326,7 +326,7 @@ class Config::Impl mutable std::string m_cacheidnocontext; FileRulesRcPtr m_fileRules; - ProcessorCacheFlags m_cacheFlags { PROCESSOR_CACHE_DEFAULT }; + mutable ProcessorCacheFlags m_cacheFlags { PROCESSOR_CACHE_DEFAULT }; mutable ProcessorCache m_processorCache; Impl() : @@ -918,7 +918,12 @@ class Config::Impl } - void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept + ProcessorCacheFlags getProcessorCacheFlags() const noexcept + { + return m_cacheFlags; + } + + void setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept { m_cacheFlags = flags; m_processorCache.enable((m_cacheFlags & PROCESSOR_CACHE_ENABLED) == PROCESSOR_CACHE_ENABLED); @@ -2716,6 +2721,8 @@ void Config::clearColorSpaces() getImpl()->refreshActiveColorSpaces(); } +/////////////////////////////////////////////////////////////////////////// + bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const { auto cs = getColorSpace(colorSpace); @@ -2767,13 +2774,7 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f }; - std::vector dst = - { - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f - }; + std::vector dst(img.size(), 0.f); PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); @@ -2781,7 +2782,7 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe auto procToReference = config.getImpl()->getProcessorWithoutCaching( config, t, TRANSFORM_DIR_FORWARD ); - auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_NONE); optCPUProc->apply(desc, descDst); float absError = 1e-5f; @@ -2828,39 +2829,31 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe // transforms, so it is equivalent to the reference space and hence linear. return true; } - -const char * Config::identifyInterchangeSpace() const -{ - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int srcInterchangeIndex = -1; - ConfigUtils::identifyInterchangeSpace(srcInterchangeIndex, *eSrcConfig); - - return getColorSpaceNameByIndex(srcInterchangeIndex); -} -const char * Config::identifyBuiltinInterchangeSpace() const +void Config::IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) { - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int builtinInterchangeIndex = -1; - ConfigUtils::identifyBuiltinInterchangeSpace(builtinInterchangeIndex, *eSrcConfig); - - return ConfigUtils::getBuiltinLinearSpaces(builtinInterchangeIndex); + // This will throw if it is unable to identify the interchange spaces. + ConfigUtils::IdentifyInterchangeSpace(srcInterchange, + builtinInterchange, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); } -const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const +const char * Config::IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) { - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int csIndex = ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *eSrcConfig); - return getColorSpaceNameByIndex(csIndex); + // This will throw if it is unable to identify the interchange spaces. + return ConfigUtils::IdentifyBuiltinColorSpace(srcConfig, + builtinConfig, + builtinColorSpaceName); } /////////////////////////////////////////////////////////////////////////// @@ -4228,6 +4221,7 @@ bool Config::filepathOnlyMatchesDefaultRule(const char * filePath) const /////////////////////////////////////////////////////////////////////////// +// GetProcessor ConstProcessorRcPtr Config::getProcessor(const ConstColorSpaceRcPtr & src, const ConstColorSpaceRcPtr & dst) const @@ -4455,50 +4449,29 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr const ConstConfigRcPtr & dstConfig, const char * dstName) { - ConstColorSpaceRcPtr srcColorSpace = srcConfig->getColorSpace(srcName); - if (!srcColorSpace) - { - std::ostringstream os; - os << "Could not find source color space '" << srcName << "'."; - throw Exception(os.str().c_str()); - } - - const bool sceneReferred = (srcColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_SCENE); - const char * exchangeRoleName = sceneReferred ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; - const char * srcExName = LookupRole(srcConfig->getImpl()->m_roles, exchangeRoleName); - if (!srcExName || !*srcExName) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' is missing in the source config."; - throw Exception(os.str().c_str()); - } - ConstColorSpaceRcPtr srcExCs = srcConfig->getColorSpace(srcExName); - if (!srcExCs) - { + // Extract the appropriate interchange roles based on the reference space type of the + // source and destination color spaces. Note that no heuristics are attempted. If these + // roles are missing, an exception is raised. (Note that the names of the returned color + // spaces should not depend on the context arguments above.) + const char * srcInterchangeName = nullptr; + const char * dstInterchangeName = nullptr; + ReferenceSpaceType interchangeType; + if( !ConfigUtils::GetInterchangeRolesForColorSpaceConversion(&srcInterchangeName, + &dstInterchangeName, + interchangeType, + srcConfig, srcName, + dstConfig, dstName) ) + { + const char * interchangeRoleName = (interchangeType == REFERENCE_SPACE_SCENE) + ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; std::ostringstream os; - os << "The role '" << exchangeRoleName << "' refers to color space '" << srcExName; - os << "' that is missing in the source config."; + os << "The required role '" << interchangeRoleName << "' is missing from the source and/or " + << "destination config."; throw Exception(os.str().c_str()); } - const char * dstExName = LookupRole(dstConfig->getImpl()->m_roles, exchangeRoleName); - if (!dstExName || !*dstExName) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' is missing in the destination config."; - throw Exception(os.str().c_str()); - } - ConstColorSpaceRcPtr dstExCs = dstConfig->getColorSpace(dstExName); - if (!dstExCs) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' refers to color space '" << dstExName; - os << "' that is missing in the destination config."; - throw Exception(os.str().c_str()); - } - - return GetProcessorFromConfigs(srcContext, srcConfig, srcName, srcExName, - dstContext, dstConfig, dstName, dstExName); + return GetProcessorFromConfigs(srcContext, srcConfig, srcName, srcInterchangeName, + dstContext, dstConfig, dstName, dstInterchangeName); } ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstConfigRcPtr & srcConfig, @@ -4569,7 +4542,14 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr ProcessorRcPtr processor = Processor::Create(); processor->getImpl()->setProcessorCacheFlags(srcConfig->getImpl()->m_cacheFlags); - processor->getImpl()->concatenate(p1, p2); + + // If either of the color spaces are data spaces, its corresponding processor + // will be empty, but need to make sure the entire result is also empty to + // better match the semantics of how data spaces are handled. + if (!srcColorSpace->isData() && !dstColorSpace->isData()) + { + processor->getImpl()->concatenate(p1, p2); + } return processor; } @@ -4589,30 +4569,14 @@ static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, throw Exception(os.str().c_str()); } - // If both configs have the interchange roles set, then it's easy. - try - { - ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - builtinConfig, - builtinColorSpaceName); - return proc; - } - catch(const Exception & e) - { - std::string str1 = "The role 'aces_interchange' is missing in the source config"; - std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; - - // Re-throw when the error is not about interchange roles. - if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) - { - throw Exception(e.what()); - } - // otherwise, do nothing and continue. - } - - const char * srcInterchange = srcConfig->identifyInterchangeSpace(); - const char * builtinInterchange = srcConfig->identifyBuiltinInterchangeSpace(); + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + Config::IdentifyInterchangeSpace(&srcInterchange, + &builtinInterchange, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); if (builtinInterchange && builtinInterchange[0]) { @@ -4620,27 +4584,27 @@ static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, if (direction == TRANSFORM_DIR_FORWARD) { proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - srcInterchange, - builtinConfig, - builtinColorSpaceName, - builtinInterchange); + srcColorSpaceName, + srcInterchange, + builtinConfig, + builtinColorSpaceName, + builtinInterchange); } else if (direction == TRANSFORM_DIR_INVERSE) { proc = Config::GetProcessorFromConfigs(builtinConfig, - builtinColorSpaceName, - builtinInterchange, - srcConfig, - srcColorSpaceName, - srcInterchange); + builtinColorSpaceName, + builtinInterchange, + srcConfig, + srcColorSpaceName, + srcInterchange); } return proc; } std::ostringstream os; os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; + << "Please set the interchange roles in the config."; throw Exception(os.str().c_str()); } @@ -4664,6 +4628,8 @@ ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * built TRANSFORM_DIR_INVERSE); } +/////////////////////////////////////////////////////////////////////////// + std::ostream& operator<< (std::ostream& os, const Config& config) { config.serialize(os); @@ -4761,7 +4727,12 @@ void Config::serialize(std::ostream& os) const } } -void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept +ProcessorCacheFlags Config::getProcessorCacheFlags() const noexcept +{ + return getImpl()->getProcessorCacheFlags(); +} + +void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept { getImpl()->setProcessorCacheFlags(flags); } diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 9dec8051c8..7a7d1f26ee 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -7,449 +7,665 @@ namespace OCIO_NAMESPACE { - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - constexpr int numberOfbuiltinLinearSpaces = 5; - constexpr const char * builtinLinearSpaces[] = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; namespace ConfigUtils { - const char * getBuiltinLinearSpaces(int index) + +////////////////////////////////////////////////////////////////////////////////////// + +// The following code needs to know the names of some of the color spaces in the +// built-in default config. If the color space names of that config are ever +// modified, the following strings should be kept in sync. + +// Name of the sRGB space in the built-in config. +// +const char * getSRGBColorSpaceName() +{ + constexpr const char * srgbColorSpaceName = "sRGB - Texture"; + return srgbColorSpaceName; +} + +// Define the set of candidate built-in default config reference linear color spaces that +// will be used when searching through the source config. If the source config scene-referred +// reference space is the equivalent of one of these spaces, it should be possible to identify +// it with the following heuristics. +// +const char * getBuiltinLinearSpaceName(int index) +{ + constexpr const char * builtinLinearSpaces[] = { "ACES2065-1", + "ACEScg", + "Linear Rec.709 (sRGB)", + "Linear P3-D65", + "Linear Rec.2020" }; + return builtinLinearSpaces[Clamp(index, 0, 4)]; +} + +inline int getNumberOfbuiltinLinearSpaces() +{ + return 5; +} + +////////////////////////////////////////////////////////////////////////////////////// + +// Use the interchange roles in the pair of provided configs to return the color space +// names to be used for the conversion between the provided pair of color spaces. +// Note that the color space names returned depend on the image state of the provided +// color spaces. The returned color space names are the names that the interchange +// roles point to and the function checks that they exist. An exception is raised if +// there are problems with the input arguments or if the interchange roles are present +// but point to color spaces that don't exist. If the interchange roles are simply +// not present, no exception is thrown but false is returned. If the returned interchange +// color space names are present and exist, true is returned. +// +// This function does NOT use any heuristics. +// +// \param[out] srcInterchangeCSName -- Name of the interchange color space from the src config. +// \param[out] dstInterchangeCSName -- Name of the interchange color space from the dst config. +// \param srcConfig -- Source config object. +// \param srcName -- Name of the color space to be converted from the source config. +// May be empty if the source color space is unknown. +// \param dstConfig -- Destination config object. +// \param dstName -- Name of the color space to be converted from the destination config. +// \return True if the necessary interchange roles were found. +// +bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, + const char ** dstInterchangeCSName, + ReferenceSpaceType & interchangeType, + const ConstConfigRcPtr & srcConfig, + const char * srcName, + const ConstConfigRcPtr & dstConfig, + const char * dstName) +{ + ConstColorSpaceRcPtr dstColorSpace = dstConfig->getColorSpace(dstName); + if (!dstColorSpace) { - return builtinLinearSpaces[index]; + std::ostringstream os; + os << "Could not find destination color space '" << dstName << "'."; + throw Exception(os.str().c_str()); } - bool containsSRGB(ConstColorSpaceRcPtr & cs) + interchangeType = REFERENCE_SPACE_SCENE; + + if (srcName && !*srcName) { - std::string name = StringUtils::Lower(cs->getName()); - if (StringUtils::Find(name, "srgb") != std::string::npos) + // If srcName is empty, just use the reference type of the destination side. + // In this scenario, the source color space is unknown but the assumption is + // that when it is found it will have the same reference space type as the + // destination color space. + if (dstColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) { - return true; + interchangeType = REFERENCE_SPACE_DISPLAY; + } + } + else + { + ConstColorSpaceRcPtr srcColorSpace = srcConfig->getColorSpace(srcName); + if (!srcColorSpace) + { + std::ostringstream os; + os << "Could not find source color space '" << srcName << "'."; + throw Exception(os.str().c_str()); } - size_t nbOfAliases = cs->getNumAliases(); - for (size_t i = 0; i < nbOfAliases; i++) + // Only use the display-referred reference space if both color spaces are + // display-referred. If only one of the spaces is display-referred, it's + // better to use the scene-referred space since the conversion to scene- + // referred will happen within the config that has the display-referred + // color space. The config with the scene-referred color space may not + // even have a default view transform to use. In addition, it's important + // that this function always use the same reference space even if the order + // of src & dst is swapped, so the result is the inverse (which it might + // not be if the view transform in the opposite config is used). + if (srcColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY && + dstColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) { - if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) - { - return true; - } + interchangeType = REFERENCE_SPACE_DISPLAY; } + } + + const char * interchangeRoleName = (interchangeType == REFERENCE_SPACE_SCENE) + ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; + if (!srcConfig->hasRole(interchangeRoleName)) + { return false; } + // Get the color space name assigned to the interchange role. + ConstColorSpaceRcPtr srcInterchangeCS = srcConfig->getColorSpace(interchangeRoleName); + if (!srcInterchangeCS) + { + std::ostringstream os; + os << "The role '" << interchangeRoleName << "' refers to a color space "; + os << "that is missing in the source config."; + throw Exception(os.str().c_str()); + } + *srcInterchangeCSName = srcInterchangeCS->getName(); - int getRefSpace(const Config & cfg) + if (!dstConfig->hasRole(interchangeRoleName)) { - // Find a color space where isData is false and it has neither a to_ref or from_ref - // transform. - auto nbCs = cfg.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); - if (cs->isData()) - { - continue; - } + return false; + } + // Get the color space name assigned to the interchange role. + ConstColorSpaceRcPtr dstInterchangeCS = dstConfig->getColorSpace(interchangeRoleName); + if (!dstInterchangeCS) + { + std::ostringstream os; + os << "The role '" << interchangeRoleName << "' refers to a color space "; + os << "that is missing in the destination config."; + throw Exception(os.str().c_str()); + } + *dstInterchangeCSName = dstInterchangeCS->getName(); - auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (t != nullptr) - { - continue; - } + return true; +} - t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (t != nullptr) - { - continue; - } +// Return true if the color space name or its aliases contains sRGB (case-insensitive). +// +bool containsSRGB(ConstColorSpaceRcPtr & cs) +{ + std::string name = StringUtils::Lower(cs->getName()); + if (StringUtils::Find(name, "srgb") != std::string::npos) + { + return true; + } - return i; + size_t nbOfAliases = cs->getNumAliases(); + for (size_t i = 0; i < nbOfAliases; i++) + { + if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) + { + return true; } - return -1; } - bool isIdentityTransform(const Config & srcConfig, - GroupTransformRcPtr & tf, - std::vector & vals, - float absTolerance) - { - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + return false; +} - ConstProcessorRcPtr proc = srcConfig.getProcessor(tf, TRANSFORM_DIR_FORWARD); - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); +// Find a color space where isData is false and it has neither a to_ref or from_ref +// transform. Currently only selecting scene-referred spaces. Note: this returns +// the first reference space found, even if it is inactive. Returns empty if none +// are found. +// +const char * getRefSpaceName(const ConstConfigRcPtr & cfg) +{ + // It's important to support inactive spaces since sometimes the only reference space + // may be inactive, e.g. the display-referred reference in the built-in configs. + int nbCs = cfg->getNumColorSpaces(SEARCH_REFERENCE_SPACE_SCENE, COLORSPACE_ALL); + + for (int i = 0; i < nbCs; i++) + { + const char * csname = cfg->getColorSpaceNameByIndex(SEARCH_REFERENCE_SPACE_SCENE, + COLORSPACE_ALL, + i); + ConstColorSpaceRcPtr cs = cfg->getColorSpace(csname); - for (size_t i = 0; i < out.size(); i++) + if (cs->isData()) { - if (!EqualWithAbsError(vals[i], out[i], absTolerance)) - { - return false; - } + continue; + } + ConstTransformRcPtr t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (t != nullptr) + { + continue; + } + t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (t != nullptr) + { + continue; } - return true; + return csname; } + return ""; +} + +// Return false if the supplied Processor modifies any of the supplied float values +// by more than the supplied absolute tolerance amount. +// +bool isIdentityTransform(ConstProcessorRcPtr proc, + std::vector & RGBAvals, + float absTolerance) +{ + std::vector out(RGBAvals.size(), 0.f); + + PackedImageDesc desc( &RGBAvals[0], (long) RGBAvals.size() / 4, 1, CHANNEL_ORDERING_RGBA ); + PackedImageDesc descDst( &out[0], (long) RGBAvals.size() / 4, 1, CHANNEL_ORDERING_RGBA ); - TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_NONE); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) { - // Build reference space of the given prims to sRGB transform. - std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(refColorSpaceName.c_str()); - csTransform->setDst(srgbColorSpaceName.c_str()); - - //ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); - //return proc; - return csTransform; + if (!EqualWithAbsError(RGBAvals[i], out[i], absTolerance)) + { + return false; + } } - int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + return true; +} + +// Helper to avoid color spaces that are without transforms or are data spaces. +// +bool hasNoTransform(const ConstColorSpaceRcPtr & cs) +{ + if (cs->isData()) { - // If the color space is a recognized linear space, return the reference space used by - // the config. - int refSpaceIndex = -1; - bool toRefDirection = true; - auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (!srcTransform) + return true; + } + ConstTransformRcPtr srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (!srcTransform) + { + srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (srcTransform) { - srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (srcTransform) - { - toRefDirection = false; - } - else - { - return -1; - } + return false; } + else + { + return true; + } + } + return false; +} - // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is - // enough of an identity. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; +// Test the supplied color space against a set of color spaces in the built-in config. +// If a match is found, it indicates what reference space is used by the config. +// Return the index into the list of built-in linear spaces, or -1 if not found. +// +// \param srcConfig -- Source config object. +// \param srcRefName -- Name of a scene-referred reference color space in the src config. +// \param cs -- Color space from the source config to test. +// \param builtinConfig -- The built-in config object. +// \return The index into the list of built-in linear spaces. +// +int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, + const char * srcRefName, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig) +{ + // Currently only handling scene-referred spaces in the heuristics. + if (cs->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) + { + return -1; + } + // Don't check spaces without transforms / data spaces. + if (hasNoTransform(cs)) + { + return -1; + } - // Generate matrices between all combinations of the Built-in linear color spaces. - // Then combine these with the transform from the current color space to see if the result is - // an identity. If so, then it identifies the reference space being used by the source config. - TransformRcPtr eSrcTransform = srcTransform->createEditableCopy(); - for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) + // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is + // enough of an identity. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, -0.2f, 0.f, + 0.3f, 0.02f, 1.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + + // Test the transform from the test color space to its reference space against all combinations + // of the built-in linear color spaces. If one of them results in an identity, that identifies + // what the source color space and reference space are. + + for (int i = 0; i < getNumberOfbuiltinLinearSpaces(); i++) + { + for (int j = 0; j < getNumberOfbuiltinLinearSpaces(); j++) { - for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) + // Ensure the built-in side of the conversion is never an identity, since if + // both the src side and built-in side are an identity, it would seem as though + // the reference space has been identified, but in fact it would not be. + if (i != j) { - if (i != j) + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs( + srcConfig, + cs->getName(), + srcRefName, + builtinConfig, + getBuiltinLinearSpaceName(i), + getBuiltinLinearSpaceName(j)); + + if (isIdentityTransform(proc, vals, 1e-3f)) { - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j]); - csTransform->setDst(builtinLinearSpaces[i]); - - GroupTransformRcPtr grptf = GroupTransform::Create(); - grptf->appendTransform(eSrcTransform); - grptf->appendTransform(csTransform); - - if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) - { - if (toRefDirection) - { - refSpaceIndex = (int) j; - } - else - { - refSpaceIndex = (int) i; - } - - return refSpaceIndex; - } + return j; } } } + } + + return -1; +} +// Test the supplied color space against a set of color spaces in the built-in config +// to see if it matches an sRGB texture color space with one of a set of known primaries +// used as its reference space. If a match is found, it indicates what reference space +// is used by the config. Return the index into the list of built-in linear spaces, +// or -1 if not found. +// +// \param srcConfig -- Source config object. +// \param srcRefName -- Name of a scene-referred reference color space in the src config. +// \param cs -- Color space from the source config to test. +// \param builtinConfig -- The built-in config object. +// \return The index into the list of built-in linear spaces. +// +int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, + const char * srcRefName, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig) +{ + // Currently only handling scene-referred spaces in the heuristics. + if (cs->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) + { return -1; } - - int getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + // Get a transform in the to-reference direction. + ConstTransformRcPtr toRefTransform; + ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (ctransform) { - // If the color space is an sRGB texture space, return the reference space used by the config. - - // Get a transform in the to-reference direction. - ConstTransformRcPtr toRefTransform; - ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (ctransform) + toRefTransform = ctransform; + } + else + { + ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (ctransform) { - toRefTransform = ctransform; + TransformRcPtr transform = ctransform->createEditableCopy(); + transform->setDirection(TRANSFORM_DIR_INVERSE); + toRefTransform = transform; } else { - ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (ctransform) - { - TransformRcPtr transform = ctransform->createEditableCopy(); - transform->setDirection(TRANSFORM_DIR_INVERSE); - toRefTransform = transform; - } - else - { - // Both directions missing. - return -1; - } + // Don't check spaces without transforms / data spaces. + return -1; } + } - // First check if it has the right non-linearity. The objective is to fail quickly on color - // spaces that are definitely not sRGB before proceeding to the longer test of guessing the - // reference space primaries. - - // Break point is at 0.039286, so include at least one value below this. - std::vector vals = - { - 0.5f, 0.5f, 0.5f, - 0.03f, 0.03f, 0.03f, - 0.25f, 0.25f, 0.25f, - 0.75f, 0.75f, 0.75f, - 0.f, 0.f, 0.f, - 1.f, 1.f , 1.f - }; - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstProcessorRcPtr proc = config.getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - // Apply the sRGB function (linear to non-lin). - // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. - if (out[i] <= 0.0030399346397784323f) - { - out[i] *= 12.923210180787857f; - } - else - { - out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; - } - - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return -1; - } - } - - // - // Then try the various primaries for the reference space. - // + // First check if it has the right non-linearity. The objective is to fail quickly on color + // spaces that are definitely not sRGB before proceeding to the longer test of guessing the + // reference space primaries. - // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact - // converting sRGB texture values to the candidate reference space. It includes 0.02 which is - // on the sRGB linear segment, color values, and neutral values. - vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f, }; - int refSpaceIndex = -1; - TransformRcPtr fromRefTransform; - TransformRcPtr eToRefTransform = toRefTransform->createEditableCopy(); - if (toRefTransform) - { - // The color space has the sRGB non-linearity. Now try combining the transform with a - // transform from the Built-in config that goes from a variety of reference spaces to an - // sRGB texture space. If the result is an identity, then that tells what the source config - // reference space is. - for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) - { - fromRefTransform = getTransformToSRGBSpace(builtinConfig, builtinLinearSpaces[i]); + // Break point is at 0.039286, so include at least one value below this. + std::vector vals = + { + 0.5f, 0.5f, 0.5f, + 0.03f, 0.03f, 0.03f, + 0.25f, 0.25f, 0.25f, + 0.75f, 0.75f, 0.75f, + 0.f, 0.f, 0.f, + 1.f, 1.f , 1.f + }; + std::vector out(vals.size(), 0.f); - GroupTransformRcPtr grptf = GroupTransform::Create(); - grptf->appendTransform(eToRefTransform); - grptf->appendTransform(fromRefTransform); + PackedImageDesc desc( &vals[0], (long) vals.size() / 3, 1, CHANNEL_ORDERING_RGB ); + PackedImageDesc descDst( &out[0], (long) vals.size() / 3, 1, CHANNEL_ORDERING_RGB ); - if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) - { - refSpaceIndex = (int) i; - break; - } - } - } + ConstProcessorRcPtr proc = srcConfig->getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); - return refSpaceIndex; - } + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_NONE); + // Convert the non-linear values to linear. + cpu->apply(desc, descDst); - void identifyInterchangeSpace(int & srcInterchangeIndex, Config & eSrcConfig) + for (size_t i = 0; i < out.size(); i++) { - if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) + // Apply the sRGB function to convert the processed linear values back to non-linear. + // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. + if (out[i] <= 0.0030399346397784323f) { - std::ostringstream os; - os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; - throw Exception(os.str().c_str()); + out[i] *= 12.923210180787857f; } - - // Get the name of (one of) the reference spaces. - int refColorSpaceIndex = getRefSpace(eSrcConfig); - if (refColorSpaceIndex < 0) + else { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); + out[i] = 1.055f * std::pow(out[i], 1.f / 2.4f) - 0.055f; } - - if (refColorSpaceIndex > -1) + // Compare against the original source values. + if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) { - srcInterchangeIndex = refColorSpaceIndex; + return -1; } - else + } + + // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact + // converting sRGB texture values to the candidate reference space. It includes 0.02 which is + // on the sRGB linear segment, color values, and neutral values. + vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f, }; + + // The color space has the sRGB non-linearity. Now try combining the transform with a + // transform from the Built-in config that goes from a variety of reference spaces to an + // sRGB texture space. If the result is an identity, then that tells what the source config + // reference space is. + + for (int i = 0; i < getNumberOfbuiltinLinearSpaces(); i++) + { + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + cs->getName(), + srcRefName, + builtinConfig, + getSRGBColorSpaceName(), + getBuiltinLinearSpaceName(i)); + if (isIdentityTransform(proc, vals, 1e-3f)) { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); + return i; } } - void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig) + return -1; +} + +// Identify the interchange spaces of the source config and the built-in default config +// that should be used to convert from the src color space to the built-in color space, +// or vice-versa. Throws if no suitable spaces are found. +// +// \param[out] srcInterchange -- Name of the interchange color space from the source config. +// \param[out] builtinInterchange -- Name of the interchange color space from the built-in config. +// \param srcConfig -- Source config object. +// \param srcColorSpaceName -- Name of the color space to be converted from the source config. +// \param builtinConfig -- Built-in config object. +// \param builtinColorSpaceName -- Name of the color space to be converted from the built-in config. +// +// \throw Exception if an interchange space cannot be found. +// +void IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) +{ + // Before resorting to heuristics, check if the configs already have the interchange + // roles defined. + // + // Note that this is the only place that srcColorSpaceName and builtinColorSpaceName + // are used, in order to determine whether the scene- or display-referred interchange + // role is most appropriate. These color spaces are not used below for the heuristics. + + ReferenceSpaceType interchangeType; + if ( GetInterchangeRolesForColorSpaceConversion(srcInterchange, builtinInterchange, + interchangeType, + srcConfig, srcColorSpaceName, + builtinConfig, builtinColorSpaceName) ) + { + // No need for the heuristics. + return; + } + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Currently only handling scene-referred spaces in the heuristics. + if (builtinConfig->getColorSpace(builtinColorSpaceName)->getReferenceSpaceType() + == REFERENCE_SPACE_DISPLAY) + { + std::ostringstream os; + os << "The heuristics currently only support scene-referred color spaces. " + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } + + // Identify the name of a reference space in the source config. + *srcInterchange = getRefSpaceName(srcConfig); + if (!*srcInterchange) { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } - if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) + // The heuristics need to create a lot of Processors and send RGB values through + // them to try and identify a known color space. Turn off the Processor cache in + // the configs to avoid polluting the cache with transforms that won't be reused + // and avoid the overhead of maintaining the cache. + SuspendCacheGuard srcGuard(srcConfig); + SuspendCacheGuard builtinGuard(builtinConfig); + + // Check for an sRGB texture space. + int refColorSpacePrimsIndex = -1; + int nbCs = srcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) { - std::ostringstream os; - os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; - throw Exception(os.str().c_str()); + refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(srcConfig, + *srcInterchange, + cs, + builtinConfig); + // Break out when a match is found. + if (refColorSpacePrimsIndex > -1) break; } + } - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Check for an sRGB texture space. - int refColorSpacePrimsIndex = -1; - int nbCs = eSrcConfig.getNumColorSpaces(); + if (refColorSpacePrimsIndex < 0) + { + // Check for a scene-linear space with known primaries. + nbCs = srcConfig->getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - ConstColorSpaceRcPtr cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (srcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { - refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(srcConfig, + *srcInterchange, + cs, + builtinConfig); // Break out when a match is found. if (refColorSpacePrimsIndex > -1) break; } } + } - if (refColorSpacePrimsIndex < 0) - { - // Check for a linear space with known primaries. - nbCs = eSrcConfig.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); - if (eSrcConfig.isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (refColorSpacePrimsIndex > -1) break; - } - } - } + if (refColorSpacePrimsIndex > -1) + { + *builtinInterchange = getBuiltinLinearSpaceName(refColorSpacePrimsIndex); + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config. " + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } +} - if (refColorSpacePrimsIndex > -1) - { - builtinInterchangeIndex = refColorSpacePrimsIndex; - } - else - { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } +// Try to find the name of a color space in the source config that is equivalent to the +// specified color space from the provided built-in config. Only active color spaces +// are searched. +// +// \param srcConfig -- The source config object to search. +// \param builtinConfig -- The built-in config object containing the desired color space. +// \param builtinColorSpaceName -- Name of the desired color space from the built-in config. +// \return The name of the color space in the source config. +// +// \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. +// +const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) +{ + // Note: Technically, the built-in config could be any config, if the interchange + // roles are set in both configs, and the supplied built-in config supports the list + // of color spaces returned by getBuiltinLinearSpaceName. + + ConstColorSpaceRcPtr builtinColorSpace = builtinConfig->getColorSpace(builtinColorSpaceName); + if (!builtinColorSpace) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); } - int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig) + ReferenceSpaceType builtinRefSpaceType = builtinColorSpace->getReferenceSpaceType(); + + // Identify interchange spaces. Passing an empty string for the source color space + // means that only the builtinColorSpace will be used to determine the reference + // space type of the interchange role. Will throw if the space cannot be found. + // Only color spaces in the srcConfig that have the same reference type as the + // builtinColorSpace will be searched by the heuristics below. + const char * srcInterchangeName = nullptr; + const char * builtinInterchangeName = nullptr; + IdentifyInterchangeSpace(&srcInterchangeName, + &builtinInterchangeName, + srcConfig, + "", + builtinConfig, + builtinColorSpaceName); + + // The heuristics need to create a lot of Processors and send RGB values through + // them to try and identify a known color space. Turn off the Processor cache in + // the configs to avoid polluting the cache with transforms that won't be reused + // and avoid the overhead of maintaining the cache. + SuspendCacheGuard srcGuard(srcConfig); + SuspendCacheGuard builtinGuard(builtinConfig); + + if (*builtinInterchangeName) { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; - int srcInterchangeIndex = -1; - int builtinInterchangeIndex = -1; - // Identify interchange space. - identifyInterchangeSpace(srcInterchangeIndex, eSrcConfig); - identifyBuiltinInterchangeSpace(builtinInterchangeIndex, eSrcConfig); - - // Get processor from that space to the built-in color space. - ConstProcessorRcPtr builtinProc; - if (builtinInterchangeIndex > -1) - { - builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinConfig->getColorSpaceNameByIndex(builtinInterchangeIndex), - builtinColorSpaceName); - } + // Loop over each non-data color space in the source config and test if the conversion to + // the specified space in the built-in config is an identity. + // + // Note that there is a possibility that both the source and built-in sides of the + // transform could be an identity (e.g., if the user asks for ACES2065-1 and that is + // also the reference space in both configs). However, this would not prevent the + // algorithm from returning the correct result, as long as the interchange spaces + // were correctly identified. - if (builtinProc && srcInterchangeIndex > -1) + int nbCs = srcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) { - // Iterate over each color space in the source config. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; - int nbCs = eSrcConfig.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (cs->isData() || (cs->getReferenceSpaceType() != builtinRefSpaceType)) { - // Get processor from that space to its reference and then use isProcessorEquivalent. - // If equivalent, return that color space name. - - const char * csName = eSrcConfig.getColorSpaceNameByIndex(i); - ConstProcessorRcPtr proc = eSrcConfig.getProcessor(eSrcConfig.getCurrentContext(), - csName, - eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); + continue; + } - auto grptf = builtinProc->createGroupTransform(); - grptf->appendTransform(proc->createGroupTransform()); - if (isIdentityTransform(eSrcConfig, grptf, vals, 1e-3f)) - { - return i; - } + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + cs->getName(), + srcInterchangeName, + builtinConfig, + builtinColorSpaceName, + builtinInterchangeName); + if (isIdentityTransform(proc, vals, 1e-3f)) + { + return cs->getName(); } } - - return -1; } + + std::ostringstream os; + os << "Heuristics were not able to find an equivalent to the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); } -} \ No newline at end of file +} // namespace ConfigUtils + +} // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 7fcc7bad0b..3963f88d38 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -13,39 +13,53 @@ namespace OCIO_NAMESPACE namespace ConfigUtils { - const char * getBuiltinLinearSpaces(int index); - - // Return whether the color space contains SRGB or not. - bool containsSRGB(ConstColorSpaceRcPtr & cs); - - // Get color space where isData is false and it has neither a to_ref or from_ref transform. - int getRefSpace(const Config & cfg); - - bool isIdentityTransform(const Config & srcConfig, GroupTransformRcPtr & tf, std::vector & vals, float absTolerance); - - // Get reference to a sRGB transform. - TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName); - - // Get reference space if the specified color space is a recognized linear space. - int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig); - - // Get reference space if the specified color space is an sRGB texture space. - int getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig); - - // Identify the interchange space of the source config and the default built-in config. - void identifyInterchangeSpace(int & srcInterchange, Config & eSrcConfig); - // Identify the interchange space the default built-in config. - void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig); - - // Find the name of the color space in the source config that is the same as - // a color space in the default built-in config. - int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig); -} + +bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, + const char ** dstInterchangeCSName, + ReferenceSpaceType & interchangeType, + const ConstConfigRcPtr & srcConfig, + const char * srcName, + const ConstConfigRcPtr & dstConfig, + const char * dstName); + +void IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + +const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + +// Temporarily deactivate the Processor cache on a Config object. +// Currently, this also clears the cache. +// +class SuspendCacheGuard +{ +public: + SuspendCacheGuard(); + SuspendCacheGuard(const SuspendCacheGuard &) = delete; + SuspendCacheGuard & operator=(const SuspendCacheGuard &) = delete; + + SuspendCacheGuard(const ConstConfigRcPtr & config) + : m_config(config), m_origCacheFlags(config->getProcessorCacheFlags()) + { + m_config->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + } + + ~SuspendCacheGuard() + { + m_config->setProcessorCacheFlags(m_origCacheFlags); + } + +private: + ConstConfigRcPtr m_config = nullptr; + ProcessorCacheFlags m_origCacheFlags; +}; + +} // namespace ConfigUtils } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index bfbe6d0f9a..9998bb319a 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -219,15 +219,6 @@ ConstCPUProcessorRcPtr Processor::getOptimizedCPUProcessor(BitDepth inBitDepth, return getImpl()->getOptimizedCPUProcessor(inBitDepth, outBitDepth, oFlags); } -bool Processor::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance) -{ - return Processor::Impl::AreProcessorsEquivalent(p1, p2, rgbaValues, numValues, tolerance); -} - // Instantiate the cache with the right types. template class ProcessorCache; diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index 3d8a8570d4..8c37c37975 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -948,7 +948,7 @@ inactive_colorspaces: [display_linear-trans, scene_linear-trans] } } -OCIO_ADD_TEST(Processor, processor_to_known_colorspace) +OCIO_ADD_TEST(ConfigUtils, processor_to_known_colorspace) { constexpr const char * CONFIG { R"( ocio_profile_version: 2 @@ -957,7 +957,37 @@ ocio_profile_version: 2 default: raw scene_linear: ref_cs +display_colorspaces: + - ! + name: CIE-XYZ-D65 + description: The CIE XYZ (D65) display connection colorspace. + isdata: false + + - ! + name: sRGB - Display CS + description: Convert CIE XYZ (D65 white) to sRGB (piecewise EOTF) + isdata: false + from_display_reference: ! {style: DISPLAY - CIE-XYZ-D65_to_sRGB} + colorspaces: + # Put a couple of test color space first in the config since the heuristics stop upon success. + + - ! + name: File color space + description: Verify that that FileTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: lut1d_green.ctf} + + - ! + name: CS Transform color space + description: Verify that that ColorSpaceTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: ref_cs, dst: not sRGB} + - ! name: raw description: A data colorspace (should not be used). @@ -965,7 +995,7 @@ ocio_profile_version: 2 - ! name: ref_cs - description: The reference colorspace. + description: The reference colorspace, ACES2065-1. isdata: false - ! @@ -990,119 +1020,26 @@ ocio_profile_version: 2 - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. isdata: false from_scene_reference: ! - name: AP0 to sRGB Rec.709 + name: AP0 to sRGB Encoded AP1 - Texture children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. isdata: false from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture + name: AP0 to sRGB Rec.709 children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - )" }; - auto checkProcessor = [](OCIO::ConstProcessorRcPtr & proc) - { - OCIO::GroupTransformRcPtr gt = proc->createGroupTransform(); - OCIO_CHECK_EQUAL(gt->getNumTransforms(), 4); - - { - OCIO::ConstLogCameraTransformRcPtr logAfTf0 = - OCIO::DynamicPtrCast(gt->getTransform(0)); - - double values[3]; - logAfTf0->getLogSideSlopeValue(values); - OCIO_CHECK_CLOSE(values[0], 0.0570776, 0.000001); - OCIO_CHECK_EQUAL(logAfTf0->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); - } - - { - auto mt1 = OCIO::DynamicPtrCast(gt->getTransform(1)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt1->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 0.6954522413574519, 0.000001); - OCIO_CHECK_EQUAL(mt1->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt2 = OCIO::DynamicPtrCast(gt->getTransform(2)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt2->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 1.45143931607166, 0.000001); - OCIO_CHECK_EQUAL(mt2->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - double vals[4]; - auto mt3 = OCIO::DynamicPtrCast(gt->getTransform(3)); - mt3->getValue(vals); - OCIO_CHECK_CLOSE(vals[0], 2.2, 0.000001); - OCIO_CHECK_EQUAL(mt3->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); - } - }; - - auto checkProcessorInverse = [](OCIO::ConstProcessorRcPtr & proc) - { - OCIO::GroupTransformRcPtr gt = proc->createGroupTransform(); - OCIO_CHECK_EQUAL(gt->getNumTransforms(), 4); - - { - double vals[4]; - auto mt0 = OCIO::DynamicPtrCast(gt->getTransform(0)); - mt0->getValue(vals); - OCIO_CHECK_CLOSE(vals[0], 2.2, 0.000001); - OCIO_CHECK_EQUAL(mt0->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt1 = OCIO::DynamicPtrCast(gt->getTransform(1)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt1->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 0.6954522413574519, 0.000001); - OCIO_CHECK_EQUAL(mt1->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt2 = OCIO::DynamicPtrCast(gt->getTransform(2)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt2->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 1.45143931607166, 0.000001); - OCIO_CHECK_EQUAL(mt2->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - OCIO::ConstLogCameraTransformRcPtr logAfTf0 = - OCIO::DynamicPtrCast(gt->getTransform(3)); - - double values[3]; - logAfTf0->getLogSideSlopeValue(values); - OCIO_CHECK_CLOSE(values[0], 0.0570776, 0.000001); - OCIO_CHECK_EQUAL(logAfTf0->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - }; - std::istringstream is; is.str(CONFIG); OCIO::ConstConfigRcPtr cfg; @@ -1110,6 +1047,10 @@ ocio_profile_version: 2 OCIO::ConfigRcPtr editableCfg = cfg->createEditableCopy(); + editableCfg->setSearchPath(OCIO::GetTestFilesDir().c_str()); + + OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); + // Make all color spaces suitable for the heuristics inactive. // The heuristics don't look at inactive color spaces. editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, Texture -- sRGB"); @@ -1117,105 +1058,524 @@ ocio_profile_version: 2 std::string srcColorSpaceName = "not sRGB"; std::string builtinColorSpaceName = "Gamma 2.2 AP1 - Texture"; + // Test throw if no suitable spaces are present. { - // Test throw if no suitable spaces are present. - - OCIO_CHECK_THROW(auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace( - editableCfg, - srcColorSpaceName.c_str(), - builtinColorSpaceName.c_str()), - + OCIO_CHECK_THROW( + auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()), OCIO::Exception ); } - { - // Test sRGB Texture space. + // Generate a reference Processor for the correct result. + OCIO::ConstProcessorRcPtr refProc = OCIO::Config::GetProcessorFromConfigs( + editableCfg, + srcColorSpaceName.c_str(), + "ref_cs", + builtinConfig, + builtinColorSpaceName.c_str(), + "ACES2065-1"); + + // Now make various spaces active and test that they enable the heuristics to find + // the appropriate interchange spaces. - editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + { + // Uses "sRGB Texture" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Test using linear color space from_ref direction. + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); { - // Test linear color space from_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + // Uses "Linear Rec.709 (sRGB)" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Test linear color space to_ref direction. + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + // Uses "ACEScg" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Generate a reference Processor for the correct result. + OCIO::ConstProcessorRcPtr invRefProc = OCIO::Config::GetProcessorFromConfigs( + builtinConfig, + builtinColorSpaceName.c_str(), + "ACES2065-1", + editableCfg, + srcColorSpaceName.c_str(), + "ref_cs"); + + // Make the reference space inactive too. + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, ref_cs"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + // Uses "sRGB Texture" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } + // Test using linear color space from_ref direction. + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB, ref_cs"); { - // Test linear color space from_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + // Uses "Linear Rec.709 (sRGB)" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } + // Test linear color space to_ref direction. + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB, ref_cs"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + // Uses "ACEScg" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } - // identifyBuiltinColorSpace tests. + // + // Test IdentifyInterchangeSpace. + // + { - const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + // Uses "ACEScg" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES2065-1")); + } + // Set the interchange role. In order to prove that it is being used rather than + // the heuristics, set it to something wrong and check that it gets returned anyway. + editableCfg->setRole("aces_interchange", "Texture -- sRGB"); + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("Texture -- sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES2065-1")); } + // Unset the interchange role, so the heuristics will be used for the other tests. + editableCfg->setRole("aces_interchange", ""); + + // Check what happens if a totally bogus config is passed for the built-in config. + // (It fails in the first heuristic that tries to use one of the known built-in spaces.) + OCIO::ConstConfigRcPtr rawCfg = OCIO::Config::CreateRaw(); + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", + rawCfg, "raw"), + OCIO::Exception, + "Could not find destination color space 'sRGB - Texture'" + ); + } + // Check what happens if the source color space doesn't exist. { - const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Foo", + rawCfg, "raw"), + OCIO::Exception, + "Could not find source color space 'Foo'." + ); + } + // Check what happens if the destination color space doesn't exist. + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Foo", + rawCfg, ""), + OCIO::Exception, + "Could not find destination color space ''." + ); } + // + // Test IdentifyBuiltinColorSpace. + // + + editableCfg->setInactiveColorSpaces(""); + { - const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); } { - const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } - // identifyInterchangeSpace tests. { - const char * srcInterchange = cfg->identifyInterchangeSpace(); - const char * builtinInterchange = cfg->identifyBuiltinInterchangeSpace(); - OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); - OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ref_cs")); + } + + editableCfg->setInactiveColorSpaces("Texture -- sRGB, ref_cs"); + + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Linear ITU-R BT.709")); + } + + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("CS Transform color space")); + } + + // Aliases for built-in color spaces must work. + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } + + // Display-referred spaces are not supported unless the display-referred interchange + // role is present. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } + + // The next three cases directly use the interchange roles rather than heuristics. + + // With the required role, it then works. + editableCfg->setRole("cie_xyz_d65_interchange", "CIE-XYZ-D65"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB - Display CS")); + } + + // Must continue to work if the color space for the interchange role is inactive. + editableCfg->setInactiveColorSpaces("CIE-XYZ-D65"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB - Display CS")); + } + + // Test the scene-referred interchange role (and even make it inactive). + editableCfg->setRole("aces_interchange", "ref_cs"); + editableCfg->setInactiveColorSpaces("ref_cs"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } +} + +OCIO_ADD_TEST(ConfigUtils, processor_to_known_colorspace_alt_config) +{ + // This test uses a config that has a different reference space than the built-in config. + + constexpr const char * CONFIG { R"( +ocio_profile_version: 2 + +environment: {} + +roles: + default: sRGB + scene_linear: scene-linear Rec.709-sRGB + rendering: scene-linear Rec.709-sRGB + +file_rules: + - ! {name: Default, colorspace: default} + +shared_views: + - ! {name: Un-tone-mapped, view_transform: Un-tone-mapped, display_colorspace: } + - ! {name: Raw, colorspace: Raw} + +displays: + sRGB: + - ! [Un-tone-mapped, Raw] + Gamma 2.2 / Rec.709: + - ! [ Un-tone-mapped, Raw] + +view_transforms: + - ! + name: Un-tone-mapped + from_scene_reference: ! {matrix: [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]} + +inactive_colorspaces: [scene-linear Rec.709-sRGB, ACES2065-1] + +display_colorspaces: + - ! + name: CIE-XYZ D65 + encoding: display-linear + isdata: false + to_display_reference: ! {matrix: [ 3.240969941905, -1.537383177570, -0.498610760293, 0, -0.969243636281, 1.875967501508, 0.041555057407, 0, 0.055630079697, -0.203976958889, 1.056971514243, 0, 0, 0, 0, 1 ]} + + - ! + name: display-linear Rec.709-sRGB + description: | + Display reference space + isdata: false + encoding: display-linear + + - ! + name: sRGB + isdata: false + categories: [ file-io ] + encoding: sdr-video + from_display_reference: ! + children: + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + - ! {min_in_value: 0., min_out_value: 0., max_in_value: 1., max_out_value: 1.} + +colorspaces: + - ! + name: Raw + isdata: true + categories: [ file-io ] + encoding: data + + - ! + name: ACES2065-1 + isdata: false + encoding: scene-linear + to_scene_reference: ! {matrix: [ 2.521686186744, -1.134130988240, -0.387555198504, 0, -0.276479914230, 1.372719087668, -0.096239173438, 0, -0.015378064966, -0.152975335867, 1.168353400833, 0, 0, 0, 0, 1 ]} + + - ! + name: scene-linear Rec.709-sRGB + description: | + Scene-linear Rec.709 or sRGB primaries -- ** This is the scene reference space ** + isdata: false + categories: [ file-io, working-space ] + encoding: scene-linear + + - ! + name: scene-linear P3-D65 + aliases: [lin_p3d65, Utility - Linear - P3-D65] + isdata: false + encoding: scene-linear + to_scene_reference: ! {matrix: [ 1.224940176281e+00, -2.249401762806e-01, 0, 0, -4.205695470969e-02, 1.042056954710e+00, 0, 0, -1.963755459033e-02, -7.863604555063e-02, 1.098273600141e+00, 0, 0, 0, 0, 1 ]} + + - ! + name: scene-linear Rec.2020 + isdata: false + encoding: scene-linear + from_scene_reference: ! {matrix: [ 0.627403895935, 0.329283038378, 0.043313065687, 0 , 0.069097289358, 0.919540395075, 0.011362315566, 0, 0.016391438875, 0.088013307877, 0.895595253248, 0, 0, 0, 0, 1 ]} + + - ! + name: texture sRGB + isdata: false + categories: [ file-io ] + encoding: sdr-video + from_scene_reference: ! + children: + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + - ! {min_in_value: 0., min_out_value: 0., max_in_value: 1., max_out_value: 1.} +)" }; + + std::istringstream is; + is.str(CONFIG); + OCIO::ConstConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + + OCIO::ConfigRcPtr editableCfg = cfg->createEditableCopy(); + + OCIO::ConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default")->createEditableCopy(); + + // Make all of the supported linear color spaces in the built-in config inactive. + // These spaces are referenced by name in the heuristics, so it should all still work. + // This is different than the inactive color spaces in the user's config, which the + // heuristics generally ignore (unless it's the reference space). (This should not + // affect the tests below, it simply confirms that inactive spaces are working as + // expected.) + builtinConfig->setInactiveColorSpaces( + "ACES2065-1, ACEScg, Linear Rec.709 (sRGB), Linear P3-D65, Linear Rec.2020, CIE-XYZ-D65, sRGB - Display" + ); + + // + // Test IdentifyBuiltinColorSpace. + // + + // The initial editableCfg inactive color spaces are: "scene-linear Rec.709-sRGB, ACES2065-1". + + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.2020"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.2020")); + } + + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear P3-D65"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear P3-D65")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1, texture sRGB"); + { + // Uses "Linear P3-D65" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("texture sRGB")); + } + + // The color space is present in editableCfg but it's inactive, so not found. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1"), + OCIO::Exception, + "Heuristics were not able to find an equivalent to the requested color space: ACES2065-1." + ); + } + + // Use interchange role rather than heuristics. + + editableCfg->setRole("aces_interchange", "ACES2065-1"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("texture sRGB")); + } + + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces(""); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap0"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES2065-1")); } -} \ No newline at end of file + editableCfg->setRole("aces_interchange", ""); + + // Display-referred spaces won't work via heuristics, need the interchange role. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } + editableCfg->setRole("cie_xyz_d65_interchange", "CIE-XYZ D65"); + { + // Note that it still works even if the built-in color space is inactive (though not for the source). + const std::string inactives{builtinConfig->getInactiveColorSpaces()}; + OCIO_CHECK_EQUAL(StringUtils::Find(inactives, "sRGB - Display"), 88); + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB")); + } + editableCfg->setRole("cie_xyz_d65_interchange", ""); + + // Check handling of color space that isn't in the built-in config. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "does not exist"), + OCIO::Exception, + "Built-in config does not contain the requested color space: does not exist." + ); + } + + // + // Test IdentifyInterchangeSpace. + // + + editableCfg->setInactiveColorSpaces("scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "scene-linear Rec.709-sRGB", + builtinConfig, "lin_rec709_srgb"); // Aliases work. + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("texture sRGB, scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "Linear P3-D65" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "lin_p3d65", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("scene-linear P3-D65, texture sRGB, scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "Linear Rec.2020" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", // A data space, but it works. + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("scene-linear P3-D65, texture sRGB, scene-linear Rec.709-sRGB"); + { + // Uses "ACES2065-1" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", + builtinConfig, "Raw"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "CIE-XYZ D65", + builtinConfig, "CIE-XYZ-D65"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } +} diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index 76b668fc8e..5f12de688e 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -5920,7 +5920,7 @@ OCIO_ADD_TEST(Config, inactive_color_space_read_write) } } -OCIO_ADD_TEST(Config, two_configs) +OCIO_ADD_TEST(Config, get_processor_from_two_configs) { constexpr const char * SIMPLE_CONFIG1{ R"( ocio_profile_version: 2 @@ -5933,6 +5933,11 @@ ocio_profile_version: 2 aces_interchange: aces1 cie_xyz_d65_interchange: display1 +view_transforms: + - ! + name: vt1 + from_scene_reference: ! {min_in_value: 0., min_out_value: 0.} + colorspaces: - ! name: raw1 @@ -5948,6 +5953,10 @@ ocio_profile_version: 2 allocation: uniform from_scene_reference: ! {value: [1.101, 1.202, 1.303, 1.404]} + - ! + name: data_space + isdata: true + display_colorspaces: - ! name: display1 @@ -6008,8 +6017,8 @@ ocio_profile_version: 2 is.str(SIMPLE_CONFIG2); OCIO_CHECK_NO_THROW(config2 = OCIO::Config::CreateFromStream(is)); + // Just specify color spaces and have OCIO use the interchange roles. OCIO::ConstProcessorRcPtr p; - // NB: Although they have the same name, they are in different configs and are different ColorSpaces. OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "test1", config2, "test2")); OCIO_REQUIRE_ASSERT(p); auto group = p->createGroupTransform(); @@ -6069,7 +6078,35 @@ ocio_profile_version: 2 auto l3 = OCIO_DYNAMIC_POINTER_CAST(t3); OCIO_CHECK_ASSERT(l3); - OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display2", config2, "test2"), + // If one of the spaces is a data space, the whole result must be a no-op. + OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "data_space", config2, "test2")); + OCIO_REQUIRE_ASSERT(p); + group = p->createGroupTransform(); + OCIO_REQUIRE_EQUAL(group->getNumTransforms(), 0); + + // Mixed Scene- and Display-referred interchange spaces. + OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "display2", config2, "test2")); + OCIO_REQUIRE_ASSERT(p); + group = p->createGroupTransform(); + OCIO_REQUIRE_EQUAL(group->getNumTransforms(), 5); + t0 = group->getTransform(0); + f0 = OCIO_DYNAMIC_POINTER_CAST(t0); + OCIO_CHECK_ASSERT(f0); + t1 = group->getTransform(1); + auto r1 = OCIO_DYNAMIC_POINTER_CAST(t1); + OCIO_CHECK_ASSERT(c1); + t2 = group->getTransform(2); + e2 = OCIO_DYNAMIC_POINTER_CAST(t2); + OCIO_CHECK_ASSERT(e2); + t3 = group->getTransform(3); + auto r3 = OCIO_DYNAMIC_POINTER_CAST(t3); + OCIO_CHECK_ASSERT(r3); + auto t4 = group->getTransform(4); + auto m4 = OCIO_DYNAMIC_POINTER_CAST(t4); + OCIO_CHECK_ASSERT(m4); + + // Second config has no view transform but is asked to connect a display color space to aces_interchange. + OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "test1", config2, "display3"), OCIO::Exception, "There is no view transform between the main scene-referred space " "and the display-referred space"); @@ -6092,6 +6129,12 @@ ocio_profile_version: 2 name: test allocation: uniform from_scene_reference: ! {offset: [0.11, 0.12, 0.13, 0]} + +display_colorspaces: + - ! + name: display5 + allocation: uniform + from_display_reference: ! {value: 2.4} )" }; is.clear(); @@ -6101,11 +6144,15 @@ ocio_profile_version: 2 OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "test1", config3, "test"), OCIO::Exception, - "The role 'aces_interchange' is missing in the destination config"); + "The required role 'aces_interchange' is missing from the source and/or destination config."); OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display1", config3, "test"), OCIO::Exception, - "The role 'cie_xyz_d65_interchange' is missing in the destination config"); + "The required role 'aces_interchange' is missing from the source and/or destination config."); + + OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display1", config3, "display5"), + OCIO::Exception, + "The required role 'cie_xyz_d65_interchange' is missing from the source and/or destination config."); } From 834b0f9a67f2e242c935f21d594c92675eb71b6c Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:42:15 -0400 Subject: [PATCH 07/22] Remove unused method Signed-off-by: Doug Walker --- include/OpenColorIO/OpenColorIO.h | 15 --------------- src/OpenColorIO/Processor.cpp | 31 ------------------------------- 2 files changed, 46 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 784140904e..055f2b57c4 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -2504,21 +2504,6 @@ class OCIOEXPORT Processor ConstCPUProcessorRcPtr getOptimizedCPUProcessor(BitDepth inBitDepth, BitDepth outBitDepth, OptimizationFlags oFlags) const; - /** - * \brief Returns whether the two processors are equivalent. - * - * \param p1 First processor to compare. - * \param p2 Second processor to compare. - * \param rgbaValues RGBA values to use to test the processors. - * \param numValues Number of RGBA values. - * \param tolerance Tolerance of the comparaison. - * \return True or false depending on whether the two processors are equivalent. - */ -// static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, -// ConstProcessorRcPtr & p2, -// float * rgbaValues, -// size_t numValues, -// float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index 9998bb319a..dab0287dfb 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -15,7 +15,6 @@ #include "OpBuilders.h" #include "ops/noop/NoOps.h" #include "Processor.h" -#include "MathUtils.h" #include "TransformBuilder.h" #include "utils/StringUtils.h" @@ -649,34 +648,4 @@ void Processor::Impl::computeMetadata() } } -bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float absTolerance) -{ - ProcessorRcPtr proc = Processor::Create(); - - proc->getImpl()->concatenate(p1, p2); - - // RGBA values. - std::vector out(numValues*4); - - PackedImageDesc desc(rgbaValues, (long) numValues, 1, CHANNEL_ORDERING_RGBA); - PackedImageDesc descDst(&out[0], (long) numValues, 1, CHANNEL_ORDERING_RGBA); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - if (!EqualWithAbsError(rgbaValues[i], out[i], absTolerance)) - { - return false; - } - } - - return true; -} - } // namespace OCIO_NAMESPACE From 968fc5417f6c8b4c845ea30249b94b1ba8e58d95 Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:45:23 -0400 Subject: [PATCH 08/22] Remove unused method, tk 2 Signed-off-by: Doug Walker --- src/OpenColorIO/ConfigUtils.h | 2 -- src/OpenColorIO/Processor.h | 7 ------- 2 files changed, 9 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 3963f88d38..3d7d00701b 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -6,8 +6,6 @@ #include -#include - namespace OCIO_NAMESPACE { diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index cad0e6c577..2bbddd9f2a 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -105,13 +105,6 @@ class Processor::Impl void computeMetadata(); - // Returns whether the specified processors are equivalents. - static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float absTolerance); - protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, OptimizationFlags oFlags) const; From 1ddda801cc6ff1c21ef55da0f22b831ba7b09f8b Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Thu, 13 Apr 2023 20:16:14 -0400 Subject: [PATCH 09/22] Tweak comments Signed-off-by: Doug Walker --- src/OpenColorIO/ConfigUtils.cpp | 62 ++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 7a7d1f26ee..959e0eb6c5 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -40,6 +40,9 @@ const char * getBuiltinLinearSpaceName(int index) return builtinLinearSpaces[Clamp(index, 0, 4)]; } +// The number of items available from getBuiltinLinearSpaceName. (Obviously, update this +// integer if that list changes.) +// inline int getNumberOfbuiltinLinearSpaces() { return 5; @@ -59,14 +62,15 @@ inline int getNumberOfbuiltinLinearSpaces() // // This function does NOT use any heuristics. // -// \param[out] srcInterchangeCSName -- Name of the interchange color space from the src config. -// \param[out] dstInterchangeCSName -- Name of the interchange color space from the dst config. -// \param srcConfig -- Source config object. -// \param srcName -- Name of the color space to be converted from the source config. -// May be empty if the source color space is unknown. -// \param dstConfig -- Destination config object. -// \param dstName -- Name of the color space to be converted from the destination config. -// \return True if the necessary interchange roles were found. +// [out] srcInterchangeCSName -- Name of the interchange color space from the src config. +// [out] dstInterchangeCSName -- Name of the interchange color space from the dst config. +// [out] interchangeType -- The ReferenceSpaceType of the interchange color space. +// srcConfig -- Source config object. +// srcName -- Name of the color space to be converted from the source config. +// May be empty if the source color space is unknown. +// dstConfig -- Destination config object. +// dstName -- Name of the color space to be converted from the destination config. +// Returns True if the necessary interchange roles were found. // bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, const char ** dstInterchangeCSName, @@ -273,11 +277,11 @@ bool hasNoTransform(const ConstColorSpaceRcPtr & cs) // If a match is found, it indicates what reference space is used by the config. // Return the index into the list of built-in linear spaces, or -1 if not found. // -// \param srcConfig -- Source config object. -// \param srcRefName -- Name of a scene-referred reference color space in the src config. -// \param cs -- Color space from the source config to test. -// \param builtinConfig -- The built-in config object. -// \return The index into the list of built-in linear spaces. +// srcConfig -- Source config object. +// srcRefName -- Name of a scene-referred reference color space in the src config. +// cs -- Color space from the source config to test. +// builtinConfig -- The built-in config object. +// Returns the index into the list of built-in linear spaces. // int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, const char * srcRefName, @@ -341,11 +345,11 @@ int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, // is used by the config. Return the index into the list of built-in linear spaces, // or -1 if not found. // -// \param srcConfig -- Source config object. -// \param srcRefName -- Name of a scene-referred reference color space in the src config. -// \param cs -- Color space from the source config to test. -// \param builtinConfig -- The built-in config object. -// \return The index into the list of built-in linear spaces. +// srcConfig -- Source config object. +// srcRefName -- Name of a scene-referred reference color space in the src config. +// cs -- Color space from the source config to test. +// builtinConfig -- The built-in config object. +// Returns the index into the list of built-in linear spaces. // int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, const char * srcRefName, @@ -459,14 +463,14 @@ int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, // that should be used to convert from the src color space to the built-in color space, // or vice-versa. Throws if no suitable spaces are found. // -// \param[out] srcInterchange -- Name of the interchange color space from the source config. -// \param[out] builtinInterchange -- Name of the interchange color space from the built-in config. -// \param srcConfig -- Source config object. -// \param srcColorSpaceName -- Name of the color space to be converted from the source config. -// \param builtinConfig -- Built-in config object. -// \param builtinColorSpaceName -- Name of the color space to be converted from the built-in config. +// [out] srcInterchange -- Name of the interchange color space from the source config. +// [out] builtinInterchange -- Name of the interchange color space from the built-in config. +// srcConfig -- Source config object. +// srcColorSpaceName -- Name of the color space to be converted from the source config. +// builtinConfig -- Built-in config object. +// builtinColorSpaceName -- Name of the color space to be converted from the built-in config. // -// \throw Exception if an interchange space cannot be found. +// Throws an exception if an interchange space cannot be found. // void IdentifyInterchangeSpace(const char ** srcInterchange, const char ** builtinInterchange, @@ -574,10 +578,10 @@ void IdentifyInterchangeSpace(const char ** srcInterchange, // specified color space from the provided built-in config. Only active color spaces // are searched. // -// \param srcConfig -- The source config object to search. -// \param builtinConfig -- The built-in config object containing the desired color space. -// \param builtinColorSpaceName -- Name of the desired color space from the built-in config. -// \return The name of the color space in the source config. +// srcConfig -- The source config object to search. +// builtinConfig -- The built-in config object containing the desired color space. +// builtinColorSpaceName -- Name of the desired color space from the built-in config. +// Returns the name of the color space in the source config. // // \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. // From 64874422fbd2446ce6413a57c767c348cf2e5621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 5 May 2023 09:52:28 -0400 Subject: [PATCH 10/22] Python binding for IdentifyInterchangeSpace and IdentifyBuiltinColorSpace methods. Adding a couple python unit tests to cover the new methods (same unit test as the C++ side, but not all of them) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/bindings/python/PyConfig.cpp | 30 ++++++ tests/python/ColorSpaceTest.py | 166 +++++++++++++++++++++++++++++-- 2 files changed, 185 insertions(+), 11 deletions(-) diff --git a/src/bindings/python/PyConfig.cpp b/src/bindings/python/PyConfig.cpp index 48270f2eca..8dcf7d4b47 100644 --- a/src/bindings/python/PyConfig.cpp +++ b/src/bindings/python/PyConfig.cpp @@ -334,6 +334,36 @@ void bindPyConfig(py::module & m) DOC(Config, setInactiveColorSpaces)) .def("getInactiveColorSpaces", &Config::getInactiveColorSpaces, DOC(Config, getInactiveColorSpaces)) + .def_static("IdentifyBuiltinColorSpace", [](const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) + { + return Config::IdentifyBuiltinColorSpace(srcConfig, + builtinConfig, + builtinColorSpaceName); + }, + "srcConfig"_a, "builtinConfig"_a, "builtinColorSpaceName"_a, + DOC(Config, IdentifyBuiltinColorSpace)) + + .def_static("IdentifyInterchangeSpace", [](const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) + { + const char * srcInterchangePtr = nullptr; + const char * builtinInterchangePtr = nullptr; + + Config::IdentifyInterchangeSpace(&srcInterchangePtr, + &builtinInterchangePtr, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); + // Return the tuple by value which copies the strings values. + return std::make_tuple(std::string(srcInterchangePtr), std::string(builtinInterchangePtr)); + }, + "srcConfig"_a, "srcColorSpaceName"_a, "builtinConfig"_a, "builtinColorSpaceName"_a, + DOC(Config, IdentifyInterchangeSpace)) // Roles .def("setRole", &Config::setRole, "role"_a, "colorSpaceName"_a, diff --git a/tests/python/ColorSpaceTest.py b/tests/python/ColorSpaceTest.py index 465539886c..bfa4d79bbf 100644 --- a/tests/python/ColorSpaceTest.py +++ b/tests/python/ColorSpaceTest.py @@ -7,7 +7,11 @@ import sys import PyOpenColorIO as OCIO -from UnitTestUtils import SIMPLE_CONFIG, TEST_NAMES, TEST_DESCS, TEST_CATEGORIES +from UnitTestUtils import (SIMPLE_CONFIG, + TEST_NAMES, + TEST_DESCS, + TEST_CATEGORIES, + TEST_DATAFILES_DIR) class ColorSpaceTest(unittest.TestCase): @@ -627,7 +631,37 @@ def test_processor_to_known_colorspace(self): default: raw scene_linear: ref_cs +display_colorspaces: + - ! + name: CIE-XYZ-D65 + description: The CIE XYZ (D65) display connection colorspace. + isdata: false + + - ! + name: sRGB - Display CS + description: Convert CIE XYZ (D65 white) to sRGB (piecewise EOTF) + isdata: false + from_display_reference: ! {style: DISPLAY - CIE-XYZ-D65_to_sRGB} + colorspaces: + # Put a couple of test color space first in the config since the heuristics stop upon success. + + - ! + name: File color space + description: Verify that that FileTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: lut1d_green.ctf} + + - ! + name: CS Transform color space + description: Verify that that ColorSpaceTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: ref_cs, dst: not sRGB} + - ! name: raw description: A data colorspace (should not be used). @@ -635,7 +669,7 @@ def test_processor_to_known_colorspace(self): - ! name: ref_cs - description: The reference colorspace. + description: The reference colorspace, ACES2065-1. isdata: false - ! @@ -660,23 +694,23 @@ def test_processor_to_known_colorspace(self): - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. isdata: false from_scene_reference: ! - name: AP0 to sRGB Rec.709 + name: AP0 to sRGB Encoded AP1 - Texture children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. isdata: false from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture + name: AP0 to sRGB Rec.709 children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} """ @@ -715,6 +749,8 @@ def check_processor_inv(self, p): cfg = OCIO.Config.CreateFromStream(CONFIG) + cfg.setSearchPath(TEST_DATAFILES_DIR) + # Make all color spaces suitable for the heuristics inactive. # The heuristics don't look at inactive color spaces. cfg.setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, Texture -- sRGB") @@ -754,4 +790,112 @@ def check_processor_inv(self, p): # Test linear color space to_ref direction. cfg.setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB") p = OCIO.Config.GetProcessorFromBuiltinColorSpace(builtin_csname, cfg, src_csname) - check_processor_inv(self, p) \ No newline at end of file + check_processor_inv(self, p) + + + editableCfg = copy.deepcopy(cfg) + builtinConfig = OCIO.Config.CreateFromFile("ocio://default") + + # + # Test IdentifyBuiltinColorSpace. + # + + editableCfg.setInactiveColorSpaces("") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + self.assertEqual(csname, "ACES cg") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture") + self.assertEqual(csname, "Texture -- sRGB") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1") + self.assertEqual(csname, "ref_cs") + + editableCfg.setInactiveColorSpaces("Texture -- sRGB, ref_cs") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)") + self.assertEqual(csname, "Linear ITU-R BT.709") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct") + self.assertEqual(csname, "CS Transform color space") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1") + self.assertEqual(csname, "ACES cg") + + # Display-referred spaces are not supported unless the display-referred interchange + # role is present. + + with self.assertRaises(OCIO.Exception) as cm: + cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual( + str(cm.exception), + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ) + + # The next three cases directly use the interchange roles rather than heuristics. + + # With the required role, it then works. + editableCfg.setRole("cie_xyz_d65_interchange", "CIE-XYZ-D65") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual(csname, "sRGB - Display CS") + + # Must continue to work if the color space for the interchange role is inactive. + editableCfg.setInactiveColorSpaces("CIE-XYZ-D65") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual(csname, "sRGB - Display CS") + + # Test the scene-referred interchange role (and even make it inactive). + editableCfg.setRole("aces_interchange", "ref_cs") + editableCfg.setInactiveColorSpaces("ref_cs") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + self.assertEqual(csname, "ACES cg") + + + # + # Test IdentifyInterchangeSpace. + # + + # Uses "ACEScg" to find the reference. + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb") # Aliases work. + self.assertEqual(spaces[0], "ref_cs") + self.assertEqual(spaces[1], "ACES2065-1") + + # Set the interchange role. In order to prove that it is being used rather than + # the heuristics, set it to something wrong and check that it gets returned anyway. + editableCfg.setRole("aces_interchange", "Texture -- sRGB") + + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb") + self.assertEqual(spaces[0], "Texture -- sRGB") + self.assertEqual(spaces[1], "ACES2065-1") + + # Unset the interchange role, so the heuristics will be used for the other tests. + editableCfg.setRole("aces_interchange", "") + + # Check what happens if a totally bogus config is passed for the built-in config. + # (It fails in the first heuristic that tries to use one of the known built-in spaces.) + rawCfg = OCIO.Config.CreateRaw() + + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Raw", rawCfg, "raw") + self.assertEqual( + str(cm.exception), + "Could not find destination color space 'sRGB - Texture'." + ) + + # Check what happens if the source color space doesn't exist. + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "raw") + self.assertEqual( + str(cm.exception), + "Could not find source color space 'Foo'." + ) + + # Check what happens if the destination color space doesn't exist. + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "") + self.assertEqual( + str(cm.exception), + "Could not find destination color space ''." + ) \ No newline at end of file From b393f47814174bc4fe21c3426bb8b650ee7363df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 23 Dec 2022 13:24:03 -0500 Subject: [PATCH 11/22] - Implementation of identifyInterchangeSpace, identifyBuiltinColorSpace and AreProcessorsEquivalent. - Remove some include of Processor.h where it wasn't needed. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 33 + src/OpenColorIO/CMakeLists.txt | 1 + src/OpenColorIO/Config.cpp | 705 +++++++----------- src/OpenColorIO/ConfigUtils.cpp | 253 +++++++ src/OpenColorIO/ConfigUtils.h | 37 + src/OpenColorIO/Processor.cpp | 40 + src/OpenColorIO/Processor.h | 7 + src/OpenColorIO/Transform.cpp | 1 - src/OpenColorIO/fileformats/FileFormatICC.cpp | 1 + src/OpenColorIO/transforms/FileTransform.h | 1 - tests/cpu/CMakeLists.txt | 1 + tests/cpu/ColorSpace_tests.cpp | 102 +++ 12 files changed, 733 insertions(+), 449 deletions(-) create mode 100644 src/OpenColorIO/ConfigUtils.cpp create mode 100644 src/OpenColorIO/ConfigUtils.h diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 313b9c6316..ec72352067 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -695,6 +695,24 @@ class OCIOEXPORT Config */ bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const; + /** + * \brief Identify the interchange space of the source config and the default built-in config. + * + * \param srcInterchange Interchange space from the source config (output). + * \param builtinInterchange Interchange space from the default built-in config (output). + * + * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. + */ + void identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const; + /** + * \brief Find the name of the color space in the source config that is the same as + * a color space in the default built-in config. + * + * \param builtinColorSpaceName Color space name in the built-in config. + * \return const char* Matching color space from the source config. Empty if not found. + */ + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; + // // Roles // @@ -2455,6 +2473,21 @@ class OCIOEXPORT Processor ConstCPUProcessorRcPtr getOptimizedCPUProcessor(BitDepth inBitDepth, BitDepth outBitDepth, OptimizationFlags oFlags) const; + /** + * \brief Returns whether the two processors are equivalent. + * + * \param p1 First processor to compare. + * \param p2 Second processor to compare. + * \param rgbaValues RGBA values to use to test the processors. + * \param numValues Number of RGBA values. + * \param tolerance Tolerance of the comparaison. + * \return True or false depending on whether the two processors are equivalent. + */ + static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/CMakeLists.txt b/src/OpenColorIO/CMakeLists.txt index 60ca4f9614..08252e860b 100755 --- a/src/OpenColorIO/CMakeLists.txt +++ b/src/OpenColorIO/CMakeLists.txt @@ -17,6 +17,7 @@ set(SOURCES ColorSpace.cpp ColorSpaceSet.cpp Config.cpp + ConfigUtils.cpp Context.cpp ContextVariableUtils.cpp CPUProcessor.cpp diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index dba7d916cf..80399fcb1b 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -15,6 +15,7 @@ #include #include "builtinconfigs/BuiltinConfigRegistry.h" +#include "ConfigUtils.h" #include "ContextVariableUtils.h" #include "Display.h" #include "fileformats/FileFormatICC.h" @@ -1095,302 +1096,14 @@ class Config::Impl return -1; } - std::string getRefSpace(ConstConfigRcPtr & cfg) const - { - // Find a color space where isData is false and it has neither a to_ref or from_ref - // transform. - auto nbCs = cfg->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = cfg->getColorSpace(cfg->getColorSpaceNameByIndex(i)); - if (cs->isData()) - { - continue; - } - - auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (t != nullptr) - { - continue; - } - - t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (t != nullptr) - { - continue; - } - - return cs->getName(); - } - return ""; - } - - bool containsSRGB(ConstColorSpaceRcPtr & cs) const - { - std::string name = StringUtils::Lower(cs->getName()); - if (StringUtils::Find(name, "srgb") != std::string::npos) - { - return true; - } - - size_t nbOfAliases = cs->getNumAliases(); - for (size_t i = 0; i < nbOfAliases; i++) - { - if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) - { - return true; - } - } - - return false; - } - - ConstProcessorRcPtr getRefToSRGBTransform(ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) const - { - // Build reference space of the given prims to sRGB transform. - std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(refColorSpaceName.c_str()); - csTransform->setDst(srgbColorSpaceName.c_str()); - - ConstProcessorRcPtr proc = getProcessorWithoutCaching(*builtinConfig, - csTransform, - TRANSFORM_DIR_FORWARD); - return proc; - } - - bool isIdentityTransform(ProcessorRcPtr & proc, std::vector & vals) const - { - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return false; - } - } - - return true; - } - - std::string getReferenceSpaceFromLinearSpace(ConstConfigRcPtr & srcConfig, - ConstColorSpaceRcPtr & cs, - ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) const - { - // If the color space is a recognized linear space, return the reference space used by - // the config. - std::string refSpace; - bool toRefDirection = true; - auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (!srcTransform) - { - srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (srcTransform) - { - toRefDirection = false; - } - else - { - return ""; - } - } - - // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is - // enough of an identity. - std::vector vals = { 0.7f, 0.4f, 0.02f, - 0.02f, 0.6f, 0.2f, - 0.3f, 0.02f, 0.5f, - 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f }; - - // Generate matrices between all combinations of the Built-in linear color spaces. - // Then combine these with the transform from the current color space to see if the result is - // an identity. If so, then it identifies the reference space being used by the source config. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) - { - for (size_t j = 0; j < builtinLinearSpaces.size(); j++) - { - if (i != j) - { - ConstProcessorRcPtr p1 = getProcessorWithoutCaching(*srcConfig, - srcTransform, - TRANSFORM_DIR_FORWARD); - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j].c_str()); - csTransform->setDst(builtinLinearSpaces[i].c_str()); - - ConstProcessorRcPtr p2 = getProcessorWithoutCaching(*builtinConfig, - csTransform, - TRANSFORM_DIR_FORWARD); - - ProcessorRcPtr proc = Processor::Create(); - proc->getImpl()->concatenate(p1, p2); - - if (isIdentityTransform(proc, vals)) - { - if (toRefDirection) - { - refSpace = builtinLinearSpaces[j]; - } - else - { - refSpace = builtinLinearSpaces[i]; - } - - return refSpace; - } - } - } - } - - return ""; - } - - std::string getReferenceSpaceFromSRGBSpace(ConstConfigRcPtr & config, - ConstColorSpaceRcPtr cs, - ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) const - { - // If the color space is an sRGB texture space, return the reference space used by the config. - - // Get a transform in the to-reference direction. - ConstTransformRcPtr toRefTransform; - ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (ctransform) - { - toRefTransform = ctransform; - } - else - { - ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (ctransform) - { - TransformRcPtr transform = ctransform->createEditableCopy(); - transform->setDirection(TRANSFORM_DIR_INVERSE); - toRefTransform = transform; - } - else - { - // Both directions missing. - return ""; - } - } - - // First check if it has the right non-linearity. The objective is to fail quickly on color - // spaces that are definitely not sRGB before proceeding to the longer test of guessing the - // reference space primaries. - - // Break point is at 0.039286, so include at least one value below this. - std::vector vals = - { - 0.5f, 0.5f, 0.5f, - 0.03f, 0.03f, 0.03f, - 0.25f, 0.25f, 0.25f, - 0.75f, 0.75f, 0.75f, - 0.f, 0.f, 0.f, - 1.f, 1.f , 1.f - }; - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstProcessorRcPtr proc = getProcessorWithoutCaching(*config, - toRefTransform, - TRANSFORM_DIR_FORWARD); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - // Apply the sRGB function (linear to non-lin). - // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. - if (out[i] <= 0.0030399346397784323f) - { - out[i] *= 12.923210180787857f; - } - else - { - out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; - } - - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return ""; - } - } - - // - // Then try the various primaries for the reference space. - // - - // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact - // converting sRGB texture values to the candidate reference space. It includes 0.02 which is - // on the sRGB linear segment, color values, and neutral values. - vals = { 0.7f, 0.4f, 0.02f, - 0.02f, 0.6f, 0.2f, - 0.3f, 0.02f, 0.5f, - 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f }; - std::string refSpace = ""; - ConstProcessorRcPtr fromRefProc; - if (toRefTransform) - { - // The color space has the sRGB non-linearity. Now try combining the transform with a - // transform from the Built-in config that goes from a variety of reference spaces to an - // sRGB texture space. If the result is an identity, then that tells what the source config - // reference space is. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) - { - fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); - - ConstProcessorRcPtr toRefProc = getProcessorWithoutCaching(*config, - toRefTransform, - TRANSFORM_DIR_FORWARD); - - ProcessorRcPtr proc = Processor::Create(); - proc->getImpl()->concatenate(toRefProc, fromRefProc); - - if (isIdentityTransform(proc, vals)) - { - refSpace = builtinLinearSpaces[i]; - } - } - } - - return refSpace; - } - - ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + static ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, const char * srcColorSpaceName, const char * builtinColorSpaceName, - TransformDirection direction) const + TransformDirection direction) { // Use the Default config as the Built-in config to interpret the known color space name. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) { std::ostringstream os; @@ -1420,79 +1133,32 @@ class Config::Impl } // otherwise, do nothing and continue. } - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(srcConfig); - if (refColorSpaceName.empty()) - { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); - } + char srcInterchange[255]; + char builtinInterchange[255]; - // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = srcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) - { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(srcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } + srcConfig->identifyInterchangeSpace(srcInterchange, builtinInterchange); - if (refColorSpacePrims.empty()) - { - // Check for a linear space with known primaries. - nbCs = srcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); - if (srcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(srcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - } - - if (!refColorSpacePrims.empty()) + if (builtinInterchange && builtinInterchange[0]) { - // Use the interchange spaces to get the processor. - std::string srcInterchange = refColorSpaceName; - std::string builtinInterchange = refColorSpacePrims; - ConstProcessorRcPtr proc; if (direction == TRANSFORM_DIR_FORWARD) { proc = Config::GetProcessorFromConfigs(srcConfig, srcColorSpaceName, - srcInterchange.c_str(), + srcInterchange, builtinConfig, builtinColorSpaceName, - builtinInterchange.c_str()); + builtinInterchange); } else if (direction == TRANSFORM_DIR_INVERSE) { proc = Config::GetProcessorFromConfigs(builtinConfig, builtinColorSpaceName, - builtinInterchange.c_str(), + builtinInterchange, srcConfig, srcColorSpaceName, - srcInterchange.c_str()); + srcInterchange); } return proc; } @@ -3253,6 +2919,151 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe return true; } +void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const +{ + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + std::vector builtinLinearSpaces = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Get the name of (one of) the reference spaces. + std::string refColorSpaceName = getRefSpace(*eSrcConfig); + if (refColorSpaceName.empty()) + { + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } + + // Check for an sRGB texture space. + std::string refColorSpacePrims = ""; + int nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) + { + refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + + if (refColorSpacePrims.empty()) + { + // Check for a linear space with known primaries. + nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + { + refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + } + + if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + { + // Copy interchange role from source config. + std::memcpy(srcInterchange, refColorSpaceName.c_str(), refColorSpaceName.size()); + // Copy interchange role from built-in config. + std::memcpy(builtinInterchange, refColorSpacePrims.c_str(), refColorSpacePrims.size()); + + // Terminate the string. + srcInterchange[refColorSpaceName.size()] = '\0'; + builtinInterchange[refColorSpacePrims.size()] = '\0'; + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } +} + +const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const +{ + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + char srcInterchange[255]; + char builtinInterchange[255]; + + // Identify interchange space. + identifyInterchangeSpace(srcInterchange, builtinInterchange); + + // Get processor from that space to the built-in color space. + ConstProcessorRcPtr builtinProc; + if (builtinInterchange && builtinInterchange[0]) + { + builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), + builtinInterchange, + builtinColorSpaceName); + } + + if (builtinProc && srcInterchange && srcInterchange[0]) + { + // Iterate over each color space in the source config. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + int nbCs = getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + // Get processor from that space to its reference and then use isProcessorEquivalent. + // If equivalent, return that color space name. + + ConstColorSpaceRcPtr cs = getColorSpace(getColorSpaceNameByIndex(i)); + const char * csName = cs->getName(); + + ConstProcessorRcPtr proc = getProcessor(getCurrentContext(), + csName, + srcInterchange); + + if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + { + return csName; + } + } + } + + return ""; +} + /////////////////////////////////////////////////////////////////////////// const char * Config::parseColorSpaceFromString(const char * str) const @@ -4973,20 +4784,20 @@ ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr src const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return srcConfig->getImpl()->getProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_FORWARD); + return Config::Impl::getProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_FORWARD); } ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return srcConfig->getImpl()->getProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_INVERSE); + return Config::Impl::getProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_INVERSE); } std::ostream& operator<< (std::ostream& os, const Config& config) @@ -5096,6 +4907,100 @@ void Config::clearProcessorCache() noexcept getImpl()->m_processorCache.clear(); } +void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) +{ + getImpl()->m_context->setConfigIOProxy(ciop); + + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +ConfigIOProxyRcPtr Config::getConfigIOProxy() const +{ + return getImpl()->m_context->getConfigIOProxy(); +} + +bool Config::isArchivable() const +{ + ConstContextRcPtr context = getCurrentContext(); + + // Current archive implementation needs a working directory to look for LUT files and + // working directory must be an absolute path. + const char * workingDirectory = getWorkingDir(); + if ((workingDirectory && !workingDirectory[0]) || !pystring::os::path::isabs(workingDirectory)) + { + return false; + } + + // Utility lambda to check the following criteria. + auto validatePathForArchiving = [](const std::string & path) + { + // Using the normalized path. + const std::string normPath = pystring::os::path::normpath(path); + if ( + // 1) Path may not be absolute. + pystring::os::path::isabs(normPath) || + // 2) Path may not start with double dot ".." (going above working directory). + pystring::startswith(normPath, "..") || + // 3) A context variable may not be located at the start of the path. + (ContainsContextVariables(path) && + (StringUtils::Find(path, "$") == 0 || + StringUtils::Find(path, "%") == 0))) + { + return false; + } + + return true; + }; + + /////////////////////////////// + // Search path verification. // + /////////////////////////////// + // Check that search paths are not absolute nor have context variables outside of config + // working directory. + int numSearchPaths = getNumSearchPaths(); + for (int i = 0; i < numSearchPaths; i++) + { + std::string currentPath = getSearchPath(i); + if (!validatePathForArchiving(currentPath)) + { + // Exit and return false. + return false; + } + } + + ///////////////////////////////// + // FileTransform verification. // + ///////////////////////////////// + ConstTransformVec allTransforms; + getImpl()->getAllInternalTransforms(allTransforms); + + std::set files; + for(const auto & transform : allTransforms) + { + GetFileReferences(files, transform); + } + + // Check that FileTransform sources are not absolute nor have context variables outside of + // config working directory. + for (const auto & path : files) + { + if (!validatePathForArchiving(path)) + { + // Exit and return false. + return false; + } + } + + return true; +} + +void Config::archive(std::ostream & ostream) const +{ + // Using utility functions in OCIOZArchive.cpp. + archiveConfig(ostream, *this, getCurrentContext()->getWorkingDir()); +} + /////////////////////////////////////////////////////////////////////////// // Config::Impl @@ -5591,98 +5496,4 @@ void Config::Impl::checkVersionConsistency() const } } -void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) -{ - getImpl()->m_context->setConfigIOProxy(ciop); - - AutoMutex lock(getImpl()->m_cacheidMutex); - getImpl()->resetCacheIDs(); -} - -ConfigIOProxyRcPtr Config::getConfigIOProxy() const -{ - return getImpl()->m_context->getConfigIOProxy(); -} - -bool Config::isArchivable() const -{ - ConstContextRcPtr context = getCurrentContext(); - - // Current archive implementation needs a working directory to look for LUT files and - // working directory must be an absolute path. - const char * workingDirectory = getWorkingDir(); - if ((workingDirectory && !workingDirectory[0]) || !pystring::os::path::isabs(workingDirectory)) - { - return false; - } - - // Utility lambda to check the following criteria. - auto validatePathForArchiving = [](const std::string & path) - { - // Using the normalized path. - const std::string normPath = pystring::os::path::normpath(path); - if ( - // 1) Path may not be absolute. - pystring::os::path::isabs(normPath) || - // 2) Path may not start with double dot ".." (going above working directory). - pystring::startswith(normPath, "..") || - // 3) A context variable may not be located at the start of the path. - (ContainsContextVariables(path) && - (StringUtils::Find(path, "$") == 0 || - StringUtils::Find(path, "%") == 0))) - { - return false; - } - - return true; - }; - - /////////////////////////////// - // Search path verification. // - /////////////////////////////// - // Check that search paths are not absolute nor have context variables outside of config - // working directory. - int numSearchPaths = getNumSearchPaths(); - for (int i = 0; i < numSearchPaths; i++) - { - std::string currentPath = getSearchPath(i); - if (!validatePathForArchiving(currentPath)) - { - // Exit and return false. - return false; - } - } - - ///////////////////////////////// - // FileTransform verification. // - ///////////////////////////////// - ConstTransformVec allTransforms; - getImpl()->getAllInternalTransforms(allTransforms); - - std::set files; - for(const auto & transform : allTransforms) - { - GetFileReferences(files, transform); - } - - // Check that FileTransform sources are not absolute nor have context variables outside of - // config working directory. - for (const auto & path : files) - { - if (!validatePathForArchiving(path)) - { - // Exit and return false. - return false; - } - } - - return true; -} - -void Config::archive(std::ostream & ostream) const -{ - // Using utility functions in OCIOZArchive.cpp. - archiveConfig(ostream, *this, getCurrentContext()->getWorkingDir()); -} - } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp new file mode 100644 index 0000000000..a0038b7cd9 --- /dev/null +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#include "ConfigUtils.h" +#include "MathUtils.h" +#include "utils/StringUtils.h" + +namespace OCIO_NAMESPACE +{ + bool containsSRGB(ConstColorSpaceRcPtr & cs) + { + std::string name = StringUtils::Lower(cs->getName()); + if (StringUtils::Find(name, "srgb") != std::string::npos) + { + return true; + } + + size_t nbOfAliases = cs->getNumAliases(); + for (size_t i = 0; i < nbOfAliases; i++) + { + if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) + { + return true; + } + } + + return false; + } + + std::string getRefSpace(const Config & cfg) + { + // Find a color space where isData is false and it has neither a to_ref or from_ref + // transform. + auto nbCs = cfg.getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); + if (cs->isData()) + { + continue; + } + + auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (t != nullptr) + { + continue; + } + + t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (t != nullptr) + { + continue; + } + + return cs->getName(); + } + return ""; + } + + ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName) + { + // Build reference space of the given prims to sRGB transform. + std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; + + ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); + csTransform->setSrc(refColorSpaceName.c_str()); + csTransform->setDst(srgbColorSpaceName.c_str()); + + ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); + return proc; + } + + std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces) + { + // If the color space is a recognized linear space, return the reference space used by + // the config. + std::string refSpace; + bool toRefDirection = true; + auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (!srcTransform) + { + srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (srcTransform) + { + toRefDirection = false; + } + else + { + return ""; + } + } + + // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is + // enough of an identity. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + + // Generate matrices between all combinations of the Built-in linear color spaces. + // Then combine these with the transform from the current color space to see if the result is + // an identity. If so, then it identifies the reference space being used by the source config. + for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + { + for (size_t j = 0; j < builtinLinearSpaces.size(); j++) + { + if (i != j) + { + ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, + TRANSFORM_DIR_FORWARD); + + ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); + csTransform->setSrc(builtinLinearSpaces[j].c_str()); + csTransform->setDst(builtinLinearSpaces[i].c_str()); + + ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, + TRANSFORM_DIR_FORWARD); + + if (Processor::AreProcessorsEquivalent(p1, p2, &vals[0], 5, 1e-3f)) + { + if (toRefDirection) + { + refSpace = builtinLinearSpaces[j]; + } + else + { + refSpace = builtinLinearSpaces[i]; + } + + return refSpace; + } + } + } + } + + return ""; + } + + std::string getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces) + { + // If the color space is an sRGB texture space, return the reference space used by the config. + + // Get a transform in the to-reference direction. + ConstTransformRcPtr toRefTransform; + ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (ctransform) + { + toRefTransform = ctransform; + } + else + { + ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (ctransform) + { + TransformRcPtr transform = ctransform->createEditableCopy(); + transform->setDirection(TRANSFORM_DIR_INVERSE); + toRefTransform = transform; + } + else + { + // Both directions missing. + return ""; + } + } + + // First check if it has the right non-linearity. The objective is to fail quickly on color + // spaces that are definitely not sRGB before proceeding to the longer test of guessing the + // reference space primaries. + + // Break point is at 0.039286, so include at least one value below this. + std::vector vals = + { + 0.5f, 0.5f, 0.5f, + 0.03f, 0.03f, 0.03f, + 0.25f, 0.25f, 0.25f, + 0.75f, 0.75f, 0.75f, + 0.f, 0.f, 0.f, + 1.f, 1.f , 1.f + }; + std::vector out = vals; + + PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); + + ConstProcessorRcPtr proc = config.getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); + + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + // Apply the sRGB function (linear to non-lin). + // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. + if (out[i] <= 0.0030399346397784323f) + { + out[i] *= 12.923210180787857f; + } + else + { + out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; + } + + if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) + { + return ""; + } + } + + // + // Then try the various primaries for the reference space. + // + + // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact + // converting sRGB texture values to the candidate reference space. It includes 0.02 which is + // on the sRGB linear segment, color values, and neutral values. + vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f, }; + std::string refSpace = ""; + ConstProcessorRcPtr fromRefProc; + if (toRefTransform) + { + // The color space has the sRGB non-linearity. Now try combining the transform with a + // transform from the Built-in config that goes from a variety of reference spaces to an + // sRGB texture space. If the result is an identity, then that tells what the source config + // reference space is. + for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + { + fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); + + ConstProcessorRcPtr toRefProc = config.getProcessor(toRefTransform, + TRANSFORM_DIR_FORWARD); + + if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) + { + refSpace = builtinLinearSpaces[i]; + } + } + } + + return refSpace; + } +} \ No newline at end of file diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h new file mode 100644 index 0000000000..124fe1b316 --- /dev/null +++ b/src/OpenColorIO/ConfigUtils.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_COLORSPACE_UTILS_H +#define INCLUDED_OCIO_COLORSPACE_UTILS_H + +#include + +#include + +namespace OCIO_NAMESPACE +{ + // Return whether the color space contains SRGB or not. + bool containsSRGB(ConstColorSpaceRcPtr & cs); + + // Get color space where isData is false and it has neither a to_ref or from_ref transform. + std::string getRefSpace(const Config & cfg); + + // Get processor to a sRGB transform. + ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName); + + // Get reference space if the specified color space is a recognized linear space. + std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces); + + // Get reference space if the specified color space is an sRGB texture space. + std::string getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + std::vector & builtinLinearSpaces); + +} // namespace OCIO_NAMESPACE + +#endif // INCLUDED_OCIO_BAKING_UTILS_H diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index dab0287dfb..72d24526de 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -15,6 +15,7 @@ #include "OpBuilders.h" #include "ops/noop/NoOps.h" #include "Processor.h" +#include "MathUtils.h" #include "TransformBuilder.h" #include "utils/StringUtils.h" @@ -218,6 +219,15 @@ ConstCPUProcessorRcPtr Processor::getOptimizedCPUProcessor(BitDepth inBitDepth, return getImpl()->getOptimizedCPUProcessor(inBitDepth, outBitDepth, oFlags); } +bool Processor::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance) +{ + return Processor::Impl::AreProcessorsEquivalent(p1, p2, rgbaValues, numValues, tolerance); +} + // Instantiate the cache with the right types. template class ProcessorCache; @@ -648,4 +658,34 @@ void Processor::Impl::computeMetadata() } } +bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance) +{ + ProcessorRcPtr proc = Processor::Create(); + + proc->getImpl()->concatenate(p1, p2); + + // RGBA values. + std::vector out(numValues*4); + + PackedImageDesc desc(rgbaValues, (long) numValues, 1, CHANNEL_ORDERING_RGBA); + PackedImageDesc descDst(&out[0], (long) numValues, 1, CHANNEL_ORDERING_RGBA); + + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + if (!EqualWithAbsError(rgbaValues[i], out[i], tolerance)) + { + return false; + } + } + + return true; +} + } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index 2bbddd9f2a..1835580c48 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -105,6 +105,13 @@ class Processor::Impl void computeMetadata(); + // Returns whether the specified processors are equivalents. + static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float tolerance); + protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, OptimizationFlags oFlags) const; diff --git a/src/OpenColorIO/Transform.cpp b/src/OpenColorIO/Transform.cpp index 7f4d7fddbc..0d723212e6 100755 --- a/src/OpenColorIO/Transform.cpp +++ b/src/OpenColorIO/Transform.cpp @@ -21,7 +21,6 @@ #include "ops/lut3d/Lut3DOp.h" #include "ops/matrix/MatrixOp.h" #include "ops/range/RangeOp.h" -#include "Processor.h" #include "TransformBuilder.h" diff --git a/src/OpenColorIO/fileformats/FileFormatICC.cpp b/src/OpenColorIO/fileformats/FileFormatICC.cpp index 786c8a5220..1fcfd9adcf 100755 --- a/src/OpenColorIO/fileformats/FileFormatICC.cpp +++ b/src/OpenColorIO/fileformats/FileFormatICC.cpp @@ -14,6 +14,7 @@ #include "ops/lut1d/Lut1DOp.h" #include "ops/matrix/MatrixOp.h" #include "ops/range/RangeOp.h" +#include "Platform.h" #include "pystring/pystring.h" #include "transforms/FileTransform.h" diff --git a/src/OpenColorIO/transforms/FileTransform.h b/src/OpenColorIO/transforms/FileTransform.h index 8754e77978..db737b8bd8 100644 --- a/src/OpenColorIO/transforms/FileTransform.h +++ b/src/OpenColorIO/transforms/FileTransform.h @@ -13,7 +13,6 @@ #include "Op.h" #include "ops/noop/NoOps.h" #include "PrivateTypes.h" -#include "Processor.h" #include "utils/StringUtils.h" diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 313f3aa846..170d0974b6 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -90,6 +90,7 @@ endfunction(add_ocio_test) set(SOURCES builtinconfigs/CGConfig.cpp builtinconfigs/StudioConfig.cpp + ConfigUtils.cpp fileformats/cdl/CDLParser.cpp fileformats/cdl/CDLReaderHelper.cpp fileformats/cdl/CDLWriter.cpp diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index db959703c7..30f3e455d6 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1188,4 +1188,106 @@ ocio_profile_version: 2 srcColorSpaceName.c_str()); checkProcessorInverse(proc); } + + OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); + { + const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + + } + + { + // Texture -- sRGB + const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + } +} + +OCIO_ADD_TEST(Processor, identify_colorspace) +{ + constexpr const char * CONFIG { R"( +ocio_profile_version: 2 + +roles: + default: raw + scene_linear: ref_cs + +colorspaces: + - ! + name: raw + description: A data colorspace (should not be used). + isdata: true + + - ! + name: ref_cs + description: The reference colorspace. + isdata: false + + - ! + name: not sRGB + description: A color space that misleadingly has sRGB in the name, even though it's not. + isdata: false + to_scene_reference: ! {style: ACEScct_to_ACES2065-1} + + - ! + name: ACES cg + description: An ACEScg space with an unusual spelling. + isdata: false + to_scene_reference: ! {style: ACEScg_to_ACES2065-1} + + - ! + name: Linear ITU-R BT.709 + description: A linear Rec.709 space with an unusual spelling. + isdata: false + from_scene_reference: ! + name: AP0 to Linear Rec.709 (sRGB) + children: + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + + - ! + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. + isdata: false + from_scene_reference: ! + name: AP0 to sRGB Rec.709 + children: + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + + - ! + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + isdata: false + from_scene_reference: ! + name: AP0 to sRGB Encoded AP1 - Texture + children: + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + +)" }; + + std::istringstream is; + is.str(CONFIG); + OCIO::ConstConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + + { + const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + + } + + { + const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + } + + { + char srcInterchange[255]; + char builtinInterchange[255]; + + cfg->identifyInterchangeSpace(srcInterchange, builtinInterchange); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); + } } \ No newline at end of file From a70879964e9355f99ec0f5147c667986c2724d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 11 Jan 2023 11:57:03 -0500 Subject: [PATCH 12/22] Moving code from Config.cpp to ConfigUtils.cpp and using wrapper in Config.cpp instead. Fixing identations issues. Fixing styling issues. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/Config.cpp | 154 ++----------------- src/OpenColorIO/ConfigUtils.cpp | 262 +++++++++++++++++++++++++++++++- src/OpenColorIO/ConfigUtils.h | 23 ++- src/OpenColorIO/Processor.cpp | 10 +- src/OpenColorIO/Processor.h | 2 +- tests/cpu/ColorSpace_tests.cpp | 72 +-------- 6 files changed, 297 insertions(+), 226 deletions(-) diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 80399fcb1b..d2af83d8a3 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -1096,10 +1096,10 @@ class Config::Impl return -1; } - static ConstProcessorRcPtr getProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName, - TransformDirection direction) + static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName, + TransformDirection direction) { // Use the Default config as the Built-in config to interpret the known color space name. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); @@ -2921,147 +2921,12 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(*eSrcConfig); - if (refColorSpaceName.empty()) - { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); - } - - // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = eSrcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) - { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - - if (refColorSpacePrims.empty()) - { - // Check for a linear space with known primaries. - nbCs = eSrcConfig->getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; - } - } - } - - if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) - { - // Copy interchange role from source config. - std::memcpy(srcInterchange, refColorSpaceName.c_str(), refColorSpaceName.size()); - // Copy interchange role from built-in config. - std::memcpy(builtinInterchange, refColorSpacePrims.c_str(), refColorSpacePrims.size()); - - // Terminate the string. - srcInterchange[refColorSpaceName.size()] = '\0'; - builtinInterchange[refColorSpacePrims.size()] = '\0'; - } - else - { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } + ConfigUtils::identifyInterchangeSpace(srcInterchange, builtinInterchange, *this); } const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } - - char srcInterchange[255]; - char builtinInterchange[255]; - - // Identify interchange space. - identifyInterchangeSpace(srcInterchange, builtinInterchange); - - // Get processor from that space to the built-in color space. - ConstProcessorRcPtr builtinProc; - if (builtinInterchange && builtinInterchange[0]) - { - builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinInterchange, - builtinColorSpaceName); - } - - if (builtinProc && srcInterchange && srcInterchange[0]) - { - // Iterate over each color space in the source config. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; - int nbCs = getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - // Get processor from that space to its reference and then use isProcessorEquivalent. - // If equivalent, return that color space name. - - ConstColorSpaceRcPtr cs = getColorSpace(getColorSpaceNameByIndex(i)); - const char * csName = cs->getName(); - - ConstProcessorRcPtr proc = getProcessor(getCurrentContext(), - csName, - srcInterchange); - - if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) - { - return csName; - } - } - } - - return ""; + return ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *this); } /////////////////////////////////////////////////////////////////////////// @@ -4784,7 +4649,7 @@ ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr src const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return Config::Impl::getProcessorToBuiltinCS(srcConfig, + return Config::Impl::GetProcessorToBuiltinCS(srcConfig, srcColorSpaceName, builtinColorSpaceName, TRANSFORM_DIR_FORWARD); @@ -4794,7 +4659,7 @@ ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * built ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return Config::Impl::getProcessorToBuiltinCS(srcConfig, + return Config::Impl::GetProcessorToBuiltinCS(srcConfig, srcColorSpaceName, builtinColorSpaceName, TRANSFORM_DIR_INVERSE); @@ -4907,6 +4772,9 @@ void Config::clearProcessorCache() noexcept getImpl()->m_processorCache.clear(); } +////////////////////////////////////////////////////////////////// +// ConfigIOProxy and Archiving + void Config::setConfigIOProxy(ConfigIOProxyRcPtr ciop) { getImpl()->m_context->setConfigIOProxy(ciop); diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index a0038b7cd9..6029ac1d37 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -6,6 +6,8 @@ #include "utils/StringUtils.h" namespace OCIO_NAMESPACE +{ +namespace ConfigUtils { bool containsSRGB(ConstColorSpaceRcPtr & cs) { @@ -105,15 +107,13 @@ namespace OCIO_NAMESPACE // Generate matrices between all combinations of the Built-in linear color spaces. // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. + ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); for (size_t i = 0; i < builtinLinearSpaces.size(); i++) { for (size_t j = 0; j < builtinLinearSpaces.size(); j++) { if (i != j) { - ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, - TRANSFORM_DIR_FORWARD); - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); csTransform->setSrc(builtinLinearSpaces[j].c_str()); csTransform->setDst(builtinLinearSpaces[i].c_str()); @@ -250,4 +250,260 @@ namespace OCIO_NAMESPACE return refSpace; } + + bool isColorSpaceLinear(const char * colorSpace, + ReferenceSpaceType referenceSpaceType, + const Config & cfg) + { + auto cs = cfg.getColorSpace(colorSpace); + + if (cs->isData()) + { + return false; + } + + // Colorspace is not linear if the types are opposite. + if (cs->getReferenceSpaceType() != referenceSpaceType) + { + return false; + } + + std::string encoding = cs->getEncoding(); + if (!encoding.empty()) + { + // Check the encoding value if it is set. + if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && + referenceSpaceType == REFERENCE_SPACE_SCENE) || + (StringUtils::Compare(cs->getEncoding(), "display-linear") && + referenceSpaceType == REFERENCE_SPACE_DISPLAY)) + { + return true; + } + else + { + return false; + } + } + + // We want to assess linearity over at least a reasonable range of values, so use a very dark + // value and a very bright value. Test neutral, red, green, and blue points to detect situations + // where the neutral may be linear but there is non-linearity off the neutral axis. + auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool + { + std::vector img = + { + 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, + 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, + 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, + 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f + }; + std::vector dst = + { + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 0.f, 0.f, 0.f + }; + + PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); + + // Create an editable copy to avoir filling the processor cache. + ConfigRcPtr eConfig = config.createEditableCopy(); + eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + auto procToReference = eConfig->getProcessor(t, TRANSFORM_DIR_FORWARD); + auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + optCPUProc->apply(desc, descDst); + + float absError = 1e-5f; + float multiplier = 64.f; + bool ret = true; + + // Test the first RGB pair. + ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); + ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); + ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); + + // Test the second RGB pair. + ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); + ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); + ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); + + // Test the third RGB pair. + ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); + ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); + ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); + + // Test the fourth RGB pair. + ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); + ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); + ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); + + return ret; + }; + + ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if ((transformToReference && transformFromReference) || transformToReference) + { + // Color space has a transform for the to-reference direction, or both directions. + return evaluate(cfg, transformToReference); + } + else if (transformFromReference) + { + // Color space only has a transform for the from-reference direction. + return evaluate(cfg, transformFromReference); + } + + // Color space matches the desired reference space type, is not a data space, and has no + // transforms, so it is equivalent to the reference space and hence linear. + return true; + } + + void identifyInterchangeSpace(char * srcInterchange, + char * builtinInterchange, + const Config & cfg) + { + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = cfg.createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + std::vector builtinLinearSpaces = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Get the name of (one of) the reference spaces. + std::string refColorSpaceName = getRefSpace(*eSrcConfig); + if (refColorSpaceName.empty()) + { + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } + + // Check for an sRGB texture space. + std::string refColorSpacePrims = ""; + int nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) + { + refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + + if (refColorSpacePrims.empty()) + { + // Check for a linear space with known primaries. + nbCs = eSrcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + { + refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); + // Break out when a match is found. + if (!refColorSpacePrims.empty()) break; + } + } + } + + if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + { + // Copy interchange role from source config. + snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); + // Copy interchange role from built-in config. + snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } + } + + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg) + { + // Use the Default config as the Built-in config. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + char srcInterchange[255]; + char builtinInterchange[255]; + + // Identify interchange space. + identifyInterchangeSpace(srcInterchange, builtinInterchange, cfg); + + // Get processor from that space to the built-in color space. + ConstProcessorRcPtr builtinProc; + if (builtinInterchange && builtinInterchange[0]) + { + builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), + builtinInterchange, + builtinColorSpaceName); + } + + if (builtinProc && srcInterchange && srcInterchange[0]) + { + // Iterate over each color space in the source config. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + int nbCs = cfg.getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + // Get processor from that space to its reference and then use isProcessorEquivalent. + // If equivalent, return that color space name. + + ConstColorSpaceRcPtr cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); + const char * csName = cs->getName(); + + ConstProcessorRcPtr proc = cfg.getProcessor(cfg.getCurrentContext(), + csName, + srcInterchange); + + if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + { + return csName; + } + } + } + + return ""; + } +} + } \ No newline at end of file diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 124fe1b316..70a6e9b751 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -1,14 +1,17 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. -#ifndef INCLUDED_OCIO_COLORSPACE_UTILS_H -#define INCLUDED_OCIO_COLORSPACE_UTILS_H +#ifndef INCLUDED_OCIO_CONFIG_UTILS_H +#define INCLUDED_OCIO_CONFIG_UTILS_H #include #include namespace OCIO_NAMESPACE +{ + +namespace ConfigUtils { // Return whether the color space contains SRGB or not. bool containsSRGB(ConstColorSpaceRcPtr & cs); @@ -31,7 +34,21 @@ namespace OCIO_NAMESPACE const ConstColorSpaceRcPtr cs, const ConstConfigRcPtr & builtinConfig, std::vector & builtinLinearSpaces); + // Returns true if the specified color space is linear. + bool isColorSpaceLinear(const char * colorSpace, + ReferenceSpaceType referenceSpaceType, + const Config & cfg); + + // Identify the interchange space of the source config and the default built-in config. + void identifyInterchangeSpace(char * srcInterchange, + char * builtinInterchange, + const Config & cfg); + + // Find the name of the color space in the source config that is the same as + // a color space in the default built-in config. + const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg); +} } // namespace OCIO_NAMESPACE -#endif // INCLUDED_OCIO_BAKING_UTILS_H +#endif // INCLUDED_OCIO_CONFIG_UTILS_H diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index 72d24526de..bfbe6d0f9a 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -659,10 +659,10 @@ void Processor::Impl::computeMetadata() } bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance) + ConstProcessorRcPtr & p2, + float * rgbaValues, + size_t numValues, + float absTolerance) { ProcessorRcPtr proc = Processor::Create(); @@ -679,7 +679,7 @@ bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, for (size_t i = 0; i < out.size(); i++) { - if (!EqualWithAbsError(rgbaValues[i], out[i], tolerance)) + if (!EqualWithAbsError(rgbaValues[i], out[i], absTolerance)) { return false; } diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index 1835580c48..cad0e6c577 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -110,7 +110,7 @@ class Processor::Impl ConstProcessorRcPtr & p2, float * rgbaValues, size_t numValues, - float tolerance); + float absTolerance); protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index 30f3e455d6..c9bd511494 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1189,11 +1189,9 @@ ocio_profile_version: 2 checkProcessorInverse(proc); } - OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); - } { @@ -1201,76 +1199,8 @@ ocio_profile_version: 2 const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } -} - -OCIO_ADD_TEST(Processor, identify_colorspace) -{ - constexpr const char * CONFIG { R"( -ocio_profile_version: 2 - -roles: - default: raw - scene_linear: ref_cs - -colorspaces: - - ! - name: raw - description: A data colorspace (should not be used). - isdata: true - - - ! - name: ref_cs - description: The reference colorspace. - isdata: false - - - ! - name: not sRGB - description: A color space that misleadingly has sRGB in the name, even though it's not. - isdata: false - to_scene_reference: ! {style: ACEScct_to_ACES2065-1} - - - ! - name: ACES cg - description: An ACEScg space with an unusual spelling. - isdata: false - to_scene_reference: ! {style: ACEScg_to_ACES2065-1} - - - ! - name: Linear ITU-R BT.709 - description: A linear Rec.709 space with an unusual spelling. - isdata: false - from_scene_reference: ! - name: AP0 to Linear Rec.709 (sRGB) - children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - - - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. - isdata: false - from_scene_reference: ! - name: AP0 to sRGB Rec.709 - children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - - ! {gamma: 2.4, offset: 0.055, direction: inverse} - - - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. - isdata: false - from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture - children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - - ! {gamma: 2.4, offset: 0.055, direction: inverse} - -)" }; - - std::istringstream is; - is.str(CONFIG); - OCIO::ConstConfigRcPtr cfg; - OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + // identify_colorspace tests { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); From eba85d7031f80eb7b9d501f326d64e8cff72d6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Thu, 12 Jan 2023 09:49:48 -0500 Subject: [PATCH 13/22] Typo and comments tweaks Moving the creation of the editable config into the wrapper of isColorSpaceLinear instead of doing it inside the function. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 6 ++-- src/OpenColorIO/ConfigUtils.cpp | 52 +++++++++++++++++++++---------- src/OpenColorIO/ConfigUtils.h | 2 +- tests/cpu/ColorSpace_tests.cpp | 6 ++-- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index ec72352067..dc59dde070 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -698,8 +698,8 @@ class OCIOEXPORT Config /** * \brief Identify the interchange space of the source config and the default built-in config. * - * \param srcInterchange Interchange space from the source config (output). - * \param builtinInterchange Interchange space from the default built-in config (output). + * \param[out] srcInterchange Interchange space from the source config (output). + * \param[out] builtinInterchange Interchange space from the default built-in config (output). * * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. */ @@ -709,7 +709,7 @@ class OCIOEXPORT Config * a color space in the default built-in config. * * \param builtinColorSpaceName Color space name in the built-in config. - * \return const char* Matching color space from the source config. Empty if not found. + * \return Matching color space from the source config. Empty if not found. */ const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 6029ac1d37..295824d02c 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -253,9 +253,9 @@ namespace ConfigUtils bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType, - const Config & cfg) + Config & editableCfg { - auto cs = cfg.getColorSpace(colorSpace); + auto cs = editableCfg.getColorSpace(colorSpace); if (cs->isData()) { @@ -307,12 +307,8 @@ namespace ConfigUtils PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); - - // Create an editable copy to avoir filling the processor cache. - ConfigRcPtr eConfig = config.createEditableCopy(); - eConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - auto procToReference = eConfig->getProcessor(t, TRANSFORM_DIR_FORWARD); + auto procToReference = config.getProcessor(t, TRANSFORM_DIR_FORWARD); auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); optCPUProc->apply(desc, descDst); @@ -348,12 +344,12 @@ namespace ConfigUtils if ((transformToReference && transformFromReference) || transformToReference) { // Color space has a transform for the to-reference direction, or both directions. - return evaluate(cfg, transformToReference); + return evaluate(editableCfg, transformToReference); } else if (transformFromReference) { // Color space only has a transform for the from-reference direction. - return evaluate(cfg, transformFromReference); + return evaluate(editableCfg, transformFromReference); } // Color space matches the desired reference space type, is not a data space, and has no @@ -421,9 +417,9 @@ namespace ConfigUtils if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. if (!refColorSpacePrims.empty()) break; } @@ -432,10 +428,34 @@ namespace ConfigUtils if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) { - // Copy interchange role from source config. - snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); - // Copy interchange role from built-in config. - snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + // Checking if the size of the role name + null terminated characters is small enough + // to fix into the char array. + + if (refColorSpaceName.size()+1 > 255) + { + // Copy interchange space from source config. + snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); + } + else + { + std::ostringstream os; + os << "The interchange space was found, but the name is too big: " + << refColorSpaceName; + throw Exception(os.str().c_str()); + } + + if (refColorSpacePrims.size()+1 > 255) + { + // Copy interchange space from built-in config. + snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); + } + else + { + std::ostringstream os; + os << "The interchange space from the built-in config was found, but the name is " + << "too big: " << refColorSpacePrims; + throw Exception(os.str().c_str()); + } } else { diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 70a6e9b751..9ac7209463 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -37,7 +37,7 @@ namespace ConfigUtils // Returns true if the specified color space is linear. bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType, - const Config & cfg); + Config & cfg); // Identify the interchange space of the source config and the default built-in config. void identifyInterchangeSpace(char * srcInterchange, diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index c9bd511494..eb11d79a44 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1189,22 +1189,21 @@ ocio_profile_version: 2 checkProcessorInverse(proc); } + // identifyBuiltinColorSpace tests. { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } { - // Texture -- sRGB const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } - // identify_colorspace tests { const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); - } { @@ -1212,6 +1211,7 @@ ocio_profile_version: 2 OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } + // identifyInterchangeSpace tests. { char srcInterchange[255]; char builtinInterchange[255]; From cf9a17a4886dc1c654f3f6da37486a63ee1df250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Mon, 16 Jan 2023 09:46:43 -0500 Subject: [PATCH 14/22] Splitting identifyInterchangeSpace into two methods, one for source config and one for builtin. Adding back isColorSpaceLinear inside Config.cpp since we need access to getProcessorWithoutCaching. Refactor some of config utils function to return index instead of string. Creating editable config in the two wrappers to prevent processor caching. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 4 +- src/OpenColorIO/Config.cpp | 190 ++++++++++-------- src/OpenColorIO/ConfigUtils.cpp | 311 ++++++++++-------------------- src/OpenColorIO/ConfigUtils.h | 30 ++- tests/cpu/ColorSpace_tests.cpp | 6 +- 5 files changed, 227 insertions(+), 314 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index dc59dde070..e56f9fc7e0 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -703,7 +703,9 @@ class OCIOEXPORT Config * * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. */ - void identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const; + const char * identifyInterchangeSpace() const; + const char * identifyBuiltinInterchangeSpace() const; + /** * \brief Find the name of the color space in the source config that is the same as * a color space in the default built-in config. diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index d2af83d8a3..0518f6ec9c 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -1095,79 +1095,6 @@ class Config::Impl // That should never happen. return -1; } - - static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName, - TransformDirection direction) - { - // Use the Default config as the Built-in config to interpret the known color space name. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } - - // If both configs have the interchange roles set, then it's easy. - try - { - ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - builtinConfig, - builtinColorSpaceName); - return proc; - } - catch(const Exception & e) - { - std::string str1 = "The role 'aces_interchange' is missing in the source config"; - std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; - - // Re-throw when the error is not about interchange roles. - if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) - { - throw Exception(e.what()); - } - // otherwise, do nothing and continue. - } - - char srcInterchange[255]; - char builtinInterchange[255]; - - srcConfig->identifyInterchangeSpace(srcInterchange, builtinInterchange); - - if (builtinInterchange && builtinInterchange[0]) - { - ConstProcessorRcPtr proc; - if (direction == TRANSFORM_DIR_FORWARD) - { - proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - srcInterchange, - builtinConfig, - builtinColorSpaceName, - builtinInterchange); - } - else if (direction == TRANSFORM_DIR_INVERSE) - { - proc = Config::GetProcessorFromConfigs(builtinConfig, - builtinColorSpaceName, - builtinInterchange, - srcConfig, - srcColorSpaceName, - srcInterchange); - } - return proc; - } - - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } }; @@ -2918,15 +2845,39 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe // transforms, so it is equivalent to the reference space and hence linear. return true; } + +const char * Config::identifyInterchangeSpace() const +{ + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int srcInterchangeIndex = -1; + ConfigUtils::identifyInterchangeSpace(srcInterchangeIndex, *eSrcConfig); -void Config::identifyInterchangeSpace(char * srcInterchange, char * builtinInterchange) const + return getColorSpaceNameByIndex(srcInterchangeIndex); +} + +const char * Config::identifyBuiltinInterchangeSpace() const { - ConfigUtils::identifyInterchangeSpace(srcInterchange, builtinInterchange, *this); + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int builtinInterchangeIndex = -1; + ConfigUtils::identifyBuiltinInterchangeSpace(builtinInterchangeIndex, *eSrcConfig); + + return ConfigUtils::getBuiltinLinearSpaces(builtinInterchangeIndex); } const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const { - return ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *this); + // Using createEditableCopy to avoid filling the processor cache in the Config object. + ConfigRcPtr eSrcConfig = createEditableCopy(); + eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + + int csIndex = ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *eSrcConfig); + return getColorSpaceNameByIndex(csIndex); } /////////////////////////////////////////////////////////////////////////// @@ -4645,24 +4596,95 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr return processor; } +static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName, + TransformDirection direction) +{ + // Use the Default config as the Built-in config to interpret the known color space name. + ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + + if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); + } + + // If both configs have the interchange roles set, then it's easy. + try + { + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); + return proc; + } + catch(const Exception & e) + { + std::string str1 = "The role 'aces_interchange' is missing in the source config"; + std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; + + // Re-throw when the error is not about interchange roles. + if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) + { + throw Exception(e.what()); + } + // otherwise, do nothing and continue. + } + + const char * srcInterchange = srcConfig->identifyInterchangeSpace(); + const char * builtinInterchange = srcConfig->identifyBuiltinInterchangeSpace(); + + if (builtinInterchange && builtinInterchange[0]) + { + ConstProcessorRcPtr proc; + if (direction == TRANSFORM_DIR_FORWARD) + { + proc = Config::GetProcessorFromConfigs(srcConfig, + srcColorSpaceName, + srcInterchange, + builtinConfig, + builtinColorSpaceName, + builtinInterchange); + } + else if (direction == TRANSFORM_DIR_INVERSE) + { + proc = Config::GetProcessorFromConfigs(builtinConfig, + builtinColorSpaceName, + builtinInterchange, + srcConfig, + srcColorSpaceName, + srcInterchange); + } + return proc; + } + + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); +} + ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr srcConfig, const char * srcColorSpaceName, const char * builtinColorSpaceName) { - return Config::Impl::GetProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_FORWARD); + return GetProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_FORWARD); } ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, ConstConfigRcPtr srcConfig, const char * srcColorSpaceName) { - return Config::Impl::GetProcessorToBuiltinCS(srcConfig, - srcColorSpaceName, - builtinColorSpaceName, - TRANSFORM_DIR_INVERSE); + return GetProcessorToBuiltinCS(srcConfig, + srcColorSpaceName, + builtinColorSpaceName, + TRANSFORM_DIR_INVERSE); } std::ostream& operator<< (std::ostream& os, const Config& config) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 295824d02c..edd68b067b 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -7,8 +7,24 @@ namespace OCIO_NAMESPACE { + // Define the set of candidate reference linear color spaces (aka, reference primaries) that + // will be used when searching through the source config. If the source config scene-referred + // reference space is the equivalent of one of these spaces, it should be possible to identify + // it with the following heuristics. + constexpr int numberOfbuiltinLinearSpaces = 5; + constexpr const char * builtinLinearSpaces[] = { "ACES - ACES2065-1", + "ACES - ACEScg", + "Utility - Linear - Rec.709", + "Utility - Linear - P3-D65", + "Utility - Linear - Rec.2020" }; + namespace ConfigUtils { + const char * getBuiltinLinearSpaces(int index) + { + return builtinLinearSpaces[index]; + } + bool containsSRGB(ConstColorSpaceRcPtr & cs) { std::string name = StringUtils::Lower(cs->getName()); @@ -29,7 +45,7 @@ namespace ConfigUtils return false; } - std::string getRefSpace(const Config & cfg) + int getRefSpace(const Config & cfg) { // Find a color space where isData is false and it has neither a to_ref or from_ref // transform. @@ -54,9 +70,9 @@ namespace ConfigUtils continue; } - return cs->getName(); + return i; } - return ""; + return -1; } ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, @@ -73,14 +89,14 @@ namespace ConfigUtils return proc; } - std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, + int getReferenceSpaceFromLinearSpace(const Config & srcConfig, const ConstColorSpaceRcPtr & cs, const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) + const char * const * builtinLinearSpaces) { // If the color space is a recognized linear space, return the reference space used by // the config. - std::string refSpace; + int refSpaceIndex = -1; bool toRefDirection = true; auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); if (!srcTransform) @@ -92,7 +108,7 @@ namespace ConfigUtils } else { - return ""; + return -1; } } @@ -108,15 +124,15 @@ namespace ConfigUtils // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { - for (size_t j = 0; j < builtinLinearSpaces.size(); j++) + for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) { if (i != j) { ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j].c_str()); - csTransform->setDst(builtinLinearSpaces[i].c_str()); + csTransform->setSrc(builtinLinearSpaces[j]); + csTransform->setDst(builtinLinearSpaces[i]); ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); @@ -125,26 +141,26 @@ namespace ConfigUtils { if (toRefDirection) { - refSpace = builtinLinearSpaces[j]; + refSpaceIndex = (int) j; } else { - refSpace = builtinLinearSpaces[i]; + refSpaceIndex = (int) i; } - return refSpace; + return refSpaceIndex; } } } } - return ""; + return -1; } - std::string getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces) + int getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig, + const char * const * builtinLinearSpaces) { // If the color space is an sRGB texture space, return the reference space used by the config. @@ -167,7 +183,7 @@ namespace ConfigUtils else { // Both directions missing. - return ""; + return -1; } } @@ -210,7 +226,7 @@ namespace ConfigUtils if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) { - return ""; + return -1; } } @@ -226,7 +242,7 @@ namespace ConfigUtils 0.3f, 0.02f, 0.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f, }; - std::string refSpace = ""; + int refSpaceIndex = -1; ConstProcessorRcPtr fromRefProc; if (toRefTransform) { @@ -234,7 +250,7 @@ namespace ConfigUtils // transform from the Built-in config that goes from a variety of reference spaces to an // sRGB texture space. If the result is an identity, then that tells what the source config // reference space is. - for (size_t i = 0; i < builtinLinearSpaces.size(); i++) + for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); @@ -243,219 +259,100 @@ namespace ConfigUtils if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) { - refSpace = builtinLinearSpaces[i]; + refSpaceIndex = (int) i; + break; } } } - return refSpace; + return refSpaceIndex; } - bool isColorSpaceLinear(const char * colorSpace, - ReferenceSpaceType referenceSpaceType, - Config & editableCfg + void identifyInterchangeSpace(int & srcInterchangeIndex, Config & eSrcConfig) { - auto cs = editableCfg.getColorSpace(colorSpace); - - if (cs->isData()) - { - return false; - } - - // Colorspace is not linear if the types are opposite. - if (cs->getReferenceSpaceType() != referenceSpaceType) + if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) { - return false; + std::ostringstream os; + os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; + throw Exception(os.str().c_str()); } - std::string encoding = cs->getEncoding(); - if (!encoding.empty()) + // Get the name of (one of) the reference spaces. + int refColorSpaceIndex = getRefSpace(eSrcConfig); + if (refColorSpaceIndex < 0) { - // Check the encoding value if it is set. - if ((StringUtils::Compare(cs->getEncoding(), "scene-linear") && - referenceSpaceType == REFERENCE_SPACE_SCENE) || - (StringUtils::Compare(cs->getEncoding(), "display-linear") && - referenceSpaceType == REFERENCE_SPACE_DISPLAY)) - { - return true; - } - else - { - return false; - } + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); } - // We want to assess linearity over at least a reasonable range of values, so use a very dark - // value and a very bright value. Test neutral, red, green, and blue points to detect situations - // where the neutral may be linear but there is non-linearity off the neutral axis. - auto evaluate = [](const Config & config, ConstTransformRcPtr &t) -> bool - { - std::vector img = - { - 0.0625f, 0.0625f, 0.0625f, 4.f, 4.f, 4.f, - 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, - 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, - 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f - }; - std::vector dst = - { - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f - }; - - PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); - - auto procToReference = config.getProcessor(t, TRANSFORM_DIR_FORWARD); - auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - optCPUProc->apply(desc, descDst); - - float absError = 1e-5f; - float multiplier = 64.f; - bool ret = true; - - // Test the first RGB pair. - ret &= EqualWithAbsError(dst[0]*multiplier, dst[3], absError); - ret &= EqualWithAbsError(dst[1]*multiplier, dst[4], absError); - ret &= EqualWithAbsError(dst[2]*multiplier, dst[5], absError); - - // Test the second RGB pair. - ret &= EqualWithAbsError(dst[6]*multiplier, dst[9], absError); - ret &= EqualWithAbsError(dst[7]*multiplier, dst[10], absError); - ret &= EqualWithAbsError(dst[8]*multiplier, dst[11], absError); - - // Test the third RGB pair. - ret &= EqualWithAbsError(dst[12]*multiplier, dst[15], absError); - ret &= EqualWithAbsError(dst[13]*multiplier, dst[16], absError); - ret &= EqualWithAbsError(dst[14]*multiplier, dst[17], absError); - - // Test the fourth RGB pair. - ret &= EqualWithAbsError(dst[18]*multiplier, dst[21], absError); - ret &= EqualWithAbsError(dst[19]*multiplier, dst[22], absError); - ret &= EqualWithAbsError(dst[20]*multiplier, dst[23], absError); - - return ret; - }; - - ConstTransformRcPtr transformToReference = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - ConstTransformRcPtr transformFromReference = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if ((transformToReference && transformFromReference) || transformToReference) + if (refColorSpaceIndex > -1) { - // Color space has a transform for the to-reference direction, or both directions. - return evaluate(editableCfg, transformToReference); + srcInterchangeIndex = refColorSpaceIndex; } - else if (transformFromReference) + else { - // Color space only has a transform for the from-reference direction. - return evaluate(editableCfg, transformFromReference); + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config.\n" + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); } - - // Color space matches the desired reference space type, is not a data space, and has no - // transforms, so it is equivalent to the reference space and hence linear. - return true; } - void identifyInterchangeSpace(char * srcInterchange, - char * builtinInterchange, - const Config & cfg) + void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig) { // Use the Default config as the Built-in config. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = cfg.createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - std::vector builtinLinearSpaces = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; - - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Get the name of (one of) the reference spaces. - std::string refColorSpaceName = getRefSpace(*eSrcConfig); - if (refColorSpaceName.empty()) + if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) { std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; + os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; throw Exception(os.str().c_str()); } + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + // Check for an sRGB texture space. - std::string refColorSpacePrims = ""; - int nbCs = eSrcConfig->getNumColorSpaces(); + int refColorSpacePrimsIndex = -1; + int nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - ConstColorSpaceRcPtr cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); + ConstColorSpaceRcPtr cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); if (containsSRGB(cs)) { - refColorSpacePrims = getReferenceSpaceFromSRGBSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; + if (refColorSpacePrimsIndex > -1) break; } } - if (refColorSpacePrims.empty()) + if (refColorSpacePrimsIndex < 0) { // Check for a linear space with known primaries. - nbCs = eSrcConfig->getNumColorSpaces(); + nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - auto cs = eSrcConfig->getColorSpace(eSrcConfig->getColorSpaceNameByIndex(i)); - if (eSrcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) + auto cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); + if (eSrcConfig.isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { - refColorSpacePrims = getReferenceSpaceFromLinearSpace(*eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(eSrcConfig, + cs, + builtinConfig, + builtinLinearSpaces); // Break out when a match is found. - if (!refColorSpacePrims.empty()) break; + if (refColorSpacePrimsIndex > -1) break; } } } - if (!refColorSpaceName.empty() && !refColorSpacePrims.empty()) + if (refColorSpacePrimsIndex > -1) { - // Checking if the size of the role name + null terminated characters is small enough - // to fix into the char array. - - if (refColorSpaceName.size()+1 > 255) - { - // Copy interchange space from source config. - snprintf(srcInterchange, refColorSpaceName.size()+1, "%s", refColorSpaceName.c_str()); - } - else - { - std::ostringstream os; - os << "The interchange space was found, but the name is too big: " - << refColorSpaceName; - throw Exception(os.str().c_str()); - } - - if (refColorSpacePrims.size()+1 > 255) - { - // Copy interchange space from built-in config. - snprintf(builtinInterchange, refColorSpacePrims.size()+1, "%s", refColorSpacePrims.c_str()); - } - else - { - std::ostringstream os; - os << "The interchange space from the built-in config was found, but the name is " - << "too big: " << refColorSpacePrims; - throw Exception(os.str().c_str()); - } + builtinInterchangeIndex = refColorSpacePrimsIndex; } else { @@ -466,7 +363,7 @@ namespace ConfigUtils } } - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg) + int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig) { // Use the Default config as the Built-in config. ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); @@ -479,22 +376,22 @@ namespace ConfigUtils throw Exception(os.str().c_str()); } - char srcInterchange[255]; - char builtinInterchange[255]; - + int srcInterchangeIndex = -1; + int builtinInterchangeIndex = -1; // Identify interchange space. - identifyInterchangeSpace(srcInterchange, builtinInterchange, cfg); - + identifyInterchangeSpace(srcInterchangeIndex, eSrcConfig); + identifyBuiltinInterchangeSpace(builtinInterchangeIndex, eSrcConfig); + // Get processor from that space to the built-in color space. ConstProcessorRcPtr builtinProc; - if (builtinInterchange && builtinInterchange[0]) + if (builtinInterchangeIndex > -1) { builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinInterchange, - builtinColorSpaceName); + builtinConfig->getColorSpaceNameByIndex(builtinInterchangeIndex), + builtinColorSpaceName); } - - if (builtinProc && srcInterchange && srcInterchange[0]) + + if (builtinProc && srcInterchangeIndex > -1) { // Iterate over each color space in the source config. std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, @@ -502,27 +399,25 @@ namespace ConfigUtils 0.3f, 0.02f, 0.5f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f }; - int nbCs = cfg.getNumColorSpaces(); + int nbCs = eSrcConfig.getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { // Get processor from that space to its reference and then use isProcessorEquivalent. // If equivalent, return that color space name. - ConstColorSpaceRcPtr cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); - const char * csName = cs->getName(); - - ConstProcessorRcPtr proc = cfg.getProcessor(cfg.getCurrentContext(), - csName, - srcInterchange); + const char * csName = eSrcConfig.getColorSpaceNameByIndex(i); + ConstProcessorRcPtr proc = eSrcConfig.getProcessor(eSrcConfig.getCurrentContext(), + csName, + eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) { - return csName; + return i; } } } - return ""; + return -1; } } diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 9ac7209463..6664f64a4e 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -13,40 +13,36 @@ namespace OCIO_NAMESPACE namespace ConfigUtils { + const char * getBuiltinLinearSpaces(int index); + // Return whether the color space contains SRGB or not. bool containsSRGB(ConstColorSpaceRcPtr & cs); // Get color space where isData is false and it has neither a to_ref or from_ref transform. - std::string getRefSpace(const Config & cfg); + int getRefSpace(const Config & cfg); // Get processor to a sRGB transform. ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, std::string refColorSpaceName); // Get reference space if the specified color space is a recognized linear space. - std::string getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces); + int getReferenceSpaceFromLinearSpace(const Config & srcConfig, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig); // Get reference space if the specified color space is an sRGB texture space. - std::string getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - std::vector & builtinLinearSpaces); - // Returns true if the specified color space is linear. - bool isColorSpaceLinear(const char * colorSpace, - ReferenceSpaceType referenceSpaceType, - Config & cfg); + int getReferenceSpaceFromSRGBSpace(const Config & config, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig); // Identify the interchange space of the source config and the default built-in config. - void identifyInterchangeSpace(char * srcInterchange, - char * builtinInterchange, - const Config & cfg); + void identifyInterchangeSpace(int & srcInterchange, Config & eSrcConfig); + // Identify the interchange space the default built-in config. + void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig); // Find the name of the color space in the source config that is the same as // a color space in the default built-in config. - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName, const Config & cfg); + int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig); } } // namespace OCIO_NAMESPACE diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index eb11d79a44..3d8a8570d4 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -1213,10 +1213,8 @@ ocio_profile_version: 2 // identifyInterchangeSpace tests. { - char srcInterchange[255]; - char builtinInterchange[255]; - - cfg->identifyInterchangeSpace(srcInterchange, builtinInterchange); + const char * srcInterchange = cfg->identifyInterchangeSpace(); + const char * builtinInterchange = cfg->identifyBuiltinInterchangeSpace(); OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); } From be62e26a34cdd305b675a5c01c94508cac5317fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Mon, 16 Jan 2023 17:07:45 -0500 Subject: [PATCH 15/22] Tentative change to phase out isProcessorEquivalent. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/ConfigUtils.cpp | 69 ++++++++++++++++++++++++--------- src/OpenColorIO/ConfigUtils.h | 8 ++-- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index edd68b067b..9dec8051c8 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -75,8 +75,33 @@ namespace ConfigUtils return -1; } - ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) + bool isIdentityTransform(const Config & srcConfig, + GroupTransformRcPtr & tf, + std::vector & vals, + float absTolerance) + { + std::vector out = vals; + + PackedImageDesc desc(&vals[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + PackedImageDesc descDst(&out[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + + ConstProcessorRcPtr proc = srcConfig.getProcessor(tf, TRANSFORM_DIR_FORWARD); + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) + { + if (!EqualWithAbsError(vals[i], out[i], absTolerance)) + { + return false; + } + } + + return true; + } + + TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName) { // Build reference space of the given prims to sRGB transform. std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; @@ -85,14 +110,15 @@ namespace ConfigUtils csTransform->setSrc(refColorSpaceName.c_str()); csTransform->setDst(srgbColorSpaceName.c_str()); - ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); - return proc; + //ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); + //return proc; + return csTransform; } int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig, + const char * const * builtinLinearSpaces) { // If the color space is a recognized linear space, return the reference space used by // the config. @@ -123,7 +149,7 @@ namespace ConfigUtils // Generate matrices between all combinations of the Built-in linear color spaces. // Then combine these with the transform from the current color space to see if the result is // an identity. If so, then it identifies the reference space being used by the source config. - ConstProcessorRcPtr p1 = srcConfig.getProcessor(srcTransform, TRANSFORM_DIR_FORWARD); + TransformRcPtr eSrcTransform = srcTransform->createEditableCopy(); for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) @@ -133,11 +159,12 @@ namespace ConfigUtils ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); csTransform->setSrc(builtinLinearSpaces[j]); csTransform->setDst(builtinLinearSpaces[i]); + + GroupTransformRcPtr grptf = GroupTransform::Create(); + grptf->appendTransform(eSrcTransform); + grptf->appendTransform(csTransform); - ConstProcessorRcPtr p2 = builtinConfig->getProcessor(csTransform, - TRANSFORM_DIR_FORWARD); - - if (Processor::AreProcessorsEquivalent(p1, p2, &vals[0], 5, 1e-3f)) + if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) { if (toRefDirection) { @@ -243,7 +270,8 @@ namespace ConfigUtils 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 0.f, }; int refSpaceIndex = -1; - ConstProcessorRcPtr fromRefProc; + TransformRcPtr fromRefTransform; + TransformRcPtr eToRefTransform = toRefTransform->createEditableCopy(); if (toRefTransform) { // The color space has the sRGB non-linearity. Now try combining the transform with a @@ -252,12 +280,13 @@ namespace ConfigUtils // reference space is. for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) { - fromRefProc = getRefToSRGBTransform(builtinConfig, builtinLinearSpaces[i]); + fromRefTransform = getTransformToSRGBSpace(builtinConfig, builtinLinearSpaces[i]); + + GroupTransformRcPtr grptf = GroupTransform::Create(); + grptf->appendTransform(eToRefTransform); + grptf->appendTransform(fromRefTransform); - ConstProcessorRcPtr toRefProc = config.getProcessor(toRefTransform, - TRANSFORM_DIR_FORWARD); - - if (Processor::AreProcessorsEquivalent(toRefProc, fromRefProc, &vals[0], 5, 1e-3f)) + if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) { refSpaceIndex = (int) i; break; @@ -410,7 +439,9 @@ namespace ConfigUtils csName, eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); - if (Processor::AreProcessorsEquivalent(builtinProc, proc, &vals[0], 5, 1e-3f)) + auto grptf = builtinProc->createGroupTransform(); + grptf->appendTransform(proc->createGroupTransform()); + if (isIdentityTransform(eSrcConfig, grptf, vals, 1e-3f)) { return i; } diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 6664f64a4e..7fcc7bad0b 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -21,9 +21,11 @@ namespace ConfigUtils // Get color space where isData is false and it has neither a to_ref or from_ref transform. int getRefSpace(const Config & cfg); - // Get processor to a sRGB transform. - ConstProcessorRcPtr getRefToSRGBTransform(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName); + bool isIdentityTransform(const Config & srcConfig, GroupTransformRcPtr & tf, std::vector & vals, float absTolerance); + + // Get reference to a sRGB transform. + TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, + std::string refColorSpaceName); // Get reference space if the specified color space is a recognized linear space. int getReferenceSpaceFromLinearSpace(const Config & srcConfig, From 7d662d76ceb2671b31ceab62966c89641f51ead9 Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:27:04 -0400 Subject: [PATCH 16/22] Refactor code and add tests Signed-off-by: Doug Walker --- include/OpenColorIO/OpenColorIO.h | 126 ++-- src/OpenColorIO/Config.cpp | 201 +++---- src/OpenColorIO/ConfigUtils.cpp | 914 ++++++++++++++++++------------ src/OpenColorIO/ConfigUtils.h | 80 +-- src/OpenColorIO/Processor.cpp | 9 - tests/cpu/ColorSpace_tests.cpp | 658 ++++++++++++++++----- tests/cpu/Config_tests.cpp | 57 +- 7 files changed, 1346 insertions(+), 699 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index e56f9fc7e0..9b5589b25b 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -695,32 +695,78 @@ class OCIOEXPORT Config */ bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const; - /** - * \brief Identify the interchange space of the source config and the default built-in config. - * - * \param[out] srcInterchange Interchange space from the source config (output). - * \param[out] builtinInterchange Interchange space from the default built-in config (output). - * - * \throw Exception if either one of srcInterchange or builtinInterchange cannot be identified. - */ - const char * identifyInterchangeSpace() const; - const char * identifyBuiltinInterchangeSpace() const; - /** * \brief Find the name of the color space in the source config that is the same as - * a color space in the default built-in config. - * - * \param builtinColorSpaceName Color space name in the built-in config. - * \return Matching color space from the source config. Empty if not found. + * a color space in the default built-in config. For example, setting the + * builtinColorSpaceName to "sRGB - Texture" (a color space name from that + * config), would return the name for the corresponding sRGB texture space in + * the current config (or empty if it was not found). Note that this method + * relies on heuristics which may evolve over time and which may not work on + * all configs. + * + * The method only looks at active color spaces. If the interchange roles are + * missing and heuristics are used, only scene-referred color spaces are searched. + * + * \param srcConfig The config to search for the desired color space. + * \param builtinConfig The built-in config to use. See \ref Config::CreateFromBuiltinConfig. + * \param builtinColorSpaceName Color space name in the built-in default config. + * \return Matching color space name from the source config. Empty if not found. + * + * \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. + */ + static const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + + /** + * \brief Identify the two names of a common color space that exists in both the + * given config and the provided built-in config that may be used for converting + * color spaces between the two configs. If both configs have the interchange + * role set, than the color spaces set to that role will be returned. Otherwise, + * heuristics will be used to try and identify a known color space in the source + * config. These are the same heuristics that are used for other methods such as + * identifyBuiltinColorSpace and GetProcessorTo/FromBuiltinColorSpace. + * + * Using this method in connection with GetProcessorFromConfigs is more efficient + * if you need to call GetProcessorTo/FromBuiltinColorSpace multiple times since it + * is only necessary to run the heuristics once (to identify the interchange spaces). + * + * The srcColorSpaceName and builtinColorSpace name are used to decide which + * interchange role to use (scene- or display-referred). However, they are not + * used if the interchange roles are not present and the heuristics are used. + * It is actually only the ReferenceSpaceType of the color spaces that are used, + * so it is not necessary to call this function multiple times if all the spaces + * are of the same type. (These are the same arguments that would also be set if + * you were instead calling GetProcessorTo/FromBuiltinColorSpace.) + * + * \param[out] srcInterchangeName Color space name from the source config. + * \param[out] builtinInterchangeName Corresponding color space name from the built-in config. + * \param srcConfig The config to search for the desired color space. + * \param srcColorSpaceName Color space name in the given config to convert to/from. + * \param builtinConfig The built-in config to use. See \ref Config::CreateFromBuiltinConfig. + * \param builtinColorSpaceName Color space name in the default built-in config. + * + * \throw Exception if either the srcInterchange or builtinInterchange cannot be identified. + */ + static void IdentifyInterchangeSpace(const char ** srcInterchangeName, + const char ** builtinInterchangeName, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + + /** + * \defgroup Methods related to Roles. + * @{ + * + * A role allows a config author to indicate that a given color space should be used + * for a particular purpose. + * + * Role names may be passed to most functions that accept color space names, such as + * getColorSpace. So for example, you may find the name of the color space assigned + * to the scene_linear role by getting the color space object for "scene_linear" and + * then calling getName on the color space object. */ - const char * identifyBuiltinColorSpace(const char * builtinColorSpaceName) const; - - // - // Roles - // - - // A role is like an alias for a colorspace. You can query the colorspace - // corresponding to a role using the normal getColorSpace fcn. /** * \brief @@ -1281,7 +1327,7 @@ class OCIOEXPORT Config * * If the source config defines the necessary Interchange Role (typically "aces_interchange"), * then the conversion will be well-defined and equivalent to calling GetProcessorFromConfigs - * with the source config and the Built-in config + * with the source config and the Built-in config. * * However, if the Interchange Roles are not present, heuristics will be used to try and * identify a common color space in the source config that may be used to allow the conversion @@ -1294,11 +1340,12 @@ class OCIOEXPORT Config * \param srcConfig The user's source config. * \param srcColorSpaceName The name of the color space in the source config. * \param builtinColorSpaceName The name of the color space in the current default Built-in config. + * + * \throw Exception if either the src or builtin interchange space cannot be identified. */ - static ConstProcessorRcPtr GetProcessorToBuiltinColorSpace( - ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName, - const char * builtinColorSpaceName); + static ConstProcessorRcPtr GetProcessorToBuiltinColorSpace(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName); /** * \brief See description of GetProcessorToBuiltinColorSpace. * @@ -1306,14 +1353,12 @@ class OCIOEXPORT Config * \param srcConfig The user's source config. * \param srcColorSpaceName The name of the color space in the source config. */ - static ConstProcessorRcPtr GetProcessorFromBuiltinColorSpace( - const char * builtinColorSpaceName, - ConstConfigRcPtr srcConfig, - const char * srcColorSpaceName); + static ConstProcessorRcPtr GetProcessorFromBuiltinColorSpace(const char * builtinColorSpaceName, + ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName); /** - * \brief Get a processor to convert between color spaces in two separate - * configs. + * \brief Get a processor to convert between color spaces in two separate configs. * * This relies on both configs having the aces_interchange role (when srcName * is scene-referred) or the role cie_xyz_d65_interchange (when srcName is @@ -1333,6 +1378,9 @@ class OCIOEXPORT Config /** * The srcInterchangeName and dstInterchangeName must refer to a pair of * color spaces in the two configs that are the same. A role name may also be used. + * + * Note: For all of the two-config GetProcessor functions, if either the source or + * destination color spaces are data spaces, the entire processor will be a no-op. */ static ConstProcessorRcPtr GetProcessorFromConfigs(const ConstConfigRcPtr & srcConfig, const char * srcColorSpaceName, @@ -2485,11 +2533,11 @@ class OCIOEXPORT Processor * \param tolerance Tolerance of the comparaison. * \return True or false depending on whether the two processors are equivalent. */ - static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance); +// static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, +// ConstProcessorRcPtr & p2, +// float * rgbaValues, +// size_t numValues, +// float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 0518f6ec9c..a6642743cb 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -326,7 +326,7 @@ class Config::Impl mutable std::string m_cacheidnocontext; FileRulesRcPtr m_fileRules; - ProcessorCacheFlags m_cacheFlags { PROCESSOR_CACHE_DEFAULT }; + mutable ProcessorCacheFlags m_cacheFlags { PROCESSOR_CACHE_DEFAULT }; mutable ProcessorCache m_processorCache; Impl() : @@ -918,7 +918,12 @@ class Config::Impl } - void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept + ProcessorCacheFlags getProcessorCacheFlags() const noexcept + { + return m_cacheFlags; + } + + void setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept { m_cacheFlags = flags; m_processorCache.enable((m_cacheFlags & PROCESSOR_CACHE_ENABLED) == PROCESSOR_CACHE_ENABLED); @@ -2732,6 +2737,8 @@ void Config::clearColorSpaces() getImpl()->refreshActiveColorSpaces(); } +/////////////////////////////////////////////////////////////////////////// + bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const { auto cs = getColorSpace(colorSpace); @@ -2783,13 +2790,7 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe 0.f, 0.0625f, 0.f, 0.f, 4.f, 0.f, 0.f, 0.f, 0.0625f, 0.f, 0.f, 4.f }; - std::vector dst = - { - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, - 0.f, 0.f, 0.f, 0.f, 0.f, 0.f - }; + std::vector dst(img.size(), 0.f); PackedImageDesc desc(&img[0], 8, 1, CHANNEL_ORDERING_RGB); PackedImageDesc descDst(&dst[0], 8, 1, CHANNEL_ORDERING_RGB); @@ -2797,7 +2798,7 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe auto procToReference = config.getImpl()->getProcessorWithoutCaching( config, t, TRANSFORM_DIR_FORWARD ); - auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); + auto optCPUProc = procToReference->getOptimizedCPUProcessor(OPTIMIZATION_NONE); optCPUProc->apply(desc, descDst); @@ -2845,39 +2846,31 @@ bool Config::isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType refe // transforms, so it is equivalent to the reference space and hence linear. return true; } - -const char * Config::identifyInterchangeSpace() const -{ - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int srcInterchangeIndex = -1; - ConfigUtils::identifyInterchangeSpace(srcInterchangeIndex, *eSrcConfig); - - return getColorSpaceNameByIndex(srcInterchangeIndex); -} -const char * Config::identifyBuiltinInterchangeSpace() const +void Config::IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) { - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int builtinInterchangeIndex = -1; - ConfigUtils::identifyBuiltinInterchangeSpace(builtinInterchangeIndex, *eSrcConfig); - - return ConfigUtils::getBuiltinLinearSpaces(builtinInterchangeIndex); + // This will throw if it is unable to identify the interchange spaces. + ConfigUtils::IdentifyInterchangeSpace(srcInterchange, + builtinInterchange, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); } -const char * Config::identifyBuiltinColorSpace(const char * builtinColorSpaceName) const +const char * Config::IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) { - // Using createEditableCopy to avoid filling the processor cache in the Config object. - ConfigRcPtr eSrcConfig = createEditableCopy(); - eSrcConfig->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); - - int csIndex = ConfigUtils::identifyBuiltinColorSpace(builtinColorSpaceName, *eSrcConfig); - return getColorSpaceNameByIndex(csIndex); + // This will throw if it is unable to identify the interchange spaces. + return ConfigUtils::IdentifyBuiltinColorSpace(srcConfig, + builtinConfig, + builtinColorSpaceName); } /////////////////////////////////////////////////////////////////////////// @@ -4251,6 +4244,7 @@ bool Config::filepathOnlyMatchesDefaultRule(const char * filePath) const /////////////////////////////////////////////////////////////////////////// +// GetProcessor ConstProcessorRcPtr Config::getProcessor(const ConstColorSpaceRcPtr & src, const ConstColorSpaceRcPtr & dst) const @@ -4478,50 +4472,29 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr const ConstConfigRcPtr & dstConfig, const char * dstName) { - ConstColorSpaceRcPtr srcColorSpace = srcConfig->getColorSpace(srcName); - if (!srcColorSpace) - { - std::ostringstream os; - os << "Could not find source color space '" << srcName << "'."; - throw Exception(os.str().c_str()); - } - - const bool sceneReferred = (srcColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_SCENE); - const char * exchangeRoleName = sceneReferred ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; - const char * srcExName = LookupRole(srcConfig->getImpl()->m_roles, exchangeRoleName); - if (!srcExName || !*srcExName) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' is missing in the source config."; - throw Exception(os.str().c_str()); - } - ConstColorSpaceRcPtr srcExCs = srcConfig->getColorSpace(srcExName); - if (!srcExCs) - { + // Extract the appropriate interchange roles based on the reference space type of the + // source and destination color spaces. Note that no heuristics are attempted. If these + // roles are missing, an exception is raised. (Note that the names of the returned color + // spaces should not depend on the context arguments above.) + const char * srcInterchangeName = nullptr; + const char * dstInterchangeName = nullptr; + ReferenceSpaceType interchangeType; + if( !ConfigUtils::GetInterchangeRolesForColorSpaceConversion(&srcInterchangeName, + &dstInterchangeName, + interchangeType, + srcConfig, srcName, + dstConfig, dstName) ) + { + const char * interchangeRoleName = (interchangeType == REFERENCE_SPACE_SCENE) + ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; std::ostringstream os; - os << "The role '" << exchangeRoleName << "' refers to color space '" << srcExName; - os << "' that is missing in the source config."; + os << "The required role '" << interchangeRoleName << "' is missing from the source and/or " + << "destination config."; throw Exception(os.str().c_str()); } - const char * dstExName = LookupRole(dstConfig->getImpl()->m_roles, exchangeRoleName); - if (!dstExName || !*dstExName) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' is missing in the destination config."; - throw Exception(os.str().c_str()); - } - ConstColorSpaceRcPtr dstExCs = dstConfig->getColorSpace(dstExName); - if (!dstExCs) - { - std::ostringstream os; - os << "The role '" << exchangeRoleName << "' refers to color space '" << dstExName; - os << "' that is missing in the destination config."; - throw Exception(os.str().c_str()); - } - - return GetProcessorFromConfigs(srcContext, srcConfig, srcName, srcExName, - dstContext, dstConfig, dstName, dstExName); + return GetProcessorFromConfigs(srcContext, srcConfig, srcName, srcInterchangeName, + dstContext, dstConfig, dstName, dstInterchangeName); } ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstConfigRcPtr & srcConfig, @@ -4592,7 +4565,14 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr ProcessorRcPtr processor = Processor::Create(); processor->getImpl()->setProcessorCacheFlags(srcConfig->getImpl()->m_cacheFlags); - processor->getImpl()->concatenate(p1, p2); + + // If either of the color spaces are data spaces, its corresponding processor + // will be empty, but need to make sure the entire result is also empty to + // better match the semantics of how data spaces are handled. + if (!srcColorSpace->isData() && !dstColorSpace->isData()) + { + processor->getImpl()->concatenate(p1, p2); + } return processor; } @@ -4612,30 +4592,14 @@ static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, throw Exception(os.str().c_str()); } - // If both configs have the interchange roles set, then it's easy. - try - { - ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - builtinConfig, - builtinColorSpaceName); - return proc; - } - catch(const Exception & e) - { - std::string str1 = "The role 'aces_interchange' is missing in the source config"; - std::string str2 = "The role 'cie_xyz_d65_interchange' is missing in the source config"; - - // Re-throw when the error is not about interchange roles. - if (!StringUtils::StartsWith(e.what(), str1) && !StringUtils::StartsWith(e.what(), str2)) - { - throw Exception(e.what()); - } - // otherwise, do nothing and continue. - } - - const char * srcInterchange = srcConfig->identifyInterchangeSpace(); - const char * builtinInterchange = srcConfig->identifyBuiltinInterchangeSpace(); + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + Config::IdentifyInterchangeSpace(&srcInterchange, + &builtinInterchange, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); if (builtinInterchange && builtinInterchange[0]) { @@ -4643,27 +4607,27 @@ static ConstProcessorRcPtr GetProcessorToBuiltinCS(ConstConfigRcPtr srcConfig, if (direction == TRANSFORM_DIR_FORWARD) { proc = Config::GetProcessorFromConfigs(srcConfig, - srcColorSpaceName, - srcInterchange, - builtinConfig, - builtinColorSpaceName, - builtinInterchange); + srcColorSpaceName, + srcInterchange, + builtinConfig, + builtinColorSpaceName, + builtinInterchange); } else if (direction == TRANSFORM_DIR_INVERSE) { proc = Config::GetProcessorFromConfigs(builtinConfig, - builtinColorSpaceName, - builtinInterchange, - srcConfig, - srcColorSpaceName, - srcInterchange); + builtinColorSpaceName, + builtinInterchange, + srcConfig, + srcColorSpaceName, + srcInterchange); } return proc; } std::ostringstream os; os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; + << "Please set the interchange roles in the config."; throw Exception(os.str().c_str()); } @@ -4687,6 +4651,8 @@ ConstProcessorRcPtr Config::GetProcessorFromBuiltinColorSpace(const char * built TRANSFORM_DIR_INVERSE); } +/////////////////////////////////////////////////////////////////////////// + std::ostream& operator<< (std::ostream& os, const Config& config) { config.serialize(os); @@ -4784,7 +4750,12 @@ void Config::serialize(std::ostream& os) const } } -void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept +ProcessorCacheFlags Config::getProcessorCacheFlags() const noexcept +{ + return getImpl()->getProcessorCacheFlags(); +} + +void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept { getImpl()->setProcessorCacheFlags(flags); } diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 9dec8051c8..7a7d1f26ee 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -7,449 +7,665 @@ namespace OCIO_NAMESPACE { - // Define the set of candidate reference linear color spaces (aka, reference primaries) that - // will be used when searching through the source config. If the source config scene-referred - // reference space is the equivalent of one of these spaces, it should be possible to identify - // it with the following heuristics. - constexpr int numberOfbuiltinLinearSpaces = 5; - constexpr const char * builtinLinearSpaces[] = { "ACES - ACES2065-1", - "ACES - ACEScg", - "Utility - Linear - Rec.709", - "Utility - Linear - P3-D65", - "Utility - Linear - Rec.2020" }; namespace ConfigUtils { - const char * getBuiltinLinearSpaces(int index) + +////////////////////////////////////////////////////////////////////////////////////// + +// The following code needs to know the names of some of the color spaces in the +// built-in default config. If the color space names of that config are ever +// modified, the following strings should be kept in sync. + +// Name of the sRGB space in the built-in config. +// +const char * getSRGBColorSpaceName() +{ + constexpr const char * srgbColorSpaceName = "sRGB - Texture"; + return srgbColorSpaceName; +} + +// Define the set of candidate built-in default config reference linear color spaces that +// will be used when searching through the source config. If the source config scene-referred +// reference space is the equivalent of one of these spaces, it should be possible to identify +// it with the following heuristics. +// +const char * getBuiltinLinearSpaceName(int index) +{ + constexpr const char * builtinLinearSpaces[] = { "ACES2065-1", + "ACEScg", + "Linear Rec.709 (sRGB)", + "Linear P3-D65", + "Linear Rec.2020" }; + return builtinLinearSpaces[Clamp(index, 0, 4)]; +} + +inline int getNumberOfbuiltinLinearSpaces() +{ + return 5; +} + +////////////////////////////////////////////////////////////////////////////////////// + +// Use the interchange roles in the pair of provided configs to return the color space +// names to be used for the conversion between the provided pair of color spaces. +// Note that the color space names returned depend on the image state of the provided +// color spaces. The returned color space names are the names that the interchange +// roles point to and the function checks that they exist. An exception is raised if +// there are problems with the input arguments or if the interchange roles are present +// but point to color spaces that don't exist. If the interchange roles are simply +// not present, no exception is thrown but false is returned. If the returned interchange +// color space names are present and exist, true is returned. +// +// This function does NOT use any heuristics. +// +// \param[out] srcInterchangeCSName -- Name of the interchange color space from the src config. +// \param[out] dstInterchangeCSName -- Name of the interchange color space from the dst config. +// \param srcConfig -- Source config object. +// \param srcName -- Name of the color space to be converted from the source config. +// May be empty if the source color space is unknown. +// \param dstConfig -- Destination config object. +// \param dstName -- Name of the color space to be converted from the destination config. +// \return True if the necessary interchange roles were found. +// +bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, + const char ** dstInterchangeCSName, + ReferenceSpaceType & interchangeType, + const ConstConfigRcPtr & srcConfig, + const char * srcName, + const ConstConfigRcPtr & dstConfig, + const char * dstName) +{ + ConstColorSpaceRcPtr dstColorSpace = dstConfig->getColorSpace(dstName); + if (!dstColorSpace) { - return builtinLinearSpaces[index]; + std::ostringstream os; + os << "Could not find destination color space '" << dstName << "'."; + throw Exception(os.str().c_str()); } - bool containsSRGB(ConstColorSpaceRcPtr & cs) + interchangeType = REFERENCE_SPACE_SCENE; + + if (srcName && !*srcName) { - std::string name = StringUtils::Lower(cs->getName()); - if (StringUtils::Find(name, "srgb") != std::string::npos) + // If srcName is empty, just use the reference type of the destination side. + // In this scenario, the source color space is unknown but the assumption is + // that when it is found it will have the same reference space type as the + // destination color space. + if (dstColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) { - return true; + interchangeType = REFERENCE_SPACE_DISPLAY; + } + } + else + { + ConstColorSpaceRcPtr srcColorSpace = srcConfig->getColorSpace(srcName); + if (!srcColorSpace) + { + std::ostringstream os; + os << "Could not find source color space '" << srcName << "'."; + throw Exception(os.str().c_str()); } - size_t nbOfAliases = cs->getNumAliases(); - for (size_t i = 0; i < nbOfAliases; i++) + // Only use the display-referred reference space if both color spaces are + // display-referred. If only one of the spaces is display-referred, it's + // better to use the scene-referred space since the conversion to scene- + // referred will happen within the config that has the display-referred + // color space. The config with the scene-referred color space may not + // even have a default view transform to use. In addition, it's important + // that this function always use the same reference space even if the order + // of src & dst is swapped, so the result is the inverse (which it might + // not be if the view transform in the opposite config is used). + if (srcColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY && + dstColorSpace->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) { - if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) - { - return true; - } + interchangeType = REFERENCE_SPACE_DISPLAY; } + } + + const char * interchangeRoleName = (interchangeType == REFERENCE_SPACE_SCENE) + ? ROLE_INTERCHANGE_SCENE : ROLE_INTERCHANGE_DISPLAY; + if (!srcConfig->hasRole(interchangeRoleName)) + { return false; } + // Get the color space name assigned to the interchange role. + ConstColorSpaceRcPtr srcInterchangeCS = srcConfig->getColorSpace(interchangeRoleName); + if (!srcInterchangeCS) + { + std::ostringstream os; + os << "The role '" << interchangeRoleName << "' refers to a color space "; + os << "that is missing in the source config."; + throw Exception(os.str().c_str()); + } + *srcInterchangeCSName = srcInterchangeCS->getName(); - int getRefSpace(const Config & cfg) + if (!dstConfig->hasRole(interchangeRoleName)) { - // Find a color space where isData is false and it has neither a to_ref or from_ref - // transform. - auto nbCs = cfg.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = cfg.getColorSpace(cfg.getColorSpaceNameByIndex(i)); - if (cs->isData()) - { - continue; - } + return false; + } + // Get the color space name assigned to the interchange role. + ConstColorSpaceRcPtr dstInterchangeCS = dstConfig->getColorSpace(interchangeRoleName); + if (!dstInterchangeCS) + { + std::ostringstream os; + os << "The role '" << interchangeRoleName << "' refers to a color space "; + os << "that is missing in the destination config."; + throw Exception(os.str().c_str()); + } + *dstInterchangeCSName = dstInterchangeCS->getName(); - auto t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (t != nullptr) - { - continue; - } + return true; +} - t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (t != nullptr) - { - continue; - } +// Return true if the color space name or its aliases contains sRGB (case-insensitive). +// +bool containsSRGB(ConstColorSpaceRcPtr & cs) +{ + std::string name = StringUtils::Lower(cs->getName()); + if (StringUtils::Find(name, "srgb") != std::string::npos) + { + return true; + } - return i; + size_t nbOfAliases = cs->getNumAliases(); + for (size_t i = 0; i < nbOfAliases; i++) + { + if (StringUtils::Find(cs->getAlias(i), "srgb") != std::string::npos) + { + return true; } - return -1; } - bool isIdentityTransform(const Config & srcConfig, - GroupTransformRcPtr & tf, - std::vector & vals, - float absTolerance) - { - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/4, 1, CHANNEL_ORDERING_RGB); + return false; +} - ConstProcessorRcPtr proc = srcConfig.getProcessor(tf, TRANSFORM_DIR_FORWARD); - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); +// Find a color space where isData is false and it has neither a to_ref or from_ref +// transform. Currently only selecting scene-referred spaces. Note: this returns +// the first reference space found, even if it is inactive. Returns empty if none +// are found. +// +const char * getRefSpaceName(const ConstConfigRcPtr & cfg) +{ + // It's important to support inactive spaces since sometimes the only reference space + // may be inactive, e.g. the display-referred reference in the built-in configs. + int nbCs = cfg->getNumColorSpaces(SEARCH_REFERENCE_SPACE_SCENE, COLORSPACE_ALL); + + for (int i = 0; i < nbCs; i++) + { + const char * csname = cfg->getColorSpaceNameByIndex(SEARCH_REFERENCE_SPACE_SCENE, + COLORSPACE_ALL, + i); + ConstColorSpaceRcPtr cs = cfg->getColorSpace(csname); - for (size_t i = 0; i < out.size(); i++) + if (cs->isData()) { - if (!EqualWithAbsError(vals[i], out[i], absTolerance)) - { - return false; - } + continue; + } + ConstTransformRcPtr t = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (t != nullptr) + { + continue; + } + t = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (t != nullptr) + { + continue; } - return true; + return csname; } + return ""; +} + +// Return false if the supplied Processor modifies any of the supplied float values +// by more than the supplied absolute tolerance amount. +// +bool isIdentityTransform(ConstProcessorRcPtr proc, + std::vector & RGBAvals, + float absTolerance) +{ + std::vector out(RGBAvals.size(), 0.f); + + PackedImageDesc desc( &RGBAvals[0], (long) RGBAvals.size() / 4, 1, CHANNEL_ORDERING_RGBA ); + PackedImageDesc descDst( &out[0], (long) RGBAvals.size() / 4, 1, CHANNEL_ORDERING_RGBA ); - TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName) + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_NONE); + cpu->apply(desc, descDst); + + for (size_t i = 0; i < out.size(); i++) { - // Build reference space of the given prims to sRGB transform. - std::string srgbColorSpaceName = "Input - Generic - sRGB - Texture"; - - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(refColorSpaceName.c_str()); - csTransform->setDst(srgbColorSpaceName.c_str()); - - //ConstProcessorRcPtr proc = builtinConfig->getProcessor(csTransform, TRANSFORM_DIR_FORWARD); - //return proc; - return csTransform; + if (!EqualWithAbsError(RGBAvals[i], out[i], absTolerance)) + { + return false; + } } - int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + return true; +} + +// Helper to avoid color spaces that are without transforms or are data spaces. +// +bool hasNoTransform(const ConstColorSpaceRcPtr & cs) +{ + if (cs->isData()) { - // If the color space is a recognized linear space, return the reference space used by - // the config. - int refSpaceIndex = -1; - bool toRefDirection = true; - auto srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (!srcTransform) + return true; + } + ConstTransformRcPtr srcTransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (!srcTransform) + { + srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (srcTransform) { - srcTransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (srcTransform) - { - toRefDirection = false; - } - else - { - return -1; - } + return false; } + else + { + return true; + } + } + return false; +} - // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is - // enough of an identity. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; +// Test the supplied color space against a set of color spaces in the built-in config. +// If a match is found, it indicates what reference space is used by the config. +// Return the index into the list of built-in linear spaces, or -1 if not found. +// +// \param srcConfig -- Source config object. +// \param srcRefName -- Name of a scene-referred reference color space in the src config. +// \param cs -- Color space from the source config to test. +// \param builtinConfig -- The built-in config object. +// \return The index into the list of built-in linear spaces. +// +int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, + const char * srcRefName, + const ConstColorSpaceRcPtr & cs, + const ConstConfigRcPtr & builtinConfig) +{ + // Currently only handling scene-referred spaces in the heuristics. + if (cs->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) + { + return -1; + } + // Don't check spaces without transforms / data spaces. + if (hasNoTransform(cs)) + { + return -1; + } - // Generate matrices between all combinations of the Built-in linear color spaces. - // Then combine these with the transform from the current color space to see if the result is - // an identity. If so, then it identifies the reference space being used by the source config. - TransformRcPtr eSrcTransform = srcTransform->createEditableCopy(); - for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) + // Define a set of (somewhat arbitrary) RGB values to test whether the combined transform is + // enough of an identity. + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, -0.2f, 0.f, + 0.3f, 0.02f, 1.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; + + // Test the transform from the test color space to its reference space against all combinations + // of the built-in linear color spaces. If one of them results in an identity, that identifies + // what the source color space and reference space are. + + for (int i = 0; i < getNumberOfbuiltinLinearSpaces(); i++) + { + for (int j = 0; j < getNumberOfbuiltinLinearSpaces(); j++) { - for (size_t j = 0; j < numberOfbuiltinLinearSpaces; j++) + // Ensure the built-in side of the conversion is never an identity, since if + // both the src side and built-in side are an identity, it would seem as though + // the reference space has been identified, but in fact it would not be. + if (i != j) { - if (i != j) + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs( + srcConfig, + cs->getName(), + srcRefName, + builtinConfig, + getBuiltinLinearSpaceName(i), + getBuiltinLinearSpaceName(j)); + + if (isIdentityTransform(proc, vals, 1e-3f)) { - ColorSpaceTransformRcPtr csTransform = ColorSpaceTransform::Create(); - csTransform->setSrc(builtinLinearSpaces[j]); - csTransform->setDst(builtinLinearSpaces[i]); - - GroupTransformRcPtr grptf = GroupTransform::Create(); - grptf->appendTransform(eSrcTransform); - grptf->appendTransform(csTransform); - - if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) - { - if (toRefDirection) - { - refSpaceIndex = (int) j; - } - else - { - refSpaceIndex = (int) i; - } - - return refSpaceIndex; - } + return j; } } } + } + + return -1; +} +// Test the supplied color space against a set of color spaces in the built-in config +// to see if it matches an sRGB texture color space with one of a set of known primaries +// used as its reference space. If a match is found, it indicates what reference space +// is used by the config. Return the index into the list of built-in linear spaces, +// or -1 if not found. +// +// \param srcConfig -- Source config object. +// \param srcRefName -- Name of a scene-referred reference color space in the src config. +// \param cs -- Color space from the source config to test. +// \param builtinConfig -- The built-in config object. +// \return The index into the list of built-in linear spaces. +// +int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, + const char * srcRefName, + const ConstColorSpaceRcPtr cs, + const ConstConfigRcPtr & builtinConfig) +{ + // Currently only handling scene-referred spaces in the heuristics. + if (cs->getReferenceSpaceType() == REFERENCE_SPACE_DISPLAY) + { return -1; } - - int getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig, - const char * const * builtinLinearSpaces) + // Get a transform in the to-reference direction. + ConstTransformRcPtr toRefTransform; + ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); + if (ctransform) { - // If the color space is an sRGB texture space, return the reference space used by the config. - - // Get a transform in the to-reference direction. - ConstTransformRcPtr toRefTransform; - ConstTransformRcPtr ctransform = cs->getTransform(COLORSPACE_DIR_TO_REFERENCE); - if (ctransform) + toRefTransform = ctransform; + } + else + { + ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); + if (ctransform) { - toRefTransform = ctransform; + TransformRcPtr transform = ctransform->createEditableCopy(); + transform->setDirection(TRANSFORM_DIR_INVERSE); + toRefTransform = transform; } else { - ctransform = cs->getTransform(COLORSPACE_DIR_FROM_REFERENCE); - if (ctransform) - { - TransformRcPtr transform = ctransform->createEditableCopy(); - transform->setDirection(TRANSFORM_DIR_INVERSE); - toRefTransform = transform; - } - else - { - // Both directions missing. - return -1; - } + // Don't check spaces without transforms / data spaces. + return -1; } + } - // First check if it has the right non-linearity. The objective is to fail quickly on color - // spaces that are definitely not sRGB before proceeding to the longer test of guessing the - // reference space primaries. - - // Break point is at 0.039286, so include at least one value below this. - std::vector vals = - { - 0.5f, 0.5f, 0.5f, - 0.03f, 0.03f, 0.03f, - 0.25f, 0.25f, 0.25f, - 0.75f, 0.75f, 0.75f, - 0.f, 0.f, 0.f, - 1.f, 1.f , 1.f - }; - std::vector out = vals; - - PackedImageDesc desc(&vals[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - PackedImageDesc descDst(&out[0], (long) vals.size()/3, 1, CHANNEL_ORDERING_RGB); - - ConstProcessorRcPtr proc = config.getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - // Apply the sRGB function (linear to non-lin). - // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. - if (out[i] <= 0.0030399346397784323f) - { - out[i] *= 12.923210180787857f; - } - else - { - out[i] = 1.055f * std::pow(out[i], 1/2.4f) - 0.055f; - } - - if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) - { - return -1; - } - } - - // - // Then try the various primaries for the reference space. - // + // First check if it has the right non-linearity. The objective is to fail quickly on color + // spaces that are definitely not sRGB before proceeding to the longer test of guessing the + // reference space primaries. - // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact - // converting sRGB texture values to the candidate reference space. It includes 0.02 which is - // on the sRGB linear segment, color values, and neutral values. - vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f, }; - int refSpaceIndex = -1; - TransformRcPtr fromRefTransform; - TransformRcPtr eToRefTransform = toRefTransform->createEditableCopy(); - if (toRefTransform) - { - // The color space has the sRGB non-linearity. Now try combining the transform with a - // transform from the Built-in config that goes from a variety of reference spaces to an - // sRGB texture space. If the result is an identity, then that tells what the source config - // reference space is. - for (size_t i = 0; i < numberOfbuiltinLinearSpaces; i++) - { - fromRefTransform = getTransformToSRGBSpace(builtinConfig, builtinLinearSpaces[i]); + // Break point is at 0.039286, so include at least one value below this. + std::vector vals = + { + 0.5f, 0.5f, 0.5f, + 0.03f, 0.03f, 0.03f, + 0.25f, 0.25f, 0.25f, + 0.75f, 0.75f, 0.75f, + 0.f, 0.f, 0.f, + 1.f, 1.f , 1.f + }; + std::vector out(vals.size(), 0.f); - GroupTransformRcPtr grptf = GroupTransform::Create(); - grptf->appendTransform(eToRefTransform); - grptf->appendTransform(fromRefTransform); + PackedImageDesc desc( &vals[0], (long) vals.size() / 3, 1, CHANNEL_ORDERING_RGB ); + PackedImageDesc descDst( &out[0], (long) vals.size() / 3, 1, CHANNEL_ORDERING_RGB ); - if (isIdentityTransform(*builtinConfig, grptf, vals, 1e-3f)) - { - refSpaceIndex = (int) i; - break; - } - } - } + ConstProcessorRcPtr proc = srcConfig->getProcessor(toRefTransform, TRANSFORM_DIR_FORWARD); - return refSpaceIndex; - } + ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_NONE); + // Convert the non-linear values to linear. + cpu->apply(desc, descDst); - void identifyInterchangeSpace(int & srcInterchangeIndex, Config & eSrcConfig) + for (size_t i = 0; i < out.size(); i++) { - if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) + // Apply the sRGB function to convert the processed linear values back to non-linear. + // Please see GammaOpUtils.cpp. This value provides continuity at the breakpoint. + if (out[i] <= 0.0030399346397784323f) { - std::ostringstream os; - os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; - throw Exception(os.str().c_str()); + out[i] *= 12.923210180787857f; } - - // Get the name of (one of) the reference spaces. - int refColorSpaceIndex = getRefSpace(eSrcConfig); - if (refColorSpaceIndex < 0) + else { - std::ostringstream os; - os << "The supplied config does not have a color space for the reference."; - throw Exception(os.str().c_str()); + out[i] = 1.055f * std::pow(out[i], 1.f / 2.4f) - 0.055f; } - - if (refColorSpaceIndex > -1) + // Compare against the original source values. + if (!EqualWithAbsError(vals[i], out[i], 1e-3f)) { - srcInterchangeIndex = refColorSpaceIndex; + return -1; } - else + } + + // Define a (somewhat arbitrary) set of RGB values to test whether the transform is in fact + // converting sRGB texture values to the candidate reference space. It includes 0.02 which is + // on the sRGB linear segment, color values, and neutral values. + vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f, }; + + // The color space has the sRGB non-linearity. Now try combining the transform with a + // transform from the Built-in config that goes from a variety of reference spaces to an + // sRGB texture space. If the result is an identity, then that tells what the source config + // reference space is. + + for (int i = 0; i < getNumberOfbuiltinLinearSpaces(); i++) + { + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + cs->getName(), + srcRefName, + builtinConfig, + getSRGBColorSpaceName(), + getBuiltinLinearSpaceName(i)); + if (isIdentityTransform(proc, vals, 1e-3f)) { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); + return i; } } - void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig) + return -1; +} + +// Identify the interchange spaces of the source config and the built-in default config +// that should be used to convert from the src color space to the built-in color space, +// or vice-versa. Throws if no suitable spaces are found. +// +// \param[out] srcInterchange -- Name of the interchange color space from the source config. +// \param[out] builtinInterchange -- Name of the interchange color space from the built-in config. +// \param srcConfig -- Source config object. +// \param srcColorSpaceName -- Name of the color space to be converted from the source config. +// \param builtinConfig -- Built-in config object. +// \param builtinColorSpaceName -- Name of the color space to be converted from the built-in config. +// +// \throw Exception if an interchange space cannot be found. +// +void IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) +{ + // Before resorting to heuristics, check if the configs already have the interchange + // roles defined. + // + // Note that this is the only place that srcColorSpaceName and builtinColorSpaceName + // are used, in order to determine whether the scene- or display-referred interchange + // role is most appropriate. These color spaces are not used below for the heuristics. + + ReferenceSpaceType interchangeType; + if ( GetInterchangeRolesForColorSpaceConversion(srcInterchange, builtinInterchange, + interchangeType, + srcConfig, srcColorSpaceName, + builtinConfig, builtinColorSpaceName) ) + { + // No need for the heuristics. + return; + } + + // Use heuristics to try and find a color space in the source config that matches + // a color space in the Built-in config. + + // Currently only handling scene-referred spaces in the heuristics. + if (builtinConfig->getColorSpace(builtinColorSpaceName)->getReferenceSpaceType() + == REFERENCE_SPACE_DISPLAY) + { + std::ostringstream os; + os << "The heuristics currently only support scene-referred color spaces. " + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } + + // Identify the name of a reference space in the source config. + *srcInterchange = getRefSpaceName(srcConfig); + if (!*srcInterchange) { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); + std::ostringstream os; + os << "The supplied config does not have a color space for the reference."; + throw Exception(os.str().c_str()); + } - if (eSrcConfig.hasRole(ROLE_INTERCHANGE_SCENE)) + // The heuristics need to create a lot of Processors and send RGB values through + // them to try and identify a known color space. Turn off the Processor cache in + // the configs to avoid polluting the cache with transforms that won't be reused + // and avoid the overhead of maintaining the cache. + SuspendCacheGuard srcGuard(srcConfig); + SuspendCacheGuard builtinGuard(builtinConfig); + + // Check for an sRGB texture space. + int refColorSpacePrimsIndex = -1; + int nbCs = srcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) + { + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (containsSRGB(cs)) { - std::ostringstream os; - os << "The role '" << ROLE_INTERCHANGE_SCENE << "' is missing in the source config."; - throw Exception(os.str().c_str()); + refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(srcConfig, + *srcInterchange, + cs, + builtinConfig); + // Break out when a match is found. + if (refColorSpacePrimsIndex > -1) break; } + } - // Use heuristics to try and find a color space in the source config that matches - // a color space in the Built-in config. - - // Check for an sRGB texture space. - int refColorSpacePrimsIndex = -1; - int nbCs = eSrcConfig.getNumColorSpaces(); + if (refColorSpacePrimsIndex < 0) + { + // Check for a scene-linear space with known primaries. + nbCs = srcConfig->getNumColorSpaces(); for (int i = 0; i < nbCs; i++) { - ConstColorSpaceRcPtr cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); - if (containsSRGB(cs)) + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (srcConfig->isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) { - refColorSpacePrimsIndex = getReferenceSpaceFromSRGBSpace(eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); + refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(srcConfig, + *srcInterchange, + cs, + builtinConfig); // Break out when a match is found. if (refColorSpacePrimsIndex > -1) break; } } + } - if (refColorSpacePrimsIndex < 0) - { - // Check for a linear space with known primaries. - nbCs = eSrcConfig.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) - { - auto cs = eSrcConfig.getColorSpace(eSrcConfig.getColorSpaceNameByIndex(i)); - if (eSrcConfig.isColorSpaceLinear(cs->getName(), REFERENCE_SPACE_SCENE)) - { - refColorSpacePrimsIndex = getReferenceSpaceFromLinearSpace(eSrcConfig, - cs, - builtinConfig, - builtinLinearSpaces); - // Break out when a match is found. - if (refColorSpacePrimsIndex > -1) break; - } - } - } + if (refColorSpacePrimsIndex > -1) + { + *builtinInterchange = getBuiltinLinearSpaceName(refColorSpacePrimsIndex); + } + else + { + std::ostringstream os; + os << "Heuristics were not able to find a known color space in the provided config. " + << "Please set the interchange roles."; + throw Exception(os.str().c_str()); + } +} - if (refColorSpacePrimsIndex > -1) - { - builtinInterchangeIndex = refColorSpacePrimsIndex; - } - else - { - std::ostringstream os; - os << "Heuristics were not able to find a known color space in the provided config.\n" - << "Please set the interchange roles."; - throw Exception(os.str().c_str()); - } +// Try to find the name of a color space in the source config that is equivalent to the +// specified color space from the provided built-in config. Only active color spaces +// are searched. +// +// \param srcConfig -- The source config object to search. +// \param builtinConfig -- The built-in config object containing the desired color space. +// \param builtinColorSpaceName -- Name of the desired color space from the built-in config. +// \return The name of the color space in the source config. +// +// \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. +// +const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) +{ + // Note: Technically, the built-in config could be any config, if the interchange + // roles are set in both configs, and the supplied built-in config supports the list + // of color spaces returned by getBuiltinLinearSpaceName. + + ConstColorSpaceRcPtr builtinColorSpace = builtinConfig->getColorSpace(builtinColorSpaceName); + if (!builtinColorSpace) + { + std::ostringstream os; + os << "Built-in config does not contain the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); } - int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig) + ReferenceSpaceType builtinRefSpaceType = builtinColorSpace->getReferenceSpaceType(); + + // Identify interchange spaces. Passing an empty string for the source color space + // means that only the builtinColorSpace will be used to determine the reference + // space type of the interchange role. Will throw if the space cannot be found. + // Only color spaces in the srcConfig that have the same reference type as the + // builtinColorSpace will be searched by the heuristics below. + const char * srcInterchangeName = nullptr; + const char * builtinInterchangeName = nullptr; + IdentifyInterchangeSpace(&srcInterchangeName, + &builtinInterchangeName, + srcConfig, + "", + builtinConfig, + builtinColorSpaceName); + + // The heuristics need to create a lot of Processors and send RGB values through + // them to try and identify a known color space. Turn off the Processor cache in + // the configs to avoid polluting the cache with transforms that won't be reused + // and avoid the overhead of maintaining the cache. + SuspendCacheGuard srcGuard(srcConfig); + SuspendCacheGuard builtinGuard(builtinConfig); + + if (*builtinInterchangeName) { - // Use the Default config as the Built-in config. - ConstConfigRcPtr builtinConfig = Config::CreateFromFile("ocio://default"); - - if (builtinConfig->getColorSpace(builtinColorSpaceName) == nullptr) - { - std::ostringstream os; - os << "Built-in config does not contain the requested color space: " - << builtinColorSpaceName << "."; - throw Exception(os.str().c_str()); - } + std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, + 0.02f, 0.6f, 0.2f, 0.f, + 0.3f, 0.02f, 0.5f, 0.f, + 0.f, 0.f, 0.f, 0.f, + 1.f, 1.f, 1.f, 0.f }; - int srcInterchangeIndex = -1; - int builtinInterchangeIndex = -1; - // Identify interchange space. - identifyInterchangeSpace(srcInterchangeIndex, eSrcConfig); - identifyBuiltinInterchangeSpace(builtinInterchangeIndex, eSrcConfig); - - // Get processor from that space to the built-in color space. - ConstProcessorRcPtr builtinProc; - if (builtinInterchangeIndex > -1) - { - builtinProc = builtinConfig->getProcessor(builtinConfig->getCurrentContext(), - builtinConfig->getColorSpaceNameByIndex(builtinInterchangeIndex), - builtinColorSpaceName); - } + // Loop over each non-data color space in the source config and test if the conversion to + // the specified space in the built-in config is an identity. + // + // Note that there is a possibility that both the source and built-in sides of the + // transform could be an identity (e.g., if the user asks for ACES2065-1 and that is + // also the reference space in both configs). However, this would not prevent the + // algorithm from returning the correct result, as long as the interchange spaces + // were correctly identified. - if (builtinProc && srcInterchangeIndex > -1) + int nbCs = srcConfig->getNumColorSpaces(); + for (int i = 0; i < nbCs; i++) { - // Iterate over each color space in the source config. - std::vector vals = { 0.7f, 0.4f, 0.02f, 0.f, - 0.02f, 0.6f, 0.2f, 0.f, - 0.3f, 0.02f, 0.5f, 0.f, - 0.f, 0.f, 0.f, 0.f, - 1.f, 1.f, 1.f, 0.f }; - int nbCs = eSrcConfig.getNumColorSpaces(); - for (int i = 0; i < nbCs; i++) + ConstColorSpaceRcPtr cs = srcConfig->getColorSpace(srcConfig->getColorSpaceNameByIndex(i)); + if (cs->isData() || (cs->getReferenceSpaceType() != builtinRefSpaceType)) { - // Get processor from that space to its reference and then use isProcessorEquivalent. - // If equivalent, return that color space name. - - const char * csName = eSrcConfig.getColorSpaceNameByIndex(i); - ConstProcessorRcPtr proc = eSrcConfig.getProcessor(eSrcConfig.getCurrentContext(), - csName, - eSrcConfig.getColorSpaceNameByIndex(srcInterchangeIndex)); + continue; + } - auto grptf = builtinProc->createGroupTransform(); - grptf->appendTransform(proc->createGroupTransform()); - if (isIdentityTransform(eSrcConfig, grptf, vals, 1e-3f)) - { - return i; - } + ConstProcessorRcPtr proc = Config::GetProcessorFromConfigs(srcConfig, + cs->getName(), + srcInterchangeName, + builtinConfig, + builtinColorSpaceName, + builtinInterchangeName); + if (isIdentityTransform(proc, vals, 1e-3f)) + { + return cs->getName(); } } - - return -1; } + + std::ostringstream os; + os << "Heuristics were not able to find an equivalent to the requested color space: " + << builtinColorSpaceName << "."; + throw Exception(os.str().c_str()); } -} \ No newline at end of file +} // namespace ConfigUtils + +} // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 7fcc7bad0b..3963f88d38 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -13,39 +13,53 @@ namespace OCIO_NAMESPACE namespace ConfigUtils { - const char * getBuiltinLinearSpaces(int index); - - // Return whether the color space contains SRGB or not. - bool containsSRGB(ConstColorSpaceRcPtr & cs); - - // Get color space where isData is false and it has neither a to_ref or from_ref transform. - int getRefSpace(const Config & cfg); - - bool isIdentityTransform(const Config & srcConfig, GroupTransformRcPtr & tf, std::vector & vals, float absTolerance); - - // Get reference to a sRGB transform. - TransformRcPtr getTransformToSRGBSpace(const ConstConfigRcPtr & builtinConfig, - std::string refColorSpaceName); - - // Get reference space if the specified color space is a recognized linear space. - int getReferenceSpaceFromLinearSpace(const Config & srcConfig, - const ConstColorSpaceRcPtr & cs, - const ConstConfigRcPtr & builtinConfig); - - // Get reference space if the specified color space is an sRGB texture space. - int getReferenceSpaceFromSRGBSpace(const Config & config, - const ConstColorSpaceRcPtr cs, - const ConstConfigRcPtr & builtinConfig); - - // Identify the interchange space of the source config and the default built-in config. - void identifyInterchangeSpace(int & srcInterchange, Config & eSrcConfig); - // Identify the interchange space the default built-in config. - void identifyBuiltinInterchangeSpace(int & builtinInterchangeIndex, Config & eSrcConfig); - - // Find the name of the color space in the source config that is the same as - // a color space in the default built-in config. - int identifyBuiltinColorSpace(const char * builtinColorSpaceName, Config & eSrcConfig); -} + +bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, + const char ** dstInterchangeCSName, + ReferenceSpaceType & interchangeType, + const ConstConfigRcPtr & srcConfig, + const char * srcName, + const ConstConfigRcPtr & dstConfig, + const char * dstName); + +void IdentifyInterchangeSpace(const char ** srcInterchange, + const char ** builtinInterchange, + const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + +const char * IdentifyBuiltinColorSpace(const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName); + +// Temporarily deactivate the Processor cache on a Config object. +// Currently, this also clears the cache. +// +class SuspendCacheGuard +{ +public: + SuspendCacheGuard(); + SuspendCacheGuard(const SuspendCacheGuard &) = delete; + SuspendCacheGuard & operator=(const SuspendCacheGuard &) = delete; + + SuspendCacheGuard(const ConstConfigRcPtr & config) + : m_config(config), m_origCacheFlags(config->getProcessorCacheFlags()) + { + m_config->setProcessorCacheFlags(PROCESSOR_CACHE_OFF); + } + + ~SuspendCacheGuard() + { + m_config->setProcessorCacheFlags(m_origCacheFlags); + } + +private: + ConstConfigRcPtr m_config = nullptr; + ProcessorCacheFlags m_origCacheFlags; +}; + +} // namespace ConfigUtils } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index bfbe6d0f9a..9998bb319a 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -219,15 +219,6 @@ ConstCPUProcessorRcPtr Processor::getOptimizedCPUProcessor(BitDepth inBitDepth, return getImpl()->getOptimizedCPUProcessor(inBitDepth, outBitDepth, oFlags); } -bool Processor::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float tolerance) -{ - return Processor::Impl::AreProcessorsEquivalent(p1, p2, rgbaValues, numValues, tolerance); -} - // Instantiate the cache with the right types. template class ProcessorCache; diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index 3d8a8570d4..8c37c37975 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -948,7 +948,7 @@ inactive_colorspaces: [display_linear-trans, scene_linear-trans] } } -OCIO_ADD_TEST(Processor, processor_to_known_colorspace) +OCIO_ADD_TEST(ConfigUtils, processor_to_known_colorspace) { constexpr const char * CONFIG { R"( ocio_profile_version: 2 @@ -957,7 +957,37 @@ ocio_profile_version: 2 default: raw scene_linear: ref_cs +display_colorspaces: + - ! + name: CIE-XYZ-D65 + description: The CIE XYZ (D65) display connection colorspace. + isdata: false + + - ! + name: sRGB - Display CS + description: Convert CIE XYZ (D65 white) to sRGB (piecewise EOTF) + isdata: false + from_display_reference: ! {style: DISPLAY - CIE-XYZ-D65_to_sRGB} + colorspaces: + # Put a couple of test color space first in the config since the heuristics stop upon success. + + - ! + name: File color space + description: Verify that that FileTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: lut1d_green.ctf} + + - ! + name: CS Transform color space + description: Verify that that ColorSpaceTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: ref_cs, dst: not sRGB} + - ! name: raw description: A data colorspace (should not be used). @@ -965,7 +995,7 @@ ocio_profile_version: 2 - ! name: ref_cs - description: The reference colorspace. + description: The reference colorspace, ACES2065-1. isdata: false - ! @@ -990,119 +1020,26 @@ ocio_profile_version: 2 - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. isdata: false from_scene_reference: ! - name: AP0 to sRGB Rec.709 + name: AP0 to sRGB Encoded AP1 - Texture children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. isdata: false from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture + name: AP0 to sRGB Rec.709 children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - )" }; - auto checkProcessor = [](OCIO::ConstProcessorRcPtr & proc) - { - OCIO::GroupTransformRcPtr gt = proc->createGroupTransform(); - OCIO_CHECK_EQUAL(gt->getNumTransforms(), 4); - - { - OCIO::ConstLogCameraTransformRcPtr logAfTf0 = - OCIO::DynamicPtrCast(gt->getTransform(0)); - - double values[3]; - logAfTf0->getLogSideSlopeValue(values); - OCIO_CHECK_CLOSE(values[0], 0.0570776, 0.000001); - OCIO_CHECK_EQUAL(logAfTf0->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); - } - - { - auto mt1 = OCIO::DynamicPtrCast(gt->getTransform(1)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt1->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 0.6954522413574519, 0.000001); - OCIO_CHECK_EQUAL(mt1->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt2 = OCIO::DynamicPtrCast(gt->getTransform(2)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt2->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 1.45143931607166, 0.000001); - OCIO_CHECK_EQUAL(mt2->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - double vals[4]; - auto mt3 = OCIO::DynamicPtrCast(gt->getTransform(3)); - mt3->getValue(vals); - OCIO_CHECK_CLOSE(vals[0], 2.2, 0.000001); - OCIO_CHECK_EQUAL(mt3->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); - } - }; - - auto checkProcessorInverse = [](OCIO::ConstProcessorRcPtr & proc) - { - OCIO::GroupTransformRcPtr gt = proc->createGroupTransform(); - OCIO_CHECK_EQUAL(gt->getNumTransforms(), 4); - - { - double vals[4]; - auto mt0 = OCIO::DynamicPtrCast(gt->getTransform(0)); - mt0->getValue(vals); - OCIO_CHECK_CLOSE(vals[0], 2.2, 0.000001); - OCIO_CHECK_EQUAL(mt0->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt1 = OCIO::DynamicPtrCast(gt->getTransform(1)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt1->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 0.6954522413574519, 0.000001); - OCIO_CHECK_EQUAL(mt1->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - auto mt2 = OCIO::DynamicPtrCast(gt->getTransform(2)); - double mat[16] = { 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0 }; - mt2->getMatrix(mat); - OCIO_CHECK_CLOSE(mat[0], 1.45143931607166, 0.000001); - OCIO_CHECK_EQUAL(mt2->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - - { - OCIO::ConstLogCameraTransformRcPtr logAfTf0 = - OCIO::DynamicPtrCast(gt->getTransform(3)); - - double values[3]; - logAfTf0->getLogSideSlopeValue(values); - OCIO_CHECK_CLOSE(values[0], 0.0570776, 0.000001); - OCIO_CHECK_EQUAL(logAfTf0->getDirection(), OCIO::TRANSFORM_DIR_FORWARD); - } - }; - std::istringstream is; is.str(CONFIG); OCIO::ConstConfigRcPtr cfg; @@ -1110,6 +1047,10 @@ ocio_profile_version: 2 OCIO::ConfigRcPtr editableCfg = cfg->createEditableCopy(); + editableCfg->setSearchPath(OCIO::GetTestFilesDir().c_str()); + + OCIO::ConstConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default"); + // Make all color spaces suitable for the heuristics inactive. // The heuristics don't look at inactive color spaces. editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, Texture -- sRGB"); @@ -1117,105 +1058,524 @@ ocio_profile_version: 2 std::string srcColorSpaceName = "not sRGB"; std::string builtinColorSpaceName = "Gamma 2.2 AP1 - Texture"; + // Test throw if no suitable spaces are present. { - // Test throw if no suitable spaces are present. - - OCIO_CHECK_THROW(auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace( - editableCfg, - srcColorSpaceName.c_str(), - builtinColorSpaceName.c_str()), - + OCIO_CHECK_THROW( + auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()), OCIO::Exception ); } - { - // Test sRGB Texture space. + // Generate a reference Processor for the correct result. + OCIO::ConstProcessorRcPtr refProc = OCIO::Config::GetProcessorFromConfigs( + editableCfg, + srcColorSpaceName.c_str(), + "ref_cs", + builtinConfig, + builtinColorSpaceName.c_str(), + "ACES2065-1"); + + // Now make various spaces active and test that they enable the heuristics to find + // the appropriate interchange spaces. - editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + { + // Uses "sRGB Texture" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Test using linear color space from_ref direction. + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); { - // Test linear color space from_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + // Uses "Linear Rec.709 (sRGB)" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Test linear color space to_ref direction. + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + // Uses "ACEScg" to find the reference. auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, srcColorSpaceName.c_str(), builtinColorSpaceName.c_str()); - checkProcessor(proc); + OCIO_CHECK_EQUAL(std::string(refProc->getCacheID()), std::string(proc->getCacheID())); } + // Generate a reference Processor for the correct result. + OCIO::ConstProcessorRcPtr invRefProc = OCIO::Config::GetProcessorFromConfigs( + builtinConfig, + builtinColorSpaceName.c_str(), + "ACES2065-1", + editableCfg, + srcColorSpaceName.c_str(), + "ref_cs"); + + // Make the reference space inactive too. + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, ref_cs"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + // Uses "sRGB Texture" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } + // Test using linear color space from_ref direction. + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB, ref_cs"); { - // Test linear color space from_ref direction. - - editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + // Uses "Linear Rec.709 (sRGB)" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } + // Test linear color space to_ref direction. + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB, ref_cs"); { - // Test linear color space to_ref direction. - - editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + // Uses "ACEScg" to find the reference. auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), editableCfg, srcColorSpaceName.c_str()); - checkProcessorInverse(proc); + OCIO_CHECK_EQUAL(std::string(invRefProc->getCacheID()), std::string(proc->getCacheID())); } - // identifyBuiltinColorSpace tests. + // + // Test IdentifyInterchangeSpace. + // + { - const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + // Uses "ACEScg" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES2065-1")); + } + // Set the interchange role. In order to prove that it is being used rather than + // the heuristics, set it to something wrong and check that it gets returned anyway. + editableCfg->setRole("aces_interchange", "Texture -- sRGB"); + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("Texture -- sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES2065-1")); } + // Unset the interchange role, so the heuristics will be used for the other tests. + editableCfg->setRole("aces_interchange", ""); + + // Check what happens if a totally bogus config is passed for the built-in config. + // (It fails in the first heuristic that tries to use one of the known built-in spaces.) + OCIO::ConstConfigRcPtr rawCfg = OCIO::Config::CreateRaw(); + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", + rawCfg, "raw"), + OCIO::Exception, + "Could not find destination color space 'sRGB - Texture'" + ); + } + // Check what happens if the source color space doesn't exist. { - const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Foo", + rawCfg, "raw"), + OCIO::Exception, + "Could not find source color space 'Foo'." + ); + } + // Check what happens if the destination color space doesn't exist. + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Foo", + rawCfg, ""), + OCIO::Exception, + "Could not find destination color space ''." + ); } + // + // Test IdentifyBuiltinColorSpace. + // + + editableCfg->setInactiveColorSpaces(""); + { - const char * csname = cfg->identifyBuiltinColorSpace("ACEScg"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); } { - const char * csname = cfg->identifyBuiltinColorSpace("sRGB - Texture"); - OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Texture -- sRGB")); } - // identifyInterchangeSpace tests. { - const char * srcInterchange = cfg->identifyInterchangeSpace(); - const char * builtinInterchange = cfg->identifyBuiltinInterchangeSpace(); - OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("ref_cs")); - OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("ACES - ACES2065-1")); + // Uses "Texture -- sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ref_cs")); + } + + editableCfg->setInactiveColorSpaces("Texture -- sRGB, ref_cs"); + + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("Linear ITU-R BT.709")); + } + + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("CS Transform color space")); + } + + // Aliases for built-in color spaces must work. + { + // Uses "ACEScg" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } + + // Display-referred spaces are not supported unless the display-referred interchange + // role is present. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } + + // The next three cases directly use the interchange roles rather than heuristics. + + // With the required role, it then works. + editableCfg->setRole("cie_xyz_d65_interchange", "CIE-XYZ-D65"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB - Display CS")); + } + + // Must continue to work if the color space for the interchange role is inactive. + editableCfg->setInactiveColorSpaces("CIE-XYZ-D65"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB - Display CS")); + } + + // Test the scene-referred interchange role (and even make it inactive). + editableCfg->setRole("aces_interchange", "ref_cs"); + editableCfg->setInactiveColorSpaces("ref_cs"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES cg")); + } +} + +OCIO_ADD_TEST(ConfigUtils, processor_to_known_colorspace_alt_config) +{ + // This test uses a config that has a different reference space than the built-in config. + + constexpr const char * CONFIG { R"( +ocio_profile_version: 2 + +environment: {} + +roles: + default: sRGB + scene_linear: scene-linear Rec.709-sRGB + rendering: scene-linear Rec.709-sRGB + +file_rules: + - ! {name: Default, colorspace: default} + +shared_views: + - ! {name: Un-tone-mapped, view_transform: Un-tone-mapped, display_colorspace: } + - ! {name: Raw, colorspace: Raw} + +displays: + sRGB: + - ! [Un-tone-mapped, Raw] + Gamma 2.2 / Rec.709: + - ! [ Un-tone-mapped, Raw] + +view_transforms: + - ! + name: Un-tone-mapped + from_scene_reference: ! {matrix: [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]} + +inactive_colorspaces: [scene-linear Rec.709-sRGB, ACES2065-1] + +display_colorspaces: + - ! + name: CIE-XYZ D65 + encoding: display-linear + isdata: false + to_display_reference: ! {matrix: [ 3.240969941905, -1.537383177570, -0.498610760293, 0, -0.969243636281, 1.875967501508, 0.041555057407, 0, 0.055630079697, -0.203976958889, 1.056971514243, 0, 0, 0, 0, 1 ]} + + - ! + name: display-linear Rec.709-sRGB + description: | + Display reference space + isdata: false + encoding: display-linear + + - ! + name: sRGB + isdata: false + categories: [ file-io ] + encoding: sdr-video + from_display_reference: ! + children: + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + - ! {min_in_value: 0., min_out_value: 0., max_in_value: 1., max_out_value: 1.} + +colorspaces: + - ! + name: Raw + isdata: true + categories: [ file-io ] + encoding: data + + - ! + name: ACES2065-1 + isdata: false + encoding: scene-linear + to_scene_reference: ! {matrix: [ 2.521686186744, -1.134130988240, -0.387555198504, 0, -0.276479914230, 1.372719087668, -0.096239173438, 0, -0.015378064966, -0.152975335867, 1.168353400833, 0, 0, 0, 0, 1 ]} + + - ! + name: scene-linear Rec.709-sRGB + description: | + Scene-linear Rec.709 or sRGB primaries -- ** This is the scene reference space ** + isdata: false + categories: [ file-io, working-space ] + encoding: scene-linear + + - ! + name: scene-linear P3-D65 + aliases: [lin_p3d65, Utility - Linear - P3-D65] + isdata: false + encoding: scene-linear + to_scene_reference: ! {matrix: [ 1.224940176281e+00, -2.249401762806e-01, 0, 0, -4.205695470969e-02, 1.042056954710e+00, 0, 0, -1.963755459033e-02, -7.863604555063e-02, 1.098273600141e+00, 0, 0, 0, 0, 1 ]} + + - ! + name: scene-linear Rec.2020 + isdata: false + encoding: scene-linear + from_scene_reference: ! {matrix: [ 0.627403895935, 0.329283038378, 0.043313065687, 0 , 0.069097289358, 0.919540395075, 0.011362315566, 0, 0.016391438875, 0.088013307877, 0.895595253248, 0, 0, 0, 0, 1 ]} + + - ! + name: texture sRGB + isdata: false + categories: [ file-io ] + encoding: sdr-video + from_scene_reference: ! + children: + - ! {gamma: 2.4, offset: 0.055, direction: inverse} + - ! {min_in_value: 0., min_out_value: 0., max_in_value: 1., max_out_value: 1.} +)" }; + + std::istringstream is; + is.str(CONFIG); + OCIO::ConstConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + + OCIO::ConfigRcPtr editableCfg = cfg->createEditableCopy(); + + OCIO::ConfigRcPtr builtinConfig = OCIO::Config::CreateFromFile("ocio://default")->createEditableCopy(); + + // Make all of the supported linear color spaces in the built-in config inactive. + // These spaces are referenced by name in the heuristics, so it should all still work. + // This is different than the inactive color spaces in the user's config, which the + // heuristics generally ignore (unless it's the reference space). (This should not + // affect the tests below, it simply confirms that inactive spaces are working as + // expected.) + builtinConfig->setInactiveColorSpaces( + "ACES2065-1, ACEScg, Linear Rec.709 (sRGB), Linear P3-D65, Linear Rec.2020, CIE-XYZ-D65, sRGB - Display" + ); + + // + // Test IdentifyBuiltinColorSpace. + // + + // The initial editableCfg inactive color spaces are: "scene-linear Rec.709-sRGB, ACES2065-1". + + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.2020"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.2020")); + } + + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear P3-D65"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear P3-D65")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1, texture sRGB"); + { + // Uses "Linear P3-D65" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces("ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("texture sRGB")); + } + + // The color space is present in editableCfg but it's inactive, so not found. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1"), + OCIO::Exception, + "Heuristics were not able to find an equivalent to the requested color space: ACES2065-1." + ); + } + + // Use interchange role rather than heuristics. + + editableCfg->setRole("aces_interchange", "ACES2065-1"); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("texture sRGB")); + } + + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("scene-linear Rec.709-sRGB")); + } + + editableCfg->setInactiveColorSpaces(""); + { + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap0"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("ACES2065-1")); } -} \ No newline at end of file + editableCfg->setRole("aces_interchange", ""); + + // Display-referred spaces won't work via heuristics, need the interchange role. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } + editableCfg->setRole("cie_xyz_d65_interchange", "CIE-XYZ D65"); + { + // Note that it still works even if the built-in color space is inactive (though not for the source). + const std::string inactives{builtinConfig->getInactiveColorSpaces()}; + OCIO_CHECK_EQUAL(StringUtils::Find(inactives, "sRGB - Display"), 88); + const char * csname = OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display"); + OCIO_CHECK_EQUAL(std::string(csname), std::string("sRGB")); + } + editableCfg->setRole("cie_xyz_d65_interchange", ""); + + // Check handling of color space that isn't in the built-in config. + { + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "does not exist"), + OCIO::Exception, + "Built-in config does not contain the requested color space: does not exist." + ); + } + + // + // Test IdentifyInterchangeSpace. + // + + editableCfg->setInactiveColorSpaces("scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "texture sRGB" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "scene-linear Rec.709-sRGB", + builtinConfig, "lin_rec709_srgb"); // Aliases work. + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("texture sRGB, scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "Linear P3-D65" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "lin_p3d65", + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("scene-linear P3-D65, texture sRGB, scene-linear Rec.709-sRGB, ACES2065-1"); + { + // Uses "Linear Rec.2020" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", // A data space, but it works. + builtinConfig, "lin_rec709_srgb"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + editableCfg->setInactiveColorSpaces("scene-linear P3-D65, texture sRGB, scene-linear Rec.709-sRGB"); + { + // Uses "ACES2065-1" to find the reference. + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "Raw", + builtinConfig, "Raw"); + OCIO_CHECK_EQUAL(std::string(srcInterchange), std::string("scene-linear Rec.709-sRGB")); + OCIO_CHECK_EQUAL(std::string(builtinInterchange), std::string("Linear Rec.709 (sRGB)")); + } + + { + const char * srcInterchange = nullptr; + const char * builtinInterchange = nullptr; + OCIO_CHECK_THROW_WHAT( + OCIO::Config::IdentifyInterchangeSpace(&srcInterchange, &builtinInterchange, + editableCfg, "CIE-XYZ D65", + builtinConfig, "CIE-XYZ-D65"), + OCIO::Exception, + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ); + } +} diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index eb42a7201b..76e9af7300 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -5899,7 +5899,7 @@ OCIO_ADD_TEST(Config, inactive_color_space_read_write) } } -OCIO_ADD_TEST(Config, two_configs) +OCIO_ADD_TEST(Config, get_processor_from_two_configs) { constexpr const char * SIMPLE_CONFIG1{ R"( ocio_profile_version: 2 @@ -5912,6 +5912,11 @@ ocio_profile_version: 2 aces_interchange: aces1 cie_xyz_d65_interchange: display1 +view_transforms: + - ! + name: vt1 + from_scene_reference: ! {min_in_value: 0., min_out_value: 0.} + colorspaces: - ! name: raw1 @@ -5927,6 +5932,10 @@ ocio_profile_version: 2 allocation: uniform from_scene_reference: ! {value: [1.101, 1.202, 1.303, 1.404]} + - ! + name: data_space + isdata: true + display_colorspaces: - ! name: display1 @@ -5987,8 +5996,8 @@ ocio_profile_version: 2 is.str(SIMPLE_CONFIG2); OCIO_CHECK_NO_THROW(config2 = OCIO::Config::CreateFromStream(is)); + // Just specify color spaces and have OCIO use the interchange roles. OCIO::ConstProcessorRcPtr p; - // NB: Although they have the same name, they are in different configs and are different ColorSpaces. OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "test1", config2, "test2")); OCIO_REQUIRE_ASSERT(p); auto group = p->createGroupTransform(); @@ -6048,7 +6057,35 @@ ocio_profile_version: 2 auto l3 = OCIO_DYNAMIC_POINTER_CAST(t3); OCIO_CHECK_ASSERT(l3); - OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display2", config2, "test2"), + // If one of the spaces is a data space, the whole result must be a no-op. + OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "data_space", config2, "test2")); + OCIO_REQUIRE_ASSERT(p); + group = p->createGroupTransform(); + OCIO_REQUIRE_EQUAL(group->getNumTransforms(), 0); + + // Mixed Scene- and Display-referred interchange spaces. + OCIO_CHECK_NO_THROW(p = OCIO::Config::GetProcessorFromConfigs(config1, "display2", config2, "test2")); + OCIO_REQUIRE_ASSERT(p); + group = p->createGroupTransform(); + OCIO_REQUIRE_EQUAL(group->getNumTransforms(), 5); + t0 = group->getTransform(0); + f0 = OCIO_DYNAMIC_POINTER_CAST(t0); + OCIO_CHECK_ASSERT(f0); + t1 = group->getTransform(1); + auto r1 = OCIO_DYNAMIC_POINTER_CAST(t1); + OCIO_CHECK_ASSERT(c1); + t2 = group->getTransform(2); + e2 = OCIO_DYNAMIC_POINTER_CAST(t2); + OCIO_CHECK_ASSERT(e2); + t3 = group->getTransform(3); + auto r3 = OCIO_DYNAMIC_POINTER_CAST(t3); + OCIO_CHECK_ASSERT(r3); + auto t4 = group->getTransform(4); + auto m4 = OCIO_DYNAMIC_POINTER_CAST(t4); + OCIO_CHECK_ASSERT(m4); + + // Second config has no view transform but is asked to connect a display color space to aces_interchange. + OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "test1", config2, "display3"), OCIO::Exception, "There is no view transform between the main scene-referred space " "and the display-referred space"); @@ -6071,6 +6108,12 @@ ocio_profile_version: 2 name: test allocation: uniform from_scene_reference: ! {offset: [0.11, 0.12, 0.13, 0]} + +display_colorspaces: + - ! + name: display5 + allocation: uniform + from_display_reference: ! {value: 2.4} )" }; is.clear(); @@ -6080,11 +6123,15 @@ ocio_profile_version: 2 OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "test1", config3, "test"), OCIO::Exception, - "The role 'aces_interchange' is missing in the destination config"); + "The required role 'aces_interchange' is missing from the source and/or destination config."); OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display1", config3, "test"), OCIO::Exception, - "The role 'cie_xyz_d65_interchange' is missing in the destination config"); + "The required role 'aces_interchange' is missing from the source and/or destination config."); + + OCIO_CHECK_THROW_WHAT(OCIO::Config::GetProcessorFromConfigs(config1, "display1", config3, "display5"), + OCIO::Exception, + "The required role 'cie_xyz_d65_interchange' is missing from the source and/or destination config."); } From f314a48b8e71ba4c7a8d565f54784ff6cf356c70 Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:42:15 -0400 Subject: [PATCH 17/22] Remove unused method Signed-off-by: Doug Walker --- include/OpenColorIO/OpenColorIO.h | 15 --------------- src/OpenColorIO/Processor.cpp | 31 ------------------------------- 2 files changed, 46 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 9b5589b25b..dec178c5bf 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -2523,21 +2523,6 @@ class OCIOEXPORT Processor ConstCPUProcessorRcPtr getOptimizedCPUProcessor(BitDepth inBitDepth, BitDepth outBitDepth, OptimizationFlags oFlags) const; - /** - * \brief Returns whether the two processors are equivalent. - * - * \param p1 First processor to compare. - * \param p2 Second processor to compare. - * \param rgbaValues RGBA values to use to test the processors. - * \param numValues Number of RGBA values. - * \param tolerance Tolerance of the comparaison. - * \return True or false depending on whether the two processors are equivalent. - */ -// static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, -// ConstProcessorRcPtr & p2, -// float * rgbaValues, -// size_t numValues, -// float tolerance); Processor(const Processor &) = delete; Processor & operator= (const Processor &) = delete; diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index 9998bb319a..dab0287dfb 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -15,7 +15,6 @@ #include "OpBuilders.h" #include "ops/noop/NoOps.h" #include "Processor.h" -#include "MathUtils.h" #include "TransformBuilder.h" #include "utils/StringUtils.h" @@ -649,34 +648,4 @@ void Processor::Impl::computeMetadata() } } -bool Processor::Impl::AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float absTolerance) -{ - ProcessorRcPtr proc = Processor::Create(); - - proc->getImpl()->concatenate(p1, p2); - - // RGBA values. - std::vector out(numValues*4); - - PackedImageDesc desc(rgbaValues, (long) numValues, 1, CHANNEL_ORDERING_RGBA); - PackedImageDesc descDst(&out[0], (long) numValues, 1, CHANNEL_ORDERING_RGBA); - - ConstCPUProcessorRcPtr cpu = proc->getOptimizedCPUProcessor(OPTIMIZATION_LOSSLESS); - cpu->apply(desc, descDst); - - for (size_t i = 0; i < out.size(); i++) - { - if (!EqualWithAbsError(rgbaValues[i], out[i], absTolerance)) - { - return false; - } - } - - return true; -} - } // namespace OCIO_NAMESPACE From 685522cca18a407348a46d77e23130169ec48d81 Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Sat, 8 Apr 2023 00:45:23 -0400 Subject: [PATCH 18/22] Remove unused method, tk 2 Signed-off-by: Doug Walker --- src/OpenColorIO/ConfigUtils.h | 2 -- src/OpenColorIO/Processor.h | 7 ------- 2 files changed, 9 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.h b/src/OpenColorIO/ConfigUtils.h index 3963f88d38..3d7d00701b 100644 --- a/src/OpenColorIO/ConfigUtils.h +++ b/src/OpenColorIO/ConfigUtils.h @@ -6,8 +6,6 @@ #include -#include - namespace OCIO_NAMESPACE { diff --git a/src/OpenColorIO/Processor.h b/src/OpenColorIO/Processor.h index cad0e6c577..2bbddd9f2a 100644 --- a/src/OpenColorIO/Processor.h +++ b/src/OpenColorIO/Processor.h @@ -105,13 +105,6 @@ class Processor::Impl void computeMetadata(); - // Returns whether the specified processors are equivalents. - static bool AreProcessorsEquivalent(ConstProcessorRcPtr & p1, - ConstProcessorRcPtr & p2, - float * rgbaValues, - size_t numValues, - float absTolerance); - protected: ConstGPUProcessorRcPtr getGPUProcessor(const OpRcPtrVec & gpuOps, OptimizationFlags oFlags) const; From 35561f485a2c61b223886e26f43284c69c23d20c Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Thu, 13 Apr 2023 20:16:14 -0400 Subject: [PATCH 19/22] Tweak comments Signed-off-by: Doug Walker --- src/OpenColorIO/ConfigUtils.cpp | 62 ++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/OpenColorIO/ConfigUtils.cpp b/src/OpenColorIO/ConfigUtils.cpp index 7a7d1f26ee..959e0eb6c5 100644 --- a/src/OpenColorIO/ConfigUtils.cpp +++ b/src/OpenColorIO/ConfigUtils.cpp @@ -40,6 +40,9 @@ const char * getBuiltinLinearSpaceName(int index) return builtinLinearSpaces[Clamp(index, 0, 4)]; } +// The number of items available from getBuiltinLinearSpaceName. (Obviously, update this +// integer if that list changes.) +// inline int getNumberOfbuiltinLinearSpaces() { return 5; @@ -59,14 +62,15 @@ inline int getNumberOfbuiltinLinearSpaces() // // This function does NOT use any heuristics. // -// \param[out] srcInterchangeCSName -- Name of the interchange color space from the src config. -// \param[out] dstInterchangeCSName -- Name of the interchange color space from the dst config. -// \param srcConfig -- Source config object. -// \param srcName -- Name of the color space to be converted from the source config. -// May be empty if the source color space is unknown. -// \param dstConfig -- Destination config object. -// \param dstName -- Name of the color space to be converted from the destination config. -// \return True if the necessary interchange roles were found. +// [out] srcInterchangeCSName -- Name of the interchange color space from the src config. +// [out] dstInterchangeCSName -- Name of the interchange color space from the dst config. +// [out] interchangeType -- The ReferenceSpaceType of the interchange color space. +// srcConfig -- Source config object. +// srcName -- Name of the color space to be converted from the source config. +// May be empty if the source color space is unknown. +// dstConfig -- Destination config object. +// dstName -- Name of the color space to be converted from the destination config. +// Returns True if the necessary interchange roles were found. // bool GetInterchangeRolesForColorSpaceConversion(const char ** srcInterchangeCSName, const char ** dstInterchangeCSName, @@ -273,11 +277,11 @@ bool hasNoTransform(const ConstColorSpaceRcPtr & cs) // If a match is found, it indicates what reference space is used by the config. // Return the index into the list of built-in linear spaces, or -1 if not found. // -// \param srcConfig -- Source config object. -// \param srcRefName -- Name of a scene-referred reference color space in the src config. -// \param cs -- Color space from the source config to test. -// \param builtinConfig -- The built-in config object. -// \return The index into the list of built-in linear spaces. +// srcConfig -- Source config object. +// srcRefName -- Name of a scene-referred reference color space in the src config. +// cs -- Color space from the source config to test. +// builtinConfig -- The built-in config object. +// Returns the index into the list of built-in linear spaces. // int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, const char * srcRefName, @@ -341,11 +345,11 @@ int getReferenceSpaceFromLinearSpace(const ConstConfigRcPtr & srcConfig, // is used by the config. Return the index into the list of built-in linear spaces, // or -1 if not found. // -// \param srcConfig -- Source config object. -// \param srcRefName -- Name of a scene-referred reference color space in the src config. -// \param cs -- Color space from the source config to test. -// \param builtinConfig -- The built-in config object. -// \return The index into the list of built-in linear spaces. +// srcConfig -- Source config object. +// srcRefName -- Name of a scene-referred reference color space in the src config. +// cs -- Color space from the source config to test. +// builtinConfig -- The built-in config object. +// Returns the index into the list of built-in linear spaces. // int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, const char * srcRefName, @@ -459,14 +463,14 @@ int getReferenceSpaceFromSRGBSpace(const ConstConfigRcPtr & srcConfig, // that should be used to convert from the src color space to the built-in color space, // or vice-versa. Throws if no suitable spaces are found. // -// \param[out] srcInterchange -- Name of the interchange color space from the source config. -// \param[out] builtinInterchange -- Name of the interchange color space from the built-in config. -// \param srcConfig -- Source config object. -// \param srcColorSpaceName -- Name of the color space to be converted from the source config. -// \param builtinConfig -- Built-in config object. -// \param builtinColorSpaceName -- Name of the color space to be converted from the built-in config. +// [out] srcInterchange -- Name of the interchange color space from the source config. +// [out] builtinInterchange -- Name of the interchange color space from the built-in config. +// srcConfig -- Source config object. +// srcColorSpaceName -- Name of the color space to be converted from the source config. +// builtinConfig -- Built-in config object. +// builtinColorSpaceName -- Name of the color space to be converted from the built-in config. // -// \throw Exception if an interchange space cannot be found. +// Throws an exception if an interchange space cannot be found. // void IdentifyInterchangeSpace(const char ** srcInterchange, const char ** builtinInterchange, @@ -574,10 +578,10 @@ void IdentifyInterchangeSpace(const char ** srcInterchange, // specified color space from the provided built-in config. Only active color spaces // are searched. // -// \param srcConfig -- The source config object to search. -// \param builtinConfig -- The built-in config object containing the desired color space. -// \param builtinColorSpaceName -- Name of the desired color space from the built-in config. -// \return The name of the color space in the source config. +// srcConfig -- The source config object to search. +// builtinConfig -- The built-in config object containing the desired color space. +// builtinColorSpaceName -- Name of the desired color space from the built-in config. +// Returns the name of the color space in the source config. // // \throw Exception if an interchange space cannot be found or the equivalent space cannot be found. // From 8b0a716c1b0f81d9137f0817bc7da59fef33a06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 5 May 2023 09:52:28 -0400 Subject: [PATCH 20/22] Python binding for IdentifyInterchangeSpace and IdentifyBuiltinColorSpace methods. Adding a couple python unit tests to cover the new methods (same unit test as the C++ side, but not all of them) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- src/bindings/python/PyConfig.cpp | 30 ++++++ tests/python/ColorSpaceTest.py | 166 +++++++++++++++++++++++++++++-- 2 files changed, 185 insertions(+), 11 deletions(-) diff --git a/src/bindings/python/PyConfig.cpp b/src/bindings/python/PyConfig.cpp index 43185622a3..d7e188bf5a 100644 --- a/src/bindings/python/PyConfig.cpp +++ b/src/bindings/python/PyConfig.cpp @@ -336,6 +336,36 @@ void bindPyConfig(py::module & m) DOC(Config, getInactiveColorSpaces)) .def("isInactiveColorSpace", &Config::isInactiveColorSpace, "colorspace"_a, DOC(Config, isInactiveColorSpace)) + .def_static("IdentifyBuiltinColorSpace", [](const ConstConfigRcPtr & srcConfig, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) + { + return Config::IdentifyBuiltinColorSpace(srcConfig, + builtinConfig, + builtinColorSpaceName); + }, + "srcConfig"_a, "builtinConfig"_a, "builtinColorSpaceName"_a, + DOC(Config, IdentifyBuiltinColorSpace)) + + .def_static("IdentifyInterchangeSpace", [](const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const ConstConfigRcPtr & builtinConfig, + const char * builtinColorSpaceName) + { + const char * srcInterchangePtr = nullptr; + const char * builtinInterchangePtr = nullptr; + + Config::IdentifyInterchangeSpace(&srcInterchangePtr, + &builtinInterchangePtr, + srcConfig, + srcColorSpaceName, + builtinConfig, + builtinColorSpaceName); + // Return the tuple by value which copies the strings values. + return std::make_tuple(std::string(srcInterchangePtr), std::string(builtinInterchangePtr)); + }, + "srcConfig"_a, "srcColorSpaceName"_a, "builtinConfig"_a, "builtinColorSpaceName"_a, + DOC(Config, IdentifyInterchangeSpace)) // Roles .def("setRole", &Config::setRole, "role"_a, "colorSpaceName"_a, diff --git a/tests/python/ColorSpaceTest.py b/tests/python/ColorSpaceTest.py index 465539886c..bfa4d79bbf 100644 --- a/tests/python/ColorSpaceTest.py +++ b/tests/python/ColorSpaceTest.py @@ -7,7 +7,11 @@ import sys import PyOpenColorIO as OCIO -from UnitTestUtils import SIMPLE_CONFIG, TEST_NAMES, TEST_DESCS, TEST_CATEGORIES +from UnitTestUtils import (SIMPLE_CONFIG, + TEST_NAMES, + TEST_DESCS, + TEST_CATEGORIES, + TEST_DATAFILES_DIR) class ColorSpaceTest(unittest.TestCase): @@ -627,7 +631,37 @@ def test_processor_to_known_colorspace(self): default: raw scene_linear: ref_cs +display_colorspaces: + - ! + name: CIE-XYZ-D65 + description: The CIE XYZ (D65) display connection colorspace. + isdata: false + + - ! + name: sRGB - Display CS + description: Convert CIE XYZ (D65 white) to sRGB (piecewise EOTF) + isdata: false + from_display_reference: ! {style: DISPLAY - CIE-XYZ-D65_to_sRGB} + colorspaces: + # Put a couple of test color space first in the config since the heuristics stop upon success. + + - ! + name: File color space + description: Verify that that FileTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: lut1d_green.ctf} + + - ! + name: CS Transform color space + description: Verify that that ColorSpaceTransforms load correctly when running the heuristics. + isdata: false + from_scene_reference: ! + children: + - ! {src: ref_cs, dst: not sRGB} + - ! name: raw description: A data colorspace (should not be used). @@ -635,7 +669,7 @@ def test_processor_to_known_colorspace(self): - ! name: ref_cs - description: The reference colorspace. + description: The reference colorspace, ACES2065-1. isdata: false - ! @@ -660,23 +694,23 @@ def test_processor_to_known_colorspace(self): - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! - name: Texture -- sRGB - description: An sRGB Texture space, spelled differently than in the built-in config. + name: sRGB Encoded AP1 - Texture + description: Another space with "sRGB" in the name that is not actually an sRGB texture space. isdata: false from_scene_reference: ! - name: AP0 to sRGB Rec.709 + name: AP0 to sRGB Encoded AP1 - Texture children: - - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} + - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} - ! - name: sRGB Encoded AP1 - Texture - description: Another space with "sRGB" in the name that is not actually an sRGB texture space. + name: Texture -- sRGB + description: An sRGB Texture space, spelled differently than in the built-in config. isdata: false from_scene_reference: ! - name: AP0 to sRGB Encoded AP1 - Texture + name: AP0 to sRGB Rec.709 children: - - ! {matrix: [1.45143931614567, -0.23651074689374, -0.214928569251925, 0, -0.0765537733960206, 1.17622969983357, -0.0996759264375522, 0, 0.00831614842569772, -0.00603244979102102, 0.997716301365323, 0, 0, 0, 0, 1]} + - ! {matrix: [2.52168618674388, -1.13413098823972, -0.387555198504164, 0, -0.276479914229922, 1.37271908766826, -0.096239173438334, 0, -0.0153780649660342, -0.152975335867399, 1.16835340083343, 0, 0, 0, 0, 1]} - ! {gamma: 2.4, offset: 0.055, direction: inverse} """ @@ -715,6 +749,8 @@ def check_processor_inv(self, p): cfg = OCIO.Config.CreateFromStream(CONFIG) + cfg.setSearchPath(TEST_DATAFILES_DIR) + # Make all color spaces suitable for the heuristics inactive. # The heuristics don't look at inactive color spaces. cfg.setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709, Texture -- sRGB") @@ -754,4 +790,112 @@ def check_processor_inv(self, p): # Test linear color space to_ref direction. cfg.setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB") p = OCIO.Config.GetProcessorFromBuiltinColorSpace(builtin_csname, cfg, src_csname) - check_processor_inv(self, p) \ No newline at end of file + check_processor_inv(self, p) + + + editableCfg = copy.deepcopy(cfg) + builtinConfig = OCIO.Config.CreateFromFile("ocio://default") + + # + # Test IdentifyBuiltinColorSpace. + # + + editableCfg.setInactiveColorSpaces("") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + self.assertEqual(csname, "ACES cg") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture") + self.assertEqual(csname, "Texture -- sRGB") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1") + self.assertEqual(csname, "ref_cs") + + editableCfg.setInactiveColorSpaces("Texture -- sRGB, ref_cs") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)") + self.assertEqual(csname, "Linear ITU-R BT.709") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct") + self.assertEqual(csname, "CS Transform color space") + + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1") + self.assertEqual(csname, "ACES cg") + + # Display-referred spaces are not supported unless the display-referred interchange + # role is present. + + with self.assertRaises(OCIO.Exception) as cm: + cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual( + str(cm.exception), + "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." + ) + + # The next three cases directly use the interchange roles rather than heuristics. + + # With the required role, it then works. + editableCfg.setRole("cie_xyz_d65_interchange", "CIE-XYZ-D65") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual(csname, "sRGB - Display CS") + + # Must continue to work if the color space for the interchange role is inactive. + editableCfg.setInactiveColorSpaces("CIE-XYZ-D65") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + self.assertEqual(csname, "sRGB - Display CS") + + # Test the scene-referred interchange role (and even make it inactive). + editableCfg.setRole("aces_interchange", "ref_cs") + editableCfg.setInactiveColorSpaces("ref_cs") + csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + self.assertEqual(csname, "ACES cg") + + + # + # Test IdentifyInterchangeSpace. + # + + # Uses "ACEScg" to find the reference. + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb") # Aliases work. + self.assertEqual(spaces[0], "ref_cs") + self.assertEqual(spaces[1], "ACES2065-1") + + # Set the interchange role. In order to prove that it is being used rather than + # the heuristics, set it to something wrong and check that it gets returned anyway. + editableCfg.setRole("aces_interchange", "Texture -- sRGB") + + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb") + self.assertEqual(spaces[0], "Texture -- sRGB") + self.assertEqual(spaces[1], "ACES2065-1") + + # Unset the interchange role, so the heuristics will be used for the other tests. + editableCfg.setRole("aces_interchange", "") + + # Check what happens if a totally bogus config is passed for the built-in config. + # (It fails in the first heuristic that tries to use one of the known built-in spaces.) + rawCfg = OCIO.Config.CreateRaw() + + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Raw", rawCfg, "raw") + self.assertEqual( + str(cm.exception), + "Could not find destination color space 'sRGB - Texture'." + ) + + # Check what happens if the source color space doesn't exist. + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "raw") + self.assertEqual( + str(cm.exception), + "Could not find source color space 'Foo'." + ) + + # Check what happens if the destination color space doesn't exist. + with self.assertRaises(OCIO.Exception) as cm: + spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "") + self.assertEqual( + str(cm.exception), + "Could not find destination color space ''." + ) \ No newline at end of file From 53bb93ad51233c7e3708305794f68d2100f4ee70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 5 May 2023 14:02:46 -0400 Subject: [PATCH 21/22] Fixing problem with the rebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- include/OpenColorIO/OpenColorIO.h | 5 ++++- src/OpenColorIO/Config.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index dec178c5bf..738dc0b93c 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -1398,10 +1398,13 @@ class OCIOEXPORT Config const char * dstColorSpaceName, const char * dstInterchangeName); + /// Get the Processor Cache flags. + ProcessorCacheFlags getProcessorCacheFlags() const noexcept; + /// Control the caching of processors in the config instance. By default, caching is on. /// The flags allow turning caching off entirely or only turning it off if dynamic /// properties are being used by the processor. - void setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept; + void setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept; /** * \brief Clears this config's cache of Processor, CPUProcessor, and GPUProcessor instances. diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 5a232c6cd5..f16e5dabae 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -4754,7 +4754,7 @@ ProcessorCacheFlags Config::getProcessorCacheFlags() const noexcept return getImpl()->getProcessorCacheFlags(); } -void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) noexcept +void Config::setProcessorCacheFlags(ProcessorCacheFlags flags) const noexcept { getImpl()->setProcessorCacheFlags(flags); } From df17326724ebd2aac7c5f406593b19f41f55a03b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Tue, 20 Jun 2023 07:59:24 -0400 Subject: [PATCH 22/22] Make sure the unit test is using the static method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- tests/python/ColorSpaceTest.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/python/ColorSpaceTest.py b/tests/python/ColorSpaceTest.py index bfa4d79bbf..7dadee0616 100644 --- a/tests/python/ColorSpaceTest.py +++ b/tests/python/ColorSpaceTest.py @@ -802,31 +802,31 @@ def check_processor_inv(self, p): editableCfg.setInactiveColorSpaces("") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") self.assertEqual(csname, "ACES cg") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Texture") self.assertEqual(csname, "Texture -- sRGB") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACES2065-1") self.assertEqual(csname, "ref_cs") editableCfg.setInactiveColorSpaces("Texture -- sRGB, ref_cs") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "Linear Rec.709 (sRGB)") self.assertEqual(csname, "Linear ITU-R BT.709") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScct") self.assertEqual(csname, "CS Transform color space") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "lin_ap1") self.assertEqual(csname, "ACES cg") # Display-referred spaces are not supported unless the display-referred interchange # role is present. with self.assertRaises(OCIO.Exception) as cm: - cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") self.assertEqual( str(cm.exception), "The heuristics currently only support scene-referred color spaces. Please set the interchange roles." @@ -836,18 +836,18 @@ def check_processor_inv(self, p): # With the required role, it then works. editableCfg.setRole("cie_xyz_d65_interchange", "CIE-XYZ-D65") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") self.assertEqual(csname, "sRGB - Display CS") # Must continue to work if the color space for the interchange role is inactive. editableCfg.setInactiveColorSpaces("CIE-XYZ-D65") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "sRGB - Display") self.assertEqual(csname, "sRGB - Display CS") # Test the scene-referred interchange role (and even make it inactive). editableCfg.setRole("aces_interchange", "ref_cs") editableCfg.setInactiveColorSpaces("ref_cs") - csname = cfg.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") + csname = OCIO.Config.IdentifyBuiltinColorSpace(editableCfg, builtinConfig, "ACEScg") self.assertEqual(csname, "ACES cg") @@ -856,8 +856,8 @@ def check_processor_inv(self, p): # # Uses "ACEScg" to find the reference. - spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", - builtinConfig, "lin_rec709_srgb") # Aliases work. + spaces = OCIO.Config.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + builtinConfig, "lin_rec709_srgb") # Aliases work. self.assertEqual(spaces[0], "ref_cs") self.assertEqual(spaces[1], "ACES2065-1") @@ -865,7 +865,7 @@ def check_processor_inv(self, p): # the heuristics, set it to something wrong and check that it gets returned anyway. editableCfg.setRole("aces_interchange", "Texture -- sRGB") - spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", + spaces = OCIO.Config.IdentifyInterchangeSpace(editableCfg, "Linear ITU-R BT.709", builtinConfig, "lin_rec709_srgb") self.assertEqual(spaces[0], "Texture -- sRGB") self.assertEqual(spaces[1], "ACES2065-1") @@ -878,7 +878,7 @@ def check_processor_inv(self, p): rawCfg = OCIO.Config.CreateRaw() with self.assertRaises(OCIO.Exception) as cm: - spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Raw", rawCfg, "raw") + spaces = OCIO.Config.IdentifyInterchangeSpace(editableCfg, "Raw", rawCfg, "raw") self.assertEqual( str(cm.exception), "Could not find destination color space 'sRGB - Texture'." @@ -886,7 +886,7 @@ def check_processor_inv(self, p): # Check what happens if the source color space doesn't exist. with self.assertRaises(OCIO.Exception) as cm: - spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "raw") + spaces = OCIO.Config.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "raw") self.assertEqual( str(cm.exception), "Could not find source color space 'Foo'." @@ -894,7 +894,7 @@ def check_processor_inv(self, p): # Check what happens if the destination color space doesn't exist. with self.assertRaises(OCIO.Exception) as cm: - spaces = cfg.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "") + spaces = OCIO.Config.IdentifyInterchangeSpace(editableCfg, "Foo", rawCfg, "") self.assertEqual( str(cm.exception), "Could not find destination color space ''."