@atlaskit/adf-schema
Version:
Shared package that contains the ADF-schema (json) and ProseMirror node/mark specs
128 lines (124 loc) • 3.79 kB
JavaScript
// https://en.wikipedia.org/wiki/HCL_color_space
// https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
// https://en.wikipedia.org/wiki/CIELAB_color_space
const clamp = (i, min, max) => Math.round(Math.min(Math.max(i, min), max));
const expandShorthandHex = input => input.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => r + r + g + g + b + b);
const rgbFromHex = input => {
const fullHex = expandShorthandHex(input);
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
return result === null ? null : {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
};
const rgbToHex = ({
r,
g,
b
}) => {
const convertComponent = c => {
const cBase16 = c.toString(16);
return cBase16.length === 1 ? `0${cBase16}` : cBase16;
};
return `#${convertComponent(r)}${convertComponent(g)}${convertComponent(b)}`;
};
const rgbToXyz = rgb => {
const convertRgbComponent = c => c > 0.04045 ? Math.pow((c + 0.055) / 1.055, 2.4) : c / 12.92;
const convertXyzComponent = c => c > 0.008856452 ? Math.pow(c, 1 / 3) : c / 0.12841855 + 0.137931034;
const r = convertRgbComponent(rgb.r / 255);
const g = convertRgbComponent(rgb.g / 255);
const b = convertRgbComponent(rgb.b / 255);
return {
x: convertXyzComponent((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / 0.95047),
y: convertXyzComponent(0.2126729 * r + 0.7151522 * g + 0.072175 * b),
z: convertXyzComponent((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / 1.08883)
};
};
const xyzToLab = ({
x,
y,
z
}) => ({
l: Math.max(116 * y - 16, 0),
a: 500 * (x - y),
b: 200 * (y - z)
});
const labToLch = ({
l,
a,
b
}) => {
let h = (Math.atan2(b, a) * (180 / Math.PI) + 360) % 360;
const c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
if (Math.round(c * 10000) === 0) h = Number.NaN;
return {
l,
c,
h
};
};
const lchToLab = ({
l,
c,
h
}) => {
const convertH = Number.isNaN(h) ? 0 : h * (Math.PI / 180);
return {
l,
a: Math.cos(convertH) * c,
b: Math.sin(convertH) * c
};
};
const labToXyz = ({
l,
a,
b
}) => {
const convertComponent = c => c > 0.206896552 ? Math.pow(c, 3) : 0.12841855 * (c - 0.137931034);
const y = (l + 16) / 116;
const x = a / 500 + y;
const z = y - b / 200;
return {
x: convertComponent(x) * 0.95047,
y: convertComponent(y),
z: convertComponent(z) * 1.08883
};
};
const xyzToRgb = ({
x,
y,
z
}) => {
const convertComponent = c => 255 * (c <= 0.00304 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055);
return {
r: clamp(convertComponent(3.2404542 * x - 1.5371385 * y - 0.4985314 * z), 0, 255),
g: clamp(convertComponent(-0.969266 * x + 1.8760108 * y + 0.041556 * z), 0, 255),
b: clamp(convertComponent(0.0556434 * x - 0.2040259 * y + 1.0572252 * z), 0, 255)
};
};
const rgbToLch = rgb => labToLch(xyzToLab(rgbToXyz(rgb)));
const lchToRgb = lch => xyzToRgb(labToXyz(lchToLab(lch)));
export const clampLightness = (color, newPercent) => {
const rgb = rgbFromHex(color);
if (rgb === null) {
return color;
}
// LCH (rather than HSL) gives the best results here as the L component in LCH is based on human color perception
const lch = rgbToLch(rgb);
lch.l = clamp(newPercent, 0, 100);
return rgbToHex(lchToRgb(lch));
};
const getLightness = color => {
const rgb = rgbFromHex(color);
if (rgb === null) {
return 0;
}
const lch = rgbToLch(rgb);
return lch.l;
};
export const getDarkModeLCHColor = currentBackgroundColor => {
const lightness = getLightness(currentBackgroundColor);
const newLightness = Math.abs(100 - lightness);
return clampLightness(currentBackgroundColor, newLightness).toUpperCase();
};