more fixes to SVAModifier constructor(s)
[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 (0)
533         , v (0)
534         , a (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                 /* no-op values for multiply */
552                 s = 1.0;
553                 v = 1.0;
554                 a = 1.0;
555                 break;
556         case '+':
557                 type = Add;
558                 /* no-op values for add */
559                 s = 0.0;
560                 v = 0.0;
561                 a = 1.0;
562                 break;
563         case '=':
564                 type = Assign;
565                 /* this will avoid assignment in operator() (see below) */
566                 s = -1.0;
567                 v = -1.0;
568                 a = -1.0;
569                 break;
570         default:
571                 throw failed_constructor ();
572         }
573
574         string::size_type pos;
575
576         while (ss) {
577                 ss >> mod;
578                 if ((pos = mod.find ("alpha:")) != string::npos) {
579                         a = PBD::atof (mod.substr (pos+6));
580                 } else if ((pos = mod.find ("saturate:")) != string::npos) {
581                         s = PBD::atof (mod.substr (pos+9));
582                 } else if ((pos = mod.find ("darkness:")) != string::npos) {
583                         v = PBD::atof (mod.substr (pos+9));
584                 } else {
585                         throw failed_constructor ();
586                 }
587         }
588 }
589
590 string
591 SVAModifier::to_string () const
592 {
593         PBD::LocaleGuard lg ("POSIX");
594         stringstream ss;
595
596         switch (type) {
597         case Add:
598                 ss << '+';
599                 break;
600         case Multiply:
601                 ss << '*';
602                 break;
603         case Assign:
604                 ss << '=';
605                 break;
606         }
607
608         if (s > -1.0) {
609                 ss << " saturate:" << s;
610         }
611
612         if (v > -1.0) {
613                 ss << " darker:" << v;
614         }
615
616         if (a > -1.0) {
617                 ss << " alpha:" << a;
618         }
619
620         return ss.str();
621 }
622
623 HSV
624 SVAModifier::operator () (HSV& hsv)  const
625 {
626         HSV r (hsv);
627         
628         switch (type) {
629         case Add:
630                 r.s += s;
631                 r.v += v;
632                 r.a += a;
633                 break;
634         case Multiply:
635                 r.s *= s;
636                 r.v *= v;
637                 r.a *= a;
638                 break;
639         case Assign:
640                 if (s > -1.0) {
641                         r.s = s;
642                 }
643                 if (v > -1.0) {
644                         r.v = v;
645                 }
646                 if (a > -1.0) {
647                         r.a = a;
648                 }
649                 break;
650         }
651
652         return r;
653 }
654