Skip to content

Commit 62a75d8

Browse files
committed
Improve validation of control points
Signed-off-by: Doug Walker <[email protected]>
1 parent 5403b55 commit 62a75d8

13 files changed

+248
-73
lines changed

include/OpenColorIO/OpenColorTransforms.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,14 +1309,18 @@ extern OCIOEXPORT std::ostream & operator<<(std::ostream &, const GradingPrimary
13091309
* The domain of the curves is [0,1] and control points outside that domain are mapped into it.
13101310
* A hue of 0 or 1 corresponds to a magenta hue.
13111311
*
1312+
* The control points are dynamic, so they may be adjusted even after the Transform is included
1313+
* in a Processor. However, creating a curve or setting the parameters will call the
1314+
* GradingBSplineCurveImpl::validate function, which will throw an exception if the control
1315+
* points do not meet certain requirements, for example that the X-coordinates are non-decreasing
1316+
* Please review that function for details on the validation. Applications that provide a UI to
1317+
* edit curves must ensure that they prevent users from creating control points that are not valid.
1318+
*
13121319
* The transform is invertible as long as the curves allow it. For example, if saturation is
13131320
* mapped to zero, obviously that cannot be resaturated. Care should be taken with the Hue-FX
13141321
* curve because it is possible to fold hues over on themselves, which also cannot be inverted.
13151322
* In most cases the Hue-FX curve is not necessary since the Hue-Hue curve provides similar
13161323
* functionality with the added benefit of being strictly invertible.
1317-
*
1318-
* The control points are dynamic, so they may be adjusted even after the Transform is included
1319-
* in a Processor.
13201324
*/
13211325
class OCIOEXPORT GradingHueCurveTransform : public Transform
13221326
{

src/OpenColorIO/DynamicProperty.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ DynamicPropertyDoubleImplRcPtr DynamicPropertyDoubleImpl::createEditableCopy() c
137137
return std::make_shared<DynamicPropertyDoubleImpl>(getType(), getValue(), isDynamic());
138138
}
139139

140+
//========================================================================================
141+
140142
DynamicPropertyGradingPrimaryImpl::DynamicPropertyGradingPrimaryImpl(GradingStyle style,
141143
TransformDirection dir,
142144
const GradingPrimary & value,
@@ -195,6 +197,8 @@ void DynamicPropertyGradingPrimaryImpl::setDirection(TransformDirection dir) noe
195197
}
196198
}
197199

200+
//========================================================================================
201+
198202
DynamicPropertyGradingRGBCurveImpl::DynamicPropertyGradingRGBCurveImpl(
199203
const ConstGradingRGBCurveRcPtr & value, bool dynamic)
200204
: DynamicPropertyImpl(DYNAMIC_PROPERTY_GRADING_RGBCURVE, dynamic)
@@ -288,6 +292,7 @@ DynamicPropertyGradingRGBCurveImplRcPtr DynamicPropertyGradingRGBCurveImpl::crea
288292
return res;
289293
}
290294

295+
//========================================================================================
291296

292297
DynamicPropertyGradingHueCurveImpl::DynamicPropertyGradingHueCurveImpl(
293298
const ConstGradingHueCurveRcPtr & value, bool dynamic)
@@ -383,6 +388,8 @@ DynamicPropertyGradingHueCurveImplRcPtr DynamicPropertyGradingHueCurveImpl::crea
383388
return res;
384389
}
385390

391+
//========================================================================================
392+
386393
DynamicPropertyGradingToneImpl::DynamicPropertyGradingToneImpl(const GradingTone & value,
387394
GradingStyle style,
388395
bool dynamic)

