From b2379ee5e5f1e4f4b318ac6023462fe218f72303 Mon Sep 17 00:00:00 2001 From: Thierry Bela Date: Tue, 27 Feb 2024 23:15:49 -0500 Subject: [PATCH] relative colors from lch() lab() oklch() and oklab() #27 --- dist/index-umd-web.js | 1639 +++++++++++--------- dist/index.cjs | 1639 +++++++++++--------- dist/lib/ast/expand.js | 2 +- dist/lib/ast/features/shorthand.js | 2 +- dist/lib/parser/declaration/list.js | 2 +- dist/lib/parser/declaration/map.js | 2 +- dist/lib/parser/declaration/set.js | 2 +- dist/lib/parser/parse.js | 2 +- dist/lib/parser/tokenize.js | 2 +- dist/lib/parser/utils/declaration.js | 2 +- dist/lib/parser/utils/syntax.js | 2 +- dist/lib/parser/utils/type.js | 2 +- dist/lib/renderer/color/color.js | 320 ++-- dist/lib/renderer/color/colormix.js | 1 + dist/lib/renderer/color/hex.js | 4 +- dist/lib/renderer/color/hsl.js | 48 +- dist/lib/renderer/color/hsv.js | 8 +- dist/lib/renderer/color/hwb.js | 69 +- dist/lib/renderer/color/lab.js | 1 - dist/lib/renderer/color/oklab.js | 2 +- dist/lib/renderer/color/relativecolor.js | 176 +-- dist/lib/renderer/color/rgb.js | 160 +- dist/lib/renderer/color/srgb.js | 236 ++- dist/lib/renderer/color/utils/constants.js | 159 +- dist/lib/renderer/color/xyz.js | 2 +- dist/lib/renderer/render.js | 17 +- dist/node/index.js | 2 +- dist/web/index.js | 2 +- src/@types/token.d.ts | 2 +- src/lib/renderer/color/color.ts | 381 +++-- src/lib/renderer/color/hex.ts | 5 +- src/lib/renderer/color/hsl.ts | 51 +- src/lib/renderer/color/hsv.ts | 12 +- src/lib/renderer/color/hwb.ts | 84 +- src/lib/renderer/color/index.ts | 4 +- src/lib/renderer/color/relativecolor.ts | 249 +-- src/lib/renderer/color/rgb.ts | 222 +-- src/lib/renderer/color/srgb.ts | 272 +++- src/lib/renderer/color/utils/constants.ts | 161 ++ src/lib/renderer/render.ts | 9 +- test/specs/code/color.js | 107 ++ 41 files changed, 3396 insertions(+), 2668 deletions(-) diff --git a/dist/index-umd-web.js b/dist/index-umd-web.js index e041e17..cab59db 100644 --- a/dist/index-umd-web.js +++ b/dist/index-umd-web.js @@ -165,12 +165,247 @@ } const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; + // name to color + const COLORS_NAMES = Object.seal({ + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', + 'rebeccapurple': '#663399', + 'transparent': '#00000000' + }); + // color to name + const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { + acc[value] = key; + return acc; + }, Object.create(null))); function getComponents(token) { return token.chi .filter((t) => ![exports.EnumToken.LiteralTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType].includes(t.typ)); } + function toHexString(acc, value) { + return acc + value.toString(16).padStart(2, '0'); + } + function reduceHexValue(value) { + const named_color = NAMES_COLORS[expandHexValue(value)]; + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; + } + function expandHexValue(value) { + if (value.length == 4) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; + } + if (value.length == 5) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; + } + return value; + } + function rgb2hex(token) { + let value = '#'; + let t; + // @ts-ignore + for (let i = 0; i < 3; i++) { + // @ts-ignore + t = token.chi[i]; + // @ts-ignore + value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + // @ts-ignore + if (token.chi.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || + (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || + (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { + // @ts-ignore + value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); + } + } + return value; + } + function hsl2hex(token) { + return `${hsl2rgb(token).reduce(toHexString, '#')}`; + } + function hwb2hex(token) { + // console.error(hwb2rgb(token)); + return `${hwb2rgb(token).reduce(toHexString, '#')}`; + } + function cmyk2hex(token) { + return `#${cmyk2rgb(token).reduce(toHexString, '')}`; + } + function oklab2hex(token) { + return `${oklab2rgb(token).reduce(toHexString, '#')}`; + } + function oklch2hex(token) { + return `${oklch2rgb(token).reduce(toHexString, '#')}`; + } + function lab2hex(token) { + return `${lab2rgb(token).reduce(toHexString, '#')}`; + } + function lch2hex(token) { + return `${lch2rgb(token).reduce(toHexString, '#')}`; + } + function XYZ_to_sRGB(x, y, z) { // @ts-ignore return gam_sRGB( @@ -217,77 +452,6 @@ return xyz.map((value, i) => value * D50[i]); } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code - // srgb-linear -> srgb - // 0 <= r, g, b <= 1 - function gam_sRGB(r, g, b) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - return [r, g, b].map((val) => { - let abs = Math.abs(val); - if (Math.abs(val) > 0.0031308) { - return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); - } - return 12.92 * val; - }); - } - // export function gam_a98rgb(r: number, g: number, b: number): number[] { - // // convert an array of linear-light a98-rgb in the range 0.0-1.0 - // // to gamma corrected form - // // negative values are also now accepted - // return [r, g, b].map(function (val: number): number { - // let sign: number = val < 0? -1 : 1; - // let abs: number = Math.abs(val); - // - // return roundWithPrecision(sign * Math.pow(abs, 256/563), val); - // }); - // } - function lin_ProPhoto(r, g, b) { - // convert an array of prophoto-rgb values - // where in-gamut colors are in the range [0.0 - 1.0] - // to linear light (un-companded) form. - // Transfer curve is gamma 1.8 with a small linear portion - // Extended transfer function - const Et2 = 16 / 512; - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs <= Et2) { - return roundWithPrecision(val / 16); - } - return roundWithPrecision(sign * Math.pow(abs, 1.8)); - }); - } - function lin_a98rgb(r, g, b) { - // convert an array of a98-rgb values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // negative values are also now accepted - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); - }); - } - function lin_2020(r, g, b) { - // convert an array of rec2020 RGB values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // ITU-R BT.2020-2 p.4 - const α = 1.09929682680944; - const β = 0.018053968510807; - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs < β * 4.5) { - return roundWithPrecision(val / 4.5); - } - return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); - }); - } - // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_sRGB(l, a, b) { // console.error({l, a, b}); @@ -318,100 +482,22 @@ 1.7076147009309444 * S); } - function toHexString(acc, value) { - return acc + value.toString(16).padStart(2, '0'); - } - function reduceHexValue(value) { - const named_color = NAMES_COLORS[expandHexValue(value)]; - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; - } - function expandHexValue(value) { - if (value.length == 4) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; - } - if (value.length == 5) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; - } - return value; - } - function rgb2hex(token) { - let value = '#'; - let t; - // @ts-ignore - for (let i = 0; i < 3; i++) { - // @ts-ignore - t = token.chi[i]; - // @ts-ignore - value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); - } - // @ts-ignore - if (token.chi.length == 4) { - // @ts-ignore - t = token.chi[3]; - // @ts-ignore - if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || - (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || - (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { - // @ts-ignore - value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); - } - } - return value; - } - function hsl2hex(token) { - return `${hsl2rgb(token).reduce(toHexString, '#')}`; - } - function hwb2hex(token) { - return `${hwb2rgb(token).reduce(toHexString, '#')}`; - } - function cmyk2hex(token) { - return `#${cmyk2rgb(token).reduce(toHexString, '')}`; - } - function oklab2hex(token) { - return `${oklab2rgb(token).reduce(toHexString, '#')}`; - } - function oklch2hex(token) { - return `${oklch2rgb(token).reduce(toHexString, '#')}`; - } - function lab2hex(token) { - return `${lab2rgb(token).reduce(toHexString, '#')}`; - } - function lch2hex(token) { - return `${lch2rgb(token).reduce(toHexString, '#')}`; - } - - function hwb2rgb(token) { - const { h: hue, s: white, l: black, a: alpha } = hslvalues(token); - const rgb = hsl2rgbvalues(hue, 1, .5); + // from https://www.w3.org/TR/css-color-4/#color-conversion-code + // srgb-linear -> srgb + // 0 <= r, g, b <= 1 + function hwb2srgb(token) { + const { h: hue, s: white, l: black, a: alpha } = hslvalues$1(token); + const rgb = hsl2srgbvalues(hue, 1, .5); for (let i = 0; i < 3; i++) { rgb[i] *= (1 - white - black); - rgb[i] = Math.round(rgb[i] + white); + rgb[i] = rgb[i] + white; } if (alpha != null && alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } return rgb; } - function hsl2rgb(token) { - let { h, s, l, a } = hslvalues(token); - return hsl2rgbvalues(h, s, l, a); - } - function cmyk2rgb(token) { + function cmyk2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -430,20 +516,20 @@ // @ts-ignore const k = getNumber(t); const rgb = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) + 1 - Math.min(1, c * (1 - k) + k), + 1 - Math.min(1, m * (1 - k) + k), + 1 - Math.min(1, y * (1 - k) + k) ]; // @ts-ignore if (token.chi.length >= 9) { // @ts-ignore t = token.chi[8]; // @ts-ignore - rgb.push(Math.round(255 * getNumber(t))); + rgb.push(getNumber(t)); } return rgb; } - function oklab2rgb(token) { + function oklab2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -461,15 +547,13 @@ t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); - const rgb = OKLab_to_sRGB(l, a, b).map(v => { - return Math.round(255 * v); - }); - if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + const rgb = OKLab_to_sRGB(l, a, b); + if (alpha != 1 && alpha != null) { + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb; //.map(((value: number) => minmax(value, 0, 255))); } - function oklch2rgb(token) { + function oklch2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -492,62 +576,9 @@ if (alpha != 1) { rgb.push(alpha); } - return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); - } - function lab2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); - } - function lch2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const h = getAngle(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const a = c * Math.cos(360 * h * Math.PI / 180); - const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(value * 255, 0, 255))); + return rgb; //.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); } - function hslvalues(token) { + function hslvalues$1(token) { const components = getComponents(token); let t; // @ts-ignore @@ -574,7 +605,7 @@ } return a == null ? { h, s, l } : { h, s, l, a }; } - function hsl2rgbvalues(h, s, l, a = null) { + function hsl2srgbvalues(h, s, l, a = null) { let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; let r = l; let g = l; @@ -621,205 +652,630 @@ break; } } - const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + const values = [r, g, b]; if (a != null && a != 1) { - values.push(Math.round(a * 255)); + values.push(a); } return values; } - - // name to color - const COLORS_NAMES = Object.seal({ - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgrey': '#a9a9a9', - 'darkgreen': '#006400', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgrey': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370d8', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#d87093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32', - 'rebeccapurple': '#663399', - 'transparent': '#00000000' - }); - // color to name - const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { - acc[value] = key; - return acc; - }, Object.create(null))); + function lab2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; + } + function lch2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const a = c * Math.cos(360 * h * Math.PI / 180); + const b = c * Math.sin(360 * h * Math.PI / 180); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; + } + function gam_sRGB(r, g, b) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + return [r, g, b].map((val) => { + let abs = Math.abs(val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); + } + return 12.92 * val; + }); + } + // export function gam_a98rgb(r: number, g: number, b: number): number[] { + // // convert an array of linear-light a98-rgb in the range 0.0-1.0 + // // to gamma corrected form + // // negative values are also now accepted + // return [r, g, b].map(function (val: number): number { + // let sign: number = val < 0? -1 : 1; + // let abs: number = Math.abs(val); + // + // return roundWithPrecision(sign * Math.pow(abs, 256/563), val); + // }); + // } + function lin_ProPhoto(r, g, b) { + // convert an array of prophoto-rgb values + // where in-gamut colors are in the range [0.0 - 1.0] + // to linear light (un-companded) form. + // Transfer curve is gamma 1.8 with a small linear portion + // Extended transfer function + const Et2 = 16 / 512; + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + if (abs <= Et2) { + return roundWithPrecision(val / 16); + } + return roundWithPrecision(sign * Math.pow(abs, 1.8)); + }); + } + function lin_a98rgb(r, g, b) { + // convert an array of a98-rgb values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // negative values are also now accepted + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); + }); + } + function lin_2020(r, g, b) { + // convert an array of rec2020 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // ITU-R BT.2020-2 p.4 + const α = 1.09929682680944; + const β = 0.018053968510807; + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + if (abs < β * 4.5) { + return roundWithPrecision(val / 4.5); + } + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); + }); + } + + function srgb2rgb(value) { + return minmax(Math.round(value * 255), 0, 255); + } + function hex2rgb(token) { + const value = expandHexValue(token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb = []; + for (let i = 1; i < value.length; i += 2) { + rgb.push(parseInt(value.slice(i, i + 2), 16)); + } + return rgb; + } + function hwb2rgb(token) { + return hwb2srgb(token).map(srgb2rgb); + } + function hsl2rgb(token) { + let { h, s, l, a } = hslvalues(token); + return hsl2rgbvalues(h, s, l, a); + } + function cmyk2rgb(token) { + return cmyk2srgb(token).map(srgb2rgb); + } + function oklab2rgb(token) { + return oklab2srgb(token).map(srgb2rgb); + } + function oklch2rgb(token) { + return oklch2srgb(token).map(srgb2rgb); + } + function lab2rgb(token) { + return lab2srgb(token).map(srgb2rgb); + } + function lch2rgb(token) { + return lch2srgb(token).map(srgb2rgb); + } + function hslvalues(token) { + const components = getComponents(token); + let t; + // @ts-ignore + let h = getAngle(components[0]); + // @ts-ignore + t = components[1]; + // @ts-ignore + let s = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + let l = getNumber(t); + let a = null; + if (token.chi?.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100) || + // @ts-ignore + (t.typ == exports.EnumToken.NumberTokenType && t.val < 1)) { + // @ts-ignore + a = getNumber(t); + } + } + return a == null ? { h, s, l } : { h, s, l, a }; + } + function hsl2rgbvalues(h, s, l, a = null) { + let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + if (a != null && a != 1) { + values.push(Math.round(a * 255)); + } + return values; + } + + function hwb2hsv(h, w, b, a) { + // @ts-ignore + return [h, 1 - w / (1 - b), 1 - b, a]; + } + // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js + function hsl2hsv(h, s, l, a = null) { + s *= l < .5 ? l : 1 - l; + const result = [ + //Range should be between 0 - 1 + h, //Hue stays the same + 2 * s / (l + s), //Saturation + l + s //Value + ]; + if (a != null) { + result.push(a); + } + return result; + } + + function eq(a, b) { + if (a == null || b == null) { + return a == b; + } + if (typeof a != 'object' || typeof b != 'object') { + return a === b; + } + if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { + return false; + } + if (Array.isArray(a)) { + if (a.length != b.length) { + return false; + } + let i = 0; + for (; i < a.length; i++) { + if (!eq(a[i], b[i])) { + return false; + } + } + return true; + } + const k1 = Object.keys(a); + const k2 = Object.keys(b); + if (k1.length != k2.length) { + return false; + } + let key; + for (key of k1) { + if (!(key in b) || !eq(a[key], b[key])) { + return false; + } + } + return true; + } + + function hex2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...hex2rgb(token)); + } + function rgb2hsl(token) { + const chi = getComponents(token); + // @ts-ignore + let t = chi[0]; + // @ts-ignore + let r = getNumber(t); + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g = getNumber(t); + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b = getNumber(t); + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a = null; + if (t != null && !eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + // @ts-ignore + a = getNumber(t) / 255; + } + const values = [r, g, b]; + if (a != null && a != 1) { + values.push(a); + } + // @ts-ignore + return rgb2hslvalues(...values); + } + // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js + function hsv2hsl(h, s, v, a) { + const result = [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + h / 2, //Lightness is (2-sat)*val/2 + ]; + if (a != null) { + result.push(a); + } + return result; + } + function hwb2hsl(token) { + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); + } + function lab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lab2rgb(token)); + } + function lch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lch2rgb(token)); + } + function oklab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklab2rgb(token)); + } + function oklch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklch2rgb(token)); + } + function rgb2hslvalues(r, g, b, a = null) { + r /= 255; + g /= 255; + b /= 255; + let max = Math.max(r, g, b); + let min = Math.min(r, g, b); + let h = 0; + let s = 0; + let l = (max + min) / 2; + if (max != min) { + let d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + const hsl = [h, s, l]; + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]); + } + // @ts-ignore + return hsl; + } + + function rgb2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + return getNumber(t) / 255; + })); + } + function hsl2hwb(token) { + // @ts-ignore + return hsl2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + if (index == 0) { + return getAngle(t); + } + return getNumber(t); + })); + } + function lab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lab2srgb(token)); + } + function lch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lch2srgb(token)); + } + function oklab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklab2srgb(token)); + } + function oklch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklch2srgb(token)); + } + function rgb2hue(r, g, b, fallback = 0) { + let value = rgb2value(r, g, b); + let whiteness = rgb2whiteness(r, g, b); + let delta = value - whiteness; + if (delta > 0) { + // calculate segment + let segment = value === r ? (g - b) / delta : (value === g + ? (b - r) / delta + : (r - g) / delta); + // calculate shift + let shift = value === r ? segment < 0 + ? 360 / 60 + : 0 / 60 : (value === g + ? 120 / 60 + : 240 / 60); + // calculate hue + return (segment + shift) * 60; + } + return fallback; + } + function rgb2value(r, g, b) { + return Math.max(r, g, b); + } + function rgb2whiteness(r, g, b) { + return Math.min(r, g, b); + } + function srgb2hwbvalues(r, g, b, a = null, fallback = 0) { + r *= 100; + g *= 100; + b *= 100; + let hue = rgb2hue(r, g, b, fallback); + let whiteness = rgb2whiteness(r, g, b); + let value = Math.round(rgb2value(r, g, b)); + let blackness = 100 - value; + const result = [hue / 360, whiteness / 100, blackness / 100]; + if (a != null) { + result.push(a); + } + return result; + } + function hsv2hwb(h, s, v, a = null) { + const result = [h, (1 - s) * v, 1 - v]; + if (a != null) { + result.push(a); + } + return result; + } + function hsl2hwbvalues(h, s, l, a = null) { + // @ts-ignore + return hsv2hwb(...hsl2hsv(h, s, l, a)); + } + function convert(token, to) { - if (to == 'rgb') { + if (token.kin == to) { + return token; + } + // console.error({token, to}); + let values = []; + let chi; + if (to == 'hsl') { + switch (token.kin) { + case 'rgb': + case 'rgba': + values.push(...rgb2hsl(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; + case 'hwb': + values.push(...hwb2hsl(token)); + break; + case 'oklab': + values.push(...oklab2hsl(token)); + break; + case 'oklch': + values.push(...oklch2hsl(token)); + break; + case 'lab': + values.push(...lab2hsl(token)); + break; + case 'lch': + values.push(...lch2hsl(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'hwb') { switch (token.kin) { case 'rgb': case 'rgba': - return token; + values.push(...rgb2hwb(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; case 'hsl': case 'hsla': - const children = token.chi.filter(c => [exports.EnumToken.PercentageTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.IdenTokenType].includes(c.typ)); - let values = children.slice(0, 3).map((c) => getNumber(c)); - if (children.length == 4) { - values.push(children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 1 : getNumber(children[3])); - } - return { - typ: exports.EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - // @ts-ignore - chi: hsl2rgb(...values).map((v) => ({ - typ: exports.EnumToken.NumberTokenType, - val: String(v) - })) - }; + values.push(...hsl2hwb(token)); + break; + case 'oklab': + values.push(...oklab2hwb(token)); + break; + case 'oklch': + values.push(...oklch2hwb(token)); + break; + case 'lab': + values.push(...lab2hwb(token)); + break; + case 'lch': + values.push(...lch2hwb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'rgb') { + switch (token.kin) { case 'hex': case 'lit': - const value = token.kin == 'hex' ? expandHexValue(token.val) : COLORS_NAMES[token.val]; - return { - typ: exports.EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - chi: value.slice(1).match(/([a-fA-F0-9]{2})/g).map((v) => ({ - typ: exports.EnumToken.NumberTokenType, - val: String(parseInt(v, 16)) - })) - }; + values.push(...hex2rgb(token)); + break; + case 'hsl': + values.push(...hsl2rgb(token)); + break; + case 'hwb': + values.push(...hwb2rgb(token)); + break; + case 'oklab': + values.push(...oklab2rgb(token)); + break; + case 'oklch': + values.push(...oklch2rgb(token)); + break; + case 'lab': + values.push(...lab2rgb(token)); + break; + case 'lch': + values.push(...lch2rgb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.NumberTokenType, val: String(values[0]) }, + { typ: exports.EnumToken.NumberTokenType, val: String(values[1]) }, + { typ: exports.EnumToken.NumberTokenType, val: String(values[2]) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'rgb', + chi, + kin: 'rgb' + }; } } return null; @@ -857,7 +1313,7 @@ function clampValues(values, colorSpace) { switch (colorSpace) { case 'srgb': - case 'oklab': + // case 'oklab': case 'display-p3': case 'srgb-linear': // case 'prophoto-rgb': @@ -902,147 +1358,6 @@ return token.val / 360; } - function hwb2hsv(h, w, b, a) { - // @ts-ignore - return [h, 1 - w / (1 - b), 1 - b, a]; - } - // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js - function hsl2hsv(h, s, l) { - s *= l < .5 ? l : 1 - l; - return [ - //Range should be between 0 - 1 - h, //Hue stays the same - 2 * s / (l + s), //Saturation - l + s //Value - ]; - } - - function rgb2hue(r, g, b, fallback = 0) { - let value = rgb2value(r, g, b); - let whiteness = rgb2whiteness(r, g, b); - let delta = value - whiteness; - if (delta > 0) { - // calculate segment - let segment = value === r ? (g - b) / delta : (value === g - ? (b - r) / delta - : (r - g) / delta); - // calculate shift - let shift = value === r ? segment < 0 - ? 360 / 60 - : 0 / 60 : (value === g - ? 120 / 60 - : 240 / 60); - // calculate hue - return (segment + shift) * 60; - } - return fallback; - } - function rgb2value(r, g, b) { - return Math.max(r, g, b); - } - function rgb2whiteness(r, g, b) { - return Math.min(r, g, b); - } - function rgb2hwb(r, g, b, a = null, fallback = 0) { - r *= 100 / 255; - g *= 100 / 255; - b *= 100 / 255; - let hue = rgb2hue(r, g, b, fallback); - let whiteness = rgb2whiteness(r, g, b); - let value = Math.round(rgb2value(r, g, b)); - let blackness = 100 - value; - const result = [hue / 360, whiteness / 100, blackness / 100]; - if (a != null) { - result.push(a); - } - return result; - } - function hsv2hwb(h, s, v) { - return [h, (1 - s) * v, 1 - v]; - } - function hsl2hwb(h, s, l) { - return hsv2hwb(...hsl2hsv(h, s, l)); - } - - function rgb2hsl(token) { - const chi = getComponents(token); - // @ts-ignore - let t = chi[0]; - // @ts-ignore - let r = getNumber(t); - // @ts-ignore - t = chi[1]; - // @ts-ignore - let g = getNumber(t); - // @ts-ignore - t = chi[2]; - // @ts-ignore - let b = getNumber(t); - // @ts-ignore - t = chi[3]; - // @ts-ignore - let a = null; - if (t != null) { - // @ts-ignore - a = getNumber(t) / 255; - } - return rgb2hslvalues(r, g, b, a); - } - // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js - function hsv2hsl(h, s, v, a) { - return [ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), - h / 2, //Lightness is (2-sat)*val/2 - //See reassignment of hue above, - // @ts-ignore - a - ]; - } - function hwb2hsl(token) { - // @ts-ignore - return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); - } - function rgb2hslvalues(r, g, b, a = null) { - r /= 255; - g /= 255; - b /= 255; - let max = Math.max(r, g, b); - let min = Math.min(r, g, b); - let h = 0; - let s = 0; - let l = (max + min) / 2; - if (max != min) { - let d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - const hsl = [h, s, l]; - if (a != null && a < 1) { - // @ts-ignore - return hsl.concat([a]); - } - // @ts-ignore - return hsl; - } - function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { if (!isRectangularOrthogonalColorspace(colorSpace)) { return null; @@ -1409,168 +1724,37 @@ } function parseRelativeColor(relativeKeys, original, rExp, gExp, bExp, aExp) { - const type = relativeKeys.join(''); let r; let g; let b; let alpha = null; let keys = {}; let values = {}; - let children; - switch (original.kin) { - case 'lit': - case 'hex': - let value = original.val.toLowerCase(); - if (original.kin == 'lit') { - if (original.val.toLowerCase() in COLORS_NAMES) { - value = COLORS_NAMES[original.val.toLowerCase()]; - } - else { - return null; - } - } - if (value.length == 4) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]; - } - else if (value.length == 5) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3] + value[4] + value[4]; - } - r = parseInt(value.slice(1, 3), 16); - g = parseInt(value.slice(3, 5), 16); - b = parseInt(value.slice(5, 7), 16); - alpha = value.length == 9 ? parseInt(value.slice(7, 9), 16) : null; - break; - case 'rgb': - case 'rgba': - children = original.chi.filter((t) => t.typ == exports.EnumToken.NumberTokenType || t.typ == exports.EnumToken.IdenTokenType || t.typ == exports.EnumToken.PercentageTokenType); - if (children.every((t) => (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || t.typ == exports.EnumToken.NumberTokenType)) { - r = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : +children[0].val; - g = children[1].typ == exports.EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : +children[1].val; - b = children[2].typ == exports.EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : +children[2].val; - alpha = children.length < 4 ? null : children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val; - } - else if (children.every((t) => t.typ == exports.EnumToken.PercentageTokenType || (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || (t.typ == exports.EnumToken.NumberTokenType && t.val == '0'))) { - // @ts-ignore - r = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : children[0].val * 255 / 100; - // @ts-ignore - g = children[1].typ == exports.EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : children[1].val * 255 / 100; - // @ts-ignore - b = children[2].typ == exports.EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : children[2].val * 255 / 100; - alpha = children.length < 4 ? null : children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val / 100; - } - else { - return null; - } - break; - case 'hsl': - case 'hsla': - case 'hwb': - children = original.chi.filter((t) => t.typ == exports.EnumToken.AngleTokenType || t.typ == exports.EnumToken.NumberTokenType || t.typ == exports.EnumToken.IdenTokenType || t.typ == exports.EnumToken.PercentageTokenType); - if (children.length == 3 || children.length == 4) { - [r, g, b, alpha] = children; - } - else { - return null; - } - break; - default: - return null; - } - const from = ['rgb', 'rgba', 'hex', 'lit'].includes(original.kin) ? 'rgb' : original.kin; - if (from != type) { - if (type == 'hsl' || type == 'hwb') { - if (from == 'rgb') { - [r, g, b] = (type == 'hwb' ? rgb2hwb : rgb2hsl)(r, g, b); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - else if (from == 'hwb' || from == 'hsl') { - if (type == 'hsl') { - if (from == 'hwb') { - [r, g, b] = hwb2hsl(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - } - else if (type == 'hwb') { - if (from == 'hsl') { - [r, g, b] = hsl2hwb(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - } - } - else { - return null; - } - } - else if (type == 'rgb') { - if (from == 'hsl' || from == 'hwb') { - [r, g, b] = (from == 'hwb' ? hwb2rgb : hsl2rgb)(original); - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.NumberTokenType, val: r }, - [relativeKeys[1]]: { typ: exports.EnumToken.NumberTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.NumberTokenType, val: b } - }; - } - else { - return null; - } - } - } - else { - values = { - [relativeKeys[0]]: r, - [relativeKeys[1]]: g, - [relativeKeys[2]]: b - }; - } - if (aExp != null && aExp.typ == exports.EnumToken.IdenTokenType && aExp.val == 'none') { - aExp = null; + const converted = convert(original, relativeKeys); + if (converted == null) { + return null; } + [r, g, b, alpha] = converted.chi; + values = { + [relativeKeys[0]]: r, + [relativeKeys[1]]: g, + [relativeKeys[2]]: b, + // @ts-ignore + alpha: alpha == null || eq(alpha, { + typ: exports.EnumToken.IdenTokenType, + val: 'none' + }) ? { typ: exports.EnumToken.NumberTokenType, val: '1' } : alpha + }; keys = { [relativeKeys[0]]: rExp, [relativeKeys[1]]: gExp, [relativeKeys[2]]: bExp, - alpha: aExp ?? { typ: exports.EnumToken.IdenTokenType, val: 'alpha' } + // @ts-ignore + alpha: aExp == null || eq(aExp, { typ: exports.EnumToken.IdenTokenType, val: 'none' }) ? { + typ: exports.EnumToken.NumberTokenType, + val: '1' + } : aExp }; - for (const [key, value] of Object.entries(values)) { - if (typeof value == 'number') { - values[key] = { typ: exports.EnumToken.NumberTokenType, val: reduceNumber(value) }; - } - } - // @ts-ignore - values.alpha = alpha != null && typeof alpha == 'object' ? alpha : b.typ == exports.EnumToken.PercentageTokenType ? { typ: exports.EnumToken.PercentageTokenType, val: String(alpha ?? 100) } : { typ: exports.EnumToken.NumberTokenType, val: String(alpha ?? 1) }; return computeComponentValue(keys, values); } function computeComponentValue(expr, values) { @@ -2058,11 +2242,9 @@ return reduceHexValue(value); } } - else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb'].includes(token.val)) { - const chi = token.chi.filter((x) => ![ - exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType - ].includes(x.typ)); - const components = parseRelativeColor(token.val.split(''), chi[1], chi[2], chi[3], chi[4], chi[5]); + else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'].includes(token.val)) { + const chi = getComponents(token); + const components = parseRelativeColor(token.val, chi[1], chi[2], chi[3], chi[4], chi[5]); if (components != null) { token.chi = Object.values(components); delete token.cal; @@ -2111,6 +2293,7 @@ return 'currentcolor'; } clamp(token); + // console.error({token}); if (Array.isArray(token.chi) && token.chi.some((t) => t.typ == exports.EnumToken.FunctionTokenType || (t.typ == exports.EnumToken.ColorTokenType && Array.isArray(t.chi)))) { return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc, curr) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == exports.EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')'; } @@ -5796,42 +5979,6 @@ return tokens; } - function eq(a, b) { - if (a == null || b == null) { - return a == b; - } - if (typeof a != 'object' || typeof b != 'object') { - return a === b; - } - if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { - return false; - } - if (Array.isArray(a)) { - if (a.length != b.length) { - return false; - } - let i = 0; - for (; i < a.length; i++) { - if (!eq(a[i], b[i])) { - return false; - } - } - return true; - } - const k1 = Object.keys(a); - const k2 = Object.keys(b); - if (k1.length != k2.length) { - return false; - } - let key; - for (key of k1) { - if (!(key in b) || !eq(a[key], b[key])) { - return false; - } - } - return true; - } - function* walk(node, filter) { const parents = [node]; const root = node; diff --git a/dist/index.cjs b/dist/index.cjs index e07dc2a..b6d05d5 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -163,12 +163,247 @@ function roundWithPrecision(value, original) { } const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; +// name to color +const COLORS_NAMES = Object.seal({ + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', + 'rebeccapurple': '#663399', + 'transparent': '#00000000' +}); +// color to name +const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { + acc[value] = key; + return acc; +}, Object.create(null))); function getComponents(token) { return token.chi .filter((t) => ![exports.EnumToken.LiteralTokenType, exports.EnumToken.CommentTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType].includes(t.typ)); } +function toHexString(acc, value) { + return acc + value.toString(16).padStart(2, '0'); +} +function reduceHexValue(value) { + const named_color = NAMES_COLORS[expandHexValue(value)]; + if (value.length == 7) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6]) { + value = `#${value[1]}${value[3]}${value[5]}`; + } + } + else if (value.length == 9) { + if (value[1] == value[2] && + value[3] == value[4] && + value[5] == value[6] && + value[7] == value[8]) { + value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; + } + } + return named_color != null && named_color.length <= value.length ? named_color : value; +} +function expandHexValue(value) { + if (value.length == 4) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; + } + if (value.length == 5) { + return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; + } + return value; +} +function rgb2hex(token) { + let value = '#'; + let t; + // @ts-ignore + for (let i = 0; i < 3; i++) { + // @ts-ignore + t = token.chi[i]; + // @ts-ignore + value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); + } + // @ts-ignore + if (token.chi.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || + (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || + (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { + // @ts-ignore + value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); + } + } + return value; +} +function hsl2hex(token) { + return `${hsl2rgb(token).reduce(toHexString, '#')}`; +} +function hwb2hex(token) { + // console.error(hwb2rgb(token)); + return `${hwb2rgb(token).reduce(toHexString, '#')}`; +} +function cmyk2hex(token) { + return `#${cmyk2rgb(token).reduce(toHexString, '')}`; +} +function oklab2hex(token) { + return `${oklab2rgb(token).reduce(toHexString, '#')}`; +} +function oklch2hex(token) { + return `${oklch2rgb(token).reduce(toHexString, '#')}`; +} +function lab2hex(token) { + return `${lab2rgb(token).reduce(toHexString, '#')}`; +} +function lch2hex(token) { + return `${lch2rgb(token).reduce(toHexString, '#')}`; +} + function XYZ_to_sRGB(x, y, z) { // @ts-ignore return gam_sRGB( @@ -215,77 +450,6 @@ function Lab_to_XYZ(l, a, b) { return xyz.map((value, i) => value * D50[i]); } -// from https://www.w3.org/TR/css-color-4/#color-conversion-code -// srgb-linear -> srgb -// 0 <= r, g, b <= 1 -function gam_sRGB(r, g, b) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - return [r, g, b].map((val) => { - let abs = Math.abs(val); - if (Math.abs(val) > 0.0031308) { - return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); - } - return 12.92 * val; - }); -} -// export function gam_a98rgb(r: number, g: number, b: number): number[] { -// // convert an array of linear-light a98-rgb in the range 0.0-1.0 -// // to gamma corrected form -// // negative values are also now accepted -// return [r, g, b].map(function (val: number): number { -// let sign: number = val < 0? -1 : 1; -// let abs: number = Math.abs(val); -// -// return roundWithPrecision(sign * Math.pow(abs, 256/563), val); -// }); -// } -function lin_ProPhoto(r, g, b) { - // convert an array of prophoto-rgb values - // where in-gamut colors are in the range [0.0 - 1.0] - // to linear light (un-companded) form. - // Transfer curve is gamma 1.8 with a small linear portion - // Extended transfer function - const Et2 = 16 / 512; - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs <= Et2) { - return roundWithPrecision(val / 16); - } - return roundWithPrecision(sign * Math.pow(abs, 1.8)); - }); -} -function lin_a98rgb(r, g, b) { - // convert an array of a98-rgb values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // negative values are also now accepted - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); - }); -} -function lin_2020(r, g, b) { - // convert an array of rec2020 RGB values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // ITU-R BT.2020-2 p.4 - const α = 1.09929682680944; - const β = 0.018053968510807; - return [r, g, b].map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - if (abs < β * 4.5) { - return roundWithPrecision(val / 4.5); - } - return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); - }); -} - // from https://www.w3.org/TR/css-color-4/#color-conversion-code function OKLab_to_sRGB(l, a, b) { // console.error({l, a, b}); @@ -316,100 +480,22 @@ function OKLab_to_sRGB(l, a, b) { 1.7076147009309444 * S); } -function toHexString(acc, value) { - return acc + value.toString(16).padStart(2, '0'); -} -function reduceHexValue(value) { - const named_color = NAMES_COLORS[expandHexValue(value)]; - if (value.length == 7) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6]) { - value = `#${value[1]}${value[3]}${value[5]}`; - } - } - else if (value.length == 9) { - if (value[1] == value[2] && - value[3] == value[4] && - value[5] == value[6] && - value[7] == value[8]) { - value = `#${value[1]}${value[3]}${value[5]}${value[7]}`; - } - } - return named_color != null && named_color.length <= value.length ? named_color : value; -} -function expandHexValue(value) { - if (value.length == 4) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}`; - } - if (value.length == 5) { - return `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}${value[4]}${value[4]}`; - } - return value; -} -function rgb2hex(token) { - let value = '#'; - let t; - // @ts-ignore - for (let i = 0; i < 3; i++) { - // @ts-ignore - t = token.chi[i]; - // @ts-ignore - value += (t.val == 'none' ? '0' : Math.round(t.typ == exports.EnumToken.PercentageTokenType ? 255 * t.val / 100 : t.val)).toString(16).padStart(2, '0'); - } - // @ts-ignore - if (token.chi.length == 4) { - // @ts-ignore - t = token.chi[3]; - // @ts-ignore - if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || - (t.typ == exports.EnumToken.NumberTokenType && +t.val < 1) || - (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100)) { - // @ts-ignore - value += Math.round(255 * getNumber(t)).toString(16).padStart(2, '0'); - } - } - return value; -} -function hsl2hex(token) { - return `${hsl2rgb(token).reduce(toHexString, '#')}`; -} -function hwb2hex(token) { - return `${hwb2rgb(token).reduce(toHexString, '#')}`; -} -function cmyk2hex(token) { - return `#${cmyk2rgb(token).reduce(toHexString, '')}`; -} -function oklab2hex(token) { - return `${oklab2rgb(token).reduce(toHexString, '#')}`; -} -function oklch2hex(token) { - return `${oklch2rgb(token).reduce(toHexString, '#')}`; -} -function lab2hex(token) { - return `${lab2rgb(token).reduce(toHexString, '#')}`; -} -function lch2hex(token) { - return `${lch2rgb(token).reduce(toHexString, '#')}`; -} - -function hwb2rgb(token) { - const { h: hue, s: white, l: black, a: alpha } = hslvalues(token); - const rgb = hsl2rgbvalues(hue, 1, .5); +// from https://www.w3.org/TR/css-color-4/#color-conversion-code +// srgb-linear -> srgb +// 0 <= r, g, b <= 1 +function hwb2srgb(token) { + const { h: hue, s: white, l: black, a: alpha } = hslvalues$1(token); + const rgb = hsl2srgbvalues(hue, 1, .5); for (let i = 0; i < 3; i++) { rgb[i] *= (1 - white - black); - rgb[i] = Math.round(rgb[i] + white); + rgb[i] = rgb[i] + white; } if (alpha != null && alpha != 1) { - rgb.push(Math.round(255 * alpha)); + rgb.push(alpha); } return rgb; } -function hsl2rgb(token) { - let { h, s, l, a } = hslvalues(token); - return hsl2rgbvalues(h, s, l, a); -} -function cmyk2rgb(token) { +function cmyk2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -428,20 +514,20 @@ function cmyk2rgb(token) { // @ts-ignore const k = getNumber(t); const rgb = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) + 1 - Math.min(1, c * (1 - k) + k), + 1 - Math.min(1, m * (1 - k) + k), + 1 - Math.min(1, y * (1 - k) + k) ]; // @ts-ignore if (token.chi.length >= 9) { // @ts-ignore t = token.chi[8]; // @ts-ignore - rgb.push(Math.round(255 * getNumber(t))); + rgb.push(getNumber(t)); } return rgb; } -function oklab2rgb(token) { +function oklab2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -459,15 +545,13 @@ function oklab2rgb(token) { t = components[3]; // @ts-ignore const alpha = t == null ? 1 : getNumber(t); - const rgb = OKLab_to_sRGB(l, a, b).map(v => { - return Math.round(255 * v); - }); - if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); + const rgb = OKLab_to_sRGB(l, a, b); + if (alpha != 1 && alpha != null) { + rgb.push(alpha); } - return rgb.map(((value) => minmax(value, 0, 255))); + return rgb; //.map(((value: number) => minmax(value, 0, 255))); } -function oklch2rgb(token) { +function oklch2srgb(token) { const components = getComponents(token); // @ts-ignore let t = components[0]; @@ -490,62 +574,9 @@ function oklch2rgb(token) { if (alpha != 1) { rgb.push(alpha); } - return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); -} -function lab2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); -} -function lch2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const h = getAngle(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const a = c * Math.cos(360 * h * Math.PI / 180); - const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(value * 255, 0, 255))); + return rgb; //.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); } -function hslvalues(token) { +function hslvalues$1(token) { const components = getComponents(token); let t; // @ts-ignore @@ -572,7 +603,7 @@ function hslvalues(token) { } return a == null ? { h, s, l } : { h, s, l, a }; } -function hsl2rgbvalues(h, s, l, a = null) { +function hsl2srgbvalues(h, s, l, a = null) { let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; let r = l; let g = l; @@ -619,205 +650,630 @@ function hsl2rgbvalues(h, s, l, a = null) { break; } } - const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + const values = [r, g, b]; if (a != null && a != 1) { - values.push(Math.round(a * 255)); + values.push(a); } return values; } - -// name to color -const COLORS_NAMES = Object.seal({ - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgrey': '#a9a9a9', - 'darkgreen': '#006400', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgrey': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370d8', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#d87093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32', - 'rebeccapurple': '#663399', - 'transparent': '#00000000' -}); -// color to name -const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { - acc[value] = key; - return acc; -}, Object.create(null))); +function lab2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const a = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const b = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; +} +function lch2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == exports.EnumToken.PercentageTokenType ? 150 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const a = c * Math.cos(360 * h * Math.PI / 180); + const b = c * Math.sin(360 * h * Math.PI / 180); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; +} +function gam_sRGB(r, g, b) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + return [r, g, b].map((val) => { + let abs = Math.abs(val); + if (Math.abs(val) > 0.0031308) { + return (Math.sign(val) || 1) * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); + } + return 12.92 * val; + }); +} +// export function gam_a98rgb(r: number, g: number, b: number): number[] { +// // convert an array of linear-light a98-rgb in the range 0.0-1.0 +// // to gamma corrected form +// // negative values are also now accepted +// return [r, g, b].map(function (val: number): number { +// let sign: number = val < 0? -1 : 1; +// let abs: number = Math.abs(val); +// +// return roundWithPrecision(sign * Math.pow(abs, 256/563), val); +// }); +// } +function lin_ProPhoto(r, g, b) { + // convert an array of prophoto-rgb values + // where in-gamut colors are in the range [0.0 - 1.0] + // to linear light (un-companded) form. + // Transfer curve is gamma 1.8 with a small linear portion + // Extended transfer function + const Et2 = 16 / 512; + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + if (abs <= Et2) { + return roundWithPrecision(val / 16); + } + return roundWithPrecision(sign * Math.pow(abs, 1.8)); + }); +} +function lin_a98rgb(r, g, b) { + // convert an array of a98-rgb values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // negative values are also now accepted + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + return roundWithPrecision(sign * Math.pow(abs, 563 / 256)); + }); +} +function lin_2020(r, g, b) { + // convert an array of rec2020 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // ITU-R BT.2020-2 p.4 + const α = 1.09929682680944; + const β = 0.018053968510807; + return [r, g, b].map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + if (abs < β * 4.5) { + return roundWithPrecision(val / 4.5); + } + return roundWithPrecision(sign * (Math.pow((abs + α - 1) / α, 1 / 0.45))); + }); +} + +function srgb2rgb(value) { + return minmax(Math.round(value * 255), 0, 255); +} +function hex2rgb(token) { + const value = expandHexValue(token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb = []; + for (let i = 1; i < value.length; i += 2) { + rgb.push(parseInt(value.slice(i, i + 2), 16)); + } + return rgb; +} +function hwb2rgb(token) { + return hwb2srgb(token).map(srgb2rgb); +} +function hsl2rgb(token) { + let { h, s, l, a } = hslvalues(token); + return hsl2rgbvalues(h, s, l, a); +} +function cmyk2rgb(token) { + return cmyk2srgb(token).map(srgb2rgb); +} +function oklab2rgb(token) { + return oklab2srgb(token).map(srgb2rgb); +} +function oklch2rgb(token) { + return oklch2srgb(token).map(srgb2rgb); +} +function lab2rgb(token) { + return lab2srgb(token).map(srgb2rgb); +} +function lch2rgb(token) { + return lch2srgb(token).map(srgb2rgb); +} +function hslvalues(token) { + const components = getComponents(token); + let t; + // @ts-ignore + let h = getAngle(components[0]); + // @ts-ignore + t = components[1]; + // @ts-ignore + let s = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + let l = getNumber(t); + let a = null; + if (token.chi?.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || (t.typ == exports.EnumToken.PercentageTokenType && +t.val < 100) || + // @ts-ignore + (t.typ == exports.EnumToken.NumberTokenType && t.val < 1)) { + // @ts-ignore + a = getNumber(t); + } + } + return a == null ? { h, s, l } : { h, s, l, a }; +} +function hsl2rgbvalues(h, s, l, a = null) { + let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + if (a != null && a != 1) { + values.push(Math.round(a * 255)); + } + return values; +} + +function hwb2hsv(h, w, b, a) { + // @ts-ignore + return [h, 1 - w / (1 - b), 1 - b, a]; +} +// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js +function hsl2hsv(h, s, l, a = null) { + s *= l < .5 ? l : 1 - l; + const result = [ + //Range should be between 0 - 1 + h, //Hue stays the same + 2 * s / (l + s), //Saturation + l + s //Value + ]; + if (a != null) { + result.push(a); + } + return result; +} + +function eq(a, b) { + if (a == null || b == null) { + return a == b; + } + if (typeof a != 'object' || typeof b != 'object') { + return a === b; + } + if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { + return false; + } + if (Array.isArray(a)) { + if (a.length != b.length) { + return false; + } + let i = 0; + for (; i < a.length; i++) { + if (!eq(a[i], b[i])) { + return false; + } + } + return true; + } + const k1 = Object.keys(a); + const k2 = Object.keys(b); + if (k1.length != k2.length) { + return false; + } + let key; + for (key of k1) { + if (!(key in b) || !eq(a[key], b[key])) { + return false; + } + } + return true; +} + +function hex2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...hex2rgb(token)); +} +function rgb2hsl(token) { + const chi = getComponents(token); + // @ts-ignore + let t = chi[0]; + // @ts-ignore + let r = getNumber(t); + // @ts-ignore + t = chi[1]; + // @ts-ignore + let g = getNumber(t); + // @ts-ignore + t = chi[2]; + // @ts-ignore + let b = getNumber(t); + // @ts-ignore + t = chi[3]; + // @ts-ignore + let a = null; + if (t != null && !eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + // @ts-ignore + a = getNumber(t) / 255; + } + const values = [r, g, b]; + if (a != null && a != 1) { + values.push(a); + } + // @ts-ignore + return rgb2hslvalues(...values); +} +// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js +function hsv2hsl(h, s, v, a) { + const result = [ + //[hue, saturation, lightness] + //Range should be between 0 - 1 + h, //Hue stays the same + //Saturation is very different between the two color spaces + //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) + //Otherwise sat*val/(2-(2-sat)*val) + //Conditional is not operating with hue, it is reassigned! + s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), + h / 2, //Lightness is (2-sat)*val/2 + ]; + if (a != null) { + result.push(a); + } + return result; +} +function hwb2hsl(token) { + // @ts-ignore + return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); +} +function lab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lab2rgb(token)); +} +function lch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lch2rgb(token)); +} +function oklab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklab2rgb(token)); +} +function oklch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklch2rgb(token)); +} +function rgb2hslvalues(r, g, b, a = null) { + r /= 255; + g /= 255; + b /= 255; + let max = Math.max(r, g, b); + let min = Math.min(r, g, b); + let h = 0; + let s = 0; + let l = (max + min) / 2; + if (max != min) { + let d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + const hsl = [h, s, l]; + if (a != null && a < 1) { + // @ts-ignore + return hsl.concat([a]); + } + // @ts-ignore + return hsl; +} + +function rgb2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + return getNumber(t) / 255; + })); +} +function hsl2hwb(token) { + // @ts-ignore + return hsl2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: exports.EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + if (index == 0) { + return getAngle(t); + } + return getNumber(t); + })); +} +function lab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lab2srgb(token)); +} +function lch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lch2srgb(token)); +} +function oklab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklab2srgb(token)); +} +function oklch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklch2srgb(token)); +} +function rgb2hue(r, g, b, fallback = 0) { + let value = rgb2value(r, g, b); + let whiteness = rgb2whiteness(r, g, b); + let delta = value - whiteness; + if (delta > 0) { + // calculate segment + let segment = value === r ? (g - b) / delta : (value === g + ? (b - r) / delta + : (r - g) / delta); + // calculate shift + let shift = value === r ? segment < 0 + ? 360 / 60 + : 0 / 60 : (value === g + ? 120 / 60 + : 240 / 60); + // calculate hue + return (segment + shift) * 60; + } + return fallback; +} +function rgb2value(r, g, b) { + return Math.max(r, g, b); +} +function rgb2whiteness(r, g, b) { + return Math.min(r, g, b); +} +function srgb2hwbvalues(r, g, b, a = null, fallback = 0) { + r *= 100; + g *= 100; + b *= 100; + let hue = rgb2hue(r, g, b, fallback); + let whiteness = rgb2whiteness(r, g, b); + let value = Math.round(rgb2value(r, g, b)); + let blackness = 100 - value; + const result = [hue / 360, whiteness / 100, blackness / 100]; + if (a != null) { + result.push(a); + } + return result; +} +function hsv2hwb(h, s, v, a = null) { + const result = [h, (1 - s) * v, 1 - v]; + if (a != null) { + result.push(a); + } + return result; +} +function hsl2hwbvalues(h, s, l, a = null) { + // @ts-ignore + return hsv2hwb(...hsl2hsv(h, s, l, a)); +} + function convert(token, to) { - if (to == 'rgb') { + if (token.kin == to) { + return token; + } + // console.error({token, to}); + let values = []; + let chi; + if (to == 'hsl') { + switch (token.kin) { + case 'rgb': + case 'rgba': + values.push(...rgb2hsl(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; + case 'hwb': + values.push(...hwb2hsl(token)); + break; + case 'oklab': + values.push(...oklab2hsl(token)); + break; + case 'oklch': + values.push(...oklch2hsl(token)); + break; + case 'lab': + values.push(...lab2hsl(token)); + break; + case 'lch': + values.push(...lch2hsl(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'hwb') { switch (token.kin) { case 'rgb': case 'rgba': - return token; + values.push(...rgb2hwb(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; case 'hsl': case 'hsla': - const children = token.chi.filter(c => [exports.EnumToken.PercentageTokenType, exports.EnumToken.NumberTokenType, exports.EnumToken.IdenTokenType].includes(c.typ)); - let values = children.slice(0, 3).map((c) => getNumber(c)); - if (children.length == 4) { - values.push(children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 1 : getNumber(children[3])); - } - return { - typ: exports.EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - // @ts-ignore - chi: hsl2rgb(...values).map((v) => ({ - typ: exports.EnumToken.NumberTokenType, - val: String(v) - })) - }; + values.push(...hsl2hwb(token)); + break; + case 'oklab': + values.push(...oklab2hwb(token)); + break; + case 'oklch': + values.push(...oklch2hwb(token)); + break; + case 'lab': + values.push(...lab2hwb(token)); + break; + case 'lch': + values.push(...lch2hwb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: exports.EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'rgb') { + switch (token.kin) { case 'hex': case 'lit': - const value = token.kin == 'hex' ? expandHexValue(token.val) : COLORS_NAMES[token.val]; - return { - typ: exports.EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - chi: value.slice(1).match(/([a-fA-F0-9]{2})/g).map((v) => ({ - typ: exports.EnumToken.NumberTokenType, - val: String(parseInt(v, 16)) - })) - }; + values.push(...hex2rgb(token)); + break; + case 'hsl': + values.push(...hsl2rgb(token)); + break; + case 'hwb': + values.push(...hwb2rgb(token)); + break; + case 'oklab': + values.push(...oklab2rgb(token)); + break; + case 'oklch': + values.push(...oklch2rgb(token)); + break; + case 'lab': + values.push(...lab2rgb(token)); + break; + case 'lch': + values.push(...lch2rgb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: exports.EnumToken.NumberTokenType, val: String(values[0]) }, + { typ: exports.EnumToken.NumberTokenType, val: String(values[1]) }, + { typ: exports.EnumToken.NumberTokenType, val: String(values[2]) }, + ]; + if (values.length == 4) { + chi.push({ typ: exports.EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: exports.EnumToken.ColorTokenType, + val: 'rgb', + chi, + kin: 'rgb' + }; } } return null; @@ -855,7 +1311,7 @@ function clamp(token) { function clampValues(values, colorSpace) { switch (colorSpace) { case 'srgb': - case 'oklab': + // case 'oklab': case 'display-p3': case 'srgb-linear': // case 'prophoto-rgb': @@ -900,147 +1356,6 @@ function getAngle(token) { return token.val / 360; } -function hwb2hsv(h, w, b, a) { - // @ts-ignore - return [h, 1 - w / (1 - b), 1 - b, a]; -} -// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -function hsl2hsv(h, s, l) { - s *= l < .5 ? l : 1 - l; - return [ - //Range should be between 0 - 1 - h, //Hue stays the same - 2 * s / (l + s), //Saturation - l + s //Value - ]; -} - -function rgb2hue(r, g, b, fallback = 0) { - let value = rgb2value(r, g, b); - let whiteness = rgb2whiteness(r, g, b); - let delta = value - whiteness; - if (delta > 0) { - // calculate segment - let segment = value === r ? (g - b) / delta : (value === g - ? (b - r) / delta - : (r - g) / delta); - // calculate shift - let shift = value === r ? segment < 0 - ? 360 / 60 - : 0 / 60 : (value === g - ? 120 / 60 - : 240 / 60); - // calculate hue - return (segment + shift) * 60; - } - return fallback; -} -function rgb2value(r, g, b) { - return Math.max(r, g, b); -} -function rgb2whiteness(r, g, b) { - return Math.min(r, g, b); -} -function rgb2hwb(r, g, b, a = null, fallback = 0) { - r *= 100 / 255; - g *= 100 / 255; - b *= 100 / 255; - let hue = rgb2hue(r, g, b, fallback); - let whiteness = rgb2whiteness(r, g, b); - let value = Math.round(rgb2value(r, g, b)); - let blackness = 100 - value; - const result = [hue / 360, whiteness / 100, blackness / 100]; - if (a != null) { - result.push(a); - } - return result; -} -function hsv2hwb(h, s, v) { - return [h, (1 - s) * v, 1 - v]; -} -function hsl2hwb(h, s, l) { - return hsv2hwb(...hsl2hsv(h, s, l)); -} - -function rgb2hsl(token) { - const chi = getComponents(token); - // @ts-ignore - let t = chi[0]; - // @ts-ignore - let r = getNumber(t); - // @ts-ignore - t = chi[1]; - // @ts-ignore - let g = getNumber(t); - // @ts-ignore - t = chi[2]; - // @ts-ignore - let b = getNumber(t); - // @ts-ignore - t = chi[3]; - // @ts-ignore - let a = null; - if (t != null) { - // @ts-ignore - a = getNumber(t) / 255; - } - return rgb2hslvalues(r, g, b, a); -} -// https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -function hsv2hsl(h, s, v, a) { - return [ - //[hue, saturation, lightness] - //Range should be between 0 - 1 - h, //Hue stays the same - //Saturation is very different between the two color spaces - //If (2-sat)*val < 1 set it to sat*val/((2-sat)*val) - //Otherwise sat*val/(2-(2-sat)*val) - //Conditional is not operating with hue, it is reassigned! - s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), - h / 2, //Lightness is (2-sat)*val/2 - //See reassignment of hue above, - // @ts-ignore - a - ]; -} -function hwb2hsl(token) { - // @ts-ignore - return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); -} -function rgb2hslvalues(r, g, b, a = null) { - r /= 255; - g /= 255; - b /= 255; - let max = Math.max(r, g, b); - let min = Math.min(r, g, b); - let h = 0; - let s = 0; - let l = (max + min) / 2; - if (max != min) { - let d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - const hsl = [h, s, l]; - if (a != null && a < 1) { - // @ts-ignore - return hsl.concat([a]); - } - // @ts-ignore - return hsl; -} - function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { if (!isRectangularOrthogonalColorspace(colorSpace)) { return null; @@ -1407,168 +1722,37 @@ function factor(tokens, ops) { } function parseRelativeColor(relativeKeys, original, rExp, gExp, bExp, aExp) { - const type = relativeKeys.join(''); let r; let g; let b; let alpha = null; let keys = {}; let values = {}; - let children; - switch (original.kin) { - case 'lit': - case 'hex': - let value = original.val.toLowerCase(); - if (original.kin == 'lit') { - if (original.val.toLowerCase() in COLORS_NAMES) { - value = COLORS_NAMES[original.val.toLowerCase()]; - } - else { - return null; - } - } - if (value.length == 4) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]; - } - else if (value.length == 5) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3] + value[4] + value[4]; - } - r = parseInt(value.slice(1, 3), 16); - g = parseInt(value.slice(3, 5), 16); - b = parseInt(value.slice(5, 7), 16); - alpha = value.length == 9 ? parseInt(value.slice(7, 9), 16) : null; - break; - case 'rgb': - case 'rgba': - children = original.chi.filter((t) => t.typ == exports.EnumToken.NumberTokenType || t.typ == exports.EnumToken.IdenTokenType || t.typ == exports.EnumToken.PercentageTokenType); - if (children.every((t) => (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || t.typ == exports.EnumToken.NumberTokenType)) { - r = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : +children[0].val; - g = children[1].typ == exports.EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : +children[1].val; - b = children[2].typ == exports.EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : +children[2].val; - alpha = children.length < 4 ? null : children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val; - } - else if (children.every((t) => t.typ == exports.EnumToken.PercentageTokenType || (t.typ == exports.EnumToken.IdenTokenType && t.val == 'none') || (t.typ == exports.EnumToken.NumberTokenType && t.val == '0'))) { - // @ts-ignore - r = children[0].typ == exports.EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : children[0].val * 255 / 100; - // @ts-ignore - g = children[1].typ == exports.EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : children[1].val * 255 / 100; - // @ts-ignore - b = children[2].typ == exports.EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : children[2].val * 255 / 100; - alpha = children.length < 4 ? null : children[3].typ == exports.EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val / 100; - } - else { - return null; - } - break; - case 'hsl': - case 'hsla': - case 'hwb': - children = original.chi.filter((t) => t.typ == exports.EnumToken.AngleTokenType || t.typ == exports.EnumToken.NumberTokenType || t.typ == exports.EnumToken.IdenTokenType || t.typ == exports.EnumToken.PercentageTokenType); - if (children.length == 3 || children.length == 4) { - [r, g, b, alpha] = children; - } - else { - return null; - } - break; - default: - return null; - } - const from = ['rgb', 'rgba', 'hex', 'lit'].includes(original.kin) ? 'rgb' : original.kin; - if (from != type) { - if (type == 'hsl' || type == 'hwb') { - if (from == 'rgb') { - [r, g, b] = (type == 'hwb' ? rgb2hwb : rgb2hsl)(r, g, b); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - else if (from == 'hwb' || from == 'hsl') { - if (type == 'hsl') { - if (from == 'hwb') { - [r, g, b] = hwb2hsl(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - } - else if (type == 'hwb') { - if (from == 'hsl') { - [r, g, b] = hsl2hwb(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: exports.EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.PercentageTokenType, val: b } - }; - } - } - } - else { - return null; - } - } - else if (type == 'rgb') { - if (from == 'hsl' || from == 'hwb') { - [r, g, b] = (from == 'hwb' ? hwb2rgb : hsl2rgb)(original); - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: exports.EnumToken.NumberTokenType, val: r }, - [relativeKeys[1]]: { typ: exports.EnumToken.NumberTokenType, val: g }, - [relativeKeys[2]]: { typ: exports.EnumToken.NumberTokenType, val: b } - }; - } - else { - return null; - } - } - } - else { - values = { - [relativeKeys[0]]: r, - [relativeKeys[1]]: g, - [relativeKeys[2]]: b - }; - } - if (aExp != null && aExp.typ == exports.EnumToken.IdenTokenType && aExp.val == 'none') { - aExp = null; + const converted = convert(original, relativeKeys); + if (converted == null) { + return null; } + [r, g, b, alpha] = converted.chi; + values = { + [relativeKeys[0]]: r, + [relativeKeys[1]]: g, + [relativeKeys[2]]: b, + // @ts-ignore + alpha: alpha == null || eq(alpha, { + typ: exports.EnumToken.IdenTokenType, + val: 'none' + }) ? { typ: exports.EnumToken.NumberTokenType, val: '1' } : alpha + }; keys = { [relativeKeys[0]]: rExp, [relativeKeys[1]]: gExp, [relativeKeys[2]]: bExp, - alpha: aExp ?? { typ: exports.EnumToken.IdenTokenType, val: 'alpha' } + // @ts-ignore + alpha: aExp == null || eq(aExp, { typ: exports.EnumToken.IdenTokenType, val: 'none' }) ? { + typ: exports.EnumToken.NumberTokenType, + val: '1' + } : aExp }; - for (const [key, value] of Object.entries(values)) { - if (typeof value == 'number') { - values[key] = { typ: exports.EnumToken.NumberTokenType, val: reduceNumber(value) }; - } - } - // @ts-ignore - values.alpha = alpha != null && typeof alpha == 'object' ? alpha : b.typ == exports.EnumToken.PercentageTokenType ? { typ: exports.EnumToken.PercentageTokenType, val: String(alpha ?? 100) } : { typ: exports.EnumToken.NumberTokenType, val: String(alpha ?? 1) }; return computeComponentValue(keys, values); } function computeComponentValue(expr, values) { @@ -2056,11 +2240,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, return reduceHexValue(value); } } - else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb'].includes(token.val)) { - const chi = token.chi.filter((x) => ![ - exports.EnumToken.LiteralTokenType, exports.EnumToken.CommaTokenType, exports.EnumToken.WhitespaceTokenType, exports.EnumToken.CommentTokenType - ].includes(x.typ)); - const components = parseRelativeColor(token.val.split(''), chi[1], chi[2], chi[3], chi[4], chi[5]); + else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'].includes(token.val)) { + const chi = getComponents(token); + const components = parseRelativeColor(token.val, chi[1], chi[2], chi[3], chi[4], chi[5]); if (components != null) { token.chi = Object.values(components); delete token.cal; @@ -2109,6 +2291,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, return 'currentcolor'; } clamp(token); + // console.error({token}); if (Array.isArray(token.chi) && token.chi.some((t) => t.typ == exports.EnumToken.FunctionTokenType || (t.typ == exports.EnumToken.ColorTokenType && Array.isArray(t.chi)))) { return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc, curr) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == exports.EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')'; } @@ -5794,42 +5977,6 @@ function parseTokens(tokens, options = {}) { return tokens; } -function eq(a, b) { - if (a == null || b == null) { - return a == b; - } - if (typeof a != 'object' || typeof b != 'object') { - return a === b; - } - if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) { - return false; - } - if (Array.isArray(a)) { - if (a.length != b.length) { - return false; - } - let i = 0; - for (; i < a.length; i++) { - if (!eq(a[i], b[i])) { - return false; - } - } - return true; - } - const k1 = Object.keys(a); - const k2 = Object.keys(b); - if (k1.length != k2.length) { - return false; - } - let key; - for (key of k1) { - if (!(key in b) || !eq(a[key], b[key])) { - return false; - } - } - return true; -} - function* walk(node, filter) { const parents = [node]; const root = node; diff --git a/dist/lib/ast/expand.js b/dist/lib/ast/expand.js index da8dba6..091b3e0 100644 --- a/dist/lib/ast/expand.js +++ b/dist/lib/ast/expand.js @@ -1,9 +1,9 @@ import { splitRule, combinators } from './minify.js'; import { parseString } from '../parser/parse.js'; import { renderToken } from '../renderer/render.js'; -import '../renderer/color/color.js'; import { EnumToken } from './types.js'; import { walkValues } from './walk.js'; +import '../renderer/color/utils/constants.js'; function expand(ast) { // diff --git a/dist/lib/ast/features/shorthand.js b/dist/lib/ast/features/shorthand.js index 85a6536..6fc7537 100644 --- a/dist/lib/ast/features/shorthand.js +++ b/dist/lib/ast/features/shorthand.js @@ -1,8 +1,8 @@ import { PropertyList } from '../../parser/declaration/list.js'; -import '../../renderer/color/color.js'; import { EnumToken } from '../types.js'; import '../minify.js'; import '../../parser/parse.js'; +import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import { MinifyFeature } from '../utils/minifyfeature.js'; diff --git a/dist/lib/parser/declaration/list.js b/dist/lib/parser/declaration/list.js index b39c523..a22bc9b 100644 --- a/dist/lib/parser/declaration/list.js +++ b/dist/lib/parser/declaration/list.js @@ -1,8 +1,8 @@ import { PropertySet } from './set.js'; -import '../../renderer/color/color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import { parseString } from '../parse.js'; +import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; import { getConfig } from '../utils/config.js'; import { PropertyMap } from './map.js'; diff --git a/dist/lib/parser/declaration/map.js b/dist/lib/parser/declaration/map.js index 73d6c01..3102e63 100644 --- a/dist/lib/parser/declaration/map.js +++ b/dist/lib/parser/declaration/map.js @@ -1,9 +1,9 @@ import { eq } from '../utils/eq.js'; import { renderToken } from '../../renderer/render.js'; -import '../../renderer/color/color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import { parseString } from '../parse.js'; +import '../../renderer/color/utils/constants.js'; import { getConfig } from '../utils/config.js'; import { matchType } from '../utils/type.js'; import { PropertySet } from './set.js'; diff --git a/dist/lib/parser/declaration/set.js b/dist/lib/parser/declaration/set.js index 4244fa0..738890d 100644 --- a/dist/lib/parser/declaration/set.js +++ b/dist/lib/parser/declaration/set.js @@ -3,7 +3,7 @@ import { isLength } from '../utils/syntax.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../parse.js'; -import '../../renderer/color/color.js'; +import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; function dedup(values) { diff --git a/dist/lib/parser/parse.js b/dist/lib/parser/parse.js index 4e9d27e..b1803c7 100644 --- a/dist/lib/parser/parse.js +++ b/dist/lib/parser/parse.js @@ -5,7 +5,7 @@ import { walkValues, walk } from '../ast/walk.js'; import { expand } from '../ast/expand.js'; import { parseDeclaration } from './utils/declaration.js'; import { renderToken } from '../renderer/render.js'; -import { COLORS_NAMES } from '../renderer/color/color.js'; +import { COLORS_NAMES } from '../renderer/color/utils/constants.js'; import { tokenize } from './tokenize.js'; const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/; diff --git a/dist/lib/parser/tokenize.js b/dist/lib/parser/tokenize.js index e3fa513..685d4c1 100644 --- a/dist/lib/parser/tokenize.js +++ b/dist/lib/parser/tokenize.js @@ -2,7 +2,7 @@ import { isWhiteSpace, isNewLine, isDigit, isNonPrintable } from './utils/syntax import { EnumToken } from '../ast/types.js'; import '../ast/minify.js'; import './parse.js'; -import '../renderer/color/color.js'; +import '../renderer/color/utils/constants.js'; import '../renderer/sourcemap/lib/encode.js'; function* tokenize(stream) { diff --git a/dist/lib/parser/utils/declaration.js b/dist/lib/parser/utils/declaration.js index 3cbabea..3987abe 100644 --- a/dist/lib/parser/utils/declaration.js +++ b/dist/lib/parser/utils/declaration.js @@ -3,7 +3,7 @@ import '../../ast/minify.js'; import { walkValues } from '../../ast/walk.js'; import '../parse.js'; import { isWhiteSpace } from './syntax.js'; -import '../../renderer/color/color.js'; +import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; function parseDeclaration(node, errors, src, position) { diff --git a/dist/lib/parser/utils/syntax.js b/dist/lib/parser/utils/syntax.js index f10c558..9b51a63 100644 --- a/dist/lib/parser/utils/syntax.js +++ b/dist/lib/parser/utils/syntax.js @@ -1,8 +1,8 @@ import { colorsFunc } from '../../renderer/render.js'; -import { COLORS_NAMES } from '../../renderer/color/color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../parse.js'; +import { COLORS_NAMES } from '../../renderer/color/utils/constants.js'; // https://www.w3.org/TR/CSS21/syndata.html#syntax // https://www.w3.org/TR/2021/CRD-css-syntax-3-20211224/#typedef-ident-token diff --git a/dist/lib/parser/utils/type.js b/dist/lib/parser/utils/type.js index 0a9d4b4..e205e4d 100644 --- a/dist/lib/parser/utils/type.js +++ b/dist/lib/parser/utils/type.js @@ -1,7 +1,7 @@ import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../parse.js'; -import '../../renderer/color/color.js'; +import '../../renderer/color/utils/constants.js'; import '../../renderer/sourcemap/lib/encode.js'; // https://www.w3.org/TR/css-values-4/#math-function diff --git a/dist/lib/renderer/color/color.js b/dist/lib/renderer/color/color.js index 8511a93..cb81465 100644 --- a/dist/lib/renderer/color/color.js +++ b/dist/lib/renderer/color/color.js @@ -1,202 +1,146 @@ import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import { hsl2rgb } from './rgb.js'; -import { expandHexValue } from './hex.js'; +import { lch2rgb, lab2rgb, oklch2rgb, oklab2rgb, hwb2rgb, hsl2rgb, hex2rgb } from './rgb.js'; +import { lch2hsl, lab2hsl, oklch2hsl, oklab2hsl, hwb2hsl, hex2hsl, rgb2hsl } from './hsl.js'; +import { lch2hwb, lab2hwb, oklch2hwb, oklab2hwb, hsl2hwb, rgb2hwb } from './hwb.js'; +import './utils/constants.js'; import '../sourcemap/lib/encode.js'; -// name to color -const COLORS_NAMES = Object.seal({ - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgrey': '#a9a9a9', - 'darkgreen': '#006400', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgrey': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370d8', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#d87093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32', - 'rebeccapurple': '#663399', - 'transparent': '#00000000' -}); -// color to name -const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { - acc[value] = key; - return acc; -}, Object.create(null))); function convert(token, to) { - if (to == 'rgb') { + if (token.kin == to) { + return token; + } + // console.error({token, to}); + let values = []; + let chi; + if (to == 'hsl') { switch (token.kin) { case 'rgb': case 'rgba': - return token; + values.push(...rgb2hsl(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; + case 'hwb': + values.push(...hwb2hsl(token)); + break; + case 'oklab': + values.push(...oklab2hsl(token)); + break; + case 'oklch': + values.push(...oklch2hsl(token)); + break; + case 'lab': + values.push(...lab2hsl(token)); + break; + case 'lch': + values.push(...lch2hsl(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'hwb') { + switch (token.kin) { + case 'rgb': + case 'rgba': + values.push(...rgb2hwb(token)); + break; + case 'hex': + case 'lit': + values.push(...hex2hsl(token)); + break; case 'hsl': case 'hsla': - const children = token.chi.filter(c => [EnumToken.PercentageTokenType, EnumToken.NumberTokenType, EnumToken.IdenTokenType].includes(c.typ)); - let values = children.slice(0, 3).map((c) => getNumber(c)); - if (children.length == 4) { - values.push(children[3].typ == EnumToken.IdenTokenType && children[3].val == 'none' ? 1 : getNumber(children[3])); - } - return { - typ: EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - // @ts-ignore - chi: hsl2rgb(...values).map((v) => ({ - typ: EnumToken.NumberTokenType, - val: String(v) - })) - }; + values.push(...hsl2hwb(token)); + break; + case 'oklab': + values.push(...oklab2hwb(token)); + break; + case 'oklch': + values.push(...oklch2hwb(token)); + break; + case 'lab': + values.push(...lab2hwb(token)); + break; + case 'lch': + values.push(...lch2hwb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg' }, + { typ: EnumToken.PercentageTokenType, val: String(values[1] * 100) }, + { typ: EnumToken.PercentageTokenType, val: String(values[2] * 100) }, + ]; + if (values.length == 4) { + chi.push({ typ: EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + }; + } + } + else if (to == 'rgb') { + switch (token.kin) { case 'hex': case 'lit': - const value = token.kin == 'hex' ? expandHexValue(token.val) : COLORS_NAMES[token.val]; - return { - typ: EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - chi: value.slice(1).match(/([a-fA-F0-9]{2})/g).map((v) => ({ - typ: EnumToken.NumberTokenType, - val: String(parseInt(v, 16)) - })) - }; + values.push(...hex2rgb(token)); + break; + case 'hsl': + values.push(...hsl2rgb(token)); + break; + case 'hwb': + values.push(...hwb2rgb(token)); + break; + case 'oklab': + values.push(...oklab2rgb(token)); + break; + case 'oklch': + values.push(...oklch2rgb(token)); + break; + case 'lab': + values.push(...lab2rgb(token)); + break; + case 'lch': + values.push(...lch2rgb(token)); + break; + } + if (values.length > 0) { + chi = [ + { typ: EnumToken.NumberTokenType, val: String(values[0]) }, + { typ: EnumToken.NumberTokenType, val: String(values[1]) }, + { typ: EnumToken.NumberTokenType, val: String(values[2]) }, + ]; + if (values.length == 4) { + chi.push({ typ: EnumToken.PercentageTokenType, val: String(values[3] * 100) }); + } + return { + typ: EnumToken.ColorTokenType, + val: 'rgb', + chi, + kin: 'rgb' + }; } } return null; @@ -234,7 +178,7 @@ function clamp(token) { function clampValues(values, colorSpace) { switch (colorSpace) { case 'srgb': - case 'oklab': + // case 'oklab': case 'display-p3': case 'srgb-linear': // case 'prophoto-rgb': @@ -279,4 +223,4 @@ function getAngle(token) { return token.val / 360; } -export { COLORS_NAMES, NAMES_COLORS, clamp, clampValues, convert, getAngle, getNumber, minmax }; +export { clamp, clampValues, convert, getAngle, getNumber, minmax }; diff --git a/dist/lib/renderer/color/colormix.js b/dist/lib/renderer/color/colormix.js index bec5d2e..be01d40 100644 --- a/dist/lib/renderer/color/colormix.js +++ b/dist/lib/renderer/color/colormix.js @@ -3,6 +3,7 @@ import { isRectangularOrthogonalColorspace } from '../../parser/utils/syntax.js' import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import { convert } from './color.js'; +import './utils/constants.js'; import '../sourcemap/lib/encode.js'; function colorMix(colorSpace, hueInterpolationMethod, color1, percentage1, color2, percentage2) { diff --git a/dist/lib/renderer/color/hex.js b/dist/lib/renderer/color/hex.js index 66d4e51..2fc0ebe 100644 --- a/dist/lib/renderer/color/hex.js +++ b/dist/lib/renderer/color/hex.js @@ -1,8 +1,9 @@ import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import { NAMES_COLORS, getNumber } from './color.js'; +import { getNumber } from './color.js'; import { hsl2rgb, hwb2rgb, cmyk2rgb, oklab2rgb, oklch2rgb, lab2rgb, lch2rgb } from './rgb.js'; +import { NAMES_COLORS } from './utils/constants.js'; import '../sourcemap/lib/encode.js'; function toHexString(acc, value) { @@ -64,6 +65,7 @@ function hsl2hex(token) { return `${hsl2rgb(token).reduce(toHexString, '#')}`; } function hwb2hex(token) { + // console.error(hwb2rgb(token)); return `${hwb2rgb(token).reduce(toHexString, '#')}`; } function cmyk2hex(token) { diff --git a/dist/lib/renderer/color/hsl.js b/dist/lib/renderer/color/hsl.js index f096fee..cb28fb7 100644 --- a/dist/lib/renderer/color/hsl.js +++ b/dist/lib/renderer/color/hsl.js @@ -1,8 +1,18 @@ import { hwb2hsv } from './hsv.js'; import { getNumber } from './color.js'; -import { hslvalues } from './rgb.js'; +import { hex2rgb, hslvalues, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb } from './rgb.js'; +import './utils/constants.js'; import { getComponents } from './utils/components.js'; +import { eq } from '../../parser/utils/eq.js'; +import { EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import '../sourcemap/lib/encode.js'; +function hex2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...hex2rgb(token)); +} function rgb2hsl(token) { const chi = getComponents(token); // @ts-ignore @@ -21,15 +31,20 @@ function rgb2hsl(token) { t = chi[3]; // @ts-ignore let a = null; - if (t != null) { + if (t != null && !eq(t, { typ: EnumToken.IdenTokenType, val: 'none' })) { // @ts-ignore a = getNumber(t) / 255; } - return rgb2hslvalues(r, g, b, a); + const values = [r, g, b]; + if (a != null && a != 1) { + values.push(a); + } + // @ts-ignore + return rgb2hslvalues(...values); } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js function hsv2hsl(h, s, v, a) { - return [ + const result = [ //[hue, saturation, lightness] //Range should be between 0 - 1 h, //Hue stays the same @@ -39,15 +54,32 @@ function hsv2hsl(h, s, v, a) { //Conditional is not operating with hue, it is reassigned! s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), h / 2, //Lightness is (2-sat)*val/2 - //See reassignment of hue above, - // @ts-ignore - a ]; + if (a != null) { + result.push(a); + } + return result; } function hwb2hsl(token) { // @ts-ignore return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); } +function lab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lab2rgb(token)); +} +function lch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...lch2rgb(token)); +} +function oklab2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklab2rgb(token)); +} +function oklch2hsl(token) { + // @ts-ignore + return rgb2hslvalues(...oklch2rgb(token)); +} function rgb2hslvalues(r, g, b, a = null) { r /= 255; g /= 255; @@ -82,4 +114,4 @@ function rgb2hslvalues(r, g, b, a = null) { return hsl; } -export { hsv2hsl, hwb2hsl, rgb2hsl, rgb2hslvalues }; +export { hex2hsl, hsv2hsl, hwb2hsl, lab2hsl, lch2hsl, oklab2hsl, oklch2hsl, rgb2hsl, rgb2hslvalues }; diff --git a/dist/lib/renderer/color/hsv.js b/dist/lib/renderer/color/hsv.js index ed036da..bb301b7 100644 --- a/dist/lib/renderer/color/hsv.js +++ b/dist/lib/renderer/color/hsv.js @@ -3,14 +3,18 @@ function hwb2hsv(h, w, b, a) { return [h, 1 - w / (1 - b), 1 - b, a]; } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -function hsl2hsv(h, s, l) { +function hsl2hsv(h, s, l, a = null) { s *= l < .5 ? l : 1 - l; - return [ + const result = [ //Range should be between 0 - 1 h, //Hue stays the same 2 * s / (l + s), //Saturation l + s //Value ]; + if (a != null) { + result.push(a); + } + return result; } export { hsl2hsv, hwb2hsv }; diff --git a/dist/lib/renderer/color/hwb.js b/dist/lib/renderer/color/hwb.js index dbe2155..e094080 100644 --- a/dist/lib/renderer/color/hwb.js +++ b/dist/lib/renderer/color/hwb.js @@ -1,5 +1,51 @@ import { hsl2hsv } from './hsv.js'; +import './utils/constants.js'; +import { getComponents } from './utils/components.js'; +import { getNumber, getAngle } from './color.js'; +import { EnumToken } from '../../ast/types.js'; +import '../../ast/minify.js'; +import '../../parser/parse.js'; +import { lab2srgb, lch2srgb, oklab2srgb, oklch2srgb } from './srgb.js'; +import { eq } from '../../parser/utils/eq.js'; +import '../sourcemap/lib/encode.js'; +function rgb2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + return getNumber(t) / 255; + })); +} +function hsl2hwb(token) { + // @ts-ignore + return hsl2hwbvalues(...getComponents(token).map((t, index) => { + if (index == 3 && eq(t, { typ: EnumToken.IdenTokenType, val: 'none' })) { + return 1; + } + if (index == 0) { + return getAngle(t); + } + return getNumber(t); + })); +} +function lab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lab2srgb(token)); +} +function lch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...lch2srgb(token)); +} +function oklab2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklab2srgb(token)); +} +function oklch2hwb(token) { + // @ts-ignore + return srgb2hwbvalues(...oklch2srgb(token)); +} function rgb2hue(r, g, b, fallback = 0) { let value = rgb2value(r, g, b); let whiteness = rgb2whiteness(r, g, b); @@ -26,10 +72,10 @@ function rgb2value(r, g, b) { function rgb2whiteness(r, g, b) { return Math.min(r, g, b); } -function rgb2hwb(r, g, b, a = null, fallback = 0) { - r *= 100 / 255; - g *= 100 / 255; - b *= 100 / 255; +function srgb2hwbvalues(r, g, b, a = null, fallback = 0) { + r *= 100; + g *= 100; + b *= 100; let hue = rgb2hue(r, g, b, fallback); let whiteness = rgb2whiteness(r, g, b); let value = Math.round(rgb2value(r, g, b)); @@ -40,11 +86,16 @@ function rgb2hwb(r, g, b, a = null, fallback = 0) { } return result; } -function hsv2hwb(h, s, v) { - return [h, (1 - s) * v, 1 - v]; +function hsv2hwb(h, s, v, a = null) { + const result = [h, (1 - s) * v, 1 - v]; + if (a != null) { + result.push(a); + } + return result; } -function hsl2hwb(h, s, l) { - return hsv2hwb(...hsl2hsv(h, s, l)); +function hsl2hwbvalues(h, s, l, a = null) { + // @ts-ignore + return hsv2hwb(...hsl2hsv(h, s, l, a)); } -export { hsl2hwb, hsv2hwb, rgb2hwb }; +export { hsl2hwb, hsl2hwbvalues, hsv2hwb, lab2hwb, lch2hwb, oklab2hwb, oklch2hwb, rgb2hwb, srgb2hwbvalues }; diff --git a/dist/lib/renderer/color/lab.js b/dist/lib/renderer/color/lab.js index 6f6bae4..1a785e4 100644 --- a/dist/lib/renderer/color/lab.js +++ b/dist/lib/renderer/color/lab.js @@ -2,7 +2,6 @@ import { D50 } from './utils/constants.js'; import '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import './color.js'; import { XYZ_to_sRGB } from './xyz.js'; import '../sourcemap/lib/encode.js'; diff --git a/dist/lib/renderer/color/oklab.js b/dist/lib/renderer/color/oklab.js index 01b1e7c..c2af254 100644 --- a/dist/lib/renderer/color/oklab.js +++ b/dist/lib/renderer/color/oklab.js @@ -1,7 +1,7 @@ +import './utils/constants.js'; import '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import './color.js'; import { gam_sRGB } from './srgb.js'; import '../sourcemap/lib/encode.js'; diff --git a/dist/lib/renderer/color/relativecolor.js b/dist/lib/renderer/color/relativecolor.js index 836b294..412ab31 100644 --- a/dist/lib/renderer/color/relativecolor.js +++ b/dist/lib/renderer/color/relativecolor.js @@ -1,177 +1,45 @@ -import { COLORS_NAMES, getNumber, getAngle } from './color.js'; +import { convert } from './color.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import { walkValues } from '../../ast/walk.js'; import '../../parser/parse.js'; import { reduceNumber } from '../render.js'; -import { hwb2rgb, hsl2rgb } from './rgb.js'; -import { rgb2hwb, hsl2hwb } from './hwb.js'; -import { rgb2hsl, hwb2hsl } from './hsl.js'; +import './utils/constants.js'; +import { eq } from '../../parser/utils/eq.js'; import { evaluate } from '../../ast/math/expression.js'; function parseRelativeColor(relativeKeys, original, rExp, gExp, bExp, aExp) { - const type = relativeKeys.join(''); let r; let g; let b; let alpha = null; let keys = {}; let values = {}; - let children; - switch (original.kin) { - case 'lit': - case 'hex': - let value = original.val.toLowerCase(); - if (original.kin == 'lit') { - if (original.val.toLowerCase() in COLORS_NAMES) { - value = COLORS_NAMES[original.val.toLowerCase()]; - } - else { - return null; - } - } - if (value.length == 4) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]; - } - else if (value.length == 5) { - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3] + value[4] + value[4]; - } - r = parseInt(value.slice(1, 3), 16); - g = parseInt(value.slice(3, 5), 16); - b = parseInt(value.slice(5, 7), 16); - alpha = value.length == 9 ? parseInt(value.slice(7, 9), 16) : null; - break; - case 'rgb': - case 'rgba': - children = original.chi.filter((t) => t.typ == EnumToken.NumberTokenType || t.typ == EnumToken.IdenTokenType || t.typ == EnumToken.PercentageTokenType); - if (children.every((t) => (t.typ == EnumToken.IdenTokenType && t.val == 'none') || t.typ == EnumToken.NumberTokenType)) { - r = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : +children[0].val; - g = children[1].typ == EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : +children[1].val; - b = children[2].typ == EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : +children[2].val; - alpha = children.length < 4 ? null : children[3].typ == EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val; - } - else if (children.every((t) => t.typ == EnumToken.PercentageTokenType || (t.typ == EnumToken.IdenTokenType && t.val == 'none') || (t.typ == EnumToken.NumberTokenType && t.val == '0'))) { - // @ts-ignore - r = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : children[0].val * 255 / 100; - // @ts-ignore - g = children[1].typ == EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : children[1].val * 255 / 100; - // @ts-ignore - b = children[2].typ == EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : children[2].val * 255 / 100; - alpha = children.length < 4 ? null : children[3].typ == EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +children[3].val / 100; - } - else { - return null; - } - break; - case 'hsl': - case 'hsla': - case 'hwb': - children = original.chi.filter((t) => t.typ == EnumToken.AngleTokenType || t.typ == EnumToken.NumberTokenType || t.typ == EnumToken.IdenTokenType || t.typ == EnumToken.PercentageTokenType); - if (children.length == 3 || children.length == 4) { - [r, g, b, alpha] = children; - } - else { - return null; - } - break; - default: - return null; - } - const from = ['rgb', 'rgba', 'hex', 'lit'].includes(original.kin) ? 'rgb' : original.kin; - if (from != type) { - if (type == 'hsl' || type == 'hwb') { - if (from == 'rgb') { - [r, g, b] = (type == 'hwb' ? rgb2hwb : rgb2hsl)(r, g, b); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - values = { - [relativeKeys[0]]: { typ: EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: EnumToken.PercentageTokenType, val: b } - }; - } - else if (from == 'hwb' || from == 'hsl') { - if (type == 'hsl') { - if (from == 'hwb') { - [r, g, b] = hwb2hsl(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: EnumToken.PercentageTokenType, val: b } - }; - } - } - else if (type == 'hwb') { - if (from == 'hsl') { - [r, g, b] = hsl2hwb(getAngle(r), getNumber(g), getNumber(b)); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: EnumToken.AngleTokenType, val: r, unit: 'deg' }, - [relativeKeys[1]]: { typ: EnumToken.PercentageTokenType, val: g }, - [relativeKeys[2]]: { typ: EnumToken.PercentageTokenType, val: b } - }; - } - } - } - else { - return null; - } - } - else if (type == 'rgb') { - if (from == 'hsl' || from == 'hwb') { - [r, g, b] = (from == 'hwb' ? hwb2rgb : hsl2rgb)(original); - // @ts-ignore - values = { - [relativeKeys[0]]: { typ: EnumToken.NumberTokenType, val: r }, - [relativeKeys[1]]: { typ: EnumToken.NumberTokenType, val: g }, - [relativeKeys[2]]: { typ: EnumToken.NumberTokenType, val: b } - }; - } - else { - return null; - } - } - } - else { - values = { - [relativeKeys[0]]: r, - [relativeKeys[1]]: g, - [relativeKeys[2]]: b - }; - } - if (aExp != null && aExp.typ == EnumToken.IdenTokenType && aExp.val == 'none') { - aExp = null; + const converted = convert(original, relativeKeys); + if (converted == null) { + return null; } + [r, g, b, alpha] = converted.chi; + values = { + [relativeKeys[0]]: r, + [relativeKeys[1]]: g, + [relativeKeys[2]]: b, + // @ts-ignore + alpha: alpha == null || eq(alpha, { + typ: EnumToken.IdenTokenType, + val: 'none' + }) ? { typ: EnumToken.NumberTokenType, val: '1' } : alpha + }; keys = { [relativeKeys[0]]: rExp, [relativeKeys[1]]: gExp, [relativeKeys[2]]: bExp, - alpha: aExp ?? { typ: EnumToken.IdenTokenType, val: 'alpha' } + // @ts-ignore + alpha: aExp == null || eq(aExp, { typ: EnumToken.IdenTokenType, val: 'none' }) ? { + typ: EnumToken.NumberTokenType, + val: '1' + } : aExp }; - for (const [key, value] of Object.entries(values)) { - if (typeof value == 'number') { - values[key] = { typ: EnumToken.NumberTokenType, val: reduceNumber(value) }; - } - } - // @ts-ignore - values.alpha = alpha != null && typeof alpha == 'object' ? alpha : b.typ == EnumToken.PercentageTokenType ? { typ: EnumToken.PercentageTokenType, val: String(alpha ?? 100) } : { typ: EnumToken.NumberTokenType, val: String(alpha ?? 1) }; return computeComponentValue(keys, values); } function computeComponentValue(expr, values) { diff --git a/dist/lib/renderer/color/rgb.js b/dist/lib/renderer/color/rgb.js index 638322d..e0a77df 100644 --- a/dist/lib/renderer/color/rgb.js +++ b/dist/lib/renderer/color/rgb.js @@ -1,163 +1,45 @@ -import { getNumber, minmax, getAngle } from './color.js'; +import { getAngle, getNumber, minmax } from './color.js'; +import { COLORS_NAMES } from './utils/constants.js'; import { getComponents } from './utils/components.js'; -import { OKLab_to_sRGB } from './oklab.js'; +import { expandHexValue } from './hex.js'; import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import { Lab_to_sRGB } from './lab.js'; +import { hwb2srgb, cmyk2srgb, oklab2srgb, oklch2srgb, lab2srgb, lch2srgb } from './srgb.js'; import '../sourcemap/lib/encode.js'; -function hwb2rgb(token) { - const { h: hue, s: white, l: black, a: alpha } = hslvalues(token); - const rgb = hsl2rgbvalues(hue, 1, .5); - for (let i = 0; i < 3; i++) { - rgb[i] *= (1 - white - black); - rgb[i] = Math.round(rgb[i] + white); - } - if (alpha != null && alpha != 1) { - rgb.push(Math.round(255 * alpha)); +function srgb2rgb(value) { + return minmax(Math.round(value * 255), 0, 255); +} +function hex2rgb(token) { + const value = expandHexValue(token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb = []; + for (let i = 1; i < value.length; i += 2) { + rgb.push(parseInt(value.slice(i, i + 2), 16)); } return rgb; } +function hwb2rgb(token) { + return hwb2srgb(token).map(srgb2rgb); +} function hsl2rgb(token) { let { h, s, l, a } = hslvalues(token); return hsl2rgbvalues(h, s, l, a); } function cmyk2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const c = getNumber(t); - // @ts-ignore - t = components[1]; - // @ts-ignore - const m = getNumber(t); - // @ts-ignore - t = components[2]; - // @ts-ignore - const y = getNumber(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const k = getNumber(t); - const rgb = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) - ]; - // @ts-ignore - if (token.chi.length >= 9) { - // @ts-ignore - t = token.chi[8]; - // @ts-ignore - rgb.push(Math.round(255 * getNumber(t))); - } - return rgb; + return cmyk2srgb(token).map(srgb2rgb); } function oklab2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t); - // @ts-ignore - t = components[1]; - // @ts-ignore - const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - const rgb = OKLab_to_sRGB(l, a, b).map(v => { - return Math.round(255 * v); - }); - if (alpha != 1) { - rgb.push(Math.round(255 * alpha)); - } - return rgb.map(((value) => minmax(value, 0, 255))); + return oklab2srgb(token).map(srgb2rgb); } function oklch2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t); - // @ts-ignore - t = components[1]; - // @ts-ignore - const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const h = getAngle(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(Math.round(255 * value), 0, 255))); + return oklch2srgb(token).map(srgb2rgb); } function lab2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(Math.round(value * 255), 0, 255))); + return lab2srgb(token).map(srgb2rgb); } function lch2rgb(token) { - const components = getComponents(token); - // @ts-ignore - let t = components[0]; - // @ts-ignore - const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); - // @ts-ignore - t = components[1]; - // @ts-ignore - const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); - // @ts-ignore - t = components[2]; - // @ts-ignore - const h = getAngle(t); - // @ts-ignore - t = components[3]; - // @ts-ignore - const alpha = t == null ? 1 : getNumber(t); - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const a = c * Math.cos(360 * h * Math.PI / 180); - const b = c * Math.sin(360 * h * Math.PI / 180); - const rgb = Lab_to_sRGB(l, a, b); - // - if (alpha != 1) { - rgb.push(alpha); - } - return rgb.map(((value) => minmax(value * 255, 0, 255))); + return lch2srgb(token).map(srgb2rgb); } function hslvalues(token) { const components = getComponents(token); @@ -240,4 +122,4 @@ function hsl2rgbvalues(h, s, l, a = null) { return values; } -export { cmyk2rgb, hsl2rgb, hsl2rgbvalues, hslvalues, hwb2rgb, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb }; +export { cmyk2rgb, hex2rgb, hsl2rgb, hsl2rgbvalues, hslvalues, hwb2rgb, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb }; diff --git a/dist/lib/renderer/color/srgb.js b/dist/lib/renderer/color/srgb.js index 3685042..e4088b9 100644 --- a/dist/lib/renderer/color/srgb.js +++ b/dist/lib/renderer/color/srgb.js @@ -1,13 +1,243 @@ import { roundWithPrecision } from './utils/round.js'; -import '../../ast/types.js'; +import './utils/constants.js'; +import { getComponents } from './utils/components.js'; +import { getNumber, getAngle } from './color.js'; +import { EnumToken } from '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import './color.js'; +import { Lab_to_sRGB } from './lab.js'; +import { OKLab_to_sRGB } from './oklab.js'; import '../sourcemap/lib/encode.js'; // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 +function hwb2srgb(token) { + const { h: hue, s: white, l: black, a: alpha } = hslvalues(token); + const rgb = hsl2srgbvalues(hue, 1, .5); + for (let i = 0; i < 3; i++) { + rgb[i] *= (1 - white - black); + rgb[i] = rgb[i] + white; + } + if (alpha != null && alpha != 1) { + rgb.push(alpha); + } + return rgb; +} +function cmyk2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const c = getNumber(t); + // @ts-ignore + t = components[1]; + // @ts-ignore + const m = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + const y = getNumber(t); + // @ts-ignore + t = components[3]; + // @ts-ignore + const k = getNumber(t); + const rgb = [ + 1 - Math.min(1, c * (1 - k) + k), + 1 - Math.min(1, m * (1 - k) + k), + 1 - Math.min(1, y * (1 - k) + k) + ]; + // @ts-ignore + if (token.chi.length >= 9) { + // @ts-ignore + t = token.chi[8]; + // @ts-ignore + rgb.push(getNumber(t)); + } + return rgb; +} +function oklab2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t); + // @ts-ignore + t = components[1]; + // @ts-ignore + const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + const rgb = OKLab_to_sRGB(l, a, b); + if (alpha != 1 && alpha != null) { + rgb.push(alpha); + } + return rgb; //.map(((value: number) => minmax(value, 0, 255))); +} +function oklch2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const rgb = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; //.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); +} +function hslvalues(token) { + const components = getComponents(token); + let t; + // @ts-ignore + let h = getAngle(components[0]); + // @ts-ignore + t = components[1]; + // @ts-ignore + let s = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + let l = getNumber(t); + let a = null; + if (token.chi?.length == 4) { + // @ts-ignore + t = token.chi[3]; + // @ts-ignore + if ((t.typ == EnumToken.IdenTokenType && t.val == 'none') || (t.typ == EnumToken.PercentageTokenType && +t.val < 100) || + // @ts-ignore + (t.typ == EnumToken.NumberTokenType && t.val < 1)) { + // @ts-ignore + a = getNumber(t); + } + } + return a == null ? { h, s, l } : { h, s, l, a }; +} +function hsl2srgbvalues(h, s, l, a = null) { + let v = l <= .5 ? l * (1.0 + s) : l + s - l * s; + let r = l; + let g = l; + let b = l; + if (v > 0) { + let m = l + l - v; + let sv = (v - m) / v; + h *= 6.0; + let sextant = Math.floor(h); + let fract = h - sextant; + let vsf = v * sv * fract; + let mid1 = m + vsf; + let mid2 = v - vsf; + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + const values = [r, g, b]; + if (a != null && a != 1) { + values.push(a); + } + return values; +} +function lab2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const a = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const b = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; +} +function lch2srgb(token) { + const components = getComponents(token); + // @ts-ignore + let t = components[0]; + // @ts-ignore + const l = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); + // @ts-ignore + t = components[1]; + // @ts-ignore + const c = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); + // @ts-ignore + t = components[2]; + // @ts-ignore + const h = getAngle(t); + // @ts-ignore + t = components[3]; + // @ts-ignore + const alpha = t == null ? 1 : getNumber(t); + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const a = c * Math.cos(360 * h * Math.PI / 180); + const b = c * Math.sin(360 * h * Math.PI / 180); + const rgb = Lab_to_sRGB(l, a, b); + // + if (alpha != 1) { + rgb.push(alpha); + } + return rgb; +} function gam_sRGB(r, g, b) { // convert an array of linear-light sRGB values in the range 0.0-1.0 // to gamma corrected form @@ -76,4 +306,4 @@ function lin_2020(r, g, b) { }); } -export { gam_sRGB, lin_2020, lin_ProPhoto, lin_a98rgb }; +export { cmyk2srgb, gam_sRGB, hsl2srgbvalues, hslvalues, hwb2srgb, lab2srgb, lch2srgb, lin_2020, lin_ProPhoto, lin_a98rgb, oklab2srgb, oklch2srgb }; diff --git a/dist/lib/renderer/color/utils/constants.js b/dist/lib/renderer/color/utils/constants.js index 3e1bee6..8b9c432 100644 --- a/dist/lib/renderer/color/utils/constants.js +++ b/dist/lib/renderer/color/utils/constants.js @@ -1,3 +1,160 @@ const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; +// name to color +const COLORS_NAMES = Object.seal({ + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', + 'rebeccapurple': '#663399', + 'transparent': '#00000000' +}); +// color to name +const NAMES_COLORS = Object.seal(Object.entries(COLORS_NAMES).reduce((acc, [key, value]) => { + acc[value] = key; + return acc; +}, Object.create(null))); -export { D50 }; +export { COLORS_NAMES, D50, NAMES_COLORS }; diff --git a/dist/lib/renderer/color/xyz.js b/dist/lib/renderer/color/xyz.js index 00c8f5b..cb9be70 100644 --- a/dist/lib/renderer/color/xyz.js +++ b/dist/lib/renderer/color/xyz.js @@ -1,7 +1,7 @@ +import './utils/constants.js'; import '../../ast/types.js'; import '../../ast/minify.js'; import '../../parser/parse.js'; -import './color.js'; import { gam_sRGB } from './srgb.js'; import '../sourcemap/lib/encode.js'; diff --git a/dist/lib/renderer/render.js b/dist/lib/renderer/render.js index b1c81d3..31f87e5 100644 --- a/dist/lib/renderer/render.js +++ b/dist/lib/renderer/render.js @@ -1,11 +1,13 @@ -import { getAngle, getNumber, clampValues, clamp, COLORS_NAMES } from './color/color.js'; +import { getAngle, getNumber, clampValues, clamp } from './color/color.js'; +import { COLORS_NAMES } from './color/utils/constants.js'; +import { getComponents } from './color/utils/components.js'; +import { reduceHexValue, rgb2hex, hsl2hex, hwb2hex, cmyk2hex, oklab2hex, oklch2hex, lab2hex, lch2hex } from './color/hex.js'; import { EnumToken } from '../ast/types.js'; import '../ast/minify.js'; import { expand } from '../ast/expand.js'; import { gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto } from './color/srgb.js'; -import { reduceHexValue, rgb2hex, hsl2hex, hwb2hex, cmyk2hex, oklab2hex, oklch2hex, lab2hex, lch2hex } from './color/hex.js'; -import { XYZ_to_sRGB } from './color/xyz.js'; import { colorMix } from './color/colormix.js'; +import { XYZ_to_sRGB } from './color/xyz.js'; import { parseRelativeColor } from './color/relativecolor.js'; import { XYZ_D65_to_sRGB } from './color/xyzd65.js'; import { SourceMap } from './sourcemap/sourcemap.js'; @@ -321,11 +323,9 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, return reduceHexValue(value); } } - else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb'].includes(token.val)) { - const chi = token.chi.filter((x) => ![ - EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType - ].includes(x.typ)); - const components = parseRelativeColor(token.val.split(''), chi[1], chi[2], chi[3], chi[4], chi[5]); + else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'].includes(token.val)) { + const chi = getComponents(token); + const components = parseRelativeColor(token.val, chi[1], chi[2], chi[3], chi[4], chi[5]); if (components != null) { token.chi = Object.values(components); delete token.cal; @@ -374,6 +374,7 @@ function renderToken(token, options = {}, cache = Object.create(null), reducer, return 'currentcolor'; } clamp(token); + // console.error({token}); if (Array.isArray(token.chi) && token.chi.some((t) => t.typ == EnumToken.FunctionTokenType || (t.typ == EnumToken.ColorTokenType && Array.isArray(t.chi)))) { return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc, curr) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')'; } diff --git a/dist/node/index.js b/dist/node/index.js index b81d08c..d090a0a 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -6,7 +6,7 @@ import { doRender } from '../lib/renderer/render.js'; export { renderToken } from '../lib/renderer/render.js'; import { doParse } from '../lib/parser/parse.js'; export { parseString, parseTokens } from '../lib/parser/parse.js'; -import '../lib/renderer/color/color.js'; +import '../lib/renderer/color/utils/constants.js'; import { resolve, dirname } from '../lib/fs/resolve.js'; import { load } from './load.js'; diff --git a/dist/web/index.js b/dist/web/index.js index d2a84e0..212a8ca 100644 --- a/dist/web/index.js +++ b/dist/web/index.js @@ -6,7 +6,7 @@ import { doRender } from '../lib/renderer/render.js'; export { renderToken } from '../lib/renderer/render.js'; import { doParse } from '../lib/parser/parse.js'; export { parseString, parseTokens } from '../lib/parser/parse.js'; -import '../lib/renderer/color/color.js'; +import '../lib/renderer/color/utils/constants.js'; import { resolve, dirname } from '../lib/fs/resolve.js'; import { load } from './load.js'; diff --git a/src/@types/token.d.ts b/src/@types/token.d.ts index 2a06e7f..4c8e850 100644 --- a/src/@types/token.d.ts +++ b/src/@types/token.d.ts @@ -341,7 +341,7 @@ export declare interface ImportantToken extends BaseToken { typ: EnumToken.ImportantTokenType; } -export declare type ColorKind = 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk'; +export declare type ColorKind = 'lit' | 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla' | 'hwb' | 'device-cmyk' | 'oklab' | 'oklch' | 'lab' | 'lch'; // xyz-d65 is an alias for xyz // display-p3 is an alias for srgb export declare type ColorSpace = diff --git a/src/lib/renderer/color/color.ts b/src/lib/renderer/color/color.ts index 4c44983..b0d3ac3 100644 --- a/src/lib/renderer/color/color.ts +++ b/src/lib/renderer/color/color.ts @@ -1,221 +1,208 @@ import {AngleToken, ColorSpace, ColorToken, IdentToken, NumberToken, PercentageToken, Token} from "../../../@types"; import {EnumToken} from "../../ast"; -import {hsl2rgb} from "./rgb"; -import {expandHexValue} from "./hex"; - -// name to color -export const COLORS_NAMES: { [key: string]: string } = Object.seal({ - 'aliceblue': '#f0f8ff', - 'antiquewhite': '#faebd7', - 'aqua': '#00ffff', - 'aquamarine': '#7fffd4', - 'azure': '#f0ffff', - 'beige': '#f5f5dc', - 'bisque': '#ffe4c4', - 'black': '#000000', - 'blanchedalmond': '#ffebcd', - 'blue': '#0000ff', - 'blueviolet': '#8a2be2', - 'brown': '#a52a2a', - 'burlywood': '#deb887', - 'cadetblue': '#5f9ea0', - 'chartreuse': '#7fff00', - 'chocolate': '#d2691e', - 'coral': '#ff7f50', - 'cornflowerblue': '#6495ed', - 'cornsilk': '#fff8dc', - 'crimson': '#dc143c', - 'cyan': '#00ffff', - 'darkblue': '#00008b', - 'darkcyan': '#008b8b', - 'darkgoldenrod': '#b8860b', - 'darkgray': '#a9a9a9', - 'darkgrey': '#a9a9a9', - 'darkgreen': '#006400', - 'darkkhaki': '#bdb76b', - 'darkmagenta': '#8b008b', - 'darkolivegreen': '#556b2f', - 'darkorange': '#ff8c00', - 'darkorchid': '#9932cc', - 'darkred': '#8b0000', - 'darksalmon': '#e9967a', - 'darkseagreen': '#8fbc8f', - 'darkslateblue': '#483d8b', - 'darkslategray': '#2f4f4f', - 'darkslategrey': '#2f4f4f', - 'darkturquoise': '#00ced1', - 'darkviolet': '#9400d3', - 'deeppink': '#ff1493', - 'deepskyblue': '#00bfff', - 'dimgray': '#696969', - 'dimgrey': '#696969', - 'dodgerblue': '#1e90ff', - 'firebrick': '#b22222', - 'floralwhite': '#fffaf0', - 'forestgreen': '#228b22', - 'fuchsia': '#ff00ff', - 'gainsboro': '#dcdcdc', - 'ghostwhite': '#f8f8ff', - 'gold': '#ffd700', - 'goldenrod': '#daa520', - 'gray': '#808080', - 'grey': '#808080', - 'green': '#008000', - 'greenyellow': '#adff2f', - 'honeydew': '#f0fff0', - 'hotpink': '#ff69b4', - 'indianred': '#cd5c5c', - 'indigo': '#4b0082', - 'ivory': '#fffff0', - 'khaki': '#f0e68c', - 'lavender': '#e6e6fa', - 'lavenderblush': '#fff0f5', - 'lawngreen': '#7cfc00', - 'lemonchiffon': '#fffacd', - 'lightblue': '#add8e6', - 'lightcoral': '#f08080', - 'lightcyan': '#e0ffff', - 'lightgoldenrodyellow': '#fafad2', - 'lightgray': '#d3d3d3', - 'lightgrey': '#d3d3d3', - 'lightgreen': '#90ee90', - 'lightpink': '#ffb6c1', - 'lightsalmon': '#ffa07a', - 'lightseagreen': '#20b2aa', - 'lightskyblue': '#87cefa', - 'lightslategray': '#778899', - 'lightslategrey': '#778899', - 'lightsteelblue': '#b0c4de', - 'lightyellow': '#ffffe0', - 'lime': '#00ff00', - 'limegreen': '#32cd32', - 'linen': '#faf0e6', - 'magenta': '#ff00ff', - 'maroon': '#800000', - 'mediumaquamarine': '#66cdaa', - 'mediumblue': '#0000cd', - 'mediumorchid': '#ba55d3', - 'mediumpurple': '#9370d8', - 'mediumseagreen': '#3cb371', - 'mediumslateblue': '#7b68ee', - 'mediumspringgreen': '#00fa9a', - 'mediumturquoise': '#48d1cc', - 'mediumvioletred': '#c71585', - 'midnightblue': '#191970', - 'mintcream': '#f5fffa', - 'mistyrose': '#ffe4e1', - 'moccasin': '#ffe4b5', - 'navajowhite': '#ffdead', - 'navy': '#000080', - 'oldlace': '#fdf5e6', - 'olive': '#808000', - 'olivedrab': '#6b8e23', - 'orange': '#ffa500', - 'orangered': '#ff4500', - 'orchid': '#da70d6', - 'palegoldenrod': '#eee8aa', - 'palegreen': '#98fb98', - 'paleturquoise': '#afeeee', - 'palevioletred': '#d87093', - 'papayawhip': '#ffefd5', - 'peachpuff': '#ffdab9', - 'peru': '#cd853f', - 'pink': '#ffc0cb', - 'plum': '#dda0dd', - 'powderblue': '#b0e0e6', - 'purple': '#800080', - 'red': '#ff0000', - 'rosybrown': '#bc8f8f', - 'royalblue': '#4169e1', - 'saddlebrown': '#8b4513', - 'salmon': '#fa8072', - 'sandybrown': '#f4a460', - 'seagreen': '#2e8b57', - 'seashell': '#fff5ee', - 'sienna': '#a0522d', - 'silver': '#c0c0c0', - 'skyblue': '#87ceeb', - 'slateblue': '#6a5acd', - 'slategray': '#708090', - 'slategrey': '#708090', - 'snow': '#fffafa', - 'springgreen': '#00ff7f', - 'steelblue': '#4682b4', - 'tan': '#d2b48c', - 'teal': '#008080', - 'thistle': '#d8bfd8', - 'tomato': '#ff6347', - 'turquoise': '#40e0d0', - 'violet': '#ee82ee', - 'wheat': '#f5deb3', - 'white': '#ffffff', - 'whitesmoke': '#f5f5f5', - 'yellow': '#ffff00', - 'yellowgreen': '#9acd32', - 'rebeccapurple': '#663399', - 'transparent': '#00000000' -}); - -// color to name -export const NAMES_COLORS: { [key: string]: string } = Object.seal(Object.entries(COLORS_NAMES).reduce((acc: { - [key: string]: string -}, [key, value]) => { - - acc[value] = key; - return acc; - -}, Object.create(null))); - -export function convert(token: ColorToken, to: 'rgb'): ColorToken | null { - - if (to == 'rgb') { +import {hex2rgb, hsl2rgb, hwb2rgb, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb} from "./rgb"; +import {hex2hsl, hwb2hsl, lab2hsl, lch2hsl, oklab2hsl, oklch2hsl, rgb2hsl} from "./hsl"; +import {hsl2hwb, lab2hwb, lch2hwb, oklab2hwb, oklch2hwb, rgb2hwb} from "./hwb"; + +export function convert(token: ColorToken, to: string): ColorToken | null { + + if (token.kin == to) { + + return token; + } + + // console.error({token, to}); + + let values: number[] = []; + let chi: Token[]; + + if (to == 'hsl') { switch (token.kin) { case 'rgb': case 'rgba': - return token; + values.push(...rgb2hsl(token)); + break; + case 'hex': + case 'lit': + + values.push(...hex2hsl(token)); + break; + case 'hwb': + + values.push(...hwb2hsl(token)); + break; + + case 'oklab': + + values.push(...oklab2hsl(token)); + break; + + case 'oklch': + + values.push(...oklch2hsl(token)); + break; + + case 'lab': + + values.push(...lab2hsl(token)); + break; + + case 'lch': + + values.push(...lch2hsl(token)); + break; + } + + if (values.length > 0) { + + + chi = [ + + {typ: EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg'}, + {typ: EnumToken.PercentageTokenType, val: String(values[1] * 100)}, + {typ: EnumToken.PercentageTokenType, val: String(values[2] * 100)}, + ]; + + if (values.length == 4) { + + chi.push({typ: EnumToken.PercentageTokenType, val: String(values[3] * 100)}); + } + + return { + typ: EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + } + } + } + + else if (to == 'hwb') { + + switch (token.kin) { + + case 'rgb': + case 'rgba': + + values.push(...rgb2hwb(token)); + break; + case 'hex': + case 'lit': + + values.push(...hex2hsl(token)); + break; case 'hsl': case 'hsla': - const children: Token[] = (token.chi).filter(c => [EnumToken.PercentageTokenType, EnumToken.NumberTokenType, EnumToken.IdenTokenType].includes(c.typ)); + values.push(...hsl2hwb(token)); + break; - let values: number[] = children.slice(0, 3).map((c: Token) => getNumber(c)); + case 'oklab': - if (children.length == 4) { + values.push(...oklab2hwb(token)); + break; - values.push(children[3].typ == EnumToken.IdenTokenType && (children[3]).val == 'none' ? 1 : getNumber(children[3])); - } + case 'oklch': - return { + values.push(...oklch2hwb(token)); + break; - typ: EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - // @ts-ignore - chi: hsl2rgb(...values).map((v: number) => ({ - typ: EnumToken.NumberTokenType, - val: String(v) - })) - } + case 'lab': + + values.push(...lab2hwb(token)); + break; + + case 'lch': + + values.push(...lch2hwb(token)); + break; + } + + if (values.length > 0) { + + chi = [ + + {typ: EnumToken.AngleTokenType, val: String(values[0] * 360), unit: 'deg'}, + {typ: EnumToken.PercentageTokenType, val: String(values[1] * 100)}, + {typ: EnumToken.PercentageTokenType, val: String(values[2] * 100)}, + ]; + + if (values.length == 4) { + + chi.push({typ: EnumToken.PercentageTokenType, val: String(values[3] * 100)}); + } + + return { + typ: EnumToken.ColorTokenType, + val: 'hsl', + chi, + kin: 'hsl' + } + } + } + + else if (to == 'rgb') { + + switch (token.kin) { case 'hex': case 'lit': - const value: string = token.kin == 'hex' ? expandHexValue(token.val) : COLORS_NAMES[token.val]; + values.push(...hex2rgb(token)); + break + case 'hsl': - return { + values.push(...hsl2rgb(token)); + break + case 'hwb': - typ: EnumToken.ColorTokenType, - kin: 'rgb', - val: 'rgb', - chi: (value.slice(1).match(/([a-fA-F0-9]{2})/g)).map((v: string) => ({ + values.push(...hwb2rgb(token)); + break; - typ: EnumToken.NumberTokenType, - val: String(parseInt(v, 16)) - })) - } + case 'oklab': + + values.push(...oklab2rgb(token)); + break; + + case 'oklch': + + values.push(...oklch2rgb(token)); + break; + + case 'lab': + + values.push(...lab2rgb(token)); + break; + + case 'lch': + + values.push(...lch2rgb(token)); + break; + } + + if (values.length > 0) { + + chi = [ + + {typ: EnumToken.NumberTokenType, val: String(values[0] )}, + {typ: EnumToken.NumberTokenType, val: String(values[1])}, + {typ: EnumToken.NumberTokenType, val: String(values[2])}, + ]; + + if (values.length == 4) { + + chi.push({typ: EnumToken.PercentageTokenType, val: String(values[3] * 100)}); + } + + return { + typ: EnumToken.ColorTokenType, + val: 'rgb', + chi, + kin: 'rgb' + } } } @@ -255,7 +242,7 @@ export function clamp(token: ColorToken): ColorToken { } else if (token.typ == EnumToken.PercentageTokenType) { - token.val = String(minmax(+token.val, 0, 100))// String(Math.min(100, Math.max(0, +token.val))); + token.val = String(minmax(+token.val, 0, 100))// String(Math.min(100, Math.max(0, +token.val))); } } }); @@ -269,7 +256,7 @@ export function clampValues(values: number[], colorSpace: ColorSpace): number[] switch (colorSpace) { case 'srgb': - case 'oklab': + // case 'oklab': case 'display-p3': case 'srgb-linear': // case 'prophoto-rgb': diff --git a/src/lib/renderer/color/hex.ts b/src/lib/renderer/color/hex.ts index f632a82..5540a98 100644 --- a/src/lib/renderer/color/hex.ts +++ b/src/lib/renderer/color/hex.ts @@ -1,7 +1,8 @@ import {ColorToken, NumberToken, PercentageToken} from "../../../@types"; import {EnumToken} from "../../ast"; -import {getNumber, NAMES_COLORS} from "./color"; +import {getNumber} from "./color"; import {cmyk2rgb, hsl2rgb, hwb2rgb, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb} from "./rgb"; +import {NAMES_COLORS} from "./utils"; function toHexString(acc: string, value: number): string { @@ -91,6 +92,8 @@ export function hsl2hex(token: ColorToken) { export function hwb2hex(token: ColorToken): string { + // console.error(hwb2rgb(token)); + return `${hwb2rgb(token).reduce(toHexString, '#')}`; } diff --git a/src/lib/renderer/color/hsl.ts b/src/lib/renderer/color/hsl.ts index 0ece48d..8bfdcf0 100644 --- a/src/lib/renderer/color/hsl.ts +++ b/src/lib/renderer/color/hsl.ts @@ -1,16 +1,18 @@ import {hwb2hsv} from "./hsv"; import {ColorToken, IdentToken, NumberToken, PercentageToken, Token} from "../../../@types"; import {getNumber} from "./color"; -import {hex2rgb, hslvalues, lab2rgb, lch2rgb, oklab2rgb} from "./rgb"; +import {hex2rgb, hslvalues, lab2rgb, lch2rgb, oklab2rgb, oklch2rgb} from "./rgb"; import {getComponents} from "./utils"; +import {eq} from "../../parser/utils/eq"; +import {EnumToken} from "../../ast"; -export function hex2hsl(token: ColorToken): [number, number, number, number | null] { +export function hex2hsl(token: ColorToken): number[] { // @ts-ignore return rgb2hslvalues(...hex2rgb(token)); } -export function rgb2hsl(token: ColorToken): [number, number, number, number | null] { +export function rgb2hsl(token: ColorToken): number[] { const chi: Token[] = getComponents(token); @@ -35,18 +37,28 @@ export function rgb2hsl(token: ColorToken): [number, number, number, number | nu // @ts-ignore let a: number = null; - if (t != null) { + if (t != null && !eq(t, {typ: EnumToken.IdenTokenType, val: 'none'})) { // @ts-ignore a = getNumber(t) / 255; } - return rgb2hslvalues(r, g, b, a); + const values: number[] = [r, g, b]; + + if (a != null && a != 1) { + + values.push(a); + } + + + // @ts-ignore + return rgb2hslvalues(...values); } // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -export function hsv2hsl(h: number, s: number, v: number, a?: number): [number, number, number, number | null] { - return [ +export function hsv2hsl(h: number, s: number, v: number, a?: number): number[] { + + const result = [ //[hue, saturation, lightness] //Range should be between 0 - 1 h, //Hue stays the same @@ -58,10 +70,13 @@ export function hsv2hsl(h: number, s: number, v: number, a?: number): [number, n s * v / ((h = (2 - s) * v) < 1 ? h : 2 - h), h / 2, //Lightness is (2-sat)*val/2 - //See reassignment of hue above, - // @ts-ignore - a - ] + ]; + + if (a != null) { + result.push(a); + } + + return result; } @@ -71,25 +86,31 @@ export function hwb2hsl(token: ColorToken): [number, number, number, number] { return hsv2hsl(...hwb2hsv(...Object.values(hslvalues(token)))); } -export function lab2hsl(token: ColorToken): [number, number, number, number | null] { +export function lab2hsl(token: ColorToken): number[] { // @ts-ignore return rgb2hslvalues(...lab2rgb(token)); } -export function lch2hsl(token: ColorToken): [number, number, number, number | null] { +export function lch2hsl(token: ColorToken): number[] { // @ts-ignore return rgb2hslvalues(...lch2rgb(token)); } -export function oklab2hsl(token: ColorToken): [number, number, number, number | null] { +export function oklab2hsl(token: ColorToken): number[] { // @ts-ignore return rgb2hslvalues(...oklab2rgb(token)); } -export function rgb2hslvalues(r: number, g: number, b: number, a: number | null = null): [number, number, number, number | null] { +export function oklch2hsl(token: ColorToken): number[] { + + // @ts-ignore + return rgb2hslvalues(...oklch2rgb(token)); +} + +export function rgb2hslvalues(r: number, g: number, b: number, a: number | null = null): number[] { r /= 255; g /= 255; diff --git a/src/lib/renderer/color/hsv.ts b/src/lib/renderer/color/hsv.ts index b662bbc..ae7bdc9 100644 --- a/src/lib/renderer/color/hsv.ts +++ b/src/lib/renderer/color/hsv.ts @@ -8,14 +8,20 @@ export function hwb2hsv(h: number, w: number, b: number, a?: number): [number, n // https://gist.github.com/defims/0ca2ef8832833186ed396a2f8a204117#file-annotated-js -export function hsl2hsv(h: number,s: number,l: number): [number, number, number] { +export function hsl2hsv(h: number,s: number,l: number, a: number | null = null): number[] { s*=l<.5?l:1-l; - return[ //[hue, saturation, value] + const result: number[] = [ //[hue, saturation, value] //Range should be between 0 - 1 h, //Hue stays the same 2*s/(l+s), //Saturation l+s //Value - ] + ]; + + if (a != null) { + result.push(a); + } + + return result; } diff --git a/src/lib/renderer/color/hwb.ts b/src/lib/renderer/color/hwb.ts index 1325b59..99f693b 100644 --- a/src/lib/renderer/color/hwb.ts +++ b/src/lib/renderer/color/hwb.ts @@ -1,4 +1,65 @@ import {hsl2hsv} from "./hsv"; +import {AngleToken, ColorToken, IdentToken, NumberToken, PercentageToken, Token} from "../../../@types"; +import {getComponents} from "./utils"; +import {getAngle, getNumber} from "./color"; +import {EnumToken} from "../../ast"; +import {eq} from "../../parser/utils/eq"; +import {lab2srgb, lch2srgb, oklab2srgb, oklch2srgb} from "./srgb"; + +export function rgb2hwb(token: ColorToken): number[] { + + // @ts-ignore + return srgb2hwbvalues(...getComponents(token).map((t: Token, index: number): number => { + + if (index == 3 && eq(t, {typ: EnumToken.IdenTokenType, val: 'none'})) { + return 1; + } + + return getNumber(t) / 255; + })); +} + +export function hsl2hwb(token: ColorToken): number[] { + + // @ts-ignore + return hsl2hwbvalues(...getComponents(token).map((t: Token, index: number) => { + + if (index == 3 && eq(t, {typ: EnumToken.IdenTokenType, val: 'none'})) { + return 1; + } + + if (index == 0) { + + return getAngle(t); + } + + return getNumber(t); + })); +} + +export function lab2hwb(token: ColorToken): number[] { + + // @ts-ignore + return srgb2hwbvalues(...lab2srgb(token)); +} + +export function lch2hwb(token: ColorToken): number[] { + + // @ts-ignore + return srgb2hwbvalues(...lch2srgb(token)); +} + +export function oklab2hwb(token: ColorToken): number[] { + + // @ts-ignore + return srgb2hwbvalues(...oklab2srgb(token)); +} + +export function oklch2hwb(token: ColorToken): number[] { + + // @ts-ignore + return srgb2hwbvalues(...oklch2srgb(token)); +} function rgb2hue(r: number, g: number, b: number, fallback: number = 0) { @@ -37,11 +98,11 @@ function rgb2whiteness(r: number, g: number, b: number): number { return Math.min(r, g, b); } -export function rgb2hwb(r: number, g: number, b: number, a: number | null = null, fallback: number = 0): number[] { +export function srgb2hwbvalues(r: number, g: number, b: number, a: number | null = null, fallback: number = 0): number[] { - r *= 100 / 255; - g *= 100 / 255; - b *= 100 / 255; + r *= 100; + g *= 100; + b *= 100; let hue: number = rgb2hue(r, g, b, fallback); let whiteness: number = rgb2whiteness(r, g, b); @@ -58,12 +119,19 @@ export function rgb2hwb(r: number, g: number, b: number, a: number | null = null } -export function hsv2hwb(h: number, s: number, v: number): number[] { +export function hsv2hwb(h: number, s: number, v: number, a: number | null = null): number[] { + + const result: number[] = [h, (1 - s) * v, 1 - v]; - return [h, (1 - s) * v, 1 - v]; + if (a != null) { + result.push(a); + } + + return result; } -export function hsl2hwb(h: number, s: number, l: number): number[] { +export function hsl2hwbvalues(h: number, s: number, l: number, a: number | null = null): number[] { - return hsv2hwb(...hsl2hsv(h, s, l)); + // @ts-ignore + return hsv2hwb(...hsl2hsv(h, s, l, a)); } \ No newline at end of file diff --git a/src/lib/renderer/color/index.ts b/src/lib/renderer/color/index.ts index be7e4cd..e36d9a7 100644 --- a/src/lib/renderer/color/index.ts +++ b/src/lib/renderer/color/index.ts @@ -12,4 +12,6 @@ export * from './xyz'; export * from './lab'; // export * from './lch'; export * from './relativecolor'; -export * from './xyzd65'; \ No newline at end of file +export * from './xyzd65'; +export {NAMES_COLORS} from "./utils"; +export {COLORS_NAMES} from "./utils"; \ No newline at end of file diff --git a/src/lib/renderer/color/relativecolor.ts b/src/lib/renderer/color/relativecolor.ts index 83aada5..090a069 100644 --- a/src/lib/renderer/color/relativecolor.ts +++ b/src/lib/renderer/color/relativecolor.ts @@ -1,20 +1,29 @@ -import {AngleToken, ColorToken, IdentToken, NumberToken, PercentageToken, Token} from "../../../@types"; -import {COLORS_NAMES, getAngle, getNumber} from "./color"; +import {ColorToken, Token} from "../../../@types"; +import {convert} from "./color"; import {EnumToken, walkValues} from "../../ast"; import {reduceNumber} from "../render"; -import {hsl2hwb, rgb2hwb} from "./hwb"; -import {hwb2hsl, rgb2hsl} from "./hsl"; -import {hsl2rgb, hwb2rgb} from "./rgb"; import {evaluate} from "../../ast/math"; +import {eq} from "../../parser/utils/eq"; type RGBKeyType = 'r' | 'g' | 'b' | 'alpha'; type HSLKeyType = 'h' | 's' | 'l' | 'alpha'; type HWBKeyType = 'h' | 'w' | 'b' | 'alpha'; +type LABKeyType = 'l' | 'a' | 'b' | 'alpha'; +type OKLABKeyType = 'l' | 'a' | 'b' | 'alpha'; +type LCHKeyType = 'l' | 'c' | 'h' | 'alpha'; +type OKLCHKeyType = 'l' | 'c' | 'h' | 'alpha'; + +export type RelativeColorTypes = + RGBKeyType + | HSLKeyType + | HWBKeyType + | LABKeyType + | OKLABKeyType + | LCHKeyType + | OKLCHKeyType; + +export function parseRelativeColor(relativeKeys: string, original: ColorToken, rExp: Token, gExp: Token, bExp: Token, aExp: Token | null): Record | null { -export type RelativeColorTypes = RGBKeyType | HSLKeyType | HWBKeyType; -export function parseRelativeColor(relativeKeys: RelativeColorTypes[], original: ColorToken, rExp: Token, gExp: Token, bExp: Token, aExp: Token | null): Record | null { - - const type: 'rgb' | 'hsl' | 'hwb' = <'rgb' | 'hsl' | 'hwb'>relativeKeys.join(''); let r: number | Token; let g: number | Token; let b: number | Token; @@ -22,208 +31,38 @@ export function parseRelativeColor(relativeKeys: RelativeColorTypes[], original: let keys: Record = >{}; let values: Record = >{}; - let children: Token[]; - - switch (original.kin) { - - case 'lit': - case 'hex': - - let value: string = original.val.toLowerCase(); - - if (original.kin == 'lit') { - - if (original.val.toLowerCase() in COLORS_NAMES) { - - value = COLORS_NAMES[original.val.toLowerCase()]; - } else { - - return null; - } - } - - if (value.length == 4) { - - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3]; - } else if (value.length == 5) { - - value = '#' + value[1] + value[1] + value[2] + value[2] + value[3] + value[3] + value[4] + value[4]; - } - - r = parseInt(value.slice(1, 3), 16); - g = parseInt(value.slice(3, 5), 16); - b = parseInt(value.slice(5, 7), 16); - alpha = value.length == 9 ? parseInt(value.slice(7, 9), 16) : null; - break; - - case 'rgb': - case 'rgba': - - children = (original.chi).filter((t: Token) => t.typ == EnumToken.NumberTokenType || t.typ == EnumToken.IdenTokenType || t.typ == EnumToken.PercentageTokenType); - - if (children.every((t: Token) => (t.typ == EnumToken.IdenTokenType && t.val == 'none') || t.typ == EnumToken.NumberTokenType)) { - - r = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : +(children)[0].val; - g = children[1].typ == EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : +(children)[1].val; - b = children[2].typ == EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : +(children)[2].val; - alpha = children.length < 4 ? null : children[3].typ == EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +(children)[3].val; - - } else if (children.every((t: Token) => t.typ == EnumToken.PercentageTokenType || (t.typ == EnumToken.IdenTokenType && t.val == 'none') || (t.typ == EnumToken.NumberTokenType && t.val == '0'))) { - - // @ts-ignore - r = children[0].typ == EnumToken.IdenTokenType && children[0].val == 'none' ? 0 : (children)[0].val * 255 / 100; - // @ts-ignore - g = children[1].typ == EnumToken.IdenTokenType && children[1].val == 'none' ? 0 : (children)[1].val * 255 / 100; - // @ts-ignore - b = children[2].typ == EnumToken.IdenTokenType && children[2].val == 'none' ? 0 : (children)[2].val * 255 / 100; - alpha = children.length < 4 ? null : children[3].typ == EnumToken.IdenTokenType && children[3].val == 'none' ? 0 : +(children)[3].val / 100; - - } else { - - return null; - } - - break; - - case 'hsl': - case 'hsla': - case 'hwb': - - children = (original.chi).filter((t: Token) => t.typ == EnumToken.AngleTokenType || t.typ == EnumToken.NumberTokenType || t.typ == EnumToken.IdenTokenType || t.typ == EnumToken.PercentageTokenType); - - if (children.length == 3 || children.length == 4) { - - [r, g, b, alpha] = children; - } else { - - return null; - } - - break; - - default: - return null; - } - - const from: string = ['rgb', 'rgba', 'hex', 'lit'].includes(original.kin) ? 'rgb' : original.kin; - - if (from != type) { - - if (type == 'hsl' || type == 'hwb') { - - if (from == 'rgb') { - [r, g, b] = (type == 'hwb' ? rgb2hwb : rgb2hsl)(r, g, b); + const converted: ColorToken = convert(original, relativeKeys); - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; + if (converted == null) { - values = >{ - [relativeKeys[0]]: {typ: EnumToken.AngleTokenType, val: r, unit: 'deg'}, - [relativeKeys[1]]: {typ: EnumToken.PercentageTokenType, val: g}, - [relativeKeys[2]]: {typ: EnumToken.PercentageTokenType, val: b} - } ; - } else if (from == 'hwb' || from == 'hsl') { - - if (type == 'hsl') { - - if (from == 'hwb') { - - [r, g, b] = hwb2hsl(getAngle(r), getNumber(g), getNumber(b)); - - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - - // @ts-ignore - values = >{ - [relativeKeys[0]]: {typ: EnumToken.AngleTokenType, val: r, unit: 'deg'}, - [relativeKeys[1]]: {typ: EnumToken.PercentageTokenType, val: g}, - [relativeKeys[2]]: {typ: EnumToken.PercentageTokenType, val: b} - }; - } - } else if (type == 'hwb') { - - if (from == 'hsl') { - - [r, g, b] = hsl2hwb(getAngle(r), getNumber(g), getNumber(b)); - - // @ts-ignore - r *= 360; - // @ts-ignore - g *= 100; - // @ts-ignore - b *= 100; - - // @ts-ignore - values = >{ - [relativeKeys[0]]: {typ: EnumToken.AngleTokenType, val: r, unit: 'deg'}, - [relativeKeys[1]]: {typ: EnumToken.PercentageTokenType, val: g}, - [relativeKeys[2]]: {typ: EnumToken.PercentageTokenType, val: b} - }; - } - } - } else { - - return null; - } - - } else if (type == 'rgb') { - - if (from == 'hsl' || from == 'hwb') { - - [r, g, b] = (from == 'hwb' ? hwb2rgb : hsl2rgb)(original); - - // @ts-ignore - values = >{ - [relativeKeys[0]]: {typ: EnumToken.NumberTokenType, val: r}, - [relativeKeys[1]]: {typ: EnumToken.NumberTokenType, val: g}, - [relativeKeys[2]]: {typ: EnumToken.NumberTokenType, val: b} - }; - - } else { - - return null; - } - } - } else { - - values = >{ - [relativeKeys[0]]: r, - [relativeKeys[1]]: g, - [relativeKeys[2]]: b - }; + return null; } - if (aExp != null && aExp.typ == EnumToken.IdenTokenType && aExp.val == 'none') { - - aExp = null; - } + [r, g, b, alpha] = converted.chi; + + values = >{ + [relativeKeys[0]]: r, + [relativeKeys[1]]: g, + [relativeKeys[2]]: b, + // @ts-ignore + alpha: alpha == null || eq(alpha, { + typ: EnumToken.IdenTokenType, + val: 'none' + }) ? {typ: EnumToken.NumberTokenType, val: '1'} : alpha + }; keys = >{ [relativeKeys[0]]: rExp, [relativeKeys[1]]: gExp, [relativeKeys[2]]: bExp, - alpha: aExp ?? {typ: EnumToken.IdenTokenType, val: 'alpha'} + // @ts-ignore + alpha: aExp == null || eq(aExp, {typ: EnumToken.IdenTokenType, val: 'none'}) ? { + typ: EnumToken.NumberTokenType, + val: '1' + } : aExp }; - for (const [key, value] of Object.entries(values)) { - - if (typeof value == 'number') { - - values[key] = {typ: EnumToken.NumberTokenType, val: reduceNumber(value)}; - } - } - - // @ts-ignore - values.alpha = alpha != null && typeof alpha == 'object' ? alpha : (b).typ == EnumToken.PercentageTokenType ? {typ: EnumToken.PercentageTokenType, val: String(alpha ?? 100)} : {typ: EnumToken.NumberTokenType, val: String(alpha ?? 1)}; return computeComponentValue(keys, values); } @@ -282,15 +121,11 @@ function computeComponentValue(expr: Record, values: if (parent.l == value) { parent.l = values[value.val]; - } - - else { + } else { parent.r = values[value.val]; } - } - - else { + } else { for (let i = 0; i < parent.chi.length; i++) { @@ -309,9 +144,7 @@ function computeComponentValue(expr: Record, values: if (result.length == 1 && result[0].typ != EnumToken.BinaryExpressionTokenType) { expr[key] = result[0]; - } - - else { + } else { return null; } diff --git a/src/lib/renderer/color/rgb.ts b/src/lib/renderer/color/rgb.ts index c089cb2..964d265 100644 --- a/src/lib/renderer/color/rgb.ts +++ b/src/lib/renderer/color/rgb.ts @@ -1,10 +1,14 @@ import {ColorToken, DimensionToken, NumberToken, PercentageToken, Token} from "../../../@types"; -import {COLORS_NAMES, getAngle, getNumber, minmax} from "./color"; -import {getComponents} from "./utils"; -import {OKLab_to_sRGB} from "./oklab"; +import {getAngle, getNumber, minmax} from "./color"; +import {COLORS_NAMES, getComponents} from "./utils"; import {expandHexValue} from "./hex"; import {EnumToken} from "../../ast"; -import {Lab_to_sRGB} from "./lab"; +import {cmyk2srgb, hwb2srgb, lab2srgb, lch2srgb, oklab2srgb, oklch2srgb} from "./srgb"; + +function srgb2rgb(value: number): number { + + return minmax(Math.round(value * 255), 0, 255); +} export function hex2rgb(token: ColorToken): number[] { @@ -21,22 +25,7 @@ export function hex2rgb(token: ColorToken): number[] { export function hwb2rgb(token: ColorToken): number[] { - const {h: hue, s: white, l: black, a: alpha} = hslvalues(token); - - const rgb: number[] = hsl2rgbvalues(hue, 1, .5); - - for (let i = 0; i < 3; i++) { - - rgb[i] *= (1 - white - black); - rgb[i] = Math.round(rgb[i] + white); - } - - if (alpha != null && alpha != 1) { - - rgb.push(Math.round(255 * alpha)); - } - - return rgb; + return hwb2srgb(token).map(srgb2rgb); } export function hsl2rgb(token: ColorToken): number[] { @@ -49,211 +38,28 @@ export function hsl2rgb(token: ColorToken): number[] { export function cmyk2rgb(token: ColorToken): number[] { - const components: Token[] = getComponents(token); - - // @ts-ignore - let t: NumberToken | PercentageToken = components[0]; - - // @ts-ignore - const c: number = getNumber(t); - - // @ts-ignore - t = components[1]; - - // @ts-ignore - const m: number = getNumber(t); - - // @ts-ignore - t = components[2]; - - // @ts-ignore - const y: number = getNumber(t); - - // @ts-ignore - t = components[3]; - - // @ts-ignore - const k: number = getNumber(t); - - const rgb: number[] = [ - Math.round(255 * (1 - Math.min(1, c * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, m * (1 - k) + k))), - Math.round(255 * (1 - Math.min(1, y * (1 - k) + k))) - ]; - - // @ts-ignore - if (token.chi.length >= 9) { - - // @ts-ignore - t = token.chi[8]; - - // @ts-ignore - rgb.push(Math.round(255 * getNumber(t))); - } - - return rgb; + return cmyk2srgb(token).map(srgb2rgb); } export function oklab2rgb(token: ColorToken): number[] { - const components: Token[] = getComponents(token); - - // @ts-ignore - let t: NumberToken | PercentageToken = components[0]; - - // @ts-ignore - const l: number = getNumber(t); - - // @ts-ignore - t = components[1]; - - // @ts-ignore - const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - - // @ts-ignore - t = components[2]; - - // @ts-ignore - const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - - // @ts-ignore - t = components[3]; - - // @ts-ignore - const alpha: number = t == null ? 1 : getNumber(t); - - const rgb: number[] = OKLab_to_sRGB(l, a, b).map(v => { - - return Math.round(255 * v) - }); - - if (alpha != 1) { - - rgb.push(Math.round(255 * alpha)); - } - - return rgb.map(((value: number) => minmax(value, 0, 255))); + return oklab2srgb(token).map(srgb2rgb); } export function oklch2rgb(token: ColorToken): number[] { - const components: Token[] = getComponents(token); - - // @ts-ignore - let t: NumberToken | PercentageToken = components[0]; - - // @ts-ignore - const l: number = getNumber(t); - - // @ts-ignore - t = components[1]; - - // @ts-ignore - const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); - - // @ts-ignore - t = components[2]; - - // @ts-ignore - const h: number = getAngle(t); - - // @ts-ignore - t = components[3]; - - // @ts-ignore - const alpha: number = t == null ? 1 : getNumber(t); - - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const rgb: number[] = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); - - if (alpha != 1) { - - rgb.push(alpha); - } - - return rgb.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); + return oklch2srgb(token).map(srgb2rgb); } export function lab2rgb(token: ColorToken): number[] { - const components: Token[] = getComponents(token); - - // @ts-ignore - let t: NumberToken | PercentageToken = components[0]; - - // @ts-ignore - const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); - - // @ts-ignore - t = components[1]; - - // @ts-ignore - const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); - - // @ts-ignore - t = components[2]; - - // @ts-ignore - const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 125 : 1); - - // @ts-ignore - t = components[3]; - - // @ts-ignore - const alpha: number = t == null ? 1 : getNumber(t); - const rgb: number[] = Lab_to_sRGB(l, a, b); - - // - if (alpha != 1) { - - rgb.push( alpha); - } - - return rgb.map(((value: number): number => minmax(Math.round(value * 255), 0, 255))); + return lab2srgb(token).map(srgb2rgb); } export function lch2rgb(token: ColorToken): number[] { - const components: Token[] = getComponents(token); - - // @ts-ignore - let t: NumberToken | PercentageToken = components[0]; - - // @ts-ignore - const l: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 100 : 1); - - // @ts-ignore - t = components[1]; - - // @ts-ignore - const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? 150 : 1); - - // @ts-ignore - t = components[2]; - - // @ts-ignore - const h: number = getAngle(t); - - // @ts-ignore - t = components[3]; - - // @ts-ignore - const alpha: number = t == null ? 1 : getNumber(t); - - // https://www.w3.org/TR/css-color-4/#lab-to-lch - const a: number = c * Math.cos(360 * h * Math.PI / 180); - const b: number = c * Math.sin(360 * h * Math.PI / 180); - - const rgb: number[] = Lab_to_sRGB(l, a, b); - - // - if (alpha != 1) { - - rgb.push(alpha); - } - - return rgb.map(((value: number): number => minmax(value * 255, 0, 255))); + return lch2srgb(token).map(srgb2rgb); } export function hslvalues(token: ColorToken): {h: number, s: number, l: number, a?: number | null} { diff --git a/src/lib/renderer/color/srgb.ts b/src/lib/renderer/color/srgb.ts index 4bc41d7..7f596fd 100644 --- a/src/lib/renderer/color/srgb.ts +++ b/src/lib/renderer/color/srgb.ts @@ -1,11 +1,279 @@ // from https://www.w3.org/TR/css-color-4/#color-conversion-code // srgb-linear -> srgb // 0 <= r, g, b <= 1 -import {getComponents, roundWithPrecision} from "./utils"; +import {COLORS_NAMES, getComponents, roundWithPrecision} from "./utils"; import {ColorToken, DimensionToken, NumberToken, PercentageToken, Token} from "../../../@types"; import {getAngle, getNumber} from "./color"; import {EnumToken} from "../../ast"; import {Lab_to_sRGB} from "./lab"; +import {expandHexValue} from "./hex"; +import {OKLab_to_sRGB} from "./oklab"; + +export function hex2srgb(token: ColorToken): number[] { + + const value: string = expandHexValue(token.kin == 'lit' ? COLORS_NAMES[token.val.toLowerCase()] : token.val); + const rgb: number[] = []; + + for (let i = 1; i < value.length; i += 2) { + + rgb.push(parseInt(value.slice(i, i + 2), 16) / 255); + } + + return rgb; +} + +export function hwb2srgb(token: ColorToken): number[] { + + const {h: hue, s: white, l: black, a: alpha} = hslvalues(token); + + const rgb: number[] = hsl2srgbvalues(hue, 1, .5); + + for (let i = 0; i < 3; i++) { + + rgb[i] *= (1 - white - black); + rgb[i] = rgb[i] + white; + } + + if (alpha != null && alpha != 1) { + + rgb.push(alpha); + } + + return rgb; +} + +export function hsl2srgb(token: ColorToken): number[] { + + let {h, s, l, a} = hslvalues(token); + + return hsl2srgbvalues(h, s, l, a); +} + + +export function cmyk2srgb(token: ColorToken): number[] { + + const components: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken = components[0]; + + // @ts-ignore + const c: number = getNumber(t); + + // @ts-ignore + t = components[1]; + + // @ts-ignore + const m: number = getNumber(t); + + // @ts-ignore + t = components[2]; + + // @ts-ignore + const y: number = getNumber(t); + + // @ts-ignore + t = components[3]; + + // @ts-ignore + const k: number = getNumber(t); + + const rgb: number[] = [ + 1 - Math.min(1, c * (1 - k) + k), + 1 - Math.min(1, m * (1 - k) + k), + 1 - Math.min(1, y * (1 - k) + k) + ]; + + // @ts-ignore + if (token.chi.length >= 9) { + + // @ts-ignore + t = token.chi[8]; + + // @ts-ignore + rgb.push(getNumber(t)); + } + + return rgb; +} + +export function oklab2srgb(token: ColorToken): number[] { + + const components: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken = components[0]; + + // @ts-ignore + const l: number = getNumber(t); + + // @ts-ignore + t = components[1]; + + // @ts-ignore + const a: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + + // @ts-ignore + t = components[2]; + + // @ts-ignore + const b: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + + // @ts-ignore + t = components[3]; + + // @ts-ignore + const alpha: number = t == null ? 1 : getNumber(t); + const rgb: number[] = OKLab_to_sRGB(l, a, b); + + if (alpha != 1 && alpha != null) { + + rgb.push(alpha); + } + + return rgb; //.map(((value: number) => minmax(value, 0, 255))); +} + +export function oklch2srgb(token: ColorToken): number[] { + + const components: Token[] = getComponents(token); + + // @ts-ignore + let t: NumberToken | PercentageToken = components[0]; + + // @ts-ignore + const l: number = getNumber(t); + + // @ts-ignore + t = components[1]; + + // @ts-ignore + const c: number = getNumber(t) * (t.typ == EnumToken.PercentageTokenType ? .4 : 1); + + // @ts-ignore + t = components[2]; + + // @ts-ignore + const h: number = getAngle(t); + + // @ts-ignore + t = components[3]; + + // @ts-ignore + const alpha: number = t == null ? 1 : getNumber(t); + + // https://www.w3.org/TR/css-color-4/#lab-to-lch + const rgb: number[] = OKLab_to_sRGB(l, c * Math.cos(360 * h * Math.PI / 180), c * Math.sin(360 * h * Math.PI / 180)); + + if (alpha != 1) { + + rgb.push(alpha); + } + + return rgb; //.map(((value: number): number => minmax(Math.round(255 * value), 0, 255))); +} + +export function hslvalues(token: ColorToken): { h: number, s: number, l: number, a?: number | null } { + + const components: Token[] = getComponents(token); + + let t: PercentageToken | NumberToken; + + // @ts-ignore + let h: number = getAngle(components[0]); + + // @ts-ignore + t = components[1]; + // @ts-ignore + let s: number = getNumber(t); + // @ts-ignore + t = components[2]; + // @ts-ignore + let l: number = getNumber(t); + + let a = null; + + if (token.chi?.length == 4) { + + // @ts-ignore + t = token.chi[3]; + + // @ts-ignore + if ((t.typ == EnumToken.IdenTokenType && t.val == 'none') || ( + t.typ == EnumToken.PercentageTokenType && +t.val < 100) || + // @ts-ignore + (t.typ == EnumToken.NumberTokenType && t.val < 1)) { + + // @ts-ignore + a = getNumber(t); + } + } + + return a == null ? {h, s, l} : {h, s, l, a}; +} + +export function hsl2srgbvalues(h: number, s: number, l: number, a: number | null = null): number[] { + + let v: number = l <= .5 ? l * (1.0 + s) : l + s - l * s; + + let r: number = l; + let g: number = l; + let b: number = l; + + if (v > 0) { + + let m: number = l + l - v; + let sv: number = (v - m) / v; + h *= 6.0; + let sextant: number = Math.floor(h); + let fract: number = h - sextant; + let vsf: number = v * sv * fract; + let mid1: number = m + vsf; + let mid2: number = v - vsf; + + switch (sextant) { + case 0: + r = v; + g = mid1; + b = m; + break; + case 1: + r = mid2; + g = v; + b = m; + break; + case 2: + r = m; + g = v; + b = mid1; + break; + case 3: + r = m; + g = mid2; + b = v; + break; + case 4: + r = mid1; + g = m; + b = v; + break; + case 5: + r = v; + g = m; + b = mid2; + break; + } + } + + const values: number[] = [r, g, b]; + + if (a != null && a != 1) { + + values.push(a); + } + + return values; +} export function lab2srgb(token: ColorToken): number[] { @@ -95,7 +363,7 @@ export function gam_sRGB(r: number, g: number, b: number): number[] { // Extended transfer function: // For negative values, linear portion extends on reflection // of axis, then uses reflected pow below that - return [r, g, b].map( (val: number): number => { + return [r, g, b].map((val: number): number => { let abs: number = Math.abs(val); diff --git a/src/lib/renderer/color/utils/constants.ts b/src/lib/renderer/color/utils/constants.ts index 696d86e..12b03e9 100644 --- a/src/lib/renderer/color/utils/constants.ts +++ b/src/lib/renderer/color/utils/constants.ts @@ -5,3 +5,164 @@ export const D65: number[] = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) export const k: number = Math.pow(29, 3) / Math.pow(3, 3); export const e: number = Math.pow(6, 3) / Math.pow(29, 3); +// name to color +export const COLORS_NAMES: { [key: string]: string } = Object.seal({ + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', + 'rebeccapurple': '#663399', + 'transparent': '#00000000' +}); +// color to name +export const NAMES_COLORS: { [key: string]: string } = Object.seal(Object.entries(COLORS_NAMES).reduce((acc: { + [key: string]: string +}, [key, value]) => { + + acc[value] = key; + return acc; + +}, Object.create(null))); \ No newline at end of file diff --git a/src/lib/renderer/render.ts b/src/lib/renderer/render.ts index 26acb72..d24c805 100644 --- a/src/lib/renderer/render.ts +++ b/src/lib/renderer/render.ts @@ -33,6 +33,7 @@ import {EnumToken, expand} from "../ast"; import {SourceMap} from "./sourcemap"; import {isColor, isNewLine} from "../parser"; import {parseRelativeColor, RelativeColorTypes,gam_sRGB, lin_2020, lin_a98rgb, lin_ProPhoto,XYZ_D50_to_sRGB, XYZ_to_sRGB} from "./color"; +import {getComponents} from "./color/utils"; export const colorsFunc: string[] = ['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'device-cmyk', 'color-mix', 'color', 'oklab', 'lab', 'oklch', 'lch']; @@ -505,12 +506,11 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { return reduceHexValue(value); } - } else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb'].includes(token.val)) { + } else if (token.cal == 'rel' && ['rgb', 'hsl', 'hwb', 'lab', 'lch', 'oklab', 'oklch'].includes(token.val)) { - const chi: Token[] = (token.chi).filter((x: Token) => ![ - EnumToken.LiteralTokenType, EnumToken.CommaTokenType, EnumToken.WhitespaceTokenType, EnumToken.CommentTokenType].includes(x.typ)); + const chi: Token[] = getComponents(token); - const components: Record = >parseRelativeColor(token.val.split(''), chi[1], chi[2], chi[3], chi[4], chi[5]); + const components: Record = >parseRelativeColor(token.val, chi[1], chi[2], chi[3], chi[4], chi[5]); if (components != null) { @@ -587,6 +587,7 @@ export function renderToken(token: Token, options: RenderOptions = {}, cache: { clamp(token); + // console.error({token}); if (Array.isArray(token.chi) && token.chi.some((t: Token): boolean => t.typ == EnumToken.FunctionTokenType || (t.typ == EnumToken.ColorTokenType && Array.isArray(t.chi)))) { return (token.val.endsWith('a') ? token.val.slice(0, -1) : token.val) + '(' + token.chi.reduce((acc: string, curr: Token) => acc + (acc.length > 0 && !(acc.endsWith('/') || curr.typ == EnumToken.LiteralTokenType) ? ' ' : '') + renderToken(curr, options, cache), '') + ')'; diff --git a/test/specs/code/color.js b/test/specs/code/color.js index 3883011..17f178e 100644 --- a/test/specs/code/color.js +++ b/test/specs/code/color.js @@ -458,6 +458,113 @@ color: lab(97.83 -12.04 62.08); }`)); // should be #fffe7a }); + it('rgb(from oklch(100% 0.4 30) r g b) #49', function () { + return parse(` +.selector { +color: rgb(from oklch(100% 0.4 30) r g b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff3306 +}`)); // should be #fffe7a + }); + + it('rgb(from oklab(100% 0.4 0.4) r g b) #50', function () { + return parse(` +.selector { +color: rgb(from oklab(100% 0.4 0.4) r g b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: red +}`)); // should be #fffe7a + }); + + it('rgb(from lab(100 125 125) r g b) #51', function () { + return parse(` +.selector { +color: rgb(from lab(100 125 125) r g b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff4d00 +}`)); // should be #fffe7a + }); + + it('rgb(from lch(50% 130 20) r g b) #52', function () { + return parse(` +.selector { +color: rgb(from lch(50% 130 20) r g b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff003b +}`)); // should be #fffe7a + }); + + it('hsl(from oklch(100% 0.4 30) h s l) #53', function () { + return parse(` +.selector { +color: hsl(from oklch(100% 0.4 30) h s l) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff3306 +}`)); // should be #fffe7a + }); + + it('hsl(from oklab(100% 0.4 0.4) h s l) #54', function () { + return parse(` +.selector { +color: hsl(from oklab(100% 0.4 0.4) h s l) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: red +}`)); // should be #fffe7a + }); + + it('hsl(from lab(100 125 125) h s l) #55', function () { + return parse(` +.selector { +color: hsl(from lab(100 125 125) h s l) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff4d00 +}`)); + }); + + it('hsl(from lch(50% 130 20) h s l) #56', function () { + return parse(` +.selector { +color: hsl(from lch(50% 130 20) h s l) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff003b +}`)); + }); + + it('hwb(from oklch(100% 0.4 30) h w b) #57', function () { + return parse(` +.selector { +color: hwb(from oklch(100% 0.4 30) h w b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff3306 +}`)); // should be #fffe7a + }); + + it('hwb(from oklab(100% 0.4 0.4) h w b) #58', function () { + return parse(` +.selector { +color: hwb(from oklab(100% 0.4 0.4) h w b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: red +}`)); // should be #fffe7a + }); + + it('hwb(from lab(100 125 125) h w b) #59', function () { + return parse(` +.selector { +color: hwb(from lab(100 125 125) h w b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff4d00 +}`)); + }); + + it('hwb(from lch(50% 130 20) h w b) #60', function () { + return parse(` +.selector { +color: hwb(from lch(50% 130 20) h w b) ; +`).then(result => expect(render(result.ast, {minify: false}).code).equals(`.selector { + color: #ff003b +}`)); + });