summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/Color.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dviware/dvisvgm/src/Color.cpp')
-rw-r--r--dviware/dvisvgm/src/Color.cpp681
1 files changed, 681 insertions, 0 deletions
diff --git a/dviware/dvisvgm/src/Color.cpp b/dviware/dvisvgm/src/Color.cpp
new file mode 100644
index 0000000000..949fcba56c
--- /dev/null
+++ b/dviware/dvisvgm/src/Color.cpp
@@ -0,0 +1,681 @@
+/*************************************************************************
+** Color.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** 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 3 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, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <array>
+#include <algorithm>
+#include <cctype>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include <sstream>
+#include "Color.hpp"
+#include "utility.hpp"
+
+using namespace std;
+
+bool Color::SUPPRESS_COLOR_NAMES = true;
+
+const Color Color::BLACK(uint32_t(0));
+const Color Color::WHITE(uint8_t(255), uint8_t(255), uint8_t(255));
+const Color Color::TRANSPARENT(uint32_t(0xff000000));
+
+
+static inline uint8_t double_to_byte (double v) {
+ v = max(0.0, min(1.0, v));
+ return uint8_t(floor(255*v+0.5));
+}
+
+
+Color::Color (const string &psname) {
+ if (!setPSName(psname, false))
+ setGray(uint8_t(0));
+}
+
+
+void Color::setRGB (double r, double g, double b) {
+ setRGB(double_to_byte(r), double_to_byte(g), double_to_byte(b));
+}
+
+
+/** Expects a PostScript color name and sets the color accordingly.
+ * @param[in] name PS color name
+ * @param[in] case_sensitive if true, upper/lower case spelling is significant
+ * @return true if color name could be applied properly */
+bool Color::setPSName (string name, bool case_sensitive) {
+ if (name[0] == '#') {
+ char *p=nullptr;
+ _rgb = uint32_t(strtol(name.c_str()+1, &p, 16));
+ while (isspace(*p))
+ p++;
+ return (*p == 0 && _rgb <= 0xFFFFFF);
+ }
+
+ struct ColorConstant {
+ const char *name;
+ const uint32_t rgb;
+ };
+ // converted color constants from color.pro
+ static const array<ColorConstant, 68> constants {{
+ {"Apricot", 0xFFAD7A},
+ {"Aquamarine", 0x2DFFB2},
+ {"Bittersweet", 0xC10200},
+ {"Black", 0x000000},
+ {"Blue", 0x0000FF},
+ {"BlueGreen", 0x26FFAA},
+ {"BlueViolet", 0x190CF4},
+ {"BrickRed", 0xB70000},
+ {"Brown", 0x660000},
+ {"BurntOrange", 0xFF7C00},
+ {"CadetBlue", 0x606DC4},
+ {"CarnationPink", 0xFF5EFF},
+ {"Cerulean", 0x0FE2FF},
+ {"CornflowerBlue", 0x59DDFF},
+ {"Cyan", 0x00FFFF},
+ {"Dandelion", 0xFFB528},
+ {"DarkOrchid", 0x9932CC},
+ {"Emerald", 0x00FF7F},
+ {"ForestGreen", 0x00E000},
+ {"Fuchsia", 0x7202EA},
+ {"Goldenrod", 0xFFE528},
+ {"Gray", 0x7F7F7F},
+ {"Green", 0x00FF00},
+ {"GreenYellow", 0xD8FF4F},
+ {"JungleGreen", 0x02FF7A},
+ {"Lavender", 0xFF84FF},
+ {"LimeGreen", 0x7FFF00},
+ {"Magenta", 0xFF00FF},
+ {"Mahogany", 0xA50000},
+ {"Maroon", 0xAD0000},
+ {"Melon", 0xFF897F},
+ {"MidnightBlue", 0x007091},
+ {"Mulberry", 0xA314F9},
+ {"NavyBlue", 0x0F75FF},
+ {"OliveGreen", 0x009900},
+ {"Orange", 0xFF6321},
+ {"OrangeRed", 0xFF007F},
+ {"Orchid", 0xAD5BFF},
+ {"Peach", 0xFF7F4C},
+ {"Periwinkle", 0x6D72FF},
+ {"PineGreen", 0x00BF28},
+ {"Plum", 0x7F00FF},
+ {"ProcessBlue", 0x0AFFFF},
+ {"Purple", 0x8C23FF},
+ {"RawSienna", 0x8C0000},
+ {"Red", 0xFF0000},
+ {"RedOrange", 0xFF3A21},
+ {"RedViolet", 0x9600A8},
+ {"Rhodamine", 0xFF2DFF},
+ {"RoyalBlue", 0x007FFF},
+ {"RoyalPurple", 0x3F19FF},
+ {"RubineRed", 0xFF00DD},
+ {"Salmon", 0xFF779E},
+ {"SeaGreen", 0x4FFF7F},
+ {"Sepia", 0x4C0000},
+ {"SkyBlue", 0x60FFE0},
+ {"SpringGreen", 0xBCFF3D},
+ {"Tan", 0xDB9370},
+ {"TealBlue", 0x1EF9A3},
+ {"Thistle", 0xE068FF},
+ {"Turquoise", 0x26FFCC},
+ {"Violet", 0x351EFF},
+ {"VioletRed", 0xFF30FF},
+ {"White", 0xFFFFFF},
+ {"WildStrawberry", 0xFF0A9B},
+ {"Yellow", 0xFFFF00},
+ {"YellowGreen", 0x8EFF42},
+ {"YellowOrange", 0xFF9300},
+ }};
+ if (case_sensitive) {
+ const ColorConstant cmppair = {name.c_str(), 0};
+ auto it = lower_bound(constants.begin(), constants.end(), cmppair,
+ [](const ColorConstant &c1, const ColorConstant &c2) {
+ return strcmp(c1.name, c2.name) < 0;
+ }
+ );
+ if (it != constants.end() && it->name == name) {
+ _rgb = it->rgb;
+ return true;
+ }
+ }
+ else {
+ name = util::tolower(name);
+ auto it = find_if(constants.begin(), constants.end(), [&](const ColorConstant &cc) {
+ return name == util::tolower(cc.name);
+ });
+ if (it != constants.end()) {
+ _rgb = it->rgb;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void Color::setHSB (double h, double s, double b) {
+ valarray<double> hsb(3), rgb(3);
+ hsb[0] = h;
+ hsb[1] = s;
+ hsb[2] = b;
+ HSB2RGB(hsb, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setCMYK (double c, double m, double y, double k) {
+ valarray<double> cmyk(4), rgb(3);
+ cmyk[0] = c;
+ cmyk[1] = m;
+ cmyk[2] = y;
+ cmyk[3] = k;
+ CMYK2RGB(cmyk, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setCMYK (const std::valarray<double> &cmyk) {
+ valarray<double> rgb(3);
+ CMYK2RGB(cmyk, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::set (ColorSpace colorSpace, VectorIterator<double> &it) {
+ switch (colorSpace) {
+ case ColorSpace::GRAY: setGray(*it++); break;
+ case ColorSpace::RGB : setRGB(*it, *(it+1), *(it+2)); it+=3; break;
+ case ColorSpace::LAB : setLab(*it, *(it+1), *(it+2)); it+=3; break;
+ case ColorSpace::CMYK: setCMYK(*it, *(it+1), *(it+2), *(it+3)); it+=4; break;
+ }
+}
+
+
+Color Color::operator *= (double c) {
+ if (abs(c) < 0.001)
+ _rgb &= 0xff000000;
+ else if (abs(c-trunc(c)) < 0.999) {
+ uint32_t rgb=0;
+ for (int i=0; i < 3; i++) {
+ rgb |= lround((_rgb & 0xff)*c) << (8*i);
+ _rgb >>= 8;
+ }
+ _rgb = rgb;
+ }
+ return *this;
+}
+
+
+/** Returns an RGB string representing the color. Depending on the
+ * color value, the string either consists of 3 or 6 hex digits
+ * plus a leading '#' character. */
+string Color::rgbString () const {
+ ostringstream oss;
+ oss << '#';
+ for (int i=2; i >= 0; i--) {
+ oss << setbase(16) << setfill('0') << setw(2)
+ << (((_rgb >> (8*i)) & 0xff));
+ }
+ // check if RGB string can be reduced to a three digit hex value
+ // #RRGGBB => #RGB, e.g. #112233 => #123
+ string hexstr = oss.str();
+ if (hexstr[1] == hexstr[2] && hexstr[3] == hexstr[4] && hexstr[5] == hexstr[6]) {
+ hexstr[2] = hexstr[3];
+ hexstr[3] = hexstr[5];
+ hexstr.resize(4);
+ }
+ return hexstr;
+}
+
+
+/** Returns an SVG color string representing the current color. The result can
+ * either be an SVG color name or an RGB string of the form #RRGGBB. The latter
+ * will be returned if parameter rgbonly is true or if the SVG standard doesn't
+ * define a name for the current color. */
+string Color::svgColorString (bool rgbonly) const {
+ if (!rgbonly) {
+ struct ColorName {
+ uint32_t rgb;
+ const char *name;
+ };
+ static const array<ColorName, 138> colornames {{
+ {0x000000, "black"},
+ {0x000080, "navy"},
+ {0x00008b, "darkblue"},
+ {0x0000cd, "mediumblue"},
+ {0x0000ff, "blue"},
+ {0x006400, "darkgreen"},
+ {0x008000, "green"},
+ {0x008080, "teal"},
+ {0x008b8b, "darkcyan"},
+ {0x00bfff, "deepskyblue"},
+ {0x00ced1, "darkturquoise"},
+ {0x00fa9a, "mediumspringgreen"},
+ {0x00ff00, "lime"},
+ {0x00ff7f, "springgreen"},
+ {0x00ffff, "cyan"},
+ {0x191970, "midnightblue"},
+ {0x1e90ff, "dodgerblue"},
+ {0x20b2aa, "lightseagreen"},
+ {0x228b22, "forestgreen"},
+ {0x2e8b57, "seagreen"},
+ {0x2f4f4f, "darkslategray"},
+ {0x32cd32, "limegreen"},
+ {0x3cb371, "mediumseagreen"},
+ {0x40e0d0, "turquoise"},
+ {0x4169e1, "royalblue"},
+ {0x4682b4, "steelblue"},
+ {0x483d8b, "darkslateblue"},
+ {0x48d1cc, "mediumturquoise"},
+ {0x4b0082, "indigo"},
+ {0x556b2f, "darkolivegreen"},
+ {0x5f9ea0, "cadetblue"},
+ {0x6495ed, "cornflowerblue"},
+ {0x66cdaa, "mediumaquamarine"},
+ {0x696969, "dimgray"},
+ {0x6a5acd, "slateblue"},
+ {0x6b8e23, "olivedrab"},
+ {0x708090, "slategray"},
+ {0x778899, "lightslategray"},
+ {0x7b68ee, "mediumslateblue"},
+ {0x7cfc00, "lawngreen"},
+ {0x7fff00, "chartreuse"},
+ {0x7fffd4, "aquamarine"},
+ {0x800000, "maroon"},
+ {0x800080, "purple"},
+ {0x808000, "olive"},
+ {0x808080, "gray"},
+ {0x87ceeb, "skyblue"},
+ {0x87cefa, "lightskyblue"},
+ {0x8a2be2, "blueviolet"},
+ {0x8b0000, "darkred"},
+ {0x8b008b, "darkmagenta"},
+ {0x8b4513, "saddlebrown"},
+ {0x8fbc8f, "darkseagreen"},
+ {0x90ee90, "lightgreen"},
+ {0x9370db, "mediumpurple"},
+ {0x9400d3, "darkviolet"},
+ {0x98fb98, "palegreen"},
+ {0x9932cc, "darkorchid"},
+ {0x9acd32, "yellowgreen"},
+ {0xa0522d, "sienna"},
+ {0xa52a2a, "brown"},
+ {0xa9a9a9, "darkgray"},
+ {0xadd8e6, "lightblue"},
+ {0xadff2f, "greenyellow"},
+ {0xafeeee, "paleturquoise"},
+ {0xb0c4de, "lightsteelblue"},
+ {0xb0e0e6, "powderblue"},
+ {0xb22222, "firebrick"},
+ {0xb8860b, "darkgoldenrod"},
+ {0xba55d3, "mediumorchid"},
+ {0xbc8f8f, "rosybrown"},
+ {0xbdb76b, "darkkhaki"},
+ {0xc0c0c0, "silver"},
+ {0xc71585, "mediumvioletred"},
+ {0xcd5c5c, "indianred"},
+ {0xcd853f, "peru"},
+ {0xd2691e, "chocolate"},
+ {0xd2b48c, "tan"},
+ {0xd3d3d3, "lightgray"},
+ {0xd8bfd8, "thistle"},
+ {0xda70d6, "orchid"},
+ {0xdaa520, "goldenrod"},
+ {0xdb7093, "palevioletred"},
+ {0xdc143c, "crimson"},
+ {0xdcdcdc, "gainsboro"},
+ {0xdda0dd, "plum"},
+ {0xdeb887, "burlywood"},
+ {0xe0ffff, "lightcyan"},
+ {0xe6e6fa, "lavender"},
+ {0xe9967a, "darksalmon"},
+ {0xee82ee, "violet"},
+ {0xeee8aa, "palegoldenrod"},
+ {0xf08080, "lightcoral"},
+ {0xf0e68c, "khaki"},
+ {0xf0f8ff, "aliceblue"},
+ {0xf0fff0, "honeydew"},
+ {0xf0ffff, "azure"},
+ {0xf4a460, "sandybrown"},
+ {0xf5deb3, "wheat"},
+ {0xf5f5dc, "beige"},
+ {0xf5f5f5, "whitesmoke"},
+ {0xf5fffa, "mintcream"},
+ {0xf8f8ff, "ghostwhite"},
+ {0xfa8072, "salmon"},
+ {0xfaebd7, "antiquewhite"},
+ {0xfaf0e6, "linen"},
+ {0xfafad2, "lightgoldenrodyellow"},
+ {0xfdf5e6, "oldlace"},
+ {0xff0000, "red"},
+ {0xff00ff, "magenta"},
+ {0xff1493, "deeppink"},
+ {0xff4500, "orangered"},
+ {0xff6347, "tomato"},
+ {0xff69b4, "hotpink"},
+ {0xff7f50, "coral"},
+ {0xff8c00, "darkorange"},
+ {0xffa07a, "lightsalmon"},
+ {0xffa500, "orange"},
+ {0xffb6c1, "lightpink"},
+ {0xffc0cb, "pink"},
+ {0xffd700, "gold"},
+ {0xffdab9, "peachpuff"},
+ {0xffdead, "navajowhite"},
+ {0xffe4b5, "moccasin"},
+ {0xffe4c4, "bisque"},
+ {0xffe4e1, "mistyrose"},
+ {0xffebcd, "blanchedalmond"},
+ {0xffefd5, "papayawhip"},
+ {0xfff0f5, "lavenderblush"},
+ {0xfff5ee, "seashell"},
+ {0xfff8dc, "cornsilk"},
+ {0xfffacd, "lemonchiffon"},
+ {0xfffaf0, "floralwhite"},
+ {0xfffafa, "snow"},
+ {0xffff00, "yellow"},
+ {0xffffe0, "lightyellow"},
+ {0xfffff0, "ivory"},
+ {0xffffff, "white"}
+ }};
+ ColorName cmppair = {_rgb, nullptr};
+ auto it = lower_bound(colornames.begin(), colornames.end(), cmppair, [](const ColorName &c1, const ColorName &c2) {
+ return c1.rgb < c2.rgb;
+ });
+ if (it != colornames.end() && it->rgb == _rgb)
+ return it->name;
+ }
+ return rgbString();
+}
+
+
+/** Approximates a CMYK color by an RGB triple. The component values
+ * are expected to be normalized, i.e. 0 <= cmyk[i],rgb[j] <= 1.
+ * @param[in] cmyk color in CMYK space
+ * @param[out] rgb RGB approximation */
+void Color::CMYK2RGB (const valarray<double> &cmyk, valarray<double> &rgb) {
+ double kc = 1.0-cmyk[3];
+ for (int i=0; i < 3; i++)
+ rgb[i] = min(1.0, max(0.0, (1.0-cmyk[i])*kc));
+}
+
+
+void Color::RGB2CMYK (const valarray<double> &rgb, valarray<double> &cmyk) {
+ double c = 1-rgb[0];
+ double m = 1-rgb[1];
+ double y = 1-rgb[2];
+ cmyk[3] = min(min(c, m), y);
+ cmyk[0] = c-cmyk[3];
+ cmyk[1] = m-cmyk[3];
+ cmyk[2] = y-cmyk[3];
+}
+
+
+/** Converts a color given in HSB coordinates to RGB.
+ * @param[in] hsb color in HSB space
+ * @param[out] rgb color in RGB space */
+void Color::HSB2RGB (const valarray<double> &hsb, valarray<double> &rgb) {
+ if (hsb[1] == 0)
+ rgb[0] = rgb[1] = rgb[2] = hsb[2];
+ else {
+ double h = hsb[0]-floor(hsb[0]);
+ int i = int(6*h);
+ double f = 6*h-i;
+ double p = hsb[2]*(1-hsb[1]);
+ double q = hsb[2]*(1-hsb[1]*f);
+ double t = hsb[2]*(1-hsb[1]*(1-f));
+ switch (i) {
+ case 0 : rgb[0]=hsb[2]; rgb[1]=t; rgb[2]=p; break;
+ case 1 : rgb[0]=q; rgb[1]=hsb[2]; rgb[2]=p; break;
+ case 2 : rgb[0]=p; rgb[1]=hsb[2]; rgb[2]=t; break;
+ case 3 : rgb[0]=p; rgb[1]=q; rgb[2]=hsb[2]; break;
+ case 4 : rgb[0]=t; rgb[1]=p; rgb[2]=hsb[2]; break;
+ case 5 : rgb[0]=hsb[2]; rgb[1]=p; rgb[2]=q; break;
+ default: ; // prevent compiler warning
+ }
+ }
+}
+
+
+double Color::getGray () const {
+ double r, g, b;
+ getRGB(r, g, b);
+ return r*0.3 + g*0.59 + b*0.11; // gray value according to NTSC video standard
+}
+
+
+void Color::getGray (valarray<double> &gray) const {
+ gray.resize(1);
+ gray[0] = getGray();
+}
+
+
+void Color::getRGB (double &r, double &g, double &b) const {
+ r = ((_rgb >> 16) & 255) / 255.0;
+ g = ((_rgb >> 8) & 255) / 255.0;
+ b = (_rgb & 255) / 255.0;
+}
+
+
+void Color::getRGB (valarray<double> &rgb) const {
+ rgb.resize(3);
+ double r, g, b;
+ getRGB(r, g, b);
+ rgb[0] = r;
+ rgb[1] = g;
+ rgb[2] = b;
+}
+
+
+void Color::getCMYK (double &c, double &m, double &y, double &k) const {
+ valarray<double> rgb(3), cmyk(4);
+ getRGB(rgb);
+ RGB2CMYK(rgb, cmyk);
+ c = cmyk[0];
+ m = cmyk[1];
+ y = cmyk[2];
+ k = cmyk[3];
+}
+
+
+void Color::getCMYK (std::valarray<double> &cmyk) const {
+ cmyk.resize(4);
+ valarray<double> rgb(3);
+ getRGB(rgb);
+ RGB2CMYK(rgb, cmyk);
+}
+
+
+void Color::getXYZ (double &x, double &y, double &z) const {
+ valarray<double> rgb(3), xyz(3);
+ getRGB(rgb);
+ RGB2XYZ(rgb, xyz);
+ x = xyz[0];
+ y = xyz[1];
+ z = xyz[2];
+}
+
+
+void Color::setXYZ (double x, double y, double z) {
+ valarray<double> xyz(3), rgb(3);
+ xyz[0] = x;
+ xyz[1] = y;
+ xyz[2] = z;
+ XYZ2RGB(xyz, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setXYZ (const valarray<double> &xyz) {
+ valarray<double> rgb(3);
+ XYZ2RGB(xyz, rgb);
+ setRGB(rgb);
+}
+
+
+void Color::setLab (double l, double a, double b) {
+ valarray<double> lab(3), xyz(3);
+ lab[0] = l;
+ lab[1] = a;
+ lab[2] = b;
+ Lab2XYZ(lab, xyz);
+ setXYZ(xyz);
+}
+
+
+void Color::setLab (const valarray<double> &lab) {
+ valarray<double> xyz(3);
+ Lab2XYZ(lab, xyz);
+ setXYZ(xyz);
+}
+
+
+/** Get the color in CIELAB color space using the sRGB working space and reference white D65. */
+void Color::getLab (double &l, double &a, double &b) const {
+ valarray<double> rgb(3), lab(3);
+ getRGB(rgb);
+ RGB2Lab(rgb, lab);
+ l = lab[0];
+ a = lab[1];
+ b = lab[2];
+}
+
+
+void Color::getLab (std::valarray<double> &lab) const {
+ lab.resize(3);
+ valarray<double> rgb(3);
+ getRGB(rgb);
+ RGB2Lab(rgb, lab);
+}
+
+
+// static inline double sqr (double x) {return x*x;}
+static inline double cube (double x) {return x*x*x;}
+
+void Color::Lab2XYZ (const valarray<double> &lab, valarray<double> &xyz) {
+ xyz.resize(3);
+ double wx=0.95047, wy=1.00, wz=1.08883; // reference white D65
+ double eps = 0.008856;
+ double kappa = 903.3;
+ double fy = (lab[0]+16)/116;
+ double fx = lab[1]/500 + fy;
+ double fz = fy - lab[2]/200;
+ double xr = (cube(fx) > eps ? cube(fx) : (116*fx-16)/kappa);
+ double yr = (lab[0] > kappa*eps ? cube((lab[0]+16)/116) : lab[0]/kappa);
+ double zr = (cube(fz) > eps ? cube(fz) : (116*fz-16)/kappa);
+ xyz[0] = xr*wx;
+ xyz[1] = yr*wy;
+ xyz[2] = zr*wz;
+}
+
+
+void Color::XYZ2RGB (const valarray<double> &xyz, valarray<double> &rgb) {
+ rgb.resize(3);
+ rgb[0] = 3.2404542*xyz[0] - 1.5371385*xyz[1] - 0.4985314*xyz[2];
+ rgb[1] = -0.9692660*xyz[0] + 1.8760108*xyz[1] + 0.0415560*xyz[2];
+ rgb[2] = 0.0556434*xyz[0] - 0.2040259*xyz[1] + 1.0572252*xyz[2];
+ for (int i=0; i < 3; i++)
+ rgb[i] = (rgb[i] <= 0.0031308 ? 12.92*rgb[i] : 1.055*pow(rgb[i], 1/2.4)-0.055);
+}
+
+
+void Color::RGB2XYZ (valarray<double> rgb, valarray<double> &xyz) {
+ xyz.resize(3);
+ for (int i=0; i < 3; i++)
+ rgb[i] = (rgb[i] <= 0.04045 ? rgb[i]/12.92 : pow((rgb[i]+0.055)/1.055, 2.4));
+ xyz[0] = 0.4124564*rgb[0] + 0.3575761*rgb[1] + 0.1804375*rgb[2];
+ xyz[1] = 0.2126729*rgb[0] + 0.7151522*rgb[1] + 0.0721750*rgb[2];
+ xyz[2] = 0.0193339*rgb[0] + 0.1191920*rgb[1] + 0.9503041*rgb[2];
+}
+
+
+void Color::RGB2Lab (const valarray<double> &rgb, valarray<double> &lab) {
+ double wx=0.95047, wy=1.00, wz=1.08883; // reference white D65
+ double eps = 0.008856;
+ double kappa = 903.3;
+ valarray<double> xyz(3);
+ RGB2XYZ(rgb, xyz);
+ xyz[0] /= wx;
+ xyz[1] /= wy;
+ xyz[2] /= wz;
+ double fx = (xyz[0] > eps ? pow(xyz[0], 1.0/3) : (kappa*xyz[0]+16)/116);
+ double fy = (xyz[1] > eps ? pow(xyz[1], 1.0/3) : (kappa*xyz[1]+16)/116);
+ double fz = (xyz[2] > eps ? pow(xyz[2], 1.0/3) : (kappa*xyz[2]+16)/116);
+ lab[0] = 116*fy-16;
+ lab[1] = 500*(fx-fy);
+ lab[2] = 200*(fy-fz);
+}
+
+
+#if 0
+/** Returns the Delta E difference (CIE 2000) between this and another color. */
+double Color::deltaE (const Color &c) const {
+ double l1, a1, b1;
+ double l2, a2, b2;
+ getLab(l1, a1, b1);
+ c.getLab(l2, a2, b2);
+ const double deltaL = l2-l1;
+ const double lBar = (l1+l2)/2;
+ const double c1 = sqrt(a1*a1 + b1*b1);
+ const double c2 = sqrt(a2*a2 + b2*b2);
+ const double cBar = (c1+c2)/2.0;
+ const double g = (1.0-sqrt(pow(cBar, 7.0)/(pow(cBar, 7.0)+6103515625.0)))/2.0;
+ const double aa1 = a1*(1.0+g);
+ const double aa2 = a2*(1.0+g);
+ const double cc1 = sqrt(aa1*aa1 + b1*b1);
+ const double cc2 = sqrt(aa2*aa2 + b2*b2);
+ const double ccBar = (cc1+cc2)/2.0;
+ const double deltaCC = cc2-cc1;
+ double hh1 = atan2(b1, aa1);
+ if (hh1 < 0) hh1 += deg2rad(360);
+ double hh2 = atan2(b2, aa2);
+ if (hh2 < 0) hh2 += deg2rad(360);
+ const double hhBar = (std::abs(hh1-hh2) > deg2rad(180) ? (hh1+hh2+deg2rad(360)) : (hh1+hh2))/2.0;
+ const double t = 1.0
+ - 0.17*cos(hhBar-deg2rad(30.0))
+ + 0.24*cos(2.0*hhBar)
+ + 0.32*cos(3.0*hhBar+deg2rad(6.0))
+ - 0.20*cos(4.0*hhBar-deg2rad(63.0));
+ double deltaHH = hh2-hh1;
+ if (deltaHH < deg2rad(-180))
+ deltaHH += deg2rad(360);
+ else if (deltaHH > deg2rad(180))
+ deltaHH -= deg2rad(360);
+ const double deltaHHH = 2.0*sqrt(cc1*cc2)*sin(deltaHH/2.0);
+ const double sl = 1.0 + 0.015*sqr(lBar-50.0)/sqrt(20.0+sqr(lBar-50.0));
+ const double sc = 1.0 + 0.045*ccBar;
+ const double sh = 1.0 + 0.015*ccBar*t;
+ const double deltaTheta = deg2rad(30)*exp(-sqr((hhBar-deg2rad(275))/deg2rad(25)));
+ const double rc = 2.0*sqrt(pow(ccBar, 7)/(pow(ccBar, 7)+6103515625.0));
+ const double rt = -rc*sin(2.0*deltaTheta);
+ const double deltaE = sqrt(sqr(deltaL/sl) + sqr(deltaCC/sc) + sqr(deltaHHH/sh) + rt*deltaCC/sc*deltaHHH/sh);
+ return deltaE;
+}
+#endif
+
+
+int Color::numComponents (ColorSpace colorSpace) {
+ switch (colorSpace) {
+ case ColorSpace::GRAY: return 1;
+ case ColorSpace::LAB:
+ case ColorSpace::RGB: return 3;
+ case ColorSpace::CMYK: return 4;
+ }
+ return 0;
+}