Fix calculation using uninitialized value.
[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 <cmath>
21 #include <stdint.h>
22 #include <cfloat>
23
24 #include "canvas/colors.h"
25 #include "canvas/colorspace.h"
26
27 using namespace std;
28 using namespace ArdourCanvas;
29
30 using std::max;
31 using std::min;
32
33 void
34 ArdourCanvas::color_to_hsv (Color color, double& h, double& s, double& v)
35 {
36         double a;
37         color_to_hsva (color, h, s, v, a);
38 }
39
40 void
41 ArdourCanvas::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
42 {
43         double r, g, b;
44         double cmax;
45         double cmin;
46         double delta;
47         
48         color_to_rgba (color, r, g, b, a);
49         
50         if (r > g) {
51                 cmax = max (r, b);
52         } else {
53                 cmax = max (g, b);
54         }
55
56         if (r < g) {
57                 cmin = min (r, b);
58         } else {
59                 cmin = min (g, b);
60         }
61
62         v = cmax;
63
64         delta = cmax - cmin;
65
66         if (cmax == 0) {
67                 // r = g = b == 0 ... v is undefined, s = 0
68                 s = 0.0;  
69                 h = 0.0;
70                 return;
71         }
72
73         if (delta != 0.0) {     
74                 if (cmax == r) {
75                         h = fmod ((g - b)/delta, 6.0);
76                 } else if (cmax == g) {
77                         h = ((b - r)/delta) + 2;
78                 } else {
79                         h = ((r - g)/delta) + 4;
80                 }
81                 
82                 h *= 60.0;
83                 
84                 if (h < 0.0) {
85                         /* negative values are legal but confusing, because
86                            they alias positive values.
87                         */
88                         h = 360 + h;
89                 }
90         }
91
92         if (delta == 0 || cmax == 0) {
93                 s = 0;
94         } else {
95                 s = delta / cmax;
96         }
97 }
98
99 ArdourCanvas::Color
100 ArdourCanvas::hsva_to_color (double h, double s, double v, double a)
101 {
102         s = min (1.0, max (0.0, s));
103         v = min (1.0, max (0.0, v));
104
105         if (s == 0) {
106                 return rgba_to_color (v, v, v, a);
107         }
108
109         h = fmod (h + 360.0, 360.0);
110
111         double c = v * s;
112         double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
113         double m = v - c;
114
115         if (h >= 0.0 && h < 60.0) {
116                 return rgba_to_color (c + m, x + m, m, a);
117         } else if (h >= 60.0 && h < 120.0) {
118                 return rgba_to_color (x + m, c + m, m, a);
119         } else if (h >= 120.0 && h < 180.0) {
120                 return rgba_to_color (m, c + m, x + m, a);
121         } else if (h >= 180.0 && h < 240.0) {
122                 return rgba_to_color (m, x + m, c + m, a);
123         } else if (h >= 240.0 && h < 300.0) {
124                 return rgba_to_color (x + m, m, c + m, a);
125         } else if (h >= 300.0 && h < 360.0) {
126                 return rgba_to_color (c + m, m, x + m, a);
127         } 
128         return rgba_to_color (m, m, m, a);
129 }
130
131 void
132 ArdourCanvas::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
133 {
134         r = ((color >> 24) & 0xff) / 255.0;
135         g = ((color >> 16) & 0xff) / 255.0;
136         b = ((color >>  8) & 0xff) / 255.0;
137         a = ((color >>  0) & 0xff) / 255.0;
138 }
139
140 ArdourCanvas::Color
141 ArdourCanvas::rgba_to_color (double r, double g, double b, double a)
142 {
143         /* clamp to [0 .. 1] range */
144
145         r = min (1.0, max (0.0, r));
146         g = min (1.0, max (0.0, g));
147         b = min (1.0, max (0.0, b));
148         a = min (1.0, max (0.0, a));
149
150         /* convert to [0..255] range */
151
152         unsigned int rc, gc, bc, ac;
153         rc = rint (r * 255.0);
154         gc = rint (g * 255.0);
155         bc = rint (b * 255.0);
156         ac = rint (a * 255.0);
157
158         /* build-an-integer */
159
160         return (rc << 24) | (gc << 16) | (bc << 8) | ac;
161 }
162
163 // Inverse of sRGB "gamma" function.
164 static inline double 
165 inv_gam_sRGB (double c) 
166 {
167         if (c <= 0.04045) {
168                 return c/12.92;
169         } else {
170                 return pow(((c+0.055)/(1.055)),2.4);
171         }
172 }
173
174 // sRGB "gamma" function
175 static inline int 
176 gam_sRGB(double v) 
177 {
178         if (v <= 0.0031308) {
179                 v *= 12.92;
180         } else {
181                 v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
182         }
183         return int (v*255+.5);
184 }
185
186 static double 
187 luminance (uint32_t c)
188 {
189         // sRGB luminance(Y) values
190         const double rY = 0.212655;
191         const double gY = 0.715158;
192         const double bY = 0.072187;
193
194         double r, g, b, a;
195
196         ArdourCanvas::color_to_rgba (c, r, g, b, a);
197         
198         return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
199 }    
200
201 uint32_t
202 ArdourCanvas::contrasting_text_color (uint32_t c)
203 {
204         static const uint32_t white = ArdourCanvas::rgba_to_color (1.0, 1.0, 1.0, 1.0);
205         static const uint32_t black = ArdourCanvas::rgba_to_color (0.0, 0.0, 0.0, 1.0);
206
207         return (luminance (c) < 0.50) ? white : black;
208 }
209
210
211
212 HSV::HSV ()
213         : h (0.0)
214         , s (1.0)
215         , v (1.0)
216         , a (1.0) 
217 {
218 }
219
220 HSV::HSV (double hh, double ss, double vv, double aa)
221         : h (hh)
222         , s (ss)
223         , v (vv)
224         , a (aa) 
225 {
226         if (h < 0.0) {
227                 /* normalize negative hue values into positive range */
228                 h = 360.0 + h;
229         }
230 }
231
232 HSV::HSV (Color c)
233 {
234         color_to_hsva (c, h, s, v, a);
235 }
236
237 bool
238 HSV::is_gray () const
239 {
240         return s == 0;
241 }
242
243 void
244 HSV::clamp ()
245 {
246         h = fmod (h, 360.0);
247         if (h < 0.0) {
248                 /* normalize negative hue values into positive range */
249                 h = 360.0 + h;
250         }
251         s = min (1.0, s);
252         v = min (1.0, v);
253         a = min (1.0, a);
254 }
255
256 HSV
257 HSV::operator+ (const HSV& operand) const
258 {
259         HSV hsv;
260         hsv.h = h + operand.h;
261         hsv.s = s + operand.s;
262         hsv.v = v + operand.v;
263         hsv.a = a + operand.a;
264         hsv.clamp ();
265         return hsv;
266 }
267
268 HSV
269 HSV::operator- (const HSV& operand) const
270 {
271         HSV hsv;
272         hsv.h = h - operand.h;
273         hsv.s = s - operand.s;
274         hsv.v = s - operand.v;
275         hsv.a = a - operand.a;
276         hsv.clamp ();
277         return hsv;
278 }
279
280 HSV&
281 HSV::operator=(Color c)
282 {
283         color_to_hsva (c, h, s, v, a);
284         clamp ();
285         return *this;
286 }
287
288 HSV&
289 HSV::operator=(const std::string& str)
290 {
291         uint32_t c;
292         c = strtol (str.c_str(), 0, 16);
293         color_to_hsva (c, h, s, v, a);
294         clamp ();
295         return *this;
296 }
297
298 bool
299 HSV::operator== (const HSV& other)
300 {
301         return h == other.h &&
302                 s == other.s &&
303                 v == other.v &&
304                 a == other.a;
305 }
306
307 HSV
308 HSV::shade (double factor) const
309 {
310         HSV hsv (*this);
311         
312         /* algorithm derived from a google palette website
313            and analysis of their color palettes.
314
315            basic rule: to make a color darker, increase its saturation 
316            until it reaches 88%, but then additionally reduce value/lightness 
317            by a larger amount.
318
319            invert rule to make a color lighter.
320         */
321
322         if (factor > 1.0) {
323                 if (s < 88) {
324                         hsv.v += (hsv.v * (factor * 10.0));
325                 } 
326                 hsv.s *= factor;
327         } else {
328                 if (s < 88) {
329                         hsv.v -= (hsv.v * (factor * 10.0));
330                 } 
331                 hsv.s *= factor;
332         }
333
334         hsv.clamp();
335
336         return hsv;
337 }
338
339 HSV
340 HSV::outline () const
341 {
342         if (luminance (color()) < 0.50) {
343                 /* light color, darker outline: black with 15% opacity */
344                 return HSV (0.0, 0.0, 0.0, 0.15);
345         } else {
346                 /* dark color, lighter outline: white with 15% opacity */
347                 return HSV (0.0, 0.0, 1.0, 0.15);
348         }
349 }
350
351 HSV
352 HSV::mix (const HSV& other, double amount) const
353 {
354         HSV hsv;
355
356         hsv.h = h + (amount * (other.h - h));
357         hsv.v = v + (amount * (other.s - s));
358         hsv.s = s + (amount * (other.v - v));
359
360         hsv.clamp();
361
362         return hsv;
363 }
364
365 HSV
366 HSV::delta (const HSV& other) const
367 {
368         HSV d;
369
370         if (is_gray() && other.is_gray()) {
371                 d.h = 0.0;
372                 d.s = 0.0;
373                 d.v = v - other.v;
374         } else {
375                 d.h = h - other.h;
376                 d.s = s - other.s;
377                 d.v = v - other.v;
378         }
379         /* do not clamp - we are returning a delta */
380         return d;
381 }
382
383 double
384 HSV::distance (const HSV& other) const
385 {
386         if (is_gray() && other.is_gray()) {
387                 /* human color perception of achromatics generates about 450
388                    distinct colors. By contrast, CIE94 could give a maximal
389                    perceptual distance of sqrt ((360^2) + 1 + 1) = 360. The 450 
390                    are not evenly spread (Webers Law), so lets use 360 as an
391                    approximation of the number of distinct achromatics.
392                    
393                    So, scale up the achromatic difference to give about
394                    a maximal distance between v = 1.0 and v = 0.0 of 360.
395                    
396                    A difference of about 0.0055 will generate a return value of
397                    2, which is roughly the limit of human perceptual
398                    discrimination for chromatics.
399                 */
400                 return fabs (360.0 * (v - other.v));
401         }
402
403         if (is_gray() != other.is_gray()) {
404                 /* no comparison possible */
405                 return DBL_MAX;
406         }
407
408         /* Use CIE94 definition for now */
409
410         double sL, sA, sB;
411         double oL, oA, oB;
412         double r, g, b, alpha;  // Careful, "a" is a field of this
413         Color c; 
414
415         c = hsva_to_color (h, s, v, a);
416         color_to_rgba (c, r, g, b, alpha);
417         Rgb2Lab (&sL, &sA, &sB, r, g, b);
418
419         c = hsva_to_color (other.h, other.s, other.v, other.a);
420         color_to_rgba (c, r, g, b, alpha);
421         Rgb2Lab (&oL, &oA, &oB, r, g, b);
422
423         // Weighting factors depending on the application (1 = default)
424
425         const double whtL = 1.0;
426         const double whtC = 1.0;
427         const double whtH = 1.0;  
428
429         const double xC1 = sqrt ((sA * sA) + (sB * oB));
430         const double xC2 = sqrt ((oA * oA) + (oB * oB));
431         double xDL = oL - sL;
432         double xDC = xC2 - xC1;
433         const double xDE = sqrt (((sL - oL) * (sL - oL))
434                                  + ((sA - oA) * (sA - oA))
435                                  + ((sB - oB) * (sB - oB)));
436         
437         double xDH;
438
439         if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
440                 xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
441         } else {
442                 xDH = 0;
443         }
444
445         const double xSC = 1 + (0.045 * xC1);
446         const double xSH = 1 + (0.015 * xC1);
447
448         xDL /= whtL;
449         xDC /= whtC * xSC;
450         xDH /= whtH * xSH;
451         
452         return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
453 }
454
455 HSV
456 HSV::opposite () const
457 {
458         HSV hsv (*this);
459         hsv.h = fmod (h + 180.0, 360.0);
460         return hsv;
461 }
462
463 HSV
464 HSV::bw_text () const
465 {
466         return HSV (contrasting_text_color (color()));
467 }
468
469 HSV
470 HSV::text () const
471 {
472         return opposite ();
473 }
474
475 HSV
476 HSV::selected () const
477 {
478         /* XXX hack */
479         return HSV (Color (0xff0000));
480 }
481
482
483 void
484 HSV::print (std::ostream& o) const
485 {
486         if (!is_gray()) {
487                 o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
488         } else {
489                 o << "gray(" << v << ')';
490         }
491 }
492
493
494 std::ostream& operator<<(std::ostream& o, const ArdourCanvas::HSV& hsv) { hsv.print (o); return o; }