src/OpenColorIO/ops/gradingrgbcurve/GradingBSplineCurve.cpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ void PrepHueCurveData(const std::vector<GradingControlPoint>& ctrlPnts,
7373
}
7474
if (!isHorizontal)
7575
{
76+
// Ensure that there is a minimum space between the y values.
7677
const float y_span = outCtrlPnts[numCtrlPnts - 1].m_y - outCtrlPnts[0].m_y;
7778
for (unsigned i = 1; i < outCtrlPnts.size(); ++i)
7879
{
@@ -665,29 +666,72 @@ void GradingBSplineCurveImpl::validate() const
665666
throw Exception("The slopes array must be the same length as the control points.");
666667
}
667668

668-
// Make sure the points are non-decreasing.
669+
// Make sure the x-coordinates are non-decreasing.
669670
float lastX = -std::numeric_limits<float>::max();
670671
for (size_t i = 0; i < numPoints; ++i)
671672
{
672-
// Test x values only.
673673
const float x = m_controlPoints[i].m_x;
674674
if (x < lastX)
675675
{
676676
std::ostringstream oss;
677677
oss << "Control point at index " << i << " has a x coordinate '" << x << "' that is ";
678-
oss << "less than previous control point x cooordinate '" << lastX << "'.";
678+
oss << "less than previous control point x coordinate '" << lastX << "'.";
679679
throw Exception(oss.str().c_str());
680680
}
681681
lastX = x;
682682
}
683683

684+
// The x-coordinates for a hue-hue spline must be in [0,1].
685+
if (m_splineType == HUE_HUE_B_SPLINE)
686+
{
687+
if (m_controlPoints[0].m_x < 0.f)
688+
{
689+
std::ostringstream oss;
690+
oss << "The HUE-HUE spline may not have negative x coordinates.";
691+
throw Exception(oss.str().c_str());
692+
}
693+
else if (m_controlPoints[numPoints - 1].m_x > 1.f)
694+
{
695+
std::ostringstream oss;
696+
oss << "The HUE-HUE spline may not have x coordinates greater than one.";
697+
throw Exception(oss.str().c_str());
698+
}
699+
}
700+
701+
// Make sure the y-coordinates are non-decreasing, for diagonal spline types.
702+
if ( m_splineType == B_SPLINE ||
703+
m_splineType == DIAGONAL_B_SPLINE ||
704+
m_splineType == HUE_HUE_B_SPLINE )
705+
{
706+
float lastY = -std::numeric_limits<float>::max();
707+
if (m_splineType == HUE_HUE_B_SPLINE)
708+
{
709+
// The curve is diagonal but continues in a periodic way, so wrap the last point
710+
// around and ensure the first point would preserve monotonicity.
711+
lastY = m_controlPoints[numPoints - 1].m_y - 1.f;
712+
}
713+
714+
for (size_t i = 0; i < numPoints; ++i)
715+
{
716+
const float y = m_controlPoints[i].m_y;
717+
if (y < lastY)
718+
{
719+
std::ostringstream oss;
720+
oss << "Control point at index " << i << " has a y coordinate '" << y << "' that is ";
721+
oss << "less than previous control point y coordinate '" << lastY << "'.";
722+
throw Exception(oss.str().c_str());
723+
}
724+
lastY = y;
725+
}
726+
}
727+
684728
// Don't allow only x values of 0 and 1 for periodic curves (since they are essentially only one point).
685729
if (numPoints == 2)
686730
{
687731
// Periodic curve types only.
688-
if( m_splineType == PERIODIC_1_B_SPLINE ||
689-
m_splineType == PERIODIC_0_B_SPLINE ||
690-
m_splineType == HUE_HUE_B_SPLINE )
732+
if ( m_splineType == PERIODIC_1_B_SPLINE ||
733+
m_splineType == PERIODIC_0_B_SPLINE ||
734+
m_splineType == HUE_HUE_B_SPLINE )
691735
{
692736
const float del_x = m_controlPoints[1].m_x - m_controlPoints[0].m_x;
693737
if (std::abs(1.f - del_x) < 1e-3)

src/OpenColorIO/ops/gradingrgbcurve/GradingRGBCurve.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ void GradingRGBCurveImpl::validate() const
104104
<< "with: " << e.what();
105105
throw Exception(oss.str().c_str());
106106
}
107+
108+
if (m_curves[c]->getSplineType() != B_SPLINE)
109+
{
110+
// TODO: Allow use of the hue curve diagonal spline types?
111+
std::ostringstream oss;
112+
oss << "GradingRGBCurve validation failed: '" << CurveType(c) << "' curve "
113+
<< "is of the wrong BSplineType.";
114+
throw Exception(oss.str().c_str());
115+
}
107116
}
108117
}
109118

