0030969: Coding Rules - refactor Quantity_Color.cxx color table definition
[occt.git] / src / Quantity / Quantity_Color.cxx
1 // Created by: NW,JPB,CAL
2 // Copyright (c) 1991-1999 Matra Datavision
3 // Copyright (c) 1999-2014 OPEN CASCADE SAS
4 //
5 // This file is part of Open CASCADE Technology software library.
6 //
7 // This library is free software; you can redistribute it and/or modify it under
8 // the terms of the GNU Lesser General Public License version 2.1 as published
9 // by the Free Software Foundation, with special exception defined in the file
10 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
11 // distribution for complete text of the license and disclaimer of any warranty.
12 //
13 // Alternatively, this file may be used under the terms of Open CASCADE
14 // commercial license or contractual agreement.
15
16 #include <Quantity_Color.hxx>
17
18 #include <Quantity_ColorRGBA.hxx>
19 #include <Standard_ErrorHandler.hxx>
20 #include <Standard_OutOfRange.hxx>
21 #include <Standard_Dump.hxx>
22 #include <TCollection_AsciiString.hxx>
23
24 #include <string.h>
25
26 #define RGBHLS_H_UNDEFINED -1.0
27
28 static Standard_Real TheEpsilon = 0.0001;
29
30 // Throw exception if RGB values are out of range.
31 #define Quantity_ColorValidateRgbRange(theR, theG, theB) \
32   if (theR < 0.0 || theR > 1.0 \
33    || theG < 0.0 || theG > 1.0 \
34    || theB < 0.0 || theB > 1.0) { throw Standard_OutOfRange("Color out"); }
35
36 // Throw exception if HLS values are out of range.
37 #define Quantity_ColorValidateHlsRange(theH, theL, theS) \
38   if ((theH < 0.0 && theH != RGBHLS_H_UNDEFINED && theS != 0.0) \
39    || (theH > 360.0) \
40     || theL < 0.0 || theL > 1.0 \
41     || theS < 0.0 || theS > 1.0) { throw Standard_OutOfRange("Color out"); }
42
43 namespace
44 {
45   //! Raw color for defining list of standard color
46   struct Quantity_StandardColor
47   {
48     const char*             StringName;
49     NCollection_Vec3<float> RgbValues;
50     Quantity_NameOfColor    EnumName;
51
52     Quantity_StandardColor (Quantity_NameOfColor theName, const char* theStringName, const NCollection_Vec3<float>& theVec3)
53     : StringName (theStringName), RgbValues (theVec3), EnumName (theName) {}
54   };
55 }
56
57 // Note that HTML/hex sRGB representation is ignored
58 #define RawColor(theName, theHex, theR, theG, theB) Quantity_StandardColor(Quantity_NOC_##theName, #theName, NCollection_Vec3<float>(theR##f, theG##f, theB##f))
59
60 //! Name list of standard materials (defined within enumeration).
61 static const Quantity_StandardColor THE_COLORS[] =
62 {
63 #include "Quantity_ColorTable.pxx"
64 };
65
66 // =======================================================================
67 // function : Epsilon
68 // purpose  :
69 // =======================================================================
70 Standard_Real Quantity_Color::Epsilon()
71 {
72   return TheEpsilon;
73 }
74
75 // =======================================================================
76 // function : SetEpsilon
77 // purpose  :
78 // =======================================================================
79 void Quantity_Color::SetEpsilon (const Standard_Real theEpsilon)
80 {
81   TheEpsilon = theEpsilon;
82 }
83
84 // =======================================================================
85 // function : valuesOf
86 // purpose  :
87 // =======================================================================
88 NCollection_Vec3<float> Quantity_Color::valuesOf (const Quantity_NameOfColor theName,
89                                                   const Quantity_TypeOfColor theType)
90 {
91   if ((Standard_Integer )theName < 0 || (Standard_Integer )theName > Quantity_NOC_WHITE)
92   {
93     throw Standard_OutOfRange("Bad name");
94   }
95
96   const NCollection_Vec3<float>& anRgb = THE_COLORS[theName].RgbValues;
97   switch (theType)
98   {
99     case Quantity_TOC_RGB: return anRgb;
100     case Quantity_TOC_HLS: return Convert_sRGB_To_HLS (anRgb);
101   }
102   throw Standard_ProgramError("Internal error");
103 }
104
105 // =======================================================================
106 // function : StringName
107 // purpose  :
108 // =======================================================================
109 Standard_CString Quantity_Color::StringName (const Quantity_NameOfColor theName)
110 {
111   if ((Standard_Integer )theName < 0 || (Standard_Integer )theName > Quantity_NOC_WHITE)
112   {
113     throw Standard_OutOfRange("Bad name");
114   }
115   return THE_COLORS[theName].StringName;
116 }
117
118 // =======================================================================
119 // function : ColorFromName
120 // purpose  :
121 // =======================================================================
122 Standard_Boolean Quantity_Color::ColorFromName (const Standard_CString theName,
123                                                 Quantity_NameOfColor&  theColor)
124 {
125   TCollection_AsciiString aName (theName);
126   aName.UpperCase();
127   if (aName.Search("QUANTITY_NOC_") == 1)
128   {
129     aName = aName.SubString (14, aName.Length());
130   }
131
132   for (Standard_Integer anIter = Quantity_NOC_BLACK; anIter <= Quantity_NOC_WHITE; ++anIter)
133   {
134     Standard_CString aColorName = THE_COLORS[anIter].StringName;
135     if (aName == aColorName)
136     {
137       theColor = (Quantity_NameOfColor )anIter;
138       return Standard_True;
139     }
140   }
141
142   // aliases
143   if      (aName == "BLUE1")       { theColor = Quantity_NOC_BLUE1; }
144   else if (aName == "CHARTREUSE1") { theColor = Quantity_NOC_CHARTREUSE1; }
145   else if (aName == "CYAN1")       { theColor = Quantity_NOC_CYAN1; }
146   else if (aName == "GOLD1")       { theColor = Quantity_NOC_GOLD1; }
147   else if (aName == "GREEN1")      { theColor = Quantity_NOC_GREEN1; }
148   else if (aName == "LIGHTCYAN1")  { theColor = Quantity_NOC_LIGHTCYAN1; }
149   else if (aName == "MAGENTA1")    { theColor = Quantity_NOC_MAGENTA1; }
150   else if (aName == "ORANGE1")     { theColor = Quantity_NOC_ORANGE1; }
151   else if (aName == "ORANGERED1")  { theColor = Quantity_NOC_ORANGERED1; }
152   else if (aName == "RED1")        { theColor = Quantity_NOC_RED1; }
153   else if (aName == "TOMATO1")     { theColor = Quantity_NOC_TOMATO1; }
154   else if (aName == "YELLOW1")     { theColor = Quantity_NOC_YELLOW1; }
155   else
156   {
157     return Standard_False;
158   }
159
160   return Standard_True;
161 }
162
163 //=======================================================================
164 // function : ColorFromHex
165 // purpose  :
166 //=======================================================================
167 bool Quantity_Color::ColorFromHex (const Standard_CString theHexColorString,
168                                    Quantity_Color& theColor)
169 {
170   Quantity_ColorRGBA aColorRGBA;
171   if (!Quantity_ColorRGBA::ColorFromHex (theHexColorString, aColorRGBA, true))
172   {
173     return false;
174   }
175   theColor = aColorRGBA.GetRGB();
176   return true;
177 }
178
179 // =======================================================================
180 // function : Quantity_Color
181 // purpose  :
182 // =======================================================================
183 Quantity_Color::Quantity_Color (const Standard_Real theR1, const Standard_Real theR2, const Standard_Real theR3,
184                                 const Quantity_TypeOfColor theType)
185 {
186   switch (theType)
187   {
188     case Quantity_TOC_RGB:
189     {
190       Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
191       myRgb.SetValues (float(theR1), float(theR2), float(theR3));
192       break;
193     }
194     case Quantity_TOC_HLS:
195     {
196       Quantity_ColorValidateHlsRange(theR1, theR2, theR3);
197       myRgb = Convert_HLS_To_sRGB (NCollection_Vec3<float> (float(theR1), float(theR2), float(theR3)));
198       break;
199     }
200   }
201 }
202
203 // =======================================================================
204 // function : Quantity_Color
205 // purpose  :
206 // =======================================================================
207 Quantity_Color::Quantity_Color (const NCollection_Vec3<float>& theRgb)
208 : myRgb (theRgb)
209 {
210   Quantity_ColorValidateRgbRange(theRgb.r(), theRgb.g(), theRgb.b());
211 }
212
213 // =======================================================================
214 // function : ChangeContrast
215 // purpose  :
216 // =======================================================================
217 void Quantity_Color::ChangeContrast (const Standard_Real theDelta)
218 {
219   NCollection_Vec3<float> aHls = Convert_sRGB_To_HLS (myRgb);
220   aHls[2] += aHls[2] * Standard_ShortReal (theDelta) / 100.0f; // saturation
221   if (!((aHls[2] > 1.0f) || (aHls[2] < 0.0f)))
222   {
223     myRgb = Convert_HLS_To_sRGB (aHls);
224   }
225 }
226
227 // =======================================================================
228 // function : ChangeIntensity
229 // purpose  :
230 // =======================================================================
231 void Quantity_Color::ChangeIntensity (const Standard_Real theDelta)
232 {
233   NCollection_Vec3<float> aHls = Convert_sRGB_To_HLS (myRgb);
234   aHls[1] += aHls[1] * Standard_ShortReal (theDelta) / 100.0f; // light
235   if (!((aHls[1] > 1.0f) || (aHls[1] < 0.0f)))
236   {
237     myRgb = Convert_HLS_To_sRGB (aHls);
238   }
239 }
240
241 // =======================================================================
242 // function : SetValues
243 // purpose  :
244 // =======================================================================
245 void Quantity_Color::SetValues (const Standard_Real theR1, const Standard_Real theR2, const Standard_Real theR3,
246                                 const Quantity_TypeOfColor theType)
247 {
248   switch (theType)
249   {
250     case Quantity_TOC_RGB:
251     {
252       Quantity_ColorValidateRgbRange(theR1, theR2, theR3);
253       myRgb.SetValues (float(theR1), float(theR2), float(theR3));
254       break;
255     }
256     case Quantity_TOC_HLS:
257     {
258       Quantity_ColorValidateHlsRange(theR1, theR2, theR3);
259       myRgb = Convert_HLS_To_sRGB (NCollection_Vec3<float> (float(theR1), float(theR2), float(theR3)));
260       break;
261     }
262   }
263 }
264
265 // =======================================================================
266 // function : Delta
267 // purpose  :
268 // =======================================================================
269 void Quantity_Color::Delta (const Quantity_Color& theColor,
270                             Standard_Real& theDC,
271                             Standard_Real& theDI) const
272 {
273   const NCollection_Vec3<float> aHls1 = Convert_sRGB_To_HLS (myRgb);
274   const NCollection_Vec3<float> aHls2 = Convert_sRGB_To_HLS (theColor.myRgb);
275   theDC = Standard_Real (aHls1[2] - aHls2[2]); // saturation
276   theDI = Standard_Real (aHls1[1] - aHls2[1]); // light
277 }
278
279 // =======================================================================
280 // function : Name
281 // purpose  :
282 // =======================================================================
283 Quantity_NameOfColor Quantity_Color::Name() const
284 {
285   Standard_ShortReal aDist2 = 4.0f;
286   Quantity_NameOfColor aResName = Quantity_NOC_BLACK;
287   for (Standard_Integer aColIter = Quantity_NOC_BLACK; aColIter <= Quantity_NOC_WHITE; ++aColIter)
288   {
289     const Standard_ShortReal aNewDist2 = (myRgb - THE_COLORS[aColIter].RgbValues).SquareModulus();
290     if (aNewDist2 < aDist2)
291     {
292       aResName = Quantity_NameOfColor (aColIter);
293       aDist2 = aNewDist2;
294       if (aNewDist2 == 0.0f)
295       {
296         break;
297       }
298     }
299   }
300   return aResName;
301 }
302
303 // =======================================================================
304 // function : Values
305 // purpose  :
306 // =======================================================================
307 void Quantity_Color::Values (Standard_Real& theR1, Standard_Real& theR2, Standard_Real& theR3,
308                              const Quantity_TypeOfColor theType) const
309 {
310   switch (theType)
311   {
312     case Quantity_TOC_RGB:
313     {
314       theR1 = myRgb.r();
315       theR2 = myRgb.g();
316       theR3 = myRgb.b();
317       break;
318     }
319     case Quantity_TOC_HLS:
320     {
321       const NCollection_Vec3<float> aHls = Convert_sRGB_To_HLS (myRgb);
322       theR1 = aHls[0];
323       theR2 = aHls[1];
324       theR3 = aHls[2];
325       break;
326     }
327   }
328 }
329
330 // =======================================================================
331 // function : Convert_HLS_To_sRGB
332 // purpose  : Reference: La synthese d'images, Collection Hermes
333 // =======================================================================
334 NCollection_Vec3<float> Quantity_Color::Convert_HLS_To_sRGB (const NCollection_Vec3<float>& theHls)
335 {
336   float aHue = theHls[0];
337   const float aLight = theHls[1];
338   const float aSaturation = theHls[2];
339   if (aSaturation == 0.0f
340    && aHue == RGBHLS_H_UNDEFINED)
341   {
342     return NCollection_Vec3<float> (aLight, aLight, aLight);
343   }
344
345   int aHueIndex = 0;
346   float lmuls = aLight * aSaturation;
347   if (aHue == 360.0f)
348   {
349     aHue = 0.0;
350     aHueIndex = 0;
351   }
352   else
353   {
354     aHue /= 60.0f;
355     aHueIndex = (int )aHue;
356   }
357
358   switch (aHueIndex)
359   {
360     case 0: return NCollection_Vec3<float> (aLight,
361                                             aLight - lmuls + lmuls * aHue,
362                                             aLight - lmuls);
363     case 1: return NCollection_Vec3<float> (aLight + lmuls - lmuls * aHue,
364                                             aLight,
365                                             aLight - lmuls);
366     case 2: return NCollection_Vec3<float> (aLight - lmuls,
367                                             aLight,
368                                             aLight - 3 * lmuls + lmuls * aHue);
369     case 3: return NCollection_Vec3<float> (aLight - lmuls,
370                                             aLight + 3 * lmuls - lmuls * aHue,
371                                             aLight);
372     case 4: return NCollection_Vec3<float> (aLight - 5 * lmuls + lmuls * aHue,
373                                             aLight - lmuls,
374                                             aLight);
375     case 5 : return NCollection_Vec3<float> (aLight,
376                                              aLight - lmuls,
377                                              aLight + 5 * lmuls - lmuls * aHue);
378   }
379   throw Standard_OutOfRange("Color out");
380 }
381
382 // =======================================================================
383 // function : Convert_sRGB_To_HLS
384 // purpose  : Reference: La synthese d'images, Collection Hermes
385 // =======================================================================
386 NCollection_Vec3<float> Quantity_Color::Convert_sRGB_To_HLS (const NCollection_Vec3<float>& theRgb)
387 {
388   float aPlus = 0.0f;
389   float aDiff = theRgb.g() - theRgb.b();
390
391   // compute maximum from RGB components, which will be a luminance
392   float aMax = theRgb.r();
393   if (theRgb.g() > aMax) { aPlus = 2.0; aDiff = theRgb.b() - theRgb.r(); aMax = theRgb.g(); }
394   if (theRgb.b() > aMax) { aPlus = 4.0; aDiff = theRgb.r() - theRgb.g(); aMax = theRgb.b(); }
395
396   // compute minimum from RGB components
397   float min = theRgb.r();
398   if (theRgb.g() < min) min = theRgb.g();
399   if (theRgb.b() < min) min = theRgb.b();
400
401   const float aDelta = aMax - min;
402
403   // compute saturation
404   float aSaturation = 0.0f;
405   if (aMax != 0.0f) aSaturation = aDelta / aMax;
406
407   // compute hue
408   float aHue = RGBHLS_H_UNDEFINED;
409   if (aSaturation != 0.0f)
410   {
411     aHue = 60.0f * (aPlus + aDiff / aDelta);
412     if (aHue < 0.0f) aHue += 360.0f;
413   }
414   return NCollection_Vec3<float> (aHue, aMax, aSaturation);
415 }
416
417 ///////////////////////////////////////////////////////////////////////////////
418 //////////////////////////////////// TESTS ////////////////////////////////////
419 ///////////////////////////////////////////////////////////////////////////////
420 static void TestOfColor()
421 {
422   Standard_Real H, L, S;
423   Standard_Real R, G, B;
424   Standard_Real DC, DI;
425   Standard_Integer i;
426
427   std::cout << "definition color tests\n----------------------\n";
428
429   Quantity_Color C1;
430   Quantity_Color C2 (Quantity_NOC_ROYALBLUE2);
431   Quantity_Color C3 (Quantity_NOC_SANDYBROWN);
432
433   // An Introduction to Standard_Object-Oriented Programming and C++ p43
434   // a comment for the "const char *const" declaration
435   const char *const cyan = "YELLOW";
436   const char *const blue = "ROYALBLUE2";
437   const char *const brown = "SANDYBROWN";
438
439   Standard_Real RR, GG, BB;
440
441   const Standard_Real DELTA = 1.0e-4;
442
443   std::cout << "Get values and names of color tests\n-----------------------------------\n";
444
445   C1.Values (R, G, B, Quantity_TOC_RGB);
446   if ((R!=1.0) || (G!=1.0) || (B!=0.0))
447   {
448     std::cout << "TEST_ERROR : Values () bad default color\n";
449     std::cout << "R, G, B values: " << R << " " << G << " " << B << "\n";
450   }
451   if ( (C1.Red ()!=1.0) || (C1.Green ()!=1.0) || (C1.Blue ()!=0.0) )
452   {
453     std::cout << "TEST_ERROR : Values () bad default color\n";
454     std::cout << "R, G, B values: " << C1.Red () << " " << C1.Green () << " " << C1.Blue () << "\n";
455   }
456   if (strcmp (Quantity_Color::StringName (C1.Name()), cyan) != 0)
457   {
458     std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C1.Name()) <<  " != YELLOW\n";
459   }
460
461   RR=0.262745; GG=0.431373; BB=0.933333;
462   C1.SetValues (RR, GG, BB, Quantity_TOC_RGB);
463   C2.Values (R, G, B, Quantity_TOC_RGB);
464   if ((Abs (RR-R) > DELTA)
465    || (Abs (GG-G) > DELTA)
466    || (Abs (BB-B) > DELTA))
467   {
468     std::cout << "TEST_ERROR : Values () bad default color\n";
469     std::cout << "R, G, B values: " << R << " " << G << " " << B << "\n";
470   }
471
472   if (C2 != C1)
473   {
474     std::cout << "TEST_ERROR : IsDifferent ()\n";
475   }
476   if (C3 == C1)
477   {
478     std::cout << "TEST_ERROR : IsEqual ()\n";
479   }
480
481   std::cout << "Distance C1,C2 " << C1.Distance (C2) << "\n";
482   std::cout << "Distance C1,C3 " << C1.Distance (C3) << "\n";
483   std::cout << "Distance C2,C3 " << C2.Distance (C3) << "\n";
484   std::cout << "SquareDistance C1,C2 " << C1.SquareDistance (C2) << "\n";
485   std::cout << "SquareDistance C1,C3 " << C1.SquareDistance (C3) << "\n";
486   std::cout << "SquareDistance C2,C3 " << C2.SquareDistance (C3) << "\n";
487
488   if (strcmp (Quantity_Color::StringName (C2.Name()), blue) != 0)
489   {
490     std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C2.Name()) <<  " != ROYALBLUE2\n";
491   }
492
493   std::cout << "conversion rgbhls tests\n-----------------------\n";
494   Quantity_Color::RgbHls (R, G, B, H, L, S);
495   Quantity_Color::HlsRgb (H, L, S, R, G, B);
496   RR=0.262745; GG=0.431373; BB=0.933333;
497   if ((Abs (RR-R) > DELTA)
498    || (Abs (GG-G) > DELTA)
499    || (Abs (BB-B) > DELTA))
500   {
501     std::cout << "TEST_ERROR : RgbHls or HlsRgb bad conversion\n";
502     std::cout << "RGB init : " << RR << " " << GG << " " << BB << "\n";
503     std::cout << "RGB values : " << R << " " << G << " " << B << "\n";
504     std::cout << "Difference RGB : " << RR-R << " " << GG-G << " " << BB-B << "\n";
505   }
506
507   std::cout << "distance tests\n--------------\n";
508   R = (float ) 0.9568631; G = (float ) 0.6431371; B = (float ) 0.3764711;
509   C2.SetValues (R, G, B, Quantity_TOC_RGB);
510   if (C2.Distance (C3) > DELTA)
511   {
512     std::cout << "TEST_ERROR : Distance () bad result\n";
513     std::cout << "Distance C2 and C3 : " << C2.Distance (C3) << "\n";
514   }
515
516   C2.Delta (C3, DC, DI);
517   if (Abs (DC) > DELTA)
518   {
519     std::cout << "TEST_ERROR : Delta () bad result for DC\n";
520   }
521   if (Abs (DI) > DELTA)
522   {
523     std::cout << "TEST_ERROR : Delta () bad result for DI\n";
524   }
525
526   std::cout << "name tests\n----------\n";
527   R = (float ) 0.9568631; G = (float ) 0.6431371; B = (float ) 0.3764711;
528   C2.SetValues (R, G, B, Quantity_TOC_RGB);
529   if (strcmp (Quantity_Color::StringName (C2.Name()), brown) != 0)
530   {
531     std::cout << "TEST_ERROR : StringName () " << Quantity_Color::StringName (C2.Name()) <<  " != SANDYBROWN\n";
532   }
533
534   std::cout << "contrast change tests\n---------------------\n";
535   for (i=1; i<=10; i++)
536   {
537     C2.ChangeContrast (10.);
538     C2.ChangeContrast (-9.09090909);
539   }
540   C2.Values (R, G, B, Quantity_TOC_RGB);
541   RR=0.956863; GG=0.6431371; BB=0.3764711;
542   if ((Abs (RR-R) > DELTA)
543    || (Abs (GG-G) > DELTA)
544    || (Abs (BB-B) > DELTA))
545   {
546     std::cout << "TEST_ERROR : ChangeContrast () bad values\n";
547     std::cout << "RGB init : " << RR << " " << GG << " " << BB << "\n";
548     std::cout << "RGB values : " << R << " " << G << " " << B << "\n";
549   }
550 }
551
552 // =======================================================================
553 // function : Test
554 // purpose  :
555 // =======================================================================
556 void Quantity_Color::Test()
557 {
558   try
559   {
560     OCC_CATCH_SIGNALS
561     TestOfColor();
562   }
563   catch (Standard_Failure const& anException)
564   {
565     std::cout << anException << std::endl;
566   }
567 }
568
569 //=======================================================================
570 //function : DumpJson
571 //purpose  : 
572 //=======================================================================
573 void Quantity_Color::DumpJson (Standard_OStream& theOStream, const Standard_Integer) const
574 {
575   OCCT_DUMP_CLASS_BEGIN (theOStream, Quantity_Color);
576   OCCT_DUMP_FIELD_VALUES_NUMERICAL (theOStream, "RGB", 3, myRgb.r(), myRgb.g(), myRgb.b())
577 }