summaryrefslogtreecommitdiff
path: root/libs/gtkmm2ext
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2017-07-17 18:34:35 +0200
committerRobin Gareus <robin@gareus.org>2017-07-17 21:06:04 +0200
commit601c317d70a03190257577bd867cefc2c70d3275 (patch)
tree077071290f084431a912414c4e87b6127c586677 /libs/gtkmm2ext
parent1f5ebc54853446a786925941bd014666e75221d2 (diff)
Clean up library inheritance (colors.h, utils.h)
make libwidget independent of libcanvas. Confine basics to pbd and gtkmm2ext.
Diffstat (limited to 'libs/gtkmm2ext')
-rw-r--r--libs/gtkmm2ext/colors.cc700
-rw-r--r--libs/gtkmm2ext/colorspace.cc944
-rw-r--r--libs/gtkmm2ext/gtkmm2ext/colors.h138
-rw-r--r--libs/gtkmm2ext/gtkmm2ext/colorspace.h59
-rw-r--r--libs/gtkmm2ext/wscript2
5 files changed, 1843 insertions, 0 deletions
diff --git a/libs/gtkmm2ext/colors.cc b/libs/gtkmm2ext/colors.cc
new file mode 100644
index 0000000000..73cf061899
--- /dev/null
+++ b/libs/gtkmm2ext/colors.cc
@@ -0,0 +1,700 @@
+/*
+ Copyright (C) 2014 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <algorithm>
+#include <sstream>
+#include <cmath>
+#include <stdint.h>
+#include <cfloat>
+
+#include "pbd/failed_constructor.h"
+#include "pbd/string_convert.h"
+
+#include "gtkmm2ext/colors.h"
+#include "gtkmm2ext/colorspace.h"
+
+using namespace std;
+using namespace Gtkmm2ext;
+
+using std::max;
+using std::min;
+
+Gtkmm2ext::Color
+Gtkmm2ext::change_alpha (Color c, double a)
+{
+ return ((c & ~0xff) | (lrintf (a*255.0) & 0xff));
+}
+
+void
+Gtkmm2ext::color_to_hsv (Color color, double& h, double& s, double& v)
+{
+ double a;
+ color_to_hsva (color, h, s, v, a);
+}
+
+void
+Gtkmm2ext::color_to_hsva (Color color, double& h, double& s, double& v, double& a)
+{
+ double r, g, b;
+ double cmax;
+ double cmin;
+ double delta;
+
+ color_to_rgba (color, r, g, b, a);
+
+ if (r > g) {
+ cmax = max (r, b);
+ } else {
+ cmax = max (g, b);
+ }
+
+ if (r < g) {
+ cmin = min (r, b);
+ } else {
+ cmin = min (g, b);
+ }
+
+ v = cmax;
+
+ delta = cmax - cmin;
+
+ if (cmax == 0) {
+ // r = g = b == 0 ... v is undefined, s = 0
+ s = 0.0;
+ h = 0.0;
+ return;
+ }
+
+ if (delta != 0.0) {
+ if (cmax == r) {
+ h = fmod ((g - b)/delta, 6.0);
+ } else if (cmax == g) {
+ h = ((b - r)/delta) + 2;
+ } else {
+ h = ((r - g)/delta) + 4;
+ }
+
+ h *= 60.0;
+
+ if (h < 0.0) {
+ /* negative values are legal but confusing, because
+ they alias positive values.
+ */
+ h = 360 + h;
+ }
+ }
+
+ if (delta == 0 || cmax == 0) {
+ s = 0;
+ } else {
+ s = delta / cmax;
+ }
+}
+
+Gtkmm2ext::Color
+Gtkmm2ext::hsva_to_color (double h, double s, double v, double a)
+{
+ s = min (1.0, max (0.0, s));
+ v = min (1.0, max (0.0, v));
+
+ if (s == 0) {
+ return rgba_to_color (v, v, v, a);
+ }
+
+ h = fmod (h + 360.0, 360.0);
+
+ double c = v * s;
+ double x = c * (1.0 - fabs(fmod(h / 60.0, 2) - 1.0));
+ double m = v - c;
+
+ if (h >= 0.0 && h < 60.0) {
+ return rgba_to_color (c + m, x + m, m, a);
+ } else if (h >= 60.0 && h < 120.0) {
+ return rgba_to_color (x + m, c + m, m, a);
+ } else if (h >= 120.0 && h < 180.0) {
+ return rgba_to_color (m, c + m, x + m, a);
+ } else if (h >= 180.0 && h < 240.0) {
+ return rgba_to_color (m, x + m, c + m, a);
+ } else if (h >= 240.0 && h < 300.0) {
+ return rgba_to_color (x + m, m, c + m, a);
+ } else if (h >= 300.0 && h < 360.0) {
+ return rgba_to_color (c + m, m, x + m, a);
+ }
+ return rgba_to_color (m, m, m, a);
+}
+
+void
+Gtkmm2ext::color_to_rgba (Color color, double& r, double& g, double& b, double& a)
+{
+ r = ((color >> 24) & 0xff) / 255.0;
+ g = ((color >> 16) & 0xff) / 255.0;
+ b = ((color >> 8) & 0xff) / 255.0;
+ a = ((color >> 0) & 0xff) / 255.0;
+}
+
+Gtkmm2ext::Color
+Gtkmm2ext::rgba_to_color (double r, double g, double b, double a)
+{
+ /* clamp to [0 .. 1] range */
+
+ r = min (1.0, max (0.0, r));
+ g = min (1.0, max (0.0, g));
+ b = min (1.0, max (0.0, b));
+ a = min (1.0, max (0.0, a));
+
+ /* convert to [0..255] range */
+
+ unsigned int rc, gc, bc, ac;
+ rc = rint (r * 255.0);
+ gc = rint (g * 255.0);
+ bc = rint (b * 255.0);
+ ac = rint (a * 255.0);
+
+ /* build-an-integer */
+
+ return (rc << 24) | (gc << 16) | (bc << 8) | ac;
+}
+
+// Inverse of sRGB "gamma" function.
+static inline double
+inv_gam_sRGB (double c)
+{
+ if (c <= 0.04045) {
+ return c/12.92;
+ } else {
+ return pow(((c+0.055)/(1.055)),2.4);
+ }
+}
+
+// sRGB "gamma" function
+static inline int
+gam_sRGB(double v)
+{
+ if (v <= 0.0031308) {
+ v *= 12.92;
+ } else {
+ v = 1.055 * pow (v, 1.0 / 2.4) - 0.055;
+ }
+ return int (v*255+.5);
+}
+
+static double
+luminance (uint32_t c)
+{
+ // sRGB luminance(Y) values
+ const double rY = 0.212655;
+ const double gY = 0.715158;
+ const double bY = 0.072187;
+
+ double r, g, b, a;
+
+ Gtkmm2ext::color_to_rgba (c, r, g, b, a);
+
+ return (gam_sRGB (rY*inv_gam_sRGB(r) + gY*inv_gam_sRGB(g) + bY*inv_gam_sRGB(b))) / 255.0;
+}
+
+uint32_t
+Gtkmm2ext::contrasting_text_color (uint32_t c)
+{
+ /* use a slightly off-white... XXX should really look this up */
+
+ static const uint32_t white = Gtkmm2ext::rgba_to_color (0.98, 0.98, 0.98, 1.0);
+ static const uint32_t black = Gtkmm2ext::rgba_to_color (0.0, 0.0, 0.0, 1.0);
+
+ return (luminance (c) < 0.50) ? white : black;
+}
+
+
+
+HSV::HSV ()
+ : h (0.0)
+ , s (1.0)
+ , v (1.0)
+ , a (1.0)
+{
+}
+
+HSV::HSV (double hh, double ss, double vv, double aa)
+ : h (hh)
+ , s (ss)
+ , v (vv)
+ , a (aa)
+{
+ if (h < 0.0) {
+ /* normalize negative hue values into positive range */
+ h = 360.0 + h;
+ }
+}
+
+HSV::HSV (Color c)
+{
+ color_to_hsva (c, h, s, v, a);
+}
+
+string
+HSV::to_string () const
+{
+ stringstream ss;
+ ss << PBD::to_string(h) << ' ';
+ ss << PBD::to_string(s) << ' ';
+ ss << PBD::to_string(v) << ' ';
+ ss << PBD::to_string(a);
+ return ss.str();
+}
+
+bool
+HSV::is_gray () const
+{
+ return s == 0;
+}
+
+void
+HSV::clamp ()
+{
+ h = fmod (h, 360.0);
+ if (h < 0.0) {
+ /* normalize negative hue values into positive range */
+ h = 360.0 + h;
+ }
+ s = min (1.0, s);
+ v = min (1.0, v);
+ a = min (1.0, a);
+}
+
+HSV
+HSV::operator+ (const HSV& operand) const
+{
+ HSV hsv;
+ hsv.h = h + operand.h;
+ hsv.s = s + operand.s;
+ hsv.v = v + operand.v;
+ hsv.a = a + operand.a;
+ hsv.clamp ();
+ return hsv;
+}
+
+HSV
+HSV::operator- (const HSV& operand) const
+{
+ HSV hsv;
+ hsv.h = h - operand.h;
+ hsv.s = s - operand.s;
+ hsv.v = s - operand.v;
+ hsv.a = a - operand.a;
+ hsv.clamp ();
+ return hsv;
+}
+
+HSV&
+HSV::operator=(Color c)
+{
+ color_to_hsva (c, h, s, v, a);
+ clamp ();
+ return *this;
+}
+
+HSV&
+HSV::operator=(const std::string& str)
+{
+ uint32_t c;
+ c = strtol (str.c_str(), 0, 16);
+ color_to_hsva (c, h, s, v, a);
+ clamp ();
+ return *this;
+}
+
+bool
+HSV::operator== (const HSV& other)
+{
+ return h == other.h &&
+ s == other.s &&
+ v == other.v &&
+ a == other.a;
+}
+
+HSV
+HSV::shade (double factor) const
+{
+ HSV hsv (*this);
+
+ /* algorithm derived from a google palette website
+ and analysis of their color palettes.
+
+ basic rule: to make a color darker, increase its saturation
+ until it reaches 88%, but then additionally reduce value/lightness
+ by a larger amount.
+
+ invert rule to make a color lighter.
+ */
+
+ if (factor > 1.0) {
+ if (s < 88) {
+ hsv.v += (hsv.v * (factor * 10.0));
+ }
+ hsv.s *= factor;
+ } else {
+ if (s < 88) {
+ hsv.v -= (hsv.v * (factor * 10.0));
+ }
+ hsv.s *= factor;
+ }
+
+ hsv.clamp();
+
+ return hsv;
+}
+
+HSV
+HSV::outline () const
+{
+ if (luminance (color()) < 0.50) {
+ /* light color, darker outline: black with 15% opacity */
+ return HSV (0.0, 0.0, 0.0, 0.15);
+ } else {
+ /* dark color, lighter outline: white with 15% opacity */
+ return HSV (0.0, 0.0, 1.0, 0.15);
+ }
+}
+
+HSV
+HSV::mix (const HSV& other, double amount) const
+{
+ HSV hsv;
+
+ hsv.h = h + (amount * (other.h - h));
+ hsv.v = v + (amount * (other.s - s));
+ hsv.s = s + (amount * (other.v - v));
+
+ hsv.clamp();
+
+ return hsv;
+}
+
+HSV
+HSV::delta (const HSV& other) const
+{
+ HSV d;
+
+ if (is_gray() && other.is_gray()) {
+ d.h = 0.0;
+ d.s = 0.0;
+ d.v = v - other.v;
+ } else {
+ d.h = h - other.h;
+ d.s = s - other.s;
+ d.v = v - other.v;
+ }
+ d.a = a - other.a;
+ /* do not clamp - we are returning a delta */
+ return d;
+}
+
+double
+HSV::distance (const HSV& other) const
+{
+ if (is_gray() && other.is_gray()) {
+ /* human color perception of achromatics generates about 450
+ distinct colors. By contrast, CIE94 could give a maximal
+ perceptual distance of sqrt ((360^2) + 1 + 1) = 360. The 450
+ are not evenly spread (Webers Law), so lets use 360 as an
+ approximation of the number of distinct achromatics.
+
+ So, scale up the achromatic difference to give about
+ a maximal distance between v = 1.0 and v = 0.0 of 360.
+
+ A difference of about 0.0055 will generate a return value of
+ 2, which is roughly the limit of human perceptual
+ discrimination for chromatics.
+ */
+ return fabs (360.0 * (v - other.v));
+ }
+
+ if (is_gray() != other.is_gray()) {
+ /* no comparison possible */
+ return DBL_MAX;
+ }
+
+ /* Use CIE94 definition for now */
+
+ double sL, sA, sB;
+ double oL, oA, oB;
+ double r, g, b, alpha; // Careful, "a" is a field of this
+ Color c;
+
+ c = hsva_to_color (h, s, v, a);
+ color_to_rgba (c, r, g, b, alpha);
+ Rgb2Lab (&sL, &sA, &sB, r, g, b);
+
+ c = hsva_to_color (other.h, other.s, other.v, other.a);
+ color_to_rgba (c, r, g, b, alpha);
+ Rgb2Lab (&oL, &oA, &oB, r, g, b);
+
+ // Weighting factors depending on the application (1 = default)
+
+ const double whtL = 1.0;
+ const double whtC = 1.0;
+ const double whtH = 1.0;
+
+ const double xC1 = sqrt ((sA * sA) + (sB * oB));
+ const double xC2 = sqrt ((oA * oA) + (oB * oB));
+ double xDL = oL - sL;
+ double xDC = xC2 - xC1;
+ const double xDE = sqrt (((sL - oL) * (sL - oL))
+ + ((sA - oA) * (sA - oA))
+ + ((sB - oB) * (sB - oB)));
+
+ double xDH;
+
+ if (sqrt (xDE) > (sqrt (abs (xDL)) + sqrt (abs (xDC)))) {
+ xDH = sqrt ((xDE * xDE) - (xDL * xDL) - (xDC * xDC));
+ } else {
+ xDH = 0;
+ }
+
+ const double xSC = 1 + (0.045 * xC1);
+ const double xSH = 1 + (0.015 * xC1);
+
+ xDL /= whtL;
+ xDC /= whtC * xSC;
+ xDH /= whtH * xSH;
+
+ return sqrt ((xDL * xDL) + (xDC * xDC) + (xDH * xDH));
+}
+
+HSV
+HSV::opposite () const
+{
+ HSV hsv (*this);
+ hsv.h = fmod (h + 180.0, 360.0);
+ return hsv;
+}
+
+HSV
+HSV::bw_text () const
+{
+ return HSV (contrasting_text_color (color()));
+}
+
+HSV
+HSV::text () const
+{
+ return opposite ();
+}
+
+HSV
+HSV::selected () const
+{
+ /* XXX hack */
+ return HSV (Color (0xff0000));
+}
+
+
+void
+HSV::print (std::ostream& o) const
+{
+ if (!is_gray()) {
+ o << '(' << h << ',' << s << ',' << v << ',' << a << ')';
+ } else {
+ o << "gray(" << v << ')';
+ }
+}
+
+
+std::ostream& operator<<(std::ostream& o, const Gtkmm2ext::HSV& hsv) { hsv.print (o); return o; }
+
+HSV
+HSV::mod (SVAModifier const & svam)
+{
+ return svam (*this);
+}
+
+SVAModifier::SVAModifier (string const &str)
+ : type (Add)
+ , _s (0)
+ , _v (0)
+ , _a (0)
+{
+ from_string (str);
+}
+
+void
+SVAModifier::from_string (string const & str)
+{
+ char op;
+ stringstream ss (str);
+ string mod;
+
+ ss >> op;
+
+ switch (op) {
+ case '*':
+ type = Multiply;
+ /* no-op values for multiply */
+ _s = 1.0;
+ _v = 1.0;
+ _a = 1.0;
+ break;
+ case '+':
+ type = Add;
+ /* no-op values for add */
+ _s = 0.0;
+ _v = 0.0;
+ _a = 0.0;
+ break;
+ case '=':
+ type = Assign;
+ /* this will avoid assignment in operator() (see below) */
+ _s = -1.0;
+ _v = -1.0;
+ _a = -1.0;
+ break;
+ default:
+ throw failed_constructor ();
+ }
+
+ string::size_type pos;
+
+ while (ss) {
+ ss >> mod;
+ if ((pos = mod.find ("alpha:")) != string::npos) {
+ _a = PBD::string_to<double>(mod.substr (pos+6));
+ } else if ((pos = mod.find ("saturate:")) != string::npos) {
+ _s = PBD::string_to<double>(mod.substr (pos+9));
+ } else if ((pos = mod.find ("darkness:")) != string::npos) {
+ _v = PBD::string_to<double>(mod.substr (pos+9));
+ } else {
+ throw failed_constructor ();
+ }
+ }
+}
+
+string
+SVAModifier::to_string () const
+{
+ stringstream ss;
+
+ switch (type) {
+ case Add:
+ ss << '+';
+ break;
+ case Multiply:
+ ss << '*';
+ break;
+ case Assign:
+ ss << '=';
+ break;
+ }
+
+ if (_s >= 0.0) {
+ ss << " saturate:" << PBD::to_string(_s);
+ }
+
+ if (_v >= 0.0) {
+ ss << " darker:" << PBD::to_string(_v);
+ }
+
+ if (_a >= 0.0) {
+ ss << " alpha:" << PBD::to_string(_a);
+ }
+
+ return ss.str();
+}
+
+HSV
+SVAModifier::operator () (HSV& hsv) const
+{
+ HSV r (hsv);
+
+ switch (type) {
+ case Add:
+ r.s += _s;
+ r.v += _v;
+ r.a += _a;
+ break;
+ case Multiply:
+ r.s *= _s;
+ r.v *= _v;
+ r.a *= _a;
+ break;
+ case Assign:
+ if (_s >= 0.0) {
+ r.s = _s;
+ }
+ if (_v >= 0.) {
+ r.v = _v;
+ }
+ if (_a >= 0.0) {
+ r.a = _a;
+ }
+ break;
+ }
+
+ return r;
+}
+
+Color
+Gtkmm2ext::color_at_alpha (Gtkmm2ext::Color c, double a)
+{
+ double r, g, b, unused;
+ color_to_rgba (c, r, g, b, unused);
+ return rgba_to_color( r,g,b, a );
+}
+
+void
+Gtkmm2ext::set_source_rgba (Cairo::RefPtr<Cairo::Context> context, Color color)
+{
+ context->set_source_rgba (
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ ((color >> 0) & 0xff) / 255.0
+ );
+}
+
+void
+Gtkmm2ext::set_source_rgb_a (Cairo::RefPtr<Cairo::Context> context, Color color, float alpha)
+{
+ context->set_source_rgba (
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ alpha
+ );
+}
+
+void
+Gtkmm2ext::set_source_rgba (cairo_t *cr, Color color)
+{
+ cairo_set_source_rgba ( cr,
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ ((color >> 0) & 0xff) / 255.0
+ );
+}
+
+void
+Gtkmm2ext::set_source_rgb_a (cairo_t *cr, Color color, float alpha)
+{
+ cairo_set_source_rgba ( cr,
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ alpha
+ );
+}
diff --git a/libs/gtkmm2ext/colorspace.cc b/libs/gtkmm2ext/colorspace.cc
new file mode 100644
index 0000000000..b614616800
--- /dev/null
+++ b/libs/gtkmm2ext/colorspace.cc
@@ -0,0 +1,944 @@
+/**
+ * @file colorspace.c
+ * @author Pascal Getreuer 2005-2010 <getreuer@gmail.com>
+ *
+ * == Summary ==
+ * This file implements routines for color transformations between the spaces
+ * sRGB, Y'UV, Y'CbCr, Y'PbPr, Y'DbDr, Y'IQ, HSV, HSL, HSI, CIEXYZ, CIELAB,
+ * CIELUV, CIELCH, and CIECAT02 LMS.
+ *
+ * == Usage ==
+ * First call GetColorTransform, specifying the source and destination color
+ * spaces as "dest<-src" or "src->dest". Then call ApplyColorTransform to
+ * perform the transform:
+@code
+ double S[3] = {173, 0.8, 0.5};
+ double D[3];
+ colortransform Trans;
+
+ if(!(GetColorTransform(&Trans, "HSI -> Lab")))
+ {
+ printf("Invalid syntax or unknown color space\n");
+ return;
+ }
+
+ ApplyColorTransform(Trans, &D[0], &D[1], &D[2], S[0], S[1], S[2]);
+@endcode
+ * "num" is a typedef defined at the beginning of colorspace.h that may be set
+ * to either double or float, depending on the application.
+ *
+ * Specific transformation routines can also be called directly. The following
+ * converts an sRGB color to CIELAB and then back to sRGB:
+@code
+ double R = 0.85, G = 0.32, B = 0.5;
+ double L, a, b;
+ Rgb2Lab(&L, &a, &b, R, G, B);
+ Lab2Rgb(&R, &G, &B, L, a, b);
+@endcode
+ * Generally, the calling syntax is
+@code
+ Foo2Bar(&B0, &B1, &B2, F0, F1, F2);
+@endcode
+ * where (F0,F1,F2) are the coordinates of a color in space "Foo" and
+ * (B0,B1,B2) are the transformed coordinates in space "Bar." For any
+ * transformation routine, its inverse has the sytax
+@code
+ Bar2Foo(&F0, &F1, &F2, B0, B1, B2);
+@endcode
+ *
+ * The conversion routines are consistently named with the first letter of a
+ * color space capitalized with following letters in lower case and omitting
+ * prime symbols. For example, "Rgb2Ydbdr" converts sRGB to Y'DbDr. For
+ * any transformation routine Foo2Bar, its inverse is Bar2Foo.
+ *
+ * All transformations assume a two degree observer angle and a D65 illuminant.
+ * The white point can be changed by modifying the WHITEPOINT_X, WHITEPOINT_Y,
+ * WHITEPOINT_Z definitions at the beginning of colorspace.h.
+ *
+ * == List of transformation routines ==
+ * - Rgb2Yuv(double *Y, double *U, double *V, double R, double G, double B)
+ * - Rgb2Ycbcr(double *Y, double *Cb, double *Cr, double R, double G, double B)
+ * - Rgb2Jpegycbcr(double *Y, double *Cb, double *Cr, double R, double G, double B)
+ * - Rgb2Ypbpr(double *Y, double *Pb, double *Pr, double R, double G, double B)
+ * - Rgb2Ydbdr(double *Y, double *Db, double *Dr, double R, double G, double B)
+ * - Rgb2Yiq(double *Y, double *I, double *Q, double R, double G, double B)
+ * - Rgb2Hsv(double *H, double *S, double *V, double R, double G, double B)
+ * - Rgb2Hsl(double *H, double *S, double *L, double R, double G, double B)
+ * - Rgb2Hsi(double *H, double *S, double *I, double R, double G, double B)
+ * - Rgb2Xyz(double *X, double *Y, double *Z, double R, double G, double B)
+ * - Xyz2Lab(double *L, double *a, double *b, double X, double Y, double Z)
+ * - Xyz2Luv(double *L, double *u, double *v, double X, double Y, double Z)
+ * - Xyz2Lch(double *L, double *C, double *h, double X, double Y, double Z)
+ * - Xyz2Cat02lms(double *L, double *M, double *S, double X, double Y, double Z)
+ * - Rgb2Lab(double *L, double *a, double *b, double R, double G, double B)
+ * - Rgb2Luv(double *L, double *u, double *v, double R, double G, double B)
+ * - Rgb2Lch(double *L, double *C, double *h, double R, double G, double B)
+ * - Rgb2Cat02lms(double *L, double *M, double *S, double R, double G, double B)
+ * (Similarly for the inverse transformations.)
+ *
+ * It is possible to transform between two arbitrary color spaces by first
+ * transforming from the source space to sRGB and then transforming from
+ * sRGB to the desired destination space. For transformations between CIE
+ * color spaces, it is convenient to use XYZ as the intermediate space. This
+ * is the strategy used by GetColorTransform and ApplyColorTransform.
+ *
+ * == References ==
+ * The definitions of these spaces and the many of the transformation formulas
+ * can be found in
+ *
+ * Poynton, "Frequently Asked Questions About Gamma"
+ * http://www.poynton.com/notes/colour_and_gamma/GammaFAQ.html
+ *
+ * Poynton, "Frequently Asked Questions About Color"
+ * http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html
+ *
+ * and Wikipedia articles
+ * http://en.wikipedia.org/wiki/SRGB
+ * http://en.wikipedia.org/wiki/YUV
+ * http://en.wikipedia.org/wiki/YCbCr
+ * http://en.wikipedia.org/wiki/YPbPr
+ * http://en.wikipedia.org/wiki/YDbDr
+ * http://en.wikipedia.org/wiki/YIQ
+ * http://en.wikipedia.org/wiki/HSL_and_HSV
+ * http://en.wikipedia.org/wiki/CIE_1931_color_space
+ * http://en.wikipedia.org/wiki/Lab_color_space
+ * http://en.wikipedia.org/wiki/CIELUV_color_space
+ * http://en.wikipedia.org/wiki/LMS_color_space
+ *
+ * == License (BSD) ==
+ * Copyright (c) 2005-2010, Pascal Getreuer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "gtkmm2ext/colorspace.h"
+
+namespace Gtkmm2ext {
+
+/** @brief Min of A and B */
+#define MIN(A,B) (((A) <= (B)) ? (A) : (B))
+
+/** @brief Max of A and B */
+#define MAX(A,B) (((A) >= (B)) ? (A) : (B))
+
+/** @brief Min of A, B, and C */
+#define MIN3(A,B,C) (((A) <= (B)) ? MIN(A,C) : MIN(B,C))
+
+/** @brief Max of A, B, and C */
+#define MAX3(A,B,C) (((A) >= (B)) ? MAX(A,C) : MAX(B,C))
+
+#ifndef M_PI
+/** @brief The constant pi */
+#define M_PI 3.14159265358979323846264338327950288
+#endif
+
+/**
+ * @brief sRGB gamma correction, transforms R to R'
+ * http://en.wikipedia.org/wiki/SRGB
+ */
+#define GAMMACORRECTION(t) \
+ (((t) <= 0.0031306684425005883) ? \
+ (12.92*(t)) : (1.055*pow((t), 0.416666666666666667) - 0.055))
+
+/**
+ * @brief Inverse sRGB gamma correction, transforms R' to R
+ */
+#define INVGAMMACORRECTION(t) \
+ (((t) <= 0.0404482362771076) ? \
+ ((t)/12.92) : pow(((t) + 0.055)/1.055, 2.4))
+
+/**
+ * @brief CIE L*a*b* f function (used to convert XYZ to L*a*b*)
+ * http://en.wikipedia.org/wiki/Lab_color_space
+ */
+#define LABF(t) \
+ ((t >= 8.85645167903563082e-3) ? \
+ pow(t,0.333333333333333) : (841.0/108.0)*(t) + (4.0/29.0))
+
+/**
+ * @brief CIE L*a*b* inverse f function
+ * http://en.wikipedia.org/wiki/Lab_color_space
+ */
+#define LABINVF(t) \
+ ((t >= 0.206896551724137931) ? \
+ ((t)*(t)*(t)) : (108.0/841.0)*((t) - (4.0/29.0)))
+
+/** @brief u'v' coordinates of the white point for CIE Lu*v* */
+#define WHITEPOINT_U ((4*WHITEPOINT_X) \
+ /(WHITEPOINT_X + 15*WHITEPOINT_Y + 3*WHITEPOINT_Z))
+#define WHITEPOINT_V ((9*WHITEPOINT_Y) \
+ /(WHITEPOINT_X + 15*WHITEPOINT_Y + 3*WHITEPOINT_Z))
+
+/** @brief Enumeration of the supported color spaces */
+#define UNKNOWN_SPACE 0
+#define RGB_SPACE 1
+#define YUV_SPACE 2
+#define YCBCR_SPACE 3
+#define JPEGYCBCR_SPACE 4
+#define YPBPR_SPACE 5
+#define YDBDR_SPACE 6
+#define YIQ_SPACE 7
+#define HSV_SPACE 8
+#define HSL_SPACE 9
+#define HSI_SPACE 10
+#define XYZ_SPACE 11
+#define LAB_SPACE 12
+#define LUV_SPACE 13
+#define LCH_SPACE 14
+#define CAT02LMS_SPACE 15
+
+#define NUM_TRANSFORM_PAIRS 18
+
+
+
+/*
+ * == Linear color transformations ==
+ *
+ * The following routines implement transformations between sRGB and
+ * the linearly-related color spaces Y'UV, Y'PbPr, Y'DbDr, and Y'IQ.
+ */
+
+
+/**
+ * @brief Convert sRGB to NTSC/PAL Y'UV Luma + Chroma
+ *
+ * @param Y, U, V pointers to hold the result
+ * @param R, G, B the input sRGB values
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/YUV
+ */
+void Rgb2Yuv(double *Y, double *U, double *V, double R, double G, double B)
+{
+ *Y = (double)( 0.299*R + 0.587*G + 0.114*B);
+ *U = (double)(-0.147*R - 0.289*G + 0.436*B);
+ *V = (double)( 0.615*R - 0.515*G - 0.100*B);
+}
+
+
+/**
+ * @brief Convert NTSC/PAL Y'UV to sRGB
+ *
+ * @param R, G, B pointers to hold the result
+ * @param Y, U, V the input YUV values
+ */
+void Yuv2Rgb(double *R, double *G, double *B, double Y, double U, double V)
+{
+ *R = (double)(Y - 3.945707070708279e-05*U + 1.1398279671717170825*V);
+ *G = (double)(Y - 0.3946101641414141437*U - 0.5805003156565656797*V);
+ *B = (double)(Y + 2.0319996843434342537*U - 4.813762626262513e-04*V);
+}
+
+
+/** @brief sRGB to Y'CbCr Luma + Chroma */
+void Rgb2Ycbcr(double *Y, double *Cb, double *Cr, double R, double G, double B)
+{
+ *Y = (double)( 65.481*R + 128.553*G + 24.966*B + 16);
+ *Cb = (double)(-37.797*R - 74.203*G + 112.0 *B + 128);
+ *Cr = (double)(112.0 *R - 93.786*G - 18.214*B + 128);
+}
+
+
+/** @brief Y'CbCr to sRGB */
+void Ycbcr2Rgb(double *R, double *G, double *B, double Y, double Cr, double Cb)
+{
+ Y -= 16;
+ Cb -= 128;
+ Cr -= 128;
+ *R = (double)(0.00456621004566210107*Y + 1.1808799897946415e-09*Cr + 0.00625892896994393634*Cb);
+ *G = (double)(0.00456621004566210107*Y - 0.00153632368604490212*Cr - 0.00318811094965570701*Cb);
+ *B = (double)(0.00456621004566210107*Y + 0.00791071623355474145*Cr + 1.1977497040190077e-08*Cb);
+}
+
+
+/** @brief sRGB to JPEG-Y'CbCr Luma + Chroma */
+void Rgb2Jpegycbcr(double *Y, double *Cb, double *Cr, double R, double G, double B)
+{
+ Rgb2Ypbpr(Y, Cb, Cr, R, G, B);
+ *Cb += (double)0.5;
+ *Cr += (double)0.5;
+}
+
+/** @brief JPEG-Y'CbCr to sRGB */
+void Jpegycbcr2Rgb(double *R, double *G, double *B, double Y, double Cb, double Cr)
+{
+ Cb -= (double)0.5;
+ Cr -= (double)0.5;
+ Ypbpr2Rgb(R, G, B, Y, Cb, Cr);
+}
+
+
+/** @brief sRGB to Y'PbPr Luma (ITU-R BT.601) + Chroma */
+void Rgb2Ypbpr(double *Y, double *Pb, double *Pr, double R, double G, double B)
+{
+ *Y = (double)( 0.299 *R + 0.587 *G + 0.114 *B);
+ *Pb = (double)(-0.1687367*R - 0.331264*G + 0.5 *B);
+ *Pr = (double)( 0.5 *R - 0.418688*G - 0.081312*B);
+}
+
+
+/** @brief Y'PbPr to sRGB */
+void Ypbpr2Rgb(double *R, double *G, double *B, double Y, double Pb, double Pr)
+{
+ *R = (double)(0.99999999999914679361*Y - 1.2188941887145875e-06*Pb + 1.4019995886561440468*Pr);
+ *G = (double)(0.99999975910502514331*Y - 0.34413567816504303521*Pb - 0.71413649331646789076*Pr);
+ *B = (double)(1.00000124040004623180*Y + 1.77200006607230409200*Pb + 2.1453384174593273e-06*Pr);
+}
+
+
+/** @brief sRGB to SECAM Y'DbDr Luma + Chroma */
+void Rgb2Ydbdr(double *Y, double *Db, double *Dr, double R, double G, double B)
+{
+ *Y = (double)( 0.299*R + 0.587*G + 0.114*B);
+ *Db = (double)(-0.450*R - 0.883*G + 1.333*B);
+ *Dr = (double)(-1.333*R + 1.116*G + 0.217*B);
+}
+
+
+/** @brief SECAM Y'DbDr to sRGB */
+void Ydbdr2Rgb(double *R, double *G, double *B, double Y, double Db, double Dr)
+{
+ *R = (double)(Y + 9.2303716147657e-05*Db - 0.52591263066186533*Dr);
+ *G = (double)(Y - 0.12913289889050927*Db + 0.26789932820759876*Dr);
+ *B = (double)(Y + 0.66467905997895482*Db - 7.9202543533108e-05*Dr);
+}
+
+
+/** @brief sRGB to NTSC YIQ */
+void Rgb2Yiq(double *Y, double *I, double *Q, double R, double G, double B)
+{
+ *Y = (double)(0.299 *R + 0.587 *G + 0.114 *B);
+ *I = (double)(0.595716*R - 0.274453*G - 0.321263*B);
+ *Q = (double)(0.211456*R - 0.522591*G + 0.311135*B);
+}
+
+
+/** @brief Convert NTSC YIQ to sRGB */
+void Yiq2Rgb(double *R, double *G, double *B, double Y, double I, double Q)
+{
+ *R = (double)(Y + 0.9562957197589482261*I + 0.6210244164652610754*Q);
+ *G = (double)(Y - 0.2721220993185104464*I - 0.6473805968256950427*Q);
+ *B = (double)(Y - 1.1069890167364901945*I + 1.7046149983646481374*Q);
+}
+
+
+
+/*
+ * == Hue Saturation Value/Lightness/Intensity color transformations ==
+ *
+ * The following routines implement transformations between sRGB and
+ * color spaces HSV, HSL, and HSI.
+ */
+
+
+/**
+ * @brief Convert an sRGB color to Hue-Saturation-Value (HSV)
+ *
+ * @param H, S, V pointers to hold the result
+ * @param R, G, B the input sRGB values scaled in [0,1]
+ *
+ * This routine transforms from sRGB to the hexcone HSV color space. The
+ * sRGB values are assumed to be between 0 and 1. The output values are
+ * H = hexagonal hue angle (0 <= H < 360),
+ * S = C/V (0 <= S <= 1),
+ * V = max(R',G',B') (0 <= V <= 1),
+ * where C = max(R',G',B') - min(R',G',B'). The inverse color transformation
+ * is given by Hsv2Rgb.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Rgb2Hsv(double *H, double *S, double *V, double R, double G, double B)
+{
+ double Max = MAX3(R, G, B);
+ double Min = MIN3(R, G, B);
+ double C = Max - Min;
+
+
+ *V = Max;
+
+ if(C > 0)
+ {
+ if(Max == R)
+ {
+ *H = (G - B) / C;
+
+ if(G < B)
+ *H += 6;
+ }
+ else if(Max == G)
+ *H = 2 + (B - R) / C;
+ else
+ *H = 4 + (R - G) / C;
+
+ *H *= 60;
+ *S = C / Max;
+ }
+ else
+ *H = *S = 0;
+}
+
+
+/**
+ * @brief Convert a Hue-Saturation-Value (HSV) color to sRGB
+ *
+ * @param R, G, B pointers to hold the result
+ * @param H, S, V the input HSV values
+ *
+ * The input values are assumed to be scaled as
+ * 0 <= H < 360,
+ * 0 <= S <= 1,
+ * 0 <= V <= 1.
+ * The output sRGB values are scaled between 0 and 1. This is the inverse
+ * transformation of Rgb2Hsv.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Hsv2Rgb(double *R, double *G, double *B, double H, double S, double V)
+{
+ double C = S * V;
+ double Min = V - C;
+ double X;
+
+
+ H -= 360*floor(H/360);
+ H /= 60;
+ X = C*(1 - fabs(H - 2*floor(H/2) - 1));
+
+ switch((int)H)
+ {
+ case 0:
+ *R = Min + C;
+ *G = Min + X;
+ *B = Min;
+ break;
+ case 1:
+ *R = Min + X;
+ *G = Min + C;
+ *B = Min;
+ break;
+ case 2:
+ *R = Min;
+ *G = Min + C;
+ *B = Min + X;
+ break;
+ case 3:
+ *R = Min;
+ *G = Min + X;
+ *B = Min + C;
+ break;
+ case 4:
+ *R = Min + X;
+ *G = Min;
+ *B = Min + C;
+ break;
+ case 5:
+ *R = Min + C;
+ *G = Min;
+ *B = Min + X;
+ break;
+ default:
+ *R = *G = *B = 0;
+ }
+}
+
+
+/**
+ * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL)
+ *
+ * @param H, S, L pointers to hold the result
+ * @param R, G, B the input sRGB values scaled in [0,1]
+ *
+ * This routine transforms from sRGB to the double hexcone HSL color space
+ * The sRGB values are assumed to be between 0 and 1. The outputs are
+ * H = hexagonal hue angle (0 <= H < 360),
+ * S = { C/(2L) if L <= 1/2 (0 <= S <= 1),
+ * { C/(2 - 2L) if L > 1/2
+ * L = (max(R',G',B') + min(R',G',B'))/2 (0 <= L <= 1),
+ * where C = max(R',G',B') - min(R',G',B'). The inverse color transformation
+ * is given by Hsl2Rgb.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Rgb2Hsl(double *H, double *S, double *L, double R, double G, double B)
+{
+ double Max = MAX3(R, G, B);
+ double Min = MIN3(R, G, B);
+ double C = Max - Min;
+
+
+ *L = (Max + Min)/2;
+
+ if(C > 0)
+ {
+ if(Max == R)
+ {
+ *H = (G - B) / C;
+
+ if(G < B)
+ *H += 6;
+ }
+ else if(Max == G)
+ *H = 2 + (B - R) / C;
+ else
+ *H = 4 + (R - G) / C;
+
+ *H *= 60;
+ *S = (*L <= 0.5) ? (C/(2*(*L))) : (C/(2 - 2*(*L)));
+ }
+ else
+ *H = *S = 0;
+}
+
+
+/**
+ * @brief Convert a Hue-Saturation-Lightness (HSL) color to sRGB
+ *
+ * @param R, G, B pointers to hold the result
+ * @param H, S, L the input HSL values
+ *
+ * The input values are assumed to be scaled as
+ * 0 <= H < 360,
+ * 0 <= S <= 1,
+ * 0 <= L <= 1.
+ * The output sRGB values are scaled between 0 and 1. This is the inverse
+ * transformation of Rgb2Hsl.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Hsl2Rgb(double *R, double *G, double *B, double H, double S, double L)
+{
+ double C = (L <= 0.5) ? (2*L*S) : ((2 - 2*L)*S);
+ double Min = L - 0.5*C;
+ double X;
+
+
+ H -= 360*floor(H/360);
+ H /= 60;
+ X = C*(1 - fabs(H - 2*floor(H/2) - 1));
+
+ switch((int)H)
+ {
+ case 0:
+ *R = Min + C;
+ *G = Min + X;
+ *B = Min;
+ break;
+ case 1:
+ *R = Min + X;
+ *G = Min + C;
+ *B = Min;
+ break;
+ case 2:
+ *R = Min;
+ *G = Min + C;
+ *B = Min + X;
+ break;
+ case 3:
+ *R = Min;
+ *G = Min + X;
+ *B = Min + C;
+ break;
+ case 4:
+ *R = Min + X;
+ *G = Min;
+ *B = Min + C;
+ break;
+ case 5:
+ *R = Min + C;
+ *G = Min;
+ *B = Min + X;
+ break;
+ default:
+ *R = *G = *B = 0;
+ }
+}
+
+
+/**
+ * @brief Convert an sRGB color to Hue-Saturation-Intensity (HSI)
+ *
+ * @param H, S, I pointers to hold the result
+ * @param R, G, B the input sRGB values scaled in [0,1]
+ *
+ * This routine transforms from sRGB to the cylindrical HSI color space. The
+ * sRGB values are assumed to be between 0 and 1. The output values are
+ * H = polar hue angle (0 <= H < 360),
+ * S = 1 - min(R',G',B')/I (0 <= S <= 1),
+ * I = (R'+G'+B')/3 (0 <= I <= 1).
+ * The inverse color transformation is given by Hsi2Rgb.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Rgb2Hsi(double *H, double *S, double *I, double R, double G, double B)
+{
+ double alpha = 0.5*(2*R - G - B);
+ double beta = 0.866025403784439*(G - B);
+
+
+ *I = (R + G + B)/3;
+
+ if(*I > 0)
+ {
+ *S = 1 - MIN3(R,G,B) / *I;
+ *H = atan2(beta, alpha)*(180/M_PI);
+
+ if(*H < 0)
+ *H += 360;
+ }
+ else
+ *H = *S = 0;
+}
+
+
+/**
+ * @brief Convert a Hue-Saturation-Intesity (HSI) color to sRGB
+ *
+ * @param R, G, B pointers to hold the result
+ * @param H, S, I the input HSI values
+ *
+ * The input values are assumed to be scaled as
+ * 0 <= H < 360,
+ * 0 <= S <= 1,
+ * 0 <= I <= 1.
+ * The output sRGB values are scaled between 0 and 1. This is the inverse
+ * transformation of Rgb2Hsi.
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/HSL_and_HSV
+ */
+void Hsi2Rgb(double *R, double *G, double *B, double H, double S, double I)
+{
+ H -= 360*floor(H/360);
+
+ if(H < 120)
+ {
+ *B = I*(1 - S);
+ *R = I*(1 + S*cos(H*(M_PI/180))/cos((60 - H)*(M_PI/180)));
+ *G = 3*I - *R - *B;
+ }
+ else if(H < 240)
+ {
+ H -= 120;
+ *R = I*(1 - S);
+ *G = I*(1 + S*cos(H*(M_PI/180))/cos((60 - H)*(M_PI/180)));
+ *B = 3*I - *R - *G;
+ }
+ else
+ {
+ H -= 240;
+ *G = I*(1 - S);
+ *B = I*(1 + S*cos(H*(M_PI/180))/cos((60 - H)*(M_PI/180)));
+ *R = 3*I - *G - *B;
+ }
+}
+
+
+/*
+ * == CIE color transformations ==
+ *
+ * The following routines implement transformations between sRGB and
+ * the CIE color spaces XYZ, L*a*b, L*u*v*, and L*C*H*. These
+ * transforms assume a 2 degree observer angle and a D65 illuminant.
+ */
+
+
+/**
+ * @brief Transform sRGB to CIE XYZ with the D65 white point
+ *
+ * @param X, Y, Z pointers to hold the result
+ * @param R, G, B the input sRGB values
+ *
+ * Poynton, "Frequently Asked Questions About Color," page 10
+ * Wikipedia: http://en.wikipedia.org/wiki/SRGB
+ * Wikipedia: http://en.wikipedia.org/wiki/CIE_1931_color_space
+ */
+void Rgb2Xyz(double *X, double *Y, double *Z, double R, double G, double B)
+{
+ R = INVGAMMACORRECTION(R);
+ G = INVGAMMACORRECTION(G);
+ B = INVGAMMACORRECTION(B);
+ *X = (double)(0.4123955889674142161*R + 0.3575834307637148171*G + 0.1804926473817015735*B);
+ *Y = (double)(0.2125862307855955516*R + 0.7151703037034108499*G + 0.07220049864333622685*B);
+ *Z = (double)(0.01929721549174694484*R + 0.1191838645808485318*G + 0.9504971251315797660*B);
+}
+
+
+/**
+ * @brief Transform CIE XYZ to sRGB with the D65 white point
+ *
+ * @param R, G, B pointers to hold the result
+ * @param X, Y, Z the input XYZ values
+ *
+ * Official sRGB specification (IEC 61966-2-1:1999)
+ * Poynton, "Frequently Asked Questions About Color," page 10
+ * Wikipedia: http://en.wikipedia.org/wiki/SRGB
+ * Wikipedia: http://en.wikipedia.org/wiki/CIE_1931_color_space
+ */
+void Xyz2Rgb(double *R, double *G, double *B, double X, double Y, double Z)
+{
+ double R1, B1, G1, Min;
+
+
+ R1 = (double)( 3.2406*X - 1.5372*Y - 0.4986*Z);
+ G1 = (double)(-0.9689*X + 1.8758*Y + 0.0415*Z);
+ B1 = (double)( 0.0557*X - 0.2040*Y + 1.0570*Z);
+
+ Min = MIN3(R1, G1, B1);
+
+ /* Force nonnegative values so that gamma correction is well-defined. */
+ if(Min < 0)
+ {
+ R1 -= Min;
+ G1 -= Min;
+ B1 -= Min;
+ }
+
+ /* Transform from RGB to R'G'B' */
+ *R = GAMMACORRECTION(R1);
+ *G = GAMMACORRECTION(G1);
+ *B = GAMMACORRECTION(B1);
+}
+
+
+/**
+ * Convert CIE XYZ to CIE L*a*b* (CIELAB) with the D65 white point
+ *
+ * @param L, a, b pointers to hold the result
+ * @param X, Y, Z the input XYZ values
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/Lab_color_space
+ */
+void Xyz2Lab(double *L, double *a, double *b, double X, double Y, double Z)
+{
+ X /= WHITEPOINT_X;
+ Y /= WHITEPOINT_Y;
+ Z /= WHITEPOINT_Z;
+ X = LABF(X);
+ Y = LABF(Y);
+ Z = LABF(Z);
+ *L = 116*Y - 16;
+ *a = 500*(X - Y);
+ *b = 200*(Y - Z);
+}
+
+
+/**
+ * Convert CIE L*a*b* (CIELAB) to CIE XYZ with the D65 white point
+ *
+ * @param X, Y, Z pointers to hold the result
+ * @param L, a, b the input L*a*b* values
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/Lab_color_space
+ */
+void Lab2Xyz(double *X, double *Y, double *Z, double L, double a, double b)
+{
+ L = (L + 16)/116;
+ a = L + a/500;
+ b = L - b/200;
+ *X = WHITEPOINT_X*LABINVF(a);
+ *Y = WHITEPOINT_Y*LABINVF(L);
+ *Z = WHITEPOINT_Z*LABINVF(b);
+}
+
+
+/**
+ * Convert CIE XYZ to CIE L*u*v* (CIELUV) with the D65 white point
+ *
+ * @param L, u, v pointers to hold the result
+ * @param X, Y, Z the input XYZ values
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/CIELUV_color_space
+ */
+void Xyz2Luv(double *L, double *u, double *v, double X, double Y, double Z)
+{
+ double u1, v1, Denom;
+
+
+ if((Denom = X + 15*Y + 3*Z) > 0)
+ {
+ u1 = (4*X) / Denom;
+ v1 = (9*Y) / Denom;
+ }
+ else
+ u1 = v1 = 0;
+
+ Y /= WHITEPOINT_Y;
+ Y = LABF(Y);
+ *L = 116*Y - 16;
+ *u = 13*(*L)*(u1 - WHITEPOINT_U);
+ *v = 13*(*L)*(v1 - WHITEPOINT_V);
+}
+
+
+/**
+ * Convert CIE L*u*v* (CIELUV) to CIE XYZ with the D65 white point
+ *
+ * @param X, Y, Z pointers to hold the result
+ * @param L, u, v the input L*u*v* values
+ *
+ * Wikipedia: http://en.wikipedia.org/wiki/CIELUV_color_space
+ */
+void Luv2Xyz(double *X, double *Y, double *Z, double L, double u, double v)
+{
+ *Y = (L + 16)/116;
+ *Y = WHITEPOINT_Y*LABINVF(*Y);
+
+ if(L != 0)
+ {
+ u /= L;
+ v /= L;
+ }
+
+ u = u/13 + WHITEPOINT_U;
+ v = v/13 + WHITEPOINT_V;
+ *X = (*Y) * ((9*u)/(4*v));
+ *Z = (*Y) * ((3 - 0.75*u)/v - 5);
+}
+
+
+/**
+ * Convert CIE XYZ to CIE L*C*H* with the D65 white point
+ *
+ * @param L, C, H pointers to hold the result
+ * @param X, Y, Z the input XYZ values
+ *
+ * CIE L*C*H* is related to CIE L*a*b* by
+ * a* = C* cos(H* pi/180),
+ * b* = C* sin(H* pi/180).
+ */
+void Xyz2Lch(double *L, double *C, double *H, double X, double Y, double Z)
+{
+ double a, b;
+
+
+ Xyz2Lab(L, &a, &b, X, Y, Z);
+ *C = sqrt(a*a + b*b);
+ *H = atan2(b, a)*180.0/M_PI;
+
+ if(*H < 0)
+ *H += 360;
+}
+
+/**
+ * Convert CIE L*C*H* to CIE XYZ with the D65 white point
+ *
+ * @param X, Y, Z pointers to hold the result
+ * @param L, C, H the input L*C*H* values
+ */
+void Lch2Xyz(double *X, double *Y, double *Z, double L, double C, double H)
+{
+ double a = C * cos(H*(M_PI/180.0));
+ double b = C * sin(H*(M_PI/180.0));
+
+
+ Lab2Xyz(X, Y, Z, L, a, b);
+}
+
+
+/** @brief XYZ to CAT02 LMS */
+void Xyz2Cat02lms(double *L, double *M, double *S, double X, double Y, double Z)
+{
+ *L = (double)( 0.7328*X + 0.4296*Y - 0.1624*Z);
+ *M = (double)(-0.7036*X + 1.6975*Y + 0.0061*Z);
+ *S = (double)( 0.0030*X + 0.0136*Y + 0.9834*Z);
+}
+
+
+/** @brief CAT02 LMS to XYZ */
+void Cat02lms2Xyz(double *X, double *Y, double *Z, double L, double M, double S)
+{
+ *X = (double)( 1.096123820835514*L - 0.278869000218287*M + 0.182745179382773*S);
+ *Y = (double)( 0.454369041975359*L + 0.473533154307412*M + 0.072097803717229*S);
+ *Z = (double)(-0.009627608738429*L - 0.005698031216113*M + 1.015325639954543*S);
+}
+
+
+/*
+ * == Glue functions for multi-stage transforms ==
+ */
+
+void Rgb2Lab(double *L, double *a, double *b, double R, double G, double B)
+{
+ double X, Y, Z;
+ Rgb2Xyz(&X, &Y, &Z, R, G, B);
+ Xyz2Lab(L, a, b, X, Y, Z);
+}
+
+
+void Lab2Rgb(double *R, double *G, double *B, double L, double a, double b)
+{
+ double X, Y, Z;
+ Lab2Xyz(&X, &Y, &Z, L, a, b);
+ Xyz2Rgb(R, G, B, X, Y, Z);
+}
+
+
+void Rgb2Luv(double *L, double *u, double *v, double R, double G, double B)
+{
+ double X, Y, Z;
+ Rgb2Xyz(&X, &Y, &Z, R, G, B);
+ Xyz2Luv(L, u, v, X, Y, Z);
+}
+
+
+void Luv2Rgb(double *R, double *G, double *B, double L, double u, double v)
+{
+ double X, Y, Z;
+ Luv2Xyz(&X, &Y, &Z, L, u, v);
+ Xyz2Rgb(R, G, B, X, Y, Z);
+}
+
+void Rgb2Lch(double *L, double *C, double *H, double R, double G, double B)
+{
+ double X, Y, Z;
+ Rgb2Xyz(&X, &Y, &Z, R, G, B);
+ Xyz2Lch(L, C, H, X, Y, Z);
+}
+
+
+void Lch2Rgb(double *R, double *G, double *B, double L, double C, double H)
+{
+ double X, Y, Z;
+ Lch2Xyz(&X, &Y, &Z, L, C, H);
+ Xyz2Rgb(R, G, B, X, Y, Z);
+}
+
+
+void Rgb2Cat02lms(double *L, double *M, double *S, double R, double G, double B)
+{
+ double X, Y, Z;
+ Rgb2Xyz(&X, &Y, &Z, R, G, B);
+ Xyz2Cat02lms(L, M, S, X, Y, Z);
+}
+
+
+void Cat02lms2Rgb(double *R, double *G, double *B, double L, double M, double S)
+{
+ double X, Y, Z;
+ Cat02lms2Xyz(&X, &Y, &Z, L, M, S);
+ Xyz2Rgb(R, G, B, X, Y, Z);
+}
+
+} /* namespace */
diff --git a/libs/gtkmm2ext/gtkmm2ext/colors.h b/libs/gtkmm2ext/gtkmm2ext/colors.h
new file mode 100644
index 0000000000..3ec6ae3a81
--- /dev/null
+++ b/libs/gtkmm2ext/gtkmm2ext/colors.h
@@ -0,0 +1,138 @@
+/*
+ Copyright (C) 2011-2013 Paul Davis
+ Author: Carl Hetherington <cth@carlh.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _GTKMM2EXT_COLORS_H_
+#define _GTKMM2EXT_COLORS_H_
+
+#include <cairomm/context.h>
+
+#include "gtkmm2ext/visibility.h"
+
+namespace Gtkmm2ext
+{
+
+typedef uint32_t Color;
+
+/* conventient way to use Gtkmm2ext::Color with libcairo */
+extern LIBGTKMM2EXT_API void set_source_rgba (Cairo::RefPtr<Cairo::Context>, Gtkmm2ext::Color);
+extern LIBGTKMM2EXT_API void set_source_rgb_a (Cairo::RefPtr<Cairo::Context>, Gtkmm2ext::Color, float alpha); //override the color's alpha
+
+extern LIBGTKMM2EXT_API void set_source_rgba (cairo_t*, Gtkmm2ext::Color);
+extern LIBGTKMM2EXT_API void set_source_rgb_a (cairo_t*, Gtkmm2ext::Color, float alpha); //override the color's alpha
+
+
+struct LIBGTKMM2EXT_API HSV;
+struct LIBGTKMM2EXT_API HSVA;
+
+extern LIBGTKMM2EXT_API Color change_alpha (Color, double alpha);
+
+extern LIBGTKMM2EXT_API Color hsva_to_color (double h, double s, double v, double a = 1.0);
+extern LIBGTKMM2EXT_API void color_to_hsva (Color color, double& h, double& s, double& v, double& a);
+extern LIBGTKMM2EXT_API Color color_at_alpha (Color, double a);
+extern LIBGTKMM2EXT_API void color_to_hsv (Color color, double& h, double& s, double& v);
+extern LIBGTKMM2EXT_API void color_to_rgba (Color, double& r, double& g, double& b, double& a);
+extern LIBGTKMM2EXT_API Color rgba_to_color (double r, double g, double b, double a);
+
+uint32_t LIBGTKMM2EXT_API contrasting_text_color (uint32_t c);
+
+struct LIBGTKMM2EXT_API HSV;
+
+class LIBGTKMM2EXT_API SVAModifier
+{
+public:
+ enum Type {
+ Add,
+ Multiply,
+ Assign
+ };
+
+ SVAModifier (std::string const &);
+ SVAModifier (Type t, double ss, double vv, double aa) : type (t), _s (ss) , _v (vv) , _a (aa) {}
+ SVAModifier () : type (Add), _s (0), _v (0), _a (0) {} /* no-op modifier */
+
+ double s() const { return _s; }
+ double v() const { return _v; }
+ double a() const { return _a; }
+
+ HSV operator () (HSV& hsv) const;
+ std::string to_string () const;
+ void from_string (std::string const &);
+
+private:
+ Type type;
+ double _s;
+ double _v;
+ double _a;
+};
+
+struct LIBGTKMM2EXT_API HSV
+{
+ HSV ();
+ HSV (double h, double s, double v, double a = 1.0);
+ HSV (Color);
+
+ double h;
+ double s;
+ double v;
+ double a;
+
+ std::string to_string() const;
+ bool is_gray() const;
+
+ Color color() const { return hsva_to_color (h,s, v, a); }
+ operator Color() const { return color(); }
+
+ HSV mod (SVAModifier const & svam);
+
+ HSV operator+ (const HSV&) const;
+ HSV operator- (const HSV&) const;
+
+ HSV& operator=(Color);
+ HSV& operator=(const std::string&);
+
+ bool operator== (const HSV& other);
+
+ double distance (const HSV& other) const;
+ HSV delta (const HSV& other) const;
+
+ HSV darker (double factor = 1.3) const { return shade (factor); }
+ HSV lighter (double factor = 0.7) const { return shade (factor); }
+
+ HSV shade (double factor) const;
+ HSV mix (const HSV& other, double amt) const;
+
+ HSV opposite() const;
+ HSV complement() const { return opposite(); }
+
+ HSV bw_text () const;
+ HSV text() const;
+ HSV selected () const;
+ HSV outline() const;
+
+ void print (std::ostream&) const;
+
+protected:
+ void clamp ();
+};
+
+} /* namespace */
+
+std::ostream& operator<<(std::ostream& o, const Gtkmm2ext::HSV& hsv);
+
+#endif
diff --git a/libs/gtkmm2ext/gtkmm2ext/colorspace.h b/libs/gtkmm2ext/gtkmm2ext/colorspace.h
new file mode 100644
index 0000000000..ed350133e8
--- /dev/null
+++ b/libs/gtkmm2ext/gtkmm2ext/colorspace.h
@@ -0,0 +1,59 @@
+/**
+ * @file colorspace.h
+ * @author Pascal Getreuer 2005-2010 <getreuer@gmail.com>
+ */
+
+#ifndef _GTKMM2EXT_COLORSPACE_H_
+#define _GTKMM2EXT_COLORSPACE_H_
+
+/** @brief XYZ color of the D65 white point */
+#define WHITEPOINT_X 0.950456
+#define WHITEPOINT_Y 1.0
+#define WHITEPOINT_Z 1.088754
+
+namespace Gtkmm2ext
+{
+
+void Rgb2Yuv(double *Y, double *U, double *V, double R, double G, double B);
+void Yuv2Rgb(double *R, double *G, double *B, double Y, double U, double V);
+void Rgb2Ycbcr(double *Y, double *Cb, double *Cr, double R, double G, double B);
+void Ycbcr2Rgb(double *R, double *G, double *B, double Y, double Cb, double Cr);
+void Rgb2Jpegycbcr(double *R, double *G, double *B, double Y, double Cb, double Cr);
+void Jpegycbcr2Rgb(double *R, double *G, double *B, double Y, double Cb, double Cr);
+void Rgb2Ypbpr(double *Y, double *Pb, double *Pr, double R, double G, double B);
+void Ypbpr2Rgb(double *R, double *G, double *B, double Y, double Pb, double Pr);
+void Rgb2Ydbdr(double *Y, double *Db, double *Dr, double R, double G, double B);
+void Ydbdr2Rgb(double *R, double *G, double *B, double Y, double Db, double Dr);
+void Rgb2Yiq(double *Y, double *I, double *Q, double R, double G, double B);
+void Yiq2Rgb(double *R, double *G, double *B, double Y, double I, double Q);
+
+void Rgb2Hsv(double *H, double *S, double *V, double R, double G, double B);
+void Hsv2Rgb(double *R, double *G, double *B, double H, double S, double V);
+void Rgb2Hsl(double *H, double *S, double *L, double R, double G, double B);
+void Hsl2Rgb(double *R, double *G, double *B, double H, double S, double L);
+void Rgb2Hsi(double *H, double *S, double *I, double R, double G, double B);
+void Hsi2Rgb(double *R, double *G, double *B, double H, double S, double I);
+
+void Rgb2Xyz(double *X, double *Y, double *Z, double R, double G, double B);
+void Xyz2Rgb(double *R, double *G, double *B, double X, double Y, double Z);
+void Xyz2Lab(double *L, double *a, double *b, double X, double Y, double Z);
+void Lab2Xyz(double *X, double *Y, double *Z, double L, double a, double b);
+void Xyz2Luv(double *L, double *u, double *v, double X, double Y, double Z);
+void Luv2Xyz(double *X, double *Y, double *Z, double L, double u, double v);
+void Xyz2Lch(double *L, double *C, double *H, double X, double Y, double Z);
+void Lch2Xyz(double *X, double *Y, double *Z, double L, double C, double H);
+void Xyz2Cat02lms(double *L, double *M, double *S, double X, double Y, double Z);
+void Cat02lms2Xyz(double *X, double *Y, double *Z, double L, double M, double S);
+
+void Rgb2Lab(double *L, double *a, double *b, double R, double G, double B);
+void Lab2Rgb(double *R, double *G, double *B, double L, double a, double b);
+void Rgb2Luv(double *L, double *u, double *v, double R, double G, double B);
+void Luv2Rgb(double *R, double *G, double *B, double L, double u, double v);
+void Rgb2Lch(double *L, double *C, double *H, double R, double G, double B);
+void Lch2Rgb(double *R, double *G, double *B, double L, double C, double H);
+void Rgb2Cat02lms(double *L, double *M, double *S, double R, double G, double B);
+void Cat02lms2Rgb(double *R, double *G, double *B, double L, double M, double S);
+
+} /* namespace */
+
+#endif
diff --git a/libs/gtkmm2ext/wscript b/libs/gtkmm2ext/wscript
index 700d1dee14..8da17f58b0 100644
--- a/libs/gtkmm2ext/wscript
+++ b/libs/gtkmm2ext/wscript
@@ -31,6 +31,8 @@ gtkmm2ext_sources = [
'cell_renderer_color_selector.cc',
'cell_renderer_pixbuf_multi.cc',
'cell_renderer_pixbuf_toggle.cc',
+ 'colors.cc',
+ 'colorspace.cc',
'cursors.cc',
'debug.cc',
'dndtreeview.cc',