API Reference

Colors are plain objects

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

/* A RGB color */
{
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 page for the channels and ranges expected for each color space). Optionally, the alpha property is used for the color's alpha channel.

Parsing and conversion

# culori.parse(string) → color or undefined · Source

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.

/* A named color */
culori.parse('red');
// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }

/* A hex color */
culori.parse('#ff0000');
// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }

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

/* A Lab color */
culori.parse('lab(100% -50 50)');
// ⇒ { l: 100, a: -50, b: 50, mode: 'lab' }

In most cases, instead of using parse() directly (which only operates on strings), you'll want to use a converter(), which accepts strings and color objects and returns objects in a predictable color space.

# culori.converter(mode = "rgb") → function (color or String) · Source

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

let rgb = culori.converter('rgb');
let lab = culori.converter('lab');

rgb('#f0f0f0');
// ⇒ { mode: "rgb", r: 0.49…, g: 0.49…, b: 0.49… }

lab('#f0f0f0');
// ⇒ { mode: "lab", l: 94.79…, a: 0, b: 0 }

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

The available modes (color spaces) are listed below. For convenience, each color space built into culori has a shortcut to its converter. For example, instead of culori.converter('hsl'), you can use culori.hsl.

Mode Color space Shortcut
a98 A98 RGB color space, compatible with Adobe RGB (1998) culori.a98(color)
cubehelix Cubehelix color space culori.cubehelix(color)
dlab DIN99o Lab color space culori.dlab(color)
dlch DIN99o LCh color space culori.dlch(color)
hsi HSI color space culori.hsi(color)
hsl HSL color space culori.hsl(color)
hsv HSV color space culori.hsv(color)
hwb HWB color space culori.hwb(color)
jab Jzazbz color space culori.jab(color)
jch Jzazbz in cylindrical form culori.jch(color)
lab CIELAB color space (D50 Illuminant) culori.lab(color)
lab65 CIELAB color space (D65 Illuminant) culori.lab65(color)
lch CIELCh color space (D50 Illuminant) culori.lch(color)
lch65 CIELCh color space (D65 Illuminant) culori.lch65(color)
lchuv CIELCHuv color space (D50 Illuminant) culori.lchuv(color)
lrgb Linear-light sRGB color space culori.lrgb(color)
luv CIELUV color space (D50 Illuminant) culori.luv(color)
oklab Oklab color space culori.oklab(color)
oklch Oklab color space, cylindrical form culori.oklch(color)
p3 Display P3 color space culori.p3(color)
prophoto ProPhoto RGB color space culori.prophoto(color)
rec2020 Rec. 2020 RGB color space culori.rec2020(color)
rgb sRGB color space culori.rgb(color)
xyz65 XYZ D65 color space culori.xyz65(color)
xyz XYZ D50 color space culori.xyz(color)
yiq YIQ color space culori.yiq(color)

Formatting

These methods serialize colors to strings, in various formats.

# culori.formatHex(color or string) → string · Source

Returns the hex string for a color. The color's alpha channel is omitted, and the red, green, and blue channels are clamped to the the interval [0, 255], i.e. colors that are not displayable are serialized as if they'd been passed through the clampRgb method.

culori.formatHex('red');
// ⇒ "#ff0000"

# culori.formatHex8(color or string) → string · Source

Returns the 8-character hex string for a color. The red, green, blue, and alpha channels are clamped to the the interval [0, 255], i.e. colors that are not displayable are serialized as if they'd been passed through the clampRgb method.

culori.formatHex8({ mode: 'rgb', r: 1, g: 0, b: 0, alpha: 0.5 });
// ⇒ "#ff000080"

# culori.formatRgb(color or string) → string · Source

Returns the rgb(…) / rgba(…) string for a color. Fully opaque colors will be serialized as rgb(), and semi-transparent colors as rgba(), in accordance with the CSSOM standard serialization. Like in the case of formatHex, the red, green, and blue channels are clamped to the interval [0, 255].

culori.formatRgb('lab(50 0 0 / 25%)');
// ⇒ "rgba(119, 119, 119, 0.25)"

Clamping

Some color spaces (Lab and LCh in particular) allow you to express colors that can't be displayed on-screen. The methods below allow you to identify when that's the case and to produce displayable versions of the colors.

# culori.displayable(color or string) → boolean · Source

Checks whether a particular color fits inside the sRGB gamut, by verifying that the r, g, and b channels are all in the interval [0, 1].

