yummy.cricket/rainbow-quox/script/color/conv.ts

127 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

export type Oklch = { type: 'oklch', l: number, c: number, h: number };
export type Oklab = { type: 'oklab', l: number, a: number, b: number };
export type Srgb = { type: 'srgb', r: number, g: number, b: number };
export type Lrgb = { type: 'lrgb', r: number, g: number, b: number };
export type AnyColor = Oklch | Oklab | Srgb;
type Deg = number;
type Rad = number;
function deg2rad(θ: Deg): Rad { return θ / 180 * Math.PI; }
function rad2deg(θ: Rad): Deg { return θ * 180 / Math.PI; }
function dcos(θ: Deg): number { return Math.cos(deg2rad(θ)); }
function dsin(θ: Deg): number { return Math.sin(deg2rad(θ)); }
function datan2(b: number, a: number): Deg { return rad2deg(Math.atan2(b, a)); }
export function normDeg(θ: Deg): Deg {
θ %= 360;
return θ < 0 ? θ + 360 : θ;
}
export function oklch2oklab({ l, c, h }: Oklch): Oklab {
return { type: 'oklab', l, a: c * dcos(h), b: c * dsin(h) };
}
export function oklab2oklch({ l, a, b }: Oklab): Oklch {
return { type: 'oklch', l, c: Math.sqrt(a*a + b*b), h: datan2(b, a) }
}
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
export function lrgb2oklab({ r, g, b }: Lrgb): Oklab {
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
const l_ = Math.cbrt(l);
const m_ = Math.cbrt(m);
const s_ = Math.cbrt(s);
return { type: 'oklab',
l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
};
}
export function oklab2lrgb({ l, a, b }: Oklab): Lrgb {
const L_ = l + 0.3963377774 * a + 0.2158037573 * b;
const M_ = l - 0.1055613458 * a - 0.0638541728 * b;
const S_ = l - 0.0894841775 * a - 1.2914855480 * b;
const L = L_ * L_ * L_;
const M = M_ * M_ * M_;
const S = S_ * S_ * S_;
return { type: 'lrgb',
r: clamp(+4.0767416621 * L - 3.3077115913 * M + 0.2309699292 * S),
g: clamp(-1.2684380046 * L + 2.6097574011 * M - 0.3413193965 * S),
b: clamp(-0.0041960863 * L - 0.7034186147 * M + 1.7076147010 * S),
};
}
function clamp(x: number): number {
return Math.max(0, Math.min(1, x));
}
// https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
function γ(x: number): number {
return x >= 0.0031308 ? 1.055 * x ** (1/2.4) - 0.055
: 12.92 * x;
}
function γ̂(x: number): number {
return x >= 0.04045 ? ((x + 0.055)/1.055) ** 2.4
: x / 12.92;
}
export function lrgb2srgb({ r, g, b }: Lrgb): Srgb {
return { type: 'srgb', r: γ(r), g: γ(g), b: γ(b) };
}
export function srgb2lrgb({ r, g, b }: Srgb): Lrgb {
return { type: 'lrgb', r: γ̂(r), g: γ̂(g), b: γ̂(b) };
}
export function oklab2srgb(c: Oklab): Srgb {
return lrgb2srgb(oklab2lrgb(c));
}
export function oklch2srgb(c: Oklch): Srgb {
return oklab2srgb(oklch2oklab(c));
}
export function srgb2oklab(c: Srgb): Oklab {
return lrgb2oklab(srgb2lrgb(c));
}
export function srgb2oklch(c: Srgb): Oklch {
return oklab2oklch(srgb2oklab(c));
}
export function toOklch(c: AnyColor): Oklch {
switch (c.type) {
case 'oklch': return c;
case 'oklab': return oklab2oklch(c);
case 'srgb': return srgb2oklch(c);
}
}
export function toOklab(c: AnyColor): Oklab {
switch (c.type) {
case 'oklch': return oklch2oklab(c);
case 'oklab': return c;
case 'srgb': return srgb2oklab(c);
}
}
export function toSrgb(c: AnyColor): Srgb {
switch (c.type) {
case 'oklch': return oklch2srgb(c);
case 'oklab': return oklab2srgb(c);
case 'srgb': return c;
}
}