/* * Copyright (C) 2009-2022 Apple Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS 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 APPLE OR ITS 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. */ WI.Color = class Color { constructor(format, components, gamut) { this.format = format; console.assert(gamut === undefined || Object.values(WI.Color.Gamut).includes(gamut)); this._gamut = gamut || WI.Color.Gamut.SRGB; console.assert(components.length === 3 || components.length === 4, components); this.alpha = components.length === 4 ? components[3] : 1; this._rgb = null; this._normalizedRGB = null; this._hsl = null; if (format === WI.Color.Format.HSL || format === WI.Color.Format.HSLA) this._hsl = components.slice(0, 3); else if (format === WI.Color.Format.ColorFunction) this._normalizedRGB = components.slice(0, 3); else this._rgb = components.slice(0, 3); this.valid = !components.some(isNaN); } // Static static fromString(colorString) { const matchRegExp = /^(?:#(?[0-9a-f]{3,8})|rgba?\((?[^)]+)\)|(?\w+)|color\((?[^)]+)\)|hsla?\((?[^)]+)\))$/i; let match = colorString.match(matchRegExp); if (!match) return null; if (match.groups.hex) { let hex = match.groups.hex.toUpperCase(); switch (hex.length) { case 3: return new WI.Color(WI.Color.Format.ShortHEX, [ parseInt(hex.charAt(0) + hex.charAt(0), 16), parseInt(hex.charAt(1) + hex.charAt(1), 16), parseInt(hex.charAt(2) + hex.charAt(2), 16), 1 ]); case 6: return new WI.Color(WI.Color.Format.HEX, [ parseInt(hex.substring(0, 2), 16), parseInt(hex.substring(2, 4), 16), parseInt(hex.substring(4, 6), 16), 1 ]); case 4: return new WI.Color(WI.Color.Format.ShortHEXAlpha, [ parseInt(hex.charAt(0) + hex.charAt(0), 16), parseInt(hex.charAt(1) + hex.charAt(1), 16), parseInt(hex.charAt(2) + hex.charAt(2), 16), parseInt(hex.charAt(3) + hex.charAt(3), 16) / 255 ]); case 8: return new WI.Color(WI.Color.Format.HEXAlpha, [ parseInt(hex.substring(0, 2), 16), parseInt(hex.substring(2, 4), 16), parseInt(hex.substring(4, 6), 16), parseInt(hex.substring(6, 8), 16) / 255 ]); } return null; } if (match.groups.keyword) { let keyword = match.groups.keyword.toLowerCase(); if (!WI.Color.Keywords.hasOwnProperty(keyword)) return null; let color = new WI.Color(WI.Color.Format.Keyword, WI.Color.Keywords[keyword].slice()); color.keyword = keyword; color.original = colorString; return color; } function splitFunctionString(string) { return string.trim().replace(/(\s*(,|\/)\s*|\s+)/g, "|").split("|"); } function parseFunctionAlpha(alpha) { let value = parseFloat(alpha); if (alpha.includes("%")) value /= 100; return Number.constrain(value, 0, 1); } if (match.groups.rgb) { let rgb = splitFunctionString(match.groups.rgb); if (rgb.length !== 3 && rgb.length !== 4) return null; function parseFunctionComponent(component) { let value = parseFloat(component); if (component.includes("%")) value = value * 255 / 100; return Number.constrain(value, 0, 255); } let alpha = 1; if (rgb.length === 4) alpha = parseFunctionAlpha(rgb[3]); return new WI.Color(rgb.length === 4 ? WI.Color.Format.RGBA : WI.Color.Format.RGB, [ parseFunctionComponent(rgb[0]), parseFunctionComponent(rgb[1]), parseFunctionComponent(rgb[2]), alpha, ]); } if (match.groups.hsl) { let hsl = splitFunctionString(match.groups.hsl); if (hsl.length !== 3 && hsl.length !== 4) return null; let alpha = 1; if (hsl.length === 4) alpha = parseFunctionAlpha(hsl[3]); function parseHueComponent(hue) { let value = parseFloat(hue); if (/(\b|\d)rad\b/.test(hue)) value = value * 180 / Math.PI; else if (/(\b|\d)grad\b/.test(hue)) value = value * 360 / 400; else if (/(\b|\d)turn\b/.test(hue)) value = value * 360; return Number.constrain(value, 0, 360); } function parsePercentageComponent(component) { let value = parseFloat(component); return Number.constrain(value, 0, 100); } return new WI.Color(hsl.length === 4 ? WI.Color.Format.HSLA : WI.Color.Format.HSL, [ parseHueComponent(hsl[0]), parsePercentageComponent(hsl[1]), parsePercentageComponent(hsl[2]), alpha, ]); } if (match.groups.color) { let colorString = match.groups.color.trim(); let components = splitFunctionString(colorString); if (components.length !== 4 && components.length !== 5) return null; let gamut = components[0].toLowerCase(); if (!Object.values(WI.Color.Gamut).includes(gamut)) return null; let alpha = 1; if (components.length === 5) alpha = parseFunctionAlpha(components[4]); function parseFunctionComponent(component) { let value = parseFloat(component); return Number.constrain(value, 0, 1); } return new WI.Color(WI.Color.Format.ColorFunction, [ parseFunctionComponent(components[1]), parseFunctionComponent(components[2]), parseFunctionComponent(components[3]), alpha, ], gamut); } return null; } static fromStringBestMatchingSuggestedFormatAndGamut(colorString, {suggestedFormat, suggestedGamut, forceSuggestedFormatAndGamut} = {}) { let newColor = WI.Color.fromString(colorString); if (forceSuggestedFormatAndGamut) { newColor.format = suggestedFormat; newColor.gamut = suggestedGamut; return newColor; } // Match the suggested gamut if we can do so losslessly. if (suggestedGamut === WI.Color.Gamut.DisplayP3 && newColor.gamut !== WI.Color.Gamut.DisplayP3) newColor.gamut = WI.Color.Gamut.DisplayP3; else if (suggestedGamut !== WI.Color.Gamut.DisplayP3 && newColor.gamut === WI.Color.Gamut.DisplayP3 && !newColor.isOutsideSRGB()) newColor.gamut = WI.Color.Gamut.SRGB; // Non-sRGB gamuts can only be expressed in the Color Function format. if (newColor.gamut !== WI.Color.Gamut.SRGB) return newColor; // Match as closely as possible the suggested format, and progressively adjust the format (e.g. ShortHEX -> HEX // -> HEXAlpha) if an exact match would be lossy. switch (suggestedFormat) { case WI.Color.Format.Original: console.assert(false, "No color should have a format of 'Original'."); break; case WI.Color.Format.Keyword: // Use the format of the color string as-provided. break; case WI.Color.Format.HEX: newColor.format = newColor.simple ? WI.Color.Format.HEX : WI.Color.Format.HEXAlpha; break; case WI.Color.Format.ShortHEX: if (newColor.canBeSerializedAsShortHEX()) newColor.format = newColor.simple ? WI.Color.Format.ShortHEX : WI.Color.Format.ShortHEXAlpha; else newColor.format = newColor.simple ? WI.Color.Format.HEX : WI.Color.Format.HEXAlpha; break; case WI.Color.Format.ShortHEXAlpha: newColor.format = newColor.canBeSerializedAsShortHEX() ? WI.Color.Format.ShortHEXAlpha : WI.Color.Format.HEXAlpha; break; case WI.Color.Format.RGB: newColor.format = newColor.simple ? WI.Color.Format.RGB : WI.Color.Format.RGBA; break; case WI.Color.Format.HSL: newColor.format = newColor.simple ? WI.Color.Format.HSL : WI.Color.Format.HSLA; break; case WI.Color.Format.HEXAlpha: case WI.Color.Format.RGBA: case WI.Color.Format.HSLA: case WI.Color.Format.ColorFunction: newColor.format = suggestedFormat; break; default: console.assert(false, "Should not be reached.", suggestedFormat); break; } return newColor; } static rgb2hsl(r, g, b) { r = WI.Color._eightBitChannel(r) / 255; g = WI.Color._eightBitChannel(g) / 255; b = WI.Color._eightBitChannel(b) / 255; let min = Math.min(r, g, b); let max = Math.max(r, g, b); let delta = max - min; let h = 0; let s = 0; let l = (max + min) / 2; if (delta === 0) h = 0; else if (max === r) h = (60 * ((g - b) / delta)) % 360; else if (max === g) h = 60 * ((b - r) / delta) + 120; else if (max === b) h = 60 * ((r - g) / delta) + 240; if (h < 0) h += 360; // Saturation if (delta === 0) s = 0; else s = delta / (1 - Math.abs((2 * l) - 1)); return [ h, s * 100, l * 100, ]; } static hsl2rgb(h, s, l) { h = Number.constrain(h, 0, 360) % 360; s = Number.constrain(s, 0, 100) / 100; l = Number.constrain(l, 0, 100) / 100; let c = (1 - Math.abs((2 * l) - 1)) * s; let x = c * (1 - Math.abs(((h / 60) % 2) - 1)); let m = l - (c / 2); let r = 0; let g = 0; let b = 0; if (h < 60) { r = c; g = x; } else if (h < 120) { r = x; g = c; } else if (h < 180) { g = c; b = x; } else if (h < 240) { g = x; b = c; } else if (h < 300) { r = x; b = c; } else if (h < 360) { r = c; b = x; } return [ (r + m) * 255, (g + m) * 255, (b + m) * 255, ]; } // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL static hsv2hsl(h, s, v) { h = Number.constrain(h, 0, 360); s = Number.constrain(s, 0, 100) / 100; v = Number.constrain(v, 0, 100) / 100; let l = v - v * s / 2; let saturation; if (l === 0 || l === 1) saturation = 0; else saturation = (v - l) / Math.min(l, 1 - l); return [h, saturation * 100, l * 100]; } // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB static rgb2hsv(r, g, b) { r = Number.constrain(r, 0, 1); g = Number.constrain(g, 0, 1); b = Number.constrain(b, 0, 1); let max = Math.max(r, g, b); let min = Math.min(r, g, b); let h = 0; let delta = max - min; let s = max === 0 ? 0 : delta / max; let v = max; if (max === min) h = 0; // Grayscale. else { switch (max) { case r: h = ((g - b) / delta) + ((g < b) ? 6 : 0); break; case g: h = ((b - r) / delta) + 2; break; case b: h = ((r - g) / delta) + 4; break; } h /= 6; } return [h * 360, s * 100, v * 100]; } // https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative static hsv2rgb(h, s, v) { h = Number.constrain(h, 0, 360); s = Number.constrain(s, 0, 100) / 100; v = Number.constrain(v, 0, 100) / 100; function fraction(n) { let k = (n + (h / 60)) % 6; return v - (v * s * Math.max(Math.min(k, 4 - k, 1), 0)); } return [fraction(5), fraction(3), fraction(1)]; } // https://www.w3.org/TR/css-color-4/#color-conversion-code static displayP3toSRGB(r, g, b) { r = Number.constrain(r, 0, 1); g = Number.constrain(g, 0, 1); b = Number.constrain(b, 0, 1); let linearP3 = WI.Color._toLinearLight([r, g, b]); // Convert an array of linear-light display-p3 values to CIE XYZ // using D65 (no chromatic adaptation). // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html const rgbToXYZMatrix = [ [0.4865709486482162, 0.26566769316909306, 0.1982172852343625], [0.2289745640697488, 0.6917385218365064, 0.079286914093745], [0.0000000000000000, 0.04511338185890264, 1.043944368900976], ]; let xyz = Math.multiplyMatrixByVector(rgbToXYZMatrix, linearP3); // Convert XYZ to linear-light sRGB. const xyzToLinearSRGBMatrix = [ [ 3.2404542, -1.5371385, -0.4985314], [-0.9692660, 1.8760108, 0.0415560], [ 0.0556434, -0.2040259, 1.0572252], ]; let linearSRGB = Math.multiplyMatrixByVector(xyzToLinearSRGBMatrix, xyz); let srgb = WI.Color._gammaCorrect(linearSRGB); return srgb.map((x) => x.maxDecimals(4)); } // https://www.w3.org/TR/css-color-4/#color-conversion-code static srgbToDisplayP3(r, g, b) { r = Number.constrain(r, 0, 1); g = Number.constrain(g, 0, 1); b = Number.constrain(b, 0, 1); let linearSRGB = WI.Color._toLinearLight([r, g, b]); // Convert an array of linear-light sRGB values to CIE XYZ // using sRGB's own white, D65 (no chromatic adaptation) // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html const linearSRGBtoXYZMatrix = [ [0.4124564, 0.3575761, 0.1804375], [0.2126729, 0.7151522, 0.0721750], [0.0193339, 0.1191920, 0.9503041], ]; let xyz = Math.multiplyMatrixByVector(linearSRGBtoXYZMatrix, linearSRGB); const xyzToLinearP3Matrix = [ [ 2.493496911941425, -0.9313836179191239, -0.40271078445071684], [-0.8294889695615747, 1.7626640603183463, 0.023624685841943577], [ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872], ]; let linearP3 = Math.multiplyMatrixByVector(xyzToLinearP3Matrix, xyz); let p3 = WI.Color._gammaCorrect(linearP3); return p3.map((x) => x.maxDecimals(4)); } // Convert gamma-corrected sRGB or Display-P3 to linear light form. // https://www.w3.org/TR/css-color-4/#color-conversion-code static _toLinearLight(rgb) { return rgb.map(function(value) { if (value < 0.04045) return value / 12.92; return Math.pow((value + 0.055) / 1.055, 2.4); }); } // Convert linear-light sRGB or Display-P3 to gamma corrected form. // Inverse of `toLinearLight`. // https://www.w3.org/TR/css-color-4/#color-conversion-code static _gammaCorrect(rgb) { return rgb.map(function(value) { if (value > 0.0031308) return 1.055 * Math.pow(value, 1 / 2.4) - 0.055; return 12.92 * value; }); } static cmyk2rgb(c, m, y, k) { c = Number.constrain(c, 0, 1); m = Number.constrain(m, 0, 1); y = Number.constrain(y, 0, 1); k = Number.constrain(k, 0, 1); return [ 255 * (1 - c) * (1 - k), 255 * (1 - m) * (1 - k), 255 * (1 - y) * (1 - k), ]; } static normalized2rgb(r, g, b) { return [ WI.Color._eightBitChannel(r * 255), WI.Color._eightBitChannel(g * 255), WI.Color._eightBitChannel(b * 255) ]; } static _eightBitChannel(value) { return Number.constrain(Math.round(value), 0, 255); } // Public nextFormat(format) { format = format || this.format; switch (format) { case WI.Color.Format.Original: case WI.Color.Format.HEX: case WI.Color.Format.HEXAlpha: return this.simple ? WI.Color.Format.RGB : WI.Color.Format.RGBA; case WI.Color.Format.RGB: case WI.Color.Format.RGBA: return WI.Color.Format.ColorFunction; case WI.Color.Format.ColorFunction: if (this.simple) return WI.Color.Format.HSL; return WI.Color.Format.HSLA; case WI.Color.Format.HSL: case WI.Color.Format.HSLA: if (this.isKeyword()) return WI.Color.Format.Keyword; if (this.simple) return this.canBeSerializedAsShortHEX() ? WI.Color.Format.ShortHEX : WI.Color.Format.HEX; return this.canBeSerializedAsShortHEX() ? WI.Color.Format.ShortHEXAlpha : WI.Color.Format.HEXAlpha; case WI.Color.Format.ShortHEX: return WI.Color.Format.HEX; case WI.Color.Format.ShortHEXAlpha: return WI.Color.Format.HEXAlpha; case WI.Color.Format.Keyword: if (this.simple) return this.canBeSerializedAsShortHEX() ? WI.Color.Format.ShortHEX : WI.Color.Format.HEX; return this.canBeSerializedAsShortHEX() ? WI.Color.Format.ShortHEXAlpha : WI.Color.Format.HEXAlpha; default: console.error("Unknown color format."); return null; } } get simple() { return this.alpha === 1; } get rgb() { if (!this._rgb) { if (this._hsl) this._rgb = WI.Color.hsl2rgb(...this._hsl); else if (this._normalizedRGB) this._rgb = this._normalizedRGB.map((component) => WI.Color._eightBitChannel(component * 255)); } return this._rgb; } get hsl() { if (!this._hsl) this._hsl = WI.Color.rgb2hsl(...this.rgb); return this._hsl; } get normalizedRGB() { if (!this._normalizedRGB) this._normalizedRGB = this.rgb.map((component) => component / 255); return this._normalizedRGB; } get rgba() { return [...this.rgb, this.alpha]; } get hsla() { return [...this.hsl, this.alpha]; } get normalizedRGBA() { return [...this.normalizedRGB, this.alpha]; } get gamut() { return this._gamut; } set gamut(gamut) { console.assert(gamut !== this._gamut); if (this._gamut === WI.Color.Gamut.DisplayP3 && gamut === WI.Color.Gamut.SRGB) { this._normalizedRGB = WI.Color.displayP3toSRGB(...this.normalizedRGB).map((x) => Number.constrain(x, 0, 1)); this._hsl = null; this._rgb = null; } else if (this._gamut === WI.Color.Gamut.SRGB && gamut === WI.Color.Gamut.DisplayP3) { this._normalizedRGB = WI.Color.srgbToDisplayP3(...this.normalizedRGB); this._hsl = null; this._rgb = null; // Display-P3 is only available with the color function syntax. this.format = WI.Color.Format.ColorFunction; } this._gamut = gamut; } copy() { switch (this.format) { case WI.Color.Format.RGB: case WI.Color.Format.HEX: case WI.Color.Format.ShortHEX: case WI.Color.Format.HEXAlpha: case WI.Color.Format.ShortHEXAlpha: case WI.Color.Format.Keyword: case WI.Color.Format.RGBA: return new WI.Color(this.format, this.rgba, this._gamut); case WI.Color.Format.HSL: case WI.Color.Format.HSLA: return new WI.Color(this.format, this.hsla, this._gamut); case WI.Color.Format.ColorFunction: return new WI.Color(this.format, this.normalizedRGBA, this._gamut); } console.error("Invalid color format: " + this.format); } toString(format) { if (!format) format = this.format; switch (format) { case WI.Color.Format.Original: return this._toOriginalString(); case WI.Color.Format.RGB: return this._toRGBString(); case WI.Color.Format.RGBA: return this._toRGBAString(); case WI.Color.Format.ColorFunction: return this._toFunctionString(); case WI.Color.Format.HSL: return this._toHSLString(); case WI.Color.Format.HSLA: return this._toHSLAString(); case WI.Color.Format.HEX: return this._toHEXString(); case WI.Color.Format.ShortHEX: return this._toShortHEXString(); case WI.Color.Format.HEXAlpha: return this._toHEXAlphaString(); case WI.Color.Format.ShortHEXAlpha: return this._toShortHEXAlphaString(); case WI.Color.Format.Keyword: return this._toKeywordString(); } console.error("Invalid color format: " + format); return ""; } toProtocol() { let [r, g, b, a] = this.rgba; return {r, g, b, a}; } isKeyword() { if (this.keyword) return true; if (this._gamut !== WI.Color.Gamut.SRGB) return false; if (!this.simple) return Array.shallowEqual(this.rgba, [0, 0, 0, 0]); return Object.keys(WI.Color.Keywords).some(key => Array.shallowEqual(WI.Color.Keywords[key], this.rgb)); } isOutsideSRGB() { if (this._gamut !== WI.Color.Gamut.DisplayP3) return false; let rgb = WI.Color.displayP3toSRGB(...this.normalizedRGB); // displayP3toSRGB(1, 1, 1) produces [0.9999, 1, 1.0001], which aren't pure white color values. // However, `color(sRGB 0.9999 1 1.0001)` looks exactly the same as color `color(sRGB 1 1 1)` // because sRGB is only 8bit per channel. The values get rounded. For example, // `rgb(255, 254.51, 255)` looks exactly the same as `rgb(255, 255, 255)`. // // Consider a color to be within sRGB even if it's actually outside of sRGB by less than half a bit. const epsilon = (1 / 255) / 2; return rgb.some((x) => x <= -epsilon || x >= 1 + epsilon); } canBeSerializedAsShortHEX() { let rgb = this.rgb; let r = this._componentToHexValue(rgb[0]); if (r[0] !== r[1]) return false; let g = this._componentToHexValue(rgb[1]); if (g[0] !== g[1]) return false; let b = this._componentToHexValue(rgb[2]); if (b[0] !== b[1]) return false; if (!this.simple) { let a = this._componentToHexValue(Math.round(this.alpha * 255)); if (a[0] !== a[1]) return false; } return true; } // Private _toOriginalString() { return this.original || this._toKeywordString(); } _toKeywordString() { if (this.keyword) return this.keyword; let rgba = this.rgba; if (!this.simple) { if (Array.shallowEqual(rgba, [0, 0, 0, 0])) return "transparent"; return this._toRGBAString(); } let keywords = WI.Color.Keywords; for (let keyword in keywords) { if (!keywords.hasOwnProperty(keyword)) continue; let keywordRGB = keywords[keyword]; if (keywordRGB[0] === rgba[0] && keywordRGB[1] === rgba[1] && keywordRGB[2] === rgba[2]) return keyword; } return this._toRGBString(); } _toShortHEXString() { if (!this.simple) return this._toRGBAString(); let [r, g, b] = this.rgb.map(this._componentToHexValue); if (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) return "#" + r[0] + g[0] + b[0]; return "#" + r + g + b; } _toHEXString() { if (!this.simple) return this._toRGBAString(); let [r, g, b] = this.rgb.map(this._componentToHexValue); return "#" + r + g + b; } _toShortHEXAlphaString() { let [r, g, b] = this.rgb.map(this._componentToHexValue); let a = this._componentToHexValue(Math.round(this.alpha * 255)); if (r[0] === r[1] && g[0] === g[1] && b[0] === b[1] && a[0] === a[1]) return "#" + r[0] + g[0] + b[0] + a[0]; return "#" + r + g + b + a; } _toHEXAlphaString() { let [r, g, b] = this.rgb.map(this._componentToHexValue); let a = this._componentToHexValue(Math.round(this.alpha * 255)); return "#" + r + g + b + a; } _toRGBString() { if (!this.simple) return this._toRGBAString(); let [r, g, b] = this.rgb.map(WI.Color._eightBitChannel); return `rgb(${r}, ${g}, ${b})`; } _toRGBAString() { let [r, g, b] = this.rgb.map(WI.Color._eightBitChannel); return `rgba(${r}, ${g}, ${b}, ${this.alpha})`; } _toFunctionString() { let [r, g, b] = this.normalizedRGB.map((x) => x.maxDecimals(4)); if (this.alpha === 1) return `color(${this._gamut} ${r} ${g} ${b})`; return `color(${this._gamut} ${r} ${g} ${b} / ${this.alpha})`; } _toHSLString() { if (!this.simple) return this._toHSLAString(); let [h, s, l] = this.hsl.map((x) => x.maxDecimals(2)); return `hsl(${h}, ${s}%, ${l}%)`; } _toHSLAString() { let [h, s, l] = this.hsl.map((x) => x.maxDecimals(2)); return `hsla(${h}, ${s}%, ${l}%, ${this.alpha})`; } _componentToHexValue(value) { let hex = WI.Color._eightBitChannel(value).toString(16); if (hex.length === 1) hex = "0" + hex; return hex; } }; WI.Color.Format = { Original: "color-format-original", Keyword: "color-format-keyword", HEX: "color-format-hex", ShortHEX: "color-format-short-hex", HEXAlpha: "color-format-hex-alpha", ShortHEXAlpha: "color-format-short-hex-alpha", RGB: "color-format-rgb", RGBA: "color-format-rgba", HSL: "color-format-hsl", HSLA: "color-format-hsla", ColorFunction: "color-format-color-function", }; WI.Color.Gamut = { SRGB: "srgb", DisplayP3: "display-p3", }; WI.Color.FunctionNames = new Set([ "rgb", "rgba", "hsl", "hsla", "color", ]); WI.Color.Keywords = { "aliceblue": [240, 248, 255, 1], "antiquewhite": [250, 235, 215, 1], "aqua": [0, 255, 255, 1], "aquamarine": [127, 255, 212, 1], "azure": [240, 255, 255, 1], "beige": [245, 245, 220, 1], "bisque": [255, 228, 196, 1], "black": [0, 0, 0, 1], "blanchedalmond": [255, 235, 205, 1], "blue": [0, 0, 255, 1], "blueviolet": [138, 43, 226, 1], "brown": [165, 42, 42, 1], "burlywood": [222, 184, 135, 1], "cadetblue": [95, 158, 160, 1], "chartreuse": [127, 255, 0, 1], "chocolate": [210, 105, 30, 1], "coral": [255, 127, 80, 1], "cornflowerblue": [100, 149, 237, 1], "cornsilk": [255, 248, 220, 1], "crimson": [237, 164, 61, 1], "cyan": [0, 255, 255, 1], "darkblue": [0, 0, 139, 1], "darkcyan": [0, 139, 139, 1], "darkgoldenrod": [184, 134, 11, 1], "darkgray": [169, 169, 169, 1], "darkgreen": [0, 100, 0, 1], "darkgrey": [169, 169, 169, 1], "darkkhaki": [189, 183, 107, 1], "darkmagenta": [139, 0, 139, 1], "darkolivegreen": [85, 107, 47, 1], "darkorange": [255, 140, 0, 1], "darkorchid": [153, 50, 204, 1], "darkred": [139, 0, 0, 1], "darksalmon": [233, 150, 122, 1], "darkseagreen": [143, 188, 143, 1], "darkslateblue": [72, 61, 139, 1], "darkslategray": [47, 79, 79, 1], "darkslategrey": [47, 79, 79, 1], "darkturquoise": [0, 206, 209, 1], "darkviolet": [148, 0, 211, 1], "deeppink": [255, 20, 147, 1], "deepskyblue": [0, 191, 255, 1], "dimgray": [105, 105, 105, 1], "dimgrey": [105, 105, 105, 1], "dodgerblue": [30, 144, 255, 1], "firebrick": [178, 34, 34, 1], "floralwhite": [255, 250, 240, 1], "forestgreen": [34, 139, 34, 1], "fuchsia": [255, 0, 255, 1], "gainsboro": [220, 220, 220, 1], "ghostwhite": [248, 248, 255, 1], "gold": [255, 215, 0, 1], "goldenrod": [218, 165, 32, 1], "gray": [128, 128, 128, 1], "green": [0, 128, 0, 1], "greenyellow": [173, 255, 47, 1], "grey": [128, 128, 128, 1], "honeydew": [240, 255, 240, 1], "hotpink": [255, 105, 180, 1], "indianred": [205, 92, 92, 1], "indigo": [75, 0, 130, 1], "ivory": [255, 255, 240, 1], "khaki": [240, 230, 140, 1], "lavender": [230, 230, 250, 1], "lavenderblush": [255, 240, 245, 1], "lawngreen": [124, 252, 0, 1], "lemonchiffon": [255, 250, 205, 1], "lightblue": [173, 216, 230, 1], "lightcoral": [240, 128, 128, 1], "lightcyan": [224, 255, 255, 1], "lightgoldenrodyellow": [250, 250, 210, 1], "lightgray": [211, 211, 211, 1], "lightgreen": [144, 238, 144, 1], "lightgrey": [211, 211, 211, 1], "lightpink": [255, 182, 193, 1], "lightsalmon": [255, 160, 122, 1], "lightseagreen": [32, 178, 170, 1], "lightskyblue": [135, 206, 250, 1], "lightslategray": [119, 136, 153, 1], "lightslategrey": [119, 136, 153, 1], "lightsteelblue": [176, 196, 222, 1], "lightyellow": [255, 255, 224, 1], "lime": [0, 255, 0, 1], "limegreen": [50, 205, 50, 1], "linen": [250, 240, 230, 1], "magenta": [255, 0, 255, 1], "maroon": [128, 0, 0, 1], "mediumaquamarine": [102, 205, 170, 1], "mediumblue": [0, 0, 205, 1], "mediumorchid": [186, 85, 211, 1], "mediumpurple": [147, 112, 219, 1], "mediumseagreen": [60, 179, 113, 1], "mediumslateblue": [123, 104, 238, 1], "mediumspringgreen": [0, 250, 154, 1], "mediumturquoise": [72, 209, 204, 1], "mediumvioletred": [199, 21, 133, 1], "midnightblue": [25, 25, 112, 1], "mintcream": [245, 255, 250, 1], "mistyrose": [255, 228, 225, 1], "moccasin": [255, 228, 181, 1], "navajowhite": [255, 222, 173, 1], "navy": [0, 0, 128, 1], "oldlace": [253, 245, 230, 1], "olive": [128, 128, 0, 1], "olivedrab": [107, 142, 35, 1], "orange": [255, 165, 0, 1], "orangered": [255, 69, 0, 1], "orchid": [218, 112, 214, 1], "palegoldenrod": [238, 232, 170, 1], "palegreen": [152, 251, 152, 1], "paleturquoise": [175, 238, 238, 1], "palevioletred": [219, 112, 147, 1], "papayawhip": [255, 239, 213, 1], "peachpuff": [255, 218, 185, 1], "peru": [205, 133, 63, 1], "pink": [255, 192, 203, 1], "plum": [221, 160, 221, 1], "powderblue": [176, 224, 230, 1], "purple": [128, 0, 128, 1], "rebeccapurple": [102, 51, 153, 1], "red": [255, 0, 0, 1], "rosybrown": [188, 143, 143, 1], "royalblue": [65, 105, 225, 1], "saddlebrown": [139, 69, 19, 1], "salmon": [250, 128, 114, 1], "sandybrown": [244, 164, 96, 1], "seagreen": [46, 139, 87, 1], "seashell": [255, 245, 238, 1], "sienna": [160, 82, 45, 1], "silver": [192, 192, 192, 1], "skyblue": [135, 206, 235, 1], "slateblue": [106, 90, 205, 1], "slategray": [112, 128, 144, 1], "slategrey": [112, 128, 144, 1], "snow": [255, 250, 250, 1], "springgreen": [0, 255, 127, 1], "steelblue": [70, 130, 180, 1], "tan": [210, 180, 140, 1], "teal": [0, 128, 128, 1], "thistle": [216, 191, 216, 1], "tomato": [255, 99, 71, 1], "transparent": [0, 0, 0, 0], "turquoise": [64, 224, 208, 1], "violet": [238, 130, 238, 1], "wheat": [245, 222, 179, 1], "white": [255, 255, 255, 1], "whitesmoke": [245, 245, 245, 1], "yellow": [255, 255, 0, 1], "yellowgreen": [154, 205, 50, 1], };