colorjs.io
Version:
Let’s get serious about color
138 lines (119 loc) • 3.55 kB
JavaScript
import ColorSpace from "../ColorSpace.js";
import { multiply_v3_m3x3 } from "../util.js";
import XYZ_Abs_D65 from "./xyz-abs-d65.js";
import { spow } from "../util.js";
/** @import { Matrix3x3, Vector3 } from "../types.js" */
const b = 1.15;
const g = 0.66;
const n = 2610 / 2 ** 14;
const ninv = 2 ** 14 / 2610;
const c1 = 3424 / 2 ** 12;
const c2 = 2413 / 2 ** 7;
const c3 = 2392 / 2 ** 7;
const p = (1.7 * 2523) / 2 ** 5;
const pinv = 2 ** 5 / (1.7 * 2523);
const d = -0.56;
const d0 = 1.6295499532821566e-11;
/** @type {Matrix3x3} */
// prettier-ignore
const XYZtoCone_M = [
[ 0.41478972, 0.579999, 0.0146480 ],
[ -0.2015100, 1.120649, 0.0531008 ],
[ -0.0166008, 0.264800, 0.6684799 ],
];
// XYZtoCone_M inverted
/** @type {Matrix3x3} */
// prettier-ignore
const ConetoXYZ_M = [
[ 1.9242264357876067, -1.0047923125953657, 0.037651404030618 ],
[ 0.35031676209499907, 0.7264811939316552, -0.06538442294808501 ],
[ -0.09098281098284752, -0.3127282905230739, 1.5227665613052603 ],
];
/** @type {Matrix3x3} */
// prettier-ignore
const ConetoIab_M = [
[ 0.5, 0.5, 0 ],
[ 3.524000, -4.066708, 0.542708 ],
[ 0.199076, 1.096799, -1.295875 ],
];
// ConetoIab_M inverted
/** @type {Matrix3x3} */
// prettier-ignore
const IabtoCone_M = [
[ 1, 0.13860504327153927, 0.05804731615611883 ],
[ 1, -0.1386050432715393, -0.058047316156118904 ],
[ 1, -0.09601924202631895, -0.81189189605603900 ],
];
export default new ColorSpace({
id: "jzazbz",
name: "Jzazbz",
coords: {
jz: {
refRange: [0, 1],
name: "Jz",
},
az: {
refRange: [-0.21, 0.21],
},
bz: {
refRange: [-0.21, 0.21],
},
},
base: XYZ_Abs_D65,
fromBase (XYZ) {
// First make XYZ absolute, not relative to media white
// Maximum luminance in PQ is 10,000 cd/m²
// Relative XYZ has Y=1 for media white
// BT.2048 says media white Y=203 at PQ 58
let [Xa, Ya, Za] = XYZ;
// modify X and Y to minimize blue curvature
let Xm = b * Xa - (b - 1) * Za;
let Ym = g * Ya - (g - 1) * Xa;
// move to LMS cone domain
let LMS = multiply_v3_m3x3([Xm, Ym, Za], XYZtoCone_M);
// PQ-encode LMS
let PQLMS = /** @type {Vector3} } */ (
LMS.map(function (val) {
let num = c1 + c2 * spow(val / 10000, n);
let denom = 1 + c3 * spow(val / 10000, n);
return spow(num / denom, p);
})
);
// almost there, calculate Iz az bz
let [Iz, az, bz] = multiply_v3_m3x3(PQLMS, ConetoIab_M);
// console.log({Iz, az, bz});
let Jz = ((1 + d) * Iz) / (1 + d * Iz) - d0;
return [Jz, az, bz];
},
toBase (Jzazbz) {
let [Jz, az, bz] = Jzazbz;
let Iz = (Jz + d0) / (1 + d - d * (Jz + d0));
// bring into LMS cone domain
let PQLMS = multiply_v3_m3x3([Iz, az, bz], IabtoCone_M);
// convert from PQ-coded to linear-light
let LMS = /** @type {Vector3} } */ (
PQLMS.map(function (val) {
let num = c1 - spow(val, pinv);
let denom = c3 * spow(val, pinv) - c2;
let x = 10000 * spow(num / denom, ninv);
return x; // luminance relative to diffuse white, [0, 70 or so].
})
);
// modified abs XYZ
let [Xm, Ym, Za] = multiply_v3_m3x3(LMS, ConetoXYZ_M);
// un-modify X and Y to get D65 XYZ, relative to media white
let Xa = (Xm + (b - 1) * Za) / b;
let Ya = (Ym + (g - 1) * Xa) / g;
return [Xa, Ya, Za];
},
formats: {
// https://drafts.csswg.org/css-color-hdr/#Jzazbz
jzazbz: {
coords: [
"<percentage> | <number>",
"<number> | <percentage>",
"<number> | <percentage>",
],
},
},
});