canvas HSV color serialization needs LocaleGuard
[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         PBD::LocaleGuard lg;
263         stringstream ss;
264         ss << h << ' ';
265         ss << s << ' ';
266         ss << v << ' ';
267         ss << a;
268         return ss.str();
269 }
270
271 bool
272 HSV::is_gray () const
273 {
274         return s == 0;
275 }
276
277 void
278 HSV::clamp ()
279 {
280         h = fmod (h, 360.0);
281         if (h < 0.0) {
282                 /* normalize negative hue values into positive range */
283                 h = 360.0 + h;
284         }
285         s = min (1.0, s);
286         v = min (1.0, v);
287         a = min (1.0, a);
288 }
289
290 HSV
291 HSV::operator+ (const HSV& operand) const
292 {
293         HSV hsv;
294         hsv.h = h + operand.h;
295         hsv.s = s + operand.s;
296         hsv.v = v + operand.v;
297         hsv.a = a + operand.a;
298         hsv.clamp ();
299         return hsv;
300 }
301
302 HSV
303 HSV::operator- (const HSV& operand) const
304 {
305         HSV hsv;
306         hsv.h = h - operand.h;
307         hsv.s = s - operand.s;
308         hsv.v = s - operand.v;
309         hsv.a = a - operand.a;
310         hsv.clamp ();
311         return hsv;
312 }
313
314 HSV&
315 HSV::operator=(Color c)
316 {
317         color_to_hsva (c, h, s, v, a);
318         clamp ();
319         return *this;
320 }
321
322 HSV&
323 HSV::operator=(const std::string& str)
324 {
325         uint32_t c;
326         c = strtol (str.c_str(), 0, 16);
327         color_to_hsva (c, h, s, v, a);
328         clamp ();
329         return *this;
330 }
331
332 bool
333 HSV::operator== (const HSV& other)
334 {
335         return h == other.h &&
336                 s == other.s &&
337                 v == other.v &&
338                 a == other.a;
339 }
340
341 HSV
342 HSV::shade (double factor) const
343 {
344         HSV hsv (*this);
345
346         /* algorithm derived from a google palette website
347            and analysis of their color palettes.
348
349            basic rule: to make a color darker, increase its saturation
350            until it reaches 88%, but then additionally reduce value/lightness
351            by a larger amount.
352
353            invert rule to make a color lighter.
354         */
355
356         if (factor > 1.0) {
357                 if (s < 88) {
358                         hsv.v += (hsv.v * (factor * 10.0));
359                 }
360                 hsv.s *= factor;
361         } else {
362                 if (s < 88) {
363                         hsv.v -= (hsv.v * (factor * 10.0));
364                 }
365                 hsv.s *= factor;
366         }
367
368         hsv.clamp();
369
370         return hsv;
371 }
372
373 HSV
374 HSV::outline () const
375 {
376         if (luminance (color()) < 0.50) {
377                 /* light color, darker outline: black with 15% opacity */
378                 return HSV (0.0, 0.0, 0.0, 0.15);
379         } else {
380                 /* dark color, lighter outline: white with 15% opacity */
381                 return HSV (0.0, 0.0, 1.0, 0.15);
382         }
383 }
384
385 HSV
386 HSV::mix (const HSV& other, double amount) const
387 {
388         HSV hsv;
389
390         hsv.h = h + (amount * (other.h - h));
391         hsv.v = v + (amount * (other.s - s));
392         hsv.s = s + (amount * (other.v - v));
393
394         hsv.clamp();
395
396         return hsv;
397 }
398
399 HSV
400 HSV::delta (const HSV& other) const
401 {
402         HSV d;
403
404         if (is_gray() && other.is_gray()) {
405                 d.h = 0.0;
406                 d.s = 0.0;
407                 d.v = v - other.v;
408         } else {
409                 d.h = h - other.h;
410                 d.s = s - other.s;
411                 d.v = v - other.v;
412         }
413         d.a = a - other.a;
414         /* do not clamp - we are returning a delta */
415         return d;
416 }
417
418 double
419 HSV::distance (const HSV& other) const
420 {
421         if (is_gray() && other.is_gray()) {
422                 /* human color perception of achromatics generates about 450
423                    distinct colors. By contrast, CIE94 could give a maximal
424                    perceptual distance of sqrt ((360^2) + 1 + 1) = 360. The 450
425                    are not evenly spread (Webers Law), so lets use 360 as an
426                    approximation of the number of distinct achromatics.
427
428                    So, scale up the achromatic difference to give about
429                    a maximal distance between v = 1.0 and v = 0.0 of 360.
430
431                    A difference of about 0.0055 will generate a return value of
432                    2, which is roughly the limit of human perceptual
433                    discrimination for chromatics.
434                 */
435                 return fabs (360.0 * (v - other.v));
436         }
437
438         if (is_gray() != other.is_gray()) {
439                 /* no comparison possible */
440                 return DBL_MAX;
441         }
442
443         /* Use CIE94 definition for now */
444
445         double sL, sA, sB;
446         double oL, oA, oB;
447         double r, g, b, alpha;  // Careful, "a" is a field of this
448         Color c;
449
450         c = hsva_to_color (h, s, v, a);
451         color_to_rgba (c, r, g, b, alpha);
452         Rgb2Lab (&sL, &sA, &sB, r, g, b);
453
454         c = hsva_to_color (other.h, other.s, other.v, other.a);
455         color_to_rgba (c, r, g, b, alpha);
456         Rgb2Lab (&oL, &oA, &oB, r, g, b);
457
458         // Weighting factors depending on the application (1 = default)
459
460         const double whtL = 1.0;
461         const double whtC = 1.0;
462         const double whtH = 1.0;
463
464         const double xC1 = sqrt ((sA * sA) + (sB * oB));
465         const double xC2 = sqrt ((oA * oA) + (oB * oB));
466         double xDL = oL - sL;
467         double xDC = xC2 - xC1;
468         const double xDE = sqrt (((sL - oL) * (sL - oL))
469                                  + ((sA - oA) * (sA - oA))
470                                  + ((sB - oB) * (sB - oB)));
471
472         double xDH;
473
474         if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
475                 xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
476         } else {
477                 xDH = 0;
478         }
479
480         const double xSC = 1 + (0.045 * xC1);
481         const double xSH = 1 + (0.015 * xC1);
482
483         xDL /= whtL;
484         xDC /= whtC * xSC;
485         xDH /= whtH * xSH;
486
487         return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
488 }
489
490 HSV
491 HSV::opposite () const
492 {
493         HSV hsv (*this);
494         hsv.h = fmod (h + 180.0, 360.0);
495         return hsv;
496 }
497
498 HSV
499 HSV::bw_text () const
500 {
501         return HSV (contrasting_text_color (color()));
502 }
503
504 HSV
505 HSV::text () const
506 {
507         return opposite ();
508 }
509
510 HSV
511 HSV::selected () const
512 {
513         /* XXX hack */
514         return HSV (Color (0xff0000));
515 }
516
517
518 void
519 HSV::print (std::ostream& o) const
520 {
521         if (!is_gray()) {
522                 o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
523         } else {
524                 o << "gray(" << v << ')';
525         }
526 }
527
528
529 std::ostream& operator<<(std::ostream& o, const ArdourCanvas::HSV& hsv) { hsv.print (o); return o; }
530
531 HSV
532 HSV::mod (SVAModifier const & svam)
533 {
534         return svam (*this);
535 }
536
537 SVAModifier::SVAModifier (string const &str)
538         : type (Add)
539         , _s (0)
540         , _v (0)
541         , _a (0)
542 {
543         from_string (str);
544 }
545
546 void
547 SVAModifier::from_string (string const & str)
548 {
549         char op;
550         stringstream ss (str);
551         string mod;
552
553         ss >> op;
554
555         switch (op) {
556         case '*':
557                 type = Multiply;
558                 /* no-op values for multiply */
559                 _s = 1.0;
560                 _v = 1.0;
561                 _a = 1.0;
562                 break;
563         case '+':
564                 type = Add;
565                 /* no-op values for add */
566                 _s = 0.0;
567                 _v = 0.0;
568                 _a = 0.0;
569                 break;
570         case '=':
571                 type = Assign;
572                 /* this will avoid assignment in operator() (see below) */
573                 _s = -1.0;
574                 _v = -1.0;
575                 _a = -1.0;
576                 break;
577         default:
578                 throw failed_constructor ();
579         }
580
581         string::size_type pos;
582
583         while (ss) {
584                 ss >> mod;
585                 if ((pos = mod.find ("alpha:")) != string::npos) {
586                         _a = PBD::atof (mod.substr (pos+6));
587                 } else if ((pos = mod.find ("saturate:")) != string::npos) {
588                         _s = PBD::atof (mod.substr (pos+9));
589                 } else if ((pos = mod.find ("darkness:")) != string::npos) {
590                         _v = PBD::atof (mod.substr (pos+9));
591                 } else {
592                         throw failed_constructor ();
593                 }
594         }
595 }
596
597 string
598 SVAModifier::to_string () const
599 {
600         PBD::LocaleGuard lg;
601         stringstream ss;
602
603         switch (type) {
604         case Add:
605                 ss << '+';
606                 break;
607         case Multiply:
608                 ss << '*';
609                 break;
610         case Assign:
611                 ss << '=';
612                 break;
613         }
614
615         if (_s >= 0.0) {
616                 ss << " saturate:" << _s;
617         }
618
619         if (_v >= 0.0) {
620                 ss << " darker:" << _v;
621         }
622
623         if (_a >= 0.0) {
624                 ss << " alpha:" << _a;
625         }
626
627         return ss.str();
628 }
629
630 HSV
631 SVAModifier::operator () (HSV& hsv)  const
632 {
633         HSV r (hsv);
634
635         switch (type) {
636         case Add:
637                 r.s += _s;
638                 r.v += _v;
639                 r.a += _a;
640                 break;
641         case Multiply:
642                 r.s *= _s;
643                 r.v *= _v;
644                 r.a *= _a;
645                 break;
646         case Assign:
647                 if (_s >= 0.0) {
648                         r.s = _s;
649                 }
650                 if (_v >= 0.) {
651                         r.v = _v;
652                 }
653                 if (_a >= 0.0) {
654                         r.a = _a;
655                 }
656                 break;
657         }
658
659         return r;
660 }
661
662 ArdourCanvas::Color
663 ArdourCanvas::color_at_alpha (ArdourCanvas::Color c, double a)
664 {
665         double r, g, b, unused;
666         color_to_rgba (c, r, g, b, unused);
667         return rgba_to_color( r,g,b, a );
668 }