chroma-js
Version:
JavaScript library for color conversions
239 lines (216 loc) • 7.45 kB
JavaScript
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;