2 Copyright (C) 2014 Paul Davis
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.
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.
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.
25 #include "pbd/convert.h"
26 #include "pbd/failed_constructor.h"
27 #include "pbd/locale_guard.h"
29 #include "canvas/colors.h"
30 #include "canvas/colorspace.h"
33 using namespace ArdourCanvas;
39 change_alpha (Color c, double a)
41 return ((c & ~0xff) | (lrintf (a*255.0) & 0xff));
45 ArdourCanvas::color_to_hsv (Color color, double& h, double& s, double& v)
48 color_to_hsva (color, h, s, v, a);
52 ArdourCanvas::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
59 color_to_rgba (color, r, g, b, a);
78 // r = g = b == 0 ... v is undefined, s = 0
86 h = fmod ((g - b)/delta, 6.0);
87 } else if (cmax == g) {
88 h = ((b - r)/delta) + 2;
90 h = ((r - g)/delta) + 4;
96 /* negative values are legal but confusing, because
97 they alias positive values.
103 if (delta == 0 || cmax == 0) {
111 ArdourCanvas::hsva_to_color (double h, double s, double v, double a)
113 s = min (1.0, max (0.0, s));
114 v = min (1.0, max (0.0, v));
117 return rgba_to_color (v, v, v, a);
120 h = fmod (h + 360.0, 360.0);
123 double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
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);
139 return rgba_to_color (m, m, m, a);
143 ArdourCanvas::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
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;
152 ArdourCanvas::rgba_to_color (double r, double g, double b, double a)
154 /* clamp to [0 .. 1] range */
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));
161 /* convert to [0..255] range */
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);
169 /* build-an-integer */
171 return (rc << 24) | (gc << 16) | (bc << 8) | ac;
174 // Inverse of sRGB "gamma" function.
176 inv_gam_sRGB (double c)
181 return pow(((c+0.055)/(1.055)),2.4);
185 // sRGB "gamma" function
189 if (v <= 0.0031308) {
192 v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
194 return int (v*255+.5);
198 luminance (uint32_t c)
200 // sRGB luminance(Y) values
201 const double rY = 0.212655;
202 const double gY = 0.715158;
203 const double bY = 0.072187;
207 ArdourCanvas::color_to_rgba (c, r, g, b, a);
209 return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
213 ArdourCanvas::contrasting_text_color (uint32_t c)
215 /* use a slightly off-white... XXX should really look this up */
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);
220 return (luminance (c) < 0.50) ? white : black;
233 HSV::HSV (double hh, double ss, double vv, double aa)
240 /* normalize negative hue values into positive range */
247 color_to_hsva (c, h, s, v, a);
250 HSV::HSV (const std::string& str)
252 stringstream ss (str);
260 HSV::to_string () const
271 HSV::is_gray () const
281 /* normalize negative hue values into positive range */
290 HSV::operator+ (const HSV& operand) const
293 hsv.h = h + operand.h;
294 hsv.s = s + operand.s;
295 hsv.v = v + operand.v;
296 hsv.a = a + operand.a;
302 HSV::operator- (const HSV& operand) const
305 hsv.h = h - operand.h;
306 hsv.s = s - operand.s;
307 hsv.v = s - operand.v;
308 hsv.a = a - operand.a;
314 HSV::operator=(Color c)
316 color_to_hsva (c, h, s, v, a);
322 HSV::operator=(const std::string& str)
325 c = strtol (str.c_str(), 0, 16);
326 color_to_hsva (c, h, s, v, a);
332 HSV::operator== (const HSV& other)
334 return h == other.h &&
341 HSV::shade (double factor) const
345 /* algorithm derived from a google palette website
346 and analysis of their color palettes.
348 basic rule: to make a color darker, increase its saturation
349 until it reaches 88%, but then additionally reduce value/lightness
352 invert rule to make a color lighter.
357 hsv.v += (hsv.v * (factor * 10.0));
362 hsv.v -= (hsv.v * (factor * 10.0));
373 HSV::outline () const
375 if (luminance (color()) < 0.50) {
376 /* light color, darker outline: black with 15% opacity */
377 return HSV (0.0, 0.0, 0.0, 0.15);
379 /* dark color, lighter outline: white with 15% opacity */
380 return HSV (0.0, 0.0, 1.0, 0.15);
385 HSV::mix (const HSV& other, double amount) const
389 hsv.h = h + (amount * (other.h - h));
390 hsv.v = v + (amount * (other.s - s));
391 hsv.s = s + (amount * (other.v - v));
399 HSV::delta (const HSV& other) const
403 if (is_gray() && other.is_gray()) {
413 /* do not clamp - we are returning a delta */
418 HSV::distance (const HSV& other) const
420 if (is_gray() && other.is_gray()) {
421 /* human color perception of achromatics generates about 450
422 distinct colors. By contrast, CIE94 could give a maximal
423 perceptual distance of sqrt ((360^2) + 1 + 1) = 360. The 450
424 are not evenly spread (Webers Law), so lets use 360 as an
425 approximation of the number of distinct achromatics.
427 So, scale up the achromatic difference to give about
428 a maximal distance between v = 1.0 and v = 0.0 of 360.
430 A difference of about 0.0055 will generate a return value of
431 2, which is roughly the limit of human perceptual
432 discrimination for chromatics.
434 return fabs (360.0 * (v - other.v));
437 if (is_gray() != other.is_gray()) {
438 /* no comparison possible */
442 /* Use CIE94 definition for now */
446 double r, g, b, alpha; // Careful, "a" is a field of this
449 c = hsva_to_color (h, s, v, a);
450 color_to_rgba (c, r, g, b, alpha);
451 Rgb2Lab (&sL, &sA, &sB, r, g, b);
453 c = hsva_to_color (other.h, other.s, other.v, other.a);
454 color_to_rgba (c, r, g, b, alpha);
455 Rgb2Lab (&oL, &oA, &oB, r, g, b);
457 // Weighting factors depending on the application (1 = default)
459 const double whtL = 1.0;
460 const double whtC = 1.0;
461 const double whtH = 1.0;
463 const double xC1 = sqrt ((sA * sA) + (sB * oB));
464 const double xC2 = sqrt ((oA * oA) + (oB * oB));
465 double xDL = oL - sL;
466 double xDC = xC2 - xC1;
467 const double xDE = sqrt (((sL - oL) * (sL - oL))
468 + ((sA - oA) * (sA - oA))
469 + ((sB - oB) * (sB - oB)));
473 if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
474 xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
479 const double xSC = 1 + (0.045 * xC1);
480 const double xSH = 1 + (0.015 * xC1);
486 return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
490 HSV::opposite () const
493 hsv.h = fmod (h + 180.0, 360.0);
498 HSV::bw_text () const
500 return HSV (contrasting_text_color (color()));
510 HSV::selected () const
513 return HSV (Color (0xff0000));
518 HSV::print (std::ostream& o) const
521 o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
523 o << "gray(" << v << ')';
528 std::ostream& operator<<(std::ostream& o, const ArdourCanvas::HSV& hsv) { hsv.print (o); return o; }
531 HSV::mod (SVAModifier const & svam)
536 SVAModifier::SVAModifier (string const &str)
546 SVAModifier::from_string (string const & str)
549 stringstream ss (str);
557 /* no-op values for multiply */
564 /* no-op values for add */
571 /* this will avoid assignment in operator() (see below) */
577 throw failed_constructor ();
580 string::size_type pos;
584 if ((pos = mod.find ("alpha:")) != string::npos) {
585 _a = PBD::atof (mod.substr (pos+6));
586 } else if ((pos = mod.find ("saturate:")) != string::npos) {
587 _s = PBD::atof (mod.substr (pos+9));
588 } else if ((pos = mod.find ("darkness:")) != string::npos) {
589 _v = PBD::atof (mod.substr (pos+9));
591 throw failed_constructor ();
597 SVAModifier::to_string () const
599 PBD::LocaleGuard lg ();
615 ss << " saturate:" << _s;
619 ss << " darker:" << _v;
623 ss << " alpha:" << _a;
630 SVAModifier::operator () (HSV& hsv) const