diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 96caa09ebe..f7e33de930 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -1210,6 +1210,46 @@ class OCIOEXPORT Config const ConstTransformRcPtr & transform, TransformDirection direction) const; + /** + * \brief Get a Processor to or from a known external color space. + * + * These methods provide a way to interface color spaces in a config with known standard + * external color spaces. The set of external color space are those contained in the current + * default Built-in config. This includes common spaces such as "Linear Rec.709 (sRGB)", + * "sRGB - Texture", "ACEScg", and "ACES2065-1". + * + * 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 + * + * 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 + * to proceed. If the heuristics fail to find a suitable space, an exception is thrown. + * The heuristics may evolve, so the results returned by this function for a given source config + * and color space may change in future releases of the library. However, the Interchange Roles + * are required in config versions 2.2 and higher, so it is hoped that the need for the heuristics + * will decrease over time. + * + * \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. + */ + static ConstProcessorRcPtr GetProcessorToBuiltinColorSpace( + ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName); + /** + * \brief See description of GetProcessorToBuiltinColorSpace. + * + * \param builtinColorSpaceName The name of the color space in the current default Built-in 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); + /** * \brief Get a processor to convert between color spaces in two separate * configs. diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 8342402ee0..8ad390a3db 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -1094,6 +1094,414 @@ class Config::Impl // That should never happen. 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, + const char * srcColorSpaceName, + const char * builtinColorSpaceName, + TransformDirection direction) const + { + // 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; + 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. + } + + // 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()); + } + + // 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; + } + } + + 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()) + { + // 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(), + builtinConfig, + builtinColorSpaceName, + builtinInterchange.c_str()); + } + else if (direction == TRANSFORM_DIR_INVERSE) + { + proc = Config::GetProcessorFromConfigs(builtinConfig, + builtinColorSpaceName, + builtinInterchange.c_str(), + srcConfig, + srcColorSpaceName, + srcInterchange.c_str()); + } + 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()); + } }; @@ -4500,6 +4908,26 @@ ConstProcessorRcPtr Config::GetProcessorFromConfigs(const ConstContextRcPtr & sr return processor; } +ConstProcessorRcPtr Config::GetProcessorToBuiltinColorSpace(ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName) +{ + return srcConfig->getImpl()->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); +} + std::ostream& operator<< (std::ostream& os, const Config& config) { config.serialize(os); diff --git a/src/bindings/python/PyConfig.cpp b/src/bindings/python/PyConfig.cpp index 3cae83b602..6d3cbadffe 100644 --- a/src/bindings/python/PyConfig.cpp +++ b/src/bindings/python/PyConfig.cpp @@ -668,7 +668,27 @@ void bindPyConfig(py::module & m) &Config::getProcessor, "context"_a, "transform"_a, "direction"_a, DOC(Config, getProcessor, 9)) - + + .def_static("GetProcessorToBuiltinColorSpace", [](const ConstConfigRcPtr & srcConfig, + const char * srcColorSpaceName, + const char * builtinColorSpaceName) + { + return Config::GetProcessorToBuiltinColorSpace(srcConfig, + srcColorSpaceName, + builtinColorSpaceName); + }, + "srcConfig"_a, "srcColorSpaceName"_a, "builtinColorSpaceName"_a, + DOC(Config, GetProcessorToBuiltinColorSpace)) + .def_static("GetProcessorFromBuiltinColorSpace", [](const char * builtinColorSpaceName, + ConstConfigRcPtr srcConfig, + const char * srcColorSpaceName) + { + return Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName, + srcConfig, + srcColorSpaceName); + }, + "builtinColorSpaceName"_a, "srcConfig"_a, "srcColorSpaceName"_a, + DOC(Config, GetProcessorFromBuiltinColorSpace)) .def_static("GetProcessorFromConfigs", [](const ConstConfigRcPtr & srcConfig, const char * srcColorSpaceName, const ConstConfigRcPtr & dstConfig, diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index 2e46fc58c8..0b1be643ab 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -934,3 +934,245 @@ inactive_colorspaces: [display_linear-trans, scene_linear-trans] testDisplayReferred("scene_ref", false, __LINE__); } } + +OCIO_ADD_TEST(Processor, processor_to_known_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} + +)" }; + + 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; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(is)); + + OCIO::ConfigRcPtr editableCfg = cfg->createEditableCopy(); + + // 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"); + + std::string srcColorSpaceName = "not sRGB"; + std::string builtinColorSpaceName = "Utility - Gamma 2.2 - AP1 - Texture"; + + { + // Test throw if no suitable spaces are present. + + OCIO_CHECK_THROW(auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace( + editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()), + + OCIO::Exception + ); + } + + { + // Test sRGB Texture space. + + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()); + checkProcessor(proc); + } + + { + // Test linear color space from_ref direction. + + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()); + checkProcessor(proc); + } + + { + // Test linear color space to_ref direction. + + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + auto proc = OCIO::Config::GetProcessorToBuiltinColorSpace(editableCfg, + srcColorSpaceName.c_str(), + builtinColorSpaceName.c_str()); + checkProcessor(proc); + } + + { + // Test linear color space to_ref direction. + + editableCfg->setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709"); + auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), + editableCfg, + srcColorSpaceName.c_str()); + checkProcessorInverse(proc); + } + + { + // Test linear color space from_ref direction. + + editableCfg->setInactiveColorSpaces("ACES cg, Texture -- sRGB"); + auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), + editableCfg, + srcColorSpaceName.c_str()); + checkProcessorInverse(proc); + } + + { + // Test linear color space to_ref direction. + + editableCfg->setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB"); + auto proc = OCIO::Config::GetProcessorFromBuiltinColorSpace(builtinColorSpaceName.c_str(), + editableCfg, + srcColorSpaceName.c_str()); + checkProcessorInverse(proc); + } +} \ No newline at end of file diff --git a/tests/python/ColorSpaceTest.py b/tests/python/ColorSpaceTest.py index 57fafa544b..166ecaf3fc 100644 --- a/tests/python/ColorSpaceTest.py +++ b/tests/python/ColorSpaceTest.py @@ -612,3 +612,140 @@ def test_display_referred(self, cfg, cs_name, expected_value): test_display_referred(self, cfg, "scene_nonlin-trans", False) test_display_referred(self, cfg, "scene_linear-trans-alias", False) test_display_referred(self, cfg, "scene_ref", False) + + def test_processor_to_known_colorspace(self): + + CONFIG = """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} + +""" + def check_processor(self, p): + gt = p.createGroupTransform() + self.assertEqual(len(gt), 4) + + self.assertAlmostEqual(gt[0].getLogSideSlopeValue()[0], 0.0570776, places=6) + self.assertEqual(gt[0].getDirection(), OCIO.TRANSFORM_DIR_INVERSE) + + self.assertAlmostEqual(gt[1].getMatrix()[0], 0.6954522413574519, places=6) + self.assertEqual(gt[1].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + + self.assertAlmostEqual(gt[2].getMatrix()[0], 1.45143931607166, places=6) + self.assertEqual(gt[2].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + + self.assertAlmostEqual(gt[3].getValue()[0], 2.2, places=6) + self.assertEqual(gt[3].getDirection(), OCIO.TRANSFORM_DIR_INVERSE) + + def check_processor_inv(self, p): + gt = p.createGroupTransform() + self.assertEqual(len(gt), 4) + + self.assertAlmostEqual(gt[0].getValue()[0], 2.2, places=6) + self.assertEqual(gt[0].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + + self.assertAlmostEqual(gt[1].getMatrix()[0], 0.6954522413574519, places=6) + self.assertEqual(gt[1].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + + self.assertAlmostEqual(gt[2].getMatrix()[0], 1.45143931607166, places=6) + self.assertEqual(gt[2].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + + self.assertAlmostEqual(gt[3].getLogSideSlopeValue()[0], 0.0570776, places=6) + self.assertEqual(gt[3].getDirection(), OCIO.TRANSFORM_DIR_FORWARD) + cfg = OCIO.Config.CreateFromStream(CONFIG) + + cfg = OCIO.Config.CreateFromStream(CONFIG) + + # 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") + + src_csname = "not sRGB" + builtin_csname = "Utility - Gamma 2.2 - AP1 - Texture" + + # Test throw if no suitable spaces are present. + with self.assertRaises(OCIO.Exception): + p = OCIO.Config.GetProcessorToBuiltinColorSpace(cfg, src_csname, builtin_csname) + + # Test sRGB Texture space. + cfg.setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709") + p = OCIO.Config.GetProcessorToBuiltinColorSpace(cfg, src_csname, builtin_csname) + check_processor(self, p) + + # Test linear color space from_ref direction. + cfg.setInactiveColorSpaces("ACES cg, Texture -- sRGB") + p = OCIO.Config.GetProcessorToBuiltinColorSpace(cfg, src_csname, builtin_csname) + check_processor(self, p) + + # Test linear color space to_ref direction. + cfg.setInactiveColorSpaces("Linear ITU-R BT.709, Texture -- sRGB") + p = OCIO.Config.GetProcessorToBuiltinColorSpace(cfg, src_csname, builtin_csname) + check_processor(self, p) + + # Test sRGB Texture space. + cfg.setInactiveColorSpaces("ACES cg, Linear ITU-R BT.709") + p = OCIO.Config.GetProcessorFromBuiltinColorSpace(builtin_csname, cfg, src_csname) + check_processor_inv(self, p) + + # Test linear color space from_ref direction. + cfg.setInactiveColorSpaces("ACES cg, Texture -- sRGB") + p = OCIO.Config.GetProcessorFromBuiltinColorSpace(builtin_csname, cfg, src_csname) + 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