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 wxString& aColorStr )
114 {
115  if( !SetFromHexString( aColorStr ) )
116  SetFromWxString( aColorStr );
117 }
118 
119 
120 COLOR4D::COLOR4D( const wxColour& aColor )
121 {
122  r = aColor.Red() / 255.0;
123  g = aColor.Green() / 255.0;
124  b = aColor.Blue() / 255.0;
125  a = aColor.Alpha() / 255.0;
126 }
127 
128 
129 bool COLOR4D::SetFromWxString( const wxString& aColorString )
130 {
131  wxColour c;
132 
133  if( c.Set( aColorString ) )
134  {
135  r = c.Red() / 255.0;
136  g = c.Green() / 255.0;
137  b = c.Blue() / 255.0;
138  a = c.Alpha() / 255.0;
139 
140  return true;
141  }
142 
143  return false;
144 }
145 
146 
147 wxString COLOR4D::ToWxString( long flags ) const
148 {
149  wxColour c = ToColour();
150  return c.GetAsString( flags );
151 }
152 
153 
154 bool COLOR4D::SetFromHexString( const wxString& aColorString )
155 {
156  wxString str = aColorString;
157  str.Trim( true );
158  str.Trim( false );
159 
160  if( str.length() < 7 || str.GetChar( 0 ) != '#' )
161  return false;
162 
163  unsigned long tmp;
164 
165  if( wxSscanf( str.wx_str() + 1, wxT( "%lx" ), &tmp ) != 1 )
166  return false;
167 
168  if( str.length() >= 9 )
169  {
170  r = ( (tmp >> 24) & 0xFF ) / 255.0;
171  g = ( (tmp >> 16) & 0xFF ) / 255.0;
172  b = ( (tmp >> 8) & 0xFF ) / 255.0;
173  a = ( tmp & 0xFF ) / 255.0;
174  }
175  else
176  {
177  r = ( (tmp >> 16) & 0xFF ) / 255.0;
178  g = ( (tmp >> 8) & 0xFF ) / 255.0;
179  b = ( tmp & 0xFF ) / 255.0;
180  a = 1.0;
181  }
182 
183  return true;
184 }
185 
186 
187 wxString COLOR4D::ToHexString() const
188 {
189  return wxString::Format( wxT("#%02X%02X%02X%02X" ),
190  KiROUND( r * 255.0 ),
191  KiROUND( g * 255.0 ),
192  KiROUND( b * 255.0 ),
193  KiROUND( a * 255.0 ) );
194 }
195 
196 
197 wxColour COLOR4D::ToColour() const
198 {
199  using CHAN_T = wxColourBase::ChannelType;
200 
201  const wxColour colour(
202  static_cast<CHAN_T>( r * 255 + 0.5 ), static_cast<CHAN_T>( g * 255 + 0.5 ),
203  static_cast<CHAN_T>( b * 255 + 0.5 ), static_cast<CHAN_T>( a * 255 + 0.5 ) );
204  return colour;
205 }
206 
207 #endif
208 
209 
210 COLOR4D COLOR4D::LegacyMix( const COLOR4D& aColor ) const
211 {
212  COLOR4D candidate;
213 
214  // Blend the two colors (i.e. OR the RGB values)
215  candidate.r = ( (unsigned) ( 255.0 * r ) | (unsigned) ( 255.0 * aColor.r ) ) / 255.0,
216  candidate.g = ( (unsigned) ( 255.0 * g ) | (unsigned) ( 255.0 * aColor.g ) ) / 255.0,
217  candidate.b = ( (unsigned) ( 255.0 * b ) | (unsigned) ( 255.0 * aColor.b ) ) / 255.0,
218 
219  // the alpha channel can be reinitialized but what is the best value?
220  candidate.a = ( aColor.a + a ) / 2;
221 
222  return candidate;
223 }
224 
225 
226 unsigned int COLOR4D::ToU32() const
227 {
228  return ToColour().GetRGB();
229 }
230 
231 
232 void COLOR4D::FromU32( unsigned int aPackedColor )
233 {
234  wxColour c;
235  c.SetRGB( aPackedColor );
236  r = c.Red() / 255.0;
237  g = c.Green() / 255.0;
238  b = c.Blue() / 255.0;
239  a = c.Alpha() / 255.0;
240 }
241 
242 
243 namespace KIGFX {
244 
245 const bool operator==( const COLOR4D& lhs, const COLOR4D& rhs )
246 {
247  return lhs.a == rhs.a && lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b;
248 }
249 
250 
251 const bool operator!=( const COLOR4D& lhs, const COLOR4D& rhs )
252 {
253  return !( lhs == rhs );
254 }
255 
256 
257 const bool operator<( const COLOR4D& lhs, const COLOR4D& rhs )
258 {
259  if( lhs.r < rhs.r )
260  return true;
261  else if( lhs.g < rhs.g )
262  return true;
263  else if( lhs.b < rhs.b )
264  return true;
265  else if( lhs.a < rhs.a )
266  return true;
267 
268  return false;
269 }
270 
271 
272 std::ostream &operator<<( std::ostream &aStream, COLOR4D const &aColor )
273 {
274  return aStream << aColor.ToWxString( wxC2S_CSS_SYNTAX );
275 }
276 
277 
278 void to_json( nlohmann::json& aJson, const COLOR4D& aColor )
279 {
280  aJson = nlohmann::json( aColor.ToWxString( wxC2S_CSS_SYNTAX ).ToStdString() );
281 }
282 
283 
284 void from_json( const nlohmann::json& aJson, COLOR4D& aColor )
285 {
286  aColor.SetFromWxString( aJson.get<std::string>() );
287 }
288 
289 }
290 
291 
292 void COLOR4D::ToHSL( double& aOutHue, double& aOutSaturation, double& aOutLightness ) const
293 {
294  auto min = std::min( r, std::min( g, b ) );
295  auto max = std::max( r, std::max( g, b ) );
296  auto diff = max - min;
297 
298  aOutLightness = ( max + min ) / 2.0;
299 
300  if( aOutLightness >= 1.0 )
301  aOutSaturation = 0.0;
302  else
303  aOutSaturation = diff / ( 1.0 - std::abs( 2.0 * aOutLightness - 1.0 ) );
304 
305  double hue;
306 
307  if( diff <= 0.0 )
308  hue = 0.0;
309  else if( max == r )
310  hue = ( g - b ) / diff;
311  else if( max == g )
312  hue = ( b - r ) / diff + 2.0;
313  else
314  hue = ( r - g ) / diff + 4.0;
315 
316  aOutHue = hue > 0.0 ? hue * 60.0 : hue * 60.0 + 360.0;
317 
318  while( aOutHue < 0.0 )
319  aOutHue += 360.0;
320 }
321 
322 
323 void COLOR4D::FromHSL( double aInHue, double aInSaturation, double aInLightness )
324 {
325  const auto P = ( 1.0 - std::abs( 2.0 * aInLightness - 1.0 ) ) * aInSaturation;
326  const auto scaled_hue = aInHue / 60.0;
327  const auto Q = P * ( 1.0 - std::abs( std::fmod( scaled_hue, 2.0 ) - 1.0 ) );
328 
329  r = g = b = aInLightness - P / 2.0;
330 
331  if (scaled_hue < 1.0)
332  {
333  r += P;
334  g += Q;
335  }
336  else if (scaled_hue < 2.0)
337  {
338  r += Q;
339  g += P;
340  }
341  else if (scaled_hue < 3.0)
342  {
343  g += P;
344  b += Q;
345  }
346  else if (scaled_hue < 4.0)
347  {
348  g += Q;
349  b += P;
350  }
351  else if (scaled_hue < 5.0)
352  {
353  r += Q;
354  b += P;
355  }
356  else
357  {
358  r += P;
359  b += Q;
360  }
361 }
362 
363 
364 void COLOR4D::ToHSV( double& aOutHue, double& aOutSaturation, double& aOutValue,
365  bool aAlwaysDefineHue ) const
366 {
367  double min, max, delta;
368 
369  min = r < g ? r : g;
370  min = min < b ? min : b;
371 
372  max = r > g ? r : g;
373  max = max > b ? max : b;
374 
375  aOutValue = max; // value
376  delta = max - min;
377 
378  if( max > 0.0 )
379  {
380  aOutSaturation = ( delta / max );
381  }
382  else // for black color (r = g = b = 0 ) saturation is set to 0.
383  {
384  aOutSaturation = 0.0;
385  aOutHue = aAlwaysDefineHue ? 0.0 : NAN;
386  return;
387  }
388 
389  /* Hue in degrees (0...360) is coded according to this table
390  * 0 or 360 : red
391  * 60 : yellow
392  * 120 : green
393  * 180 : cyan
394  * 240 : blue
395  * 300 : magenta
396  */
397  if( delta != 0.0 )
398  {
399  if( r >= max )
400  aOutHue = ( g - b ) / delta; // between yellow & magenta
401  else if( g >= max )
402  aOutHue = 2.0 + ( b - r ) / delta; // between cyan & yellow
403  else
404  aOutHue = 4.0 + ( r - g ) / delta; // between magenta & cyan
405 
406  aOutHue *= 60.0; // degrees
407 
408  if( aOutHue < 0.0 )
409  aOutHue += 360.0;
410  }
411  else // delta = 0 means r = g = b. hue is set to 0.0
412  {
413  aOutHue = aAlwaysDefineHue ? 0.0 : NAN;
414  }
415 }
416 
417 
418 void COLOR4D::FromHSV( double aInH, double aInS, double aInV )
419 {
420  if( aInS <= 0.0 )
421  {
422  r = aInV;
423  g = aInV;
424  b = aInV;
425  return;
426  }
427 
428  double hh = aInH;
429 
430  while( hh >= 360.0 )
431  hh -= 360.0;
432 
433  /* Hue in degrees (0...360) is coded according to this table
434  * 0 or 360 : red
435  * 60 : yellow
436  * 120 : green
437  * 180 : cyan
438  * 240 : blue
439  * 300 : magenta
440  */
441  hh /= 60.0;
442 
443  int i = (int) hh;
444  double ff = hh - i;
445 
446  double p = aInV * ( 1.0 - aInS );
447  double q = aInV * ( 1.0 - ( aInS * ff ) );
448  double t = aInV * ( 1.0 - ( aInS * ( 1.0 - ff ) ) );
449 
450  switch( i )
451  {
452  case 0:
453  r = aInV;
454  g = t;
455  b = p;
456  break;
457 
458  case 1:
459  r = q;
460  g = aInV;
461  b = p;
462  break;
463 
464  case 2:
465  r = p;
466  g = aInV;
467  b = t;
468  break;
469 
470  case 3:
471  r = p;
472  g = q;
473  b = aInV;
474  break;
475 
476  case 4:
477  r = t;
478  g = p;
479  b = aInV;
480  break;
481 
482  case 5:
483  default:
484  r = aInV;
485  g = p;
486  b = q;
487  break;
488  }
489 }
490 
491 
492 COLOR4D& COLOR4D::Saturate( double aFactor )
493 {
494  // One can saturate a color only when r, v, b are not equal
495  if( r == g && r == b )
496  return *this;
497 
498  double h, s, v;
499 
500  ToHSV( h, s, v, true );
501  FromHSV( h, aFactor, 1.0 );
502 
503  return *this;
504 }
505 
506 
507 const COLOR4D COLOR4D::UNSPECIFIED( 0, 0, 0, 0 );
508 const COLOR4D COLOR4D::WHITE( 1, 1, 1, 1 );
509 const COLOR4D COLOR4D::BLACK( 0, 0, 0, 1 );
510 const COLOR4D COLOR4D::CLEAR( 1, 0, 1, 0 );
511 
512 
513 double COLOR4D::Distance( const COLOR4D& other ) const
514 {
515  return ( r - other.r ) * ( r - other.r )
516  + ( g - other.g ) * ( g - other.g )
517  + ( b - other.b ) * ( b - other.b );
518 }
519 
520 
522 {
523  EDA_COLOR_T candidate = EDA_COLOR_T::BLACK;
524 
525  /* Find the 'nearest' color in the palette. This is fun. There is
526  a gazilion of metrics for the color space and no one of the
527  useful one is in the RGB color space. Who cares, this is a CAD,
528  not a photosomething...
529 
530  I hereby declare that the distance is the sum of the square of the
531  component difference. Think about the RGB color cube. Now get the
532  euclidean distance, but without the square root... for ordering
533  purposes it's the same, obviously. Also each component can't be
534  less of the target one, since I found this currently work better...
535  */
536  int nearest_distance = 255 * 255 * 3 + 1; // Can't beat this
537 
538  for( EDA_COLOR_T trying = EDA_COLOR_T::BLACK; trying < EDA_COLOR_T::NBCOLORS;
539  trying = static_cast<EDA_COLOR_T>( int( trying ) + 1 ) )
540  {
541  const StructColors &c = colorRefs()[trying];
542  int distance = ( aR - c.m_Red ) * ( aR - c.m_Red ) +
543  ( aG - c.m_Green ) * ( aG - c.m_Green ) +
544  ( aB - c.m_Blue ) * ( aB - c.m_Blue );
545 
546  if( distance < nearest_distance && c.m_Red >= aR &&
547  c.m_Green >= aG && c.m_Blue >= aB )
548  {
549  nearest_distance = distance;
550  candidate = trying;
551  }
552  }
553 
554  return candidate;
555 }
556 
557 
558 COLOR4D& COLOR4D::FromCSSRGBA( int aRed, int aGreen, int aBlue, double aAlpha )
559 {
560  r = std::max( 0, std::min( 255, aRed ) ) / 255.0;
561  g = std::max( 0, std::min( 255, aGreen ) ) / 255.0;
562  b = std::max( 0, std::min( 255, aBlue ) ) / 255.0;
563  a = std::max( 0.0, std::min( 1.0, aAlpha ) );
564 
565  return *this;
566 }
void ToHSV(double &aOutHue, double &aOutSaturation, double &aOutValue, bool aAlwaysDefineHue=false) const
Convert current color (stored in RGB) to HSV format.
Definition: color4d.cpp:364
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:558
unsigned char m_Red
Definition: color4d.h:88
The Cairo implementation of the graphics abstraction layer.
Definition: color4d.cpp:243
COLOR4D & Saturate(double aFactor)
Saturates the color to a given factor (in HSV model)
Definition: color4d.cpp:492
unsigned char m_Green
Definition: color4d.h:87
Definition: color4d.h:61
static const COLOR4D BLACK
Definition: color4d.h:394
double g
Green component.
Definition: color4d.h:385
Definition: color4d.h:44
void from_json(const nlohmann::json &aJson, COLOR4D &aColor)
Definition: color4d.cpp:284
nlohmann::json json
Definition: gerbview.cpp:41
const bool operator<(const COLOR4D &lhs, const COLOR4D &rhs)
Definition: color4d.cpp:257
double b
Blue component.
Definition: color4d.h:386
Number of colors.
Definition: color4d.h:79
Definition: color4d.h:76
Definition: color4d.h:67
double a
Alpha component.
Definition: color4d.h:387
static const COLOR4D CLEAR
Definition: color4d.h:395
Definition: color4d.h:57
unsigned char m_Blue
Definition: color4d.h:86
Definition: color4d.h:58
double Distance(const COLOR4D &other) const
Returns the distance (in RGB space) between two colors.
Definition: color4d.cpp:513
const StructColors * colorRefs()
Global list of legacy color names, still used all over the place for constructing COLOR4D's.
Definition: color4d.cpp:39
static const COLOR4D WHITE
Definition: color4d.h:393
void ToHSL(double &aOutHue, double &aOutSaturation, double &aOutValue) const
Converts current color (stored in RGB) to HSL format.
Definition: color4d.cpp:292
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:272
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:418
EDA_COLOR_T
Legacy color enumeration.
Definition: color4d.h:41
void to_json(nlohmann::json &aJson, const COLOR4D &aColor)
Definition: color4d.cpp:278
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:245
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition: color4d.h:390
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:521
constexpr int delta
double r
Red component.
Definition: color4d.h:384
const bool operator!=(const COLOR4D &lhs, const COLOR4D &rhs)
Not equality operator, are two colors not equal.
Definition: color4d.cpp:251
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:323
#define TS(string)
Definition: color4d.cpp:35
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:103