JSPM

culori

0.7.3
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 236472
  • Score
    100M100P100Q176076F
  • License MIT

Package Exports

  • culori

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (culori) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Culori

npm version

Culori is a general-purpose color library for JavaScript. It incorporates, and extends, ideas from Mike Bostock's D3.js and Gregor Aisch's chroma.js.

Color Spaces. Culori supports all the formats defined in the CSS Colors Level 4: Named colors, Hex colors (3 to 8 digits), RGB, HSL, HWB, Lab and LCh, and Grays. Additionally, the Linear RGB, HSV (also known as HSB), HSI, and Cubehelix color spaces are supported.

Color Differences. Culori can compute color differences with either a simple Euclidean distance or the CIELAB Delta E* metric as formulated by CIE76, CIE94, CIEDE2000 and CMC l:c (1984). They're also available as a D3 plugin. It can also find the closest N colors from a set of colors based on any of these differences.

⚠ Note: The API may change as we near the first stable release (1.0.0). Keep an eye on the CHANGELOG.

Foreword

If you're thinking Do we really need another JavaScript color library?, I hear you. Reader, for the most part, we don't. Mike Bostock's d3-color, and Gregor Aisch's chroma.js are two robust libraries that provide most of what you need for working with colors on the web1. I'll admit culori2 is foremost a reflection of my own curiousity in understanding color spaces at a deeper level. But it also ended up a fairly fast, and fairly comprehensive, toolkit for manipulating colors β€” and with an implementation that has certain advantages.

The alpha channel which governs a color's opacity is treated differently than in other libaries, in that we don't equate an undefined alpha with an alpha of 1. The hex string #ff0000 should eventually be interpreted as a fully-opaque red color, but at the color-manipulation level we want to draw the distinction between #ff0000 and #ff0000ff, which has an explicit alpha channel.

When developing the API, I tried to balance brevity, convenience and flexibility. It ended up a more-or-less functional API, i.e. a collection of functions you can pipe data through. It's not as readable as a fluent API where you chain methods, but it's much more flexible, I think.


1 Other popular libraries you may want to look at are TinyColor by Brian Grinstead, color by Heather Arthur, color.js by Andrew Brehaut et al, and chromatist by Jacob Rus.

2 from the Romanian word for β€˜colors’.

Getting Started

Try it online

You can use npm.runkit.com/culori as a playground to test various methods in from the API without installing culori in your project. Observable is another great place to tinker with the library.

Install as npm package

culori is bundled as UMD and ES on npm. Install it using npm or yarn:

# using npm
npm install culori

# using yarn
yarn add culori

You can then import culori in your project:

// CJS-style
var culori = require('culori');

// ES-style
import { rgb } from 'culori';

Add culori via the <script> tag

To import culori as a <script> tag to use in a web page, load it from unpkg:

<script src='https://unpkg.com/culori'/>

API Reference

Color representation

Culori does not have a Color class. Instead, it uses plain objects to represent colors:

// A color in the RGB space
{
  mode: 'rgb',
  r: 0.1,
  g: 0.2,
  b: 1,
  alpha: 1
}

The object needs to have a mode property that identifies the color space, and values for each channel in that particular color space. See the Color Spaces section for the channels expected of each color space. Optionally, the alpha property is used for the color's alpha channel.

A note on the API

πŸ•³ explain choice of functional style and its benefits.

Basic methods

# culori.parse(string) β†’ color or undefined <>

Parses a string and returns the corresponding color. The color will be in the matching color space, e.g. RGB for hex strings, HSL for hsl(…, …, …) strings, et cetera. If no built-in parsers can match the string, the function will return undefined.

// named colors
culori.parse('red'); // β‡’ { r: 1, g: 0, b: 0, mode: 'rgb' }

// hex strings
culori.parse('#ff0000'); // β‡’ { r: 1, g: 0, b: 0, mode: 'rgb' }

// HSL color
culori.parse('hsl(60 50% 10% / 100%)'); // β‡’ { h: 60, s: 0.5, b: 0.1, alpha: 1, mode: 'hsl' }

// Lab color
culori.parse('lab(100 -50 50)'); // β‡’ { l: 100, a: -50, b: 50, mode: 'lab' }

# culori.converter(mode = "rgb") β†’ function (color or String) <>

Returns a function that can then convert any color to the mode color space:

var rgb = culori.converter('rgb');

rgb('#f0f0f0'); // β‡’ { "mode": "rgb", "r": 0.4980392156862745, "g": 0.4980392156862745, "b": 0.4980392156862745 }

Converters accept either strings (which will be parsed with culori.parse) or color objects. If the mode key is absent from the color, it's assumed to be in the converter's color space.

The available modes (color spaces) are listed below. Each color space has a convenience method for converting to that color space.

