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 ArdourCanvas::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
272 HSV::is_gray () const
282 /* normalize negative hue values into positive range */
291 HSV::operator+ (const HSV& operand) const
294 hsv.h = h + operand.h;
295 hsv.s = s + operand.s;
296 hsv.v = v + operand.v;
297 hsv.a = a + operand.a;
303 HSV::operator- (const HSV& operand) const
306 hsv.h = h - operand.h;
307 hsv.s = s - operand.s;
308 hsv.v = s - operand.v;
309 hsv.a = a - operand.a;
315 HSV::operator=(Color c)
317 color_to_hsva (c, h, s, v, a);
323 HSV::operator=(const std::string& str)
326 c = strtol (str.c_str(), 0, 16);
327 color_to_hsva (c, h, s, v, a);
333 HSV::operator== (const HSV& other)
335 return h == other.h &&
342 HSV::shade (double factor) const
346 /* algorithm derived from a google palette website
347 and analysis of their color palettes.
349 basic rule: to make a color darker, increase its saturation
350 until it reaches 88%, but then additionally reduce value/lightness
353 invert rule to make a color lighter.
358 hsv.v += (hsv.v * (factor * 10.0));
363 hsv.v -= (hsv.v * (factor * 10.0));
374 HSV::outline () const
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);
380 /* dark color, lighter outline: white with 15% opacity */
381 return HSV (0.0, 0.0, 1.0, 0.15);
386 HSV::mix (const HSV& other, double amount) const
390 hsv.h = h + (amount * (other.h - h));
391 hsv.v = v + (amount * (other.s - s));
392 hsv.s = s + (amount * (other.v - v));
400 HSV::delta (const HSV& other) const
404 if (is_gray() && other.is_gray()) {
414 /* do not clamp - we are returning a delta */
419 HSV::distance (const HSV& other) const
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.
428 So, scale up the achromatic difference to give about
429 a maximal distance between v = 1.0 and v = 0.0 of 360.
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.
435 return fabs (360.0 * (v - other.v));
438 if (is_gray() != other.is_gray()) {
439 /* no comparison possible */
443 /* Use CIE94 definition for now */
447 double r, g, b, alpha; // Careful, "a" is a field of this
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);
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);
458 // Weighting factors depending on the application (1 = default)
460 const double whtL = 1.0;
461 const double whtC = 1.0;
462 const double whtH = 1.0;
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)));
474 if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
475 xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
480 const double xSC = 1 + (0.045 * xC1);
481 const double xSH = 1 + (0.015 * xC1);
487 return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
491 HSV::opposite () const
494 hsv.h = fmod (h + 180.0, 360.0);
499 HSV::bw_text () const
501 return HSV (contrasting_text_color (color()));
511 HSV::selected () const
514 return HSV (Color (0xff0000));
519 HSV::print (std::ostream& o) const
522 o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
524 o << "gray(" << v << ')';
529 std::ostream& operator<<(std::ostream& o, const ArdourCanvas::HSV& hsv) { hsv.print (o); return o; }
532 HSV::mod (SVAModifier const & svam)
537 SVAModifier::SVAModifier (string const &str)
547 SVAModifier::from_string (string const & str)
550 stringstream ss (str);
558 /* no-op values for multiply */
565 /* no-op values for add */
572 /* this will avoid assignment in operator() (see below) */
578 throw failed_constructor ();
581 string::size_type pos;
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));
592 throw failed_constructor ();
598 SVAModifier::to_string () const
616 ss << " saturate:" << _s;
620 ss << " darker:" << _v;
624 ss << " alpha:" << _a;
631 SVAModifier::operator () (HSV& hsv) const
663 ArdourCanvas::color_at_alpha (ArdourCanvas::Color c, double a)
665 double r, g, b, unused;
666 color_to_rgba (c, r, g, b, unused);
667 return rgba_to_color( r,g,b, a );