// #region patterns const float = '-?\\d*(?:\\.\\d+)'; export const number = `(${float}?)`; export const percentage = `(${float}?%)`; export const numberOrPercentage = `(${float}?%?)`; const clamp = (num, min, max) => Math.min(Math.max(min, num), max); const hexCharacters = 'a-f\\d'; const match3or4Hex = `#?[${hexCharacters}]{3}[${hexCharacters}]?`; const match6or8Hex = `#?[${hexCharacters}]{6}([${hexCharacters}]{2})?`; const nonHexChars = new RegExp(`[^#${hexCharacters}]`, 'gi'); const validHexSize = new RegExp(`^${match3or4Hex}$|^${match6or8Hex}$`, 'i'); export const hex_pattern = new RegExp(/^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/, "i"); export const hsl3_pattern = new RegExp(`^ hsla?\\( \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s*, \\s*${percentage}\\s*, \\s*${percentage}\\s* (?:,\\s*${numberOrPercentage}\\s*)? \\) $ `.replace(/\n|\s/g, '')) export const hsl4_pattern = new RegExp(`^ hsla?\\( \\s*(-?\\d*(?:\\.\\d+)?(?:deg|rad|turn)?)\\s* \\s+${percentage} \\s+${percentage} \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? \\) $ `.replace(/\n|\s/g, '')) export const rgb3_pattern = new RegExp(`^ rgba?\\( \\s*${number}\\s*, \\s*${number}\\s*, \\s*${number}\\s* (?:,\\s*${numberOrPercentage}\\s*)? \\) $ `.replace(/\n|\s/g, '')) export const rgb4_pattern = new RegExp(`^ rgba?\\( \\s*${number} \\s+${number} \\s+${number} \\s*(?:\\s*\\/\\s*${numberOrPercentage}\\s*)? \\) $ `.replace(/\n|\s/g, '')); export const transparent_pattern = new RegExp(/^transparent$/, 'i'); // #endregion // #region utils /* 500 => 255, -10 => 0, 128 => 128 */ const parseRGB = (num) => { let n = num; if (typeof n !== 'number') { n = n.endsWith('%') ? (parseFloat(n) * 255) / 100 : parseFloat(n); } return clamp(Math.round(n), 0, 255); }; /* 200 => 100, -100 => 0, 50 => 50 */ const parsePercentage = (percentage) => clamp(parseFloat(percentage), 0, 100); /* '50%' => 5.0, 200 => 1, -10 => 0 */ function parseAlpha(alpha) { let a = alpha; if (typeof a !== 'number') { a = a.endsWith('%') ? parseFloat(a) / 100 : parseFloat(a); } return clamp(a, 0, 1); } export function getHEX(hex) { const [r, g, b, a] = hex2Rgb(hex, { format: 'array' }); return getRGB([null, ...[r, g, b, a]]); } export function getHSL([, h, s, l, a = 1]) { let hh = h; if (hh.endsWith('turn')) { hh = (parseFloat(hh) * 360) / 1; } else if (hh.endsWith('rad')) { hh = Math.round((parseFloat(hh) * 180) / Math.PI); } else { hh = parseFloat(hh); } return { type: 'hsl', values: [hh, parsePercentage(s), parsePercentage(l)], alpha: parseAlpha(a === null ? 1 : a) }; } export function getRGB([, r, g, b, a = 1]) { return { type: 'rgb', values: [r, g, b].map(parseRGB), alpha: parseAlpha(a === null ? 1 : a) }; } export function hex2Rgb(hex, options = {}) { if (typeof hex !== 'string' || nonHexChars.test(hex) || !validHexSize.test(hex)) { throw new TypeError('Expected a valid hex string'); } hex = hex.replace(/^#/, ''); let alphaFromHex = 1; if (hex.length === 8) { alphaFromHex = Number.parseInt(hex.slice(6, 8), 16) / 255; hex = hex.slice(0, 6); } if (hex.length === 4) { alphaFromHex = Number.parseInt(hex.slice(3, 4).repeat(2), 16) / 255; hex = hex.slice(0, 3); } if (hex.length === 3) { hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } const number = Number.parseInt(hex, 16); const red = number >> 16; const green = (number >> 8) & 255; const blue = number & 255; const alpha = typeof options.alpha === 'number' ? options.alpha : alphaFromHex; if (options.format === 'array') { return [red, green, blue, alpha]; } if (options.format === 'css') { const alphaString = alpha === 1 ? '' : ` / ${Number((alpha * 100).toFixed(2))}%`; return `rgb(${red} ${green} ${blue}${alphaString})`; } return {red, green, blue, alpha}; } // #endregion // #region colorNames export const colorName = { aliceblue: [240, 248, 255], antiquewhite: [250, 235, 215], aqua: [0, 255, 255], aquamarine: [127, 255, 212], azure: [240, 255, 255], beige: [245, 245, 220], bisque: [255, 228, 196], black: [0, 0, 0], blanchedalmond: [255, 235, 205], blue: [0, 0, 255], blueviolet: [138, 43, 226], brown: [165, 42, 42], burlywood: [222, 184, 135], cadetblue: [95, 158, 160], chartreuse: [127, 255, 0], chocolate: [210, 105, 30], coral: [255, 127, 80], cornflowerblue: [100, 149, 237], cornsilk: [255, 248, 220], crimson: [220, 20, 60], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgoldenrod: [184, 134, 11], darkgray: [169, 169, 169], darkgreen: [0, 100, 0], darkgrey: [169, 169, 169], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkseagreen: [143, 188, 143], darkslateblue: [72, 61, 139], darkslategray: [47, 79, 79], darkslategrey: [47, 79, 79], darkturquoise: [0, 206, 209], darkviolet: [148, 0, 211], deeppink: [255, 20, 147], deepskyblue: [0, 191, 255], dimgray: [105, 105, 105], dimgrey: [105, 105, 105], dodgerblue: [30, 144, 255], firebrick: [178, 34, 34], floralwhite: [255, 250, 240], forestgreen: [34, 139, 34], fuchsia: [255, 0, 255], gainsboro: [220, 220, 220], ghostwhite: [248, 248, 255], gold: [255, 215, 0], goldenrod: [218, 165, 32], gray: [128, 128, 128], green: [0, 128, 0], greenyellow: [173, 255, 47], grey: [128, 128, 128], honeydew: [240, 255, 240], hotpink: [255, 105, 180], indianred: [205, 92, 92], indigo: [75, 0, 130], ivory: [255, 255, 240], khaki: [240, 230, 140], lavender: [230, 230, 250], lavenderblush: [255, 240, 245], lawngreen: [124, 252, 0], lemonchiffon: [255, 250, 205], lightblue: [173, 216, 230], lightcoral: [240, 128, 128], lightcyan: [224, 255, 255], lightgoldenrodyellow: [250, 250, 210], lightgray: [211, 211, 211], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightsalmon: [255, 160, 122], lightseagreen: [32, 178, 170], lightskyblue: [135, 206, 250], lightslategray: [119, 136, 153], lightslategrey: [119, 136, 153], lightsteelblue: [176, 196, 222], lightyellow: [255, 255, 224], lime: [0, 255, 0], limegreen: [50, 205, 50], linen: [250, 240, 230], magenta: [255, 0, 255], maroon: [128, 0, 0], mediumaquamarine: [102, 205, 170], mediumblue: [0, 0, 205], mediumorchid: [186, 85, 211], mediumpurple: [147, 112, 219], mediumseagreen: [60, 179, 113], mediumslateblue: [123, 104, 238], mediumspringgreen: [0, 250, 154], mediumturquoise: [72, 209, 204], mediumvioletred: [199, 21, 133], midnightblue: [25, 25, 112], mintcream: [245, 255, 250], mistyrose: [255, 228, 225], moccasin: [255, 228, 181], navajowhite: [255, 222, 173], navy: [0, 0, 128], oldlace: [253, 245, 230], olive: [128, 128, 0], olivedrab: [107, 142, 35], orange: [255, 165, 0], orangered: [255, 69, 0], orchid: [218, 112, 214], palegoldenrod: [238, 232, 170], palegreen: [152, 251, 152], paleturquoise: [175, 238, 238], palevioletred: [219, 112, 147], papayawhip: [255, 239, 213], peachpuff: [255, 218, 185], peru: [205, 133, 63], pink: [255, 192, 203], plum: [221, 160, 221], powderblue: [176, 224, 230], purple: [128, 0, 128], rebeccapurple: [102, 51, 153], red: [255, 0, 0], rosybrown: [188, 143, 143], royalblue: [65, 105, 225], saddlebrown: [139, 69, 19], salmon: [250, 128, 114], sandybrown: [244, 164, 96], seagreen: [46, 139, 87], seashell: [255, 245, 238], sienna: [160, 82, 45], silver: [192, 192, 192], skyblue: [135, 206, 235], slateblue: [106, 90, 205], slategray: [112, 128, 144], slategrey: [112, 128, 144], snow: [255, 250, 250], springgreen: [0, 255, 127], steelblue: [70, 130, 180], tan: [210, 180, 140], teal: [0, 128, 128], thistle: [216, 191, 216], tomato: [255, 99, 71], turquoise: [64, 224, 208], violet: [238, 130, 238], wheat: [245, 222, 179], white: [255, 255, 255], whitesmoke: [245, 245, 245], yellow: [255, 255, 0], yellowgreen: [154, 205, 50] } // #endregion export const parseCSSColor = (str, debug=false) => { if (typeof str !== 'string') { console.error(`parseCSSColor: expected a string found ${typeof str}`,str); return null; } const hex = hex_pattern.exec(str); if (hex) { if (debug){ console.debug('parseCSSColor: hex', hex); } return getHEX(hex[0]); } const hsl = hsl4_pattern.exec(str) || hsl3_pattern.exec(str); if (hsl) { if (debug){ console.debug('parseCSSColor: hsl', hsl); } return getHSL(hsl); } const rgb = rgb4_pattern.exec(str) || rgb3_pattern.exec(str) if (rgb) { if (debug){ console.debug('parseCSSColor: rgb', rgb); } return getRGB(rgb); } if (transparent_pattern.exec(str)) { if (debug){ console.debug('parseCSSColor: transparent'); } return getRGB([null, 0, 0, 0, 0]); } const cn = colorName[str.toLowerCase()]; if (cn) { if (debug){ console.debug('parseCSSColor: colorName', cn); } return getRGB([null, cn[0], cn[1], cn[2], 1]); } console.error('parseCSSColor: unknown color', str); return null; }; export default parseCSSColor;