1 /*
2 * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <algorithm>
20 #include <sstream>
21 #include <cmath>
22 #include <stdint.h>
23 #include <cfloat>
24
25 #include "pbd/failed_constructor.h"
26 #include "pbd/string_convert.h"
27
28 #include "gtkmm2ext/colors.h"
29 #include "gtkmm2ext/colorspace.h"
30
31 using namespace std;
32 using namespace Gtkmm2ext;
33
34 using std::max;
35 using std::min;
36
37 Gtkmm2ext::Color
change_alpha(Color c,double a)38 Gtkmm2ext::change_alpha (Color c, double a)
39 {
40 return ((c & ~0xff) | (lrintf (a*255.0) & 0xff));
41 }
42
43 void
color_to_hsv(Color color,double & h,double & s,double & v)44 Gtkmm2ext::color_to_hsv (Color color, double& h, double& s, double& v)
45 {
46 double a;
47 color_to_hsva (color, h, s, v, a);
48 }
49
50 void
color_to_hsva(Color color,double & h,double & s,double & v,double & a)51 Gtkmm2ext::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
52 {
53 double r, g, b;
54 double cmax;
55 double cmin;
56 double delta;
57
58 color_to_rgba (color, r, g, b, a);
59
60 if (r > g) {
61 cmax = max (r, b);
62 } else {
63 cmax = max (g, b);
64 }
65
66 if (r < g) {
67 cmin = min (r, b);
68 } else {
69 cmin = min (g, b);
70 }
71
72 v = cmax;
73
74 delta = cmax - cmin;
75
76 if (cmax == 0) {
77 // r = g = b == 0 ... v is undefined, s = 0
78 s = 0.0;
79 h = 0.0;
80 return;
81 }
82
83 if (delta != 0.0) {
84 if (cmax == r) {
85 h = fmod ((g - b)/delta, 6.0);
86 } else if (cmax == g) {
87 h = ((b - r)/delta) + 2;
88 } else {
89 h = ((r - g)/delta) + 4;
90 }
91
92 h *= 60.0;
93
94 if (h < 0.0) {
95 /* negative values are legal but confusing, because
96 they alias positive values.
97 */
98 h = 360 + h;
99 }
100 }
101
102 if (delta == 0 || cmax == 0) {
103 s = 0;
104 } else {
105 s = delta / cmax;
106 }
107 }
108
109 Gtkmm2ext::Color
hsva_to_color(double h,double s,double v,double a)110 Gtkmm2ext::hsva_to_color (double h, double s, double v, double a)
111 {
112 s = min (1.0, max (0.0, s));
113 v = min (1.0, max (0.0, v));
114
115 if (s == 0) {
116 return rgba_to_color (v, v, v, a);
117 }
118
119 h = fmod (h + 360.0, 360.0);
120
121 double c = v * s;
122 double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
123 double m = v - c;
124
125 if (h >= 0.0 && h < 60.0) {
126 return rgba_to_color (c + m, x + m, m, a);
127 } else if (h >= 60.0 && h < 120.0) {
128 return rgba_to_color (x + m, c + m, m, a);
129 } else if (h >= 120.0 && h < 180.0) {
130 return rgba_to_color (m, c + m, x + m, a);
131 } else if (h >= 180.0 && h < 240.0) {
132 return rgba_to_color (m, x + m, c + m, a);
133 } else if (h >= 240.0 && h < 300.0) {
134 return rgba_to_color (x + m, m, c + m, a);
135 } else if (h >= 300.0 && h < 360.0) {
136 return rgba_to_color (c + m, m, x + m, a);
137 }
138 return rgba_to_color (m, m, m, a);
139 }
140
141 void
color_to_rgba(Color color,double & r,double & g,double & b,double & a)142 Gtkmm2ext::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
143 {
144 r = ((color >> 24) & 0xff) / 255.0;
145 g = ((color >> 16) & 0xff) / 255.0;
146 b = ((color >> 8) & 0xff) / 255.0;
147 a = ((color >> 0) & 0xff) / 255.0;
148 }
149
150 Gtkmm2ext::Color
rgba_to_color(double r,double g,double b,double a)151 Gtkmm2ext::rgba_to_color (double r, double g, double b, double a)
152 {
153 /* clamp to [0 .. 1] range */
154
155 r = min (1.0, max (0.0, r));
156 g = min (1.0, max (0.0, g));
157 b = min (1.0, max (0.0, b));
158 a = min (1.0, max (0.0, a));
159
160 /* convert to [0..255] range */
161
162 unsigned int rc, gc, bc, ac;
163 rc = rint (r * 255.0);
164 gc = rint (g * 255.0);
165 bc = rint (b * 255.0);
166 ac = rint (a * 255.0);
167
168 /* build-an-integer */
169
170 return (rc << 24) | (gc << 16) | (bc << 8) | ac;
171 }
172
173 // Inverse of sRGB "gamma" function.
174 static inline double
inv_gam_sRGB(double c)175 inv_gam_sRGB (double c)
176 {
177 if (c <= 0.04045) {
178 return c/12.92;
179 } else {
180 return pow(((c+0.055)/(1.055)),2.4);
181 }
182 }
183
184 // sRGB "gamma" function
185 static inline int
gam_sRGB(double v)186 gam_sRGB(double v)
187 {
188 if (v <= 0.0031308) {
189 v *= 12.92;
190 } else {
191 v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
192 }
193 return int (v*255+.5);
194 }
195
196 static double
luminance(uint32_t c)197 luminance (uint32_t c)
198 {
199 // sRGB luminance(Y) values
200 const double rY = 0.212655;
201 const double gY = 0.715158;
202 const double bY = 0.072187;
203
204 double r, g, b, a;
205
206 Gtkmm2ext::color_to_rgba (c, r, g, b, a);
207
208 return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
209 }
210
211 uint32_t
contrasting_text_color(uint32_t c)212 Gtkmm2ext::contrasting_text_color (uint32_t c)
213 {
214 /* use a slightly off-white... XXX should really look this up */
215
216 static const uint32_t white = Gtkmm2ext::rgba_to_color (0.98, 0.98, 0.98, 1.0);
217 static const uint32_t black = Gtkmm2ext::rgba_to_color (0.0, 0.0, 0.0, 1.0);
218
219 return (luminance (c) < 0.50) ? white : black;
220 }
221
222
223
HSV()224 HSV::HSV ()
225 : h (0.0)
226 , s (1.0)
227 , v (1.0)
228 , a (1.0)
229 {
230 }
231
HSV(double hh,double ss,double vv,double aa)232 HSV::HSV (double hh, double ss, double vv, double aa)
233 : h (hh)
234 , s (ss)
235 , v (vv)
236 , a (aa)
237 {
238 if (h < 0.0) {
239 /* normalize negative hue values into positive range */
240 h = 360.0 + h;
241 }
242 }
243
HSV(Color c)244 HSV::HSV (Color c)
245 {
246 color_to_hsva (c, h, s, v, a);
247 }
248
249 string
to_string() const250 HSV::to_string () const
251 {
252 stringstream ss;
253 ss << PBD::to_string(h) << ' ';
254 ss << PBD::to_string(s) << ' ';
255 ss << PBD::to_string(v) << ' ';
256 ss << PBD::to_string(a);
257 return ss.str();
258 }
259
260 bool
is_gray() const261 HSV::is_gray () const
262 {
263 return s == 0;
264 }
265
266 void
clamp()267 HSV::clamp ()
268 {
269 h = fmod (h, 360.0);
270 if (h < 0.0) {
271 /* normalize negative hue values into positive range */
272 h = 360.0 + h;
273 }
274 s = min (1.0, s);
275 v = min (1.0, v);
276 a = min (1.0, a);
277 }
278
279 HSV
operator +(const HSV & operand) const280 HSV::operator+ (const HSV& operand) const
281 {
282 HSV hsv;
283 hsv.h = h + operand.h;
284 hsv.s = s + operand.s;
285 hsv.v = v + operand.v;
286 hsv.a = a + operand.a;
287 hsv.clamp ();
288 return hsv;
289 }
290
291 HSV
operator -(const HSV & operand) const292 HSV::operator- (const HSV& operand) const
293 {
294 HSV hsv;
295 hsv.h = h - operand.h;
296 hsv.s = s - operand.s;
297 hsv.v = s - operand.v;
298 hsv.a = a - operand.a;
299 hsv.clamp ();
300 return hsv;
301 }
302
303 HSV&
operator =(Color c)304 HSV::operator=(Color c)
305 {
306 color_to_hsva (c, h, s, v, a);
307 clamp ();
308 return *this;
309 }
310
311 HSV&
operator =(const std::string & str)312 HSV::operator=(const std::string& str)
313 {
314 uint32_t c;
315 c = strtol (str.c_str(), 0, 16);
316 color_to_hsva (c, h, s, v, a);
317 clamp ();
318 return *this;
319 }
320
321 bool
operator ==(const HSV & other)322 HSV::operator== (const HSV& other)
323 {
324 return h == other.h &&
325 s == other.s &&
326 v == other.v &&
327 a == other.a;
328 }
329
330 HSV
shade(double factor) const331 HSV::shade (double factor) const
332 {
333 HSV hsv (*this);
334
335 /* algorithm derived from a google palette website
336 and analysis of their color palettes.
337
338 basic rule: to make a color darker, increase its saturation
339 until it reaches 88%, but then additionally reduce value/lightness
340 by a larger amount.
341
342 invert rule to make a color lighter.
343 */
344
345 if (factor > 1.0) {
346 if (s < 88) {
347 hsv.v += (hsv.v * (factor * 10.0));
348 }
349 hsv.s *= factor;
350 } else {
351 if (s < 88) {
352 hsv.v -= (hsv.v * (factor * 10.0));
353 }
354 hsv.s *= factor;
355 }
356
357 hsv.clamp();
358
359 return hsv;
360 }
361
362 HSV
outline() const363 HSV::outline () const
364 {
365 if (luminance (color()) < 0.50) {
366 /* light color, darker outline: black with 15% opacity */
367 return HSV (0.0, 0.0, 0.0, 0.15);
368 } else {
369 /* dark color, lighter outline: white with 15% opacity */
370 return HSV (0.0, 0.0, 1.0, 0.15);
371 }
372 }
373
374 HSV
mix(const HSV & other,double amount) const375 HSV::mix (const HSV& other, double amount) const
376 {
377 HSV hsv;
378
379 hsv.h = h + (amount * (other.h - h));
380 hsv.v = v + (amount * (other.s - s));
381 hsv.s = s + (amount * (other.v - v));
382
383 hsv.clamp();
384
385 return hsv;
386 }
387
388 HSV
delta(const HSV & other) const389 HSV::delta (const HSV& other) const
390 {
391 HSV d;
392
393 if (is_gray() && other.is_gray()) {
394 d.h = 0.0;
395 d.s = 0.0;
396 d.v = v - other.v;
397 } else {
398 d.h = h - other.h;
399 d.s = s - other.s;
400 d.v = v - other.v;
401 }
402 d.a = a - other.a;
403 /* do not clamp - we are returning a delta */
404 return d;
405 }
406
407 double
distance(const HSV & other) const408 HSV::distance (const HSV& other) const
409 {
410 if (is_gray() && other.is_gray()) {
411 /* human color perception of achromatics generates about 450
412 distinct colors. By contrast, CIE94 could give a maximal
413 perceptual distance of sqrt ((360^2) + 1 + 1) = 360. The 450
414 are not evenly spread (Webers Law), so lets use 360 as an
415 approximation of the number of distinct achromatics.
416
417 So, scale up the achromatic difference to give about
418 a maximal distance between v = 1.0 and v = 0.0 of 360.
419
420 A difference of about 0.0055 will generate a return value of
421 2, which is roughly the limit of human perceptual
422 discrimination for chromatics.
423 */
424 return fabs (360.0 * (v - other.v));
425 }
426
427 if (is_gray() != other.is_gray()) {
428 /* no comparison possible */
429 return DBL_MAX;
430 }
431
432 /* Use CIE94 definition for now */
433
434 double sL, sA, sB;
435 double oL, oA, oB;
436 double r, g, b, alpha; // Careful, "a" is a field of this
437 Color c;
438
439 c = hsva_to_color (h, s, v, a);
440 color_to_rgba (c, r, g, b, alpha);
441 Rgb2Lab (&sL, &sA, &sB, r, g, b);
442
443 c = hsva_to_color (other.h, other.s, other.v, other.a);
444 color_to_rgba (c, r, g, b, alpha);
445 Rgb2Lab (&oL, &oA, &oB, r, g, b);
446
447 // Weighting factors depending on the application (1 = default)
448
449 const double whtL = 1.0;
450 const double whtC = 1.0;
451 const double whtH = 1.0;
452
453 const double xC1 = sqrt ((sA * sA) + (sB * oB));
454 const double xC2 = sqrt ((oA * oA) + (oB * oB));
455 double xDL = oL - sL;
456 double xDC = xC2 - xC1;
457 const double xDE = sqrt (((sL - oL) * (sL - oL))
458 + ((sA - oA) * (sA - oA))
459 + ((sB - oB) * (sB - oB)));
460
461 double xDH;
462
463 if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
464 xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
465 } else {
466 xDH = 0;
467 }
468
469 const double xSC = 1 + (0.045 * xC1);
470 const double xSH = 1 + (0.015 * xC1);
471
472 xDL /= whtL;
473 xDC /= whtC * xSC;
474 xDH /= whtH * xSH;
475
476 return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
477 }
478
479 HSV
opposite() const480 HSV::opposite () const
481 {
482 HSV hsv (*this);
483 hsv.h = fmod (h + 180.0, 360.0);
484 return hsv;
485 }
486
487 HSV
bw_text() const488 HSV::bw_text () const
489 {
490 return HSV (contrasting_text_color (color()));
491 }
492
493 HSV
text() const494 HSV::text () const
495 {
496 return opposite ();
497 }
498
499 HSV
selected() const500 HSV::selected () const
501 {
502 /* XXX hack */
503 return HSV (Color (0xff0000));
504 }
505
506
507 void
print(std::ostream & o) const508 HSV::print (std::ostream& o) const
509 {
510 if (!is_gray()) {
511 o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
512 } else {
513 o << "gray(" << v << ')';
514 }
515 }
516
517
operator <<(std::ostream & o,const Gtkmm2ext::HSV & hsv)518 std::ostream& operator<<(std::ostream& o, const Gtkmm2ext::HSV& hsv) { hsv.print (o); return o; }
519
520 HSV
mod(SVAModifier const & svam)521 HSV::mod (SVAModifier const & svam)
522 {
523 return svam (*this);
524 }
525
SVAModifier(string const & str)526 SVAModifier::SVAModifier (string const &str)
527 : type (Add)
528 , _s (0)
529 , _v (0)
530 , _a (0)
531 {
532 from_string (str);
533 }
534
535 void
from_string(string const & str)536 SVAModifier::from_string (string const & str)
537 {
538 char op;
539 stringstream ss (str);
540 string mod;
541
542 ss >> op;
543
544 switch (op) {
545 case '*':
546 type = Multiply;
547 /* no-op values for multiply */
548 _s = 1.0;
549 _v = 1.0;
550 _a = 1.0;
551 break;
552 case '+':
553 type = Add;
554 /* no-op values for add */
555 _s = 0.0;
556 _v = 0.0;
557 _a = 0.0;
558 break;
559 case '=':
560 type = Assign;
561 /* this will avoid assignment in operator() (see below) */
562 _s = -1.0;
563 _v = -1.0;
564 _a = -1.0;
565 break;
566 default:
567 throw failed_constructor ();
568 }
569
570 string::size_type pos;
571
572 while (ss) {
573 ss >> mod;
574 if ((pos = mod.find ("alpha:")) != string::npos) {
575 _a = PBD::string_to<double>(mod.substr (pos+6));
576 } else if ((pos = mod.find ("saturate:")) != string::npos) {
577 _s = PBD::string_to<double>(mod.substr (pos+9));
578 } else if ((pos = mod.find ("darkness:")) != string::npos) {
579 _v = PBD::string_to<double>(mod.substr (pos+9));
580 } else {
581 throw failed_constructor ();
582 }
583 }
584 }
585
586 string
to_string() const587 SVAModifier::to_string () const
588 {
589 stringstream ss;
590
591 switch (type) {
592 case Add:
593 ss << '+';
594 break;
595 case Multiply:
596 ss << '*';
597 break;
598 case Assign:
599 ss << '=';
600 break;
601 }
602
603 if (_s >= 0.0) {
604 ss << " saturate:" << PBD::to_string(_s);
605 }
606
607 if (_v >= 0.0) {
608 ss << " darker:" << PBD::to_string(_v);
609 }
610
611 if (_a >= 0.0) {
612 ss << " alpha:" << PBD::to_string(_a);
613 }
614
615 return ss.str();
616 }
617
618 HSV
operator ()(HSV & hsv) const619 SVAModifier::operator () (HSV& hsv) const
620 {
621 HSV r (hsv);
622
623 switch (type) {
624 case Add:
625 r.s += _s;
626 r.v += _v;
627 r.a += _a;
628 break;
629 case Multiply:
630 r.s *= _s;
631 r.v *= _v;
632 r.a *= _a;
633 break;
634 case Assign:
635 if (_s >= 0.0) {
636 r.s = _s;
637 }
638 if (_v >= 0.) {
639 r.v = _v;
640 }
641 if (_a >= 0.0) {
642 r.a = _a;
643 }
644 break;
645 }
646
647 return r;
648 }
649
650 Color
color_at_alpha(Gtkmm2ext::Color c,double a)651 Gtkmm2ext::color_at_alpha (Gtkmm2ext::Color c, double a)
652 {
653 double r, g, b, unused;
654 color_to_rgba (c, r, g, b, unused);
655 return rgba_to_color( r,g,b, a );
656 }
657
658 void
set_source_rgba(Cairo::RefPtr<Cairo::Context> context,Color color)659 Gtkmm2ext::set_source_rgba (Cairo::RefPtr<Cairo::Context> context, Color color)
660 {
661 context->set_source_rgba (
662 ((color >> 24) & 0xff) / 255.0,
663 ((color >> 16) & 0xff) / 255.0,
664 ((color >> 8) & 0xff) / 255.0,
665 ((color >> 0) & 0xff) / 255.0
666 );
667 }
668
669 void
set_source_rgb_a(Cairo::RefPtr<Cairo::Context> context,Color color,float alpha)670 Gtkmm2ext::set_source_rgb_a (Cairo::RefPtr<Cairo::Context> context, Color color, float alpha)
671 {
672 context->set_source_rgba (
673 ((color >> 24) & 0xff) / 255.0,
674 ((color >> 16) & 0xff) / 255.0,
675 ((color >> 8) & 0xff) / 255.0,
676 alpha
677 );
678 }
679
680 void
set_source_rgba(cairo_t * cr,Color color)681 Gtkmm2ext::set_source_rgba (cairo_t *cr, Color color)
682 {
683 cairo_set_source_rgba ( cr,
684 ((color >> 24) & 0xff) / 255.0,
685 ((color >> 16) & 0xff) / 255.0,
686 ((color >> 8) & 0xff) / 255.0,
687 ((color >> 0) & 0xff) / 255.0
688 );
689 }
690
691 void
set_source_rgb_a(cairo_t * cr,Color color,float alpha)692 Gtkmm2ext::set_source_rgb_a (cairo_t *cr, Color color, float alpha)
693 {
694 cairo_set_source_rgba ( cr,
695 ((color >> 24) & 0xff) / 255.0,
696 ((color >> 16) & 0xff) / 255.0,
697 ((color >> 8) & 0xff) / 255.0,
698 alpha
699 );
700 }
701