culori.displayable('red');
// ⇒ true

culori.displayable('rgb(300 255 255)');
// ⇒ false

# culori.clampRgb(color or string) → color · Source

Obtains a displayable version of the color by clamping the r, g, b channel values of the color's RGB representation to the interval [0, 1]. The returned color is in the same color space as the original color.

This is the faster, simpler, way to make a color displayable. It's what browsers do when you use a CSS color whose channels exceed the gamut. For example, rgb(300 100 200) is interpreted as rgb(255 100 200).

Because clamping individual red, green, and blue values independently can alter their proportions in the final color, it often changes the color's hue.

// RGB clamping:
culori.clampRgb('lab(50% 100 100)');
// ⇒ { mode: "lab", l: 54.29…, a: 80.81…, b: 69.88… }

# culori.clampChroma(color or string) → color · Source

Obtains a displayable version of the color by converting it to the lch color space, and trying to find the largest Chroma value that's displayable for the given Lightness and Hue. Compared to clampRgb, the function has the advantage of preserving the hue of the original color. The returned color is converted back to the original color space.

If the chroma-finding algorithm fails to find a displayable color (which can happen when not even the achromatic version, with Chroma = 0, is displayable), the method falls back to the clampRgb method, as a last resort.

The function uses the bisection method to speed up the search for the largest Chroma value. However, due to discontinuities in the CIELCh color space, the function is not guaranteed to return the optimal result. See this discussion for details.

// Chroma clamping:
culori.clampChroma('lab(50% 100 100)');
// ⇒ { mode: "lab", l:50.00…, a: 63.11…, b: 63.11… }

Interpolation

In any color space, colors occupy positions given by their values for each channel. Interpolating colors means tracing a line through the coordinates of these colors, and figuring out what colors reside on the line at various positions.

red and blue, linearly interpolated

Above is the path between red and blue in the RGB color space. Going from left to right, we start at red and steadily blend in more and more blue as we progress, until the color is fully blue at destination. This is a linear interpolation between two colors.

# culori.interpolate(colors, mode = "rgb", overrides) · Source

Returns an interpolator in the mode color space for an array of colors. The interpolator is a function that accepts a value t in the interval [0, 1] and returns the interpolated color in the mode color space.

The colors in the array can be in any color space, or they can even be strings.

let grays = culori.interpolate(['#fff', '#000']);
grays(0.5);
// ⇒ { mode: 'rgb', r: 0.5, g: 0.5, b: 0.5 }

By default, colors in all spaces are interpolated linearly across all channels. You can override the way specific channels are interpolated with the overrides object, the third argument of interpolate().

let my_interpolator = culori.interpolate(['blue', 'red'], 'lch', {
// spline instead of linear interpolation:
h: culori.interpolatorSplineBasis
});

There are a few interpolation methods available, listed below. Depending on the channel, the numeric values can be interpreted/interpolated in various modes. The hue channel, for example, is interpolated by taking into account the shortest path around the hue circle (fixupHue). And the fixupAlpha mode assumes an undefined alpha is 1.

Color stop positions

You can specify positions of color stops to interpolate in the way they're defined in the CSS Images Module Level 4 specification:

culori.interpolate(['red', ['green', 0.25], 'blue']);

In the image below, you can see the effect of interpolating with evenly-spaced colors (1) vs. positioned colors stops (2):

Evenly spaced vs. positions

To specify a positioned color stop, use an array that contains the color followed by its position. The color stops should be specified in ascending order.

For omitted (implicit) positions, we apply the rules from the spec:

  1. if the first color doesn't have a position, it's assumed to be 0; if the last color doesn't have a position, it's assumed to be 1;
  2. any other color stops that don't have a position will be evenly distributed along the gradient line between the positioned color stops.

Easing functions

You can add easing functions between any two colors in the array:

const easeIn = t => t * t;
culori.interpolate(['red', easeIn, 'green']);

Any function in the colors array will be interpreted as an easing function, which is (for our purposes), a function that takes an argument t ∈ [0, 1] and returns a value v ∈ [0, 1].

To apply the same easing function between all color pairs, instead of individual ones, add the easing as the first element in the array:

const easeIn = t => t * t;

// this form:
culori.interpolate([easeIn, 'red', 'green', 'blue']);

// is equivalent to:
culori.interpolate(['red', easeIn, 'green', easeIn, 'blue']);

