ciecam02-ts
This library provides tools to work with the CIECAM02 color space.
It is a TypeScript port of @baskerville/ciecam02 with slight modifications (removed dependency, cleaned up code) and TypeScript declarations.
This hybrid package provides both CommonJS and ES module.
Install
// If you use yarn
yarn add ciecam02-ts
// If you use npm
npm install ciecam02-ts
Usage
API
Converters
cam(viewingConditions?, correlates?) -> {
fromXyz(XYZ) -> CAM,
toXyz(CAM) -> XYZ,
fillOut(correlates, inputs) -> outputs
}
ucs(name?="UCS") -> {
fromCam(CAM) -> UCS,
toCam(UCS) -> CAM,
distance(UCS1, UCS2) -> number
}
hq: {
fromHue(h) -> H,
toHue(H) -> h,
fromNotation(N) -> H,
toNotation(H) -> N
}
Default viewing conditions
{
whitePoint: illuminant.D65,
adaptingLuminance: 40,
backgroundLuminance: 20,
surroundType: "average",
discounting: false
}
Gamut helpers
gamut(xyz, cam, epsilon?=1e-6) -> {
contains(CAM) -> (boolean, RGB),
limit(camIn, camOut, prec?=1e-3) -> CAM,
spine(t) -> CAM
}
Misc helpers
cfs(str) -> correlates,
lerp(CAM1, CAM2, t) -> CAM
Example
See also this source file.
import * as ciebase from "ciebase-ts";
import { Vector3D } from "ciebase-ts";
import * as ciecam02 from "../lib";
import { IJchProps, IViewingConditions } from "../lib";
const { illuminant, rgb, workspace } = ciebase;
const { cfs, lerp, hq } = ciecam02;
const { min, max } = Math;
const xyz = ciebase.xyz(workspace.sRGB, illuminant.D65);
const viewingConditions: IViewingConditions = {
adaptingLuminance: 40,
backgroundLuminance: 20,
discounting: false,
surroundType: "average",
whitePoint: illuminant.D65,
};
const cam = ciecam02.cam(viewingConditions, cfs("JCh"));
const gamut = ciecam02.gamut(xyz, cam);
const ucs = ciecam02.ucs();
function hexToCam(hex: string) {
return cam.fromXyz(xyz.fromRgb(rgb.fromHex(hex)));
}
function camToHex(CAM: IJchProps) {
return rgb.toHex(xyz.toRgb(cam.toXyz(CAM)));
}
function crop(v: number) {
return max(0, min(1, v));
}
const example1 = () => {
const camSand = hexToCam("e0cda9");
const camOrange = { ...camSand, C: 90 };
const [isInside, rgbOrange] = gamut.contains(camOrange);
if (!isInside) {
const camOrange1 = gamut.limit(camSand, camOrange);
const camOrange2 = cam.fromXyz(xyz.fromRgb(rgbOrange.map(crop) as Vector3D));
console.log([camOrange1, camOrange2].map(camToHex));
} else {
console.log(rgb.toHex(rgbOrange));
}
};
const example2 = () => {
function gradient(camStart: IJchProps, camEnd: IJchProps, steps = 3) {
const result = [];
for (let ε = 1 / (steps + 1), t = 0; steps > -2; t += ε, steps -= 1) {
const camBetween = lerp(camStart as any, camEnd as any, crop(t));
const hex = rgb.toHex(xyz.toRgb(cam.toXyz(camBetween as any) as Vector3D).map(crop) as Vector3D);
result.push(hex);
}
return result;
}
const hexCodes = gradient(
hexToCam("17657d"),
hexToCam("fee7f0"),
8,
);
console.log(hexCodes);
};
const objectMap = <T, R>(
obj: { [key: string]: T },
fn: (key: string, value: T, index: number, o: { [key: string]: T }) => R,
) => {
const r: { [key: string]: R } = {};
Object.keys(obj).forEach((key, index) => {
r[key] = fn(key, obj[key], index, obj);
});
return r;
};
const example3 = () => {
function ucsLimit(camIn: IJchProps, camOut: IJchProps, prec = 1e-3) {
let [ucsIn, ucsOut] = [camIn, camOut].map((v) => ucs.fromCam(cam.fillOut(cfs("JMh"), v)));
while (ucs.distance(ucsIn, ucsOut) > prec) {
const ucsMid = lerp(ucsIn as any, ucsOut as any, 0.5);
const [isInside] = gamut.contains(ucs.toCam(ucsMid as any) as any);
if (isInside) {
ucsIn = ucsMid as any;
} else {
ucsOut = ucsMid as any;
}
}
return cam.fillOut(objectMap(camIn as any, () => true), ucs.toCam(ucsIn as any) as any);
}
function hue(N: string) {
return hq.toHue(hq.fromNotation(N));
}
const topChroma = max(...["f00", "0f0", "00f"].map((v) => hexToCam(v).C));
const camRed: IJchProps = { J: 60, C: topChroma + 1, h: hue("R") };
const camYellow: IJchProps = { J: 90, C: topChroma + 1, h: hue("Y") };
const camGreen: IJchProps = { J: 90, C: topChroma + 1, h: hue("G") };
const camBlue: IJchProps = { J: 70, C: topChroma + 1, h: hue("B") };
const hexCodes = [camRed, camYellow, camGreen, camBlue].map((CAM) => {
CAM = ucsLimit(gamut.spine(CAM.J / 100) as any, CAM);
return camToHex(CAM);
});
console.log(hexCodes);
};
const main = () => {
console.log("Example 1");
example1();
console.log("Example 2");
example2();
console.log("Example 3");
example3();
console.log("Done");
};
main();
License: MIT
See the LICENSE file.