tests/cpu/Config_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4650,7 +4650,7 @@ OCIO_ADD_TEST(Config, grading_huecurve_serialization)
46504650
" children:\n"
46514651
" - !<GradingHueCurveTransform>\n"
46524652
" style: log\n"
4653-
" hue_hue: {control_points: [0, 0, 0.5, 0.5, 1, 1.123456]}\n"
4653+
" hue_hue: {control_points: [0, 0.15, 0.5, 0.5, 1, 1.123456]}\n"
46544654
" - !<GradingHueCurveTransform>\n"
46554655
" style: log\n"
46564656
" hue_sat: {control_points: [0, 0, 0.5, 0.5, 1, 1.5]}\n"
@@ -4663,7 +4663,7 @@ OCIO_ADD_TEST(Config, grading_huecurve_serialization)
46634663
" lum_lum: {control_points: [-1, -1, 0, 0.1, 0.5, 0.6, 1, 1.1]}\n"
46644664
" - !<GradingHueCurveTransform>\n"
46654665
" style: video\n"
4666-
" hue_hue: {control_points: [-0.2, 0, 0.5, 0.5, 1.2, 1.5]}\n"
4666+
" hue_hue: {control_points: [0.02, -0.1, 0.5, 0.5, 0.9, 0.8]}\n"
46674667
" hue_lum: {control_points: [0, 0, 0.2, 0.5, 1, 1.5]}\n"
46684668
" lum_sat: {control_points: [0, 0, 0.1, 0.5, 1, 1.5], slopes: [0, 1, 1.1]}\n"
46694669
" sat_lum: {control_points: [-1, -1, 0, 0.1, 0.5, 0.6, 1, 1.1]}\n"

tests/cpu/DynamicProperty_tests.cpp

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,55 @@ OCIO_ADD_TEST(DynamicPropertyImpl, equal_grading_rgb_curve)
257257
OCIO_CHECK_ASSERT(!(*dp0 == *dpImplDouble));
258258
}
259259

