pex-color
Version:
Color utilities (css, p3, hex, hsl, hsv, hwb, lab, lch, xyz, okhsl, okhsv, oklab, oklch, hpluv, hsluv, lchuv, bytes) for PEX.
126 lines (98 loc) • 2.76 kB
JavaScript
import { toLinear } from "./linear.js";
import { fromOklab } from "./oklab.js";
import {
oklabToLinear,
linearToOklab,
toe,
toeInv,
getStMax,
TAU,
} from "./utils.js";
/**
* @typedef {number[]} okhsv
*
* All components in the range 0 <= x <= 1
* @see {@link https://bottosson.github.io/posts/colorpicker/#hsv-2}
*/
const S0 = 0.5;
/**
* Updates a color based on Okhsv values and alpha.
* @alias module:pex-color.fromOkhsv
* @param {import("./color.js").color} color
* @param {number} h
* @param {number} s
* @param {number} v
* @param {number} [α]
* @returns {import("./color.js").color}
*/
export function fromOkhsv(color, h, s, v, α) {
let L = toeInv(v);
let a = 0; // null
let b = 0; // null
// Avoid processing gray or colors with undefined hues
if (L !== 0 && s !== 0) {
const a_ = Math.cos(TAU * h);
const b_ = Math.sin(TAU * h);
const [S, T] = getStMax(a_, b_);
const k = 1 - S0 / S;
const Lv = 1 - (s * S0) / (S0 + T - T * k * s);
const Cv = (s * T * S0) / (S0 + T - T * k * s);
L = v * Lv;
let C = v * Cv;
const Lvt = toeInv(Lv);
const Cvt = (Cv * Lvt) / Lv;
const Lnew = toeInv(L);
C = (C * Lnew) / L;
L = Lnew;
oklabToLinear(Lvt, a_ * Cvt, b_ * Cvt, color);
const scaleL = Math.cbrt(1 / Math.max(color[0], color[1], color[2], 0));
L = L * scaleL;
C = C * scaleL;
a = C * a_;
b = C * b_;
}
return fromOklab(color, L, a, b, α);
}
/**
* Returns an Okhsv representation of a given color.
* @alias module:pex-color.toOkhsv
* @param {import("./color.js").color} color
* @param {Array} out
* @returns {okhsv}
*/
export function toOkhsv(color, out = []) {
toLinear(color, out);
linearToOklab(out[0], out[1], out[2], out);
const H = 0.5 + (0.5 * Math.atan2(-out[2], -out[1])) / Math.PI;
let L = out[0];
let C = Math.sqrt(out[1] * out[1] + out[2] * out[2]);
if (L !== 0 && L !== 1 && C !== 0) {
const a_ = out[1] / C;
const b_ = out[2] / C;
const [S, T] = getStMax(a_, b_);
const t = T / (C + L * T);
const Lv = t * L;
const Cv = t * C;
const Lvt = toeInv(Lv);
const Cvt = (Cv * Lvt) / Lv;
oklabToLinear(Lvt, a_ * Cvt, b_ * Cvt, out);
const scaleL = Math.cbrt(1 / Math.max(out[0], out[1], out[2], 0));
L = L / scaleL;
C = C / scaleL;
const toeL = toe(L);
C = (C * toeL) / L;
out[1] = ((S0 + T) * Cv) / (T * S0 + T * (1 - S0 / S) * Cv);
out[2] = toeL / Lv;
} else {
out[1] = 0;
out[2] = toe(L);
}
// Epsilon for saturation just needs to be sufficiently close when denoting achromatic
const ε = 1e-4;
if (Math.abs(out[1]) < ε || out[2] === 0) {
out[0] = 0; // null
} else {
out[0] = H;
}
return out;
}