}
}
+//=======================================================================
+//function : MakeUniformColors
+//purpose :
+//=======================================================================
+Aspect_SequenceOfColor AIS_ColorScale::MakeUniformColors (Standard_Integer theNbColors,
+ Standard_Real theLightness,
+ Standard_Real theHueFrom,
+ Standard_Real theHueTo)
+{
+ Aspect_SequenceOfColor aResult;
+
+ // adjust range to be within (0, 360], with sign according to theHueFrom and theHueTo
+ Standard_Real aHueRange = std::fmod (theHueTo - theHueFrom, 360.);
+ const Standard_Real aHueEps = Precision::Angular() * 180. / M_PI;
+ if (Abs (aHueRange) <= aHueEps)
+ {
+ aHueRange = (aHueRange < 0 ? -360. : 360.);
+ }
+
+ // treat limit cases
+ if (theNbColors < 1)
+ {
+ return aResult;
+ }
+ if (theNbColors == 1)
+ {
+ Standard_Real aHue = std::fmod (theHueFrom, 360.);
+ if (aHue < 0.)
+ {
+ aHue += 360.;
+ }
+ Quantity_Color aColor (theLightness, 130., aHue, Quantity_TOC_CIELch);
+ aResult.Append (aColor);
+ return aResult;
+ }
+
+ // discretize the range with 1 degree step
+ const int NBCOLORS = 2 + (int)Abs (aHueRange / 1.);
+ Standard_Real aHueStep = aHueRange / (NBCOLORS - 1);
+ NCollection_Array1<Quantity_Color> aGrid (0, NBCOLORS - 1);
+ for (Standard_Integer i = 0; i < NBCOLORS; i++)
+ {
+ Standard_Real aHue = std::fmod (theHueFrom + i * aHueStep, 360.);
+ if (aHue < 0.)
+ {
+ aHue += 360.;
+ }
+ aGrid(i).SetValues (theLightness, 130., aHue, Quantity_TOC_CIELch);
+ }
+
+ // and compute distances between each two colors in a grid
+ TColStd_Array1OfReal aMetric (0, NBCOLORS - 1);
+ Standard_Real aLength = 0.;
+ for (Standard_Integer i = 0, j = NBCOLORS - 1; i < NBCOLORS; j = i++)
+ {
+ aLength += (aMetric(i) = aGrid(i).DeltaE2000 (aGrid(j)));
+ }
+
+ // determine desired step by distance;
+ // normally we aim to distribute colors from start to end
+ // of the range, but if distance between first and last points of the range
+ // is less than that step (e.g. range is full 360 deg),
+ // then distribute by the whole 360 deg scope to ensure that first
+ // and last colors are sufficiently distanced
+ Standard_Real aDStep = (aLength - aMetric.First()) / (theNbColors - 1);
+ if (aMetric.First() < aDStep)
+ {
+ aDStep = aLength / theNbColors;
+ }
+
+ // generate sequence
+ aResult.Append(aGrid(0));
+ Standard_Real aParam = 0., aPrev = 0., aTarget = aDStep;
+ for (int i = 1; i < NBCOLORS; i++)
+ {
+ aParam = aPrev + aMetric(i);
+ while (aTarget <= aParam)
+ {
+ float aCoefPrev = float((aParam - aTarget) / (aParam - aPrev));
+ float aCoefCurr = float((aTarget - aPrev) / (aParam - aPrev));
+ Quantity_Color aColor (aGrid(i).Rgb() * aCoefCurr + aGrid(i-1).Rgb() * aCoefPrev);
+ aResult.Append (aColor);
+ aTarget += aDStep;
+ }
+ aPrev = aParam;
+ }
+ if (aResult.Length() < theNbColors)
+ {
+ aResult.Append (aGrid.Last());
+ }
+ Standard_ASSERT_VOID (aResult.Length() == theNbColors, "Failed to generate requested nb of colors");
+ return aResult;
+}
+
//=======================================================================
//function : SizeHint
//purpose :
//! The length of the sequence should be equal to GetNumberOfIntervals().
Standard_EXPORT void SetColors (const Aspect_SequenceOfColor& theSeq);
+ //! Populates colors scale by colors of the same lightness value in CIE Lch
+ //! color space, distributed by hue, with perceptually uniform differences
+ //! between consequent colors.
+ //! See MakeUniformColors() for description of parameters.
+ void SetUniformColors (Standard_Real theLightness,
+ Standard_Real theHueFrom, Standard_Real theHueTo)
+ {
+ SetColors (MakeUniformColors (myNbIntervals, theLightness, theHueFrom, theHueTo));
+ SetColorType (Aspect_TOCSD_USER);
+ }
+
+ //! Generates sequence of colors of the same lightness value in CIE Lch
+ //! color space (see #Quantity_TOC_CIELch), with hue values in the specified range.
+ //! The colors are distributed across the range such as to have perceptually
+ //! same difference between neighbour colors.
+ //! For each color, maximal chroma value fitting in sRGB gamut is used.
+ //!
+ //! @param theNbColors - number of colors to generate
+ //! @param theLightness - lightness to be used (0 is black, 100 is white, 32 is
+ //! lightness of pure blue)
+ //! @param theHueFrom - hue value at the start of the scale
+ //! @param theHueTo - hue value defining the end of the scale
+ //!
+ //! Hue value can be out of the range [0, 360], interpreted as modulo 360.
+ //! The colors of the scale will be in the order of increasing hue if
+ //! theHueTo > theHueFrom, and decreasing otherwise.
+ Standard_EXPORT static Aspect_SequenceOfColor
+ MakeUniformColors (Standard_Integer theNbColors, Standard_Real theLightness,
+ Standard_Real theHueFrom, Standard_Real theHueTo);
+
//! Returns the position of labels concerning color filled rectangles, Aspect_TOCSP_RIGHT by default.
Aspect_TypeOfColorScalePosition GetLabelPosition() const { return myLabelPos; }
|| theL < 0.0 || theL > 1.0 \
|| theS < 0.0 || theS > 1.0) { throw Standard_OutOfRange("Color out"); }
+// Throw exception if CIELab color values are out of range.
+#define Quantity_ColorValidateLabRange(theL, thea, theb) \
+ if (theL < 0. || theL > 100. || thea < -100. || thea > 100. || theb < -110. || theb > 100.) \
+ { throw Standard_OutOfRange("Color out"); }
+
+// Throw exception if CIELch color values are out of range.
+#define Quantity_ColorValidateLchRange(theL, thec, theh) \
+ if (theL < 0. || theL > 100. || thec < 0. || thec > 135. || \
+ theh < 0.0 || theh > 360.) { throw Standard_OutOfRange("Color out"); }
+
namespace
{
//! Raw color for defining list of standard color
case Quantity_TOC_RGB: return anRgb;
case Quantity_TOC_sRGB: return Convert_LinearRGB_To_sRGB (anRgb);
case Quantity_TOC_HLS: return Convert_LinearRGB_To_HLS (anRgb);
+ case Quantity_TOC_CIELab: return Convert_LinearRGB_To_Lab (anRgb);
+ case Quantity_TOC_CIELch: return Convert_Lab_To_Lch (Convert_LinearRGB_To_Lab (anRgb));
}
throw Standard_ProgramError("Internal error");
}
// function : Quantity_Color
// purpose :
// =======================================================================
-Quantity_Color::Quantity_Color (const Standard_Real theR1, const Standard_Real theR2, const Standard_Real theR3,
+Quantity_Color::Quantity_Color (const Standard_Real theC1, const Standard_Real theC2, const Standard_Real theC3,
const Quantity_TypeOfColor theType)
{
- switch (theType)
- {
- case Quantity_TOC_RGB:
- {
- Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
- myRgb.SetValues (float(theR1), float(theR2), float(theR3));
- break;
- }
- case Quantity_TOC_sRGB:
- {
- Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
- myRgb.SetValues ((float )Convert_sRGB_To_LinearRGB (theR1),
- (float )Convert_sRGB_To_LinearRGB (theR2),
- (float )Convert_sRGB_To_LinearRGB (theR3));
- break;
- }
- case Quantity_TOC_HLS:
- {
- Quantity_ColorValidateHlsRange(theR1, theR2, theR3);
- myRgb = Convert_HLS_To_LinearRGB (NCollection_Vec3<float> (float(theR1), float(theR2), float(theR3)));
- break;
- }
- }
+ SetValues (theC1, theC2, theC3, theType);
}
// =======================================================================
// function : SetValues
// purpose :
// =======================================================================
-void Quantity_Color::SetValues (const Standard_Real theR1, const Standard_Real theR2, const Standard_Real theR3,
+void Quantity_Color::SetValues (const Standard_Real theC1, const Standard_Real theC2, const Standard_Real theC3,
const Quantity_TypeOfColor theType)
{
switch (theType)
{
case Quantity_TOC_RGB:
{
- Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
- myRgb.SetValues (float(theR1), float(theR2), float(theR3));
+ Quantity_ColorValidateRgbRange(theC1, theC2, theC3);
+ myRgb.SetValues (float(theC1), float(theC2), float(theC3));
break;
}
case Quantity_TOC_sRGB:
{
- Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
- myRgb.SetValues ((float )Convert_sRGB_To_LinearRGB (theR1),
- (float )Convert_sRGB_To_LinearRGB (theR2),
- (float )Convert_sRGB_To_LinearRGB (theR3));
+ Quantity_ColorValidateRgbRange(theC1, theC2, theC3);
+ myRgb.SetValues ((float )Convert_sRGB_To_LinearRGB (theC1),
+ (float )Convert_sRGB_To_LinearRGB (theC2),
+ (float )Convert_sRGB_To_LinearRGB (theC3));
break;
}
case Quantity_TOC_HLS:
{
- Quantity_ColorValidateHlsRange(theR1, theR2, theR3);
- myRgb = Convert_HLS_To_LinearRGB (NCollection_Vec3<float> (float(theR1), float(theR2), float(theR3)));
+ Quantity_ColorValidateHlsRange(theC1, theC2, theC3);
+ myRgb = Convert_HLS_To_LinearRGB (NCollection_Vec3<float> (float(theC1), float(theC2), float(theC3)));
+ break;
+ }
+ case Quantity_TOC_CIELab:
+ {
+ Quantity_ColorValidateLabRange(theC1, theC2, theC3);
+ myRgb = Convert_Lab_To_LinearRGB (NCollection_Vec3<float> (float(theC1), float(theC2), float(theC3)));
+ break;
+ }
+ case Quantity_TOC_CIELch:
+ {
+ Quantity_ColorValidateLchRange(theC1, theC2, theC3);
+ myRgb = Convert_Lab_To_LinearRGB (Convert_Lch_To_Lab (NCollection_Vec3<float> (float(theC1), float(theC2), float(theC3))));
break;
}
}
theDI = Standard_Real (aHls1[1] - aHls2[1]); // light
}
+// =======================================================================
+// function : DeltaE2000
+// purpose : color difference according to CIE Delta E 2000 formula
+// see http://brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
+// =======================================================================
+Standard_Real Quantity_Color::DeltaE2000 (const Quantity_Color& theOther) const
+{
+ // get color components in CIE Lch space
+ Standard_Real aL1, aL2, aa1, aa2, ab1, ab2;
+ this ->Values (aL1, aa1, ab1, Quantity_TOC_CIELab);
+ theOther.Values (aL2, aa2, ab2, Quantity_TOC_CIELab);
+
+ // mean L
+ Standard_Real aLx_mean = 0.5 * (aL1 + aL2);
+
+ // mean C
+ Standard_Real aC1 = Sqrt (aa1 * aa1 + ab1 * ab1);
+ Standard_Real aC2 = Sqrt (aa2 * aa2 + ab2 * ab2);
+ Standard_Real aC_mean = 0.5 * (aC1 + aC2);
+ Standard_Real aC_mean_pow7 = Pow (aC_mean, 7);
+ static const double a25_pow7 = Pow (25., 7);
+ Standard_Real aG = 0.5 * (1. - Sqrt (aC_mean_pow7 / (aC_mean_pow7 + a25_pow7)));
+ Standard_Real aa1x = aa1 * (1. + aG);
+ Standard_Real aa2x = aa2 * (1. + aG);
+ Standard_Real aC1x = Sqrt (aa1x * aa1x + ab1 * ab1);
+ Standard_Real aC2x = Sqrt (aa2x * aa2x + ab2 * ab2);
+ Standard_Real aCx_mean = 0.5 * (aC1x + aC2x);
+
+ // mean H
+ Standard_Real ah1x = (aC1x > TheEpsilon ? ATan2 (ab1, aa1x) * 180. / M_PI : 270.);
+ Standard_Real ah2x = (aC2x > TheEpsilon ? ATan2 (ab2, aa2x) * 180. / M_PI : 270.);
+ if (ah1x < 0.) ah1x += 360.;
+ if (ah2x < 0.) ah2x += 360.;
+ Standard_Real aHx_mean = 0.5 * (ah1x + ah2x);
+ Standard_Real aDeltahx = ah2x - ah1x;
+ if (Abs (aDeltahx) > 180.)
+ {
+ aHx_mean += (aHx_mean < 180. ? 180. : -180.);
+ aDeltahx += (ah1x >= ah2x ? 360. : -360.);
+ }
+
+ // deltas
+ Standard_Real aDeltaLx = aL2 - aL1;
+ Standard_Real aDeltaCx = aC2x - aC1x;
+ Standard_Real aDeltaHx = 2. * Sqrt (aC1x * aC2x) * Sin (0.5 * aDeltahx * M_PI / 180.);
+
+ // factors
+ Standard_Real aT = 1. - 0.17 * Cos (( aHx_mean - 30.) * M_PI / 180.) +
+ 0.24 * Cos ((2. * aHx_mean ) * M_PI / 180.) +
+ 0.32 * Cos ((3. * aHx_mean + 6.) * M_PI / 180.) -
+ 0.20 * Cos ((4. * aHx_mean - 63.) * M_PI / 180.);
+
+ Standard_Real aLx_mean50_2 = (aLx_mean - 50.) * (aLx_mean - 50.);
+ Standard_Real aS_L = 1. + 0.015 * aLx_mean50_2 / Sqrt (20. + aLx_mean50_2);
+ Standard_Real aS_C = 1. + 0.045 * aCx_mean;
+ Standard_Real aS_H = 1. + 0.015 * aCx_mean * aT;
+
+ Standard_Real aDelta_theta = 30. * Exp (-(aHx_mean - 275.) * (aHx_mean - 275.) / 625.);
+ Standard_Real aCx_mean_pow7 = Pow(aCx_mean, 7);
+ Standard_Real aR_C = 2. * Sqrt (aCx_mean_pow7 / (aCx_mean_pow7 + a25_pow7));
+ Standard_Real aR_T = -aR_C * Sin (2. * aDelta_theta * M_PI / 180.);
+
+ // finally, the difference
+ Standard_Real aDL = aDeltaLx / aS_L;
+ Standard_Real aDC = aDeltaCx / aS_C;
+ Standard_Real aDH = aDeltaHx / aS_H;
+ Standard_Real aDeltaE2000 = Sqrt (aDL * aDL + aDC * aDC + aDH * aDH + aR_T * aDC * aDH);
+ return aDeltaE2000;
+}
+
// =======================================================================
// function : Name
// purpose :
theR3 = aHls[2];
break;
}
+ case Quantity_TOC_CIELab:
+ {
+ const NCollection_Vec3<float> aLab = Convert_LinearRGB_To_Lab (myRgb);
+ theR1 = aLab[0];
+ theR2 = aLab[1];
+ theR3 = aLab[2];
+ break;
+ }
+ case Quantity_TOC_CIELch:
+ {
+ const NCollection_Vec3<float> aLch = Convert_Lab_To_Lch (Convert_LinearRGB_To_Lab (myRgb));
+ theR1 = aLch[0];
+ theR2 = aLch[1];
+ theR3 = aLch[2];
+ break;
+ }
}
}
return NCollection_Vec3<float> (aHue, aMax, aSaturation);
}
-///////////////////////////////////////////////////////////////////////////////
-//////////////////////////////////// TESTS ////////////////////////////////////
-///////////////////////////////////////////////////////////////////////////////
-static void TestOfColor()
+// =======================================================================
+// function : CIELab_f
+// purpose : non-linear function transforming XYZ coordinates to CIE Lab
+// see http://www.brucelindbloom.com/index.html?Equations.html
+// =======================================================================
+static inline double CIELab_f (double theValue)
{
- Standard_Real H, L, S;
- Standard_Real R, G, B;
- Standard_Real DC, DI;
- Standard_Integer i;
-
- std::cout << "definition color tests\n----------------------\n";
-
- Quantity_Color C1;
- Quantity_Color C2 (Quantity_NOC_ROYALBLUE2);
- Quantity_Color C3 (Quantity_NOC_SANDYBROWN);
-
- // An Introduction to Standard_Object-Oriented Programming and C++ p43
- // a comment for the "const char *const" declaration
- const char *const cyan = "YELLOW";
- const char *const blue = "ROYALBLUE2";
- const char *const brown = "SANDYBROWN";
-
- Standard_Real RR, GG, BB;
-
- const Standard_Real DELTA = 1.0e-4;
-
- std::cout << "Get values and names of color tests\n-----------------------------------\n";
-
- C1.Values (R, G, B, Quantity_TOC_RGB);
- if ((R!=1.0) || (G!=1.0) || (B!=0.0))
- {
- std::cout << "TEST_ERROR : Values () bad default color\n";
- std::cout << "R, G, B values: " << R << " " << G << " " << B << "\n";
- }
- if ( (C1.Red ()!=1.0) || (C1.Green ()!=1.0) || (C1.Blue ()!=0.0) )
- {
- std::cout << "TEST_ERROR : Values () bad default color\n";
- std::cout << "R, G, B values: " << C1.Red () << " " << C1.Green () << " " << C1.Blue () << "\n";
- }
- if (strcmp (Quantity_Color::StringName (C1.Name()), cyan) != 0)
- {
- std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C1.Name()) << " != YELLOW\n";
- }
-
- RR=0.262745; GG=0.431373; BB=0.933333;
- C1.SetValues (RR, GG, BB, Quantity_TOC_RGB);
- C2.Values (R, G, B, Quantity_TOC_RGB);
- if ((Abs (RR-R) > DELTA)
- || (Abs (GG-G) > DELTA)
- || (Abs (BB-B) > DELTA))
- {
- std::cout << "TEST_ERROR : Values () bad default color\n";
- std::cout << "R, G, B values: " << R << " " << G << " " << B << "\n";
- }
-
- if (C2 != C1)
- {
- std::cout << "TEST_ERROR : IsDifferent ()\n";
- }
- if (C3 == C1)
- {
- std::cout << "TEST_ERROR : IsEqual ()\n";
- }
+ return theValue > 0.008856451679035631 ? Pow (theValue, 1./3.) : (7.787037037037037 * theValue) + 16. / 116.;
+}
- std::cout << "Distance C1,C2 " << C1.Distance (C2) << "\n";
- std::cout << "Distance C1,C3 " << C1.Distance (C3) << "\n";
- std::cout << "Distance C2,C3 " << C2.Distance (C3) << "\n";
- std::cout << "SquareDistance C1,C2 " << C1.SquareDistance (C2) << "\n";
- std::cout << "SquareDistance C1,C3 " << C1.SquareDistance (C3) << "\n";
- std::cout << "SquareDistance C2,C3 " << C2.SquareDistance (C3) << "\n";
+// =======================================================================
+// function : CIELab_invertf
+// purpose : inverse of non-linear function transforming XYZ coordinates to CIE Lab
+// see http://www.brucelindbloom.com/index.html?Equations.html
+// =======================================================================
+static inline double CIELab_invertf (double theValue)
+{
+ double aV3 = theValue * theValue * theValue;
+ return aV3 > 0.008856451679035631 ? aV3 : (theValue - 16. / 116.) / 7.787037037037037;
+}
- if (strcmp (Quantity_Color::StringName (C2.Name()), blue) != 0)
- {
- std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C2.Name()) << " != ROYALBLUE2\n";
- }
+// =======================================================================
+// function : Convert_LinearRGB_To_Lab
+// purpose : convert RGB color to CIE Lab color
+// see https://www.easyrgb.com/en/math.php
+// =======================================================================
+NCollection_Vec3<float> Quantity_Color::Convert_LinearRGB_To_Lab (const NCollection_Vec3<float>& theRgb)
+{
+ double aR = theRgb[0];
+ double aG = theRgb[1];
+ double aB = theRgb[2];
+
+ // convert to XYZ normalized to D65 / 2 deg (CIE 1931) standard illuminant intensities
+ // see http://www.brucelindbloom.com/index.html?Equations.html
+ double aX = (aR * 0.4124564 + aG * 0.3575761 + aB * 0.1804375) * 100. / 95.047;
+ double aY = (aR * 0.2126729 + aG * 0.7151522 + aB * 0.0721750) * 100. / 100.000;
+ double aZ = (aR * 0.0193339 + aG * 0.1191920 + aB * 0.9503041) * 100. / 108.883;
+
+ // convert to Lab
+ double afX = CIELab_f (aX);
+ double afY = CIELab_f (aY);
+ double afZ = CIELab_f (aZ);
+
+ double aL = 116. * afY - 16.;
+ double aa = 500. * (afX - afY);
+ double ab = 200. * (afY - afZ);
+
+ return NCollection_Vec3<float> ((float)aL, (float)aa, (float)ab);
+}
- std::cout << "conversion rgbhls tests\n-----------------------\n";
- Quantity_Color::RgbHls (R, G, B, H, L, S);
- Quantity_Color::HlsRgb (H, L, S, R, G, B);
- RR=0.262745; GG=0.431373; BB=0.933333;
- if ((Abs (RR-R) > DELTA)
- || (Abs (GG-G) > DELTA)
- || (Abs (BB-B) > DELTA))
- {
- std::cout << "TEST_ERROR : RgbHls or HlsRgb bad conversion\n";
- std::cout << "RGB init : " << RR << " " << GG << " " << BB << "\n";
- std::cout << "RGB values : " << R << " " << G << " " << B << "\n";
- std::cout << "Difference RGB : " << RR-R << " " << GG-G << " " << BB-B << "\n";
+// =======================================================================
+// function : Convert_Lab_To_LinearRGB
+// purpose : convert CIE Lab color to RGB
+// see https://www.easyrgb.com/en/math.php
+// =======================================================================
+NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_LinearRGB (const NCollection_Vec3<float>& theLab)
+{
+ double aL = theLab[0];
+ double aa = theLab[1];
+ double ab = theLab[2];
+
+ // conversion from Lab to RGB can yield point outside of RGB cube,
+ // in such case we will reduce a and b components gradually
+ // (by 0.1% at each step) until we fit into the range;
+ // NB: the procedure could be improved to get more precise
+ // result but this does not seem really crucial
+ const int NBSTEPS = 1000;
+ for (Standard_Integer aRate = NBSTEPS; ; aRate--)
+ {
+ double aC = aRate / (double)NBSTEPS;
+
+ // convert to XYZ for D65 / 2 deg (CIE 1931) standard illuminant
+ double afY = (aL + 16.) / 116.;
+ double afX = aC * aa / 500. + afY;
+ double afZ = afY - aC * ab / 200.;
+
+ double aX = CIELab_invertf(afX) * 95.047;
+ double aY = CIELab_invertf(afY) * 100.000;
+ double aZ = CIELab_invertf(afZ) * 108.883;
+
+ // convert to RGB
+ // see http://www.brucelindbloom.com/index.html?Equations.html
+ double aR = (aX * 3.2404542 + aY * -1.5371385 + aZ * -0.4985314) / 100.;
+ double aG = (aX * -0.9692660 + aY * 1.8760108 + aZ * 0.0415560) / 100.;
+ double aB = (aX * 0.0556434 + aY * -0.2040259 + aZ * 1.0572252) / 100.;
+
+ // exit if we are in range or at zero C
+ if (aRate == 0 ||
+ (aR >= 0. && aR <= 1. && aG >= 0. && aG <= 1. && aB >= 0. && aB <= 1.))
+ {
+ return NCollection_Vec3<float>((float)aR, (float)aG, (float)aB);
+ }
}
+}
- std::cout << "distance tests\n--------------\n";
- R = (float ) 0.9568631; G = (float ) 0.6431371; B = (float ) 0.3764711;
- C2.SetValues (R, G, B, Quantity_TOC_RGB);
- if (C2.Distance (C3) > DELTA)
- {
- std::cout << "TEST_ERROR : Distance () bad result\n";
- std::cout << "Distance C2 and C3 : " << C2.Distance (C3) << "\n";
- }
+// =======================================================================
+// function : Convert_Lab_To_Lch
+// purpose : convert CIE Lab color to CIE Lch color
+// see https://www.easyrgb.com/en/math.php
+// =======================================================================
+NCollection_Vec3<float> Quantity_Color::Convert_Lab_To_Lch (const NCollection_Vec3<float>& theLab)
+{
+ double aa = theLab[1];
+ double ab = theLab[2];
- C2.Delta (C3, DC, DI);
- if (Abs (DC) > DELTA)
- {
- std::cout << "TEST_ERROR : Delta () bad result for DC\n";
- }
- if (Abs (DI) > DELTA)
- {
- std::cout << "TEST_ERROR : Delta () bad result for DI\n";
- }
+ double aC = Sqrt (aa * aa + ab * ab);
+ double aH = (aC > TheEpsilon ? ATan2 (ab, aa) * 180. / M_PI : 0.);
- std::cout << "name tests\n----------\n";
- R = (float ) 0.9568631; G = (float ) 0.6431371; B = (float ) 0.3764711;
- C2.SetValues (R, G, B, Quantity_TOC_RGB);
- if (strcmp (Quantity_Color::StringName (C2.Name()), brown) != 0)
- {
- std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C2.Name()) << " != SANDYBROWN\n";
- }
+ if (aH < 0.) aH += 360.;
- std::cout << "contrast change tests\n---------------------\n";
- for (i=1; i<=10; i++)
- {
- C2.ChangeContrast (10.);
- C2.ChangeContrast (-9.09090909);
- }
- C2.Values (R, G, B, Quantity_TOC_RGB);
- RR=0.956863; GG=0.6431371; BB=0.3764711;
- if ((Abs (RR-R) > DELTA)
- || (Abs (GG-G) > DELTA)
- || (Abs (BB-B) > DELTA))
- {
- std::cout << "TEST_ERROR : ChangeContrast () bad values\n";
- std::cout << "RGB init : " << RR << " " << GG << " " << BB << "\n";
- std::cout << "RGB values : " << R << " " << G << " " << B << "\n";
- }
+ return NCollection_Vec3<float> (theLab[0], (float)aC, (float)aH);
}
// =======================================================================
-// function : Test
-// purpose :
+// function : Convert_Lch_To_Lab
+// purpose : convert CIE Lch color to CIE Lab color
+// see https://www.easyrgb.com/en/math.php
// =======================================================================
-void Quantity_Color::Test()
+NCollection_Vec3<float> Quantity_Color::Convert_Lch_To_Lab (const NCollection_Vec3<float>& theLch)
{
- try
- {
- OCC_CATCH_SIGNALS
- TestOfColor();
- }
- catch (Standard_Failure const& anException)
- {
- std::cout << anException << std::endl;
- }
+ double aC = theLch[1];
+ double aH = theLch[2];
+
+ aH *= M_PI / 180.;
+
+ double aa = aC * Cos (aH);
+ double ab = aC * Sin (aH);
+
+ return NCollection_Vec3<float> (theLch[0], (float)aa, (float)ab);
}
//=======================================================================
//! Creates a color according to the definition system theType.
//! Throws exception if values are out of range.
- Standard_EXPORT Quantity_Color (const Standard_Real theR1,
- const Standard_Real theR2,
- const Standard_Real theR3,
+ Standard_EXPORT Quantity_Color (const Standard_Real theC1,
+ const Standard_Real theC2,
+ const Standard_Real theC3,
const Quantity_TypeOfColor theType);
- //! Define color from RGB values.
+ //! Define color from linear RGB values.
Standard_EXPORT explicit Quantity_Color (const NCollection_Vec3<float>& theRgb);
//! Returns the name of the nearest color from the Quantity_NameOfColor enumeration.
//! Updates the color from specified named color.
void SetValues (const Quantity_NameOfColor theName) { myRgb = valuesOf (theName, Quantity_TOC_RGB); }
+ //! Return the color as vector of 3 float elements.
+ const NCollection_Vec3<float>& Rgb () const { return myRgb; }
+
//! Return the color as vector of 3 float elements.
operator const NCollection_Vec3<float>&() const { return myRgb; }
- //! Returns in theR1, theR2 and theR3 the components of this color according to the color system definition theType.
- Standard_EXPORT void Values (Standard_Real& theR1,
- Standard_Real& theR2,
- Standard_Real& theR3,
+ //! Returns in theC1, theC2 and theC3 the components of this color
+ //! according to the color system definition theType.
+ Standard_EXPORT void Values (Standard_Real& theC1,
+ Standard_Real& theC2,
+ Standard_Real& theC3,
const Quantity_TypeOfColor theType) const;
//! Updates a color according to the mode specified by theType.
//! Throws exception if values are out of range.
- Standard_EXPORT void SetValues (const Standard_Real theR1,
- const Standard_Real theR2,
- const Standard_Real theR3,
+ Standard_EXPORT void SetValues (const Standard_Real theC1,
+ const Standard_Real theC2,
+ const Standard_Real theC3,
const Quantity_TypeOfColor theType);
//! Returns the Red component (quantity of red) of the color within range [0.0; 1.0].
Standard_EXPORT void Delta (const Quantity_Color& theColor,
Standard_Real& DC, Standard_Real& DI) const;
+ //! Returns the value of the perceptual difference between this color
+ //! and @p theOther, computed using the CIEDE2000 formula.
+ //! The difference is in range [0, 100.], with 1 approximately corresponding
+ //! to the minimal percievable difference (usually difference 5 or greater is
+ //! needed for the difference to be recognizable in practice).
+ Standard_EXPORT Standard_Real DeltaE2000 (const Quantity_Color& theOther) const;
+
public:
//! Returns the color from Quantity_NameOfColor enumeration nearest to specified RGB values.
return true;
}
+public:
+ //!@name Routines converting colors between different encodings and color spaces
+
//! Parses the string as a hex color (like "#FF0" for short sRGB color, or "#FFFF00" for sRGB color)
//! @param theHexColorString the string to be parsed
//! @param theColor a color that is a result of parsing
return Convert_sRGB_To_LinearRGB (Convert_HLS_To_sRGB (theHls));
}
+ //! Converts linear RGB components into CIE Lab ones.
+ Standard_EXPORT static NCollection_Vec3<float> Convert_LinearRGB_To_Lab (const NCollection_Vec3<float>& theRgb);
+
+ //! Converts CIE Lab components into CIE Lch ones.
+ Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_Lch (const NCollection_Vec3<float>& theLab);
+
+ //! Converts CIE Lab components into linear RGB ones.
+ //! Note that the resulting values may be out of the valid range for RGB.
+ Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_LinearRGB (const NCollection_Vec3<float>& theLab);
+
+ //! Converts CIE Lch components into CIE Lab ones.
+ Standard_EXPORT static NCollection_Vec3<float> Convert_Lch_To_Lab (const NCollection_Vec3<float>& theLch);
+
//! Convert the color value to ARGB integer value, with alpha equals to 0.
//! So the output is formatted as 0x00RRGGBB.
//! Note that this unpacking does NOT involve non-linear sRGB -> linear RGB conversion,
theColor.SetValues (aColor.r() / 255.0, aColor.g() / 255.0, aColor.b() / 255.0, Quantity_TOC_sRGB);
}
-public:
-
//! Convert linear RGB component into sRGB using OpenGL specs formula (double precision), also known as gamma correction.
static Standard_Real Convert_LinearRGB_To_sRGB (Standard_Real theLinearValue)
{
Convert_sRGB_To_LinearRGB (theRGB.b()));
}
-public:
-
//! Convert linear RGB component into sRGB using approximated uniform gamma coefficient 2.2.
static float Convert_LinearRGB_To_sRGB_approx22 (float theLinearValue) { return powf (theLinearValue, 2.2f); }
Convert_sRGB_To_LinearRGB_approx22 (theRGB.b()));
}
-public:
-
- //! Returns the value used to compare two colors for equality; 0.0001 by default.
- Standard_EXPORT static Standard_Real Epsilon();
-
- //! Set the value used to compare two colors for equality.
- Standard_EXPORT static void SetEpsilon (const Standard_Real theEpsilon);
-
//! Converts HLS components into sRGB ones.
static void HlsRgb (const Standard_Real theH, const Standard_Real theL, const Standard_Real theS,
Standard_Real& theR, Standard_Real& theG, Standard_Real& theB)
theS = aHls[2];
}
- //! Internal test
- Standard_EXPORT static void Test();
+public:
+
+ //! Returns the value used to compare two colors for equality; 0.0001 by default.
+ Standard_EXPORT static Standard_Real Epsilon();
+
+ //! Set the value used to compare two colors for equality.
+ Standard_EXPORT static void SetEpsilon (const Standard_Real theEpsilon);
//! Dumps the content of me into the stream
Standard_EXPORT void DumpJson (Standard_OStream& theOStream, Standard_Integer theDepth = -1) const;
//! Identifies color definition systems.
enum Quantity_TypeOfColor
{
- Quantity_TOC_RGB, //!< normalized linear RGB (red, green, blue) values within range [0..1] for each component
- Quantity_TOC_sRGB, //!< normalized non-linear gamma-shifted RGB (red, green, blue) values within range [0..1] for each component
- Quantity_TOC_HLS, //!< hue + light + saturation components, where:
- //! - First component is the Hue (H) angle in degrees within range [0.0; 360.0], 0.0 being Red;
- //! value -1.0 is a special value reserved for grayscale color (S should be 0.0).
- //! - Second component is the Lightness (L) within range [0.0; 1.0]
- //! - Third component is the Saturation (S) within range [0.0; 1.0]
+ //! Normalized linear RGB (red, green, blue) values within range [0..1] for each component
+ Quantity_TOC_RGB,
+
+ //! Normalized non-linear gamma-shifted RGB (red, green, blue) values within range [0..1] for each component
+ Quantity_TOC_sRGB,
+
+ //! Hue + light + saturation components, where:
+ //! - First component is the Hue (H) angle in degrees within range [0.0; 360.0], 0.0 being Red;
+ //! value -1.0 is a special value reserved for grayscale color (S should be 0.0).
+ //! - Second component is the Lightness (L) within range [0.0; 1.0]
+ //! - Third component is the Saturation (S) within range [0.0; 1.0]
+ Quantity_TOC_HLS,
+
+ //! CIE L*a*b* color space, constructed to be perceptually uniform for human eye.
+ //! The values are assumed to be with respect to D65 2° white point.
+ //!
+ //! The color is defined by:
+ //! - L: lightness in range [0, 100] (from black to white)
+ //! - a: green-to-red axis, approximately in range [-90, 100]
+ //! - b: blue-to-yellow axis, approximately in range [-110, 95]
+ //!
+ //! Note that not all combinations of L, a, and b values represent visible
+ //! colors, and RGB cube takes only part of visible color space.
+ //!
+ //! When Lab color is converted to RGB, a and b components may be reduced
+ //! (with the same proportion) to fit the result into the RGB range.
+ Quantity_TOC_CIELab,
+
+ //! CIE L*c*h* color space, same as L*a*b* in cylindrical coordinates:
+ //! - L: lightness in range [0, 100] (from black to white)
+ //! - c: chroma, approximately in range [0, 135], 0 corresponds to greyscale
+ //! - h: hue angle, in range [0., 360.]
+ //!
+ //! The hue values of standard colors are approximately:
+ //! - red at 40,
+ //! - yellow at 103,
+ //! - green at 136,
+ //! - cyan at 196,
+ //! - blue at 306,
+ //! - magenta at 328.
+ //!
+ //! When Lch color is converted to RGB, chroma component may be reduced
+ //! to fit the color into the RGB range.
+ Quantity_TOC_CIELch
};
#endif // _Quantity_TypeOfColor_HeaderFile
aColorScale->SetColors (aSeq);
aColorScale->SetColorType (Aspect_TOCSD_USER);
}
+ else if (aFlag == "-uniform")
+ {
+ const Standard_Real aLightness = Draw::Atof (theArgVec[++anArgIter]);
+ const Standard_Real aHueStart = Draw::Atof (theArgVec[++anArgIter]);
+ const Standard_Real aHueEnd = Draw::Atof (theArgVec[++anArgIter]);
+ aColorScale->SetUniformColors (aLightness, aHueStart, aHueEnd);
+ aColorScale->SetColorType (Aspect_TOCSD_USER);
+ }
else if (aFlag == "-labels"
|| aFlag == "-freelabels")
{
return 0;
}
+//===============================================================================================
+//function : VColorConvert
+//purpose :
+//===============================================================================================
+static int VColorConvert (Draw_Interpretor& theDI, Standard_Integer theNbArgs, const char** theArgVec)
+{
+ if (theNbArgs != 6)
+ {
+ std::cerr << "Error: command syntax is incorrect, see help" << std::endl;
+ return 1;
+ }
+
+ Standard_Boolean convertFrom = (! strcasecmp (theArgVec[1], "from"));
+ if (! convertFrom && strcasecmp (theArgVec[1], "to"))
+ {
+ std::cerr << "Error: first argument must be either \"to\" or \"from\"" << std::endl;
+ return 1;
+ }
+
+ const char* aTypeStr = theArgVec[2];
+ Quantity_TypeOfColor aType = Quantity_TOC_RGB;
+ if (! strcasecmp (aTypeStr, "srgb"))
+ {
+ aType = Quantity_TOC_sRGB;
+ }
+ else if (! strcasecmp (aTypeStr, "hls"))
+ {
+ aType = Quantity_TOC_HLS;
+ }
+ else if (! strcasecmp (aTypeStr, "lab"))
+ {
+ aType = Quantity_TOC_CIELab;
+ }
+ else if (! strcasecmp (aTypeStr, "lch"))
+ {
+ aType = Quantity_TOC_CIELch;
+ }
+ else
+ {
+ std::cerr << "Error: unknown colorspace type: " << aTypeStr << std::endl;
+ return 1;
+ }
+
+ double aC1 = Draw::Atof (theArgVec[3]);
+ double aC2 = Draw::Atof (theArgVec[4]);
+ double aC3 = Draw::Atof (theArgVec[5]);
+
+ Quantity_Color aColor (aC1, aC2, aC3, convertFrom ? aType : Quantity_TOC_RGB);
+ aColor.Values (aC1, aC2, aC3, convertFrom ? Quantity_TOC_RGB : aType);
+
+ // print values with 6 decimal digits
+ char buffer[1024];
+ Sprintf (buffer, "%.6f %.6f %.6f", aC1, aC2, aC3);
+ theDI << buffer;
+
+ return 0;
+}
+
+//===============================================================================================
+//function : VColorDiff
+//purpose :
+//===============================================================================================
+static int VColorDiff (Draw_Interpretor& theDI, Standard_Integer theNbArgs, const char** theArgVec)
+{
+ if (theNbArgs != 7)
+ {
+ std::cerr << "Error: command syntax is incorrect, see help" << std::endl;
+ return 1;
+ }
+
+ double aR1 = Draw::Atof (theArgVec[1]);
+ double aG1 = Draw::Atof (theArgVec[2]);
+ double aB1 = Draw::Atof (theArgVec[3]);
+ double aR2 = Draw::Atof (theArgVec[4]);
+ double aG2 = Draw::Atof (theArgVec[5]);
+ double aB2 = Draw::Atof (theArgVec[6]);
+
+ Quantity_Color aColor1 (aR1, aG1, aB1, Quantity_TOC_RGB);
+ Quantity_Color aColor2 (aR2, aG2, aB2, Quantity_TOC_RGB);
+
+ theDI << aColor1.DeltaE2000 (aColor2);
+
+ return 0;
+}
+
//=======================================================================
//function : ViewerCommands
//purpose :
"\n\t\t: [-labels Label1 Label2 ...] [-label Index Label]"
"\n\t\t: [-freeLabels NbOfLabels Label1 Label2 ...]"
"\n\t\t: [-xy Left=0 Bottom=0]"
+ "\n\t\t: [-uniform lightness hue_from hue_to]"
"\n\t\t: -demo - displays a color scale with demonstratio values"
"\n\t\t: -colors - set colors for all intervals"
"\n\t\t: -color - set color for specific interval"
+ "\n\t\t: -uniform - generate colors with the same lightness"
"\n\t\t: -textpos - horizontal label position relative to color scale bar"
"\n\t\t: -labelAtBorder - vertical label position relative to color interval;"
"\n\t\t: at border means the value inbetween neighbor intervals,"
"\n\t\t: -title - set title"
"\n\t\t: -reversed - setup smooth color transition between intervals"
"\n\t\t: -smoothTransition - swap colorscale direction"
- "\n\t\t: -hueRange - set hue angles corresponding to minimum and maximum values"
+ "\n\t\t: -hueRange - set hue angles corresponding to minimum and maximum values",
__FILE__, VColorScale, group);
theCommands.Add("vgraduatedtrihedron",
"vgraduatedtrihedron : -on/-off [-xname Name] [-yname Name] [-zname Name] [-arrowlength Value]\n"
"\n\t\t: -duration Seconds animation duration in seconds",
__FILE__, VViewCube, group);
+ theCommands.Add("vcolorconvert" ,
+ "vcolorconvert {from|to} type C1 C2 C2"
+ "\n\t\t: vcolorconvert from type C1 C2 C2: Converts color from specified color space to linear RGB"
+ "\n\t\t: vcolorconvert to type R G B: Converts linear RGB color to specified color space"
+ "\n\t\t: type can be sRGB, HLS, Lab, or Lch",
+ __FILE__,VColorConvert,group);
+ theCommands.Add("vcolordiff" ,
+ "vcolordiff R1 G1 B1 R2 G2 B2: returns CIEDE2000 color difference between two RGB colors",
+ __FILE__,VColorDiff,group);
}
puts "============"
-puts "OCC25632"
+puts "OCC22632"
puts "Display logarithmic colorscale."
puts "============"
puts ""
--- /dev/null
+puts "============"
+puts "0031454: Visualization - perceptually uniform color scale"
+puts "============"
+puts ""
+
+vclear
+vinit View1 -width 600
+#vsetcolorbg 1 1 1
+vaxo
+
+set nbcolors 10
+
+# create default color scale with 20 steps
+vcolorscale hsl -range 0 1 $nbcolors -xy 0 0 -title HSL
+
+# create color scales with uniform lightness
+vcolorscale lch30 -range 0 1 $nbcolors -xy 100 0 -uniform 30 300 40 -title L=30
+vcolorscale lch40 -range 0 1 $nbcolors -xy 200 0 -uniform 40 300 40 -title L=40
+vcolorscale lch50 -range 0 1 $nbcolors -xy 300 0 -uniform 50 300 40 -title L=50
+vcolorscale lch60 -range 0 1 $nbcolors -xy 400 0 -uniform 60 300 40 -title L=60
+vcolorscale lch70 -range 0 1 $nbcolors -xy 500 0 -uniform 70 300 40 -title L=70
+
+vdump ${imagedir}/${casename}.png
--- /dev/null
+# Auxiliary procedure to compare triplet of numbers
+# against reference values, with tolerance
+proc check3reals {name value1 value2 value3 ref1 ref2 ref3 tol} {
+ checkreal "${name}, component 1" $value1 $ref1 $tol 1e-6
+ checkreal "${name}, component 2" $value2 $ref2 $tol 1e-6
+ checkreal "${name}, component 3" $value3 $ref3 $tol 1e-6
+}
+
+# weird way to disable unnecessary screen dumps
+set to_dump_screen 0
\ No newline at end of file
--- /dev/null
+# Check calculation of CIE Ddlta E 2000 color difference
+
+# Reference data are obtained using online calculator
+# http://brucelindbloom.com/index.html?ColorDifferenceCalc.html
+# or
+# https://cielab.xyz/
+# Second one also shows color and reports if it is out of RGB gamut.
+#
+# Note that values out of RGB gamut would be amended during
+# conversion to RGB and thus the result would be different!
+#
+# Samples aimed at testing discontinuity of CIEDE2000
+# formula are very sensitive to accuracy, we need higher tolerance
+# because conversion is done via RGB floats and loses precision.
+#
+# Format: { {L1 a1 b1} {L2 a2 b2} expected_diff [tolerance] }
+set lab_diff_samples {
+ { # synthetic color pairs }
+ { {0 0 0} {50 0 0} 36.519268 }
+ { {50 0 0} {100 0 0} 36.519268 }
+ { {0 0 0} {100 0 0} 100. }
+ { {20 10 10} {80 10 10} 60. }
+ { {50 0 0} {50 0 50} 23.529412 }
+ { {50 60 60} {50 60 0} 28.016927 }
+ { {30 30 40} {30 30 -60} 44.606253 }
+
+ { # discontinuity of CIEDE2000 formula }
+ { {30 50.00 40} {20 -10 -8} 39.105394 0.001 }
+ { {30 50.01 40} {20 -10 -8} 43.53247 0.001 }
+ { {20 30.00 30.01} {60 -10 -10} 49.416742 0.05 }
+ { {20 30.01 30.00} {60 -10 -10} 52.448227 0.05 }
+
+ { # randomly generated data }
+ { {73.4450 34.9839 -24.6753} {87.6216 -18.4863 57.8838} 62.402500 }
+ { {93.6166 -27.3677 29.3893} {46.9191 12.3400 -27.5948} 54.640034 }
+ { {53.9062 61.0929 -51.7583} {65.5157 26.3376 -37.0512} 15.679046 }
+ { {83.6996 9.3358 -24.5571} {93.2268 -3.8589 3.5217} 23.158692 }
+ { {64.8053 -27.3177 -8.9602} {65.8225 37.3192 -38.1465} 34.670514 }
+ { {94.7633 -19.7915 69.2787} {90.9238 -16.7535 4.1936} 26.093024 }
+ { {85.4699 5.6078 -11.1083} {67.9455 -28.4536 7.8808} 31.115070 }
+ { {83.5473 -15.7170 8.3546} {81.3193 -37.2851 57.7090} 19.696753 }
+ { {75.7406 -12.0785 -12.3505} {80.0810 -54.8591 52.1739} 35.834099 }
+ { {62.8209 32.1209 16.9113} {82.1106 25.0843 -7.9416} 21.178519 }
+}
+
+foreach sample $lab_diff_samples {
+ set lab1 [lindex $sample 0]
+ if { $lab1 == "#" } continue
+ set lab2 [lindex $sample 1]
+ set dref [lindex $sample 2]
+ set diff [vcolordiff {*}[vcolorconvert from lab {*}$lab1] {*}[vcolorconvert from lab {*}$lab2]]
+
+ # use tolerance 1e-4 except if other value is defined in sample data
+ set tol [lindex $sample 3]
+ if { $tol == "" } { set tol 1e-4 }
+
+ checkreal "CIEDE2000 diff of Lab colors ($lab1) and ($lab2)" $diff $dref $tol 1e-6
+}
--- /dev/null
+# Check calculation of CIE Ddlta E 2000 color difference
+
+# Reference data taken from
+# "The CIEDE2000 Color-Difference Formula: Implementation Notes,
+# Supplementary Test Data, and Mathematical Observations",
+# G. Sharma, W. Wu, E. N. Dalal,
+# Color Research and Application, vol. 30. No. 1, pp. 21-30, February 2005
+# http://www2.ece.rochester.edu/~gsharma/ciede2000/
+#
+# Note: samples 1 to 6 and 28 are commented because the colors
+# are out of RGB gamut.
+# Samples 10 and 15 are aimed at testing discontinuity of CIEDE2000
+# formula and so are very sensitive to accuracy, we need higher tolerance
+# because conversion is done via RGB floats and loses precision.
+#
+# Format: L1 a1 b1 L2 a2 b2 expected_diff [tolerance]
+set lab_diff_samples {
+# 50.0000 2.6772 -79.7751 50.0000 0.0000 -82.7485 2.0425
+# 50.0000 3.1571 -77.2803 50.0000 0.0000 -82.7485 2.8615
+# 50.0000 2.8361 -74.0200 50.0000 0.0000 -82.7485 3.4412
+# 50.0000 -1.3802 -84.2814 50.0000 0.0000 -82.7485 1.0000
+# 50.0000 -1.1848 -84.8006 50.0000 0.0000 -82.7485 1.0000
+# 50.0000 -0.9009 -85.5211 50.0000 0.0000 -82.7485 1.0000
+50.0000 0.0000 0.0000 50.0000 -1.0000 2.0000 2.3669
+50.0000 -1.0000 2.0000 50.0000 0.0000 0.0000 2.3669
+50.0000 2.4900 -0.0010 50.0000 -2.4900 0.0009 7.1792
+50.0000 2.4900 -0.0010 50.0000 -2.4900 0.0010 7.1792 0.05
+50.0000 2.4900 -0.0010 50.0000 -2.4900 0.0011 7.2195
+50.0000 2.4900 -0.0010 50.0000 -2.4900 0.0012 7.2195
+50.0000 -0.0010 2.4900 50.0000 0.0009 -2.4900 4.8045
+50.0000 -0.0010 2.4900 50.0000 0.0010 -2.4900 4.8045
+50.0000 -0.0010 2.4900 50.0000 0.0011 -2.4900 4.7461 0.06
+50.0000 2.5000 0.0000 50.0000 0.0000 -2.5000 4.3065
+50.0000 2.5000 0.0000 73.0000 25.0000 -18.0000 27.1492
+50.0000 2.5000 0.0000 61.0000 -5.0000 29.0000 22.8977
+50.0000 2.5000 0.0000 56.0000 -27.0000 -3.0000 31.9030
+50.0000 2.5000 0.0000 58.0000 24.0000 15.0000 19.4535
+50.0000 2.5000 0.0000 50.0000 3.1736 0.5854 1.0000
+50.0000 2.5000 0.0000 50.0000 3.2972 0.0000 1.0000
+50.0000 2.5000 0.0000 50.0000 1.8634 0.5757 1.0000
+50.0000 2.5000 0.0000 50.0000 3.2592 0.3350 1.0000
+60.2574 -34.0099 36.2677 60.4626 -34.1751 39.4387 1.2644
+63.0109 -31.0961 -5.8663 62.8187 -29.7946 -4.0864 1.2630
+61.2901 3.7196 -5.3901 61.4292 2.2480 -4.9620 1.8731
+# 35.0831 -44.1164 3.7933 35.0232 -40.0716 1.5901 1.8645
+22.7233 20.0904 -46.6940 23.0331 14.9730 -42.5619 2.0373
+36.4612 47.8580 18.3852 36.2715 50.5065 21.2231 1.4146
+90.8027 -2.0831 1.4410 91.1528 -1.6435 0.0447 1.4441
+90.9257 -0.5406 -0.9208 88.6381 -0.8985 -0.7239 1.5381
+6.7747 -0.2908 -2.4247 5.8714 -0.0985 -2.2286 0.6377
+2.0776 0.0795 -1.1350 0.9033 -0.0636 -0.5514 0.9082
+}
+
+set index -1
+foreach sample [split $lab_diff_samples \n] {
+ incr index
+ set lab1 [lrange $sample 0 2]
+ if { [lindex $lab1 0] == "#" || $lab1 == "" } continue
+ set lab2 [lrange $sample 3 5]
+ set dref [lindex $sample 6]
+ set diff [vcolordiff {*}[vcolorconvert from lab {*}$lab1] {*}[vcolorconvert from lab {*}$lab2]]
+
+ # use tolerance 1e-3 except if other value is defined in sample data
+ set tol [lindex $sample 7]
+ if { $tol == "" } { set tol 1e-3 }
+
+ checkreal "Sample $index: Lab ($lab1) and ($lab2), diff" $diff $dref $tol 1e-6
+}
--- /dev/null
+# Check conversion of RGB colors to CIE Lab color space
+
+# Samples are obtained (with Ref. White D65, Gamma = 1 for linear RGB) using
+# http://brucelindbloom.com/index.html?ColorCalculator.html
+set rgb_to_lab_samples {
+ { # black, white, 50% gray }
+ { {0 0 0} {0 0 0} }
+ { {1 1 1} {100 0 0} }
+ { {0.5 0.5 0.5} {76.0693 0 0} }
+
+ { # pure colors }
+ { {1 0 0} {53.2408 80.0925 67.2032} }
+ { {0 1 0} {87.7347 -86.1827 83.1793} }
+ { {0 0 1} {32.2970 79.1875 -107.8602} }
+ { {0 1 1} {91.1132 -48.0875 -14.1312} }
+ { {1 1 0} {97.1393 -21.5537 94.4780} }
+ { {1 0 1} {60.3242 98.2343 -60.8249} }
+
+ { # shades of pure red }
+ { {0.1 0 0} {16.1387 37.1756 25.0600} }
+ { {0.3 0 0} {30.3521 53.6166 44.0349} }
+ { {0.5 0 0} {38.9565 63.5695 53.3392} }
+ { {0.7 0 0} {45.4792 71.1144 59.6700} }
+ { {0.9 0 0} {50.8512 77.3285 64.8840} }
+
+ { # random colors }
+ { {0.3 0.5 0.9} {75.2228 0.7560 -31.8425} }
+}
+
+foreach sample $rgb_to_lab_samples {
+ set rgb [lindex $sample 0]
+ if { $rgb == "#" } continue
+
+ set ref [lindex $sample 1]
+ set lab [vcolorconvert to lab {*}$rgb]
+ check3reals "RGB ($rgb) to Lab" {*}$lab {*}$ref 1e-4
+}
--- /dev/null
+# Check conversion of RGB colors to CIE Lch color space
+
+# Samples are obtained (with Ref. White D65, Gamma = 1 for linear RGB) using
+# http://brucelindbloom.com/index.html?ColorCalculator.html
+# Note that for c = 0 we have h = 0 (not 270 as in the above link)
+set rgb_to_lch_samples {
+ { # black, white, 50% gray }
+ { {0 0 0} {0 0 0} }
+ { {1 1 1} {100 0 0} }
+ { {0.5 0.5 0.5} {76.0693 0 0} }
+
+ { # pure colors }
+ { {1 0 0} {53.2408 104.5518 39.9990} }
+ { {0 1 0} {87.7347 119.7759 136.0160} }
+ { {0 0 1} {32.2970 133.8076 306.2849} }
+ { {0 1 1} {91.1132 50.1209 196.3762} }
+ { {1 1 0} {97.1393 96.9054 102.8512} }
+ { {1 0 1} {60.3242 115.5407 328.2350} }
+
+ { # shades of pure red }
+ { {0.1 0 0} {16.1387 44.8334 33.9838} }
+ { {0.3 0 0} {30.3521 69.3816 39.3960} }
+ { {0.5 0 0} {38.9565 82.9828 39.9990} }
+ { {0.7 0 0} {45.4792 92.8320 39.9990} }
+ { {0.9 0 0} {50.8512 100.9436 39.9990} }
+
+ { # random colors }
+ { {0.3 0.5 0.9} {75.2228 31.8514 271.3601} }
+}
+
+foreach sample $rgb_to_lch_samples {
+ set rgb [lindex $sample 0]
+ if { $rgb == "#" } continue
+
+ set ref [lindex $sample 1]
+ set lch [vcolorconvert to lch {*}$rgb]
+ check3reals "RGB ($rgb) to Lch" {*}$lch {*}$ref 1e-4
+}
--- /dev/null
+# Check stability of conversion of RGB colors to CIE Lab and Lch
+# color spaces and back on random colors
+
+# check color diff on random colors
+for {set i 1} {$i < 1000} {incr i} {
+ set rgb "[expr rand()] [expr rand()] [expr rand()]"
+
+ set lab [vcolorconvert to lab {*}$rgb]
+ set lch [vcolorconvert to lch {*}$rgb]
+
+ set rgb_lab [vcolorconvert from lab {*}$lab]
+ set rgb_lch [vcolorconvert from lch {*}$lch]
+
+ check3reals "RGB ($rgb) to Lab and back" {*}$rgb_lab {*}$rgb 1e-4
+ check3reals "RGB ($rgb) to Lch and back" {*}$rgb_lch {*}$rgb 1e-4
+}
021 dimensions
022 transparency
023 viewcube
+024 colors