Mode For Shortcut
rgb RGB color space culori( color ) and culori.rgb( color )
hsl HSL color space culori.hsl(color)
hsv HSV color space culori.hsv(color)
hsi HSI color space culori.hsi(color)
hwb HWB color space culori.hwb(color)
lab Lab color space culori.lab(color)
lch LCh color space culori.lch(color)
lrgb Linearized RGB color space culori.lrgb(color)
cubehelix Cubehelix color space culori.cubehelix(color)
dlab DIN99o Lab color space culori.dlab(color)
dlch DIN99o LCh color space culori.dlch(color)
yiq YIQ color space culori.yiq(color)

# culori.formatter(format = 'rgb') β†’ function (color) <>

Returns a formatter function that can transform colors to useful string representations.

let hex = culori.formatter('hex');

hex('red'); // β‡’ "#ff0000"

Available formats:

Format Description
hex Returns the hex string for a color
rgb Returns the rgb(…) / rgba(…) string for a color

# culori.displayable(color or String) <>

For some color spaces β€” particularly Lab and LCh β€” not all colors that can be expressed can be displayed on-screen. This function lets you check whether a particular color fits inside the sRGB gamut, i.e. that its r, g, and b channels are in the interval [0, 1].

culori.displayable('red'); // β‡’ true
culori.displayable('rgb(300 255 255)'); // β‡’ false

# culori.clamp(method = 'rgb') β†’ function (color) <>

Returns a function which you can then use to retreive a representation of any color that's displayable on the screen, i.e. fits within the sRGB gamut. There are two available methods:

  • method = 'rgb' clamps the r, g, b channel values of the color's RGB representation to the interval [0, 1];
  • method = 'lch' converts the color to the LCh space and finds the largest Chroma channel value that's displayable for the given Lightness and Hue; if not even the achromatic version (Chroma = 0) of the LCh color is displayable, it falls back to the rgb method.
culori.clamp('lch')('lch(50 120 5)'); // β‡’ { mode: 'lch', l: 50, c: 77.48291015625, h: 5 }

# culori.round(n = 8) <>

A (rather miscellaneous) utility that returns a function with which to round numbers to at most n digits of precision.

let approximate = culori.round(4);

approximate(0.38393993); // => 0.3839

Interpolation

# culori.interpolate(colors, mode = "rgb") <>

Returns an interpolator between an array of colors in the mode color space.

# culori.samples(n = 2, Ξ³ = 1) <>

Returns an array of n equally-spaced samples from the [0, 1] range, with 0 and 1 at the ends. The function also accepts a Ξ³ (gamma) parameter which will map each value t to tΞ³.

culori.samples(3); // => [0, 0.5, 1]
culori.samples(5); // => [0, 0.25, 0.5, 0.75, 1]

The samples are useful for culori.interpolate() to generate color scales:

let grays = culori.interpolate(['#fff', '#000']);
samples(5).map(grays);

Interpolation functions

# culori.interpolateLinear <>

# culori.interpolateSplineBasis <>

# culori.interpolateSplineNatural <>

# culori.interpolateSplineMonotone <>

# culori.interpolateCosine <>

Interpolation modes

# culori.interpolateHue <>

# culori.interpolateAlpha <>

Difference

These methods are concerned to finding the distance between two colors based on various formulas. Each of these formulas will return a function (colorA, colorB) that lets you measure the distance between two colors. Also available as a separate d3 plugin.

# culori.differenceEuclidean(mode = 'rgb', weights = [1, 1, 1, 0]) <>

Returns a Euclidean distance function in a certain color space.

You can optionally assign different weights to the channels in the color space. See, for example, the Kotsarenko/Ramos distance.

The default weights [1, 1, 1, 0] mean that the alpha, which is the fourth channel in all the color spaces Culori defines, is not taken into account. Send [1, 1, 1, 1] as the weights to include it in the computation.

For the h channel in the color (in any color space that has this channel), we're using a shortest hue distance to compute the hue's contribution to the distance. In spaces such as HSL or HSV, where the range of this difference is [0, 180] β€” as opposed to [0, 1] for the other channels β€” consider adjusting the weights so that the hue contributes "equally" to the distance:

let hsl_distance = culori.differenceEuclidean('hsl', [
    1 / (180 * 180),
    1,
    1,
    0
]);

# culori.differenceCie76() <>

Computes the CIE76 Ξ”E*ab color difference between the colors a and b. The computation is done in the Lab color space and it is analogous to culori.differenceEuclidean('lab').

# culori.differenceCie94(kL = 1, K1 = 0.045, K2 = 0.015) <>

Computes the CIE94 Ξ”E*94 color difference between the colors a and b. The computation is done in the Lab color space.

# culori.differenceCiede2000(Kl = 1, Kc = 1, Kh = 1) <>

Computes the CIEDE2000 Ξ”E*00 color difference between the colors a and b as implemented by G. Sharma. The computation is done in the Lab color space.

Returns a CIEDE2000 Delta E* function.

# culori.differenceCmc() <>

Computes the CMC l:c (1984) Ξ”E*CMC color difference between the colors a and b. The computation is done in the Lab color space.

Note: Ξ”E*CMC is not considered a metric since it's not symmetrical, i.e. the distance from a to b is not always equal to the distance from b to a. Therefore it cannot be reliably used with culori.nearest().

