Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ciecam02-ts

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ciecam02-ts

TypeScript port of ciecam - An implementation of CIECAM02

  • 0.1.0
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
494
decreased by-10.18%
Maintainers
1
Weekly downloads
 
Created
Source

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.

Keywords

FAQs

Package last updated on 29 Jul 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc