UNPKG

chromaticity-color-utilities

Version:
1,358 lines 69.4 kB
"use strict"; // chromaticity-color-utilities // Copyright (C) 2022 Emma Litwa-Vulcu // // This program is free software: you can ristribute 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 <https://www.gnu.org/licenses/>. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var Colors = __importStar(require("./Colors")); var Util_1 = __importDefault(require("./Util")); var Reference_1 = require("./Reference"); var Convert = /** @class */ (function () { function Convert() { } /////////// NORMALIZE & GAMMA /////////// /** * Normalize RGB values to 0-1 * * @param {Colors.rgb} rgb * @return {Colors.rgbNormalized} */ Convert.rgbNormalize = function (rgb) { return new Colors.rgbNormalized(rgb.getR() / rgb.getMax(), rgb.getG() / rgb.getMax(), rgb.getB() / rgb.getMax(), rgb.getA() / rgb.getMax()); }; /** * Apply gamma to normalized RGB value * NOT to be used with sRGB, L*, or other color spaces that utilize companding transformation formulae * * @param {Colors.rgbNormalized} rgb * @param {number} gamma * @return {Colors.rgbNormalized} */ Convert.applyGamma = function (rgb, gamma) { var gammaN; if (typeof gamma === 'number') { gammaN = gamma; } else if (typeof Reference_1.colorSpaces[gamma]['gamma'] === 'number') { gammaN = Reference_1.colorSpaces[gamma]['gamma']; } else { throw new Error('Gamma not found for specificed color space'); } var r = Math.pow(rgb.getR(), gammaN); var g = Math.pow(rgb.getG(), gammaN); var b = Math.pow(rgb.getB(), gammaN); return new Colors.rgbNormalized(r, g, b, rgb.getA(), gammaN); }; /////////// HUE, SATURATION, VALUE/LIGHTNESS/BRIGHTNESS /////////// /** * Convert RGB to HSV * Saturation and Value are in percentages * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @return {Colors.hsv} */ Convert.rgb2hsv = function (rgb, round) { if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var max = Math.max(r, g, b); var min = Math.min(r, g, b); var range = max - min; var val = (max / 1) * 100; var sat = max ? (range / max) * 100 : 0; var hue; if (!range) hue = 0; else if (r == max) hue = (g - b) / range; else if (g == max) hue = (b - r) / range + 2; else if (b == max) hue = (r - g) / range + 4; else hue = 0; hue *= 60; while (hue >= 360) hue -= 360; while (hue < 0) hue += 360; var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100); if (round) { hue = Math.round(hue); sat = Math.round(sat); val = Math.round(val); a = Math.round(a); } return new Colors.hsv(hue, sat, val, a); }; /** * Convert RGB to HSL * Saturation and Lightness are in percentages * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @return {Colors.hsl} */ Convert.rgb2hsl = function (rgb, round) { if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var max = Math.max(r, g, b); var min = Math.min(r, g, b); var chroma = max - min; var lit = (max + min) / 2; var sat = (lit == 0 || lit == 1 ? 0 : (max - lit) / Math.min(lit, 1 - lit)) * 100; lit *= 100; var hue; if (!chroma) hue = 0; else if (max == r) hue = (g - b) / chroma; else if (max == g) hue = (b - r) / chroma + 2; else if (max == b) hue = (r - g) / chroma + 4; else hue = 0; hue *= 60; while (hue >= 360) hue -= 360; while (hue < 0) hue += 360; var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100); if (round) { hue = Math.round(hue); sat = Math.round(sat); lit = Math.round(lit); a = Math.round(a); } return new Colors.hsl(hue, sat, lit, a); }; /** * Convert RGB to HSI * Saturation and Intensity are in percentages * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @return {Colors.hsi} */ Convert.rgb2hsi = function (rgb, round) { if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var i = (r + g + b) / 3; var h = 0, s = 0; if (i) { s = 1 - Math.min(r, g, b) / i; h = Math.atan2((Math.sqrt(3) / 2) * (g - b), 0.5 * (2 * r - g - b)) * (180 / Math.PI); if (h < 0) h += 360; s = Math.min(Math.max(s, 0), 1); i = Math.min(Math.max(i, 0), 1); s *= 100; i *= 100; } var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100, round); if (round) { h = Math.round(h); s = Math.round(s); i = Math.round(i); } return new Colors.hsi(h, s, i, a); }; /** * Convert RGB to HSI * Saturation and Intensity are in percentages * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @return {Colors.hsi} */ Convert.rgb2hsi_deprecated = function (rgb, round) { if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var max = Math.max(r, g, b); var min = Math.min(r, g, b); var chroma = max - min; var int = (r + g + b) * (1 / 3); var hue; var sat; if (!chroma) { hue = 0; sat = 0; } else { if (max == r) hue = Util_1.default.fmod((g - b) / chroma, 6); else if (max == g) hue = (b - r) / chroma + 2; else hue = (r - g) / chroma + 4; hue *= 60; while (hue >= 360) hue -= 360; while (hue < 0) hue += 360; sat = chroma && int ? (1 - min / int) * 100 : 0; } int *= 100; var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100); if (round) { hue = Math.round(hue); sat = Math.round(sat); int = Math.round(int); a = Math.round(a); } return new Colors.hsi(hue, sat, int, a); }; /** * Convert HSV to RGB * Saturation and Value should be in percentages * * @param {Colors.hsv} hsv * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.hsv2rgb = function (hsv, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var r, g, b; var max = Math.pow(2, bitDepth) - 1; if (hsv.getS() == 0) { var all = hsv.getV() / 100; r = all; g = all; b = all; } else { var h = hsv.getH() / 60; var s = hsv.getS() / 100; var v = hsv.getV() / 100; var i = Math.floor(h); var f = h - i; var p = v * (1 - s); var q = v * (1 - s * f); var t = v * (1 - s * (1 - f)); switch (i) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; default: r = v; g = p; b = q; } } r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); r *= max; g *= max; b *= max; var a = Util_1.default.scaleValueRange(hsv.getA(), 0, 100, 0, max, round); if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); a = Math.round(a); } return new Colors.rgb(r, g, b, a, bitDepth); }; /** * Convert HSV to HSL * Saturation and Value should be in percentages * Saturation and Lightness are in percentages * * @param {Colors.hsv} hsv * @param {boolean} [round=true] * @return {Colors.hsl} */ Convert.hsv2hsl = function (hsv, round) { if (round === void 0) { round = true; } var s = hsv.getS() / 100; var v = hsv.getV() / 100; var lit = v * (1 - s / 2); var sat; if (lit == 0 || lit == 1) { sat = 0; } else { sat = (v - lit) / Math.min(lit, 1 - lit); } lit *= 100; sat *= 100; if (round) { lit = Math.round(lit); sat = Math.round(sat); } return new Colors.hsl(hsv.getH(), sat, lit, hsv.getA()); }; /** * Convert HSV to HSI * Saturation(V) and Value should be in percentages * Saturation(I) and Intensity will be in percentages * * @param {Colors.hsv} hsv * @param {boolean} [round=true] * @return {Colors.hsi} */ Convert.hsv2hsi = function (hsv, round) { if (round === void 0) { round = true; } var rgb = this.hsv2rgb(hsv, false); var hsi = this.rgb2hsi(rgb, round); return hsi; }; /** * Convert HSL to HSV * Saturation and Lightness should be in percentages * Saturation and Value are in percentages * * @param {Colors.hsl} hsl * @param {boolean} [round=true] * @return {Colors.hsv} */ Convert.hsl2hsv = function (hsl, round) { if (round === void 0) { round = true; } var s = hsl.getS() / 100; var l = hsl.getL() / 100; var val = l + s * Math.min(l, 1 - l); var sat = val ? 2 * (1 - l / val) : 0; val *= 100; sat *= 100; if (round) { val = Math.round(val); sat = Math.round(sat); } return new Colors.hsv(hsl.getH(), sat, val, hsl.getA()); }; /** * Convert HSL to RGB * Saturation and Lightness should be in percentages * * @param {Colors.hsl} hsl * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.hsl2rgb = function (hsl, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var h = hsl.getH() / 60; var s = hsl.getS() / 100; var l = hsl.getL() / 100; var r; var g; var b; if (!s) { r = l; g = l; b = l; } else { var chroma = (1 - Math.abs(2 * l - 1)) * s; var huef = Math.floor(h); var huefmod = Number((h - Math.floor(h / 2) * 2).toPrecision(8)); var x = chroma * (1 - Math.abs(huefmod - 1)); var m = l - chroma / 2; switch (huef) { case 0: r = chroma + m; g = x + m; b = m; break; case 1: r = x + m; g = chroma + m; b = m; break; case 2: r = m; g = chroma + m; b = x + m; break; case 3: r = m; g = x + m; b = chroma + m; break; case 4: r = x + m; g = m; b = chroma + m; break; case 5: r = chroma + m; g = m; b = x + m; break; default: r = m; g = m; b = m; } } r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); var max = Math.pow(2, bitDepth) - 1; r *= max; g *= max; b *= max; var a = Util_1.default.scaleValueRange(hsl.getA(), 0, 100, 0, max, round); if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); a = Math.round(a); } return new Colors.rgb(r, g, b, a, bitDepth); }; /** * Convert HSL to HSI * Saturation(L) and Lightness should be in percentages * Saturation(I) and Intensity will be in percentages * * @param {Colors.hsl} hsl * @param {boolean} [round=true] * @return {Colors.hsi} */ Convert.hsl2hsi = function (hsl, round) { if (round === void 0) { round = true; } var rgb = this.hsl2rgb(hsl, false); var hsi = this.rgb2hsi(rgb, round); return hsi; }; /** * Convert HSI to RGB * Saturation and Intensity should be in percentages * * @param {Colors.hsi} hsi * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.hsi2rgb = function (hsi, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var h = hsi.getH() - 360 * Math.floor(hsi.getH() / 360); var s = hsi.getS() / 100; var i = hsi.getI() / 100; var r, g, b; if (h < 120) { b = i * (1 - s); r = i * (1 + (s * Math.cos(h * (Math.PI / 180))) / Math.cos((60 - h) * (Math.PI / 180))); g = 3 * i - r - b; } else if (h < 240) { h = h - 120; r = i * (1 - s); g = i * (1 + (s * Math.cos(h * (Math.PI / 180))) / Math.cos((60 - h) * (Math.PI / 180))); b = 3 * i - r - g; } else { h = h - 240; g = i * (1 - s); b = i * (1 + (s * Math.cos(h * (Math.PI / 180))) / Math.cos((60 - h) * (Math.PI / 180))); r = 3 * i - g - b; } r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); var max = Math.pow(2, bitDepth) - 1; r *= max; g *= max; b *= max; if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); } var a = Util_1.default.scaleValueRange(hsi.getA(), 0, 100, 0, Math.pow(2, bitDepth) - 1, round); return new Colors.rgb(r, g, b, a, bitDepth); }; /** * Convert HSI to RGB * Saturation and Intensity should be in percentages * * @param {Colors.hsi} hsi * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.hsi2rgb_deprecated = function (hsi, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var h = hsi.getH() / 60; var s = hsi.getS() / 100; var i = hsi.getI() / 100; var m = i * (1 - s); var r; var g; var b; if (!s) { r = m; g = m; b = m; } else { var hfmod2 = Number((h - Math.floor(h / 2) * 2).toPrecision(8)); var z = 1 - Math.abs(hfmod2 - 1); var chroma = (3 * i * s) / (1 + z); var x = chroma * z; var huef = Math.floor(h); switch (huef) { case 0: r = chroma + m; g = x + m; b = m; break; case 1: r = x + m; g = chroma + m; b = m; break; case 2: r = m; g = chroma + m; b = x + m; break; case 3: r = m; g = x + m; b = chroma + m; break; case 4: r = x + m; g = m; b = chroma + m; break; case 5: r = chroma + m; g = m; b = x + m; break; default: r = m; g = m; b = m; } } r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); var max = Math.pow(2, bitDepth) - 1; r *= max; g *= max; b *= max; var a = Util_1.default.scaleValueRange(hsi.getA(), 0, 100, 0, max, round); if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); a = Math.round(a); } return new Colors.rgb(r, g, b, a, bitDepth); }; /** * Convert RGB to HSP * Saturation and Perceived Brightness will be in percentages * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @return {Colors.hsp} */ Convert.rgb2hsp = function (rgb, round, Pb, Pr) { if (round === void 0) { round = true; } if (Pb === void 0) { Pb = 0.114; } if (Pr === void 0) { Pr = 0.299; } if (Pr + Pb > 1) { throw new Error('Pr + Pg + Pb must = 1'); } var Pg = 1 - Pr - Pb; var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var pb = Math.sqrt(Math.pow(r, 2) * Pr + Math.pow(g, 2) * Pg + Math.pow(b, 2) * Pb); var value = Math.max(r, g, b); var chroma = value - Math.min(r, g, b); var s = value ? chroma / value : 0; var h; if (!chroma) h = 0; else if (value == r) h = (g - b) / chroma; else if (value == g) h = (b - r) / chroma + 2; else if (value == b) h = (r - g) / chroma + 4; else h = 0; h *= 60; while (h >= 360) h -= 360; while (h < 0) h += 360; s *= 100; pb *= 100; var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, 100, round); if (round) { h = Math.round(h); s = Math.round(s); pb = Math.round(pb); a = Math.round(a); } return new Colors.hsp(h, s, pb, a, Pb, Pr); }; /** * Convert HSP to RGB * Saturation and Perceived Brightness should be in percentages * * @param {Colors.hsp} hsp * @param {boolean} [round=true] * @return {Colors.rgb} */ Convert.hsp2rgb = function (hsp, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var hp = hsp.getH() / 60; var s = hsp.getS() / 100; var pb = hsp.getP() / 100; var s0 = 1 - s; var r, g, b; var hpf = Math.floor(hp); // console.log(s0, hpf) var hpp; if (s0 > 0) { switch (hpf) { case 0: //R>G>B hpp = hp; b = pb / Math.sqrt(hsp.getPr() / Math.pow(s0, 2) + hsp.getPg() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPb()); r = b / s0; g = b + hpp * (r - b); break; case 1: //G>R>B hpp = -1 * hp + 2; b = pb / Math.sqrt(hsp.getPg() / Math.pow(s0, 2) + hsp.getPr() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPb()); g = b / s0; r = b + hpp * (g - b); break; case 2: //G>B>R hpp = hp - 2; r = pb / Math.sqrt(hsp.getPg() / Math.pow(s0, 2) + hsp.getPb() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPr()); g = r / s0; b = r + hpp * (g - r); break; case 3: //B>G>R hpp = -1 * hp + 4; r = pb / Math.sqrt(hsp.getPb() / Math.pow(s0, 2) + hsp.getPg() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPr()); b = r / s0; g = r + hpp * (b - r); break; case 4: //B>R>G hpp = hp - 4; g = pb / Math.sqrt(hsp.getPb() / Math.pow(s0, 2) + hsp.getPr() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPg()); b = g / s0; r = g + hpp * (b - g); break; case 5: //R>B>G default: hpp = -1 * hp + 6; g = pb / Math.sqrt(hsp.getPr() / Math.pow(s0, 2) + hsp.getPb() * Math.pow(1 + hpp * (1 / s0 - 1), 2) + hsp.getPg()); r = g / s0; b = g + hpp * (r - g); } } else { switch (hpf) { case 0: //R>G>B hpp = hp; r = Math.sqrt(Math.pow(pb, 2) / (hsp.getPr() + hsp.getPg() * Math.pow(hpp, 2))); g = r * hpp; b = 0; break; case 1: //G>R>B hpp = -1 * hp + 2; g = Math.sqrt(Math.pow(pb, 2) / (hsp.getPg() + hsp.getPr() * Math.pow(hpp, 2))); r = g * hpp; b = 0; break; case 2: //G>B>R hpp = hp - 2; g = Math.sqrt(Math.pow(pb, 2) / (hsp.getPg() + hsp.getPb() * Math.pow(hpp, 2))); b = g * hpp; r = 0; break; case 3: //B>G>R hpp = -1 * hp + 4; b = Math.sqrt(Math.pow(pb, 2) / (hsp.getPb() + hsp.getPg() * Math.pow(hpp, 2))); g = b * hpp; r = 0; break; case 4: //B>R>G hpp = hp - 4; b = Math.sqrt(Math.pow(pb, 2) / (hsp.getPb() + hsp.getPr() * Math.pow(hpp, 2))); r = b * hpp; g = 0; break; case 5: //R>B>G default: hpp = -1 * hp + 6; r = Math.sqrt(Math.pow(pb, 2) / (hsp.getPr() + hsp.getPb() * Math.pow(hpp, 2))); b = r * hpp; g = 0; } } r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); var max = Math.pow(2, bitDepth) - 1; r *= max; g *= max; b *= max; var a = Util_1.default.scaleValueRange(hsp.getA(), 0, 100, 0, max, round); if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); a = Math.round(a); } return new Colors.rgb(r, g, b, a, bitDepth); }; /////////// CMYK /////////// /** * Convert RGB to CMYK * This conversion is mathematical and does not take pigment conversion into account * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @param {number} [bitDepth=255] RGB max value per channel * @return {Colors.cmyk} */ Convert.rgb2cmyk = function (rgb, round) { if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); //todo: if alpha, blend value with white var c; var m; var y; var k = 1 - Math.max(r, g, b); if (k == 1) { c = 0; m = 0; y = 0; } else { c = ((1 - r - k) / (1 - k)) * 100; m = ((1 - g - k) / (1 - k)) * 100; y = ((1 - b - k) / (1 - k)) * 100; } k *= 100; if (round) { c = Math.round(c); m = Math.round(m); y = Math.round(y); k = Math.round(k); } return new Colors.cmyk(c, m, y, k); }; /** * Convert CMYK to RGB * This conversion is mathematical and does not take pigment conversion into account * * @param {Colors.cmyk} cmyk * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.cmyk2rgb = function (cmyk, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var c = cmyk.getC() / 100; var m = cmyk.getM() / 100; var y = cmyk.getY() / 100; var k = cmyk.getK() / 100; var max = Math.pow(2, bitDepth) - 1; var r = (1 - c) * (1 - k) * max; var g = (1 - m) * (1 - k) * max; var b = (1 - y) * (1 - k) * max; if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); } return new Colors.rgb(r, g, b, max, bitDepth); }; /////////// YIQ /////////// /** * Convert RGB to YIQ * TODO: Validate Algorithm * * @param {Colors.rgb} rgb * @param {boolean} [normalize=true] true = Y[0,255], I&Q[-128,128]; false = Y[0,1], I[-0.5957,0.5957], Q[-0.5226,0.5226] * @param {boolean} [round=true] will not round if not normalized * @return {Colors.yiq} */ Convert.rgb2yiq = function (rgb, normalize, round) { if (normalize === void 0) { normalize = true; } if (round === void 0) { round = true; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var y = 0.299 * r + 0.587 * g + 0.114 * b; var i = 0.5959 * r + -0.2746 * g + -0.3213 * b; var q = 0.2115 * r + -0.5227 * g + 0.3112 * b; y = Math.min(Math.max(y, 0), 1); i = Math.min(Math.max(i, -0.5957), 0.5957); q = Math.min(Math.max(q, -0.5226), 0.5226); if (normalize) { y = Util_1.default.scaleValueRange(y, 0, 1, 0, 255, false); i = Util_1.default.scaleValueRange(i + 0.5957, 0, 1.1914, 0, 256, false) - 128; q = Util_1.default.scaleValueRange(q + 0.5226, 0, 1.0452, 0, 256, false) - 128; if (round) { y = Math.round(y); i = Math.round(i); q = Math.round(q); } } return new Colors.yiq(y, i, q, normalize); }; /** * Convert YIQ to RGB * * @param {Colors.yiq} yiq * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.yiq2rgb = function (yiq, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var y = yiq.getY(); var i = yiq.getI(); var q = yiq.getQ(); if (yiq.isNormalized()) { y = Util_1.default.scaleValueRange(y, 0, 255, 0, 1, false); i = Util_1.default.scaleValueRange(i, -128, 128, -0.5957, 0.5957, false); q = Util_1.default.scaleValueRange(q, -128, 128, -0.5226, 0.5226, false); } var r = y + 0.956 * i + 0.621 * q; var g = y + -0.272 * i + -0.647 * q; var b = y + -1.106 * i + 1.703 * q; r = Math.min(Math.max(r, 0), 1); g = Math.min(Math.max(g, 0), 1); b = Math.min(Math.max(b, 0), 1); var max = Math.pow(2, bitDepth) - 1; r *= max; g *= max; b *= max; if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); } return new Colors.rgb(r, g, b, max, bitDepth); }; /////////// XYZ, xyY /////////// /** * Convert RGB to XYZ * X, Y, and Z will be between 0 and the white point reference XYZ values * * @param {Colors.rgb} rgb * @param {string} [colorSpace=srgb] RGB color space (e.g. sRGB) * @param {string} [referenceWhite=d65] RGB reference white (e.g. D65) * @return {Colors.xyz} */ Convert.rgb2xyz = function (rgb, colorSpace, referenceWhite) { if (colorSpace === void 0) { colorSpace = 'srgb'; } if (referenceWhite === void 0) { referenceWhite = 'd65'; } var r = rgb.getR() / rgb.getMax(); var g = rgb.getG() / rgb.getMax(); var b = rgb.getB() / rgb.getMax(); var space = Util_1.default.validColorSpace(colorSpace); referenceWhite = referenceWhite.toLowerCase(); if (typeof space['rgb2xyz'] == 'undefined' || typeof space['rgb2xyz'][referenceWhite] == 'undefined') { throw new Error('Transformation matrix unavailable for this color space and reference white'); } var m = space['rgb2xyz'][referenceWhite]; if (colorSpace == 'srgb') { // sRGB r = r <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); g = g <= 0.04045 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); b = b <= 0.04045 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); } else if (colorSpace == 'ecirgb') { // L* r = r <= 0.08 ? 100 * (r / Reference_1.cieK) : Math.pow((r + 0.16) / 1.16, 3); g = g <= 0.08 ? 100 * (g / Reference_1.cieK) : Math.pow((g + 0.16) / 1.16, 3); b = b <= 0.08 ? 100 * (b / Reference_1.cieK) : Math.pow((b + 0.16) / 1.16, 3); } else { // Gamma if (typeof space['gamma'] == 'undefined') { throw new Error('Gamma not defined for this color space'); } var gamma = space['gamma']; r = Math.pow(r, gamma); g = Math.pow(g, gamma); b = Math.pow(b, gamma); } // [X] [R] // [Y] = [M 3x3]*[G] // [Z] [B] var x = m[0][0] * r + m[0][1] * g + m[0][2] * b; var y = m[1][0] * r + m[1][1] * g + m[1][2] * b; var z = m[2][0] * r + m[2][1] * g + m[2][2] * b; return new Colors.xyz(x, y, z); }; /** * Convert XYZ to RGB * RGB values that fall outsize representable values will be clamped * * @param {Colors.xyz} xyz * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.xyz2rgb = function (xyz, colorSpace, referenceWhite, round, bitDepth) { if (colorSpace === void 0) { colorSpace = 'srgb'; } if (referenceWhite === void 0) { referenceWhite = 'd65'; } if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var space = Util_1.default.validColorSpace(colorSpace); referenceWhite = referenceWhite.toLowerCase(); if (typeof space['xyz2rgb'] == 'undefined' || typeof space['xyz2rgb'][referenceWhite] == 'undefined') { throw new Error('Transformation matrix unavailable for this color space and reference white'); } var m = space['xyz2rgb'][referenceWhite]; // [R] [X] // [G] = [M]*[Y] where [M] is [RGB to XYZ matrix]^-1 // [B] [Z] var r = m[0][0] * xyz.getX() + m[0][1] * xyz.getY() + m[0][2] * xyz.getZ(); var g = m[1][0] * xyz.getX() + m[1][1] * xyz.getY() + m[1][2] * xyz.getZ(); var b = m[2][0] * xyz.getX() + m[2][1] * xyz.getY() + m[2][2] * xyz.getZ(); if (colorSpace == 'srgb') { // sRGB r = r <= 0.0031308 ? r * 12.92 : 1.055 * Math.pow(r, 1 / 2.4) - 0.055; g = g <= 0.0031308 ? g * 12.92 : 1.055 * Math.pow(g, 1 / 2.4) - 0.055; b = b <= 0.0031308 ? b * 12.92 : 1.055 * Math.pow(b, 1 / 2.4) - 0.055; } else if (colorSpace == 'ecirgb') { // L* r = r <= Reference_1.cieE ? (r * Reference_1.cieK) / 100 : 1.16 * Math.pow(r, 1 / 3) - 0.16; r = g <= Reference_1.cieE ? (g * Reference_1.cieK) / 100 : 1.16 * Math.pow(g, 1 / 3) - 0.16; r = b <= Reference_1.cieE ? (b * Reference_1.cieK) / 100 : 1.16 * Math.pow(b, 1 / 3) - 0.16; } else { // Gamma if (typeof space['gamma'] == 'undefined') { throw new Error('Gamma not defined for this color space'); } r = Math.pow(r, 1 / space['gamma']); g = Math.pow(g, 1 / space['gamma']); b = Math.pow(b, 1 / space['gamma']); } var max = Math.pow(2, bitDepth) - 1; r = Math.min(Math.max(r, 0), 1) * max; g = Math.min(Math.max(g, 0), 1) * max; b = Math.min(Math.max(b, 0), 1) * max; if (round) { r = Math.round(r); g = Math.round(g); b = Math.round(b); } return new Colors.rgb(r, g, b, max, bitDepth); }; /** * Convert XYZ to xyY * If X = Z = Y = 0, set x and y to chromaticity coordinates of reference white * * @param {Colors.xyz} xyz * @return {Colors.xyy} */ Convert.xyz2xyy = function (xyz, referenceWhite) { if (referenceWhite === void 0) { referenceWhite = 'd65'; } var w = Util_1.default.validReferenceWhite(referenceWhite); var cx; var cy; var sum = xyz.getX() + xyz.getY() + xyz.getZ(); if (!sum) { cx = w.x; cy = w.y; } else { cx = xyz.getX() / sum; cy = xyz.getY() / sum; } return new Colors.xyy(cx, cy, xyz.getY()); }; /** * Convert xyY to XYZ * * @param {Colors.xyy} xyy * @return {Colors.xyz} */ Convert.xyy2xyz = function (xyy) { var cx, cz; if (!xyy.getYY()) { cx = 0; cz = 0; } else { cx = (xyy.getX() * xyy.getYY()) / xyy.getY(); cz = ((1 - xyy.getX() - xyy.getY()) * xyy.getYY()) / xyy.getY(); } return new Colors.xyz(cx, xyy.getYY(), cz); }; /////////// Lab /////////// /** * Convert XYZ to Lab * * @param {Colors.xyz} xyz * @param {string} referenceWhite * @param {boolean} [round=true] * @return {Colors.lab} */ Convert.xyz2lab = function (xyz, referenceWhite, round) { if (referenceWhite === void 0) { referenceWhite = 'd65'; } if (round === void 0) { round = true; } var w = Util_1.default.validReferenceWhite(referenceWhite); var xr = xyz.getX() / w.x; var yr = xyz.getY() / w.y; var zr = xyz.getZ() / w.z; var fx = xr > Reference_1.cieE ? Math.pow(xr, 1 / 3) : (Reference_1.cieK * xr + 16) / 116; var fy = yr > Reference_1.cieE ? Math.pow(yr, 1 / 3) : (Reference_1.cieK * yr + 16) / 116; var fz = zr > Reference_1.cieE ? Math.pow(zr, 1 / 3) : (Reference_1.cieK * zr + 16) / 116; var l = 116 * fy - 16; var a = 500 * (fx - fy); var b = 200 * (fy - fz); l = Math.max(l, 0); // specular white can be over 100 if (round) { l = Math.round(l); a = Math.round(a); b = Math.round(b); } return new Colors.lab(l, a, b); }; /** * Convert Lab to XYZ * * @param {Colors.lab} lab * @return {Colors.xyz} */ Convert.lab2xyz = function (lab, referenceWhite) { if (referenceWhite === void 0) { referenceWhite = 'd65'; } var w = Util_1.default.validReferenceWhite(referenceWhite); var lr = (lab.getL() + 16) / 116; // y var ar = lab.getA() / 500 + lr; // x var br = lr - lab.getB() / 200; // z var xr = Math.pow(ar, 3) > Reference_1.cieE ? Math.pow(ar, 3) : (116 * ar - 16) / Reference_1.cieK; // the following two y(r) formulae seem to be equivalent??? somehow??? // let yr = lab.getL() > cieK * cieE ? Math.pow(lr, 3) : lab.getL() / cieK var yr = Math.pow(lr, 3) > Reference_1.cieE ? Math.pow(lr, 3) : (116 * lr - 16) / Reference_1.cieK; var zr = Math.pow(br, 3) > Reference_1.cieE ? Math.pow(br, 3) : (116 * br - 16) / Reference_1.cieK; var x = xr * w.x; var y = yr * w.y; var z = zr * w.z; return new Colors.xyz(x, y, z); }; /////////// Luv /////////// /** * Convert XYZ to Luv * L will range between 0% and 100% * u and v will range between -100% and 100% * * @param {Colors.xyz} xyz * @return {Colors.luv} */ Convert.xyz2luv = function (xyz, referenceWhite, round) { if (referenceWhite === void 0) { referenceWhite = 'd65'; } if (round === void 0) { round = true; } var w = Util_1.default.validReferenceWhite(referenceWhite); var yr = xyz.getY() / w.y; var div = xyz.getX() + 15 * xyz.getY() + 3 * xyz.getZ(); var up, vp; if (!div) { up = 0; vp = 0; } else { up = (4 * xyz.getX()) / div; vp = (9 * xyz.getY()) / div; } var upr = (4 * w.x) / (w.x + 15 * w.y + 3 * w.z); var vpr = (9 * w.y) / (w.x + 15 * w.y + 3 * w.z); var l = yr > Reference_1.cieE ? 116 * Math.pow(yr, 1 / 3) - 16 : Reference_1.cieK * yr; var u = 13 * l * (up - upr); var v = 13 * l * (vp - vpr); l = Math.min(Math.max(l, 0), 100); if (round) { l = Math.round(l); u = Math.round(u); v = Math.round(v); } return new Colors.luv(l, u, v); }; /** * Convert Luv to XYZ * X, Y, and Z will be in range 0 to 1 * * @param {Colors.luv} luv * @return {Colors.xyz} */ Convert.luv2xyz = function (luv, referenceWhite) { if (referenceWhite === void 0) { referenceWhite = 'd65'; } var w = Util_1.default.validReferenceWhite(referenceWhite); var u0 = (4 * w.x) / (w.x + 15 * w.y + 3 * w.z); var v0 = (9 * w.y) / (w.x + 15 * w.y + 3 * w.z); var y = luv.getL() > Reference_1.cieK * Reference_1.cieE ? Math.pow((luv.getL() + 16) / 116, 3) : luv.getL() / Reference_1.cieK; var ad = luv.getU() + 13 * luv.getL() * u0; var adf = ad ? (52 * luv.getL()) / ad : 0; var a = (1 / 3) * (adf - 1); var x = a + 1 / 3 ? (y * ((39 * luv.getL()) / (luv.getV() + 13 * luv.getL() * v0) - 5) + 5 * y) / (a + 1 / 3) : 0; var z = x * a - 5 * y; return new Colors.xyz(x, y, z); }; /////////// YCbCr and STANDARDS /////////// /** * Convert RGB to Rec709 RGB * Will output either 8-bit or 10-bit depending on input color space * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @param {number} [bitRate=8] * @return {Colors.rec709rgb} */ Convert.rgb2rec709rgb = function (rgb, round, bitRate) { if (round === void 0) { round = true; } if (bitRate === void 0) { bitRate = 8; } // output must be 8-bit or 10-bit, pick whichever is closer to input depth var rgbLower, rgbUpper; if (bitRate == 8) { rgbLower = 16; rgbUpper = 235; } else if (bitRate == 10) { rgbLower = 64; rgbUpper = 940; } else { throw new Error('Invalid bitrate for Rec709, must be 8 or 10'); } var r = Util_1.default.scaleValueRange(rgb.getR(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var g = Util_1.default.scaleValueRange(rgb.getG(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var b = Util_1.default.scaleValueRange(rgb.getB(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, Math.pow(2, bitRate) - 1, round); return new Colors.rec709rgb(r, g, b, a, bitRate); }; /** * Convert Rec709 RGB to RGB * Converts 8-bit or 10-bit Rec709 RGB values to standard (0 - $color_depth) range * Input RGB values outside of legal black and white points will be clamped * * @param {Colors.rec709rgb} rgb709 * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.rec709rgb2rgb = function (rgb709, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var minFrom, maxFrom; if (rgb709.getBitDepth() == 8) { minFrom = 16; maxFrom = 235; } else if (rgb709.getBitDepth() == 10) { minFrom = 64; maxFrom = 940; } else { throw new Error('Invalid bitrate (must be 8 or 10)'); } // Rather than require bounds, clamp values var r709 = Math.min(Math.max(rgb709.getR(), minFrom), maxFrom); var g709 = Math.min(Math.max(rgb709.getG(), minFrom), maxFrom); var b709 = Math.min(Math.max(rgb709.getB(), minFrom), maxFrom); var a709 = Math.min(Math.max(rgb709.getA(), minFrom), maxFrom); var max = Math.pow(2, bitDepth) - 1; var r = Util_1.default.scaleValueRange(r709, minFrom, maxFrom, 0, max, round); var g = Util_1.default.scaleValueRange(g709, minFrom, maxFrom, 0, max, round); var b = Util_1.default.scaleValueRange(b709, minFrom, maxFrom, 0, max, round); var a = Util_1.default.scaleValueRange(a709, 0, rgb709.getMax(), 0, max, round); return new Colors.rgb(r, g, b, a, bitDepth); }; /** * Convert RGB to Rec2020 RGB * Will output either 10-bit or 12-bit depending on input color space * * @param {Colors.rgb} rgb * @param {boolean} [round=true] * @param {number} [bitRate=10] * @return {Colors.rec2020rgb} */ Convert.rgb2rec2020rgb = function (rgb, round, bitRate) { if (round === void 0) { round = true; } if (bitRate === void 0) { bitRate = 10; } // output must be 10-bit or 12-bit, pick whichever is closer to input depth var rgbLower, rgbUpper; if (bitRate == 10) { rgbLower = 64; rgbUpper = 940; } else if (bitRate == 12) { rgbLower = 256; rgbUpper = 3760; } else { throw new Error('Invalid bitrate for Rec2020, must be 10 or 12'); } var r = Util_1.default.scaleValueRange(rgb.getR(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var g = Util_1.default.scaleValueRange(rgb.getG(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var b = Util_1.default.scaleValueRange(rgb.getB(), 0, rgb.getMax(), rgbLower, rgbUpper, round); var a = Util_1.default.scaleValueRange(rgb.getA(), 0, rgb.getMax(), 0, Math.pow(2, bitRate) - 1, round); return new Colors.rec2020rgb(r, g, b, a, bitRate); }; /** * Convert Rec2020 RGB to RGB * Converts 10-bit or 12-bit Rec2020 RGB values to standard (0 - $color_depth) range * Input RGB values outside of legal black and white points will be clamped * * @param {Colors.rec2020rgb} rgb2020 * @param {boolean} [round=true] * @param {number} [bitDepth=8] * @return {Colors.rgb} */ Convert.rec2020rgb2rgb = function (rgb2020, round, bitDepth) { if (round === void 0) { round = true; } if (bitDepth === void 0) { bitDepth = 8; } var minFrom, maxFrom; if (rgb2020.getBitDepth() == 10) { minFrom = 64; maxFrom = 940; } else if (rgb2020.getBitDepth() == 12) { minFrom = 256; maxFrom = 3760; } else { throw new Error('Invalid bitrate (must be 10 or 12)'); } // Rather than require bounds, clamp values var r2020 = Math.min(Math.max(rgb2020.getR(), minFrom), maxFrom); var g2020 = Math.min(Math.max(rgb2020.getG(), minFrom), maxFrom); va