| /* |
| * Copyright (C) 2009, 2013 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 = /^(?:#(?<hex>[0-9a-f]{3,8})|rgba?\((?<rgb>[^)]+)\)|(?<keyword>\w+)|color\((?<color>[^)]+)\)|hsla?\((?<hsl>[^)]+)\))$/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 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], |
| }; |