1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
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 along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <algorithm>
20 #include <sstream>
21 #include <cmath>
22 #include <stdint.h>
23 #include <cfloat>
24 
25 #include "pbd/failed_constructor.h"
26 #include "pbd/string_convert.h"
27 
28 #include "gtkmm2ext/colors.h"
29 #include "gtkmm2ext/colorspace.h"
30 
31 using namespace std;
32 using namespace Gtkmm2ext;
33 
34 using std::max;
35 using std::min;
36 
37 Gtkmm2ext::Color
change_alpha(Color c,double a)38 Gtkmm2ext::change_alpha (Color c, double a)
39 {
40 	return ((c & ~0xff) | (lrintf (a*255.0) & 0xff));
41 }
42 
43 void
color_to_hsv(Color color,double & h,double & s,double & v)44 Gtkmm2ext::color_to_hsv (Color color, double& h, double& s, double& v)
45 {
46 	double a;
47 	color_to_hsva (color, h, s, v, a);
48 }
49 
50 void
color_to_hsva(Color color,double & h,double & s,double & v,double & a)51 Gtkmm2ext::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
52 {
53 	double r, g, b;
54 	double cmax;
55 	double cmin;
56 	double delta;
57 
58 	color_to_rgba (color, r, g, b, a);
59 
60 	if (r > g) {
61 		cmax = max (r, b);
62 	} else {
63 		cmax = max (g, b);
64 	}
65 
66 	if (r < g) {
67 		cmin = min (r, b);
68 	} else {
69 		cmin = min (g, b);
70 	}
71 
72 	v = cmax;
73 
74 	delta = cmax - cmin;
75 
76 	if (cmax == 0) {
77 		// r = g = b == 0 ... v is undefined, s = 0
78 		s = 0.0;
79 		h = 0.0;
80 		return;
81 	}
82 
83 	if (delta != 0.0) {
84 		if (cmax == r) {
85 			h = fmod ((g - b)/delta, 6.0);
86 		} else if (cmax == g) {
87 			h = ((b - r)/delta) + 2;
88 		} else {
89 			h = ((r - g)/delta) + 4;
90 		}
91 
92 		h *= 60.0;
93 
94 		if (h < 0.0) {
95 			/* negative values are legal but confusing, because
96 			   they alias positive values.
97 			*/
98 			h = 360 + h;
99 		}
100 	}
101 
102 	if (delta == 0 || cmax == 0) {
103 		s = 0;
104 	} else {
105 		s = delta / cmax;
106 	}
107 }
108 
109 Gtkmm2ext::Color
hsva_to_color(double h,double s,double v,double a)110 Gtkmm2ext::hsva_to_color (double h, double s, double v, double a)
111 {
112 	s = min (1.0, max (0.0, s));
113 	v = min (1.0, max (0.0, v));
114 
115 	if (s == 0) {
116 		return rgba_to_color (v, v, v, a);
117 	}
118 
119 	h = fmod (h + 360.0, 360.0);
120 
121 	double c = v * s;
122         double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
123         double m = v - c;
124 
125         if (h >= 0.0 && h < 60.0) {
126 		return rgba_to_color (c + m, x + m, m, a);
127         } else if (h >= 60.0 && h < 120.0) {
128 		return rgba_to_color (x + m, c + m, m, a);
129         } else if (h >= 120.0 && h < 180.0) {
130 		return rgba_to_color (m, c + m, x + m, a);
131         } else if (h >= 180.0 && h < 240.0) {
132 		return rgba_to_color (m, x + m, c + m, a);
133         } else if (h >= 240.0 && h < 300.0) {
134 		return rgba_to_color (x + m, m, c + m, a);
135         } else if (h >= 300.0 && h < 360.0) {
136 		return rgba_to_color (c + m, m, x + m, a);
137         }
138 	return rgba_to_color (m, m, m, a);
139 }
140 
141 void
color_to_rgba(Color color,double & r,double & g,double & b,double & a)142 Gtkmm2ext::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
143 {
144 	r = ((color >> 24) & 0xff) / 255.0;
145 	g = ((color >> 16) & 0xff) / 255.0;
146 	b = ((color >>  8) & 0xff) / 255.0;
147 	a = ((color >>  0) & 0xff) / 255.0;
148 }
149 
150 Gtkmm2ext::Color
rgba_to_color(double r,double g,double b,double a)151 Gtkmm2ext::rgba_to_color (double r, double g, double b, double a)
152 {
153 	/* clamp to [0 .. 1] range */
154 
155 	r = min (1.0, max (0.0, r));
156 	g = min (1.0, max (0.0, g));
157 	b = min (1.0, max (0.0, b));
158 	a = min (1.0, max (0.0, a));
159 
160 	/* convert to [0..255] range */
161 
162 	unsigned int rc, gc, bc, ac;
163 	rc = rint (r * 255.0);
164 	gc = rint (g * 255.0);
165 	bc = rint (b * 255.0);
166 	ac = rint (a * 255.0);
167 
168 	/* build-an-integer */
169 
170 	return (rc << 24) | (gc << 16) | (bc << 8) | ac;
171 }
172 
173 // Inverse of sRGB "gamma" function.
174 static inline double
inv_gam_sRGB(double c)175 inv_gam_sRGB (double c)
176 {
177         if (c <= 0.04045) {
178                 return c/12.92;
179         } else {
180                 return pow(((c+0.055)/(1.055)),2.4);
181         }
182 }
183 
184 // sRGB "gamma" function
185 static inline int
gam_sRGB(double v)186 gam_sRGB(double v)
187 {
188         if (v <= 0.0031308) {
189                 v *= 12.92;
190         } else {
191                 v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
192         }
193         return int (v*255+.5);
194 }
195 
196 static double
luminance(uint32_t c)197 luminance (uint32_t c)
198 {
199         // sRGB luminance(Y) values
200         const double rY = 0.212655;
201         const double gY = 0.715158;
202         const double bY = 0.072187;
203 
204         double r, g, b, a;
205 
206         Gtkmm2ext::color_to_rgba (c, r, g, b, a);
207 
208         return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
209 }
210 
211 uint32_t
contrasting_text_color(uint32_t c)212 Gtkmm2ext::contrasting_text_color (uint32_t c)
213 {
214 	/* use a slightly off-white... XXX should really look this up */
215 
216         static const uint32_t white = Gtkmm2ext::rgba_to_color (0.98, 0.98, 0.98, 1.0);
217         static const uint32_t black = Gtkmm2ext::rgba_to_color (0.0, 0.0, 0.0, 1.0);
218 
219 	return (luminance (c) < 0.50) ? white : black;
220 }
221 
222 
223 
HSV()224 HSV::HSV ()
225 	: h (0.0)
226 	, s (1.0)
227 	, v (1.0)
228 	, a (1.0)
229 {
230 }
231 
HSV(double hh,double ss,double vv,double aa)232 HSV::HSV (double hh, double ss, double vv, double aa)
233 	: h (hh)
234 	, s (ss)
235 	, v (vv)
236 	, a (aa)
237 {
238 	if (h < 0.0) {
239 		/* normalize negative hue values into positive range */
240 		h = 360.0 + h;
241 	}
242 }
243 
HSV(Color c)244 HSV::HSV (Color c)
245 {
246 	color_to_hsva (c, h, s, v, a);
247 }
248 
249 string
to_string() const250 HSV::to_string () const
251 {
252 	stringstream ss;
253 	ss << PBD::to_string(h) << ' ';
254 	ss << PBD::to_string(s) << ' ';
255 	ss << PBD::to_string(v) << ' ';
256 	ss << PBD::to_string(a);
257 	return ss.str();
258 }
259 
260 bool
is_gray() const261 HSV::is_gray () const
262 {
263 	return s == 0;
264 }
265 
266 void
clamp()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
operator +(const HSV & operand) const280 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
operator -(const HSV & operand) const292 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&
operator =(Color c)304 HSV::operator=(Color c)
305 {
306 	color_to_hsva (c, h, s, v, a);
307 	clamp ();
308 	return *this;
309 }
310 
311 HSV&
operator =(const std::string & str)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
operator ==(const HSV & other)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
shade(double factor) const331 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
outline() const363 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
mix(const HSV & other,double amount) const375 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
delta(const HSV & other) const389 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
distance(const HSV & other) const408 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
opposite() const480 HSV::opposite () const
481 {
482 	HSV hsv (*this);
483 	hsv.h = fmod (h + 180.0, 360.0);
484 	return hsv;
485 }
486 
487 HSV
bw_text() const488 HSV::bw_text () const
489 {
490 	return HSV (contrasting_text_color (color()));
491 }
492 
493 HSV
text() const494 HSV::text () const
495 {
496 	return opposite ();
497 }
498 
499 HSV
selected() const500 HSV::selected () const
501 {
502 	/* XXX hack */
503 	return HSV (Color (0xff0000));
504 }
505 
506 
507 void
print(std::ostream & o) const508 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 
operator <<(std::ostream & o,const Gtkmm2ext::HSV & hsv)518 std::ostream& operator<<(std::ostream& o, const Gtkmm2ext::HSV& hsv) { hsv.print (o); return o; }
519 
520 HSV
mod(SVAModifier const & svam)521 HSV::mod (SVAModifier const & svam)
522 {
523 	return svam (*this);
524 }
525 
SVAModifier(string const & str)526 SVAModifier::SVAModifier (string const &str)
527 	: type (Add)
528 	, _s (0)
529 	, _v (0)
530 	, _a (0)
531 {
532 	from_string (str);
533 }
534 
535 void
from_string(string const & str)536 SVAModifier::from_string (string const & str)
537 {
538 	char op;
539 	stringstream ss (str);
540 	string mod;
541 
542 	ss >> op;
543 
544 	switch (op) {
545 	case '*':
546 		type = Multiply;
547 		/* no-op values for multiply */
548 		_s = 1.0;
549 		_v = 1.0;
550 		_a = 1.0;
551 		break;
552 	case '+':
553 		type = Add;
554 		/* no-op values for add */
555 		_s = 0.0;
556 		_v = 0.0;
557 		_a = 0.0;
558 		break;
559 	case '=':
560 		type = Assign;
561 		/* this will avoid assignment in operator() (see below) */
562 		_s = -1.0;
563 		_v = -1.0;
564 		_a = -1.0;
565 		break;
566 	default:
567 		throw failed_constructor ();
568 	}
569 
570 	string::size_type pos;
571 
572 	while (ss) {
573 		ss >> mod;
574 		if ((pos = mod.find ("alpha:")) != string::npos) {
575 			_a = PBD::string_to<double>(mod.substr (pos+6));
576 		} else if ((pos = mod.find ("saturate:")) != string::npos) {
577 			_s = PBD::string_to<double>(mod.substr (pos+9));
578 		} else if ((pos = mod.find ("darkness:")) != string::npos) {
579 			_v = PBD::string_to<double>(mod.substr (pos+9));
580 		} else {
581 			throw failed_constructor ();
582 		}
583 	}
584 }
585 
586 string
to_string() const587 SVAModifier::to_string () const
588 {
589 	stringstream ss;
590 
591 	switch (type) {
592 	case Add:
593 		ss << '+';
594 		break;
595 	case Multiply:
596 		ss << '*';
597 		break;
598 	case Assign:
599 		ss << '=';
600 		break;
601 	}
602 
603 	if (_s >= 0.0) {
604 		ss << " saturate:" << PBD::to_string(_s);
605 	}
606 
607 	if (_v >= 0.0) {
608 		ss << " darker:" << PBD::to_string(_v);
609 	}
610 
611 	if (_a >= 0.0) {
612 		ss << " alpha:" << PBD::to_string(_a);
613 	}
614 
615 	return ss.str();
616 }
617 
618 HSV
operator ()(HSV & hsv) const619 SVAModifier::operator () (HSV& hsv)  const
620 {
621 	HSV r (hsv);
622 
623 	switch (type) {
624 	case Add:
625 		r.s += _s;
626 		r.v += _v;
627 		r.a += _a;
628 		break;
629 	case Multiply:
630 		r.s *= _s;
631 		r.v *= _v;
632 		r.a *= _a;
633 		break;
634 	case Assign:
635 		if (_s >= 0.0) {
636 			r.s = _s;
637 		}
638 		if (_v >= 0.) {
639 			r.v = _v;
640 		}
641 		if (_a >= 0.0) {
642 			r.a = _a;
643 		}
644 		break;
645 	}
646 
647 	return r;
648 }
649 
650 Color
color_at_alpha(Gtkmm2ext::Color c,double a)651 Gtkmm2ext::color_at_alpha (Gtkmm2ext::Color c, double a)
652 {
653 	double r, g, b, unused;
654 	color_to_rgba (c, r, g, b, unused);
655 	return rgba_to_color( r,g,b, a );
656 }
657 
658 void
set_source_rgba(Cairo::RefPtr<Cairo::Context> context,Color color)659 Gtkmm2ext::set_source_rgba (Cairo::RefPtr<Cairo::Context> context, Color color)
660 {
661 	context->set_source_rgba (
662 		((color >> 24) & 0xff) / 255.0,
663 		((color >> 16) & 0xff) / 255.0,
664 		((color >>  8) & 0xff) / 255.0,
665 		((color >>  0) & 0xff) / 255.0
666 		);
667 }
668 
669 void
set_source_rgb_a(Cairo::RefPtr<Cairo::Context> context,Color color,float alpha)670 Gtkmm2ext::set_source_rgb_a (Cairo::RefPtr<Cairo::Context> context, Color color, float alpha)
671 {
672 	context->set_source_rgba (
673 		((color >> 24) & 0xff) / 255.0,
674 		((color >> 16) & 0xff) / 255.0,
675 		((color >>  8) & 0xff) / 255.0,
676 		alpha
677 		);
678 }
679 
680 void
set_source_rgba(cairo_t * cr,Color color)681 Gtkmm2ext::set_source_rgba (cairo_t *cr, Color color)
682 {
683 	cairo_set_source_rgba ( cr,
684 		((color >> 24) & 0xff) / 255.0,
685 		((color >> 16) & 0xff) / 255.0,
686 		((color >>  8) & 0xff) / 255.0,
687 		((color >>  0) & 0xff) / 255.0
688 		);
689 }
690 
691 void
set_source_rgb_a(cairo_t * cr,Color color,float alpha)692 Gtkmm2ext::set_source_rgb_a (cairo_t *cr, Color color, float alpha)
693 {
694 	cairo_set_source_rgba ( cr,
695 		((color >> 24) & 0xff) / 255.0,
696 		((color >> 16) & 0xff) / 255.0,
697 		((color >>  8) & 0xff) / 255.0,
698 		alpha
699 		);
700 }
701