Prepare session-metadata export to external command
[ardour.git] / libs / gtkmm2ext / colors.cc
1 /*
2     Copyright (C) 2014 Paul Davis
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
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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
38 Gtkmm2ext::change_alpha (Color c, double a)
39 {
40         return ((c & ~0xff) | (lrintf (a*255.0) & 0xff));
41 }
42
43 void
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
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
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
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
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
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
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
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
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
224 HSV::HSV ()
225         : h (0.0)
226         , s (1.0)
227         , v (1.0)
228         , a (1.0)
229 {
230 }
231
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
244 HSV::HSV (Color c)
245 {
246         color_to_hsva (c, h, s, v, a);
247 }
248
249 string
250 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
261 HSV::is_gray () const
262 {
263         return s == 0;
264 }
265
266 void
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
280 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
292 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&
304 HSV::operator=(Color c)
305 {
306         color_to_hsva (c, h, s, v, a);
307         clamp ();
308         return *this;
309 }
310
311 HSV&
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
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
331 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
363 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
375 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
389 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
408 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
480 HSV::opposite () const
481 {
482         HSV hsv (*this);
483         hsv.h = fmod (h + 180.0, 360.0);
484         return hsv;
485 }
486
487 HSV
488 HSV::bw_text () const
489 {
490         return HSV (contrasting_text_color (color()));
491 }
492
493 HSV
494 HSV::text () const
495 {
496         return opposite ();
497 }
498
499 HSV
500 HSV::selected () const
501 {
502         /* XXX hack */
503         return HSV (Color (0xff0000));
504 }
505
506
507 void
508 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
518 std::ostream& operator<<(std::ostream& o, const Gtkmm2ext::HSV& hsv) { hsv.print (o); return o; }
519
520 HSV
521 HSV::mod (SVAModifier const & svam)
522 {
523         return svam (*this);
524 }
525
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
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
587 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
619 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
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
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
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
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
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 }