ciecam02-ts
TypeScript icon, indicating that this package has built-in type declarations

0.1.0 • Public • Published

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.

// The reference for understanding CIECAM02 is:
// http://www.springer.com/cda/content/document/cda_downloaddocument/9781441961891-c1.pdf

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;

//
// Shared settings for the examples
//

const xyz = ciebase.xyz(workspace.sRGB, illuminant.D65);

const viewingConditions: IViewingConditions = {
    adaptingLuminance: 40,
    backgroundLuminance: 20,
    discounting: false,
    surroundType: "average",
    whitePoint: illuminant.D65,
};

// By default, 7 correlates are returned when converting from XYZ to CAM.
// For the purpose of this example, we will limit ourselves to the JCh correlates.
// (J is the lightness, C the chroma and h the hue.)
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));
}

//
// Example 1
//

const example1 = () => {
    const camSand = hexToCam("e0cda9");
    // {J: 77.82, C: 16.99, h: 81.01}
    const camOrange = { ...camSand, C: 90 };
    // {J: 77.82, C: 90.00, h: 81.01}
    const [isInside, rgbOrange] = gamut.contains(camOrange);
    // [false, [1.09, 0.73, -0.7]]

    if (!isInside) {
        // The gamut.limit function interpolates between an inside and an outside point
        // and return an inside point as close as possible to the boundary.
        // (The gamut is the set of CAM values that maps to valid RGB coordinates.)
        const camOrange1 = gamut.limit(camSand, camOrange);
        // {J: 77.82, C: 55.23, h: 81.01}
        // The alternative method is to simply crop the RGB coordinates
        const camOrange2 = cam.fromXyz(xyz.fromRgb(rgbOrange.map(crop) as Vector3D));
        // {J: 74.43, C: 67.60, h: 81.30}

        // tslint:disable-next-line:no-console
        console.log([camOrange1, camOrange2].map(camToHex));             // #ffc447   #ffb900
    } else {
        // tslint:disable-next-line:no-console
        console.log(rgb.toHex(rgbOrange));
    }
};

//
// Example 2
//

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(
        // camStart
        hexToCam("17657d"),
        // camEnd
        hexToCam("fee7f0"),
        // steps
        8,
    );

    // tslint:disable-next-line:no-console
    console.log(hexCodes);
};

//
// Example 3
//

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) {
        // UCS is based on the JMh correlates
        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);
    }

    // The hue notation is a different writting of the hue quadrant,
    // of the form a(p?b)? where a and b are in {R, Y, G, B} (a ≠ b)
    // and p is in ]0, 100[. apb = b(100-p)a, ab = a50b.
    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);
    });

    // tslint:disable-next-line:no-console
    console.log(hexCodes);
};

//
// Main
//

const main = () => {
    // tslint:disable-next-line:no-console
    console.log("Example 1");
    example1();
    // tslint:disable-next-line:no-console
    console.log("Example 2");
    example2();
    // tslint:disable-next-line:no-console
    console.log("Example 3");
    example3();
    // tslint:disable-next-line:no-console
    console.log("Done");
};

main();

License: MIT

See the LICENSE file.

Package Sidebar

Install

npm i ciecam02-ts

Weekly Downloads

2

Version

0.1.0

License

MIT

Unpacked Size

78.1 kB

Total Files

41

Last publish

Collaborators

  • gfmio