UNPKG

node-red-contrib-deconz

Version:
1,014 lines (922 loc) 27.8 kB
/** * @author Pascal Getreuer 2005-2010 <getreuer@gmail.com> * @author Zehir 2021 <zehir@zorim.fr> * * == 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. * * * == References == * Based on https://github.com/dresden-elektronik/deconz-rest-plugin/blob/master/colorspace.cpp * * 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. */ const Colorspace = {}; /** @brief XYZ color of the D65 white point */ Colorspace.WHITEPOINT_X = 0.950456; Colorspace.WHITEPOINT_Y = 1.0; Colorspace.WHITEPOINT_Z = 1.088754; /** @brief u'v' coordinates of the white point for CIE Lu*v* */ Colorspace.WHITEPOINT_U = (4 * Colorspace.WHITEPOINT_X) / (Colorspace.WHITEPOINT_X + 15 * Colorspace.WHITEPOINT_Y + 3 * Colorspace.WHITEPOINT_Z); Colorspace.WHITEPOINT_V = (9 * Colorspace.WHITEPOINT_Y) / (Colorspace.WHITEPOINT_X + 15 * Colorspace.WHITEPOINT_Y + 3 * Colorspace.WHITEPOINT_Z); /** @brief Min of A and B */ Colorspace.MIN = (A, B) => (A <= B ? A : B); /** @brief Max of A and B */ Colorspace.MAX = (A, B) => (A >= B ? A : B); /** @brief Min of A, B, and C */ Colorspace.MIN3 = (A, B, C) => A <= B ? Colorspace.MIN(A, C) : Colorspace.MIN(B, C); /** @brief Max of A, B, and C */ Colorspace.MAX3 = (A, B, C) => A >= B ? Colorspace.MAX(A, C) : Colorspace.MAX(B, C); /** @brief The constant pi */ Colorspace.M_PI = 3.14159265358979323846264338327950288; /** * @brief sRGB gamma correction, transforms R to R' * http://en.wikipedia.org/wiki/SRGB */ Colorspace.GAMMACORRECTION = (t) => t <= 0.0031306684425005883 ? 12.92 * t : 1.055 * Math.pow(t, 0.416666666666666667) - 0.055; /** * @brief Inverse sRGB gamma correction, transforms R' to R */ Colorspace.INVGAMMACORRECTION = (t) => t <= 0.0404482362771076 ? t / 12.92 : Math.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 */ Colorspace.LABF = (t) => t >= 8.85645167903563082e-3 ? Math.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 */ Colorspace.LABINVF = (t) => t >= 0.206896551724137931 ? t * t * t : (108.0 / 841.0) * (t - 4.0 / 29.0); /* * == 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 * * Wikipedia: http://en.wikipedia.org/wiki/YUV */ Colorspace.Rgb2Yuv = (R, G, B) => { return { Y: 0.299 * R + 0.587 * G + 0.114 * B, U: -0.147 * R - 0.289 * G + 0.436 * B, V: 0.615 * R - 0.515 * G - 0.1 * B, }; }; /** @brief Convert NTSC/PAL Y'UV to sRGB */ Colorspace.Yuv2Rgb = (Y, U, V) => { return { R: Y - 3.945707070708279e-5 * U + 1.1398279671717170825 * V, G: Y - 0.3946101641414141437 * U - 0.5805003156565656797 * V, B: Y + 2.0319996843434342537 * U - 4.813762626262513e-4 * V, }; }; /** @brief sRGB to Y'CbCr Luma + Chroma */ Colorspace.Rgb2Ycbcr = (R, G, B) => { return { Y: 65.481 * R + 128.553 * G + 24.966 * B + 16, Cb: -37.797 * R - 74.203 * G + 112.0 * B + 128, Cr: 112.0 * R - 93.786 * G - 18.214 * B + 128, }; }; /** @brief Y'CbCr to sRGB */ Colorspace.Ycbcr2Rgb = (Y, Cr, Cb) => { Y -= 16; Cb -= 128; Cr -= 128; return { R: 0.00456621004566210107 * Y + 1.1808799897946415e-9 * Cr + 0.00625892896994393634 * Cb, G: 0.00456621004566210107 * Y - 0.00153632368604490212 * Cr - 0.00318811094965570701 * Cb, B: 0.00456621004566210107 * Y + 0.00791071623355474145 * Cr + 1.1977497040190077e-8 * Cb, }; }; /** @brief sRGB to JPEG-Y'CbCr Luma + Chroma */ Colorspace.Rgb2Jpegycbcr = (R, G, B) => { let C1 = Colorspace.Rgb2Ypbpr(R, G, B); return { Y: C1.Y, Cb: C1.Pb + 0.5, Cr: C1.Pr + 0.5, }; }; /** @brief JPEG-Y'CbCr to sRGB */ Colorspace.Jpegycbcr2Rgb = (Y, Cb, Cr) => { Cb -= 0.5; Cr -= 0.5; return Colorspace.Ypbpr2Rgb(Y, Cb, Cr); }; /** @brief sRGB to Y'PbPr Luma (ITU-R BT.601) + Chroma */ Colorspace.Rgb2Ypbpr = (R, G, B) => { return { Y: 0.299 * R + 0.587 * G + 0.114 * B, Pb: -0.1687367 * R - 0.331264 * G + 0.5 * B, Pr: 0.5 * R - 0.418688 * G - 0.081312 * B, }; }; /** @brief Y'PbPr to sRGB */ Colorspace.Ypbpr2Rgb = (Y, Pb, Pr) => { return { R: 0.99999999999914679361 * Y - 1.2188941887145875e-6 * Pb + 1.4019995886561440468 * Pr, G: 0.99999975910502514331 * Y - 0.34413567816504303521 * Pb - 0.71413649331646789076 * Pr, B: 1.0000012404000462318 * Y + 1.772000066072304092 * Pb + 2.1453384174593273e-6 * Pr, }; }; /** @brief sRGB to SECAM Y'DbDr Luma + Chroma */ Colorspace.Rgb2Ydbdr = (R, G, B) => { return { Y: 0.299 * R + 0.587 * G + 0.114 * B, Db: -0.45 * R - 0.883 * G + 1.333 * B, Dr: -1.333 * R + 1.116 * G + 0.217 * B, }; }; /** @brief SECAM Y'DbDr to sRGB */ Colorspace.Ydbdr2Rgb = (Y, Db, Dr) => { return { R: Y + 9.2303716147657e-5 * Db - 0.52591263066186533 * Dr, G: Y - 0.12913289889050927 * Db + 0.26789932820759876 * Dr, B: Y + 0.66467905997895482 * Db - 7.9202543533108e-5 * Dr, }; }; /** @brief sRGB to NTSC YIQ */ Colorspace.Rgb2Yiq = (R, G, B) => { return { Y: 0.299 * R + 0.587 * G + 0.114 * B, I: 0.595716 * R - 0.274453 * G - 0.321263 * B, Q: 0.211456 * R - 0.522591 * G + 0.311135 * B, }; }; /** @brief Convert NTSC YIQ to sRGB */ Colorspace.Yiq2Rgb = (Y, I, Q) => { return { R: Y + 0.9562957197589482261 * I + 0.6210244164652610754 * Q, G: Y - 0.2721220993185104464 * I - 0.6473805968256950427 * Q, B: 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 R the input sRGB values scaled in [0,1] * @param G the input sRGB values scaled in [0,1] * @param 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 */ Colorspace.Rgb2Hsv = (R, G, B) => { let H, S, V; let Max = Colorspace.MAX3(R, G, B); let Min = Colorspace.MIN3(R, G, B); let 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; } return { H, S, V }; }; /** * @brief Convert a Hue-Saturation-Value (HSV) color to sRGB * * @param H the input HSV values * @param S the input HSV values * @param 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 */ Colorspace.Hsv2Rgb = (H, S, V) => { let R, G, B; let C = S * V; let Min = V - C; let X; H -= 360 * Math.floor(H / 360); H /= 60; X = C * (1 - Math.abs(H - 2 * Math.floor(H / 2) - 1)); switch (Math.round(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; } return { R, G, B }; }; /** * @brief Convert an sRGB color to Hue-Saturation-Lightness (HSL) * * @param R the input sRGB values scaled in [0,1] * @param G the input sRGB values scaled in [0,1] * @param 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 */ Colorspace.Rgb2Hsl = (R, G, B) => { let H, S, L; let Max = Colorspace.MAX3(R, G, B); let Min = Colorspace.MIN3(R, G, B); let 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; } return { H, S, L }; }; /** * @brief Convert a Hue-Saturation-Lightness (HSL) color to sRGB * * @param H the input HSL values * @param S the input HSL values * @param 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 */ Colorspace.Hsl2Rgb = (H, S, L) => { let R, G, B; let C = L <= 0.5 ? 2 * L * S : (2 - 2 * L) * S; let Min = L - 0.5 * C; let X; H -= 360 * floor(H / 360); H /= 60; X = C * (1 - Math.abs(H - 2 * floor(H / 2) - 1)); switch (Math.round(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; } return { R, G, B }; }; /** * @brief Convert an sRGB color to Hue-Saturation-Intensity (HSI) * * @param R the input sRGB values scaled in [0,1] * @param G the input sRGB values scaled in [0,1] * @param 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 */ Colorspace.Rgb2Hsi = (R, G, B) => { let H, S, I; let alpha = 0.5 * (2 * R - G - B); let beta = 0.866025403784439 * (G - B); I = (R + G + B) / 3; if (I > 0) { S = 1 - Colorspace.MIN3(R, G, B) / I; H = Math.atan2(beta, alpha) * (180 / Colorspace.M_PI); if (H < 0) H += 360; } else { H = S = 0; } return { H, S, I }; }; /** * @brief Convert a Hue-Saturation-Intesity (HSI) color to sRGB * * @param H the input HSI values * @param S the input HSI values * @param 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 */ Colorspace.Hsi2Rgb = (H, S, I) => { let R, G, B; H -= 360 * Math.floor(H / 360); if (H < 120) { B = I * (1 - S); R = I * (1 + (S * Math.cos(H * (Colorspace.M_PI / 180))) / Math.cos((60 - H) * (Colorspace.M_PI / 180))); G = 3 * I - R - B; } else if (H < 240) { H -= 120; R = I * (1 - S); G = I * (1 + (S * Math.cos(H * (Colorspace.M_PI / 180))) / Math.cos((60 - H) * (Colorspace.M_PI / 180))); B = 3 * I - R - G; } else { H -= 240; G = I * (1 - S); B = I * (1 + (S * Math.cos(H * (Colorspace.M_PI / 180))) / Math.cos((60 - H) * (Colorspace.M_PI / 180))); R = 3 * I - G - B; } return { R, 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 R the input sRGB values * @param G the input sRGB values * @param 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 */ Colorspace.Rgb2Xyz = (R, G, B) => { R = Colorspace.INVGAMMACORRECTION(R); G = Colorspace.INVGAMMACORRECTION(G); B = Colorspace.INVGAMMACORRECTION(B); return { X: 0.4123955889674142161 * R + 0.3575834307637148171 * G + 0.1804926473817015735 * B, Y: 0.2125862307855955516 * R + 0.7151703037034108499 * G + 0.07220049864333622685 * B, Z: 0.01929721549174694484 * R + 0.1191838645808485318 * G + 0.950497125131579766 * B, }; }; /** * @brief Transform sRGB to xy * * @param R the input sRGB values * @param G the input sRGB values * @param B the input sRGB values */ Colorspace.Rgb2Xy = (R, G, B) => { let C1 = Colorspace.Rgb2Xyz(R, G, B); let C2 = Colorspace.Xyz2Xyb(C1.X, C1.Y, C1.Z); return { X: C2.X, Y: C2.Y, }; }; /** * @brief Transform CIE XYZ to sRGB with the D65 white point * * @param X the input XYZ values * @param Y the input XYZ values * @param 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 */ Colorspace.Xyz2Rgb = (X, Y, Z) => { let R, G, B; let R1, B1, G1, Min; R1 = 3.2406 * X - 1.5372 * Y - 0.4986 * Z; G1 = -0.9689 * X + 1.8758 * Y + 0.0415 * Z; B1 = 0.0557 * X - 0.204 * Y + 1.057 * Z; Min = Colorspace.MIN3(R1, G1, B1); /* Force nonnegative values so that gamma correction is well-defined. */ if (Min < 0) { R1 -= Min; G1 -= Min; B1 -= Min; } /* Convert value greather than 1 */ if (R1 > 1 && R1 > G1 && R1 > B1) { R1 = 1; G1 /= R1; B1 /= R1; } else if (G1 > 1 && G1 > R1 && G1 > B1) { R1 /= G1; G1 = 1; B1 /= G1; } else if (B1 > 1 && B1 > R1 && B1 > G1) { R1 /= B1; G1 /= B1; B1 = 1; } /* Transform from RGB to R'G'B' */ R = Colorspace.GAMMACORRECTION(R1); G = Colorspace.GAMMACORRECTION(G1); B = Colorspace.GAMMACORRECTION(B1); return { R, G, B }; }; /** * Convert CIE XYZ to CIE L*a*b* (CIELAB) with the D65 white point * * @param X the input XYZ values * @param Y the input XYZ values * @param Z the input XYZ values * * Wikipedia: http://en.wikipedia.org/wiki/Lab_color_space */ Colorspace.Xyz2Lab = (X, Y, Z) => { let L, A, B; X /= Colorspace.WHITEPOINT_X; Y /= Colorspace.WHITEPOINT_Y; Z /= Colorspace.WHITEPOINT_Z; X = Colorspace.LABF(X); Y = Colorspace.LABF(Y); Z = Colorspace.LABF(Z); L = 116 * Y - 16; A = 500 * (X - Y); B = 200 * (Y - Z); return { L, A, B }; }; /** * Convert CIE L*a*b* (CIELAB) to CIE XYZ with the D65 white point * * @param L the input L*a*b* values * @param A the input L*a*b* values * @param B the input L*a*b* values * * Wikipedia: http://en.wikipedia.org/wiki/Lab_color_space */ Colorspace.Lab2Xyz = (L, A, B) => { L = (L + 16) / 116; A = L + A / 500; B = L - B / 200; return { X: Colorspace.WHITEPOINT_X * Colorspace.LABINVF(A), Y: Colorspace.WHITEPOINT_Y * Colorspace.LABINVF(L), Z: Colorspace.WHITEPOINT_Z * Colorspace.LABINVF(B), }; }; /** * Convert CIE XYZ to CIE L*u*v* (CIELUV) with the D65 white point * * @param X the input XYZ values * @param Y the input XYZ values * @param Z the input XYZ values * * Wikipedia: http://en.wikipedia.org/wiki/CIELUV_color_space */ Colorspace.Xyz2Luv = (X, Y, Z) => { let L, U, V; let u1, v1, Denom; if ((Denom = X + 15 * Y + 3 * Z) > 0) { u1 = (4 * X) / Denom; v1 = (9 * Y) / Denom; } else { u1 = v1 = 0; } Y /= Colorspace.WHITEPOINT_Y; Y = Colorspace.LABF(Y); L = 116 * Y - 16; U = 13 * L * (u1 - Colorspace.WHITEPOINT_U); V = 13 * L * (v1 - Colorspace.WHITEPOINT_V); return { L, U, V }; }; /** * Convert CIE L*u*v* (CIELUV) to CIE XYZ with the D65 white point * * @param L the input L*u*v* values * @param U the input L*u*v* values * @param V the input L*u*v* values * * Wikipedia: http://en.wikipedia.org/wiki/CIELUV_color_space */ Colorspace.Luv2Xyz = (L, U, V) => { let X, Y, Z; Y = (L + 16) / 116; Y = Colorspace.WHITEPOINT_Y * Colorspace.LABINVF(Y); if (L !== 0) { U /= L; V /= L; } U = U / 13 + Colorspace.WHITEPOINT_U; V = V / 13 + Colorspace.WHITEPOINT_V; X = Y * ((9 * U) / (4 * V)); Z = Y * ((3 - 0.75 * U) / V - 5); return { X, Y, Z }; }; /** * Convert CIE XYZ to CIE L*C*H* with the D65 white point * * @param X the input XYZ values * @param Y the input XYZ values * @param 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). */ Colorspace.Xyz2Lch = (X, Y, Z) => { let L, C, H; let C1 = Colorspace.Xyz2Lab(X, Y, Z); L = C1.L; C = Math.sqrt(C1.A * C1.A + C1.B * C1.B); H = (Math.atan2(C1.B, C1.A) * 180.0) / Colorspace.M_PI; if (H < 0) H += 360; return { L, C, H }; }; /** * Convert CIE L*C*H* to CIE XYZ with the D65 white point * * @param L the input L*C*H* values * @param C the input L*C*H* values * @param H the input L*C*H* values */ Colorspace.Lch2Xyz = (L, C, H) => { let a = C * Math.cos(H * (Colorspace.M_PI / 180.0)); let b = C * Math.sin(H * (Colorspace.M_PI / 180.0)); return Colorspace.Lab2Xyz(L, a, b); }; /** @brief XYZ to CAT02 LMS */ Colorspace.Xyz2Cat02lms = (X, Y, Z) => { return { L: 0.7328 * X + 0.4296 * Y - 0.1624 * Z, M: -0.7036 * X + 1.6975 * Y + 0.0061 * Z, S: 0.003 * X + 0.0136 * Y + 0.9834 * Z, }; }; /** @brief CAT02 LMS to XYZ */ Colorspace.Cat02lms2Xyz = (L, M, S) => { return { X: 1.096123820835514 * L - 0.278869000218287 * M + 0.182745179382773 * S, Y: 0.454369041975359 * L + 0.473533154307412 * M + 0.072097803717229 * S, Z: -0.009627608738429 * L - 0.005698031216113 * M + 1.015325639954543 * S, }; }; /** @brief XYB to XYZ */ Colorspace.Xyb2Xyz = (X, Y, B) => { let z = 1 - X - Y; return { X: (B / Y) * X, Y: B, Z: (B / Y) * z, }; }; /** @brief XYZ to XYB */ Colorspace.Xyz2Xyb = (X, Y, Z) => { return { X: X / (X + Y + Z), Y: Y / (X + Y + Z), B: Y, }; }; /** * == Glue functions for multi-stage transforms == */ Colorspace.Rgb2Lab = (R, G, B) => { let C1 = Colorspace.Rgb2Xyz(R, G, B); return Colorspace.Xyz2Lab(C1.X, C1.Y, C1.Z); }; Colorspace.Lab2Rgb = (L, A, B) => { let C1 = Colorspace.Lab2Xyz(L, A, B); return Colorspace.Xyz2Rgb(C1.X, C1.Y, C1.Z); }; Colorspace.Rgb2Luv = (R, G, B) => { let C1 = Colorspace.Rgb2Xyz(R, G, B); return Colorspace.Xyz2Luv(C1.X, C1.Y, C1.Z); }; Colorspace.Luv2Rgb = (L, U, V) => { let C1 = Colorspace.Luv2Xyz(L, U, V); return Colorspace.Xyz2Rgb(C1.X, C1.Y, C1.Z); }; Colorspace.Rgb2Lch = (R, G, B) => { let C1 = Colorspace.Rgb2Xyz(R, G, B); return Colorspace.Xyz2Lch(C1.X, C1.Y, C1.Z); }; Colorspace.Lch2Rgb = (L, C, H) => { let C1 = Colorspace.Lch2Xyz(L, C, H); return Colorspace.Xyz2Rgb(C1.X, C1.Y, C1.Z); }; Colorspace.Rgb2Cat02lms = (R, G, B) => { let C1 = Colorspace.Rgb2Xyz(R, G, B); return Colorspace.Xyz2Cat02lms(C1.X, C1.Y, C1.Z); }; Colorspace.Cat02lms2Rgb = (L, M, S) => { let C1 = Colorspace.Cat02lms2Xyz(L, M, S); return Colorspace.Xyz2Rgb(C1.X, C1.Y, C1.Z); }; Colorspace.Xyb2Hsv = (X, Y, B) => { let C1 = Colorspace.Xyb2Xyz(X, Y, B); let C2 = Colorspace.Xyz2Rgb(C1.X, C1.Y, C1.Z); return Colorspace.Rgb2Hsv(C2.R, C2.G, C2.B); }; Colorspace.Hsv2Xyb = (H, S, V) => { let C1 = Colorspace.Hsv2Rgb(H, S, V); let C2 = Colorspace.Rgb2Xyz(C1.R, C1.G, C1.B); return Colorspace.Xyz2Xyb(C2.X, C2.Y, C2.Z); }; Colorspace.TEMPERATURE_TO_X_TEMPERATURE_TRESHOLD = 4000; Colorspace.TEMPERATURE_TO_Y_FIRST_TEMPERATURE_TRESHOLD = 2222; Colorspace.TEMPERATURE_TO_Y_SECOND_TEMPERATURE_TRESHOLD = 4000; Colorspace.TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION = 17440695910400; Colorspace.TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION = 15358885888; Colorspace.TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION = 57520658; Colorspace.TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION = 11790; Colorspace.TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION = 198301902438400; Colorspace.TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION = 138086835814; Colorspace.TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION = 14590587; Colorspace.TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION = 15754; Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION = 18126; Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION = 22087; Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION = 35808; Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION = 3312; Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION = 15645; Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION = 22514; Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION = 34265; Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION = 2744; Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION = 50491; Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION = 96229; Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION = 61458; Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION = 6062; Colorspace.XY_TO_TEMPERATURE_X_EPICENTER = 21757; Colorspace.XY_TO_TEMPERATURE_Y_EPICENTER = 12176; /** * Converts color temperature to appropriate XY coordinates * * @param temperature - color temperature (attribute value); */ Colorspace.MiredColorTemperatureToXY = (temperature) => { if (temperature < 153) temperature = 153; let localX, localY; let temp = 1000000 / temperature; if (Colorspace.TEMPERATURE_TO_X_TEMPERATURE_TRESHOLD > temp) localX = Colorspace.TEMPERATURE_TO_X_THIRD_FACTOR_FIRST_EQUATION / temp + Colorspace.TEMPERATURE_TO_X_FOURTH_FACTOR_FIRST_EQUATION - Colorspace.TEMPERATURE_TO_X_SECOND_FACTOR_FIRST_EQUATION / temp / temp - Colorspace.TEMPERATURE_TO_X_FIRST_FACTOR_FIRST_EQUATION / temp / temp / temp; else localX = Colorspace.TEMPERATURE_TO_X_SECOND_FACTOR_SECOND_EQUATION / temp / temp + Colorspace.TEMPERATURE_TO_X_THIRD_FACTOR_SECOND_EQUATION / temp + Colorspace.TEMPERATURE_TO_X_FOURTH_FACTOR_SECOND_EQUATION - Colorspace.TEMPERATURE_TO_X_FIRST_FACTOR_SECOND_EQUATION / temp / temp / temp; if (Colorspace.TEMPERATURE_TO_Y_FIRST_TEMPERATURE_TRESHOLD > temp) localY = (Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_FIRST_EQUATION * localX) / 65536 - (Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_FIRST_EQUATION * localX * localX * localX) / 281474976710656 - (Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_FIRST_EQUATION * localX * localX) / 4294967296 - Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_FIRST_EQUATION; else if (Colorspace.TEMPERATURE_TO_Y_SECOND_TEMPERATURE_TRESHOLD > temp) localY = (Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_SECOND_EQUATION * localX) / 65536 - (Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_SECOND_EQUATION * localX * localX * localX) / 281474976710656 - (Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_SECOND_EQUATION * localX * localX) / 4294967296 - Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_SECOND_EQUATION; else { localY = (Colorspace.TEMPERATURE_TO_Y_THIRD_FACTOR_THIRD_EQUATION * localX) / 65536 + (Colorspace.TEMPERATURE_TO_Y_FIRST_FACTOR_THIRD_EQUATION * localX * localX * localX) / 281474976710656 - (Colorspace.TEMPERATURE_TO_Y_SECOND_FACTOR_THIRD_EQUATION * localX * localX) / 4294967296 - Colorspace.TEMPERATURE_TO_Y_FOURTH_FACTOR_THIRD_EQUATION; } localY *= 4; return { X: localX, Y: localY, }; }; module.exports = Colorspace;