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