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