The easing function can alternatively be applied the hard way:

culori.interpolate(
['red', 'green', 'blue'],
'rgb',
culori.interpolatorPiecewise((a, b, t) => culori.lerp(a, b)(easeIn(t)))
);

This formula can be helpful if you wanted to apply a different easing function per channel:

function piecewiseEasing(easingFn) {
return culori.interpolatorPiecewise((a, b, t) =>
culori.lerp(a, b)(easingFn(t))
);
}

culori.interpolate(['red', 'green', 'blue'], 'rgb', {
r: piecewiseEasing(easeIn),
g: piecewiseEasing(easeOut),
b: piecewiseEasing(easeInOut)
});

Culori comes with just a few easing functions, but you can find several online:

Interpolation hints

Any number in he colors array will be interpreted as an interpolation hint:

// interpolation hint:
culori.interpolate(['red', 0.25, 'green']);

As opposed to how current browsers implement the CSS spec (see discussion), interpolation hints do not affect color stop positions in culori.

Built-in easing functions

A few easing functions that come with culori:

# culori.easingMidpoint(H = 0.5) · Source

Proposed here, the midpoint easing function lets you shift the midpoint of a gradient like in tools such as Adobe Photoshop. You can use it with culori.interpolate() as an alternative to interpolation hints:

// Explicit midpoint easing:
culori.interpolate(['red', culori.easingMidpoint(0.25), 'blue']);

// ...is equivalent to:
culori.interpolate(['red', 0.25, 'blue']);

# culori.easingSmoothstep · Source

The Smoothstep easing function.

# culori.easingSmootherstep · Source

Smootherstep is a variant of the Smoothstep easing function.

# culori.easingInOutSine · Source

Sinusoidal in-out easing. Can be used to create, for example, a cosine interpolation as described by Paul Bourke:

culori.interpolate([culori.easingInOutSine, 'red', 'green', 'blue']);

# culori.easingGamma(γ = 1) → function(t) · Source

The gamma easing.

culori.samples(5).map(culori.easingGamma(2));
// ⇒ [0, 0.0625, 0.25, 0.5625, 1]

Interpolation methods

You'll use these methods when you want to override how colors get interpolated in a specific color space, or when defining the default interpolation for custom color spaces.

# culori.interpolatorLinear(values) · Source

A linear interpolator for values in a channel.

Basis splines

Basis splines (also called B-splines) are available in the following variants:

# culori.interpolatorSplineBasis(values) · Source

A basis spline which uses one-sided finite differences for the slopes at the boundaries.

# culori.interpolatorSplineBasisClosed(values) · Source

A basis spline which considers the values array to be periodic.

Natural splines

Natural splines are available in the following variants:

# culori.interpolatorSplineNatural(values) · Source

A natural spline which uses one-sided finite differences for the slopes at the boundaries.

# culori.interpolatorSplineNaturalClosed(values) · Source

A natural spline which considers the values array to be periodic.

Monotone splines

The monotone splines are based on the following paper (via d3-shape):

Steffen, M. "A simple method for monotonic interpolation in one dimension." in Astronomy and Astrophysics, Vol. 239, p. 443-450 (Nov. 1990), Provided by the SAO/NASA Astrophysics Data System.

The following variants are available:

# culori.interpolatorSplineMonotone(values) · Source

A monotone spline that uses one-sided finite differences to find the slopes at the boundaries.

# culori.interpolatorSplineMonotone2(values) · Source

A monotone spline for which we derive the slopes at the boundaries by tracing a parabola through the first/last three values.

# culori.interpolatorSplineMonotoneClosed(values) · Source

A monotone spline which considers the values array to be periodic.

Custom piecewise interpolation

# culori.interpolatorPiecewise(interpolator) Source

Use a custom piecewise interpolator function in the form function (a, b, t) => value:

let linear = (a, b, t) => (1 - t) * a + t * b;
culori.interpolate(['red', 'green'], culori.interpolatorPiecewise(linear));

When one of the two values to be interpolated is undefined, it will mirror the defined value: [undefined, b] becomes [b, b]. If both values are undefined, they are left as-is.

The culori.interpolatorLinear() function uses interpolatorPiecewise() under the hood.

Interpolation Fixup

By default, channel values that need to be interpolated are treated as normal numbers. However, for some channels, the values hold special singificance and can be fixed up before interpolation for better results.