# culori.differenceDin99o() <>

Computes the DIN99o Ξ”E*99o color difference between the colors a and b. The computation is done in the DIN99o color space.

# culori.differenceKotsarenkoRamos() <>

Computes the Kotsarenko/Ramos color difference between the colors a and b. This is a weighted Euclidean distance in the YIQ color space.

Nearest color(s)

# culori.nearest(colors, metric = differenceEuclidean(), accessor = identity) β†’ function(color, n = 1, Ο„ = Infinity) <>

For a given metric color difference formula, and an array of colors, returns a function with which you can find n colors nearest to color, with a maximum distance of Ο„.

Pass n = Infinity to get all colors in the array with a maximum distance of Ο„.

Color Spaces

RGB / LRGB (Linear RGB)

πŸ•³ expand this section

HSL / HSV / HSI

HSL, HSV, and HSI are a family of representations of the RGB color space, created in 1970 to provide color spaces more closely aligned to how humans perceive colors.

πŸ’‘ In this family of color spaces, the hue is undefined for achromatic colors (i.e. shades of gray).

hsl

Channel Range Description
h [0, 360) Hue
s [0, 1] Saturation in HSL
l [0, 1] Lightness

The figure below shows a slice of the HSL color space for a particular hue:

hsv

Channel Range Description
h [0, 360) Hue
s [0, 1] Saturation in HSV
v [0, 1] Value

The figure below shows a slice of the HSV color space for a particular hue:

hsi

Channel Range Description
h [0, 360) Hue
s [0, 1] Saturation in HSI
i [0, 1] Intensity

The figure below shows a slice of the HSI color space for a particular hue:

πŸ’‘ While the hue in this family of color spaces retains its value in all of them, the saturation in HSL is not interchangeable with the saturation from HSV, nor HSI β€” they're computed differently, depending on the color space.

HWB

The HWB color space was developed by Alvy Ray Smith, who also created the HSV color space. It's meant to be more intuitive for humans to use and faster to compute.

References:

Lab / LCh (CIE)

As defined in the CSS Color Module Level 4 spec, we use the D50 illuminant as a reference white for these color spaces.

lab

Channel Range Description
l [0 β€” 100] Lightness
a [-79.2872, 93.55] Green–red component
b [-112.0294, 93.3884] Blue–yellow component

lch

Channel Range Description
l [0 - 100] Lightness
c [0 - 131.207] Chroma
h [0 - 360] Hue

πŸ’‘ The range for the a and b channels in Lab, and the c channel in LCh, depend on the specific implementation. I've obtained the ranges from the tables above by converting all sRGB colors defined by r, g, b ∈ β„• β‹‚ [0, 255] into Lab and LCh respectively.

DIN99 Lab / LCh

The DIN99 color space "squishes" the CIE Lab color space to obtain an effective color difference metric that can be expressed as a simple Euclidean distance. We implement the latest iteration of the the standard, DIN99o.

dlab

Channel Range Description
l ? Lightness
a ?
b ?

dlch

Channel Range Description
l ? Lightness
c ? Chroma
h ? Hue

References:

YIQ

YIQ is the color space used by the NTSC color TV system. It contains the following channels:

Channel Range Description
Y [0,1] Luma
I [-0.5957, 0.5957] In-phase (orange-blue axis)
Q [-0.5226, 0.5226] Quadrature (green-purple axis)

Cubehelix

The Cubehelix color scheme was described by Dave Green in this paper:

It was expanded into a color space by Mike Bostock and Jason Davies in D3.

cubehelix

The channels in the cubehelix color space maintain the conventions from D3, namely:

Channel Range Description
h [0, 360) Hue (Based on start color and rotations as defined in Green's paper)
s [0, 4.6143] Saturation (Called hue in op. cit.)
l [0, 1] Lightness

Culori Recipes

Relative luminance of a color

The relative luminance of a color is defined as:

L = 0.2126 * R + 0.7152 * G + 0.0722 * B;

Where R, G, and B are the components from the LRGB color space.

To compute it in culori:

function luminance(color) {
    let c = culori.lrgb(color);
    return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
}

Note: The WCAG defines the luminance using a deprecated value for converting sRGB to LRGB. If you'd like a strict implementation, you'll need to write your own sRGB β†’ LRGB conversion.

Contrast ratio

Using the luminance() function above, the contrast() ratio is simply the ratio between the luminances of two colors, with the values shifted by 0.05 to avoid division by zero when comparing against black.

function contrast(colorA, colorB) {
    let L1 = luminance(colorA);
    let L2 = luminance(colorB);
    return (L1 + 0.05) / (L2 + 0.05);
}

Extending culori

Defining a color space

Note: The order of the items in the channels array matters. To keep things simple, we're making the following conventions:

  • the fourth item in the array should be alpha
  • any cyclical values (e.g. hue) should be identified by h, in the range [0, 360)

These constrains make sure differenceEuclidean() works as expected.

See also

These libraries add more functionality to culori:

Further reading

Colophon