|
|
|
|
|
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'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
}; |
|
|
|
|
|
const parsePercentage = (percentage) => clamp(parseFloat(percentage), 0, 100); |
|
|
|
|
|
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}; |
|
} |
|
|
|
|
|
|
|
|
|
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] |
|
} |
|
|
|
|
|
|
|
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; |
|
|
|
|