UNPKG

chroma-js

Version:

JavaScript library for color conversions

239 lines (216 loc) 7.45 kB
import hsl2rgb from '../hsl/hsl2rgb.js'; import lab2rgb from '../lab/lab2rgb.js'; import lch2rgb from '../lch/lch2rgb.js'; import oklab2rgb from '../oklab/oklab2rgb.js'; import oklch2rgb from '../oklch/oklch2rgb.js'; import input from '../input.js'; import limit from '../../utils/limit.js'; import { getLabWhitePoint, setLabWhitePoint } from '../lab/lab-constants.js'; const INT_OR_PCT = /((?:-?\d+)|(?:-?\d+(?:\.\d+)?)%|none)/.source; const FLOAT_OR_PCT = /((?:-?(?:\d+(?:\.\d*)?|\.\d+)%?)|none)/.source; const PCT = /((?:-?(?:\d+(?:\.\d*)?|\.\d+)%)|none)/.source; const RE_S = /\s*/.source; const SEP = /\s+/.source; const COMMA = /\s*,\s*/.source; const ANLGE = /((?:-?(?:\d+(?:\.\d*)?|\.\d+)(?:deg)?)|none)/.source; const ALPHA = /\s*(?:\/\s*((?:[01]|[01]?\.\d+)|\d+(?:\.\d+)?%))?/.source; // e.g. rgb(250 20 0), rgb(100% 50% 20%), rgb(100% 50% 20% / 0.5) const RE_RGB = new RegExp( '^rgba?\\(' + RE_S + [INT_OR_PCT, INT_OR_PCT, INT_OR_PCT].join(SEP) + ALPHA + '\\)$' ); const RE_RGB_LEGACY = new RegExp( '^rgb\\(' + RE_S + [INT_OR_PCT, INT_OR_PCT, INT_OR_PCT].join(COMMA) + RE_S + '\\)$' ); const RE_RGBA_LEGACY = new RegExp( '^rgba\\(' + RE_S + [INT_OR_PCT, INT_OR_PCT, INT_OR_PCT, FLOAT_OR_PCT].join(COMMA) + RE_S + '\\)$' ); const RE_HSL = new RegExp( '^hsla?\\(' + RE_S + [ANLGE, PCT, PCT].join(SEP) + ALPHA + '\\)$' ); const RE_HSL_LEGACY = new RegExp( '^hsl?\\(' + RE_S + [ANLGE, PCT, PCT].join(COMMA) + RE_S + '\\)$' ); const RE_HSLA_LEGACY = /^hsla\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/; const RE_LAB = new RegExp( '^lab\\(' + RE_S + [FLOAT_OR_PCT, FLOAT_OR_PCT, FLOAT_OR_PCT].join(SEP) + ALPHA + '\\)$' ); const RE_LCH = new RegExp( '^lch\\(' + RE_S + [FLOAT_OR_PCT, FLOAT_OR_PCT, ANLGE].join(SEP) + ALPHA + '\\)$' ); const RE_OKLAB = new RegExp( '^oklab\\(' + RE_S + [FLOAT_OR_PCT, FLOAT_OR_PCT, FLOAT_OR_PCT].join(SEP) + ALPHA + '\\)$' ); const RE_OKLCH = new RegExp( '^oklch\\(' + RE_S + [FLOAT_OR_PCT, FLOAT_OR_PCT, ANLGE].join(SEP) + ALPHA + '\\)$' ); const { round } = Math; const roundRGB = (rgb) => { return rgb.map((v, i) => (i <= 2 ? limit(round(v), 0, 255) : v)); }; const percentToAbsolute = (pct, min = 0, max = 100, signed = false) => { if (typeof pct === 'string' && pct.endsWith('%')) { pct = parseFloat(pct.substring(0, pct.length - 1)) / 100; if (signed) { // signed percentages are in the range -100% to 100% pct = min + (pct + 1) * 0.5 * (max - min); } else { pct = min + pct * (max - min); } } return +pct; }; const noneToValue = (v, noneValue) => { return v === 'none' ? noneValue : v; }; const css2rgb = (css) => { css = css.toLowerCase().trim(); if (css === 'transparent') { return [0, 0, 0, 0]; } let m; if (input.format.named) { try { return input.format.named(css); // eslint-disable-next-line } catch (e) {} } // rgb(250 20 0) or rgb(250,20,0) if ((m = css.match(RE_RGB)) || (m = css.match(RE_RGB_LEGACY))) { let rgb = m.slice(1, 4); for (let i = 0; i < 3; i++) { rgb[i] = +percentToAbsolute(noneToValue(rgb[i], 0), 0, 255); } rgb = roundRGB(rgb); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; // default alpha return rgb; } // rgba(250,20,0,0.4) if ((m = css.match(RE_RGBA_LEGACY))) { const rgb = m.slice(1, 5); for (let i = 0; i < 4; i++) { rgb[i] = +percentToAbsolute(rgb[i], 0, 255); } return rgb; } // hsl(0,100%,50%) if ((m = css.match(RE_HSL)) || (m = css.match(RE_HSL_LEGACY))) { const hsl = m.slice(1, 4); hsl[0] = +noneToValue(hsl[0].replace('deg', ''), 0); hsl[1] = +percentToAbsolute(noneToValue(hsl[1], 0), 0, 100) * 0.01; hsl[2] = +percentToAbsolute(noneToValue(hsl[2], 0), 0, 100) * 0.01; const rgb = roundRGB(hsl2rgb(hsl)); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; return rgb; } // hsla(0,100%,50%,0.5) if ((m = css.match(RE_HSLA_LEGACY))) { const hsl = m.slice(1, 4); hsl[1] *= 0.01; hsl[2] *= 0.01; const rgb = hsl2rgb(hsl); for (let i = 0; i < 3; i++) { rgb[i] = round(rgb[i]); } rgb[3] = +m[4]; // default alpha = 1 return rgb; } if ((m = css.match(RE_LAB))) { const lab = m.slice(1, 4); lab[0] = percentToAbsolute(noneToValue(lab[0], 0), 0, 100); lab[1] = percentToAbsolute(noneToValue(lab[1], 0), -125, 125, true); lab[2] = percentToAbsolute(noneToValue(lab[2], 0), -125, 125, true); // convert to D50 Lab whitepoint const wp = getLabWhitePoint(); setLabWhitePoint('d50'); const rgb = roundRGB(lab2rgb(lab)); // convert back to original Lab whitepoint setLabWhitePoint(wp); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; return rgb; } if ((m = css.match(RE_LCH))) { const lch = m.slice(1, 4); lch[0] = percentToAbsolute(lch[0], 0, 100); lch[1] = percentToAbsolute(noneToValue(lch[1], 0), 0, 150, false); lch[2] = +noneToValue(lch[2].replace('deg', ''), 0); // convert to D50 Lab whitepoint const wp = getLabWhitePoint(); setLabWhitePoint('d50'); const rgb = roundRGB(lch2rgb(lch)); // convert back to original Lab whitepoint setLabWhitePoint(wp); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; return rgb; } if ((m = css.match(RE_OKLAB))) { const oklab = m.slice(1, 4); oklab[0] = percentToAbsolute(noneToValue(oklab[0], 0), 0, 1); oklab[1] = percentToAbsolute(noneToValue(oklab[1], 0), -0.4, 0.4, true); oklab[2] = percentToAbsolute(noneToValue(oklab[2], 0), -0.4, 0.4, true); const rgb = roundRGB(oklab2rgb(oklab)); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; return rgb; } if ((m = css.match(RE_OKLCH))) { const oklch = m.slice(1, 4); oklch[0] = percentToAbsolute(noneToValue(oklch[0], 0), 0, 1); oklch[1] = percentToAbsolute(noneToValue(oklch[1], 0), 0, 0.4, false); oklch[2] = +noneToValue(oklch[2].replace('deg', ''), 0); const rgb = roundRGB(oklch2rgb(oklch)); const alpha = m[4] !== undefined ? +percentToAbsolute(m[4], 0, 1) : 1; rgb[3] = alpha; return rgb; } }; css2rgb.test = (s) => { return ( // modern RE_RGB.test(s) || RE_HSL.test(s) || RE_LAB.test(s) || RE_LCH.test(s) || RE_OKLAB.test(s) || RE_OKLCH.test(s) || // legacy RE_RGB_LEGACY.test(s) || RE_RGBA_LEGACY.test(s) || RE_HSL_LEGACY.test(s) || RE_HSLA_LEGACY.test(s) || s === 'transparent' ); }; export default css2rgb;