izzi
SVG SUBSET C++ API
Loading...
Searching...
No Matches
a60-svg-color.h
Go to the documentation of this file.
1// svg color -*- mode: C++ -*-
2
3// Copyright (C) 2014-2025 Benjamin De Kosnik <b.dekosnik@gmail.com>
4
5// This file is part of the alpha60-MiL SVG library. This library is
6// free software; you can redistribute it and/or modify it under the
7// terms of the GNU General Public License as published by the Free
8// Software Foundation; either version 3, or (at your option) any
9// later version.
10
11// This library is distributed in the hope that it will be useful, but
12// WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14// General Public License for more details.
15
16#ifndef MiL_SVG_COLOR_H
17#define MiL_SVG_COLOR_H 1
18
19#include <map>
20#include <algorithm>
21#include <random>
22#include <iomanip>
23
24
25namespace svg {
26
27/**
28 HUE
29
30 color = color enum (and name string conversion)
31
32 color_qi = color quantified as RGB integral values 0-255
33 (similar to Scalar in OpenCV).
34
35 color_qf = color quantified as Hue Saturation Value (HSV) decimal values 0-1
36
37 spectrum = color spectrum as finite array of color enum values
38*/
39
40/// Color enumerated as types.
41enum class color
42{
44
45 // 21 tints / shades
53 gray25, // gainsboro
60 gray75, // slategray
64
68
69 // 15 yellow
70 kanzoiro, // daylily light orange
71 kohakuiro, // amber
72 kinsusutake, // golden-gray bamoo
85
86 // 7 orange
94
95 // 17 red
98 ginshu, // gray red
99 akabeni, // pure crimson
100 akebonoiro, // dawn color
103 benikaba, // red birch
104 benitobi, // red kite bird
105 ake, // scarlet/blood
113
114 // 4 brown
120
121 // 20 green
122 byakuroku, // whitish green
123 usumoegi, // pale onion
124 moegi, // onion green
125 hiwamoegi, // siskin sprout
128 aotakeiro, // green bamboo
129 seiheki, // blue green
130 seijiiro, // celadon
131 yanagizome, // willow dye
143
144 // 35 blue
148 hanada, // blue silk
149 ruriiro, // lapis
152 asagiiro, // light blue
154 rurikon, // dark blue lapis
180
181 // 32 purple
183 murasaki, // purple
186 futaai, // dark indigo
187 benimidori, // stained red/violet
188 redwisteria, // dusty rose
189 botan, // tree peony
190 kokimurasaki, // deep purple
191 usuiro, // thin
215
216 // STOS
220
221 // Must be last, so can be cast to int for size.
223};
224
225/// Total number of enumerated colors.
226constexpr uint color_max_size = static_cast<uint>(color::last);
227
228/// Convert color to RGB color value string.
229const std::string&
231{
232 using enum_map_type = std::map<color, std::string>;
233
234 static enum_map_type enum_map;
235 if (enum_map.empty())
236 {
237 enum_map[color::white] = "rgb(255, 255, 255)";
238 enum_map[color::black] = "rgb(0, 0, 0)";
239 enum_map[color::gray90] = "rgb(25, 25, 25)";
240 enum_map[color::gray80] = "rgb(50, 50, 50)";
241 enum_map[color::gray75] = "rgb(64, 64, 64)";
242 enum_map[color::gray70] = "rgb(77, 77, 77)";
243 enum_map[color::gray66] = "rgb(87, 87, 87)";
244 enum_map[color::gray60] = "rgb(100, 100, 100)";
245 enum_map[color::gray50] = "rgb(128, 128, 128)";
246 enum_map[color::gray40] = "rgb(150, 150, 150)";
247 enum_map[color::gray30] = "rgb(180, 180, 180)";
248 enum_map[color::gray33] = "rgb(171, 171, 171)";
249 enum_map[color::gray25] = "rgb(191, 191, 191)";
250 enum_map[color::gray20] = "rgb(200, 200, 200)";
251 enum_map[color::gray10] = "rgb(230, 230, 230)";
252 enum_map[color::gray05] = "rgb(242, 242, 242)";
253 enum_map[color::gray02] = "rgb(248, 248, 248)";
254 enum_map[color::gray01] = "rgb(252, 252, 252)";
255
256 enum_map[color::wcag_lgray] = "rgb(148, 148, 148)"; // LG TXT on white 3:1
257 enum_map[color::wcag_gray] = "rgb(118, 118, 118)"; // min on white 4.5:1
258 enum_map[color::wcag_dgray] = "rgb(46, 46, 46)"; // on white 13.6:1
259
260 enum_map[color::command] = "rgb(255, 0, 171)";
261 enum_map[color::science] = "rgb(150, 230, 191)";
262 enum_map[color::engineering] = "rgb(161, 158, 178)";
263
264 enum_map[color::kissmepink] = "rgb(255, 59, 241)";
265
266 enum_map[color::red] = "rgb(255, 0, 0)";
267 enum_map[color::green] = "rgb(0, 255, 0)";
268 enum_map[color::blue] = "rgb(0, 0, 255)";
269
270 enum_map[color::asamablue] = "rgb(1, 137, 255)";
271 enum_map[color::asamaorange] = "rgb(236, 75, 37)";
272 enum_map[color::asamapink] = "rgb(200, 56, 81)";
273
274 // Yellows
275 enum_map[color::kanzoiro] = "rgb(255, 137, 54)";
276 enum_map[color::kohakuiro] = "rgb(202, 105, 36)";
277 enum_map[color::kinsusutake] = "rgb(125, 78, 45)";
278 enum_map[color::daylily] = "rgb(255, 137, 54)";
279 enum_map[color::goldenyellow] = "rgb(255, 164, 0)";
280 enum_map[color::hellayellow] = "rgb(255, 255, 0)";
281 enum_map[color::antiquewhite] = "rgb(250, 235, 215)";
282 enum_map[color::lemonchiffon] = "rgb(255, 250, 205)";
283 enum_map[color::goldenrod] = "rgb(250, 250, 210)";
284 enum_map[color::navajowhite] = "rgb(255, 222, 173)";
285
286 enum_map[color::ivory] = "rgb(255, 255, 240)";
287 enum_map[color::gold] = "rgb(255, 215, 0)";
288
289 enum_map[color::duboisyellow1] = "rgb(255, 255, 5)";
290 enum_map[color::duboisyellow2] = "rgb(255, 234, 18)";
291 enum_map[color::duboisyellow3] = "rgb(255, 215, 1)";
292
293 // Orange
294 enum_map[color::orange] = "rgb(255, 165, 0)";
295 enum_map[color::orangered] = "rgb(255, 69, 0)";
296 enum_map[color::redorange] = "rgb(220, 48, 35)";
297 enum_map[color::darkorange] = "rgb(255, 140, 17)";
298 enum_map[color::dutchorange] = "rgb(250, 155, 30)";
299 enum_map[color::internationalorange] = "rgb(255, 79, 0)";
300
301 // Brown
302 enum_map[color::duboisbrown1] = "rgb(128, 5, 5)";
303 enum_map[color::duboisbrown2] = "rgb(134, 90, 61)";
304 enum_map[color::duboisbrown3] = "rgb(81, 55, 42)";
305 enum_map[color::duboisbrown4] = "rgb(197, 146, 37)";
306 enum_map[color::duboisbrown5] ="rgb(255, 240, 200)";
307
308 // Reds
309 enum_map[color::foreigncrimson] = "rgb(201, 31, 55)";
310 enum_map[color::ginshu] = "rgb(188, 45, 41)";
311 enum_map[color::akabeni] = "rgb(195, 39,43)";
312 enum_map[color::akebonoiro] = "rgb(250, 123, 98)";
313
314 enum_map[color::ochre] = "rgb(255, 78, 32)";
315 enum_map[color::sohi] = "rgb(227, 92, 56)";
316 enum_map[color::benikaba] = "rgb(157, 43, 34)";
317 enum_map[color::benitobi] = "rgb(145, 50, 40)";
318 enum_map[color::ake] = "rgb(207, 58, 36)";
319
320 enum_map[color::crimson] = "rgb(220, 20, 60)";
321 enum_map[color::tomato] = "rgb(255, 99, 71)";
322 enum_map[color::coral] = "rgb(255, 127, 80)";
323 enum_map[color::salmon] = "rgb(250, 128, 114)";
324
325 enum_map[color::duboisred1] = "rgb(255, 29, 16)";
326 enum_map[color::duboisred2] = "rgb(249,110, 11)";
327 enum_map[color::duboisred3] = "rgb(215, 25, 50)";
328
329 // Greens
330 enum_map[color::byakuroku] = "rgb(165, 186, 147)";
331 enum_map[color::usumoegi] = "rgb(141, 178, 85)";
332 enum_map[color::moegi] = "rgb(91, 137, 48)";
333 enum_map[color::hiwamoegi] = "rgb(122, 148, 46)";
334 enum_map[color::midori] = "rgb(42, 96, 59)";
335 enum_map[color::rokusho] = "rgb(64, 122, 82)";
336 enum_map[color::aotakeiro] = "rgb(0, 100, 66)";
337 enum_map[color::seiheki] = "rgb(58, 105, 96)";
338 enum_map[color::seijiiro] = "rgb(129, 156, 139)";
339 enum_map[color::yanagizome] = "rgb(140, 158, 94)";
340
341 enum_map[color::chartreuse] = "rgb(127, 255, 0)";
342 enum_map[color::greenyellow] = "rgb(173, 255, 47)";
343 enum_map[color::limegreen] = "rgb(50, 205, 50)";
344 enum_map[color::springgreen] = "rgb(0, 255, 127)";
345 enum_map[color::aquamarine] = "rgb(127, 255, 212)";
346
347 enum_map[color::duboisgreen1] = "rgb(5, 255, 5)";
348 enum_map[color::duboisgreen2] = "rgb(127, 225, 15)";
349 enum_map[color::duboisgreen3] = "rgb(16, 114, 9)";
350 enum_map[color::duboisgreen4] = "rgb(0, 148, 16)";
351 enum_map[color::duboisgreen5] = "rgb(24, 57, 30)";
352
353 // Blues
354 enum_map[color::ultramarine] = "rgb(93, 140, 174)";
355 enum_map[color::shinbashiiro] = "rgb(0, 108, 127)";
356 enum_map[color::hanada] = "rgb(4, 79, 103)";
357 enum_map[color::ruriiro] = "rgb(31, 71, 136)";
358 enum_map[color::bellflower] = "rgb(25, 31, 69)";
359 enum_map[color::navy] = "rgb(0, 49, 113)";
360 enum_map[color::asagiiro] = "rgb(72, 146, 155)";
361 enum_map[color::indigo] = "rgb(38, 67, 72)";
362 enum_map[color::rurikon] = "rgb(27, 41, 75)";
363 enum_map[color::cyan] = "rgb(0, 255, 255)";
364
365 enum_map[color::lightcyan] = "rgb(224, 255, 255)";
366 enum_map[color::powderblue] = "rgb(176, 224, 230)";
367 enum_map[color::steelblue] = "rgb(70, 130, 237)";
368 enum_map[color::cornflowerblue] = "rgb(100, 149, 237)";
369 enum_map[color::deepskyblue] = "rgb(0, 191, 255)";
370 enum_map[color::dodgerblue] = "rgb(30, 144, 255)";
371 enum_map[color::lightblue] = "rgb(173, 216, 230)";
372 enum_map[color::skyblue] = "rgb(135, 206, 235)";
373 enum_map[color::lightskyblue] = "rgb(173, 206, 250)";
374 enum_map[color::midnightblue] = "rgb(25, 25, 112)";
375
376 enum_map[color::mediumblue] = "rgb(0, 0, 205)";
377 enum_map[color::royalblue] = "rgb(65, 105, 225)";
378 enum_map[color::darkslateblue] = "rgb(72, 61, 139)";
379 enum_map[color::slateblue] = "rgb(106, 90, 205)";
380 enum_map[color::azure] = "rgb(240, 255, 255)";
381 enum_map[color::crayolacerulean] = "rgb(29, 172, 214)";
382
383 enum_map[color::duboisblue1] = "rgb(37, 42, 255)";
384 enum_map[color::duboisblue2] = "rgb(100, 150, 245)";
385 enum_map[color::duboisblue3] = "rgb(74, 87, 129)";
386 enum_map[color::duboisblue4] = "rgb(49, 64, 103)";
387
388 enum_map[color::blueprintlight] = "rgb(0, 25, 166)";
389 enum_map[color::blueprint] = "rgb(0, 20, 132)";
390 enum_map[color::blueprintdark] = "rgb(0, 16, 106)";
391
392 // Purples
393 enum_map[color::wisteria] = "rgb(135, 95, 154)";
394 enum_map[color::murasaki] = "rgb(79, 40, 75)";
395 enum_map[color::ayameiro] = "rgb(118, 53, 104)";
396 enum_map[color::peony] = "rgb(164, 52, 93)";
397 enum_map[color::futaai] = "rgb(97, 78, 110)";
398 enum_map[color::benimidori] = "rgb(120, 119, 155)";
399 enum_map[color::redwisteria] = "rgb(187, 119, 150)";
400 enum_map[color::botan] = "rgb(164, 52, 93)";
401 enum_map[color::kokimurasaki] = "rgb(58, 36, 59)";
402 enum_map[color::usuiro] = "rgb(168, 124, 160)";
403
404 enum_map[color::blueviolet] = "rgb(138, 43, 226)";
405 enum_map[color::darkmagenta] = "rgb(139, 0, 139)";
406 enum_map[color::darkviolet] = "rgb(148, 0, 211)";
407 enum_map[color::thistle] = "rgb(216, 191, 216)";
408 enum_map[color::plum] = "rgb(221, 160, 221)";
409 enum_map[color::violet] = "rgb(238, 130, 238)";
410 enum_map[color::magenta] = "rgb(255, 0, 255)";
411 enum_map[color::dfuschia] = "rgb(255, 35, 255)";
412 enum_map[color::deeppink] = "rgb(255, 20, 147)";
413 enum_map[color::hotpink] = "rgb(255, 105, 180)";
414 enum_map[color::pink] = "rgb(255, 192, 203)";
415
416 enum_map[color::palevioletred] = "rgb(219, 112, 147)";
417 enum_map[color::mediumvioletred] = "rgb(199, 21, 133)";
418 enum_map[color::lavender] = "rgb(230, 230, 250)";
419 enum_map[color::orchid] = "rgb(218, 112, 214)";
420 enum_map[color::mediumorchid] = "rgb(186, 85, 211)";
421 enum_map[color::darkestmagenta] = "rgb(180, 0, 180)";
422 enum_map[color::mediumpurple] = "rgb(147, 112, 219)";
423 enum_map[color::purple] = "rgb(128, 0, 128)";
424 enum_map[color::dustyrose] = "rgb(191, 136, 187)";
425 enum_map[color::atmosphericp] = "rgb(228, 210, 231)";
426
427 enum_map[color::none] = "rgb(1, 0, 0)";
428 enum_map[color::last] = "rgb(0, 0, 1)";
429
430 // Error check to make sure all the colors have names/values.
431 if (enum_map.size() != color_max_size + 1)
432 {
433 string m("to_string(color)::color map size fail ");
434 m += k::newline;
435 m += std::to_string(enum_map.size());
436 m += " not equal to named colors of size ";
437 m += k::newline;
438 m += std::to_string(color_max_size);
439 throw std::runtime_error(m);
440 }
441 }
442 return enum_map[e];
443}
444
445
446/// Color quantified as integral RGB components in the range [0,255].
447/// aka like Scalar in OpenCV.
449{
450 using itype = unsigned short;
451
455
456 // Return "rgb(64, 64, 64)";
457
458 static string
460 {
461 std::ostringstream oss;
462 oss << "rgb(" << s.r << ',' << s.g << ',' << s.b << ")";
463 return oss.str();
464 }
465
466 // From "rgb(64, 64, 64)";
467 static color_qi
468 from_string(string s)
469 {
470 // Kill rgb() enclosing, if be.
471 if (s.empty() || s.size() < 5 || s[0] != 'r')
472 {
473 string m("color_qi::from_string input is not in rbg form: ");
474 m += s;
475 m += k::newline;
476 throw std::runtime_error(m);
477 }
478 else
479 {
480 s.pop_back();
481 s = s.substr(4);
482 }
483
484 // String stream which eats whitespace and knows number separation.
485 std::istringstream iss(s);
486 iss >> std::skipws;
487
488 char c(0);
489
490 ushort rs(0);
491 iss >> rs;
492 itype r = static_cast<itype>(rs);
493 iss >> c;
494
495 ushort gs(0);
496 iss >> gs;
497 itype g = static_cast<itype>(gs);
498 iss >> c;
499
500 ushort bs(0);
501 iss >> bs;
502 itype b = static_cast<itype>(bs);
503
504 return color_qi(r, g, b);
505 }
506
507 color_qi() = default;
508 color_qi(const color_qi&) = default;
509 color_qi& operator=(const color_qi&) = default;
510
511 // auto operator<=>(const color_qi&) const = default;
512
513 color_qi(itype ra, itype ga, itype ba) : r(ra), g(ga), b(ba) { }
514
516 {
518 r = klr.r;
519 b = klr.b;
520 g = klr.g;
521 }
522};
523
524inline bool
525operator==(const color_qi& c1, const color_qi& c2)
526{
527 const bool t1 = c1.r == c2.r;
528 const bool t2 = c1.g == c2.g;
529 const bool t3 = c1.b == c2.b;
530 return t1 && t2 && t3;
531}
532
533
534/// Convert color_qi to string.
535const std::string
537{ return color_qi::to_string(klr); }
538
539
540/**
541 Color quantified as floating point HSV components in the range [0,1].
542
543 https://web.archive.org/web/20150303174723/
544 http://en.literateprograms.org/RGB_to_HSV_color_space_conversion_(C)
545*/
547{
548 using ftype = float;
549
550 ftype h; /// Hue degree between 0.0 and 360.0
551 ftype s; /// Saturation between 0.0 (gray) and 1.0
552 ftype v; /// Value between 0.0 (black) and 1.0
553
554 color_qf() = default;
555 color_qf(const color_qf&) = default;
556 color_qf& operator=(const color_qf&) = default;
557
558 // auto operator<=>(const color_qf&) const = default;
559
560 color_qf(ftype vh, ftype vs, ftype vv) : h(vh), s(vs), v(vv) { }
561
562 // Conversion constructor, convert from RGB to HSV.
563 color_qf(const color_qi& cqi)
564 {
565 auto hsv = rgb_to_hsv(cqi);
566 h = hsv.h;
567 s = hsv.s;
568 v = hsv.v;
569 }
570
572 {
573 color_qi klr(e);
574 *this = color_qf(klr);
575 }
576
577 static string
579 {
580 std::ostringstream oss;
581 oss << std::fixed << std::setprecision(2);
582 oss << "hsv(" << s.h << ',' << s.s << ',' << s.v << ")";
583 return oss.str();
584 }
585
586 /// Back to RGB
587 /// https://www.rapidtables.com/convert/color/hsv-to-rgb.html
590 { return hsv_to_rgb({ h, s, v }); }
591
592 /// Convert RGB to HSV
594 rgb_to_hsv(const color_qi& rgb) const
595 {
596 color_qf hsv;
597
598 double r = rgb.r / 255.0;
599 double g = rgb.g / 255.0;
600 double b = rgb.b / 255.0;
601
602 double max_val = std::max({r, g, b});
603 double min_val = std::min({r, g, b});
604 double delta = max_val - min_val;
605
606 // Hue calculation
607 if (delta == 0.0)
608 {
609 hsv.h = 0.0;
610 }
611 else if (max_val == r)
612 {
613 hsv.h = 60.0 * fmod(((g - b) / delta), 6.0);
614 }
615 else if (max_val == g)
616 {
617 hsv.h = 60.0 * (((b - r) / delta) + 2.0);
618 }
619 else
620 {
621 // max_val == b
622 hsv.h = 60.0 * (((r - g) / delta) + 4.0);
623 }
624
625 if (hsv.h < 0.0)
626 hsv.h += 360.0;
627
628 // Saturation calculation
629 hsv.s = (max_val == 0.0) ? 0.0 : (delta / max_val);
630
631 // Value calculation
632 hsv.v = max_val;
633
634 return hsv;
635 }
636
637
638 /// Convert HSV to RGB
640 hsv_to_rgb(const color_qf& hsv) const
641 {
642 color_qi rgb;
643
644 double c = hsv.v * hsv.s;
645 double x = c * (1.0 - std::abs(fmod(hsv.h / 60.0, 2.0) - 1.0));
646 double m = hsv.v - c;
647
648 double r_prime, g_prime, b_prime;
649
650 if (hsv.h >= 0.0 && hsv.h < 60.0)
651 {
652 r_prime = c; g_prime = x; b_prime = 0.0;
653 }
654 else if (hsv.h >= 60.0 && hsv.h < 120.0)
655 {
656 r_prime = x; g_prime = c; b_prime = 0.0;
657 }
658 else if (hsv.h >= 120.0 && hsv.h < 180.0)
659 {
660 r_prime = 0.0; g_prime = c; b_prime = x;
661 }
662 else if (hsv.h >= 180.0 && hsv.h < 240.0)
663 {
664 r_prime = 0.0; g_prime = x; b_prime = c;
665 }
666 else if (hsv.h >= 240.0 && hsv.h < 300.0)
667 {
668 r_prime = x; g_prime = 0.0; b_prime = c;
669 }
670 else
671 {
672 // 300° - 360°
673 r_prime = c; g_prime = 0.0; b_prime = x;
674 }
675
676 using itype = color_qi::itype;
677 rgb.r = static_cast<itype>(std::clamp((r_prime + m) * 255.0, 0.0, 255.0));
678 rgb.g = static_cast<itype>(std::clamp((g_prime + m) * 255.0, 0.0, 255.0));
679 rgb.b = static_cast<itype>(std::clamp((b_prime + m) * 255.0, 0.0, 255.0));
680
681 return rgb;
682 }
683
684 /// Interpolated tinting algorithm from palette (ciecam16j70_palette).
685 /// @param n is the [0,10] 11-step perceptual gradation from dark to light.
686 /// Assume that color_qi has been converted to color_qf.
689 {
690 // 11 steps total (0-10)
691 uint step = std::min(n, 10u);
692
693 // White is HSV(any, 0.0, 1.0)
694 // const color_qf white_hsv = { h, 0.0, 1.0};
695
696 // Use a custom interpolation curve
697 // First increase value, then decrease saturation
698 color_qf result_hsv(*this);
699 if (step <= 4)
700 {
701 // For steps 0-4, t is interpolate value: base.v → 1.0
702 double t = step / 4.0;
703 result_hsv.s = s; // Keep saturation
704 result_hsv.v = v + t * (1.0 - v);
705 }
706 else
707 {
708 // For steps 5-10, interpolate saturation: 1.0 → 0.3
709 double sat_t = (step - 4) / 6.0; // 0.0 to 1.0
710 double target_sat = 1.0 - 0.7 * sat_t; // 1.0 → 0.3
711 result_hsv.s = s * target_sat;
712 result_hsv.v = 1.0; // Max value
713 }
714
715 return hsv_to_rgb(result_hsv);
716 }
717
718 /// Procedural tinting algorithm.
719 /// @param tp tint percentage original color remaining
720 /// @param trange upper/lower bounds for percentage range (0, 100)
722 tint_percentage(const double tp,
723 const double rmin = 0, const double rmax = 100)
724 {
725 double percentage = std::clamp(tp, rmin, rmax);
726
727 // Convert percentage to adjustment factor
728 // 10% tint = 10% of original saturation
729 // 90% tint = 90% of original saturation
730 double saturationf = percentage / 100.0;
731
732 // Alternative: Mix with white by reducing saturation and increasing value
733 color_qf result_hsv(*this);
734
735 // Reduce saturation based on tint percentage
736 result_hsv.s *= saturationf;
737
738 // Slightly increase value/brightness for tint effect
739 // White has maximum brightness, so tinting moves toward value = 1.0
740 result_hsv.v = v + (1.0 - v) * (1.0 - saturationf) * 0.5;
741
742 return hsv_to_rgb(result_hsv);
743 }
744};
745
746
747/// Convert color_qf to string.
748const std::string
750{ return color_qf::to_string(klr); }
751
752
753/// Less than compare for color_qf
754bool
756{
757 const bool eqh = k1.h == k2.h;
758 const bool lth = k1.h < k2.h;
759 const bool lts = k1.v < k2.v;
760
761 if (eqh)
762 return lts;
763 else
764 return lth;
765};
766
767bool
769{
770 const bool eqh = k1.h == k2.h;
771 const bool lth = k1.h < k2.h;
772
773 if (eqh)
774 {
775 const color_qf::ftype k1hyp = std::sqrt((k1.s * k1.s) + (k1.v * k1.v));
776 const color_qf::ftype k2hyp = std::sqrt((k2.s * k2.s) + (k2.v * k2.v));
777 const bool ltothers = k1hyp < k2hyp;
778 return ltothers;
779 }
780 else
781 return lth;
782};
783
786{
787 using ftype = color_qf::ftype;
788 const ftype sdist = std::abs(k1.s - k2.s);
789 const ftype vdist = std::abs(k1.v - k2.v);
790
791 const ftype habs = std::abs(k1.h - k2.h);
792 const ftype hdist = habs > 180 ? 360 - habs : habs;
793
794 // Euclidean distance.
795 //return std::sqrt(hdist*hdist + sdist*sdist + vdist*vdist);
796
797 // Weighted.
798 //return hdist*hweight + sdist*sweight + vdist*vweight;
799 return hdist*1.2 + sdist*2 + vdist*3;
800}
801
802
803/// Default compare distances from k1,k2 to black
804bool
805color_qf_lt_v(const color_qf& k1, const color_qf& k2)
806{
807 using ftype = color_qf::ftype;
808
809 const color_qf originklr = color::black;
810
811 // Compute distance from both arguments to compare color.
812 ftype d1 = color_qf_distance(k1, originklr);
813 ftype d2 = color_qf_distance(k2, originklr);
814 const bool ret = d1 < d2;
815 return ret;
816};
817
818
819/// Forwarding function.
820inline bool
821color_qf_lt(const color_qf& k1, const color_qf& k2)
822{ return color_qf_lt_v(k1, k2); };
823
824
825inline bool
826operator==(const color_qf& c1, const color_qf& c2)
827{
828 const bool t1 = c1.h == c2.h;
829 const bool t2 = c1.s == c2.s;
830 const bool t3 = c1.v == c2.v;
831 return t1 && t2 && t3;
832}
833
834inline bool
835operator<(const color_qf& c1, const color_qf& c2)
836{ return color_qf_lt(c1, c2); }
837
838
839/// Return a variant on saturation/value only.
840color_qf
842{
843 color_qf ret(k);
844 static std::mt19937_64 rg(std::random_device{}());
845 auto distr = std::uniform_real_distribution<>(0.5, 1);
846
847 // saturation 0.5 to 1, aka more saturated.
848 double stry = distr(rg);
849 if (ret.s < stry)
850 ret.s = stry;
851
852 // value 0.5 to 1, aka less dark
853 double vtry = distr(rg);
854 if (ret.v < vtry)
855 ret.v = vtry;
856
857 return ret;
858}
859
860
861/**
862 Combine color a with color b in percentages ad and ab, respectively.
863
864 To average, constrain paramters ad and ab such that: ad + ab == 2.
865
866 Like so:
867 ushort ur = (a.r + b.r) / 2;
868 ushort ug = (a.g + b.g) / 2;
869 ushort ub = (a.b + b.b) / 2;
870*/
871color_qi
872combine_color_qi(const color_qi& a, const double ad,
873 const color_qi& b, const double bd)
874{
875 double denom = ad + bd;
876 double ur = ((a.r * ad) + (b.r * bd)) / denom;
877 double ug = ((a.g * ad) + (b.g * bd)) / denom;
878 double ub = ((a.b * ad) + (b.b * bd)) / denom;
879
880 using itype = color_qi::itype;
881 itype cr = static_cast<itype>(ur);
882 itype cg = static_cast<itype>(ug);
883 itype cb = static_cast<itype>(ub);
884 return color_qi { cr, cg, cb };
885}
886
887/// Average two colors, return the result.
888color_qi
890{ return combine_color_qi(a, 1.0, b, 1.0); }
891
892
893/// Types for Color iteration and combinatorics.
894using color_qis = std::vector<color_qi>;
895using color_qfs = std::vector<color_qf>;
896
897} // namespace svg
898
899#endif
constexpr uint color_max_size
Total number of enumerated colors.
color
Color enumerated as types.
unsigned short ushort
Base integer type: positive and negative, signed integral value.
Definition a60-svg.h:57
std::vector< color_qf > color_qfs
const string to_string(const unit e)
std::vector< color_qi > color_qis
Types for Color iteration and combinatorics.
bool operator<(const color_qf &c1, const color_qf &c2)
color_qi average_color_qi(const color_qi &a, const color_qi &b)
Average two colors, return the result.
color_qf mutate_color_qf(const color_qf &k)
Return a variant on saturation/value only.
bool color_qf_lt_hue_v2(const color_qf &k1, const color_qf &k2)
bool operator==(const color_qi &c1, const color_qi &c2)
color_qi combine_color_qi(const color_qi &a, const double ad, const color_qi &b, const double bd)
color_qf::ftype color_qf_distance(const color_qf &k1, const color_qf &k2)
bool color_qf_lt_v(const color_qf &k1, const color_qf &k2)
Default compare distances from k1,k2 to black.
bool color_qf_lt(const color_qf &k1, const color_qf &k2)
Forwarding function.
unsigned int uint
Definition a60-svg.h:58
bool color_qf_lt_hue_v1(const color_qf &k1, const color_qf &k2)
Less than compare for color_qf.
ftype v
Saturation between 0.0 (gray) and 1.0.
color_qf rgb_to_hsv(const color_qi &rgb) const
Convert RGB to HSV.
color_qf(const color e)
color_qf(ftype vh, ftype vs, ftype vv)
color_qf()=default
Value between 0.0 (black) and 1.0.
color_qi tint_percentage(const double tp, const double rmin=0, const double rmax=100)
Procedural tinting algorithm.
ftype s
Hue degree between 0.0 and 360.0.
color_qf & operator=(const color_qf &)=default
color_qf(const color_qf &)=default
static string to_string(color_qf s)
color_qi tint_perceptual(const uint n)
Interpolated tinting algorithm from palette (ciecam16j70_palette).
color_qf(const color_qi &cqi)
color_qi to_color_qi() const
Back to RGB https://www.rapidtables.com/convert/color/hsv-to-rgb.html.
color_qi hsv_to_rgb(const color_qf &hsv) const
Convert HSV to RGB.
Color quantified as integral RGB components in the range [0,255]. aka like Scalar in OpenCV.
color_qi(const color_qi &)=default
color_qi & operator=(const color_qi &)=default
color_qi()=default
static string to_string(color_qi s)
color_qi(itype ra, itype ga, itype ba)
unsigned short itype
static color_qi from_string(string s)
color_qi(const color e)