Euphoria
rgb.cc
Go to the documentation of this file.
1 #include "base/rgb.h"
2 
3 #include <iostream>
4 #include <map>
5 #include <iomanip>
6 
8 #include "base/numeric.h"
9 #include "base/stringutils.h"
10 #include "base/range.h"
11 #include "base/stringmerger.h"
12 
13 
14 namespace eu
15 {
16  Rgb::Rgb(float red, float green, float blue) : r(red), g(green), b(blue) {}
17 
18  Rgb::Rgb(float gray) : r(gray), g(gray), b(gray) {}
19 
20  Rgb::Rgb(NamedColor color) : Rgb(Rgb::from_hex(colorutil::to_color_hex(color))) {}
21 
22  Rgb
23  Rgb::from_hex(unsigned int hex)
24  {
25  const auto b = colorutil::to_float(colorutil::get_blue(hex));
26  const auto g = colorutil::to_float(colorutil::get_green(hex));
27  const auto r = colorutil::to_float(colorutil::get_red(hex));
28  return {r, g, b};
29  }
30 
31 
32  float
34  {
35  return sqrt(get_length_squared());
36  }
37 
38  float
40  {
41  return square(r) + square(g) + square(b);
42  }
43 
44 
45  float
47  {
48  // 0.3 0.59 0.11?
49  return 0.2126f*r + 0.7152f*b + 0.0722f*b;
50  }
51 
52  void
53  Rgb::operator+=(const Rgb& rhs)
54  {
55  r += rhs.r;
56  g += rhs.g;
57  b += rhs.b;
58  }
59 
60 
61  void
62  Rgb::operator-=(const Rgb& rhs)
63  {
64  r -= rhs.r;
65  g -= rhs.g;
66  b -= rhs.b;
67  }
68 
69 
70  void
71  Rgb::operator*=(const Rgb& rhs)
72  {
73  r *= rhs.r;
74  g *= rhs.g;
75  b *= rhs.b;
76  }
77 
78 
79  void
80  Rgb::operator/=(float rhs)
81  {
82  r /= rhs;
83  g /= rhs;
84  b /= rhs;
85  }
86 
87 
88  Rgb
89  operator+(const Rgb& lhs, const Rgb& rhs)
90  {
91  auto r = lhs;
92  r += rhs;
93  return r;
94  }
95 
96  Rgb
97  operator-(const Rgb& lhs, const Rgb& rhs)
98  {
99  auto r = lhs;
100  r -= rhs;
101  return r;
102  }
103 
104  Rgb
105  operator*(const Rgb& lhs, const Rgb& rhs)
106  {
107  auto r = lhs;
108  r *= rhs;
109  return r;
110  }
111 
112  Rgb
113  operator/(const Rgb& lhs, float rhs)
114  {
115  auto r = lhs;
116  r /= rhs;
117  return r;
118  }
119 
120 
121  Rgb
122  operator*(const Rgb& lhs, float rhs)
123  {
124  return {lhs.r*rhs, lhs.g*rhs, lhs.b*rhs};
125  }
126 
127 
128  Rgb
129  operator*(float lhs, const Rgb& rhs)
130  {
131  return {rhs.r*lhs, rhs.g*lhs, rhs.b*lhs};
132  }
133 
134 
135 
136 
137  float
138  dot(const Rgb& lhs, const Rgb& rhs)
139  {
140  return lhs.r * rhs.r + lhs.g * rhs.g + lhs.b * rhs.b;
141  }
142 
143  Rgb
144  clamp(const Rgb& c)
145  {
146  return
147  {
148  keep_within(r01, c.r),
149  keep_within(r01, c.g),
150  keep_within(r01, c.b)
151  };
152  }
153 
154 
156 
157  Rgba::Rgba(const Rgb& c, float alpha)
158  : r(c.r), g(c.g), b(c.b), a(alpha)
159  {
160  }
161 
162 
163  Rgba::Rgba(float red, float green, float blue, float alpha)
164  : r(red), g(green), b(blue), a(alpha)
165  {
166  }
167 
169 
170  std::string to_string(const Rgbi& c)
171  {
172  return fmt::format("#{:0>2x}{:0>2x}{:0>2x}", c.r, c.g, c.b);
173  }
174 
175  std::string to_string(const Rgbai& c)
176  {
177  return fmt::format("({}, {}, {}, {})", c.r, c.g, c.b, c.a);
178  }
179 
180  std::string to_string(const Rgb& v)
181  {
182  return fmt::format("({}, {}, {})", v.r, v.g, v.b);
183  }
184 
185  std::string to_string(const Rgba& v)
186  {
187  return fmt::format("({}, {}, {}, {})", v.r, v.g, v.b, v.a);
188  }
189 
190  std::string to_string(const Hsl& v)
191  {
192  return fmt::format("({:.0f}°, {:.0f}%, {:.0f}%)", v.h.as_degrees(), v.s * 100, v.l * 100);
193  }
194 
195 
197  // Default compare
198 
199  bool
200  operator==(const Rgbi& lhs, const Rgbi& rhs)
201  {
202  return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b;
203  }
204 
205 
206  bool
207  operator!=(const Rgbi& lhs, const Rgbi& rhs)
208  {
209  return !(lhs == rhs);
210  }
211 
212 
213  bool
214  operator==(const Rgbai& lhs, const Rgbai& rhs)
215  {
216  return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a;
217  }
218 
219 
220  bool
221  operator!=(const Rgbai& lhs, const Rgbai& rhs)
222  {
223  return !(lhs == rhs);
224  }
225 
227 
228  Rgb
229  to_rgb(const Rgbi& c)
230  {
231  return
232  {
233  colorutil::to_float(c.r),
234  colorutil::to_float(c.g),
236  };
237  }
238 
239  Rgb
240  to_rgb(const Rgba& c)
241  {
242  return {c.r, c.g, c.b};
243  }
244 
245  Rgb
246  to_rgb(const Rgbai& c)
247  {
248  return
249  {
250  colorutil::to_float(c.r),
251  colorutil::to_float(c.g),
253  };
254  }
255 
256  Rgb
257  to_rgb(const Hsl& hsl)
258  {
259  // based on https://gist.github.com/mjackson/5311256
260  if(hsl.s == 0)
261  {
262  return Rgb {hsl.l}; // achromatic
263  }
264  else
265  {
266  auto hue2rgb = [](float p, float q, float t)
267  {
268  if(t < 0.0f) { t += 1.0f; }
269  if(t > 1.0f) { t -= 1.0f; }
270 
271  if(t < 1.0f / 6.0f) { return p + (q - p) * 6.0f * t; }
272  else if(t < 1.0f / 2.0f) { return q; }
273  else if(t < 2.0f / 3.0f) { return p + (q - p) * (2.0f / 3.0f - t) * 6.0f; }
274  else { return p; }
275  };
276 
277  const auto q = hsl.l < 0.5f ? hsl.l * (1.0f + hsl.s)
278  : hsl.l + hsl.s - hsl.l * hsl.s;
279  const auto p = 2.0f * hsl.l - q;
280 
281  const auto r = hue2rgb(p, q, hsl.h.from_percent_of_360() + 1.0f / 3.0f);
282  const auto g = hue2rgb(p, q, hsl.h.from_percent_of_360());
283  const auto b = hue2rgb(p, q, hsl.h.from_percent_of_360() - 1.0f / 3.0f);
284  return {r, g, b};
285  }
286  }
287 
288  // Convert functions (hsl)
289 
290  Hsl
291  to_hsl(const Rgb& c)
292  {
293  // todo(Gustav): max/min are terrible names for local variables...
294  // based on https://gist.github.com/mjackson/5311256
295  const auto max = eu::max(c.r, eu::max(c.g, c.b));
296  const auto min = eu::min(c.r, eu::min(c.g, c.b));
297  const auto l = (max + min) / 2;
298  // var h, s;
299 
300  enum class BiggestValue
301  {
302  r,
303  g,
304  b,
305  same
306  };
307 
308  const auto cl = [](float r, float g, float b) -> BiggestValue {
309  constexpr auto min_diff = 0.001f;
310  if(abs(r - g) < min_diff && abs(g - b) < min_diff)
311  {
312  return BiggestValue::same;
313  }
314  if(r >= g && r >= b)
315  {
316  return BiggestValue::r;
317  }
318  if(g >= r && g >= b)
319  {
320  return BiggestValue::g;
321  }
322  ASSERTX(b >= r && b >= g, r, g, b);
323  return BiggestValue::b;
324  }(c.r, c.g, c.b);
325 
326  if(cl == BiggestValue::same)
327  {
328  return {Angle::from_radians(0), 0, l}; // achromatic
329  }
330  else
331  {
332  const auto d = max - min;
333  const auto s = l > 0.5f ? d / (2 - max - min) : d / (max + min);
334 
335  const float h = [cl, &c, d]() -> float
336  {
337  switch(cl)
338  {
339  case BiggestValue::r: return (c.g - c.b) / d + (c.g < c.b ? 6.0f : 0.0f);
340  case BiggestValue::g: return (c.b - c.r) / d + 2;
341  case BiggestValue::b: return (c.r - c.g) / d + 4;
342  default: DIE("Unreachable"); return 0.0f;
343  }
344  }() / 6;
345  return {Angle::from_percent_of_360(h), s, l};
346  }
347  }
348 
349  // Convert functions (Rgbi)
350 
351  Rgbi
352  to_rgbi(const Rgb& c)
353  {
354  return
355  {
359  };
360  }
361 
362  Rgbi
363  to_rgbi(const Rgba& c)
364  {
365  return
366  {
370  };
371  }
372 
373  Rgbi
374  to_rgbi(const Rgbai& c)
375  {
376  return {c.r, c.g, c.b};
377  }
378 
379  //
380  Rgba
381  to_rgba(const Rgbai& c)
382  {
383  return
384  {
385  {
386  colorutil::to_float(c.r),
387  colorutil::to_float(c.g),
388  colorutil::to_float(c.b),
389  },
391  };
392  }
393 
394  Rgbai
395  to_rgbai(const Rgba& c)
396  {
397  return
398  {
399  {
403  },
405  };
406  }
407 
409 
410  Rgb lerp_rgb(const Rgb& from, float v, const Rgb& to)
411  {
412  return
413  {
414  lerp_float(from.r, v, to.r),
415  lerp_float(from.g, v, to.g),
416  lerp_float(from.b, v, to.b)
417  };
418  }
419 
421 
422  std::string to_js_hex_color(const Rgbi& c)
423  {
424  return fmt::format("0x{:0>2x}{:0>2x}{:0>2x}", c.r, c.g, c.b);
425  }
426 
427  std::string to_html_rgb(const Rgbi& c)
428  {
429  return fmt::format("rgb({}, {}, {})", c.r, c.g, c.b);
430  }
431 
433 
434  Hsl
435  get_saturated(const Hsl& ahsl, float amount, IsAbsolute method)
436  {
437  auto hsl = ahsl;
438 
439  if(method == IsAbsolute::no)
440  {
441  hsl.s += hsl.s * amount;
442  }
443  else
444  {
445  hsl.s += amount;
446  }
447  hsl.s = keep_within(r01, hsl.s);
448  return hsl;
449  }
450 
451  Hsl
452  get_desaturated(const Hsl& ahsl, float amount, IsAbsolute method)
453  {
454  auto hsl = ahsl;
455 
456  if(method == IsAbsolute::no)
457  {
458  hsl.s -= hsl.s * amount;
459  }
460  else
461  {
462  hsl.s -= amount;
463  }
464  hsl.s = keep_within(r01, hsl.s);
465  return hsl;
466  }
467 
468  Hsl
469  get_lightened(const Hsl& ahsl, float amount, IsAbsolute method)
470  {
471  auto hsl = ahsl;
472 
473  if(method == IsAbsolute::no)
474  {
475  hsl.l += hsl.l * amount;
476  }
477  else
478  {
479  hsl.l += amount;
480  }
481  hsl.l = keep_within(r01, hsl.l);
482  return hsl;
483  }
484 
485  Hsl
486  get_darkened(const Hsl& ahsl, float amount, IsAbsolute method)
487  {
488  auto hsl = ahsl;
489 
490  if(method == IsAbsolute::no)
491  {
492  hsl.l -= hsl.l * amount;
493  }
494  else
495  {
496  hsl.l -= amount;
497  }
498  hsl.l = keep_within(r01, hsl.l);
499  return hsl;
500  }
501 
502  Rgb
503  get_shaded_color(const Rgb& color, float percentage)
504  {
505  // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
506  const float t = percentage < 0 ? 0.0f : 1.0f;
507  const float p = percentage < 0 ? -percentage : percentage;
508  const float r = (t - color.r) * p + color.r;
509  const float g = (t - color.g) * p + color.g;
510  const float b = (t - color.b) * p + color.b;
511  return {r, g, b};
512  }
513 
515 
516  namespace
517  {
518  constexpr std::optional<U8> parse_hex_char(char c)
519  {
520  switch (c)
521  {
522  case '0': return {static_cast<U8>(0)};
523  case '1': return {static_cast<U8>(1)};
524  case '2': return {static_cast<U8>(2)};
525  case '3': return {static_cast<U8>(3)};
526  case '4': return {static_cast<U8>(4)};
527  case '5': return {static_cast<U8>(5)};
528  case '6': return {static_cast<U8>(6)};
529  case '7': return {static_cast<U8>(7)};
530  case '8': return {static_cast<U8>(8)};
531  case '9': return {static_cast<U8>(9)};
532 
533  case 'a': case 'A': return {static_cast<U8>(10)};
534  case 'b': case 'B': return {static_cast<U8>(11)};
535  case 'c': case 'C': return {static_cast<U8>(12)};
536  case 'd': case 'D': return {static_cast<U8>(13)};
537  case 'e': case 'E': return {static_cast<U8>(14)};
538  case 'f': case 'F': return {static_cast<U8>(15)};
539 
540  default:
541  return std::nullopt;
542  }
543  }
544 
545  constexpr std::optional<U8> combine_hex_char(std::optional<U8> pc, std::optional<U8> pd)
546  {
547  if (pc && pd)
548  {
549  return static_cast<U8>((*pc<<4) | *pd);
550  }
551  else
552  {
553  return std::nullopt;
554  }
555  }
556 
557  // give a #fff or a #ffffff string (depending on size arg), interpret it as a array and parse a index
558  template<int size> // is each 1 or 2 chars
559  std::pair<std::optional<U8>, std::string_view>
560  parse_hex(const std::string_view& value, int index)
561  {
562  const auto s = value.substr(1 + index * size, size);
563 
564  std::optional<U8> r;
565  if constexpr (size == 1)
566  {
567  const auto p = parse_hex_char(s[0]);
568  r = combine_hex_char(p, p);
569  }
570  else if constexpr (size == 2)
571  {
572  r = combine_hex_char
573  (
574  parse_hex_char(s[0]),
575  parse_hex_char(s[1])
576  );
577  }
578  else
579  {
580  ASSERT(false && "unreachable");
581  }
582 
583  if (r) { return { *r, s }; }
584  else { return { std::nullopt, s }; };
585  }
586 
587  using R = Result<Rgbi>;
588 
589  // given a component sie, parses a #fff or a #ffffff string to a color
590  template<int size>
591  R parse_rgb_hex(const std::string_view& value)
592  {
593  const auto [r, r_value] = parse_hex<size>(value, 0);
594  const auto [g, g_value] = parse_hex<size>(value, 1);
595  const auto [b, b_value] = parse_hex<size>(value, 2);
596  if (r && g && b)
597  {
598  return R::create_value({ *r, *g, *b });
599  }
600  else
601  {
602  auto invalids = std::vector<std::string>{};
603  if (!r) { invalids.emplace_back(fmt::format("red({})", r_value)); }
604  if (!g) { invalids.emplace_back(fmt::format("green({})", g_value)); }
605  if (!b) { invalids.emplace_back(fmt::format("blue({})", b_value)); }
606  return R::create_error
607  (
608  fmt::format(
609  "#color contains invalid hex for {}",
610  string_mergers::english_and.merge(invalids)
611  )
612  );
613  }
614  }
615 
616  // parses a #fff or a #ffffff string as a color
617  R
618  parse_hash_hex_rgbi(const std::string& value)
619  {
620  const auto size = value.length();
621  switch(size)
622  {
623  case 4: return parse_rgb_hex<1>(value);
624  case 7: return parse_rgb_hex<2>(value);
625  default:
626  return R::create_error
627  (
628  fmt::format
629  ("a hexadecimal color needs to be either #abc or #aabbcc. current count: {}", size-1)
630  );
631  }
632  }
633  }
634 
635  [[nodiscard]]
636  Result<Rgbi>
637  to_rgbi(const std::string& original_value)
638  {
639  const auto value = trim(original_value);
640 
641  if(value.empty()) { return R::create_error("empty string is not a color");}
642 
643  if(value[0] == '#')
644  {
645  return parse_hash_hex_rgbi(value);
646  }
647  else
648  {
649  const auto match = from_string_to_enum<NamedColor>(value);
650  if(match.single_match) { return R::create_value(to_rgbi(match.values[0])); }
651  return R::create_error
652  (
653  fmt::format
654  (
655  "bad name. Hex values require a #, but it could also be either {}",
657  (
658  match.names
659  )
660  )
661  );
662  }
663  }
664 
665 }
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
#define DIE(message)
Definition: assert.h:67
std::string trim(const std::string &string_to_trim, std::string_view trim_characters)
Remove characters from both the start and the end.
Definition: stringutils.cc:79
constexpr unsigned int to_color_hex(unsigned int r, unsigned int g, unsigned int b)
Definition: rgb.h:251
constexpr U8 get_green(unsigned int c)
Definition: rgb.h:230
constexpr float to_float(U8 c)
Definition: rgb.h:240
constexpr U8 to_unsigned_char(float f)
Definition: rgb.h:245
constexpr U8 get_red(unsigned int c)
Definition: rgb.h:225
constexpr U8 get_blue(unsigned int c)
Definition: rgb.h:235
constexpr StringMerger english_and
Definition: stringmerger.h:89
constexpr StringMerger english_or
Definition: stringmerger.h:90
Definition: assert.h:90
T keep_within(const Range< T > &range, T value)
Definition: range.h:115
float sqrt(float r)
Definition: numeric.cc:101
Rgbai to_rgbai(const Rgba &c)
Definition: rgb.cc:395
Angle operator+(const Angle &lhs, const Angle &rhs)
Definition: angle.cc:123
bool operator==(const Lrud< T > &lhs, const Lrud< T > &rhs)
Definition: lrud.h:75
Rgbi to_rgbi(const Rgb &c)
Definition: rgb.cc:352
Angle operator-(const Angle &lhs, const Angle &rhs)
Definition: angle.cc:132
Hsl get_saturated(const Hsl &ahsl, float amount, IsAbsolute method)
Definition: rgb.cc:435
Angle operator/(const Angle &lhs, float rhs)
Definition: angle.cc:141
float lerp_float(float f, float scale, float t)
Definition: numeric.cc:87
Hsl get_lightened(const Hsl &ahsl, float amount, IsAbsolute method)
Definition: rgb.cc:469
Rgb to_rgb(const Rgbi &c)
Definition: rgb.cc:229
std::string to_html_rgb(const Rgbi &c)
Definition: rgb.cc:427
constexpr float abs(float r)
Definition: numeric.h:8
NamedColor
Definition: colors.h:12
float square(float r)
Definition: numeric.cc:94
IsAbsolute
Definition: rgb.h:120
std::uint8_t U8
Definition: ints.h:15
std::string to_string(const Aabb &a)
Definition: aabb.cc:110
Hsl to_hsl(const Rgb &c)
Definition: rgb.cc:291
bool operator!=(const Lrud< T > &lhs, const Lrud< T > &rhs)
Definition: lrud.h:88
Rgb get_shaded_color(const Rgb &color, float percentage)
Makes a color brighter or darker.
Definition: rgb.cc:503
Result< Rgbi > to_rgbi(const std::string &original_value)
Definition: rgb.cc:637
size2f min(const size2f lhs, const size2f rhs)
Definition: size2.cc:140
Rgba to_rgba(const Rgbai &c)
Definition: rgb.cc:381
Rgb lerp_rgb(const Rgb &from, float v, const Rgb &to)
Definition: rgb.cc:410
Hsl get_darkened(const Hsl &ahsl, float amount, IsAbsolute method)
Definition: rgb.cc:486
constexpr Range< float > r01
Definition: range.h:57
std::string to_js_hex_color(const Rgbi &c)
Definition: rgb.cc:422
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
Angle operator*(const Angle &lhs, float rhs)
Definition: angle.cc:149
float dot(const quatf &lhs, const quatf &rhs)
Definition: quat.cc:363
Rgb clamp(const Rgb &c)
Definition: rgb.cc:144
Hsl get_desaturated(const Hsl &ahsl, float amount, IsAbsolute method)
Definition: rgb.cc:452
String utility functions.
constexpr static Angle from_percent_of_360(float percent)
Definition: angle.h:28
constexpr static Angle from_radians(float radians)
Definition: angle.h:22
constexpr float from_percent_of_360() const
Definition: angle.h:56
Definition: rgb.h:104
Angle h
Definition: rgb.h:106
float l
Definition: rgb.h:108
float s
Definition: rgb.h:107
Definition: rgb.h:62
void operator-=(const Rgb &rhs)
Definition: rgb.cc:62
void operator/=(float rhs)
Definition: rgb.cc:80
float calc_luminance() const
Definition: rgb.cc:46
Rgb(float red, float green, float blue)
Definition: rgb.cc:16
float b
Definition: rgb.h:65
float get_length_squared() const
Definition: rgb.cc:39
float g
Definition: rgb.h:64
static Rgb from_hex(unsigned int hex)
Definition: rgb.cc:23
void operator*=(const Rgb &rhs)
Definition: rgb.cc:71
float r
Definition: rgb.h:63
void operator+=(const Rgb &rhs)
Definition: rgb.cc:53
float get_length() const
Definition: rgb.cc:33
Definition: rgb.h:143
Rgba(float red, float green, float blue, float alpha=1.0f)
Definition: rgb.cc:163
Definition: rgb.h:45
U8 g
Definition: rgb.h:47
U8 r
Definition: rgb.h:46
U8 b
Definition: rgb.h:48
U8 a
Definition: rgb.h:49
Definition: rgb.h:26
U8 b
Definition: rgb.h:29
U8 r
Definition: rgb.h:27
U8 g
Definition: rgb.h:28