Hue fixup

Hue is a circular value, so there are two directions in which to interpolate between two hues (clockwise and anti-clockwise). The functions below take an array of hues and adjusts them to impose a certain interpolation direction while maintaining the absolute difference between consecutive hues.

Adjusted hues will not necessarily be in the [0, 360) interval. All fixup methods leave undefined values, and the values immediately following them, unaltered. The names of the methods come from this discussion.

# culori.fixupHueShorter(values) → Array · Source

Adjusts the hues so that values are interpolated along the shortest path around the hue circle.

This is the default in all built-in color spaces using a hue channel. Below is an extract from the definition of the HSL color space:

/* --- hsl/definition.js --- */
export default {
// ...
interpolate: {
h: {
use: interpolatorLinear,
fixup: fixupHueShorter
},
s: interpolatorLinear,
l: interpolatorLinear,
alpha: {
use: interpolatorLinear,
fixup: fixupAlpha
}
}
// ...
};

To omit the fixup and treat hues as normal numbers, use a custom interpolation on the h channel, and overwrite the fixup function with an identity function:

let hsl_long = culori.interpolate(['blue', 'red', 'green'], 'hsl', {
h: {
fixup: arr => arr
}
});

Treating the hues array as-is (with an identity function) corresponds to the specified fixup method in the CSSWG issue mentioned earlier.

# culori.fixupHueLonger(values) → Array · Source

Adjusts the hues so that they are interpolated along the longest path around the hue circle.

# culori.fixupHueIncreasing(values) → Array · Source

Adjusts the hues so that every hue is larger than the previous.

# culori.fixupHueDecreasing(values) → Array · Source

Adjusts the hues so that every hue is smaller than the previous.

Alpha fixup

# culori.fixupAlpha(values) → Array · Source

Turns all undefined values in the array to 1 (full opacity), unless all values in the array are undefined, in which case it leaves the values unaltered.

This is the default method for the alpha channel in all built-in color spaces.

Evenly-spaced samples

# culori.samples(n = 2) · Source

Returns an array of n equally-spaced samples in the [0, 1] range, with 0 and 1 at the ends.

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']);
culori.samples(5).map(grays).map(culori.formatHex);
// ⇒ ["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"]

As with the culori.interpolate() method, you can map the samples through an easing function or scale to obtain a different distribution of the samples.

let culori = require('culori');
let easing = require('bezier-easing');

// Bezier easing:
let bezier = easing(0, 0, 1, 0.5);
culori.samples(10).map(bezier);

// easeInQuad:
culori.samples(10).map(t => t * t);

Lerp

# culori.lerp(a, b, t) → value · Source

Interpolates between the values a and b at the point t ∈ [0, 1].

culori.lerp(5, 10, 0.5);
// ⇒ 7.5

Mappings

# culori.mapper(fn, mode = "rgb") → function (color | string) · Source

Creates a mapping that applies fn on each channel of the color in the mode color space.

The resulting function accepts a single argument (a color object or a string), which it converts to the mode color space if not already there.

The mode parameter can be omitted, in which case the mappe will iterate through all the channels in the color's original color space.

The fn callback has the following signature:

fn(value, channel, color, mode)

where:

Here's the implementation of alpha premultiplication:

const multiplyAlpha = culori.mapper((val, ch, color) => {
if (ch !== 'alpha') {
return (val || 0) / (color.alpha !== undefined ? color.alpha : 1);
}
return val;
}, 'rgb');

multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 });
// ⇒ { mode: 'rgb', r: 0.5, g: 0.3, b: 0.2, a: 0.5 }

All channels are included in the mapping, so you might want to exclude the alpha channel from your function, like we do above.

Returning undefined or NaN from the function will omit that channel from the resulting color.

Built-in mappings

# culori.mapAlphaMultiply · Source

Multiplies the color's alpha value into all its other channels:

let multiplyAlpha = culori.mapper(culori.mapAlphaMultiply, 'rgb');
multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 });
// ⇒ { mode: 'rgb', r: 0.5, g: 0.3, b: 0.2, a: 0.5 }

Any undefined channel value will be considered to be 0 (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).

# culori.mapAlphaDivide · Source

Divides a color's other channels by its alpha value. It's the opposite of culori.mapAlphaMultiply, and is used in interpolation with alpha premultiplication:

let multiplyAlpha = culori.mapper(culori.mapAlphaMultiply, 'rgb');
let divideAlpha = culori.mapper(culori.mapAlphaDivide, 'rgb');

divideAlpha(multiplyAlpha({ r: 1, g: 0.6, b: 0.4, a: 0.5 }));
// ⇒ { mode: 'rgb', r: 1, g: 0.6, b: 0.4, a: 0.5 }

Any undefined channel value will be considered to be 0 (zero), to enable alpha-premultiplied interpolation with achromatic colors in hue-based color spaces (HSL, LCh, etc.).

# culori.mapTransferLinear(slope = 1, intercept = 0) · Source

# culori.mapTransferGamma(amplitude = 1, exponent = 1, offset = 0) · Source

Interpolating with mappings

# culori.interpolateWith(premap, postmap) · Source

Adds a pre-mapping and a post-mapping to an interpolation, to enable things like alpha premultiplication:

let interpolateWithAlphaPremult = culori.interpolateWith(
culori.mapAlphaMultiply,
culori.mapAlphaDivide
);

interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);

To chain more than one mapping:

const mapChromaMultiply = (v, ch, c, mode) => {
// ...
};

const mapChromaDivide = (v, ch, c, mode) => {
// ...
};

let interpolateWithAlphaChromaPremult = culori.interpolateWith(
(...args) => mapChromaMultiply(culori.mapAlphaMultiply(...args)),
(...args) => culori.mapAlphaDivide(mapChromaDivide(...args))
);

interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);

# culori.interpolateWithPremultipliedAlpha(colors, mode = "rgb", overrides) · Source

Takes the same arguments as culori.interpolate(), but applies alpha premultiplication.

let colors = ['red', 'transparent', 'blue'];

// alpha ignored for the R/G/B channels:
culori.interpolate(colors, 'rgb');

// alpha premultiplied into the R/G/B channels:
culori.interpolateWithPremultipliedAlpha(colors, 'rgb');

Color 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.

Euclidean distance

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

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.

In cylindrical spaces, the hue is factored into the Euclidean distance in a variety of ways. The functions below are used internally:

# culori.differenceHueChroma(colorA, colorB) · Source

Computes the hue contribution as the geometric mean of chord lengths belonging to the chromas of the two colors. This is the handling of hue in cylindrical forms of CIE-related color spaces: lch, lchuv, dlch, oklch, jch.

# culori.differenceHueSaturation(colorA, colorB) · Source

Computes the hue contribution as the geometric mean of chord lengths belonging to the saturations of the two colors. This is the handling of hue in the HSL / HSV / HSI family of color spaces.

# culori.differenceHueNaive(colorA, colorB) · Source

For remaining color spaces (HWB), we consider hues numbers, but apply a shortest path around the hue circle (analogous to culori.fixupHueShorter). If you insist on using Euclidean distances on these spaces, you can use the weights to control the contribution of the hue difference towards the total difference.

CIE color difference formulas

All these color difference functions operate on the lab65 color space.

# culori.differenceCie76() · Source

Computes the CIE76 ΔE*ab color difference between the colors a and b. The function is identical to culori.differenceEuclidean('lab65').

# culori.differenceCie94(kL = 1, K1 = 0.045, K2 = 0.015) · Source

Computes the CIE94 ΔE*94 color difference between the colors a and b.

# culori.differenceCiede2000(Kl = 1, Kc = 1, Kh = 1) · Source

Computes the CIEDE2000 ΔE*00 color difference between the colors a and b as implemented by G. Sharma.

Returns a CIEDE2000 Delta E* function.

# culori.differenceCmc() · Source

Computes the CMC l:c (1984) ΔE*CMC color difference between the colors a and b.

ΔE*CMC is not considered a metric since it's not symmetrical, that is 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().

Other difference formulas

# culori.differenceDin99o() · Source

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

# culori.differenceKotsarenkoRamos() · Source

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) · Source

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 τ.

/*
Get the three closest CSS named colors
*/


let colors = Object.keys(culori.colorsNamed);
let nearestNamedColors = culori.nearest(colors, culori.differenceCiede2000());

nearestNamedColors('lch(50% 70 60)', 3);
// => ["chocolate", "sienna", "peru"]

Blending

Culori makes available the separable blend modes defined in the W3C Compositing and Blending Level 2 specification.

# culori.blend(colors, type = 'normal', mode = 'rgb') → color · Source