260+
OCIO_ADD_TEST(DynamicPropertyImpl, setter_validation)
261+
{
262+
// Make an identity dynamic transform.
263+
OCIO::GradingHueCurveTransformRcPtr gct = OCIO::GradingHueCurveTransform::Create(OCIO::GRADING_LOG);
264+
gct->makeDynamic();
265+
266+
// Apply it on CPU.
267+
OCIO::ConfigRcPtr config = OCIO::Config::Create();
268+
OCIO::ConstProcessorRcPtr processor = config->getProcessor(gct);
269+
OCIO::ConstCPUProcessorRcPtr cpuProcessor = processor->getDefaultCPUProcessor();
270+
271+
float pixel[3] = { 0.4f, 0.3f, 0.2f };
272+
cpuProcessor->applyRGB(pixel);
273+
274+
const float error = 1e-5f;
275+
OCIO_CHECK_CLOSE(pixel[0], pixel[0], error);
276+
OCIO_CHECK_CLOSE(pixel[1], pixel[1], error);
277+
OCIO_CHECK_CLOSE(pixel[2], pixel[2], error);
278+
279+
// Get a handle to the dynamic property.
280+
OCIO::DynamicPropertyRcPtr dp;
281+
OCIO_CHECK_NO_THROW(dp = cpuProcessor->getDynamicProperty(OCIO::DYNAMIC_PROPERTY_GRADING_HUECURVE));
282+
auto dpVal = OCIO::DynamicPropertyValue::AsGradingHueCurve(dp);
283+
OCIO_REQUIRE_ASSERT(dpVal);
284+
285+
// Set a non-identity value.
286+
OCIO::GradingHueCurveRcPtr hueCurve = dpVal->getValue()->createEditableCopy();
287+
OCIO::GradingBSplineCurveRcPtr huehue = hueCurve->getCurve(OCIO::HUE_HUE);
288+
huehue->setNumControlPoints(3);
289+
huehue->getControlPoint(0) = OCIO::GradingControlPoint(0.f, -0.1f);
290+
huehue->getControlPoint(1) = OCIO::GradingControlPoint(0.5f, 0.5f);
291+
huehue->getControlPoint(2) = OCIO::GradingControlPoint(0.8f, 0.8f);
292+
dpVal->setValue(hueCurve);
293+
cpuProcessor->applyRGB(pixel);
294+
295+
OCIO_CHECK_CLOSE(pixel[0], 0.4385873675f, error);
296+
OCIO_CHECK_CLOSE(pixel[1], 0.2829087377f, error);
297+
OCIO_CHECK_CLOSE(pixel[2], 0.2556785941f, error);
298+
299+
// Ensure that validation of control points is happening as expected. Set the last point
300+
// so that it is no longer monotonic with respect to the first point. Because it is periodic,
301+
// the last point Y value becomes -0.05 when wrapped around to an X value of -0.2.
302+
huehue->getControlPoint(2) = OCIO::GradingControlPoint(0.8f, 0.95f);
303+
OCIO_CHECK_THROW_WHAT(dpVal->setValue(hueCurve),
304+
OCIO::Exception,
305+
"GradingHueCurve validation failed for 'hue_hue' curve with: Control point at index 0 "
306+
"has a y coordinate '-0.1' that is less than previous control point y coordinate '-0.05'.");
307+
}
308+
260309
OCIO_ADD_TEST(DynamicPropertyImpl, grading_rgb_curve_knots_coefs)
261310
{
262311
auto curve11 = OCIO::GradingBSplineCurve::Create({ { 0.f, 10.f },{ 2.f, 10.f },{ 3.f, 10.f },
@@ -431,8 +480,8 @@ void checkKnotsAndCoefs(
431480

432481
OCIO_ADD_TEST(DynamicPropertyImpl, grading_hue_curve_knots_coefs)
433482
{
434-
auto hh = OCIO::GradingBSplineCurve::Create(
435-
{ {-0.1f, -0.15f}, {0.2f, 0.3f}, {0.5f, 0.25f}, {0.8f, 0.7f}, {0.85f, 0.8f}, {1.05f, 0.9f} },
483+
auto hh = OCIO::GradingBSplineCurve::Create(
484+
{ {0.1f, 0.05f}, {0.2f, 0.3f}, {0.5f, 0.4f}, {0.8f, 0.7f}, {0.9f, 0.75f}, {1.0f, 0.9f} },
436485
OCIO::HUE_HUE);
437486
auto hs = OCIO::GradingBSplineCurve::Create(
438487
{ {-0.15f, 1.25f}, {0.f, 0.8f}, {0.2f, 0.9f}, {0.4f, 1.8f}, {0.6f, 1.4f}, {0.8f, 1.3f}, {0.9f, 1.1f}, {1.1f, 0.7f} },
@@ -505,20 +554,20 @@ OCIO_ADD_TEST(DynamicPropertyImpl, grading_hue_curve_knots_coefs)
505554

506555
{
507556
// Hue-Hue
557+
const float true_knots[15] = {-0.1f, -0.06928571f, 0.0f, 0.05642857f, 0.1f, 0.17549634f, 0.2f, 0.33714286f,
558+
0.5f, 0.62499860f, 0.8f, 0.85261905f, 0.9f, 0.93071429f, 1.f };
508559

509-
const float true_knots[15] = {-0.1f, -0.01816964f, 0.05f, 0.125f, 0.2f, 0.35f, 0.5f, 0.57558291f, 0.8f, 0.82731918f,
510-
0.85f, 0.87808036f, 0.9f, 0.98183036f, 1.05f };
511560
// Quadratic coefs.
512-
const float true_coefsA[14] = { -2.29385141e+00f, 3.43265663e+00f, 2.95979024e+01f, -3.34850652e+01f, -2.11943922e-02f,
513-
2.11186821e-02f, 9.58311056e+00f, 3.05897301e-01f, 1.69849435e+01f, -2.62364216e+01f,
514-
-5.36565978e+00f, -1.21350984e+01f, -2.29385141e+00f, 3.43265663e+00f};
561+
const float true_coefsA[14] = { 15.95930233f, -1.66237113f, -1.44778481f, 6.17827869f, 10.39930009f,
562+
-58.70626575f, -1.54375789f, 1.03834397f, 3.7077401f, -2.12344738f,
563+
-3.54260935f, 4.81365159f, 15.95930233f,-1.66237113f };
515564
// Linear coefs.
516-
const float true_coefsB[14] = { 5.00000000e-01f, 1.24586640e-01f, 5.92592593e-01f, 5.03227795e+00f, 9.51817043e-03f,
517-
3.15985276e-03f, 9.49545739e-03f, 1.45813415e+00f, 1.59543132e+00f, 2.52346065e+00f,
518-
1.33333333e+00f, 1.03199405e+00f, 5.00000000e-01f, 1.24586640e-01f };
565+
const float true_coefsB[14] = { 0.75f, 1.73035714f, 1.5f, 1.33660714f, 1.875f, 3.44521825f, 0.56818182f,
566+
0.14475108f, 0.48295455f, 1.40987919f, 0.66666667f, 0.29384921f, 0.75f, 1.73035714f };
567+
519568
// Constant coefs.
520-
const float true_coefsC[14] = { -0.15f, -0.12444493f, -0.1f, 0.11093265f, 0.3f, 0.30095085f, 0.3019f, 0.35736386f,
521-
0.7f, 0.75626237f, 0.8f, 0.83320962f, 0.85f, 0.87555507f };
569+
const float true_coefsC[14] = { -0.25f, -0.2119088f, -0.1f, -0.01996716f, 0.05f, 0.25082851f, 0.3f,
570+
0.34888683f, 0.4f, 0.51830078f, 0.7f, 0.72527072f, 0.75f, 0.7880912f };
522571

523572
checkKnotsAndCoefs(dp, 0, true_knots, true_coefsA, true_coefsB, true_coefsC, __LINE__);
524573
}

0 commit comments

Comments
 (0)