colorizr
Version:
Manipulate colors like a boss
1,725 lines (1,667 loc) • 65.6 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/converters/index.ts
var converters_exports = {};
__export(converters_exports, {
hex2hsl: () => hex2hsl,
hex2oklab: () => hex2oklab,
hex2oklch: () => hex2oklch,
hex2rgb: () => hex2rgb,
hsl2hex: () => hsl2hex,
hsl2oklab: () => hsl2oklab,
hsl2oklch: () => hsl2oklch,
hsl2rgb: () => hsl2rgb,
oklab2hex: () => oklab2hex,
oklab2hsl: () => oklab2hsl,
oklab2oklch: () => oklab2oklch,
oklab2rgb: () => oklab2rgb,
oklch2hex: () => oklch2hex,
oklch2hsl: () => oklch2hsl,
oklch2oklab: () => oklch2oklab,
oklch2rgb: () => oklch2rgb,
rgb2hex: () => rgb2hex,
rgb2hsl: () => rgb2hsl,
rgb2oklab: () => rgb2oklab,
rgb2oklch: () => rgb2oklch
});
// src/modules/constants.ts
var COLOR_KEYS = {
hsl: ["h", "s", "l"],
oklab: ["l", "a", "b"],
oklch: ["l", "c", "h"],
rgb: ["r", "g", "b"]
};
var CLMS_TO_OKLAB = [
[0.210454268309314, 0.7936177747023054, -0.0040720430116193],
[1.9779985324311684, -2.42859224204858, 0.450593709617411],
[0.0259040424655478, 0.7827717124575296, -0.8086757549230774]
];
var DEG2RAD = Math.PI / 180;
var LMS_TO_LRGB = [
[4.0767416621, -3.3077115913, 0.2309699292],
[-1.2684380046, 2.6097574011, -0.3413193965],
[-0.0041960863, -0.7034186147, 1.707614701]
];
var LRGB_TO_LMS = [
[0.4122214708, 0.5363325363, 0.0514459929],
[0.2119034982, 0.6806995451, 0.1073969566],
[0.0883024619, 0.2817188376, 0.6299787005]
];
var OKLAB_TO_CLMS = [
[1, 0.3963377773761749, 0.2158037573099136],
[1, -0.1055613458156586, -0.0638541728258133],
[1, -0.0894841775298119, -1.2914855480194092]
];
var P3_TO_SRGB = [
[1.22494017628056, -0.22494017628055996, 0],
[-0.04205695470968812, 1.0420569547096883, 0],
[-0.019637554590334422, -0.07863604555063179, 1.0982736001409665]
];
var P3_TO_XYZ = [
[0.4865709486482162, 0.26566769316909306, 0.1982172852343625],
[0.22897456406974884, 0.6917385218365064, 0.079286914093745],
[0, 0.04511338185890264, 1.043944368900976]
];
var SRGB_TO_P3 = [
[0.8224270476, 0.1775729524, 0],
[0.0331008087, 0.9668991913, 0],
[0.0170720188, 0.0723477973, 0.9105801839]
];
var XYZ_TO_SRGB = [
[3.2409699419045226, -1.537383177570094, -0.4986107602930034],
[-0.9692436362808796, 1.8759675015077202, 0.0415550574071756],
[0.0556300796969936, -0.2039769588889765, 1.0569715142428786]
];
var GAMUT_EPSILON = 1e-6;
var PRECISION = 5;
var RAD2DEG = 180 / Math.PI;
var MESSAGES = {
alpha: "alpha must be a number between 0 and 1",
alphaAdjustment: "alpha must be a number between -1 and 1",
amount: "amount must be a number between 0 and 100",
colorRequired: "color is required",
degreesRange: "degrees must be a number between -360 and 360",
hueArgs: "point, chroma and h are required",
hueRange: "hue must be a number between 0 and 360",
input: "input is required",
inputHex: "input is required and must be a hex",
inputNumber: "input is required and must be a number",
inputString: "input is required and must be a string",
invalid: "invalid input",
invalidColor: "invalid color",
invalidCSS: "invalid CSS string",
invalidHex: "invalid hex",
invalidKey: "invalid key",
invalidModel: "invalid model",
invalidRange: "color value out of range",
left: "left is required and must be a string",
lightnessRange: "lightness must be a number between 0 and 1",
options: "invalid options",
paletteSize: "palette size must be at least 2",
ratioRange: "ratio must be a number between 0 and 1",
right: "right is required and must be a string",
threshold: "threshold must be a number between 0 and 255",
thresholdNormalized: "threshold must be a number between 0 and 1"
};
var MONOCHROMATIC_LIGHTNESS_MAX = 80;
// src/modules/invariant.ts
function invariant(condition, message) {
if (condition) {
return;
}
if (process.env.NODE_ENV !== "production" && message === void 0) {
throw new Error("invariant requires an error message argument");
}
const error = !message ? new Error(
"Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings."
) : new Error(message);
error.name = "colorizr";
throw error;
}
// src/modules/css-colors.ts
var cssColors = {
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",
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",
lightgreen: "#90ee90",
lightgrey: "#d3d3d3",
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: "#9370db",
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: "#db7093",
papayawhip: "#ffefd5",
peachpuff: "#ffdab9",
peru: "#cd853f",
pink: "#ffc0cb",
plum: "#dda0dd",
powderblue: "#b0e0e6",
purple: "#800080",
rebeccapurple: "#663399",
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"
};
// src/modules/validators.ts
var hexRegex = /^#(?:[\da-f]{3,4}|[\da-f]{6,8})$/i;
function hasValidMatches(input) {
return Array.isArray(input) && input.length === 6;
}
function isHex(input) {
if (!isString(input)) {
return false;
}
return hexRegex.test(input);
}
function isHSL(input) {
if (!isPlainObject(input)) {
return false;
}
const entries = Object.entries(input);
return !!entries.length && entries.every(([key, value]) => {
if (key === "h") {
return value >= 0 && value <= 360;
}
if (key === "alpha") {
return value >= 0 && value <= 1;
}
return COLOR_KEYS.hsl.includes(key) && value >= 0 && value <= 100;
});
}
function isLAB(input) {
if (!isPlainObject(input)) {
return false;
}
const entries = Object.entries(input);
return !!entries.length && entries.every(([key, value]) => {
if (key === "l") {
return value >= 0 && value <= 100;
}
if (key === "alpha") {
return value >= 0 && value <= 1;
}
return COLOR_KEYS.oklab.includes(key) && value >= -1 && value <= 1;
});
}
function isLCH(input) {
if (!isPlainObject(input)) {
return false;
}
const entries = Object.entries(input);
return !!entries.length && entries.every(([key, value]) => {
if (key === "l") {
return value >= 0 && value <= 100;
}
if (key === "alpha") {
return value >= 0 && value <= 1;
}
return COLOR_KEYS.oklch.includes(key) && value >= 0 && value <= (key === "h" ? 360 : 1);
});
}
function isNamedColor(input) {
return isString(input) && Object.keys(cssColors).includes(input.toLowerCase());
}
function isNumber(input) {
return typeof input === "number" && !Number.isNaN(input);
}
function isNumberInRange(input, min, max) {
return isNumber(input) && input >= min && input <= max;
}
function isPlainObject(input) {
if (!input) {
return false;
}
const { toString } = Object.prototype;
const prototype = Object.getPrototypeOf(input);
return toString.call(input) === "[object Object]" && (prototype === null || prototype === Object.getPrototypeOf({}));
}
function isRGB(input) {
if (!isPlainObject(input)) {
return false;
}
const entries = Object.entries(input);
return !!entries.length && entries.every(([key, value]) => {
if (key === "alpha") {
return value >= 0 && value <= 1;
}
return COLOR_KEYS.rgb.includes(key) && value >= 0 && value <= 255;
});
}
function isString(input, validate = true) {
const isValid = typeof input === "string";
if (validate) {
return isValid && !!input.trim().length;
}
return isValid;
}
function isValidColorModel(input) {
return isHSL(input) || isRGB(input) || isLAB(input) || isLCH(input);
}
// src/format-hex.ts
function formatHex(input) {
invariant(isString(input), MESSAGES.inputString);
let color = input.startsWith("#") ? input.slice(1) : input;
invariant(isHex(`#${color}`), MESSAGES.inputHex);
if (color.length === 3 || color.length === 4) {
const values = [...color];
color = "";
values.forEach((d) => {
color += `${d}${d}`;
});
}
const hex = `#${color}`;
invariant(isHex(hex), MESSAGES.invalidHex);
return hex;
}
// src/modules/utils.ts
function clamp(value, min = 0, max = 100) {
return Math.min(Math.max(value, min), max);
}
function constrainDegrees(input, amount) {
invariant(isNumber(input), MESSAGES.inputNumber);
return ((input + amount) % 360 + 360) % 360;
}
function normalizeOkLightness(color) {
if (color.l > 1) {
return { ...color, l: parseFloat((color.l / 100).toPrecision(15)) };
}
return color;
}
function parseInput(input, model) {
const keys = COLOR_KEYS[model];
const validator = {
hsl: isHSL,
oklab: isLAB,
oklch: isLCH,
rgb: isRGB
};
invariant(isPlainObject(input) || Array.isArray(input), MESSAGES.invalid);
const value = Array.isArray(input) ? { [keys[0]]: input[0], [keys[1]]: input[1], [keys[2]]: input[2] } : input;
invariant(validator[model](value), `${MESSAGES.invalidColor}: ${model}`);
return value;
}
function restrictValues(input, precision, forcePrecision = true) {
if (precision == null) {
return input;
}
const output = new Map(Object.entries(input));
for (const [key, value] of output.entries()) {
output.set(key, round(value, precision, forcePrecision));
}
return Object.fromEntries(output);
}
function round(input, precision = 2, forcePrecision = true) {
if (!isNumber(input) || input === 0) {
return 0;
}
if (forcePrecision) {
const factor2 = 10 ** precision;
return Math.round(input * factor2) / factor2;
}
const integer = Math.trunc(input);
let digits = 0;
if (integer) {
digits = Math.floor(Math.log10(Math.abs(integer))) + 1;
}
const factor = 10 ** (precision - digits);
return Math.floor(input * factor + 0.5) / factor;
}
function warn(message) {
if (process.env.NODE_ENV !== "production") {
console.warn(`[colorizr] ${message}`);
}
}
var STEP_KEYS = {
3: [100, 500, 900],
4: [100, 400, 600, 900],
5: [100, 300, 500, 700, 900],
6: [100, 200, 400, 600, 800, 900],
7: [100, 200, 400, 500, 600, 800, 900],
8: [100, 200, 300, 500, 600, 700, 800, 900],
9: [100, 200, 300, 400, 500, 600, 700, 800, 900],
10: [50, 100, 200, 300, 400, 500, 600, 700, 800, 900],
11: [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950],
12: [50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 950],
13: [50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 850, 900, 950],
14: [50, 100, 150, 200, 250, 300, 400, 500, 600, 700, 800, 850, 900, 950],
15: [50, 100, 150, 200, 250, 300, 400, 500, 600, 700, 750, 800, 850, 900, 950],
16: [50, 100, 150, 200, 250, 300, 350, 400, 500, 600, 700, 750, 800, 850, 900, 950],
17: [50, 100, 150, 200, 250, 300, 350, 400, 500, 600, 650, 700, 750, 800, 850, 900, 950],
18: [50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 600, 650, 700, 750, 800, 850, 900, 950],
19: [
50,
100,
150,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950
],
20: [
50,
100,
150,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1e3
]
};
function getScaleStepKeys(steps) {
const value = clamp(Math.round(steps), 3, 20);
return STEP_KEYS[value];
}
// src/modules/alpha.ts
function addAlpha(input, alpha) {
invariant(isValidColorModel(input), MESSAGES.invalid);
const value = normalizeAlpha(alpha);
if (value === void 0 || value === 1) {
return input;
}
return { ...input, alpha: value };
}
function addAlphaToHex(input, alpha) {
invariant(isHex(input), MESSAGES.inputHex);
invariant(isNumberInRange(alpha, 0, 1), MESSAGES.alpha);
if (alpha >= 1) {
return removeAlphaFromHex(input);
}
return `${removeAlphaFromHex(input)}${convertAlphaToHex(alpha)}`;
}
function convertAlphaToHex(input) {
invariant(isNumber(input), MESSAGES.inputNumber);
const alpha = normalizeAlpha(input);
return Math.round(alpha * 255).toString(16).padStart(2, "0");
}
function extractAlpha(input) {
if (Array.isArray(input)) {
return void 0;
}
return input.alpha;
}
function extractAlphaFromHex(input) {
invariant(isHex(input), MESSAGES.inputString);
const alpha = input.substring(7, 9);
if (!alpha) {
return 1;
}
return round(parseInt(alpha, 16) / 255);
}
function normalizeAlpha(value) {
if (value === void 0) {
return void 0;
}
return Math.min(1, Math.max(0, value > 1 ? value / 100 : value));
}
function removeAlphaFromHex(input) {
invariant(isHex(input), MESSAGES.inputHex);
if (input.length === 5) {
return input.substring(0, 4);
}
return input.substring(0, 7);
}
// src/converters/hex2rgb.ts
function hex2rgb(input) {
invariant(isHex(input), MESSAGES.inputHex);
const hex = formatHex(input).slice(1);
const alpha = extractAlphaFromHex(input);
return addAlpha(
{
r: parseInt(hex.charAt(0) + hex.charAt(1), 16),
g: parseInt(hex.charAt(2) + hex.charAt(3), 16),
b: parseInt(hex.charAt(4) + hex.charAt(5), 16)
},
alpha
);
}
// src/converters/rgb2hsl.ts
function rgb2hsl(input) {
const value = parseInput(input, "rgb");
const alpha = extractAlpha(input);
const rLimit = clamp(value.r, 0, 255) / 255;
const gLimit = clamp(value.g, 0, 255) / 255;
const bLimit = clamp(value.b, 0, 255) / 255;
const min = Math.min(rLimit, gLimit, bLimit);
const max = Math.max(rLimit, gLimit, bLimit);
const delta = max - min;
let h = 0;
let s;
const l = (max + min) / 2;
let rate;
switch (max) {
case rLimit:
rate = !delta ? 0 : (gLimit - bLimit) / delta;
h = 60 * rate;
break;
case gLimit:
rate = (bLimit - rLimit) / delta;
h = 60 * rate + 120;
break;
case bLimit:
rate = (rLimit - gLimit) / delta;
h = 60 * rate + 240;
break;
/* v8 ignore next 2 -- @preserve */
default:
break;
}
if (h < 0) {
h = 360 + h;
}
if (min === max) {
s = 0;
} else {
s = l < 0.5 ? delta / (2 * l) : delta / (2 - 2 * l);
}
return addAlpha(
{
h: Math.abs(+(h % 360).toFixed(2)),
s: +(s * 100).toFixed(2),
l: +(l * 100).toFixed(2)
},
alpha
);
}
// src/converters/hex2hsl.ts
function hex2hsl(input) {
invariant(isHex(input), MESSAGES.inputHex);
const alpha = extractAlphaFromHex(input);
return addAlpha(rgb2hsl(hex2rgb(input)), alpha);
}
// src/modules/gamma.ts
function srgbGammaDecode(input) {
const abs = Math.abs(input);
if (abs < 0.04045) {
return input / 12.92;
}
return (Math.sign(input) || 1) * ((abs + 0.055) / 1.055) ** 2.4;
}
function srgbGammaEncode(input) {
const abs = Math.abs(input);
const sign = input < 0 ? -1 : 1;
if (abs > 31308e-7) {
return sign * (abs ** (1 / 2.4) * 1.055 - 0.055);
}
return input * 12.92;
}
// src/converters/rgb2oklab.ts
var { cbrt } = Math;
function rgb2oklab(input, precision) {
const value = parseInput(input, "rgb");
const alpha = extractAlpha(input);
const [lr, lg, lb] = [
srgbGammaDecode(value.r / 255),
srgbGammaDecode(value.g / 255),
srgbGammaDecode(value.b / 255)
];
const l = cbrt(LRGB_TO_LMS[0][0] * lr + LRGB_TO_LMS[0][1] * lg + LRGB_TO_LMS[0][2] * lb);
const m = cbrt(LRGB_TO_LMS[1][0] * lr + LRGB_TO_LMS[1][1] * lg + LRGB_TO_LMS[1][2] * lb);
const s = cbrt(LRGB_TO_LMS[2][0] * lr + LRGB_TO_LMS[2][1] * lg + LRGB_TO_LMS[2][2] * lb);
const lab = restrictValues(
{
l: CLMS_TO_OKLAB[0][0] * l + CLMS_TO_OKLAB[0][1] * m + CLMS_TO_OKLAB[0][2] * s,
a: CLMS_TO_OKLAB[1][0] * l + CLMS_TO_OKLAB[1][1] * m + CLMS_TO_OKLAB[1][2] * s,
b: CLMS_TO_OKLAB[2][0] * l + CLMS_TO_OKLAB[2][1] * m + CLMS_TO_OKLAB[2][2] * s
},
precision
);
return addAlpha(lab, alpha);
}
// src/converters/hex2oklab.ts
function hex2oklab(input, precision) {
invariant(isHex(input), MESSAGES.inputHex);
const alpha = extractAlphaFromHex(input);
return addAlpha(rgb2oklab(hex2rgb(input), precision), alpha);
}
// src/converters/oklab2oklch.ts
var { atan2, sqrt } = Math;
function oklab2oklch(input, precision) {
const { l, a, b } = parseInput(input, "oklab");
const alpha = extractAlpha(input);
const c = sqrt(a ** 2 + b ** 2);
let h = (atan2(b, a) * RAD2DEG + 360) % 360;
if (c < 1e-6) {
h = 0;
}
return addAlpha(restrictValues({ l, c, h }, precision), alpha);
}
// src/converters/rgb2oklch.ts
function rgb2oklch(input, precision) {
const value = parseInput(input, "rgb");
const alpha = extractAlpha(input);
return addAlpha(oklab2oklch(rgb2oklab(value), precision), alpha);
}
// src/converters/hex2oklch.ts
function hex2oklch(input, precision) {
invariant(isHex(input), MESSAGES.inputHex);
const alpha = extractAlphaFromHex(input);
return addAlpha(rgb2oklch(hex2rgb(input), precision), alpha);
}
// src/modules/hue2rgb.ts
function hue2rgb(point, chroma2, h) {
invariant(isNumber(point) && isNumber(chroma2) && isNumber(h), MESSAGES.hueArgs);
let hue = h;
if (hue < 0) {
hue += 1;
}
if (hue > 1) {
hue -= 1;
}
if (hue < 1 / 6) {
return point + (chroma2 - point) * 6 * hue;
}
if (hue < 1 / 2) {
return chroma2;
}
if (hue < 2 / 3) {
return point + (chroma2 - point) * (2 / 3 - hue) * 6;
}
return point;
}
// src/converters/hsl2rgb.ts
function hsl2rgb(input) {
const value = parseInput(input, "hsl");
const alpha = extractAlpha(input);
const h = value.h / 360;
const s = value.s / 100;
const l = value.l / 100;
let r;
let g;
let b;
let point;
let chroma2;
if (s < 1e-4) {
r = l;
g = l;
b = l;
} else {
chroma2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
point = 2 * l - chroma2;
r = hue2rgb(point, chroma2, h + 1 / 3);
g = hue2rgb(point, chroma2, h);
b = hue2rgb(point, chroma2, h - 1 / 3);
}
return addAlpha(
{
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
},
alpha
);
}
// src/converters/rgb2hex.ts
function rgb2hex(input) {
const rgb = parseInput(input, "rgb");
const alpha = extractAlpha(input);
const hex = `#${[rgb.r, rgb.g, rgb.b].map((d) => `0${Math.floor(d).toString(16)}`.slice(-2)).join("")}`;
return alpha !== void 0 && alpha < 1 ? addAlphaToHex(hex, alpha) : hex;
}
// src/converters/hsl2hex.ts
function hsl2hex(input) {
const value = parseInput(input, "hsl");
const alpha = extractAlpha(input);
const hex = rgb2hex(hsl2rgb(value));
return alpha !== void 0 && alpha < 1 ? addAlphaToHex(hex, alpha) : hex;
}
// src/converters/hsl2oklab.ts
function hsl2oklab(input, precision) {
const value = parseInput(input, "hsl");
const alpha = extractAlpha(input);
return addAlpha(rgb2oklab(hsl2rgb(value), precision), alpha);
}
// src/converters/hsl2oklch.ts
function hsl2oklch(input, precision) {
const value = parseInput(input, "hsl");
const alpha = extractAlpha(input);
return addAlpha(rgb2oklch(hsl2rgb(value), precision), alpha);
}
// src/converters/oklab2rgb.ts
function oklab2rgb(input, precision = 0) {
const { l: L, a: A, b: B } = parseInput(input, "oklab");
const alpha = extractAlpha(input);
const l = (OKLAB_TO_CLMS[0][0] * L + OKLAB_TO_CLMS[0][1] * A + OKLAB_TO_CLMS[0][2] * B) ** 3;
const m = (OKLAB_TO_CLMS[1][0] * L + OKLAB_TO_CLMS[1][1] * A + OKLAB_TO_CLMS[1][2] * B) ** 3;
const s = (OKLAB_TO_CLMS[2][0] * L + OKLAB_TO_CLMS[2][1] * A + OKLAB_TO_CLMS[2][2] * B) ** 3;
const r = 255 * srgbGammaEncode(LMS_TO_LRGB[0][0] * l + LMS_TO_LRGB[0][1] * m + LMS_TO_LRGB[0][2] * s);
const g = 255 * srgbGammaEncode(LMS_TO_LRGB[1][0] * l + LMS_TO_LRGB[1][1] * m + LMS_TO_LRGB[1][2] * s);
const b = 255 * srgbGammaEncode(LMS_TO_LRGB[2][0] * l + LMS_TO_LRGB[2][1] * m + LMS_TO_LRGB[2][2] * s);
return addAlpha(
{
r: clamp(round(r, precision), 0, 255),
g: clamp(round(g, precision), 0, 255),
b: clamp(round(b, precision), 0, 255)
},
alpha
);
}
// src/converters/oklab2hex.ts
function oklab2hex(input) {
const value = parseInput(input, "oklab");
const alpha = extractAlpha(input);
const hex = rgb2hex(oklab2rgb(value));
return alpha !== void 0 && alpha < 1 ? addAlphaToHex(hex, alpha) : hex;
}
// src/converters/oklab2hsl.ts
function oklab2hsl(input) {
const value = parseInput(input, "oklab");
const alpha = extractAlpha(input);
return addAlpha(rgb2hsl(oklab2rgb(value)), alpha);
}
// src/converters/oklch2oklab.ts
var { sin, cos } = Math;
function oklch2oklab(input, precision) {
let { l, c, h } = parseInput(input, "oklch");
const alpha = extractAlpha(input);
if (Number.isNaN(h) || h < 0) {
h = 0;
}
return addAlpha(
restrictValues({ l, a: c * cos(h * DEG2RAD), b: c * sin(h * DEG2RAD) }, precision),
alpha
);
}
// src/converters/oklch2rgb.ts
function oklch2rgb(input, precision = 0) {
const value = parseInput(input, "oklch");
const alpha = extractAlpha(input);
return addAlpha(oklab2rgb(oklch2oklab(value), precision), alpha);
}
// src/converters/oklch2hex.ts
function oklch2hex(input) {
const value = parseInput(input, "oklch");
const alpha = extractAlpha(input);
const hex = rgb2hex(oklch2rgb(value));
return alpha !== void 0 && alpha < 1 ? addAlphaToHex(hex, alpha) : hex;
}
// src/converters/oklch2hsl.ts
function oklch2hsl(input) {
const value = parseInput(input, "oklch");
const alpha = extractAlpha(input);
return addAlpha(rgb2hsl(oklch2rgb(value)), alpha);
}
// src/format-css.ts
function getColorModel(input) {
if (isHex(input) || isNamedColor(input)) {
return "hex";
}
if (isHSL(input)) {
return "hsl";
}
if (isLAB(input)) {
return "oklab";
}
if (isLCH(input)) {
return "oklch";
}
if (isRGB(input)) {
return "rgb";
}
throw new Error(MESSAGES.invalid);
}
function getColorValue(input, output) {
const value = isNamedColor(input) ? cssColors[input.toLowerCase()] : input;
const from = getColorModel(value);
if (from === output) {
return value;
}
const converterKey = `${from}2${output}`;
const converter = converters_exports[converterKey];
if (!converter) {
throw new Error(`Converter not found for ${from} to ${output}`);
}
return converter(value);
}
function formatCSS(input, formatOrOptions) {
invariant(isHex(input) || isValidColorModel(input), MESSAGES.invalid);
const {
alpha,
format,
precision = PRECISION,
separator: baseSeparator = " "
} = isString(formatOrOptions, false) ? { format: formatOrOptions } : formatOrOptions ?? {};
const colorFormat = format || getColorModel(input);
const normalizedAlpha = isNumber(alpha) ? normalizeAlpha(alpha) : void 0;
const opacity2 = normalizedAlpha !== void 0 && normalizedAlpha !== 1 ? `${round(normalizedAlpha * 100)}%` : null;
let params = [];
let separator = baseSeparator;
switch (colorFormat) {
case "hsl": {
const { h, s, l } = getColorValue(input, "hsl");
params = [h, `${s}%`, `${l}%`];
break;
}
case "oklab": {
separator = " ";
const { l, a, b } = restrictValues(getColorValue(input, "oklab"), precision, false);
params = [`${round(l * 100, precision, false)}%`, a, b];
break;
}
case "oklch": {
separator = " ";
const { l, c, h } = restrictValues(getColorValue(input, "oklch"), precision, false);
params = [`${round(l * 100, precision, false)}%`, c, c === 0 ? "none" : h];
break;
}
case "rgb": {
const { r, g, b } = getColorValue(input, "rgb");
params = [r, g, b];
break;
}
default: {
const hex = removeAlphaFromHex(getColorValue(input, "hex"));
if (normalizedAlpha !== void 0 && normalizedAlpha !== 1) {
return `${hex}${convertAlphaToHex(normalizedAlpha)}`;
}
return hex;
}
}
return `${colorFormat}(${params.join(separator)}${opacity2 ? ` / ${opacity2}` : ""})`;
}
// src/extract-color-parts.ts
var MODEL = "(rgb|hsl|oklab|oklch)a?";
var SEP = "(?:\\s*[,/]\\s*|\\s+)";
var VALUE = "(none|[\\d%.-]+(?:deg|grad|rad|turn)?)";
var colorRegex = new RegExp(
`${MODEL}\\s*\\(\\s*${VALUE}${SEP}${VALUE}${SEP}${VALUE}(?:${SEP}${VALUE})?\\s*\\)`,
"i"
);
function parseAngle(value) {
const number_ = parseFloat(value);
let result;
if (value.endsWith("grad")) {
result = number_ * 0.9;
} else if (value.endsWith("rad")) {
result = number_ * (180 / Math.PI);
} else if (value.endsWith("turn")) {
result = number_ * 360;
} else {
result = number_;
}
return Math.round(result * 1e5) / 1e5;
}
function extractColorParts(input) {
invariant(isString(input), MESSAGES.inputString);
if (isHex(input)) {
const keys2 = COLOR_KEYS.rgb;
const { r, g, b } = hex2rgb(input);
const alpha2 = extractAlphaFromHex(input);
return {
model: "rgb",
[keys2[0]]: r,
[keys2[1]]: g,
[keys2[2]]: b,
alpha: alpha2 < 1 ? alpha2 : void 0
};
}
const matches = colorRegex.exec(input);
invariant(hasValidMatches(matches), MESSAGES.invalidCSS);
let rawAlpha = 1;
if (matches[5]) {
rawAlpha = matches[5] === "none" ? 0 : parseFloat(matches[5]);
}
const model = matches[1];
const keys = COLOR_KEYS[model];
const alpha = normalizeAlpha(rawAlpha);
const parseValue = (value, index) => {
if (value === "none") {
return 0;
}
const isHue = model === "hsl" && index === 0 || model === "oklch" && index === 2;
if (isHue) {
return parseAngle(value);
}
const parsedValue = parseFloat(value);
const isPercent = value.includes("%");
if (!isPercent) {
return parsedValue;
}
if (model === "oklch") {
if (index === 1) {
return parsedValue * 0.4 / 100;
}
} else if (model === "oklab" && (index === 1 || index === 2)) {
return parsedValue * 0.4 / 100;
}
return parsedValue;
};
const values = [parseValue(matches[2], 0), parseValue(matches[3], 1), parseValue(matches[4], 2)];
if (model === "oklab") {
invariant(values[1] >= -0.4 && values[1] <= 0.4, MESSAGES.invalidRange);
invariant(values[2] >= -0.4 && values[2] <= 0.4, MESSAGES.invalidRange);
} else if (model === "oklch") {
invariant(values[1] >= 0 && values[1] <= 0.4, MESSAGES.invalidRange);
}
return {
model,
[keys[0]]: values[0],
[keys[1]]: values[1],
[keys[2]]: values[2],
alpha: alpha < 1 ? alpha : void 0
};
}
// src/modules/parsed-color.ts
var toHexConverters = {
hsl: hsl2hex,
oklab: oklab2hex,
oklch: oklch2hex,
rgb: rgb2hex
};
var fromHexConverters = {
hsl: hex2hsl,
oklab: hex2oklab,
oklch: hex2oklch,
rgb: hex2rgb
};
var modelConverters = {
hsl: {
oklab: oklab2hsl,
oklch: oklch2hsl,
rgb: rgb2hsl
},
oklab: {
hsl: hsl2oklab,
oklch: oklch2oklab,
rgb: rgb2oklab
},
oklch: {
hsl: hsl2oklch,
oklab: oklab2oklch,
rgb: rgb2oklch
},
rgb: {
hsl: hsl2rgb,
oklab: oklab2rgb,
oklch: oklch2rgb
}
};
var ParsedColorImpl = class {
constructor(type, value, alpha) {
__publicField(this, "__parsed", true);
__publicField(this, "type");
__publicField(this, "alpha");
__publicField(this, "_sourceValue");
__publicField(this, "_cache", {});
this.type = type;
this.alpha = alpha;
this._sourceValue = value;
this._cache[type] = value;
}
get hex() {
return this._getAs("hex");
}
get hsl() {
return this._getAs("hsl");
}
get rgb() {
return this._getAs("rgb");
}
get oklab() {
return this._getAs("oklab");
}
get oklch() {
return this._getAs("oklch");
}
toCSS(options) {
const format = options?.format ?? this.type;
const alpha = options?.alpha ?? (this.alpha < 1 ? this.alpha : void 0);
if (format === "hex") {
const hex = removeAlphaFromHex(this.hex);
if (alpha !== void 0 && alpha < 1) {
return `${hex}${convertAlphaToHex(alpha)}`;
}
return hex;
}
return formatCSS(this[format], { ...options, format, alpha });
}
_getAs(target) {
if (this._cache[target]) {
return this._cache[target];
}
let result;
if (this.type === "hex") {
result = target === "hex" ? this._sourceValue : fromHexConverters[target](this._sourceValue);
} else if (target === "hex") {
const tuple = Object.values(this._sourceValue);
result = toHexConverters[this.type](tuple);
if (this.alpha < 1) {
result = `${result}${convertAlphaToHex(this.alpha)}`;
}
} else {
const converter = modelConverters[target]?.[this.type];
if (!converter) {
result = fromHexConverters[target](this.hex);
} else {
const tuple = Object.values(this._sourceValue);
result = converter(tuple);
}
}
this._cache[target] = result;
return result;
}
};
function isParsedColor(input) {
return typeof input === "object" && input !== null && "__parsed" in input && input.__parsed === true;
}
function resolveColor(input) {
if (isParsedColor(input)) {
return input;
}
if (isPlainObject(input)) {
const { alpha: rawAlpha = 1 } = input;
const alpha2 = clamp(normalizeAlpha(rawAlpha), 0, 1);
if (isHSL(input)) {
return new ParsedColorImpl(
"hsl",
{ h: clamp(input.h, 0, 360), s: clamp(input.s), l: clamp(input.l) },
alpha2
);
}
if (isRGB(input)) {
return new ParsedColorImpl(
"rgb",
{ r: clamp(input.r, 0, 255), g: clamp(input.g, 0, 255), b: clamp(input.b, 0, 255) },
alpha2
);
}
if (isLAB(input)) {
return new ParsedColorImpl("oklab", { l: input.l, a: input.a, b: input.b }, alpha2);
}
if (isLCH(input)) {
return new ParsedColorImpl("oklch", { l: input.l, c: input.c, h: input.h }, alpha2);
}
}
const value = isNamedColor(input) ? cssColors[input.toLowerCase()] : input;
if (isHex(value)) {
const alpha2 = extractAlphaFromHex(value);
const hex = removeAlphaFromHex(value);
return new ParsedColorImpl("hex", hex, alpha2);
}
const parts = extractColorParts(value);
const { alpha, model, ...color } = parts;
const colorValue = ["oklab", "oklch"].includes(model) ? normalizeOkLightness(color) : color;
const colorType = model;
return new ParsedColorImpl(colorType, colorValue, alpha ?? 1);
}
// src/convertCSS.ts
function convertCSS(input, format) {
const parsed = resolveColor(input);
return formatCSS(parsed[format], { format, alpha: parsed.alpha < 1 ? parsed.alpha : void 0 });
}
// src/apca.ts
var APCA_VERSION = "0.0.98G-4g";
var mainTRC = 2.4;
var normBG = 0.56;
var normTXT = 0.57;
var revBG = 0.65;
var revTXT = 0.62;
var sRco = 0.2126729;
var sGco = 0.7151522;
var sBco = 0.072175;
var blkThreshold = 0.022;
var blkClamp = 1.414;
var scaleBoW = 1.14;
var scaleWoB = 1.14;
var loBoWOffset = 0.027;
var loWoBOffset = 0.027;
var loClip = 0.1;
var deltaYmin = 5e-4;
function softClamp(Y) {
return Y > blkThreshold ? Y : Y + (blkThreshold - Y) ** blkClamp;
}
function sRGBtoY(r, g, b) {
return sRco * (r / 255) ** mainTRC + sGco * (g / 255) ** mainTRC + sBco * (b / 255) ** mainTRC;
}
function apcaContrast(background, foreground) {
invariant(isString(background), MESSAGES.inputString);
invariant(isString(foreground), MESSAGES.inputString);
const bg = resolveColor(background).rgb;
const fg = resolveColor(foreground).rgb;
const txtY = softClamp(sRGBtoY(fg.r, fg.g, fg.b));
const bgY = softClamp(sRGBtoY(bg.r, bg.g, bg.b));
if (Math.abs(bgY - txtY) < deltaYmin) {
return 0;
}
const SAPC = bgY > txtY ? (bgY ** normBG - txtY ** normTXT) * scaleBoW : (bgY ** revBG - txtY ** revTXT) * scaleWoB;
if (Math.abs(SAPC) < loClip) {
return 0;
}
const Lc = SAPC > 0 ? (SAPC - loBoWOffset) * 100 : (SAPC + loWoBOffset) * 100;
return round(Lc, 5);
}
// src/brightness-difference.ts
function brightnessDifference(left, right, precision = PRECISION) {
invariant(isString(left), MESSAGES.left);
invariant(isString(right), MESSAGES.right);
const RGBLeft = resolveColor(left).rgb;
const RGBRight = resolveColor(right).rgb;
const brightnessLeft = (RGBLeft.r * 299 + RGBLeft.g * 587 + RGBLeft.b * 114) / 1e3;
const brightnessRight = (RGBRight.r * 299 + RGBRight.g * 587 + RGBRight.b * 114) / 1e3;
return round(Math.abs(brightnessRight - brightnessLeft), precision);
}
// src/chroma.ts
function chroma(input) {
invariant(isString(input), MESSAGES.inputString);
const { r, g, b } = resolveColor(input).rgb;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
return round((max - min) / 255, 4);
}
// src/color-difference.ts
function colorDifference(left, right) {
invariant(isString(left), MESSAGES.left);
invariant(isString(right), MESSAGES.right);
const RGBLeft = resolveColor(left).rgb;
const RGBRight = resolveColor(right).rgb;
return Math.max(RGBLeft.r, RGBRight.r) - Math.min(RGBLeft.r, RGBRight.r) + (Math.max(RGBLeft.g, RGBRight.g) - Math.min(RGBLeft.g, RGBRight.g)) + (Math.max(RGBLeft.b, RGBRight.b) - Math.min(RGBLeft.b, RGBRight.b));
}
// src/luminance.ts
function luminance(input) {
invariant(isString(input), MESSAGES.inputString);
const { r, g, b } = resolveColor(input).rgb;
const lr = srgbGammaDecode(r / 255);
const lg = srgbGammaDecode(g / 255);
const lb = srgbGammaDecode(b / 255);
return round(0.2126 * lr + 0.7152 * lg + 0.0722 * lb, 4);
}
// src/contrast.ts
function contrast(left, right) {
invariant(isString(left), MESSAGES.left);
invariant(isString(right), MESSAGES.right);
const LuminanceLeft = luminance(left);
const LuminanceRight = luminance(right);
return round(
LuminanceLeft >= LuminanceRight ? (LuminanceLeft + 0.05) / (LuminanceRight + 0.05) : (LuminanceRight + 0.05) / (LuminanceLeft + 0.05)
);
}
// src/compare.ts
function compare(left, right) {
invariant(isString(left), MESSAGES.left);
invariant(isString(right), MESSAGES.right);
const colorThreshold = 500;
const brightnessThreshold = 125;
const colorDifference2 = colorDifference(left, right);
const contrast2 = contrast(left, right);
const brightnessDifference2 = brightnessDifference(left, right);
const isBright = brightnessDifference2 >= brightnessThreshold;
const hasEnoughDifference = colorDifference2 >= colorThreshold;
let compliant = 0;
if (isBright && hasEnoughDifference) {
compliant = 2;
} else if (isBright || hasEnoughDifference) {
compliant = 1;
}
return {
brightnessDifference: brightnessDifference2,
colorDifference: colorDifference2,
compliant,
contrast: contrast2,
largeAA: contrast2 >= 3,
largeAAA: contrast2 >= 4.5,
normalAA: contrast2 >= 4.5,
normalAAA: contrast2 >= 7
};
}
// src/modules/updater.ts
function updater(key, operator, format) {
return (input, amount) => {
invariant(isString(input) || isParsedColor(input), MESSAGES.inputString);
invariant(isNumberInRange(amount, 0, 100), MESSAGES.amount);
const parsed = resolveColor(input);
const { hsl } = parsed;
const output = format ?? parsed.type;
return formatCSS(
{
...hsl,
[key]: clamp(hsl[key] + (operator === "+" ? amount : -amount), 0, 100)
},
{ format: output, alpha: parsed.alpha < 1 ? parsed.alpha : void 0 }
);
};
}
// src/darken.ts
function darken(input, amount, format) {
return updater("l", "-", format)(input, amount);
}
// src/delta-e.ts
var DELTA_E_JND = 0.02;
function deltaE(left, right, precision = PRECISION) {
invariant(isString(left), MESSAGES.left);
invariant(isString(right), MESSAGES.right);
const lab1 = resolveColor(left).oklab;
const lab2 = resolveColor(right).oklab;
return round(
Math.sqrt((lab1.l - lab2.l) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2),
precision
);
}
// src/desaturate.ts
function desaturate(input, amount, format) {
return updater("s", "-", format)(input, amount);
}
// src/grayscale.ts
function grayscale(input, format) {
invariant(isString(input), MESSAGES.inputString);
const parsed = resolveColor(input);
const lch = parsed.oklch;
const output = format ?? parsed.type;
return formatCSS(
{ ...lch, c: 0 },
{ format: output, alpha: parsed.alpha < 1 ? parsed.alpha : void 0 }
);
}
// src/rotate.ts
function rotate(input, degrees, format) {
invariant(isString(input), MESSAGES.inputString);
invariant(isNumberInRange(degrees, -360, 360), MESSAGES.degreesRange);
const parsed = resolveColor(input);
const color = parsed.hsl;
const output = format ?? parsed.type;
return formatCSS(
{
...color,
h: constrainDegrees(color.h, degrees)
},
{ format: output, alpha: parsed.alpha < 1 ? parsed.alpha : void 0 }
);
}
// src/invert.ts
function invert(input) {
invariant(isString(input), MESSAGES.inputString);
return rotate(input, 180);
}
// src/lighten.ts
function lighten(input, amount, format) {
return updater("l", "+", format)(input, amount);
}
// src/mix.ts
function interpolateAlpha(a1, a2, ratio) {
return a1 + (a2 - a1) * ratio;
}
function interpolateHue(h1, h2, c1, c2, ratio, mode = "shorter") {
if (c1 < 1e-4) {
return h2;
}
if (c2 < 1e-4) {
return h1;
}
let diff = h2 - h1;
switch (mode) {
case "shorter": {
if (diff > 180) {
diff -= 360;
} else if (diff < -180) {
diff += 360;
}
break;
}
case "longer": {
if (diff > 0 && diff < 180) {
diff -= 360;
} else if (diff > -180 && diff <= 0) {
diff += 360;
}
break;
}
case "increasing": {
if (diff < 0) {
diff += 360;
}
break;
}
case "decreasing": {
if (diff > 0) {
diff -= 360;
}
break;
}
}
const result = h1 + diff * ratio;
return (result % 360 + 360) % 360;
}
function mixInHSL(c1, c2, ratio, hueMode) {
return {
h: interpolateHue(c1.h, c2.h, c1.s, c2.s, ratio, hueMode),
s: c1.s + (c2.s - c1.s) * ratio,
l: c1.l + (c2.l - c1.l) * ratio
};
}
function mixInOkLab(c1, c2, ratio) {
return {
l: c1.l + (c2.l - c1.l) * ratio,
a: c1.a + (c2.a - c1.a) * ratio,
b: c1.b + (c2.b - c1.b) * ratio
};
}
function mixInOkLCH(c1, c2, ratio, hueMode) {
return {
l: c1.l + (c2.l - c1.l) * ratio,
c: c1.c + (c2.c - c1.c) * ratio,
h: interpolateHue(c1.h, c2.h, c1.c, c2.c, ratio, hueMode)
};
}
function mixInRGB(c1, c2, ratio) {
return {
r: Math.round(c1.r + (c2.r - c1.r) * ratio),
g: Math.round(c1.g + (c2.g - c1.g) * ratio),
b: Math.round(c1.b + (c2.b - c1.b) * ratio)
};
}
function mix(color1, color2, ratio = 0.5, formatOrOptions) {
invariant(isString(color1), MESSAGES.inputString);
invariant(isString(color2), MESSAGES.inputString);
invariant(isNumberInRange(ratio, 0, 1), MESSAGES.ratioRange);
const {
format,
hue = "shorter",
space = "oklch"
} = isString(formatOrOptions, false) ? { format: formatOrOptions } : formatOrOptions ?? {};
const parsed1 = resolveColor(color1);
const parsed2 = resolveColor(color2);
const output = format ?? parsed1.type;
let color;
switch (space) {
case "hsl": {
color = mixInHSL(parsed1.hsl, parsed2.hsl, ratio, hue);
break;
}
case "oklab": {
color = mixInOkLab(parsed1.oklab, parsed2.oklab, ratio);
break;
}
case "rgb": {
color = mixInRGB(parsed1.rgb, parsed2.rgb, ratio);
break;
}
case "oklch":
default: {
color = mixInOkLCH(parsed1.oklch, parsed2.oklch, ratio, hue);
break;
}
}
const alpha = interpolateAlpha(parsed1.alpha, parsed2.alpha, ratio);
return formatCSS(color, { format: output, alpha: alpha < 1 ? alpha : void 0 });
}
// src/opacify.ts
function opacify(input, alpha, format) {
invariant(isString(input), MESSAGES.inputString);
invariant(isNumberInRange(alpha, 0, 1), MESSAGES.alpha);
const parsed = resolveColor(input);
return formatCSS(parsed.oklch, { format: format ?? parsed.type, alpha });
}
// src/opacity.ts
function opacity(input) {
invariant(isString(input), MESSAGES.inputString);
return resolveColor(input).alpha;
}
// src/readable-color.ts
function pickByContrast(darkContrast, lightContrast, darkColor, lightColor) {
return darkContrast >= lightContrast ? darkColor : lightColor;
}
function readableColor(backgroundColor, methodOrOptions) {
const {
darkColor = "#000000",
lightColor = "#ffffff",
method = "yiq",
threshold
} = isString(methodOrOptions, false) ? { method: methodOrOptions } : methodOrOptions ?? {};
invariant(isString(backgroundColor), MESSAGES.inputString);
const parsed = resolveColor(backgroundColor);
const bgCSS = parsed.toCSS();
switch (method) {
case "yiq": {
const yiqThreshold = threshold ?? 128;
invariant(yiqThreshold >= 0 && yiqThreshold <= 255, MESSAGES.threshold);
const { r, g, b } = parsed.rgb;
const yiq = (r * 299 + g * 587 + b * 114) / 1e3;
return yiq >= yiqThreshold ? darkColor : lightColor;
}
case "wcag": {
const wcagThreshold = threshold ?? 0.5;
invariant(wcagThreshold >= 0 && wcagThreshold <= 1, MESSAGES.thresholdNormalized);
const lum = luminance(bgCSS);
return lum >= wcagThreshold ? darkColor : lightColor;
}
case "contrast": {
const darkContrast = contrast(darkColor, bgCSS);
const lightContrast = contrast(lightColor, bgCSS);
return pickByContrast(darkContrast, lightContrast, darkColor, lightColor);
}
case "oklab": {
const oklabThreshold = threshold ?? 0.5;
invariant(oklabThreshold >= 0 && oklabThreshold <= 1, MESSAGES.thresholdNormalized);
const { l } = parsed.oklab;
return l >= oklabThreshold ? darkColor : lightColor;
}
case "apca": {
const darkContrast = Math.abs(apcaContrast(bgCSS, darkColor));
const lightContrast = Math.abs(apcaContrast(bgCSS, lightColor));
return pickByContrast(darkContrast, lightContrast, darkColor, lightColor);
}
/* v8 ignore next 3 -- @preserve */
default: {
throw new Error(`Unknown method: ${method}`);
}
}
}
// src/saturate.ts
function saturate(input, amount, format) {
return updater("s", "+", format)(input, amount);
}
// src/modules/linear-rgb.ts
function multiplyMatrix(matrix, vector) {
return [
matrix[0][0] * vector[0] + matrix[0][1] * vector[1] + matrix[0][2] * vector[2],
matrix[1][0] * vector[0] + matrix[1][1] * vector[1] + matrix[1][2] * vector[2],
matrix[2][0] * vector[0] + matrix[2][1] * vector[1] + matrix[2][2] * vector[2]
];
}
function isInGamut(color) {
return color.every((component) => component >= 0 - GAMUT_EPSILON && component <= 1 + GAMUT_EPSILON);
}
function oklabToLinearP3(L, a, b) {
return multiplyMatrix(SRGB_TO_P3, oklabToLinearSRGB(L, a, b));
}
function oklabToLinearSRGB(L, a, b) {
const l = (OKLAB_TO_CLMS[0][0] * L + OKLAB_TO_CLMS[0][1] * a + OKLAB_TO_CLMS[0][2] * b) ** 3;
const m = (OKLAB_TO_CLMS[1][0] * L + OKLAB_TO_CLMS[1][1] * a + OKLAB_TO_CLMS[1][2] * b) ** 3;
const s = (OKLAB_TO_CLMS[2][0] * L + OKLAB_TO_CLMS[2][1] * a + OKLAB_TO_CLMS[2][2] * b) ** 3;
return [
LMS_TO_LRGB[0][0] * l + LMS_TO_LRGB[0][1] * m + LMS_TO_LRGB[0][2] * s,
LMS_TO_LRGB[1][0] * l + LMS_TO_LRGB[1][1] * m + LMS_TO_LRGB[1][2] * s,
LMS_TO_LRGB[2][0] * l + LMS_TO_LRGB[2][1] * m + LMS_TO_LRGB[2][2] * s
];
}
// src/to-gamut.ts
function toGamut(input, format) {
invariant(isString(input), MESSAGES.inputString);
const parsed = resolveColor(input);
const lch = parsed.oklch;
const output = format ?? parsed.type;
const alpha = parsed.alpha < 1 ? parsed.alpha : void 0;
if (lch.l <= 0) {
return formatCSS({ r: 0, g: 0, b: 0 }, { format: output, alpha });
}
if (lch.l >= 1) {
return formatCSS({ r: 255, g: 255, b: 255 }, { format: output, alpha });
}
const lab = oklch2oklab(lch, 16);
if (isInGamut(oklabToLinearSRGB(lab.l, lab.a, lab.b))) {
return formatCSS(lch, { format: output, alpha });
}
const epsilon = GAMUT_EPSILON;
let low = 0;
let high = lch.c;
while (high - low > epsilon) {
const mid = (low + high) / 2;
const midLab = oklch2oklab({ l: lch.l, c: mid, h: lch.h }, 16);
if (isInGamut(oklabToLinearSRGB(midLab.l, midLab.a, midLab.b))) {
low = mid;
} else {
high = mid;
}
}
return formatCSS({ l: lch.l, c: low, h: lch.h }, { format: output, alpha });
}
// src/transparentize.ts
function transparentize(input, alpha, format) {
invariant(isString(input), MESSAGES.inputString);
invariant(isNumberInRange(alpha, -1, 1), MESSAGES.alphaAdjustment);
const parsed = resolveColor(input);
const value = round(clamp(parsed.alpha - alpha, 0, 1));
return formatCSS(parsed.oklch, { format: format ?? parsed.type, alpha: value });
}
// src/colorizr.ts
var Colorizr = class {
constructor(color, options = {}) {
/** The alpha/opacity value (0-1). */
__publicField(this, "alpha");
__publicField(this, "hex");
__publicField(this, "hsl");
__publicField(this, "oklab");
__publicField(this, "oklch");
__publicField(this, "rgb");
__publicField(this, "type");
invariant(!!color, MESSAGES.colorRequired);
const parsed = resolveColor(color);
this.hex = parsed.hex;
this.hsl = parsed.hsl;
this.oklab = parsed.oklab;
this.oklch = parsed.oklch;
this.rgb = parsed.rgb;
this.alpha = parsed.alpha;
this.type = options.format ?? parsed.type;
}
/**
* Get CSS string
*/
get css() {
return this.currentColor;
}
/**
* Get the red value
*/
get red() {
return this.rgb.r;
}
/**
* Get the green value
*/
get green() {
return this.rgb.g;
}
/**
* Get the blue value
*/
get blue() {
return this.rgb.b;
}
/**
* Get the hue value
*/
get hue() {
return this.hsl.h;
}
/**
* Get the saturation value
*/
get saturation() {
return this.hsl.s;
}
/**
* Get the lightness value
*/
get lightness() {
return this.hsl.l;
}
/**
* Get the luminance value
*/
get luminance() {
return luminance(this.currentColor);
}
/**
* Get the chroma value
*/
get chroma() {
return chroma(this.currentColor);
}
/**
* Get the opacity value.
*/
get opacity() {
return opacity(this.currentColor);
}
/**
* Get the most readable color (light or dark) for this color as a background.
*/
get readableColor() {
return readableColor(this.currentColor);
}
get currentColor() {
return formatCSS(this[this.type], { format: this.type, alpha: this.alpha });
}
/**
* Get the brightness difference between this color and another.
*
* @param input - The color to compare against.
* @returns The brightness difference value.
*/
brightnessDifference(input) {
return brightnessDifference(this.curren