A separable blend mode is a simple formula that gets applied to each channel in the color space independently. The available blend modes are color-burn, color-dodge, darken, difference, exclusion, hard-light, lighten, multiply, normal, overlay, screen , and soft-light. They are designed to work on RGB colors, so mode is expected to be rgb or lrgb.

An example of blending three colors:

culori.blend(
['rgba(255, 0, 0, 0.5)', 'rgba(0, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)'],
'screen'
);
// ⇒ { mode: 'rgb', alpha: 0.875, r: 0.57…, g: 0.57…, b:0.57… }

In addition to strings, the type parameter supports a function (b, s) → v that takes the values of the backdrop and source color to return the blended value. This allows you to write your own (separable) blending functions. For example, an average blending mode:

culori.blend(['red', 'green'], function average(b, s) {
return (b + s) / 2;
});

The non-separable blend modes — color, hue, saturation, and lightness — are not available. The effect which they mean to produce is better obtained with simple formulas on a cylindrical color space (e.g. HSL).

Average color

# culori.average(colors, mode = 'rgb', overrides) · Source

Returns the average color of the colors array, in the color space specified by the mode argument. The color is obtained by the arithmetic average of values on each individual channel.

Colors with undefined values on a channel don't participate in the average for that channel.

culori.average(['salmon', 'tomato'], 'lab');
// ⇒ { 'mode': 'lab', l: 65.41…, a: 53.00…, b: 39.01… }

# culori.averageNumber(values) · Source

The arithmetic mean of values in the values array.

# culori.averageAngle(values) · Source

The function used by default to average hue values in all built-in color spaces, using the formula for the mean of circular quantities.

Random colors

# culori.random(mode = 'rgb', constraints = {}) · Source

Obtain a random color from a particular color space, with optional constraints. The resulting color will be in the color space from where it has been picked.

Basic usage:

culori.random();
// ⇒ { mode: 'rgb', r: 0.75, g: 0.12, b: 0.99 }

Specifying constraints

Random colors are, by definition, all over the color space and not all of them will look particularly nice. Some color spaces, such as HSL or HSV, are also biased towards colors close to black and/or white, because of the way these color spaces stretch the RGB cube into cylinders.

For more control on how the colors are generated, you can specify constraints for each individual channel in the color space. Constraints can be either a constant number or an interval from where to pick the channel value:

culori.random('hsv', {
h: 120, // number
s: [0.25, 0.75] // interval
});
// ⇒ { mode: 'hsv', h: 120, s: 0.51…, v: 0.89… }

The alpha channel is excluded by default. To obtain colors with random alpha values, include a constraint for alpha:

culori.random('lrgb');
// ⇒ { mode: 'lrgb', r: 0.74…, g: 0.15…, b: 0.34… }

culori.random('lrgb', { alpha: [0, 1] });
// ⇒ { mode: 'lrgb', r: 0.33…, g: 0.72…, b: 0.04…, alpha: 0.12… }

Displayable random colors

The value for any channel in the color space for which there are no constraints will be picked from the entire range of that channel. However, some color spaces, such as CIELAB or CIELCH, don't have explicit ranges for certain channels; for these, some approximate ranges have been pre-computed as the limits of the displayable sRGB gamut.

Even with these ranges in place, a combination of channel values may not be displayable. Check if that's the case with culori.displayable(), and pass the color through a culori.clamp* function to obtain a displayable version.

WCAG utilities

A couple of utility functions based on the Web Content Acccessibility Guidelines 2.0 specification.

# culori.wcagLuminance(color) · Source

Computes the relative luminance of a color.

culori.wcagLuminance('red');
// ⇒ 0.2126

# culori.wcagContrast(colorA, colorB) · Source

Computes the contrast ratio between two colors.

culori.wcagContrast('red', 'black');
// ⇒ 5.252

Filters

Filters apply certain graphical effects to a color. Culori currently implements two sets of filter functions:

CSS Filter Effects

These correspond to the filter effects defined in the W3C Filter Effects Module Level 1 specification.

The amount parameter is usually in the [0, 1] interval, but may go above 1 for some filters. The filters were designed for RGB colors, so the mode parameter is expected to be rgb or lrgb.

The resulting color is returned in the color space of the original color.

# culori.filterBrightness(amount = 1, mode = 'rgb') · Source

