Skip to content

Commit

Permalink
Adsk contrib - Add a method to query whether a color space is linear (#…
Browse files Browse the repository at this point in the history
…1703)

* First iteration of isColorspaceLinear.

Signed-off-by: Cedrik Fuoco <[email protected]>

* - Comments.
- Added a private getProcessor method that ignore caching.
- Fix the algorithm for isColorspaceLinear. - Now testing R, G, B and neutral values.

Signed-off-by: Cedrik Fuoco <[email protected]>

* - Passing in the config reference instead of using this in the capture clause of the lambda function as this was causing issue on other platforms than Windows.
- Remove unused variable in unit test.
- other minors changes

Signed-off-by: Cedrik Fuoco <[email protected]>

* Removing a comment and removing the helper function as it is not needed.

Signed-off-by: Cedrik Fuoco <[email protected]>

* - Tweaking algorithm. Returns true at the end since the 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.
- Added a color space in the test config to cover that.

Signed-off-by: Cedrik Fuoco <[email protected]>

* Adding python binding for isColorSpaceLinear and python unit test.

Signed-off-by: Cedrik Fuoco <[email protected]>

Signed-off-by: Cedrik Fuoco <[email protected]>
Co-authored-by: Doug Walker <[email protected]>
  • Loading branch information
cedrik-fuoco-adsk and doug-walker authored Oct 27, 2022
1 parent 5dc32f9 commit 3ffcc3f
Show file tree
Hide file tree
Showing 5 changed files with 584 additions and 0 deletions.
37 changes: 37 additions & 0 deletions include/OpenColorIO/OpenColorIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,43 @@ class OCIOEXPORT Config
*/
void setInactiveColorSpaces(const char * inactiveColorSpaces);
const char * getInactiveColorSpaces() const;

/**
* \brief Return true if the specified color space is linear.
*
* The determination of linearity is made with respect to one of the two reference spaces
* (i.e., either the scene-referred one or the display-referred one). If the reference space
* type of the color space is the opposite of the requested reference space type, false is
* returned immediately rather than trying to invoke the default view transform to convert
* between the reference spaces.
*
* Note: This function relies on heuristics that may sometimes give an incorrect result.
* For example, if the encoding attribute is not set appropriately or the sampled values fail
* to detect non-linearity.
*
* The algorithm proceeds as follows:
* -- If the color space isdata attribute is true, return false.
* -- If the reference space type of the color space differs from the requested reference
* space type, return false.
* -- If the color space's encoding attribute is present, return true if it matches the
* expected reference space type (i.e., "scene-linear" for REFERENCE_SPACE_SCENE or
* "display-linear" for REFERENCE_SPACE_DISPLAY) and false otherwise.
* -- If the color space has no to_reference or from_reference transform, return true.
* -- Evaluate several points through the color space's transform and check if the output only
* differs by a scale factor (which may be different per channel, e.g. allowing an arbitrary
* matrix transform, with no offset).
*
* Note that the encoding test happens before the sampled value test to give config authors
* ultimate control over the linearity determination. For example, they could set the encoding
* attribute to indicate linearity if they want to ignore some areas of non-linearity
* (e.g., at extreme values). Or they could set it to indicate that a color space should not
* be considered linear, even if it is, in a mathematical sense.
*
* \param colorSpace Color space to evaluate.
* \param referenceSpaceType Evaluate linearity with respect to the specified reference space
* (either scene-referred or display-referred).
*/
bool isColorSpaceLinear(const char * colorSpace, ReferenceSpaceType referenceSpaceType) const;

//
// Roles
Expand Down
122 changes: 122 additions & 0 deletions src/OpenColorIO/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,22 @@ class Config::Impl
m_cacheFlags = flags;
m_processorCache.enable((m_cacheFlags & PROCESSOR_CACHE_ENABLED) == PROCESSOR_CACHE_ENABLED);
}

ConstProcessorRcPtr getProcessorWithoutCaching(
const Config & config,
const ConstTransformRcPtr & transform,
TransformDirection direction) const
{
if (!transform)
{
throw Exception("Config::GetProcessor failed. Transform is null.");
}

ProcessorRcPtr processor = Processor::Create();
processor->getImpl()->setProcessorCacheFlags(PROCESSOR_CACHE_OFF);
processor->getImpl()->setTransform(config, m_context, transform, direction);
return processor;
}

int instantiateDisplay(const std::string & monitorName,
const std::string & monitorDescription,
Expand Down Expand Up @@ -2699,6 +2715,112 @@ void Config::clearColorSpaces()
getImpl()->refreshActiveColorSpaces();
}

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<float> 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<float> 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;
}

///////////////////////////////////////////////////////////////////////////

Expand Down
2 changes: 2 additions & 0 deletions src/bindings/python/PyConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ void bindPyConfig(py::module & m)
DOC(Config, addColorSpace))
.def("removeColorSpace", &Config::removeColorSpace, "name"_a,
DOC(Config, removeColorSpace))
.def("isColorSpaceLinear", &Config::isColorSpaceLinear, "colorSpace"_a, "referenceSpaceType"_a,
DOC(Config, isColorSpaceLinear))
.def("isColorSpaceUsed", &Config::isColorSpaceUsed, "name"_a,
DOC(Config, isColorSpaceUsed))
.def("clearColorSpaces", &Config::clearColorSpaces,
Expand Down
Loading

0 comments on commit 3ffcc3f

Please sign in to comment.