lots more color work, closer and closer to being ready for ... being taken apart...
[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 "canvas/colors.h"
26 #include "canvas/colorspace.h"
27
28 using namespace std;
29 using namespace ArdourCanvas;
30
31 using std::max;
32 using std::min;
33
34 void
35 ArdourCanvas::color_to_hsv (Color color, double& h, double& s, double& v)
36 {
37         double a;
38         color_to_hsva (color, h, s, v, a);
39 }
40
41 void
42 ArdourCanvas::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
43 {
44         double r, g, b;
45         double cmax;
46         double cmin;
47         double delta;
48         
49         color_to_rgba (color, r, g, b, a);
50         
51         if (r > g) {
52                 cmax = max (r, b);
53         } else {
54                 cmax = max (g, b);
55         }
56
57         if (r < g) {
58                 cmin = min (r, b);
59         } else {
60                 cmin = min (g, b);
61         }
62
63         v = cmax;
64
65         delta = cmax - cmin;
66
67         if (cmax == 0) {
68                 // r = g = b == 0 ... v is undefined, s = 0
69                 s = 0.0;  
70                 h = 0.0;
71                 return;
72         }
73
74         if (delta != 0.0) {     
75                 if (cmax == r) {
76                         h = fmod ((g - b)/delta, 6.0);
77                 } else if (cmax == g) {
78                         h = ((b - r)/delta) + 2;
79                 } else {
80                         h = ((r - g)/delta) + 4;
81                 }
82                 
83                 h *= 60.0;
84                 
85                 if (h < 0.0) {
86                         /* negative values are legal but confusing, because
87                            they alias positive values.
88                         */
89                         h = 360 + h;
90                 }
91         }
92
93         if (delta == 0 || cmax == 0) {
94                 s = 0;
95         } else {
96                 s = delta / cmax;
97         }
98 }
99
100 ArdourCanvas::Color
101 ArdourCanvas::hsva_to_color (double h, double s, double v, double a)
102 {
103         s = min (1.0, max (0.0, s));
104         v = min (1.0, max (0.0, v));
105
106         if (s == 0) {
107                 return rgba_to_color (v, v, v, a);
108         }
109
110         h = fmod (h + 360.0, 360.0);
111
112         double c = v * s;
113         double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
114         double m = v - c;
115
116         if (h >= 0.0 && h < 60.0) {
117                 return rgba_to_color (c + m, x + m, m, a);
118         } else if (h >= 60.0 && h < 120.0) {
119                 return rgba_to_color (x + m, c + m, m, a);
120         } else if (h >= 120.0 && h < 180.0) {
121                 return rgba_to_color (m, c + m, x + m, a);
122         } else if (h >= 180.0 && h < 240.0) {
123                 return rgba_to_color (m, x + m, c + m, a);
124         } else if (h >= 240.0 && h < 300.0) {
125                 return rgba_to_color (x + m, m, c + m, a);
126         } else if (h >= 300.0 && h < 360.0) {
127                 return rgba_to_color (c + m, m, x + m, a);
128         } 
129         return rgba_to_color (m, m, m, a);
130 }
131
132 void
133 ArdourCanvas::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
134 {
135         r = ((color >> 24) & 0xff) / 255.0;
136         g = ((color >> 16) & 0xff) / 255.0;
137         b = ((color >>  8) & 0xff) / 255.0;
138         a = ((color >>  0) & 0xff) / 255.0;
139 }
140
141 ArdourCanvas::Color
142 ArdourCanvas::rgba_to_color (double r, double g, double b, double a)
143 {
144         /* clamp to [0 .. 1] range */
145
146         r = min (1.0, max (0.0, r));
147         g = min (1.0, max (0.0, g));
148         b = min (1.0, max (0.0, b));
149         a = min (1.0, max (0.0, a));
150
151         /* convert to [0..255] range */
152
153         unsigned int rc, gc, bc, ac;
154         rc = rint (r * 255.0);
155         gc = rint (g * 255.0);
156         bc = rint (b * 255.0);
157         ac = rint (a * 255.0);
158
159         /* build-an-integer */
160
161         return (rc << 24) | (gc << 16) | (bc << 8) | ac;
162 }
163
164 // Inverse of sRGB "gamma" function.
165 static inline double 
166 inv_gam_sRGB (double c) 
167 {
168         if (c <= 0.04045) {
169                 return c/12.92;
170         } else {
171                 return pow(((c+0.055)/(1.055)),2.4);
172         }
173 }
174
175 // sRGB "gamma" function
176 static inline int 
177 gam_sRGB(double v) 
178 {
179         if (v <= 0.0031308) {
180                 v *= 12.92;
181         } else {
182                 v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
183         }
184         return int (v*255+.5);
185 }
186
187 static double 
188 luminance (uint32_t c)
189 {
190         // sRGB luminance(Y) values
191         const double rY = 0.212655;
192         const double gY = 0.715158;
193         const double bY = 0.072187;
194
195         double r, g, b, a;
196
197         ArdourCanvas::color_to_rgba (c, r, g, b, a);
198         
199         return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
200 }    
201
202 uint32_t
203 ArdourCanvas::contrasting_text_color (uint32_t c)
204 {
205         /* use a slightly off-white... XXX should really look this up */
206
207         static const uint32_t white = ArdourCanvas::rgba_to_color (0.98, 0.98, 0.98, 1.0);
208         static const uint32_t black = ArdourCanvas::rgba_to_color (0.0, 0.0, 0.0, 1.0);
209
210         return (luminance (c) < 0.50) ? white : black;
211 }
212
213
214
215 HSV::HSV ()
216         : h (0.0)
217         , s (1.0)
218         , v (1.0)
219         , a (1.0) 
220 {
221 }
222
223 HSV::HSV (double hh, double ss, double vv, double aa)
224         : h (hh)
225         , s (ss)
226         , v (vv)
227         , a (aa) 
228 {
229         if (h < 0.0) {
230                 /* normalize negative hue values into positive range */
231                 h = 360.0 + h;
232         }
233 }
234
235 HSV::HSV (Color c)
236 {
237         color_to_hsva (c, h, s, v, a);
238 }
239
240 HSV::HSV (const std::string& str)
241 {
242         stringstream ss (str);
243         ss >> h;
244         ss >> s;
245         ss >> v;
246         ss >> a;
247 }
248
249 string
250 HSV::to_string () const
251 {
252         stringstream ss;
253         ss << h << ' ';
254         ss << s << ' ';
255         ss << v << ' ';
256         ss << 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 ArdourCanvas::HSV& hsv) { hsv.print (o); return o; }