The brightness() CSS filter. An amount of 1 leaves the color unchanged. Smaller values darken the color (with 0 being fully black), while larger values brighten it.

let brighten = culori.filterBrightness(2, 'lrgb');
brighten('salmon');
// ⇒ { mode: 'rgb', r: 1.32…, g: 0.68…, b: 0.61… }

# culori.filterContrast(amount = 1, mode = 'rgb') · Source

The contrast() filter. An amount of 1 leaves the color unchanged. Smaller values decrease the contrast (with 0 being fully gray), while larger values increase it.

# culori.filterSepia(amount = 1, mode = 'rgb') · Source

The sepia() filter. An amount of 0 leaves the color unchanged, and 1 applies the sepia effect fully.

# culori.filterGrayscale(amount = 1, mode = 'rgb') · Source

The grayscale() filter. An amount of 0 leaves the color unchanged, and 1 makes the color fully achromatic.

# culori.filterSaturate(amount = 1, mode = 'rgb') · Source

The saturate() filter. An amount of 1 leaves the color unchanged. Smaller values desaturate the color (with 0 being fully achromatic), while larger values saturate it.

# culori.filterInvert(amount = 1, mode = 'rgb') · Source

The invert() filter. An amount of 0 leaves the color unchanged, and 1 makes the color fully inverted.

# culori.filterHueRotate(degrees = 0, mode = 'rgb') · Source

The hue-rotate() filter.

culori
.samples(5)
.map(culori.interpolate(['red', 'green', 'blue']))
.map(culori.filterSepia(0.5))
.map(culori.formatHex);

// ⇒ ["#751800", "#664200", "#576c00", "#1a3e82", "#0010ff"];

Some of the effects may be obtained more straightforwardly with simple calculations in other color spaces. For example, hue rotation can just as well be implemented as color.h += angle in a cylindrical color space such as HSL.

Color vision deficiency (CVD) simulation

Simulate how a color may be perceived by people with color vision deficiencies (CVD).

# culori.filterDeficiencyProt(severity = 1) → function (color) · Source

Simulate protanomaly and protanopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to protanopia.

# culori.filterDeficiencyDeuter(severity = 1) → function (color) · Source

Simulate deuteranomaly and deuteranopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to deuteranopia.

# culori.filterDeficiencyTrit(severity = 1) → function (color) · Source

Simuate tritanomaly and tritanopia. The severity parameter is in the interval [0, 1], where 0 corresponds to normal vision and 1 (the default value) corresponds to tritanopia.

Examples:

culori
.samples(5)
.map(culori.interpolate(['red', 'green', 'blue']))
.map(culori.filterDeficiencyProt(0.5))
.map(culori.formatHex);

// ⇒ ["#751800", "#664200", "#576c00", "#1a3e82", "#0010ff"];

Based on the work of Machado, Oliveira and Fernandes (2009), using precomputed matrices provided by the authors. References thanks to the colorspace package for R.

G. M. Machado, M. M. Oliveira and L. A. F. Fernandes, "A Physiologically-based Model for Simulation of Color Vision Deficiency," in IEEE Transactions on Visualization and Computer Graphics, vol. 15, no. 6, pp. 1291-1298, Nov.-Dec. 2009, doi: 10.1109/TVCG.2009.113.

Miscellaneous

# culori.round(n = 8) · Source

Returns a rounder: a function with which to round numbers to at most n digits of precision.

let approx = culori.round(4);
approx(0.38393993);
// ⇒ 0.3839

Extending culori

Warning: This part of the API is not yet finalized and may change.

# culori.defineMode(definition) · Source

Defines a new color space through a definition object. Here's the full definition of the HSL color space:

{
mode: 'hsl',
output: {
rgb: convertHslToRgb
},
input: {
rgb: convertRgbToHsl
},
channels: ['h', 's', 'l', 'alpha'],
ranges: {
h: [0, 360]
},
parsers: [parseHsl],
interpolate: {
h: {
use: interpolatorLinear,
fixup: fixupHueShorter
},
s: interpolatorLinear,
l: interpolatorLinear,
alpha: {
use: interpolatorLinear,
fixup: fixupAlpha
}
},
difference: {
h: differenceHueSaturation
},
average: {
h: averageAngle
}
};

The properties a definition needs are the following:

All built-in color spaces follow these conventions in regards to the channels array follows:

This makes sure culori.differenceEuclidean() works as expected, but there may be more hidden assumptions in the codebase.