KiCad PCB EDA Suite
color4d.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KICAD, a free EDA CAD application.
3  *
4  * Copyright 2012 Torsten Hueter, torstenhtr <at> gmx.de
5  * Copyright 2017-2021 Kicad Developers, see AUTHORS.txt for contributors.
6  *
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  */
25 
26 #include <map>
27 #include <nlohmann/json.hpp>
28 #include <gal/color4d.h>
29 #include <i18n_utility.h>
30 #include <wx/crt.h>
31 #include <math/util.h>
32 
33 using namespace KIGFX;
34 
35 #define TS( string ) wxString( _HKI( string ) ).ToStdString()
36 
37 // We can't have this as a plain static variable, because it is referenced during the initialization
38 // of other static variables, so we must initialize it explicitly on first use.
40 {
41  static StructColors s_ColorRefs[NBCOLORS] =
42  {
43  { 0, 0, 0, BLACK, TS( "Black" ), DARKDARKGRAY },
44  { 72, 72, 72, DARKDARKGRAY, TS( "Gray 1" ), DARKGRAY },
45  { 132, 132, 132, DARKGRAY, TS( "Gray 2" ), LIGHTGRAY },
46  { 194, 194, 194, LIGHTGRAY, TS( "Gray 3" ), WHITE },
47  { 255, 255, 255, WHITE, TS( "White" ), WHITE },
48  { 194, 255, 255, LIGHTYELLOW, TS( "L.Yellow" ), WHITE },
49  { 191, 229, 255, LIGHTERORANGE, TS( "L.Orange" ), WHITE },
50  { 72, 0, 0, DARKBLUE, TS( "Blue 1" ), BLUE },
51  { 0, 72, 0, DARKGREEN, TS( "Green 1" ), GREEN },
52  { 72, 72, 0, DARKCYAN, TS( "Cyan 1" ), CYAN },
53  { 0, 0, 72, DARKRED, TS( "Red 1" ), RED },
54  { 72, 0, 72, DARKMAGENTA, TS( "Magenta 1" ), MAGENTA },
55  { 0, 72, 72, DARKBROWN, TS( "Brown 1" ), BROWN },
56  { 0, 77, 128, DARKORANGE, TS( "Orange 1" ), ORANGE },
57  { 132, 0, 0, BLUE, TS( "Blue 2" ), LIGHTBLUE },
58  { 0, 132, 0, GREEN, TS( "Green 2" ), LIGHTGREEN },
59  { 132, 132, 0, CYAN, TS( "Cyan 2" ), LIGHTCYAN },
60  { 0, 0, 132, RED, TS( "Red 2" ), LIGHTRED },
61  { 132, 0, 132, MAGENTA, TS( "Magenta 2" ), LIGHTMAGENTA },
62  { 0, 132, 132, BROWN, TS( "Brown 2" ), YELLOW },
63  { 0, 102, 204, ORANGE, TS( "Orange 2" ), LIGHTORANGE },
64  { 194, 0, 0, LIGHTBLUE, TS( "Blue 3" ), PUREBLUE, },
65  { 0, 194, 0, LIGHTGREEN, TS( "Green 3" ), PUREGREEN },
66  { 194, 194, 0, LIGHTCYAN, TS( "Cyan 3" ), PURECYAN },
67  { 0, 0, 194, LIGHTRED, TS( "Red 3" ), PURERED },
68  { 194, 0, 194, LIGHTMAGENTA, TS( "Magenta 3" ), PUREMAGENTA },
69  { 0, 194, 194, YELLOW, TS( "Yellow 3" ), PUREYELLOW },
70  { 0, 133, 221, LIGHTORANGE, TS( "Orange 3" ), PUREORANGE },
71  { 255, 0, 0, PUREBLUE, TS( "Blue 4" ), WHITE },
72  { 0, 255, 0, PUREGREEN, TS( "Green 4" ), WHITE },
73  { 255, 255, 0, PURECYAN, TS( "Cyan 4" ), WHITE },
74  { 0, 0, 255, PURERED, TS( "Red 4" ), WHITE },
75  { 255, 0, 255, PUREMAGENTA, TS( "Magenta 4" ), WHITE },
76  { 0, 255, 255, PUREYELLOW, TS( "Yellow 4" ), WHITE },
77  { 0, 153, 255, PUREORANGE, TS( "Orange 4" ), WHITE },
78  };
79  return s_ColorRefs;
80 }
81 
82 
84 {
85  if( aColor <= UNSPECIFIED_COLOR || aColor >= NBCOLORS )
86  {
87  *this = COLOR4D::UNSPECIFIED;
88  return;
89  }
90 
91  int candidate = 0;
92 
93  for( ; candidate < NBCOLORS; ++candidate )
94  {
95  if( colorRefs()[candidate].m_Numcolor == aColor )
96  break;
97  }
98 
99  if( candidate >= NBCOLORS )
100  {
101  *this = COLOR4D::UNSPECIFIED;
102  return;
103  }
104 
105  r = colorRefs()[candidate].m_Red / 255.0;
106  g = colorRefs()[candidate].m_Green / 255.0;
107  b = colorRefs()[candidate].m_Blue / 255.0;
108  a = 1.0;
109 }
110 
111 
112 #ifdef WX_COMPATIBILITY
113 COLOR4D::COLOR4D( const wxColour& aColor )
114 {
115  r = aColor.Red() / 255.0;
116  g = aColor.Green() / 255.0;
117  b = aColor.Blue() / 255.0;
118  a = aColor.Alpha() / 255.0;
119 }
120 
121 
122 bool COLOR4D::SetFromWxString( const wxString& aColorString )
123 {
124  wxColour c;
125 
126  if( c.Set( aColorString ) )
127  {
128  r = c.Red() / 255.0;
129  g = c.Green() / 255.0;
130  b = c.Blue() / 255.0;
131  a = c.Alpha() / 255.0;
132 
133  return true;
134  }
135 
136  return false;
137 }
138 
139 
140 wxString COLOR4D::ToWxString( long flags ) const
141 {
142  wxColour c = ToColour();
143  return c.GetAsString( flags );
144 }
145 
146 
147 bool COLOR4D::SetFromHexString( const wxString& aColorString )
148 {
149  wxString str = aColorString;
150  str.Trim( true );
151  str.Trim( false );
152 
153  if( str.length() < 7 || str.GetChar( 0 ) != '#' )
154  return false;
155 
156  unsigned long tmp;
157 
158  if( wxSscanf( str.wx_str() + 1, wxT( "%lx" ), &tmp ) != 1 )
159  return false;
160 
161  if( str.length() >= 9 )
162  {
163  r = ( (tmp >> 24) & 0xFF ) / 255.0;
164  g = ( (tmp >> 16) & 0xFF ) / 255.0;
165  b = ( (tmp >> 8) & 0xFF ) / 255.0;
166  a = ( tmp & 0xFF ) / 255.0;
167  }
168  else
169  {
170  r = ( (tmp >> 16) & 0xFF ) / 255.0;
171  g = ( (tmp >> 8) & 0xFF ) / 255.0;
172  b = ( tmp & 0xFF ) / 255.0;
173  a = 1.0;
174  }
175 
176  return true;
177 }
178 
179 
180 wxString COLOR4D::ToHexString() const
181 {
182  return wxString::Format( wxT("#%02X%02X%02X%02X" ),
183  KiROUND( r * 255.0 ),
184  KiROUND( g * 255.0 ),
185  KiROUND( b * 255.0 ),
186  KiROUND( a * 255.0 ) );
187 }
188 
189 
190 wxColour COLOR4D::ToColour() const
191 {
192  using CHAN_T = wxColourBase::ChannelType;
193 
194  const wxColour colour(
195  static_cast<CHAN_T>( r * 255 + 0.5 ), static_cast<CHAN_T>( g * 255 + 0.5 ),
196  static_cast<CHAN_T>( b * 255 + 0.5 ), static_cast<CHAN_T>( a * 255 + 0.5 ) );
197  return colour;
198 }
199 
200 
201 COLOR4D COLOR4D::LegacyMix( const COLOR4D& aColor ) const
202 {
203  COLOR4D candidate;
204 
205  // Blend the two colors (i.e. OR the RGB values)
206  candidate.r = ( (unsigned) ( 255.0 * r ) | (unsigned) ( 255.0 * aColor.r ) ) / 255.0,
207  candidate.g = ( (unsigned) ( 255.0 * g ) | (unsigned) ( 255.0 * aColor.g ) ) / 255.0,
208  candidate.b = ( (unsigned) ( 255.0 * b ) | (unsigned) ( 255.0 * aColor.b ) ) / 255.0,
209 
210  // the alpha channel can be reinitialized but what is the best value?
211  candidate.a = ( aColor.a + a ) / 2;
212 
213  return candidate;
214 }
215 
216 
217 unsigned int COLOR4D::ToU32() const
218 {
219  return ToColour().GetRGB();
220 }
221 
222 
223 void COLOR4D::FromU32( unsigned int aPackedColor )
224 {
225  wxColour c;
226  c.SetRGB( aPackedColor );
227  r = c.Red() / 255.0;
228  g = c.Green() / 255.0;
229  b = c.Blue() / 255.0;
230  a = c.Alpha() / 255.0;
231 }
232 
233 #endif
234 
235 
236 namespace KIGFX {
237 
238 const bool operator==( const COLOR4D& lhs, const COLOR4D& rhs )
239 {
240  return lhs.a == rhs.a && lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b;
241 }
242 
243 
244 const bool operator!=( const COLOR4D& lhs, const COLOR4D& rhs )
245 {
246  return !( lhs == rhs );
247 }
248 
249 
250 const bool operator<( const COLOR4D& lhs, const COLOR4D& rhs )
251 {
252  if( lhs.r < rhs.r )
253  return true;
254  else if( lhs.g < rhs.g )
255  return true;
256  else if( lhs.b < rhs.b )
257  return true;
258  else if( lhs.a < rhs.a )
259  return true;
260 
261  return false;
262 }
263 
264 
265 std::ostream &operator<<( std::ostream &aStream, COLOR4D const &aColor )
266 {
267  return aStream << aColor.ToWxString( wxC2S_CSS_SYNTAX );
268 }
269 
270 
271 void to_json( nlohmann::json& aJson, const COLOR4D& aColor )
272 {
273  aJson = nlohmann::json( aColor.ToWxString( wxC2S_CSS_SYNTAX ).ToStdString() );
274 }
275 
276 
277 void from_json( const nlohmann::json& aJson, COLOR4D& aColor )
278 {
279  aColor.SetFromWxString( aJson.get<std::string>() );
280 }
281 
282 }
283 
284 
285 void COLOR4D::ToHSL( double& aOutHue, double& aOutSaturation, double& aOutLightness ) const
286 {
287  auto min = std::min( r, std::min( g, b ) );
288  auto max = std::max( r, std::max( g, b ) );
289  auto diff = max - min;
290 
291  aOutLightness = ( max + min ) / 2.0;
292 
293  if( aOutLightness >= 1.0 )
294  aOutSaturation = 0.0;
295  else
296  aOutSaturation = diff / ( 1.0 - std::abs( 2.0 * aOutLightness - 1.0 ) );
297 
298  double hue;
299 
300  if( diff <= 0.0 )
301  hue = 0.0;
302  else if( max == r )
303  hue = ( g - b ) / diff;
304  else if( max == g )
305  hue = ( b - r ) / diff + 2.0;
306  else
307  hue = ( r - g ) / diff + 4.0;
308 
309  aOutHue = hue > 0.0 ? hue * 60.0 : hue * 60.0 + 360.0;
310 
311  while( aOutHue < 0.0 )
312  aOutHue += 360.0;
313 }
314 
315 
316 void COLOR4D::FromHSL( double aInHue, double aInSaturation, double aInLightness )
317 {
318  const auto P = ( 1.0 - std::abs( 2.0 * aInLightness - 1.0 ) ) * aInSaturation;
319  const auto scaled_hue = aInHue / 60.0;
320  const auto Q = P * ( 1.0 - std::abs( std::fmod( scaled_hue, 2.0 ) - 1.0 ) );
321 
322  r = g = b = aInLightness - P / 2.0;
323 
324  if (scaled_hue < 1.0)
325  {
326  r += P;
327  g += Q;
328  }
329  else if (scaled_hue < 2.0)
330  {
331  r += Q;
332  g += P;
333  }
334  else if (scaled_hue < 3.0)
335  {
336  g += P;
337  b += Q;
338  }
339  else if (scaled_hue < 4.0)
340  {
341  g += Q;
342  b += P;
343  }
344  else if (scaled_hue < 5.0)
345  {
346  r += Q;
347  b += P;
348  }
349  else
350  {
351  r += P;
352  b += Q;
353  }
354 }
355 
356 
357 void COLOR4D::ToHSV( double& aOutHue, double& aOutSaturation, double& aOutValue,
358  bool aAlwaysDefineHue ) const
359 {
360  double min, max, delta;
361 
362  min = r < g ? r : g;
363  min = min < b ? min : b;
364 
365  max = r > g ? r : g;
366  max = max > b ? max : b;
367 
368  aOutValue = max; // value
369  delta = max - min;
370 
371  if( max > 0.0 )
372  {
373  aOutSaturation = ( delta / max );
374  }
375  else // for black color (r = g = b = 0 ) saturation is set to 0.
376  {
377  aOutSaturation = 0.0;
378  aOutHue = aAlwaysDefineHue ? 0.0 : NAN;
379  return;
380  }
381 
382  /* Hue in degrees (0...360) is coded according to this table
383  * 0 or 360 : red
384  * 60 : yellow
385  * 120 : green
386  * 180 : cyan
387  * 240 : blue
388  * 300 : magenta
389  */
390  if( delta != 0.0 )
391  {
392  if( r >= max )
393  aOutHue = ( g - b ) / delta; // between yellow & magenta
394  else if( g >= max )
395  aOutHue = 2.0 + ( b - r ) / delta; // between cyan & yellow
396  else
397  aOutHue = 4.0 + ( r - g ) / delta; // between magenta & cyan
398 
399  aOutHue *= 60.0; // degrees
400 
401  if( aOutHue < 0.0 )
402  aOutHue += 360.0;
403  }
404  else // delta = 0 means r = g = b. hue is set to 0.0
405  {
406  aOutHue = aAlwaysDefineHue ? 0.0 : NAN;
407  }
408 }
409 
410 
411 void COLOR4D::FromHSV( double aInH, double aInS, double aInV )
412 {
413  if( aInS <= 0.0 )
414  {
415  r = aInV;
416  g = aInV;
417  b = aInV;
418  return;
419  }
420 
421  double hh = aInH;
422 
423  while( hh >= 360.0 )
424  hh -= 360.0;
425 
426  /* Hue in degrees (0...360) is coded according to this table
427  * 0 or 360 : red
428  * 60 : yellow
429  * 120 : green
430  * 180 : cyan
431  * 240 : blue
432  * 300 : magenta
433  */
434  hh /= 60.0;
435 
436  int i = (int) hh;
437  double ff = hh - i;
438 
439  double p = aInV * ( 1.0 - aInS );
440  double q = aInV * ( 1.0 - ( aInS * ff ) );
441  double t = aInV * ( 1.0 - ( aInS * ( 1.0 - ff ) ) );
442 
443  switch( i )
444  {
445  case 0:
446  r = aInV;
447  g = t;
448  b = p;
449  break;
450 
451  case 1:
452  r = q;
453  g = aInV;
454  b = p;
455  break;
456 
457  case 2:
458  r = p;
459  g = aInV;
460  b = t;
461  break;
462 
463  case 3:
464  r = p;
465  g = q;
466  b = aInV;
467  break;
468 
469  case 4:
470  r = t;
471  g = p;
472  b = aInV;
473  break;
474 
475  case 5:
476  default:
477  r = aInV;
478  g = p;
479  b = q;
480  break;
481  }
482 }
483 
484 
485 COLOR4D& COLOR4D::Saturate( double aFactor )
486 {
487  // One can saturate a color only when r, v, b are not equal
488  if( r == g && r == b )
489  return *this;
490 
491  double h, s, v;
492 
493  ToHSV( h, s, v, true );
494  FromHSV( h, aFactor, 1.0 );
495 
496  return *this;
497 }
498 
499 
500 const COLOR4D COLOR4D::UNSPECIFIED( 0, 0, 0, 0 );
501 const COLOR4D COLOR4D::WHITE( 1, 1, 1, 1 );
502 const COLOR4D COLOR4D::BLACK( 0, 0, 0, 1 );
503 const COLOR4D COLOR4D::CLEAR( 1, 0, 1, 0 );
504 
505 
506 int COLOR4D::Distance( const COLOR4D& other ) const
507 {
508  return ( r - other.r ) * ( r - other.r )
509  + ( g - other.g ) * ( g - other.g )
510  + ( b - other.b ) * ( b - other.b );
511 }
512 
513 
515 {
516  EDA_COLOR_T candidate = EDA_COLOR_T::BLACK;
517 
518  /* Find the 'nearest' color in the palette. This is fun. There is
519  a gazilion of metrics for the color space and no one of the
520  useful one is in the RGB color space. Who cares, this is a CAD,
521  not a photosomething...
522 
523  I hereby declare that the distance is the sum of the square of the
524  component difference. Think about the RGB color cube. Now get the
525  euclidean distance, but without the square root... for ordering
526  purposes it's the same, obviously. Also each component can't be
527  less of the target one, since I found this currently work better...
528  */
529  int nearest_distance = 255 * 255 * 3 + 1; // Can't beat this
530 
531  for( EDA_COLOR_T trying = EDA_COLOR_T::BLACK; trying < EDA_COLOR_T::NBCOLORS;
532  trying = static_cast<EDA_COLOR_T>( int( trying ) + 1 ) )
533  {
534  const StructColors &c = colorRefs()[trying];
535  int distance = ( aR - c.m_Red ) * ( aR - c.m_Red ) +
536  ( aG - c.m_Green ) * ( aG - c.m_Green ) +
537  ( aB - c.m_Blue ) * ( aB - c.m_Blue );
538 
539  if( distance < nearest_distance && c.m_Red >= aR &&
540  c.m_Green >= aG && c.m_Blue >= aB )
541  {
542  nearest_distance = distance;
543  candidate = trying;
544  }
545  }
546 
547  return candidate;
548 }
549 
550 
551 COLOR4D& COLOR4D::FromCSSRGBA( int aRed, int aGreen, int aBlue, double aAlpha )
552 {
553  r = std::max( 0, std::min( 255, aRed ) ) / 255.0;
554  g = std::max( 0, std::min( 255, aGreen ) ) / 255.0;
555  b = std::max( 0, std::min( 255, aBlue ) ) / 255.0;
556  a = std::max( 0.0, std::min( 1.0, aAlpha ) );
557 
558  return *this;
559 }
void ToHSV(double &aOutHue, double &aOutSaturation, double &aOutValue, bool aAlwaysDefineHue=false) const
Convert current color (stored in RGB) to HSV format.
Definition: color4d.cpp:357
COLOR4D & FromCSSRGBA(int aRed, int aGreen, int aBlue, double aAlpha=1.0)
Initialize the color from a RGBA value with 0-255 red/green/blue and 0-1 alpha.
Definition: color4d.cpp:551
unsigned char m_Red
Definition: color4d.h:88
The Cairo implementation of the graphics abstraction layer.
Definition: color4d.cpp:236
COLOR4D & Saturate(double aFactor)
Saturates the color to a given factor (in HSV model)
Definition: color4d.cpp:485
unsigned char m_Green
Definition: color4d.h:87
Definition: color4d.h:61
static const COLOR4D BLACK
Definition: color4d.h:387
double g
Green component.
Definition: color4d.h:378
Definition: color4d.h:44
void from_json(const nlohmann::json &aJson, COLOR4D &aColor)
Definition: color4d.cpp:277
nlohmann::json json
Definition: gerbview.cpp:41
const bool operator<(const COLOR4D &lhs, const COLOR4D &rhs)
Definition: color4d.cpp:250
double b
Blue component.
Definition: color4d.h:379
Number of colors.
Definition: color4d.h:79
Definition: color4d.h:76
Definition: color4d.h:67
double a
Alpha component.
Definition: color4d.h:380
static const COLOR4D CLEAR
Definition: color4d.h:388
Definition: color4d.h:57
unsigned char m_Blue
Definition: color4d.h:86
Definition: color4d.h:58
const StructColors * colorRefs()
Global list of legacy color names, still used all over the place for constructing COLOR4D's.
Definition: color4d.cpp:39
int Distance(const COLOR4D &other) const
Returns the distance (in RGB space) between two colors.
Definition: color4d.cpp:506
static const COLOR4D WHITE
Definition: color4d.h:386
void ToHSL(double &aOutHue, double &aOutSaturation, double &aOutValue) const
Converts current color (stored in RGB) to HSL format.
Definition: color4d.cpp:285
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
Definition: color4d.h:59
std::ostream & operator<<(std::ostream &aStream, COLOR4D const &aColor)
Syntactic sugar for outputting colors to strings.
Definition: color4d.cpp:265
void FromHSV(double aInH, double aInS, double aInV)
Changes currently used color to the one given by hue, saturation and value parameters.
Definition: color4d.cpp:411
EDA_COLOR_T
Legacy color enumeration.
Definition: color4d.h:41
void to_json(nlohmann::json &aJson, const COLOR4D &aColor)
Definition: color4d.cpp:271
Some functions to handle hotkeys in KiCad.
Definition: color4d.h:48
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
Definition: color4d.h:56
const bool operator==(const COLOR4D &lhs, const COLOR4D &rhs)
Equality operator, are two colors equal.
Definition: color4d.cpp:238
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition: color4d.h:383
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:73
static EDA_COLOR_T FindNearestLegacyColor(int aR, int aG, int aB)
Returns a legacy color ID that is closest to the given 8-bit RGB values.
Definition: color4d.cpp:514
constexpr int delta
double r
Red component.
Definition: color4d.h:377
const bool operator!=(const COLOR4D &lhs, const COLOR4D &rhs)
Not equality operator, are two colors not equal.
Definition: color4d.cpp:244
void FromHSL(double aInHue, double aInSaturation, double aInLightness)
Change currently used color to the one given by hue, saturation and lightness parameters.
Definition: color4d.cpp:316
#define TS(string)
Definition: color4d.cpp:35
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:103