// Created by: NW,JPB,CAL // Copyright (c) 1991-1999 Matra Datavision // Copyright (c) 1999-2014 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #include #include #include #include #include #include #include #define RGBHLS_H_UNDEFINED -1.0 static Standard_Real TheEpsilon = 0.0001; // Throw exception if RGB values are out of range. #define Quantity_ColorValidateRgbRange(theR, theG, theB) \ if (theR < 0.0 || theR > 1.0 \ || theG < 0.0 || theG > 1.0 \ || theB < 0.0 || theB > 1.0) { throw Standard_OutOfRange("Color out"); } // Throw exception if HLS values are out of range. #define Quantity_ColorValidateHlsRange(theH, theL, theS) \ if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) \ || (theH > 360.0) \ || 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 struct Quantity_StandardColor { const char* StringName; NCollection_Vec3 sRgbValues; NCollection_Vec3 RgbValues; Quantity_NameOfColor EnumName; Quantity_StandardColor (Quantity_NameOfColor theName, const char* theStringName, const NCollection_Vec3& thesRGB, const NCollection_Vec3& theRGB) : StringName (theStringName), sRgbValues (thesRGB), RgbValues (theRGB), EnumName (theName) {} }; } // Note that HTML/hex sRGB representation is ignored #define RawColor(theName, theHex, SRGB, sR, sG, sB, RGB, theR, theG, theB) \ Quantity_StandardColor(Quantity_NOC_##theName, #theName, NCollection_Vec3(sR##f, sG##f, sB##f), NCollection_Vec3(theR##f, theG##f, theB##f)) //! Name list of standard materials (defined within enumeration). static const Quantity_StandardColor THE_COLORS[] = { #include "Quantity_ColorTable.pxx" }; // ======================================================================= // function : Epsilon // purpose : // ======================================================================= Standard_Real Quantity_Color::Epsilon() { return TheEpsilon; } // ======================================================================= // function : SetEpsilon // purpose : // ======================================================================= void Quantity_Color::SetEpsilon (const Standard_Real theEpsilon) { TheEpsilon = theEpsilon; } // ======================================================================= // function : valuesOf // purpose : // ======================================================================= NCollection_Vec3 Quantity_Color::valuesOf (const Quantity_NameOfColor theName, const Quantity_TypeOfColor theType) { if ((Standard_Integer )theName < 0 || (Standard_Integer )theName > Quantity_NOC_WHITE) { throw Standard_OutOfRange("Bad name"); } const NCollection_Vec3& anRgb = THE_COLORS[theName].RgbValues; switch (theType) { 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 : StringName // purpose : // ======================================================================= Standard_CString Quantity_Color::StringName (const Quantity_NameOfColor theName) { if ((Standard_Integer )theName < 0 || (Standard_Integer )theName > Quantity_NOC_WHITE) { throw Standard_OutOfRange("Bad name"); } return THE_COLORS[theName].StringName; } // ======================================================================= // function : ColorFromName // purpose : // ======================================================================= Standard_Boolean Quantity_Color::ColorFromName (const Standard_CString theName, Quantity_NameOfColor& theColor) { TCollection_AsciiString aName (theName); aName.UpperCase(); if (aName.Search("QUANTITY_NOC_") == 1) { aName = aName.SubString (14, aName.Length()); } for (Standard_Integer anIter = Quantity_NOC_BLACK; anIter <= Quantity_NOC_WHITE; ++anIter) { Standard_CString aColorName = THE_COLORS[anIter].StringName; if (aName == aColorName) { theColor = (Quantity_NameOfColor )anIter; return Standard_True; } } // aliases if (aName == "BLUE1") { theColor = Quantity_NOC_BLUE1; } else if (aName == "CHARTREUSE1") { theColor = Quantity_NOC_CHARTREUSE1; } else if (aName == "CYAN1") { theColor = Quantity_NOC_CYAN1; } else if (aName == "GOLD1") { theColor = Quantity_NOC_GOLD1; } else if (aName == "GREEN1") { theColor = Quantity_NOC_GREEN1; } else if (aName == "LIGHTCYAN1") { theColor = Quantity_NOC_LIGHTCYAN1; } else if (aName == "MAGENTA1") { theColor = Quantity_NOC_MAGENTA1; } else if (aName == "ORANGE1") { theColor = Quantity_NOC_ORANGE1; } else if (aName == "ORANGERED1") { theColor = Quantity_NOC_ORANGERED1; } else if (aName == "RED1") { theColor = Quantity_NOC_RED1; } else if (aName == "TOMATO1") { theColor = Quantity_NOC_TOMATO1; } else if (aName == "YELLOW1") { theColor = Quantity_NOC_YELLOW1; } else { return Standard_False; } return Standard_True; } //======================================================================= // function : ColorFromHex // purpose : //======================================================================= bool Quantity_Color::ColorFromHex (const Standard_CString theHexColorString, Quantity_Color& theColor) { Quantity_ColorRGBA aColorRGBA; if (!Quantity_ColorRGBA::ColorFromHex (theHexColorString, aColorRGBA, true)) { return false; } theColor = aColorRGBA.GetRGB(); return true; } // ======================================================================= // function : Quantity_Color // purpose : // ======================================================================= Quantity_Color::Quantity_Color (const Standard_Real theC1, const Standard_Real theC2, const Standard_Real theC3, const Quantity_TypeOfColor theType) { SetValues (theC1, theC2, theC3, theType); } // ======================================================================= // function : Quantity_Color // purpose : // ======================================================================= Quantity_Color::Quantity_Color (const NCollection_Vec3& theRgb) : myRgb (theRgb) { Quantity_ColorValidateRgbRange(theRgb.r(), theRgb.g(), theRgb.b()); } // ======================================================================= // function : ChangeContrast // purpose : // ======================================================================= void Quantity_Color::ChangeContrast (const Standard_Real theDelta) { NCollection_Vec3 aHls = Convert_LinearRGB_To_HLS (myRgb); aHls[2] += aHls[2] * Standard_ShortReal (theDelta) / 100.0f; // saturation if (!((aHls[2] > 1.0f) || (aHls[2] < 0.0f))) { myRgb = Convert_HLS_To_LinearRGB (aHls); } } // ======================================================================= // function : ChangeIntensity // purpose : // ======================================================================= void Quantity_Color::ChangeIntensity (const Standard_Real theDelta) { NCollection_Vec3 aHls = Convert_LinearRGB_To_HLS (myRgb); aHls[1] += aHls[1] * Standard_ShortReal (theDelta) / 100.0f; // light if (!((aHls[1] > 1.0f) || (aHls[1] < 0.0f))) { myRgb = Convert_HLS_To_LinearRGB (aHls); } } // ======================================================================= // function : SetValues // purpose : // ======================================================================= 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(theC1, theC2, theC3); myRgb.SetValues (float(theC1), float(theC2), float(theC3)); break; } case Quantity_TOC_sRGB: { 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(theC1, theC2, theC3); myRgb = Convert_HLS_To_LinearRGB (NCollection_Vec3 (float(theC1), float(theC2), float(theC3))); break; } case Quantity_TOC_CIELab: { Quantity_ColorValidateLabRange(theC1, theC2, theC3); myRgb = Convert_Lab_To_LinearRGB (NCollection_Vec3 (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(theC1), float(theC2), float(theC3)))); break; } } } // ======================================================================= // function : Delta // purpose : // ======================================================================= void Quantity_Color::Delta (const Quantity_Color& theColor, Standard_Real& theDC, Standard_Real& theDI) const { const NCollection_Vec3 aHls1 = Convert_LinearRGB_To_HLS (myRgb); const NCollection_Vec3 aHls2 = Convert_LinearRGB_To_HLS (theColor.myRgb); theDC = Standard_Real (aHls1[2] - aHls2[2]); // saturation 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 : // ======================================================================= Quantity_NameOfColor Quantity_Color::Name() const { // it is better finding closest sRGB color (closest to human eye) instead of linear RGB color, // as enumeration defines color names for human const NCollection_Vec3 ansRgbVec (Convert_LinearRGB_To_sRGB (NCollection_Vec3 (myRgb))); Standard_ShortReal aDist2 = ShortRealLast(); Quantity_NameOfColor aResName = Quantity_NOC_BLACK; for (Standard_Integer aColIter = Quantity_NOC_BLACK; aColIter <= Quantity_NOC_WHITE; ++aColIter) { const Standard_ShortReal aNewDist2 = (ansRgbVec - THE_COLORS[aColIter].sRgbValues).SquareModulus(); if (aNewDist2 < aDist2) { aResName = Quantity_NameOfColor (aColIter); aDist2 = aNewDist2; if (aNewDist2 == 0.0f) { break; } } } return aResName; } // ======================================================================= // function : Values // purpose : // ======================================================================= void Quantity_Color::Values (Standard_Real& theR1, Standard_Real& theR2, Standard_Real& theR3, const Quantity_TypeOfColor theType) const { switch (theType) { case Quantity_TOC_RGB: { theR1 = myRgb.r(); theR2 = myRgb.g(); theR3 = myRgb.b(); break; } case Quantity_TOC_sRGB: { theR1 = Convert_LinearRGB_To_sRGB ((Standard_Real )myRgb.r()); theR2 = Convert_LinearRGB_To_sRGB ((Standard_Real )myRgb.g()); theR3 = Convert_LinearRGB_To_sRGB ((Standard_Real )myRgb.b()); break; } case Quantity_TOC_HLS: { const NCollection_Vec3 aHls = Convert_LinearRGB_To_HLS (myRgb); theR1 = aHls[0]; theR2 = aHls[1]; theR3 = aHls[2]; break; } case Quantity_TOC_CIELab: { const NCollection_Vec3 aLab = Convert_LinearRGB_To_Lab (myRgb); theR1 = aLab[0]; theR2 = aLab[1]; theR3 = aLab[2]; break; } case Quantity_TOC_CIELch: { const NCollection_Vec3 aLch = Convert_Lab_To_Lch (Convert_LinearRGB_To_Lab (myRgb)); theR1 = aLch[0]; theR2 = aLch[1]; theR3 = aLch[2]; break; } } } // ======================================================================= // function : Convert_HLS_To_sRGB // purpose : Reference: La synthese d'images, Collection Hermes // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_HLS_To_sRGB (const NCollection_Vec3& theHls) { float aHue = theHls[0]; const float aLight = theHls[1]; const float aSaturation = theHls[2]; if (aSaturation == 0.0f && aHue == RGBHLS_H_UNDEFINED) { return NCollection_Vec3 (aLight, aLight, aLight); } int aHueIndex = 0; float lmuls = aLight * aSaturation; if (aHue == 360.0f) { aHue = 0.0; aHueIndex = 0; } else { aHue /= 60.0f; aHueIndex = (int )aHue; } switch (aHueIndex) { case 0: return NCollection_Vec3 (aLight, aLight - lmuls + lmuls * aHue, aLight - lmuls); case 1: return NCollection_Vec3 (aLight + lmuls - lmuls * aHue, aLight, aLight - lmuls); case 2: return NCollection_Vec3 (aLight - lmuls, aLight, aLight - 3 * lmuls + lmuls * aHue); case 3: return NCollection_Vec3 (aLight - lmuls, aLight + 3 * lmuls - lmuls * aHue, aLight); case 4: return NCollection_Vec3 (aLight - 5 * lmuls + lmuls * aHue, aLight - lmuls, aLight); case 5 : return NCollection_Vec3 (aLight, aLight - lmuls, aLight + 5 * lmuls - lmuls * aHue); } throw Standard_OutOfRange("Color out"); } // ======================================================================= // function : Convert_sRGB_To_HLS // purpose : Reference: La synthese d'images, Collection Hermes // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_sRGB_To_HLS (const NCollection_Vec3& theRgb) { float aPlus = 0.0f; float aDiff = theRgb.g() - theRgb.b(); // compute maximum from RGB components, which will be a luminance float aMax = theRgb.r(); if (theRgb.g() > aMax) { aPlus = 2.0; aDiff = theRgb.b() - theRgb.r(); aMax = theRgb.g(); } if (theRgb.b() > aMax) { aPlus = 4.0; aDiff = theRgb.r() - theRgb.g(); aMax = theRgb.b(); } // compute minimum from RGB components float min = theRgb.r(); if (theRgb.g() < min) min = theRgb.g(); if (theRgb.b() < min) min = theRgb.b(); const float aDelta = aMax - min; // compute saturation float aSaturation = 0.0f; if (aMax != 0.0f) aSaturation = aDelta / aMax; // compute hue float aHue = RGBHLS_H_UNDEFINED; if (aSaturation != 0.0f) { aHue = 60.0f * (aPlus + aDiff / aDelta); if (aHue < 0.0f) aHue += 360.0f; } return NCollection_Vec3 (aHue, aMax, aSaturation); } // ======================================================================= // 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) { return theValue > 0.008856451679035631 ? Pow (theValue, 1./3.) : (7.787037037037037 * theValue) + 16. / 116.; } // ======================================================================= // 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; } // ======================================================================= // function : Convert_LinearRGB_To_Lab // purpose : convert RGB color to CIE Lab color // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_LinearRGB_To_Lab (const NCollection_Vec3& 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)aL, (float)aa, (float)ab); } // ======================================================================= // function : Convert_Lab_To_LinearRGB // purpose : convert CIE Lab color to RGB // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_Lab_To_LinearRGB (const NCollection_Vec3& 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)aR, (float)aG, (float)aB); } } } // ======================================================================= // function : Convert_Lab_To_Lch // purpose : convert CIE Lab color to CIE Lch color // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_Lab_To_Lch (const NCollection_Vec3& theLab) { double aa = theLab[1]; double ab = theLab[2]; double aC = Sqrt (aa * aa + ab * ab); double aH = (aC > TheEpsilon ? ATan2 (ab, aa) * 180. / M_PI : 0.); if (aH < 0.) aH += 360.; return NCollection_Vec3 (theLab[0], (float)aC, (float)aH); } // ======================================================================= // function : Convert_Lch_To_Lab // purpose : convert CIE Lch color to CIE Lab color // see https://www.easyrgb.com/en/math.php // ======================================================================= NCollection_Vec3 Quantity_Color::Convert_Lch_To_Lab (const NCollection_Vec3& theLch) { 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 (theLch[0], (float)aa, (float)ab); } //======================================================================= //function : DumpJson //purpose : //======================================================================= void Quantity_Color::DumpJson (Standard_OStream& theOStream, Standard_Integer) const { OCCT_DUMP_FIELD_VALUES_NUMERICAL (theOStream, "RGB", 3, myRgb.r(), myRgb.g(), myRgb.b()) }