diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 2d4347e8..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": ["eslint:recommended", "plugin:import/recommended"], - "parserOptions": { - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": ["import"], - "rules": { - "constructor-super": 1, - "import/no-anonymous-default-export": 1, - "import/no-unused-modules": 1, - "no-const-assign": 1, - "no-loss-of-precision": 0, - "no-mixed-spaces-and-tabs": 0, - "no-this-before-super": 1, - "no-undef": 2, - "no-unused-vars": [1, { "args": "none" }], - "valid-typeof": 1 - } -} diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit deleted file mode 100755 index e29abb23..00000000 --- a/.git-hooks/pre-commit +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -npx pretty-quick --staged diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 316ccb69..00000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -*.sublime-workspace -.DS_Store -build/ -bundled/ -node_modules -*.log -.nyc_output/ -coverage/ -yarn-error.log - -# Website -www/ \ No newline at end of file diff --git a/docs/static/.nojekyll b/.nojekyll similarity index 100% rename from docs/static/.nojekyll rename to .nojekyll diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 2e1fa2d5..00000000 --- a/.prettierignore +++ /dev/null @@ -1 +0,0 @@ -*.md \ No newline at end of file diff --git a/docs/static/CNAME b/CNAME similarity index 100% rename from docs/static/CNAME rename to CNAME diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index cffe52ec..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,34 +0,0 @@ -# Contributing - -## Development - -Tests are defined using the `node:test` and `node:assert` modules. Running the tests require Node 21 or newer: - -```bash -npm run test -``` - -We use ESLint to lint JavaScript code. - - -```bash -npm run lint -``` - -While Culori can be used from source in environments supporting ES Modules, we also use esbuild to build bundles in CommonJS, ESM, and UMD formats. - -```bash -npm run build -``` - -## Documentation - -The documentation website [culorijs.org] is built with Eleventy out of the `www/` folder, and deployed on GitHub pages via the `gh-pages` branch. - -The following scripts are available: - -```bash -npm run docs:start -npm run docs:build -npm run docs:deploy -``` \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index fa64274b..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Dan Burzo - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index b55a14c3..00000000 --- a/README.md +++ /dev/null @@ -1,23 +0,0 @@ -![culori](./docs/img/culori.svg) - -npm version bundle size - -Culori is a comprehensive color library for JavaScript that works across many color spaces to offer conversion, interpolation, color difference formulas, blending functions, and more. It provides up-to-date support for the color spaces defined in [CSS Color Module Level 4](https://drafts.csswg.org/css-color/) specification. - -```bash -npm install culori -``` - -The full documentation is published on [culorijs.org](https://culorijs.org). Some quick entry points: - -- [Getting started](https://culorijs.org/getting-started) -- [API References](https://culorijs.org/api/) -- [Supported color spaces](https://culorijs.org/color-spaces/) - -## Contributing - -Contributions of all kinds (feedback, ideas, bug fixes, documentation) are welcome. - -Please open a GitHub issue/discussion before putting in any work that’s not straightforward. - -More in [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/api/index.html b/api/index.html new file mode 100644 index 00000000..25d55084 --- /dev/null +++ b/api/index.html @@ -0,0 +1,1653 @@ + + + + + + + API Reference · culori + + + + + + +
+
+ + +
+
+ +
+
+

API Reference

+
+API table of contents + +
+

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

+

# parse(string) → color or undefined

+

src/parse.js

+

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.

+
import { parse } from 'culori';
+
+/* A named color */
+parse('red');
+// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }
+
+/* A hex color */
+parse('#ff0000');
+// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' }
+
+/* A HSL color */
+parse('hsl(60 50% 10% / 100%)');
+// ⇒ { h: 60, s: 0.5, b: 0.1, alpha: 1, mode: 'hsl' }
+
+/* A Lab color */
+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.

+

# converter(mode = "rgb") → function (color or String)

+

src/converter.js

+

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

+
import { converter } from 'culori';
+
+let rgb = converter('rgb');
+let lab = 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 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.

+

Formatting

+

These methods serialize colors to strings, in various formats.

+

# formatHex(color or string) → string

+

src/formatter.js

+

Returns the hex string for the given 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.

+
import { formatHex } from 'culori';
+
+formatHex('red');
+// ⇒ "#ff0000"
+

# formatHex8(color or string) → string

+

src/formatter.js

+

Returns the 8-character hex string for the given 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.

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

# formatRgb(color or string) → string

+

src/formatter.js

+

Returns the rgb(…) / rgba(…) string for the given 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].

+
import { formatRgb } from 'culori';
+
+formatRgb('lab(50 0 0 / 25%)');
+// ⇒ "rgba(119, 119, 119, 0.25)"
+

# formatHsl(color or string) → string

+

src/formatter.js

+

Returns the hsl(…) / hsla(…) string for the given color. Fully opaque colors will be serialized as hsl(), and semi-transparent colors as hsla(). All values are rounded to a precision of two digits. The Saturation and Lightness are clamped to the interval [0%, 100%].

+
import { formatHsl } from 'culori';
+
+formatHsl('lab(50 0 0 / 25%)');
+// ⇒ 'hsla(194.33, 0%, 46.63%, 0.25)'
+

# formatCss(color or string) → string

+

src/formatter.js

+

Returns a CSS string for the given color, based on the CSS Color Level 4 specification. A few color spaces, such as hsl or lab, have their own functional representation in CSS. We use that whenever possible; the hsl color space is represented as hsl(h% s l / alpha). Predefined color spaces are represented using the color() notation with the appropriate identifier for the color space, e.g. color(display-p3 r g b / alpha). All other colors paces use the color() notation with a dashed identifier. For example, jab is represented as color(--jzazbz j a b / alpha).

+

You can find the exact string produced for each color space under the Serialized as entry on the Color Spaces page.

+

Channel values are serialized as-is, with no change in the precision. To avoid compatibility issues, sRGB colors are represented as color(srgb r g b / alpha) rather than rgb(r, g, b, alpha). For the latter, use the formatRgb() method instead.

+

An alpha of exactly 1 is omitted from the representation.

+

Note: The strings returned by these methods are not widely supported in current browsers and should not be used in CSS as-is.

+
import { formatCss } from 'culori';
+
+/* 
+	A mode with its own function notation.
+*/
+formatCss({ mode: 'hsl', h: 30, s: 1, l: 0.5, alpha: 0.5 });
+// ⇒ 'hsl(30 100% 50% / 0.5)'
+
+/*
+	A predefined color space.
+ */
+formatCss({ mode: 'p3', r: 0.5, s: 0.25, b: 1, alpha: 1 });
+// ⇒ 'color(display-p3 0.5 0.25 1)'
+
+/*
+	sRGB colors.
+ */
+formatCss({ mode: 'rgb', r: 0.5, s: 0.25, b: 1, alpha: 0.25 });
+// ⇒ 'color(srgb 0.5 0.25 1 / 0.25)'
+
+/*
+	A custom color space.
+ */
+formatCss({ mode: 'lrgb', r: 0.5, s: 0.25, b: 1, alpha: 0.25 });
+// ⇒ 'color(--srgb-linear 0.5 0.25 1 / 0.25)'
+

Gamut mapping

+

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.

+

# inGamut(mode = "rgb") → function (color | string)

+

src/clamp.js

+

Given a color space, returns a function with which to check whether a particular color is within the gamut of that color space.

+

This is meant to be used with RGB-based color spaces and their derivates (hsl, hsv, etc.). If the color space has no gamut limits, the function will always return true, regardless of the color passed to it. To find out which color spaces have gamut limits, see the Color Spaces page.

+
import { inGamut } from 'culori';
+
+const inRgb = inGamut('rgb');
+
+inRgb('red');
+// ⇒ true
+
+inRgb('color(srgb 1.1 0 0)');
+// ⇒ false
+

# displayable(color or string) → boolean

+

src/clamp.js

+

Checks whether a particular color fits inside the sRGB gamut. Equivalent to inGamut('rgb').

+
import { displayable } from 'culori';
+
+displayable('red');
+// ⇒ true
+
+displayable('color(srgb 1.1 0 0)');
+// ⇒ false
+

# clampRgb(color or string) → color

+

src/clamp.js

+

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.

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

# clampGamut(mode = 'rgb') → function(color | string)

+

This function extends the functionality of clampRgb to other color spaces. Given a color space, it returns a function with which to obtain colors within the gamut of that color space.

+

If the color space has no gamut limits, colors are returned unchanged. To find out which color spaces have gamut limits, see the Color Spaces page.

+

The in-gamut color is always returned in the color space of the original color.

+
import { formatCss, clampGamut } from 'culori';
+
+const crimson = 'color(display-p3 0.8 0.1 0.3)';
+const toRgb = clampGamut('rgb');
+
+formatCss(toRgb(crimson));
+// ⇒ 'color(display-p3 0.801… 0.169… 0.302…)'
+

src/clamp.js

+

# clampChroma(color or string, mode = 'lch', rgbGamut = 'rgb') → color

+

src/clamp.js

+

Obtains a displayable version of the color by converting it to a temporary color space containing a Chroma channel, then looking for the closest 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 displayable color returned by this function will be converted back to the original color space.

+
import { clampChroma } from 'culori';
+
+clampChroma('lab(50% 100 100)');
+// ⇒ { mode: 'lab', l:50.00…, a: 63.11…, b: 63.11… }
+

By default, the color is converted to lch to perform the clamping, but any color space that contains a Chroma dimension can be used by sending an explicit mode argument.

+

Likewise, the destination RGB gamut can be overriden with the corresponding parameter.

+
import { clampChroma } from 'culori';
+
+clampChroma({ mode: 'oklch', l: 0.5, c: 0.16, h: 180 }, 'oklch');
+// ⇒ { mode: 'oklch', l: 0.5, c: 0.09, h: 180 }
+

In general, chroma clamping is more accurate and computationally simpler when performed in the color's original space, where possible. Here's some sample code that uses the color's own mode for color spaces containing a Chroma dimension, and lch otherwise:

+
import { clampChroma } from 'culori';
+
+clampChroma(color, color.c !== undefined ? color.mode : 'lch');
+

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.

+

# toGamut(dest = 'rgb', mode = 'oklch', delta = differenceEuclidean('oklch'), jnd = 0.02) → function (color | string)

+

src/clamp.js

+

Obtain a color that's in the dest gamut, by first converting it to the mode color space and then finding the largest chroma that's in gamut, similar to clampChroma().

+

The color returned is in the dest color space.

+
import { p3, toGamut } from 'culori';
+
+const color = 'lch(80% 150 60)';
+
+p3(color);
+// ⇒ { mode: "p3", r: 1.229…, g: 0.547…, b: -0.073… }
+
+const toP3 = toGamut('p3');
+toP3(color);
+// ⇒ { mode: "p3", r: 0.999…, g: 0.696…, b: 0.508… }
+

To address the shortcomings of clampChroma, which can sometimes produce colors more desaturated than necessary, the test used in the binary search is replaced with “is color is roughly in gamut”, by comparing the candidate to the clipped version (obtained with clampGamut). The test passes if the colors are not to dissimilar, judged by the delta color difference function and an associated jnd just-noticeable difference value.

+

The default arguments for this function correspond to the gamut mapping algorithm defined in the CSS Color Module Level 4 spec, but the algorithm itself is slightly different.

+

The “roughly in gamut” aspect of the algorithm can be disabled by passing null for the delta color difference function:

+
import { toGamut } from 'culori';
+const clampToP3 = toGamut('p3', 'oklch', null);
+

The algorithm expects an LCH-like color space, containing lightness and chroma components, for the mode argument.

+

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.

+

# interpolate(colors, mode = "rgb", overrides)

+

src/interpolate/interpolate.js

+

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.

+
import { interpolate } from 'culori';
+
+let grays = 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().

+
import { interpolate, interpolatorSplineBasis } from 'culori';
+
+let my_interpolator = interpolate(['blue', 'red'], 'lch', {
+	// spline instead of linear interpolation:
+	h: 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:

+
import { interpolate } from '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. +
  3. any other color stops that don't have a position will be evenly distributed along the gradient line between the positioned color stops.
  4. +
+

Easing functions

+

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

+
import { interpolate } from 'culori';
+
+const easeIn = t => t * t;
+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:

+
import { interpolate } from 'culori';
+
+const easeIn = t => t * t;
+
+// this form:
+interpolate([easeIn, 'red', 'green', 'blue']);
+
+// is equivalent to:
+interpolate(['red', easeIn, 'green', easeIn, 'blue']);
+

The easing function can alternatively be applied the hard way:

+
import { interpolate, interpolatorPiecewise, lerp } from 'culori';
+
+const easeIn = t => t * t;
+
+interpolate(
+	['red', 'green', 'blue'],
+	'rgb',
+	interpolatorPiecewise((a, b, t) => lerp(a, b, easeIn(t)))
+);
+

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

+
import { interpolate, interpolatorPiecewise, lerp } from 'culori';
+function piecewiseEasing(easingFn) {
+	return interpolatorPiecewise((a, b, t) => lerp(a, b, easingFn(t)));
+}
+
+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 the colors array will be interpreted as an interpolation hint:

+
import { interpolate } from 'culori';
+
+// interpolation hint:
+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

+

# easingMidpoint(H = 0.5)

+

src/easing/midpoint.js

+

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 interpolate() as an alternative to interpolation hints:

+
import { interpolate, easingMidpoint } from 'culori';
+// Explicit midpoint easing:
+interpolate(['red', easingMidpoint(0.25), 'blue']);
+
+// ...is equivalent to:
+interpolate(['red', 0.25, 'blue']);
+

# easingSmoothstep

+

src/easing/smoothstep.js

+

The Smoothstep easing function.

+

# easingSmoothstep

+

src/easing/smoothstep.js

+

The inverse of the Smoothstep easing function.

+

# easingSmootherstep

+

src/easing/smootherstep.js

+

Smootherstep is a variant of the Smoothstep easing function.

+

# easingInOutSine

+

src/easing/inOutSine.js

+

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

+
import { interpolate, easingInOutSine } from 'culori';
+interpolate([easingInOutSine, 'red', 'green', 'blue']);
+

# easingGamma(γ = 1) → function(t)

+

src/easing/gamma.js

+

The gamma easing.

+
import { samples, easingGamma } from 'culori';
+samples(5).map(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.

+

# interpolatorLinear(values)

+

src/interpolate/linear.js

+ +

A linear interpolator for values in a channel.

+

Basis splines

+

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

+

# interpolatorSplineBasis(values)

+

src/interpolate/splineBasis.js

+ +

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

+

# interpolatorSplineBasisClosed(values)

+

src/interpolate/splineBasis.js

+ +

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

+

Natural splines

+

Natural interpolating splines are related to basis splines, as explained in this handout by Kirby A. Baker (sections 4 and 5).

+

# interpolatorSplineNatural(values)

+

src/interpolate/splineNatural.js

+ +

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

+

# interpolatorSplineNaturalClosed(values)

+

src/interpolate/splineNatural.js

+ +

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:

+

# interpolatorSplineMonotone(values)

+

src/interpolate/splineMonotone.js

+ +

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

+

# interpolatorSplineMonotone2(values)

+

src/interpolate/splineMonotone.js

+ +

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

+

# interpolatorSplineMonotoneClosed(values)

+

src/interpolate/splineMonotone.js

+ +

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

+

Custom piecewise interpolation

+

# interpolatorPiecewise(interpolator) src/interpolate/piecewise.js

+

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

+
import { interpolate, interpolatorPiecewise } from 'culori';
+
+let linear = (a, b, t) => (1 - t) * a + t * b;
+interpolate(['red', 'green'], 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 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.

+

# fixupHueShorter(values) → Array

+

src/fixup/hue.js

+

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:

+
import { interpolate } from 'culori';
+
+let hsl_long = 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.

+

# fixupHueLonger(values) → Array

+

src/fixup/hue.js

+

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

+

# fixupHueIncreasing(values) → Array

+

src/fixup/hue.js

+

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

+

# fixupHueDecreasing(values) → Array

+

src/fixup/hue.js

+

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

+

Alpha fixup

+

# fixupAlpha(values) → Array

+

src/fixup/alpha.js

+

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

+

# samples(n = 2)

+

src/samples.js

+

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

+
import { samples } from 'culori';
+
+samples(3);
+// ⇒ [0, 0.5, 1]
+
+samples(5);
+// ⇒ [0, 0.25, 0.5, 0.75, 1]
+

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

+
import { samples, interpolate, formatHex } from 'culori';
+
+let grays = interpolate(['#fff', '#000']);
+samples(5).map(grays).map(formatHex);
+// ⇒ ["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"]
+

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

+
import { samples } from 'culori';
+import easing from 'bezier-easing';
+
+// Bezier easing:
+let bezier = easing(0, 0, 1, 0.5);
+samples(10).map(bezier);
+
+// easeInQuad:
+samples(10).map(t => t * t);
+

Lerp

+

# lerp(a, b, t) → value

+

src/interpolate/lerp.js

+

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

+
import { lerp } from 'culori';
+lerp(5, 10, 0.5);
+// ⇒ 7.5
+

# unlerp(a, b, v) → value

+

src/interpolate/lerp.js

+

Returns the point t at which the value v is located between the values a and b. The inverse of lerp.

+

# blerp(a00, a01, a10, a11, tx, ty) → value

+

src/interpolate/lerp.js

+

Perform the bilinear interpolation of the four values a00, a01, a10, and a11 at the point (tx, ty), with tx, ty ∈ [0, 1]. This is the extension of lerp to two dimensions.

+

# trilerp(a000, a010, a100, a110, a001, a011, a101, a111, tx, ty, tz) → value

+

src/interpolate/lerp.js

+

Perform the trilinear interpolation of the eight values a000, a010, a100, a110, a001, a011, a101, and a111 at the point (tx, ty, tz), with tx, ty, tz ∈ [0, 1]. This is the extension of lerp to three dimensions.

+

Mappings

+

# mapper(fn, mode = "rgb") → function (color | string)

+

src/map.js

+

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

+

The mode parameter can be set to null, in which case the mapper will iterate through 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:

+
import { mapper } from 'culori';
+
+const multiplyAlpha = 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, including alpha, are included in the mapping. You may want to handle the alpha channel differently in the callback function, like in the example above.

+

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

+

Built-in mappings

+

# mapAlphaMultiply

+

src/map.js

+

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

+
import { mapper, mapAlphaMultiply } from 'culori';
+
+let multiplyAlpha = mapper(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.).

+

# mapAlphaDivide

+

src/map.js

+

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

+
import { mapper, mapAlphaMultiply, mapAlphaDivide } from 'culori';
+
+let multiplyAlpha = mapper(mapAlphaMultiply, 'rgb');
+let divideAlpha = mapper(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.).

+

# mapTransferLinear(slope = 1, intercept = 0)

+

src/map.js

+

# mapTransferGamma(amplitude = 1, exponent = 1, offset = 0)

+

src/map.js

+

Interpolating with mappings

+

# interpolateWith(premap, postmap)

+

src/interpolate.js

+

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

+
import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori';
+
+let interpolateWithAlphaPremult = interpolateWith(
+	mapAlphaMultiply,
+	mapAlphaDivide
+);
+
+interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);
+

To chain more than one mapping:

+
import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori';
+
+const mapChromaMultiply = (v, ch, c, mode) => {
+	// ...
+};
+
+const mapChromaDivide = (v, ch, c, mode) => {
+	// ...
+};
+
+let interpolateWithAlphaChromaPremult = interpolateWith(
+	(...args) => mapChromaMultiply(mapAlphaMultiply(...args)),
+	(...args) => mapAlphaDivide(mapChromaDivide(...args))
+);
+
+interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25);
+

# interpolateWithPremultipliedAlpha(colors, mode = "rgb", overrides)

+

src/interpolate.js

+

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

+
import { interpolate, interpolateWithPremultipliedAlpha } from 'culori';
+let colors = ['red', 'transparent', 'blue'];
+
+// alpha ignored for the R/G/B channels:
+interpolate(colors, 'rgb');
+
+// alpha premultiplied into the R/G/B channels:
+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

+

# differenceEuclidean(mode = 'rgb', weights = [1, 1, 1, 0])

+

src/difference.js

+

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 or ΔEITP.

+

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:

+

# differenceHueChroma(colorA, colorB)

+

src/difference.js

+

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.

+

# differenceHueSaturation(colorA, colorB)

+

src/difference.js

+

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.

+

# differenceHueNaive(colorA, colorB)

+

src/difference.js

+

For remaining color spaces (HWB), we consider hues numbers, but apply a shortest path around the hue circle (analogous to 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.

+

# differenceCie76()

+

src/difference.js

+

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

+

# differenceCie94(kL = 1, K1 = 0.045, K2 = 0.015)

+

src/difference.js

+

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

+

# differenceCiede2000(Kl = 1, Kc = 1, Kh = 1)

+

src/difference.js

+

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

+

# differenceCmc()

+

src/difference.js

+

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 nearest().

+

# differenceHyab()

+

src/difference.js

+

Computes the HyAB color difference between the colors a and b, as proposed in:

+
+

Abasi S, Amani Tehran M, Fairchild MD. Distance metrics for very large color differences. Color Res Appl. 2019; 1–16. https://doi.org/10.1002/col.22451 (PDF)

+
+

The HyAB formula combines the Euclidean and city block distance and has been experimentally shown to work better for large color differences than CIEDE2000, while still holding up well for smaller color differences, making it a "good candidate formula for image processing and computer vision applications".

+

Other difference formulas

+

# differenceKotsarenkoRamos()

+

src/difference.js

+

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

+

# differenceItp()

+

src/difference.js

+

Computes the ΔEITP color difference metric between the colors a and b. This is a weighted Euclidean distance in the itp color space, scaled by a factor of 720 so that a the just-noticeable difference (JND) corresponds to a value of 1.

+

Nearest color(s)

+

# nearest(colors, metric = differenceEuclidean(), accessor = identity) → function(color, n = 1, τ = Infinity)

+

src/nearest.js

+

Takes a colors array and a metric color difference formula, and returns a function with which you can find n colors nearest to color, with a maximum distance of τ. Use n = Infinity to get all colors in the array with a maximum distance of τ.

+
/*
+	Example: get three CSS named colors closest to any color
+ */
+import { colorsNamed, nearest, differenceCiede2000 } from 'culori';
+
+let colors = Object.keys(colorsNamed);
+let nearestNamedColors = nearest(colors, differenceCiede2000());
+
+nearestNamedColors('lch(50% 70 60)', 3);
+// => ["chocolate", "sienna", "peru"]
+

By default, colors needs to be an array of color values. If your array contains something other than a simple color value, you can provide the accessor argument to point to the color value associated with each item in the array.

+

The example below shows a common data structure for a color palette: an object whose keys are the names and whose values are their associated color representations.

+
import { nearest, differenceEuclidean } from 'culori';
+
+/*
+	Example: get the closest color from a palette
+ */
+let palette = {
+	Burgundy: '#914e72',
+	Blue: '#0078bf',
+	Green: '#00a95c',
+	'Medium Blue': '#3255a4',
+	'Bright Red': '#f15060'
+};
+
+let names = Object.keys(palette);
+
+let nearestColors = nearest(
+	names,
+	differenceEuclidean(),
+	name => palette[name]
+);
+
+nearestColors('red', 1);
+// => ["Bright Red"]
+

Blending

+

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

+

# blend(colors, type = 'normal', mode = 'rgb') → color

+

src/blend.js

+

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:

+
import { blend } from '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:

+
import { blend } from '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

+

# average(colors, mode = 'rgb', overrides)

+

src/average.js

+

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.

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

# averageNumber(values)

+

src/average.js

+

The arithmetic mean of values in the values array.

+

# averageAngle(values)

+

src/average.js

+

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

+

# random(mode = 'rgb', constraints = {})

+

src/random.js

+

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:

+
import { random } from '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:

+
import { random } from '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:

+
import { random } from 'culori';
+
+random('lrgb');
+// ⇒ { mode: 'lrgb', r: 0.74…, g: 0.15…, b: 0.34… }
+
+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 displayable(), and pass the color through a clamp* function to obtain a displayable version.

+

WCAG utilities

+

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

+

# wcagLuminance(color)

+

src/wcag.js

+

Computes the relative luminance of a color.

+
import { wcagLuminance } from 'culori';
+
+wcagLuminance('red');
+// ⇒ 0.2126
+

# wcagContrast(colorA, colorB)

+

src/wcag.js

+

Computes the contrast ratio between two colors.

+
import { wcagContrast } from '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.

+

# filterBrightness(amount = 1, mode = 'rgb')

+

src/filter.js

+

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.

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

# filterContrast(amount = 1, mode = 'rgb')

+

src/filter.js

+

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.

+

# filterSepia(amount = 1, mode = 'rgb')

+

src/filter.js

+

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

+

# filterGrayscale(amount = 1, mode = 'rgb')

+

src/filter.js

+

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

+

# filterSaturate(amount = 1, mode = 'rgb')

+

src/filter.js

+

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.

+

# filterInvert(amount = 1, mode = 'rgb')

+

src/filter.js

+

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

+

# filterHueRotate(degrees = 0, mode = 'rgb')

+

src/filter.js

+

The hue-rotate() filter.

+
import { samples, interpolate, filterSepia, formatHex } from 'culori';
+
+samples(5)
+	.map(interpolate(['red', 'green', 'blue']))
+	.map(filterSepia(0.5))
+	.map(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).

+

# filterDeficiencyProt(severity = 1) → function (color)

+

src/deficiency.js

+

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.

+

# filterDeficiencyDeuter(severity = 1) → function (color)

+

src/deficiency.js

+

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.

+

# filterDeficiencyTrit(severity = 1) → function (color)

+

src/deficiency.js

+

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:

+
import { interpolate, filterDeficiencyProt, formatHex } from 'culori';
+culori
+	.samples(5)
+	.map(interpolate(['red', 'green', 'blue']))
+	.map(filterDeficiencyProt(0.5))
+	.map(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

+

# colorsNamed

+

src/colors/named.js

+

An object whose keys are all the CSS named colors.

+

# round(n = 8)

+

src/round.js

+

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

+
import { round } from 'culori';
+
+let approx = round(4);
+approx(0.38393993);
+// ⇒ 0.3839
+

+ # + Available color spaces +

+

The default import (culori) comes with all the color spaces pre-registered into the library. For convenience, you can also import directly mode as a shortcut to converter(mode). For example, instead of converter('hsl'), you can import hsl:

+
// Instead of this:
+import { converter } from 'culori';
+const hsl = converter('hsl');
+
+// You can do this:
+import { hsl } from 'culori';
+

On the other hand, when importing the tree-shaken version (culori/fn), color spaces need to be registered manually with useMode() based on their definition object:

+
import { useMode, modeHsl } from 'culori/fn';
+const hsl = useMode(modeHsl);
+

The table below summarizes two pieces of information:

+ +

The available color spaces are discussed into more detail on the Color Spaces page.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModeColor spaceDefinition object
a98A98 RGB color space, compatible with Adobe RGB (1998)modeA98
cubehelixCubehelix color spacemodeCubehelix
dlabDIN99o Lab color spacemodeDlab
dlchDIN99o LCh color spacemodeDlch
hsiHSI color spacemodeHsi
hslHSL color spacemodeHsl
hsvHSV color spacemodeHsv
hwbHWB color spacemodeHwb
itpICtCp color spacemodeItp
jabJzazbz color spacemodeJab
jchJzazbz in cylindrical formmodeJch
labCIELAB color space (D50 Illuminant)modeLab
lab65CIELAB color space (D65 Illuminant)modeLab65
lchCIELCh color space (D50 Illuminant)modeLch
lch65CIELCh color space (D65 Illuminant)modeLch65
lchuvCIELCHuv color space (D50 Illuminant)modeLchuv
lrgbLinear-light sRGB color spacemodeLrgb
luvCIELUV color space (D50 Illuminant)modeLuv
oklabOklab color spacemodeOklab
oklchOklab color space, cylindrical formmodeOklch
p3Display P3 color spacemodeP3
prophotoProPhoto RGB color spacemodeProphoto
rec2020Rec. 2020 RGB color spacemodeRec2020
rgbsRGB color spacemodeRgb
xybXYB color spacemodeXyb
xyz50XYZ with D50 white-pointmodeXyz50
xyz65XYZ with D65 white-pointmodeXyz65
yiqYIQ color spacemodeYiq
+

Extending culori

+

# useMode(definition) → function.

+

src/modes.js

+

Defines a new color space based on its definition. See Color mode definition for the expected object shape.

+

Returns a converter function for the newly defined mode.

+
import { useMode } from 'culori';
+
+const hsl = useMode({
+	mode: 'hsl'
+	// ...
+});
+
+hsl('hsl(50 100% 100% / 100)');
+

# getMode(mode)

+

src/modes.js

+

Returns the definition object for the mode color space.

+

+ # + Color mode definition +

+

The properties a definition needs are the following:

+

mode (string)

+

The string identifier for the color space.

+

channels (array)

+

A list of channels for the color space.

+

toMode (object)

+

A set of functions to convert from the color space we're defining to other color spaces. At least rgb needs to be included; in case a specific conversion pair between two color spaces is missing, RGB is used as the "buffer" for the conversion.

+

fromMode (object)

+

The opposite of toMode. A set of function to convert from various color spaces to the color space we're defining. At least rgb needs to be included.

+

ranges (object, optional)

+

The reference ranges for values in specific channels; if left unspecified, defaults to [0, 1].

+

parse (array, optional)

+

Any parsers for the color space that can transform strings into colors. These can be either functions, or strings — the latter is used as the color space's identifier to parse the color(<ident>) CSS syntax.

+

serialize (function or string, optional)

+

Defines how to serialize the color space to a CSS string with formatCss().

+

If you pass in a function, it receives a color object as its only argument, and should return a string that can be used in CSS. If you pass in a string, it's used as a color profile identifier, and the color is serialized using the color() CSS syntax. When omitted altogether, the default color profile identifier is --${mode}.

+

interpolate

+

The default interpolations for the color space, one for each channel. Each interpolation is defined by its interpolator (the use key) and its fixup function (the fixup key). When defined as a function, a channel interpolation is meant to define its interpolator, with the fixup being a no-op.

+

difference

+

The default Euclidean distance method for each channel in the color space; mostly used for the h channel in cylindrical color spaces.

+

average

+

The default average function for each channel in the color space; when left unspecified, defaults to averageNumber.

+

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

+ +

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

+

Here's a sample definition for the HSL color space:

+
{
+  mode: 'hsl',
+  fromMode: {
+    rgb: convertRgbToHsl
+  },
+  toMode: {
+    rgb: convertHslToRgb
+  },
+  channels: ['h', 's', 'l', 'alpha'],
+  ranges: {
+    h: [0, 360]
+  },
+  parse: [parseHsl],
+  serialize: serializeHsl,
+  interpolate: {
+    h: {
+      use: interpolatorLinear,
+      fixup: fixupHueShorter
+    },
+    s: interpolatorLinear,
+    l: interpolatorLinear,
+    alpha: {
+      use: interpolatorLinear,
+      fixup: fixupAlpha
+    }
+  },
+  difference: {
+    h: differenceHueSaturation
+  },
+  average: {
+    h: averageAngle
+  }
+};
+

# useParser(parser, mode)

+

src/modes.js

+

Register a new parser. The parser can be:

+ +
import { useParser } from 'culori';
+
+// Register custom parser
+useParser(function(str) => {
+  let color = {};
+  // parse the string
+  return color;
+});
+
+// Register `color(--oklab)` syntax
+useParser('--oklab', 'oklab');
+

# removeParser(parser)

+

src/modes.js

+

Remove a previously registered parser function or string, including parsers registered by default.

+
import { parse, parseNamed, removeParser } from 'culori';
+
+parse('tomato');
+// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 }
+
+removeParser(parseNamed);
+parse('tomato');
+// ⇒ undefined
+

+ # + Low-level API +

+

+ # + Parsing functions +

+

# parseHex(string) → color

+

Parses hex strings of 3, 4, 6, or 8 characters, with or without the # prefix, and returns rgb color objects.

+
import { parseHex } from 'culori';
+
+parseHex('#abc');
+parseHex('#abcd');
+parseHex('#abcdef');
+parseHex('#abcdef12');
+

# parseHsl(string) → color

+

Parses hsl(…) strings in the modern format and returns hsl color objects.

+

# parseHslLegacy(string) → color

+

Parses hsl(…) / hsla(…) strings in the legacy (comma-separated) format and returns hsl color objects.

+

# parseHwb(string) → color

+

Parses hwb(…) strings and returns hwb color objects.

+

# parseLab(string) → color

+

Parses lab(…) strings and returns lab color objects.

+

# parseLch(string) → color

+

Parses lch(…) strings and returns lch color objects.

+

# parseNamed(string) → color

+

Parses named CSS colors (eg. tomato) and returns rgb color objects.

+

# parseOklab(string) → color

+

Parses oklab(…) strings and returns oklab color objects.

+

# parseOklch(string) → color

+

Parses oklch(…) strings and returns oklch color objects.

+

# parseRgb(color) → color

+

Parses rgb(…) strings in the modern syntax and returns rgb color objects.

+

# parseRgbLegacy(color) → color

+

Parses rgb(…) / rgba(…) strings in the legacy (comma-separated) syntax and returns rgb color objects.

+

#parseTransparent(string) → color

+

Parses the transparent string and returns a transparent black rgb color object.

+

Serialization functions

+

#serializeHex(color)

+

src/formatter.js

+

Serialize a rgb color to a 6-character hex code. See formatHex() for details.

+

#serializeHex8(color)

+

src/formatter.js

+

Serialize a rgb color to a 8-character hex code. See formatHex8() for details.

+

#serializeHsl(color)

+

src/formatter.js

+

Serialize a hsl color to a hsl(…) string. See formatHsl() for details.

+

#serializeRgb(color)

+

src/formatter.js

+

Serialize a rgb color to a rgb(…) string. See formatRgb() for details.

+

Conversion functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FunctionConversion
convertA98ToXyz65(color) → colora98xyz65
convertCubehelixToRgb(color) → colorcubehelixrgb
convertDlchToLab65(color) → colordlchlab65
convertHsiToRgb(color) → colorhsirgb
convertHslToRgb(color) → colorhslrgb
convertHsvToRgb(color) → colorhsvrgb
convertHwbToRgb(color) → colorhwbrgb
convertJabToJch(color) → colorjabjch
convertJabToRgb(color) → colorjabrgb
convertJabToXyz65(color) → colorjabxyz65
convertJchToJab(color) → colorjchjab
convertLab65ToDlch(color) → colorlab65dlch
convertLab65ToRgb(color) → colorlab65rgb
convertLab65ToXyz65(color) → colorlab65xyz65
convertLabToLch(color) → colorlablch
convertLabToRgb(color) → colorlabrgb
convertLabToXyz50(color) → colorlabxyz50
convertLchToLab(color) → colorlchlab
convertLchuvToLuv(color) → colorlchuvluv
convertLrgbToOklab(color) → colorlrgboklab
convertLrgbToRgb(color) → colorlrgbrgb
convertLuvToLchuv(color) → colorluvlchuv
convertLuvToXyz50(color) → colorluvxyz50
convertOkhslToOklab(color) → colorokhsloklab
convertOkhsvToOklab(color) → colorokhsvoklab
convertOklabToLrgb(color) → coloroklablrgb
convertOklabToOkhsl(color) → coloroklabokhsl
convertOklabToOkhsv(color) → coloroklabokhsv
convertOklabToRgb(color) → coloroklabrgb
convertP3ToXyz65(color) → colorp3xyz65
convertProphotoToXyz50(color) → colorprophotoxyz50
convertRec2020ToXyz65(color) → colorrec2020xyz65
convertRgbToCubehelix(color) → colorrgbcubehelix
convertRgbToHsi(color) → colorrgbhsi
convertRgbToHsl(color) → colorrgbhsl
convertRgbToHsv(color) → colorrgbhsv
convertRgbToHwb(color) → colorrgbhwb
convertRgbToJab(color) → colorrgbjab
convertRgbToLab65(color) → colorrgblab65
convertRgbToLab(color) → colorrgblab
convertRgbToLrgb(color) → colorrgblrgb
convertRgbToOklab(color) → colorrgboklab
convertRgbToXyb(color) → colorrgbxyb
convertRgbToXyz50(color) → colorrgbxyz50
convertRgbToXyz65(color) → colorrgbxyz65
convertRgbToYiq(color) → colorrgbyiq
convertXybToRgb(color) → colorxybrgb
convertXyz50ToLab(color) → colorxyz50lab
convertXyz50ToLuv(color) → colorxyz50luv
convertXyz50ToProphoto(color) → colorxyz50prophoto
convertXyz50ToRgb(color) → colorxyz50rgb
convertXyz50ToXyz65(color) → colorxyz50xyz65
convertXyz65ToA98(color) → colorxyz65a98
convertXyz65ToJab(color) → colorxyz65jab
convertXyz65ToLab65(color) → colorxyz65lab65
convertXyz65ToP3(color) → colorxyz65p3
convertXyz65ToRec2020(color) → colorxyz65rec2020
convertXyz65ToRgb(color) → colorxyz65rgb
convertXyz65ToXyz50(color) → colorxyz65xyz50
convertYiqToRgb(color) → coloryiqrgb
+ +
+
+ + + + + + diff --git a/benchmark/README.md b/benchmark/README.md deleted file mode 100644 index e8ffc724..00000000 --- a/benchmark/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Culori Benchmarks - -In the `culori/benchmark` folder, run `npm install` to fetch all the dependencies. With `node index.js` you can then execute all the benchmarks. diff --git a/benchmark/index.html b/benchmark/index.html deleted file mode 100644 index c207e26f..00000000 --- a/benchmark/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - culori browser playground - - - - diff --git a/benchmark/index.js b/benchmark/index.js deleted file mode 100644 index 8f312bbc..00000000 --- a/benchmark/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import './tests/rgb-parse-speed.js'; -import './tests/namedcolors-parse-speed.js'; -import './tests/interpolate-speed.js'; diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json deleted file mode 100644 index 6e7e4c06..00000000 --- a/benchmark/package-lock.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "name": "culori-benchmarks", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "culori-benchmarks", - "version": "1.0.0", - "license": "MIT", - "devDependencies": { - "chroma-js": "^3.1.2", - "colorjs.io": "^0.5.2", - "d3-color": "^3.1.0", - "d3-interpolate": "^3.0.1", - "tinycolor2": "^1.6.0" - } - }, - "node_modules/chroma-js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.1.2.tgz", - "integrity": "sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==", - "dev": true, - "license": "(BSD-3-Clause AND Apache-2.0)" - }, - "node_modules/colorjs.io": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", - "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dev": true, - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true, - "license": "MIT" - } - }, - "dependencies": { - "chroma-js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.1.2.tgz", - "integrity": "sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==", - "dev": true - }, - "colorjs.io": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", - "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", - "dev": true - }, - "d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "dev": true - }, - "d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dev": true, - "requires": { - "d3-color": "1 - 3" - } - }, - "tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true - } - } -} diff --git a/benchmark/package.json b/benchmark/package.json deleted file mode 100644 index 9d631427..00000000 --- a/benchmark/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "culori-benchmarks", - "type": "module", - "version": "1.0.0", - "main": "index.js", - "license": "MIT", - "devDependencies": { - "chroma-js": "^3.1.2", - "d3-color": "^3.1.0", - "d3-interpolate": "^3.0.1", - "tinycolor2": "^1.6.0", - "colorjs.io": "^0.5.2" - } -} diff --git a/benchmark/tests/ciede2000-vs-din99o.js b/benchmark/tests/ciede2000-vs-din99o.js deleted file mode 100644 index 94518ae9..00000000 --- a/benchmark/tests/ciede2000-vs-din99o.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - nearest, - differenceCiede2000, - differenceDin99o -} from '../../src/index.js'; -import benchmark from '../util/benchmark.js'; - -let colors = Object.keys(culori.colorsNamed); - -let ciede2000 = nearest(colors, differenceCiede2000()); -let din99o = nearest(colors, differenceDin99o()); - -benchmark('ciede2000', () => { - for (var k = 0; k < 100; k++) { - for (var i = 0; i < colors.length; i++) { - ciede2000(colors[i]); - } - } -}); - -benchmark('din99o', () => { - for (var k = 0; k < 100; k++) { - for (var i = 0; i < colors.length; i++) { - din99o(colors[i]); - } - } -}); diff --git a/benchmark/tests/interpolate-speed.js b/benchmark/tests/interpolate-speed.js deleted file mode 100644 index 71a4e654..00000000 --- a/benchmark/tests/interpolate-speed.js +++ /dev/null @@ -1,85 +0,0 @@ -import chroma from 'chroma-js'; -import { color } from 'd3-color'; -import { quantize, interpolateRgb } from 'd3-interpolate'; -import tinycolor from 'tinycolor2'; -import { samples, interpolate, formatHex } from '../../src/index.js'; -import benchmark from '../util/benchmark.js'; - -console.log(` -Benchmark: interpolate speed -============================ -`); - -let count = 1000; -let iterations = 1000; - -benchmark('culori: #fff -> #000 in RGB', () => { - for (var i = 0; i < iterations; i++) { - samples(count) - .map(interpolate(['#fff', '#000'])) - .map(formatHex); - } -}); - -let interpolator = interpolate(['#fff', '#000']); -let hex = formatHex; - -benchmark('culori: #fff -> #000 in RGB (cached)', () => { - for (var i = 0; i < iterations; i++) { - samples(count).map(interpolator).map(hex); - } -}); - -benchmark('culori: #fff -> #000 in RGB (cached, single map)', () => { - for (var i = 0; i < iterations; i++) { - samples(count).map(c => hex(interpolator(c))); - } -}); - -benchmark('chroma: #fff -> #000 in RGB', () => { - for (var i = 0; i < iterations; i++) { - chroma.scale(['#fff', '#000']).colors(count); - } -}); - -let scale = chroma.scale(['#fff', '#000']); -benchmark('chroma: #fff -> #000 in RGB (cached)', () => { - for (var i = 0; i < iterations; i++) { - scale.colors(count); - } -}); - -benchmark('d3-color: #fff -> #000 in RGB', () => { - for (var i = 0; i < iterations; i++) { - quantize(interpolateRgb('#fff', '#000'), count); - } -}); - -let interp = interpolateRgb('#fff', '#000'); -benchmark('d3-color: #fff -> #000 in RGB (cached)', () => { - for (var i = 0; i < iterations; i++) { - quantize(interp, count); - } -}); - -let colors = ['red', 'white', 'green', 'blue', 'black', 'fuchsia', 'cyan']; - -benchmark('culori: multiple colors in RGB', () => { - for (var i = 0; i < iterations; i++) { - samples(count).map(interpolate(colors)).map(hex); - } -}); - -benchmark('culori: multiple colors in RGB (cached)', () => { - let it = interpolate(colors); - for (var i = 0; i < iterations; i++) { - samples(count).map(it).map(hex); - } -}); - -let scale2 = chroma.scale(colors); -benchmark('chroma: multiple colors in RGB (cached)', () => { - for (var i = 0; i < iterations; i++) { - scale2.colors(count); - } -}); diff --git a/benchmark/tests/namedcolors-parse-speed.js b/benchmark/tests/namedcolors-parse-speed.js deleted file mode 100644 index ce79929b..00000000 --- a/benchmark/tests/namedcolors-parse-speed.js +++ /dev/null @@ -1,45 +0,0 @@ -import chroma from 'chroma-js'; -import { color } from 'd3-color'; -import tinycolor from 'tinycolor2'; -import { rgb, colorsNamed } from '../../src/index.js'; -import benchmark from '../util/benchmark.js'; - -console.log(` -Benchmark: named colors parse speed -=================================== -`); - -let colors = Object.keys(colorsNamed); -let iterations = 10000; - -benchmark('chroma: chroma("colorname")', () => { - for (var it = 0; it < iterations; it++) { - for (var i = 0; i < colors.length; i++) { - chroma(colors[i]); - } - } -}); - -benchmark('d3-color: color("colorname")', () => { - for (var it = 0; it < iterations; it++) { - for (var i = 0; i < colors.length; i++) { - color(colors[i]); - } - } -}); - -benchmark('tinycolor: tinycolor("colorname")', () => { - for (var it = 0; it < iterations; it++) { - for (var i = 0; i < colors.length; i++) { - tinycolor(colors[i]); - } - } -}); - -benchmark('culori: culori("colorname")', () => { - for (var it = 0; it < iterations; it++) { - for (var i = 0; i < colors.length; i++) { - rgb(colors[i]); - } - } -}); diff --git a/benchmark/tests/rgb-parse-speed.js b/benchmark/tests/rgb-parse-speed.js deleted file mode 100644 index d129be9b..00000000 --- a/benchmark/tests/rgb-parse-speed.js +++ /dev/null @@ -1,74 +0,0 @@ -import chroma from 'chroma-js'; -import { color } from 'd3-color'; -import tinycolor from 'tinycolor2'; -import { ColorSpace, sRGB, parse } from 'colorjs.io/fn'; -import { rgb } from '../../src/index.js'; -import benchmark from '../util/benchmark.js'; - -console.log(` -Benchmark: RGB parse speed -========================== -`); - -let increment = 2; - -benchmark('chroma: chroma("rgb(r,g,b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - chroma(`rgb(${r},${g},${b})`); - } - } - } -}); - -benchmark('d3-color: color("rgb(r,g,b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - color(`rgb(${r},${g},${b})`); - } - } - } -}); - -benchmark('tinycolor: tinycolor("rgb(r,g,b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - tinycolor(`rgb(${r},${g},${b})`); - } - } - } -}); - -benchmark('culori: culori("rgb(r,g,b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - rgb(`rgb(${r},${g},${b})`); - } - } - } -}); - -benchmark('culori: culori("rgb(r g b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - rgb(`rgb(${r} ${g} ${b})`); - } - } - } -}); - -ColorSpace.register(sRGB); -benchmark('colorjs.io: parse("rgb(r g b)")', () => { - for (var r = 0; r <= 255; r += increment) { - for (var g = 0; g <= 255; g += increment) { - for (var b = 0; b <= 255; b += increment) { - parse(`rgb(${r} ${g} ${b})`); - } - } - } -}); diff --git a/benchmark/util/benchmark.js b/benchmark/util/benchmark.js deleted file mode 100644 index 8dc09f2a..00000000 --- a/benchmark/util/benchmark.js +++ /dev/null @@ -1,15 +0,0 @@ -var start, end; -function startBench() { - start = process.hrtime(); -} - -function endBench() { - end = process.hrtime(start); - return end[0] + 's ' + end[1] / 1000000 + 'ms'; -} - -export default function (name, fn) { - startBench(); - fn(); - console.log(name, endBench()); -} diff --git a/build.js b/build.js deleted file mode 100644 index 473dd45c..00000000 --- a/build.js +++ /dev/null @@ -1,109 +0,0 @@ -import { build } from 'esbuild'; -import { writeFile } from 'node:fs/promises'; - -const builds = []; - -// Bundled CJS -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - format: 'cjs', - outfile: 'bundled/culori.cjs' - }) -); - -// Bundled CJS, minified -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - minify: true, - format: 'cjs', - outfile: 'bundled/culori.min.cjs' - }) -); - -// Bundled ESM -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - format: 'esm', - metafile: true, - outfile: 'bundled/culori.mjs' - }).then(result => { - writeFile( - 'bundled/meta.json', - JSON.stringify(result.metafile, null, 2) - ); - }) -); - -// Bundled ESM, minified -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - minify: true, - format: 'esm', - outfile: 'bundled/culori.min.mjs' - }) -); - -// Bundled IIFE -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - format: 'iife', - globalName: 'culori', - outfile: 'bundled/culori.js' - }) -); - -// Bundled IIFE, minified -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - minify: true, - format: 'iife', - globalName: 'culori', - outfile: 'bundled/culori.min.js' - }) -); - -// Bundled UMD -// Adapted from: https://github.com/umdjs/umd/blob/master/templates/returnExports.js -builds.push( - build({ - entryPoints: ['./src/index.js'], - logLevel: 'info', - bundle: true, - format: 'iife', - globalName: 'culori', - banner: { - js: `(function(root, factory) { - if (typeof define === 'function' && define.amd) { - define([], factory); - } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); - } else { - root.culori = factory(); - } - } - (typeof self !== 'undefined' ? self : this, function() {` - }, - footer: { js: `return culori; }));` }, - outfile: 'bundled/culori.umd.js' - }) -); - -await Promise.all(builds); diff --git a/colophon/index.html b/colophon/index.html new file mode 100644 index 00000000..7d6479d1 --- /dev/null +++ b/colophon/index.html @@ -0,0 +1,101 @@ + + + + + + + Colophon · culori + + + + + + +
+
+ + +
+
+ +
+
+

Colophon

+

Culori is developed by Dan Burzo with the help of many collaborators, and released under the MIT license.

+

The library has been inspired by Mike Bostock's D3.js and Gregor Aisch's chroma.js: they have popularized color science in the browser and provided a blueprint for a color manipulation API. I have learned a tremendous amount by reading through the documentation the source code while developing Culori. D3, in particular, has been a treasure trove of ideas and pointers to academic references.

+

I pronounce the name of the library as [kuːlori]. Culori is the Romanian word for ‘colors’. The logo is typeset in Hatch by Mark Caneso.

+

The code is formatted with Prettier, upkept with ESLint, tested with tape, and bundled with esbuild. This website is statically generated using Eleventy and published to GitHub Pages.

+ +
+
+ + + + + + diff --git a/color-spaces/index.html b/color-spaces/index.html new file mode 100644 index 00000000..bbfc1d0e --- /dev/null +++ b/color-spaces/index.html @@ -0,0 +1,924 @@ + + + + + + + Color Spaces · culori + + + + + + +
+
+ + +
+
+ +
+
+

Color Spaces

+

This is an overview of the color spaces built into Culori, listing their channels and expected ranges.

+
+A note on terminology +

A color model is a way to describe colors along certain dimensions. RGB, for example, is a color model: color is expressed as a combination of red, green and blue.

+

A color model, along with a precise description of how colors in the model are to be interpreted, makes a color space. sRGB, Display P3 and ProPhoto RGB are all color spaces that employ the RGB model: they describe colors as combination of red, green, and blue primaries; however, they have different notions of how these red, green, and blue primary colors look.

+

In some color spaces, such as CIELAB or CIELCh, some channels don't have fixed ranges. For these channels, approximate ranges are obtained by converting all sRGB colors defined by r, g, b ∈ ℕ ⋂ [0, 255] to that specific color space. Whenever that's the case, the range is marked with the approximation symbol .

+

In addition to the channels listed below, all color spaces also take an optional alpha channel with the range [0, 1].

+
+

Built-in color spaces

+

The RGB model

+

The RGB color model describes colors as mixtures of red, green, and blue primaries. Culori implements several RGB color spaces, all sharing these channels:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
r[0, 1]Red channel
g[0, 1]Green channel
b[0, 1]Blue channel
+

rgb

+

The sRGB color space, which most people refer to when talking about RGB colors.

+

Serialized as color(srgb r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

lrgb

+

The linear-light form of the sRGB color space.

+

Serialized as color(srgb-linear r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

a98

+

The A98 RGB color space, compatible with the Adobe RGB (1998) color space.

+

Serialized as color(a98-rgb r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

p3

+

The Display P3 color space.

+

Serialized as color(display-p3 r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

prophoto

+

The ProPhoto RGB color space.

+

Serialized as color(prophoto-rgb r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

rec2020

+

The Rec. 2020 color space.

+

Serialized as color(rec2020 r g b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

The HSL/HSV/HSI family

+

HSL, HSV, and HSI are alternative representations of the RGB color model, created in an attempt to provide a more intuitive way to specify colors.

+

The hue is identical across all color models in this family; however, the saturaton is computed differently in each. The saturation in HSL is not interchangeable with the saturation from HSV, nor HSI. Achromatic colors (shades of gray) will have an undefined hue.

+

As color spaces, they relate to the sRGB color space.

+

hsl

+

The HSL color space.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
s[0, 1]Saturation in HSL
l[0, 1]Lightness
+

Serialized as hsl(h s% l%). A missing hue is serialized as 0, with the none keyword for any other missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

hsv

+

The HSV color space.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
s[0, 1]Saturation in HSV
v[0, 1]Value
+

Serialized as color(--hsv h s v), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

hsi

+

The HSI color space.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
s[0, 1]Saturation in HSI
i[0, 1]Intensity
+

Serialized as color(--hsi h s i), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+

HWB

+

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

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
w[0, 1]Whiteness
b[0, 1]Blackness
+

Serialized as hwb(h w% b%).

+

Serialized as hwb(h w% b%). A missing hue is serialized as 0, with the none keyword for any other missing color channel. An explicit alpha < 1 is included as / alpha.

+

Has gamut limits.

+
+

Smith, Alvy Ray (1996) — "HWB — A More Intuitive Hue-Based Color Model", Journal of Graphics, GPU and Game tools.

+
+

CIELAB

+

The CIELAB color space, also known as CIE 1976 L*a*b*, in Cartesian (Lab) and cylindrical (LCh) forms.

+

lab

+

The CIELAB color space using the D50 standard illuminant as the reference white, following the CSS Color Module Level 4 specification.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelCSS Reference RangeDescription
l[0, 100]Lightness
a[-125, 125]Green–red component
b[-125, 125]Blue–yellow component
+

Serialized as lab(l a b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

lch

+

The CIELCh color space using the D50 standard illuminant.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelCSS Reference RangeDescription
l[0, 100]Lightness
c[0, 150]Chroma
h[0, 360)Hue
+

Serialized as lch(l c h). A missing hue is serialized as 0, with the none keyword for any other missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

lab65

+

CIELAB relative to the D65 standard illuminant.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
a[-125, 125]Green–red component
b[-125, 125]Blue–yellow component
+

Serialized as color(--lab-d65 l a b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

lch65

+

CIELCh relative to the D65 standard illuminant.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
c[0, 150]Chroma
h[0, 360)Hue
+

Serialized as color(--lch-d65 l c h), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

CIELUV

+

The CIELUV color space in Cartesian (Luv) and cylindrical (LCh) forms, using the D50 standard illuminant.

+

CIELuv has an effective Euclidean color difference function:

+
let deltaE_uv = culori.colorDifferenceEuclidean('luv');
+

luv

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
u[-84.936, 175.042]Green–red component
v[-125.882, 87.243]Blue–yellow component
+

Serialized as color(--luv l u v), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

lchuv

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
c[0, 176.956]Chroma
h[0, 360)Hue
+

Serialized as color(--lchuv l c h), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

DIN99 Lab / LCh

+

The DIN99 color space "squishes" the CIELAB D65 color space to obtain an effective color difference metric that can be expressed as a simple Euclidean distance. The latest iteration of the the standard, DIN99o, is available in Cartesian (dlab) and polar (dlch) form.

+
+

Industrial Color Physics, Georg A. Klein, Springer (2010) +DIN 6176, Colorimetric determination of colour differences of object colours according to the DIN99o formula

+
+

dlab

+

The DIN99o color space in Cartesian form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
a[-40.09, 45.501]
b[-40.469, 44.344]
+

Serialized as color(--din99o-lab l a b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

dlch

+

The DIN99o color space in cylindrical form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 100]Lightness
c[0, 51.484]Chroma
h[0, 360)Hue
+

Serialized as color(--din99o-lch l c h), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

Oklab, Oklch, Okhsl, Okhsv

+

The Oklab color space, in Cartesian (Lab) and cylindrical (LCh) forms. It uses the D65 standard illuminant.

+

See also: Okhsl and Okhsv, two new color spaces for color picking.

+

oklab

+

The Oklab color space in Cartesian form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelCSS Reference RangeDescription
l[0, 1]Lightness
a[-0.4, 0.4]Green–red component
b[-0.4, 0.4]Blue–yellow component
+

Serialized as oklab(l a b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

oklch

+

The Oklab color space in cylindrical form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
l[0, 1]Lightness
c[0, 0.4]Chroma
h[0, 360)Hue
+

Serialized as oklch(l c h), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

okhsl

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
s[0, 1]Saturation (Okhsl)
l[0, 1]Lightness
+

Serialized as color(--okhsl h s l), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

okhsv

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
h[0, 360)Hue
s[0, 1]Saturation (Okhsv)
v[0, 1]Value
+

Serialized as color(--okhsv h s v), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

Further reading

+ +

Jzazbz

+

The Jzazbz color space, as defined by:

+
+

Muhammad Safdar, Guihua Cui, Youn Jin Kim, and Ming Ronnier Luo, "Perceptually uniform color space for image signals including high dynamic range and wide gamut", Opt. Express 25, 15131-15151 (2017)

+
+

jab

+

The Jzazbz color space in Cartesian form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
j[0, 0.222]Lightness
a[-0.109, 0.129]Green–red component
b[-0.185, 0.134]Blue–yellow component
+

Serialized as color(--jzazbz j a b), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

jch

+

The Jzazbz color space in cylindrical form.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
j[0, 0.222]Lightness
c[0, 0.190]Chroma
h[0, 360)Hue
+

Serialized as color(--jzczhz j c h), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

YIQ (yiq)

+

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

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
y[0, 1]Luma
i[-0.595, 0.595]In-phase (orange-blue axis)
q[-0.522, 0.522]Quadrature (green-purple axis)
+

Serialized as color(--yiq y i q), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

The conversion matrices between the sRGB and YIQ color spaces are taken from:

+
+

Yuriy Kotsarenko, Fernando Ramos, Measuring perceived color difference using YIQ NTSC transmission color space in mobile applications, Programación Matemática y Software (2010), Vol. 2, No 2.

+
+

CIE XYZ

+

The CIE XYZ color space, also known as the CIE 1931 color space.

+

xyz50

+

The CIE XYZ color space in respect to the D50 standard illuminant.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
x[0, 0.964]?
y[0, 0.999]?
z[0, 0.825]?
+

Serialized as color(xyz-d50 x y z), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

xyz65

+

The CIE XYZ color space in respect to the D65 standard illuminant.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
x[0, 0.950]?
y[0, 1]?
z[0, 1.088]?
+

Serialized as color(xyz-d65 x y z), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

XYB

+

The XYB color model is part of the JPEG XL Image Coding System, as an LMS-based colour model inspired by the human visual system, facilitating perceptually uniform quantization. It uses a gamma of 3 for computationally efficient decoding.

+

xyb

+

The default XYB color space, defined in relationship to sRGB.

+

It has the default Chroma from Luma adjustment applied (effectively Y is subtracted from B) so that colors with { x: 0, b: 0 } coordinates are achromatic.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
x[-0.0154, 0.0281]Cyan-red component
y[0, 0.8453]Luma
b[ -0.2778, 0.3880 ]Blue-yellow component
+

Does not have gamut limits.

+

ICtCp

+

ICtCp (or ITP) color space developed by Dolby Laboratories, as defined in ITU-R Recommendation BT.2100. It is included in the CSS HDR Color Module Level 1 specification.

+

itp

+ + + + + + + + + + + + + + + + + + + + + + + + + +
ChannelRangeDescription
i[0, 0.581]Intensity
t[-0.282, 0.278]Blue-yellow component (“tritanopia”)
p[-0.162, 0.279]Green–red component (“protanopia”)
+

Serialized as color(--ictcp i t p), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+

The itp color space is the basis of the ΔEITP color difference metric defined in Objective metric for the assessment of the potential visibility of colour differences in television (Rec. ITU-R BT.2124).

+

Cubehelix

+

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

+
+

Green, D. A., 2011, "A colour scheme for the display of astronomical intensity images", Bulletin of the Astronomical Society of India, 39, 289. (2011BASI...39..289G at ADS)

+
+

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

+

cubehelix

+

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

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

Serialized as color(--cubehelix h s l), with the none keyword for any missing color channel. An explicit alpha < 1 is included as / alpha.

+

Does not have gamut limits.

+ +
+
+ + + + + + diff --git a/docs/css/main.css b/css/main.css similarity index 100% rename from docs/css/main.css rename to css/main.css diff --git a/culori.min.mjs b/culori.min.mjs new file mode 100644 index 00000000..0b24f260 --- /dev/null +++ b/culori.min.mjs @@ -0,0 +1,4 @@ +var kn=(e,t)=>{if(typeof e=="number"){if(t===3)return{mode:"rgb",r:(e>>8&15|e>>4&240)/255,g:(e>>4&15|e&240)/255,b:(e&15|e<<4&240)/255};if(t===4)return{mode:"rgb",r:(e>>12&15|e>>8&240)/255,g:(e>>8&15|e>>4&240)/255,b:(e>>4&15|e&240)/255,alpha:(e&15|e<<4&240)/255};if(t===6)return{mode:"rgb",r:(e>>16&255)/255,g:(e>>8&255)/255,b:(e&255)/255};if(t===8)return{mode:"rgb",r:(e>>24&255)/255,g:(e>>16&255)/255,b:(e>>8&255)/255,alpha:(e&255)/255}}},st=kn;var Ln={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},rr=Ln;var Rn=e=>st(rr[e.toLowerCase()],6),or=Rn;var wn=/^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i,_n=e=>{let t;return(t=e.match(wn))?st(parseInt(t[1],16),t[1].length):void 0},nr=_n;var I="([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)",gi=`(?:${I}|none)`,he=`${I}%`,Mi=`(?:${I}%|none)`,Ye=`(?:${I}%|${I})`,Hn=`(?:${I}%|${I}|none)`,Mo=`(?:${I}(deg|grad|rad|turn)|${I})`,vi=`(?:${I}(deg|grad|rad|turn)|${I}|none)`,le="\\s*,\\s*";var yi=new RegExp("^"+Hn+"$");var Xn=new RegExp(`^rgba?\\(\\s*${I}${le}${I}${le}${I}\\s*(?:,\\s*${Ye}\\s*)?\\)$`),Sn=new RegExp(`^rgba?\\(\\s*${he}${le}${he}${le}${he}\\s*(?:,\\s*${Ye}\\s*)?\\)$`),Pn=e=>{let t={mode:"rgb"},r;if(r=e.match(Xn))r[1]!==void 0&&(t.r=r[1]/255),r[2]!==void 0&&(t.g=r[2]/255),r[3]!==void 0&&(t.b=r[3]/255);else if(r=e.match(Sn))r[1]!==void 0&&(t.r=r[1]/100),r[2]!==void 0&&(t.g=r[2]/100),r[3]!==void 0&&(t.b=r[3]/100);else return;return r[4]!==void 0?t.alpha=Math.max(0,Math.min(1,r[4]/100)):r[5]!==void 0&&(t.alpha=Math.max(0,Math.min(1,+r[5]))),t},ar=Pn;var Cn=(e,t)=>e===void 0?void 0:typeof e!="object"?fr(e):e.mode!==void 0?e:t?{...e,mode:t}:void 0,P=Cn;var $n=(e="rgb")=>t=>(t=P(t,e))!==void 0?t.mode===e?t:F[t.mode][e]?F[t.mode][e](t):e==="rgb"?F[t.mode].rgb(t):F.rgb[e](F[t.mode].rgb(t)):void 0,v=$n;var F={},vo={},be=[],ct={},Nn=e=>e,T=e=>(F[e.mode]={...F[e.mode],...e.toMode},Object.keys(e.fromMode||{}).forEach(t=>{F[t]||(F[t]={}),F[t][e.mode]=e.fromMode[t]}),e.ranges||(e.ranges={}),e.difference||(e.difference={}),e.channels.forEach(t=>{if(e.ranges[t]===void 0&&(e.ranges[t]=[0,1]),!e.interpolate[t])throw new Error(`Missing interpolator for: ${t}`);typeof e.interpolate[t]=="function"&&(e.interpolate[t]={use:e.interpolate[t]}),e.interpolate[t].fixup||(e.interpolate[t].fixup=Nn)}),vo[e.mode]=e,(e.parse||[]).forEach(t=>{yo(t,e.mode)}),v(e.mode)),R=e=>vo[e],yo=(e,t)=>{if(typeof e=="string"){if(!t)throw new Error("'mode' required when 'parser' is a string");ct[e]=t}else typeof e=="function"&&be.indexOf(e)<0&&be.push(e)},In=e=>{if(typeof e=="string")delete ct[e];else if(typeof e=="function"){let t=be.indexOf(e);t>0&&be.splice(t,1)}};var ir=/[^\x00-\x7F]|[a-zA-Z_]/,On=/[^\x00-\x7F]|[-\w]/,d={Function:"function",Ident:"ident",Number:"number",Percentage:"percentage",ParenClose:")",None:"none",Hue:"hue",Alpha:"alpha"},x=0;function ht(e){let t=e[x],r=e[x+1];return t==="-"||t==="+"?/\d/.test(r)||r==="."&&/\d/.test(e[x+2]):t==="."?/\d/.test(r):/\d/.test(t)}function lr(e){if(x>=e.length)return!1;let t=e[x];if(ir.test(t))return!0;if(t==="-"){if(e.length-x<2)return!1;let r=e[x+1];return!!(r==="-"||ir.test(r))}return!1}var An={deg:1,rad:180/Math.PI,grad:9/10,turn:360};function Ge(e){let t="";if((e[x]==="-"||e[x]==="+")&&(t+=e[x++]),t+=bt(e),e[x]==="."&&/\d/.test(e[x+1])&&(t+=e[x++]+bt(e)),(e[x]==="e"||e[x]==="E")&&((e[x+1]==="-"||e[x+1]==="+")&&/\d/.test(e[x+2])?t+=e[x++]+e[x++]+bt(e):/\d/.test(e[x+1])&&(t+=e[x++]+bt(e))),lr(e)){let r=xt(e);return r==="deg"||r==="rad"||r==="turn"||r==="grad"?{type:d.Hue,value:t*An[r]}:void 0}return e[x]==="%"?(x++,{type:d.Percentage,value:+t}):{type:d.Number,value:+t}}function bt(e){let t="";for(;/\d/.test(e[x]);)t+=e[x++];return t}function xt(e){let t="";for(;x4)){if(r.length===4){if(r[3].type!==d.Alpha)return;r[3]=r[3].value}return r.length===3&&r.push({type:d.None,value:void 0}),r.every(n=>n.type!==d.Alpha)?r:void 0}}function Jn(e,t){e._i=0;let r=e[e._i++];if(!r||r.type!==d.Function)return;let o=To(e,t);if(o)return o.unshift(r.value),o}var jn=e=>{if(typeof e!="string")return;let t=Dn(e),r=t?Jn(t,!0):void 0,o,n=0,a=be.length;for(;ne==="transparent"?{mode:"rgb",r:0,g:0,b:0,alpha:0}:void 0,dr=Gn;var V=(e,t,r)=>e+r*(t-e),Bn=(e,t,r)=>(r-e)/(t-e),ur=(e,t,r,o,n,a)=>V(V(e,t,n),V(r,o,n),a),Zn=(e,t,r,o,n,a,f,i,l,m,u)=>V(ur(e,t,r,o,l,m),ur(n,a,f,i,l,m),u);var Fn=e=>{let t=[];for(let r=0;rt=>{let r=Fn(t);return o=>{let n=o*r.length,a=o>=1?r.length-1:Math.max(Math.floor(n),0),f=r[a];return f===void 0?void 0:e(f[0],f[1],n-a)}};var p=mr(V);var y=e=>{let t=!1,r=e.map(o=>o!==void 0?(t=!0,o):1);return t?r:e};var Wn={mode:"rgb",channels:["r","g","b","alpha"],parse:[pr,nr,ar,or,dr,"srgb"],serialize:"srgb",interpolate:{r:p,g:p,b:p,alpha:{use:p,fixup:y}},gamut:!0,white:{r:1,g:1,b:1},black:{r:0,g:0,b:0}},Z=Wn;var sr=(e=0)=>Math.pow(Math.abs(e),2.19921875)*Math.sign(e),Un=e=>{let t=sr(e.r),r=sr(e.g),o=sr(e.b),n={mode:"xyz65",x:.5766690429101305*t+.1855582379065463*r+.1882286462349947*o,y:.297344975250536*t+.6273635662554661*r+.0752914584939979*o,z:.0270313613864123*t+.0706888525358272*r+.9913375368376386*o};return e.alpha!==void 0&&(n.alpha=e.alpha),n},gt=Un;var cr=e=>Math.pow(Math.abs(e),.4547069271758437)*Math.sign(e),Kn=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"a98",r:cr(e*2.0415879038107465-t*.5650069742788597-.3447313507783297*r),g:cr(e*-.9692436362808798+t*1.8759675015077206+.0415550574071756*r),b:cr(e*.0134442806320312-t*.1183623922310184+1.0151749943912058*r)};return o!==void 0&&(n.alpha=o),n},Mt=Kn;var hr=(e=0)=>{let t=Math.abs(e);return t<=.04045?e/12.92:(Math.sign(e)||1)*Math.pow((t+.055)/1.055,2.4)},Qn=({r:e,g:t,b:r,alpha:o})=>{let n={mode:"lrgb",r:hr(e),g:hr(t),b:hr(r)};return o!==void 0&&(n.alpha=o),n},j=Qn;var Vn=e=>{let{r:t,g:r,b:o,alpha:n}=j(e),a={mode:"xyz65",x:.4123907992659593*t+.357584339383878*r+.1804807884018343*o,y:.2126390058715102*t+.715168678767756*r+.0721923153607337*o,z:.0193308187155918*t+.119194779794626*r+.9505321522496607*o};return n!==void 0&&(a.alpha=n),a},O=Vn;var br=(e=0)=>{let t=Math.abs(e);return t>.0031308?(Math.sign(e)||1)*(1.055*Math.pow(t,.4166666666666667)-.055):e*12.92},e0=({r:e,g:t,b:r,alpha:o},n="rgb")=>{let a={mode:n,r:br(e),g:br(t),b:br(r)};return o!==void 0&&(a.alpha=o),a},Y=e0;var t0=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Y({r:e*3.2409699419045226-t*1.537383177570094-.4986107602930034*r,g:e*-.9692436362808796+t*1.8759675015077204+.0415550574071756*r,b:e*.0556300796969936-t*.2039769588889765+1.0569715142428784*r});return o!==void 0&&(n.alpha=o),n},A=t0;var r0={...Z,mode:"a98",parse:["a98-rgb"],serialize:"a98-rgb",fromMode:{rgb:e=>Mt(O(e)),xyz65:Mt},toMode:{rgb:e=>A(gt(e)),xyz65:gt}},zo=r0;var o0=e=>(e=e%360)<0?e+360:e,k=o0;var vt=(e,t)=>e.map((r,o,n)=>{if(r===void 0)return r;let a=k(r);return o===0||e[o-1]===void 0?a:t(a-k(n[o-1]))}).reduce((r,o)=>!r.length||o===void 0||r[r.length-1]===void 0?(r.push(o),r):(r.push(o+r[r.length-1]),r),[]),X=e=>vt(e,t=>Math.abs(t)<=180?t:t-360*Math.sign(t)),n0=e=>vt(e,t=>Math.abs(t)>=180||t===0?t:t-360*Math.sign(t)),a0=e=>vt(e,t=>t>=0?t:t+360),f0=e=>vt(e,t=>t<=0?t:t-360);var _=[-.14861,1.78277,-.29227,-.90649,1.97294,0],ko=Math.PI/180,Lo=180/Math.PI;var Ro=_[3]*_[4],wo=_[1]*_[4],_o=_[1]*_[2]-_[0]*_[3],i0=({r:e,g:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=(_o*r+e*Ro-t*wo)/(_o+Ro-wo),a=r-n,f=(_[4]*(t-n)-_[2]*a)/_[3],i={mode:"cubehelix",l:n,s:n===0||n===1?void 0:Math.sqrt(a*a+f*f)/(_[4]*n*(1-n))};return i.s&&(i.h=Math.atan2(f,a)*Lo-120),o!==void 0&&(i.alpha=o),i},xr=i0;var l0=({h:e,s:t,l:r,alpha:o})=>{let n={mode:"rgb"};e=(e===void 0?0:e+120)*ko,r===void 0&&(r=0);let a=t===void 0?0:t*r*(1-r),f=Math.cos(e),i=Math.sin(e);return n.r=r+a*(_[0]*f+_[1]*i),n.g=r+a*(_[2]*f+_[3]*i),n.b=r+a*(_[4]*f+_[5]*i),o!==void 0&&(n.alpha=o),n},gr=l0;var ee=(e,t)=>{if(e.h===void 0||t.h===void 0||!e.s||!t.s)return 0;let r=k(e.h),o=k(t.h),n=Math.sin((o-r+360)/2*Math.PI/180);return 2*Math.sqrt(e.s*t.s)*n},Mr=(e,t)=>{if(e.h===void 0||t.h===void 0)return 0;let r=k(e.h),o=k(t.h);return Math.abs(o-r)>180?r-(o-360*Math.sign(o-r)):o-r},te=(e,t)=>{if(e.h===void 0||t.h===void 0||!e.c||!t.c)return 0;let r=k(e.h),o=k(t.h),n=Math.sin((o-r+360)/2*Math.PI/180);return 2*Math.sqrt(e.c*t.c)*n},pe=(e="rgb",t=[1,1,1,0])=>{let r=R(e),o=r.channels,n=r.difference,a=v(e);return(f,i)=>{let l=a(f),m=a(i);return Math.sqrt(o.reduce((u,s,h)=>{let c=n[s]?n[s](l,m):l[s]-m[s];return u+(t[h]||0)*Math.pow(isNaN(c)?0:c,2)},0))}},p0=()=>pe("lab65"),d0=(e=1,t=.045,r=.015)=>{let o=v("lab65");return(n,a)=>{let f=o(n),i=o(a),l=f.l,m=f.a,u=f.b,s=Math.sqrt(m*m+u*u),h=i.l,c=i.a,b=i.b,M=Math.sqrt(c*c+b*b),g=Math.pow(l-h,2),z=Math.pow(s-M,2),L=Math.pow(m-c,2)+Math.pow(u-b,2)-z;return Math.sqrt(g/Math.pow(e,2)+z/Math.pow(1+t*s,2)+L/Math.pow(1+r*s,2))}},u0=(e=1,t=1,r=1)=>{let o=v("lab65");return(n,a)=>{let f=o(n),i=o(a),l=f.l,m=f.a,u=f.b,s=Math.sqrt(m*m+u*u),h=i.l,c=i.a,b=i.b,M=Math.sqrt(c*c+b*b),g=(s+M)/2,z=.5*(1-Math.sqrt(Math.pow(g,7)/(Math.pow(g,7)+Math.pow(25,7)))),L=m*(1+z),H=c*(1+z),C=Math.sqrt(L*L+u*u),$=Math.sqrt(H*H+b*b),N=Math.abs(L)+Math.abs(u)===0?0:Math.atan2(u,L);N+=(N<0)*2*Math.PI;let q=Math.abs(H)+Math.abs(b)===0?0:Math.atan2(b,H);q+=(q<0)*2*Math.PI;let me=h-l,ie=$-C,B=C*$===0?0:q-N;B-=(B>Math.PI)*2*Math.PI,B+=(B<-Math.PI)*2*Math.PI;let Q=2*Math.sqrt(C*$)*Math.sin(B/2),se=(l+h)/2,ce=(C+$)/2,D;C*$===0?D=N+q:(D=(N+q)/2,D-=(Math.abs(N-q)>Math.PI)*Math.PI,D+=(D<0)*2*Math.PI);let dt=Math.pow(se-50,2),ut=1-.17*Math.cos(D-Math.PI/6)+.24*Math.cos(2*D)+.32*Math.cos(3*D+Math.PI/30)-.2*Math.cos(4*D-63*Math.PI/180),Ee=1+.015*dt/Math.sqrt(20+dt),Je=1+.045*ce,ke=1+.015*ce*ut,Vt=30*Math.PI/180*Math.exp(-1*Math.pow((180/Math.PI*D-275)/25,2)),mt=2*Math.sqrt(Math.pow(ce,7)/(Math.pow(ce,7)+Math.pow(25,7))),je=-1*Math.sin(2*Vt)*mt;return Math.sqrt(Math.pow(me/(e*Ee),2)+Math.pow(ie/(t*Je),2)+Math.pow(Q/(r*ke),2)+je*ie/(t*Je)*Q/(r*ke))}},m0=(e=1,t=1)=>{let r=v("lab65");return(o,n)=>{let a=r(o),f=a.l,i=a.a,l=a.b,m=Math.sqrt(i*i+l*l),u=Math.atan2(l,i);u=u+2*Math.PI*(u<0);let s=r(n),h=s.l,c=s.a,b=s.b,M=Math.sqrt(c*c+b*b),g=Math.pow(f-h,2),z=Math.pow(m-M,2),L=Math.pow(i-c,2)+Math.pow(l-b,2)-z,H=Math.sqrt(Math.pow(m,4)/(Math.pow(m,4)+1900)),C=u>=164/180*Math.PI&&u<=345/180*Math.PI?.56+Math.abs(.2*Math.cos(u+168/180*Math.PI)):.36+Math.abs(.4*Math.cos(u+35/180*Math.PI)),$=f<16?.511:.040975*f/(1+.01765*f),N=.0638*m/(1+.0131*m)+.638,q=N*(H*C+1-H);return Math.sqrt(g/Math.pow(e*$,2)+z/Math.pow(t*N,2)+L/Math.pow(q,2))}},s0=()=>{let e=v("lab65");return(t,r)=>{let o=e(t),n=e(r),a=o.l-n.l,f=o.a-n.a,i=o.b-n.b;return Math.abs(a)+Math.sqrt(f*f+i*i)}},c0=()=>pe("yiq",[.5053,.299,.1957]),h0=()=>pe("itp",[518400,129600,518400]);var S=e=>{let t=e.reduce((o,n)=>{if(n!==void 0){let a=n*Math.PI/180;o.sin+=Math.sin(a),o.cos+=Math.cos(a)}return o},{sin:0,cos:0}),r=Math.atan2(t.sin,t.cos)*180/Math.PI;return r<0?360+r:r},Ho=e=>{let t=e.filter(r=>r!==void 0);return t.length?t.reduce((r,o)=>r+o,0)/t.length:void 0},vr=e=>typeof e=="function";function b0(e,t="rgb",r){let o=R(t),n=e.map(v(t));return o.channels.reduce((a,f)=>{let i=n.map(l=>l[f]).filter(l=>l!==void 0);if(i.length){let l;vr(r)?l=r:r&&vr(r[f])?l=r[f]:o.average&&vr(o.average[f])?l=o.average[f]:l=Ho,a[f]=l(i,f)}return a},{mode:t})}var x0={mode:"cubehelix",channels:["h","s","l","alpha"],parse:["--cubehelix"],serialize:"--cubehelix",ranges:{h:[0,360],s:[0,4.614],l:[0,1]},fromMode:{rgb:xr},toMode:{rgb:gr},interpolate:{h:{use:p,fixup:X},s:p,l:p,alpha:{use:p,fixup:y}},difference:{h:ee},average:{h:S}},Xo=x0;var g0=({l:e,a:t,b:r,alpha:o},n="lch")=>{t===void 0&&(t=0),r===void 0&&(r=0);let a=Math.sqrt(t*t+r*r),f={mode:n,l:e,c:a};return a&&(f.h=k(Math.atan2(r,t)*180/Math.PI)),o!==void 0&&(f.alpha=o),f},E=g0;var M0=({l:e,c:t,h:r,alpha:o},n="lab")=>{r===void 0&&(r=0);let a={mode:n,l:e,a:t?t*Math.cos(r/180*Math.PI):0,b:t?t*Math.sin(r/180*Math.PI):0};return o!==void 0&&(a.alpha=o),a},J=M0;var yt=Math.pow(29,3)/Math.pow(3,3),Tt=Math.pow(6,3)/Math.pow(29,3);var w={X:.9642956764295677,Y:1,Z:.8251046025104602},de={X:.3127/.329,Y:1,Z:(1-.3127-.329)/.329},Al=Math.pow(29,3)/Math.pow(3,3),ql=Math.pow(6,3)/Math.pow(29,3);var yr=e=>Math.pow(e,3)>Tt?Math.pow(e,3):(116*e-16)/yt,v0=({l:e,a:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=(e+16)/116,a=t/500+n,f=n-r/200,i={mode:"xyz65",x:yr(a)*de.X,y:yr(n)*de.Y,z:yr(f)*de.Z};return o!==void 0&&(i.alpha=o),i},Be=v0;var y0=e=>A(Be(e)),re=y0;var Tr=e=>e>Tt?Math.cbrt(e):(yt*e+16)/116,T0=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Tr(e/de.X),a=Tr(t/de.Y),f=Tr(r/de.Z),i={mode:"lab65",l:116*a-16,a:500*(n-a),b:200*(a-f)};return o!==void 0&&(i.alpha=o),i},Ze=T0;var z0=e=>{let t=Ze(O(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},oe=z0;var xe=.14444444444444443*Math.PI,Le=Math.cos(xe),Re=Math.sin(xe),zt=100/Math.log(139/100);var k0=({l:e,c:t,h:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"lab65",l:(Math.exp(e*1/zt)-1)/.0039},a=(Math.exp(.0435*t*1*1)-1)/.075,f=a*Math.cos(r/180*Math.PI-xe),i=a*Math.sin(r/180*Math.PI-xe);return n.a=f*Le-i/.83*Re,n.b=f*Re+i/.83*Le,o!==void 0&&(n.alpha=o),n},we=k0;var L0=({l:e,a:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=t*Le+r*Re,a=.83*(r*Le-t*Re),f=Math.sqrt(n*n+a*a),i={mode:"dlch",l:zt/1*Math.log(1+.0039*e),c:Math.log(1+.075*f)/(.0435*1*1)};return i.c&&(i.h=k((Math.atan2(a,n)+xe)/Math.PI*180)),o!==void 0&&(i.alpha=o),i},_e=L0;var So=e=>we(E(e,"dlch")),Po=e=>J(_e(e),"dlab"),R0={mode:"dlab",parse:["--din99o-lab"],serialize:"--din99o-lab",toMode:{lab65:So,rgb:e=>re(So(e))},fromMode:{lab65:Po,rgb:e=>Po(oe(e))},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-40.09,45.501],b:[-40.469,44.344]},interpolate:{l:p,a:p,b:p,alpha:{use:p,fixup:y}}},Co=R0;var w0={mode:"dlch",parse:["--din99o-lch"],serialize:"--din99o-lch",toMode:{lab65:we,dlab:e=>J(e,"dlab"),rgb:e=>re(we(e))},fromMode:{lab65:_e,dlab:e=>E(e,"dlch"),rgb:e=>_e(oe(e))},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,51.484],h:[0,360]},interpolate:{l:p,c:p,h:{use:p,fixup:X},alpha:{use:p,fixup:y}},difference:{h:te},average:{h:S}},$o=w0;function kt({h:e,s:t,i:r,alpha:o}){e=k(e!==void 0?e:0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.abs(e/60%2-1),a;switch(Math.floor(e/60)){case 0:a={r:r*(1+t*(3/(2-n)-1)),g:r*(1+t*(3*(1-n)/(2-n)-1)),b:r*(1-t)};break;case 1:a={r:r*(1+t*(3*(1-n)/(2-n)-1)),g:r*(1+t*(3/(2-n)-1)),b:r*(1-t)};break;case 2:a={r:r*(1-t),g:r*(1+t*(3/(2-n)-1)),b:r*(1+t*(3*(1-n)/(2-n)-1))};break;case 3:a={r:r*(1-t),g:r*(1+t*(3*(1-n)/(2-n)-1)),b:r*(1+t*(3/(2-n)-1))};break;case 4:a={r:r*(1+t*(3*(1-n)/(2-n)-1)),g:r*(1-t),b:r*(1+t*(3/(2-n)-1))};break;case 5:a={r:r*(1+t*(3/(2-n)-1)),g:r*(1-t),b:r*(1+t*(3*(1-n)/(2-n)-1))};break;default:a={r:r*(1-t),g:r*(1-t),b:r*(1-t)}}return a.mode="rgb",o!==void 0&&(a.alpha=o),a}function Lt({r:e,g:t,b:r,alpha:o}){e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.max(e,t,r),a=Math.min(e,t,r),f={mode:"hsi",s:e+t+r===0?0:1-3*a/(e+t+r),i:(e+t+r)/3};return n-a!==0&&(f.h=(n===e?(t-r)/(n-a)+(t{switch(t){case"deg":return+e;case"rad":return e/Math.PI*180;case"grad":return e/10*9;case"turn":return e*360}},Io=H0;var X0=new RegExp(`^hsla?\\(\\s*${Mo}${le}${he}${le}${he}\\s*(?:,\\s*${Ye}\\s*)?\\)$`),S0=e=>{let t=e.match(X0);if(!t)return;let r={mode:"hsl"};return t[3]!==void 0?r.h=+t[3]:t[1]!==void 0&&t[2]!==void 0&&(r.h=Io(t[1],t[2])),t[4]!==void 0&&(r.s=Math.min(Math.max(0,t[4]/100),1)),t[5]!==void 0&&(r.l=Math.min(Math.max(0,t[5]/100),1)),t[6]!==void 0?r.alpha=Math.max(0,Math.min(1,t[6]/100)):t[7]!==void 0&&(r.alpha=Math.max(0,Math.min(1,+t[7]))),r},kr=S0;function P0(e,t){if(!t||t[0]!=="hsl"&&t[0]!=="hsla")return;let r={mode:"hsl"},[,o,n,a,f]=t;if(o.type!==d.None){if(o.type===d.Percentage)return;r.h=o.value}if(n.type!==d.None){if(n.type===d.Hue)return;r.s=n.value/100}if(a.type!==d.None){if(a.type===d.Hue)return;r.l=a.value/100}return f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var Lr=P0;var C0={mode:"hsl",toMode:{rgb:Rt},fromMode:{rgb:wt},channels:["h","s","l","alpha"],ranges:{h:[0,360]},gamut:"rgb",parse:[Lr,kr],serialize:e=>`hsl(${e.h!==void 0?e.h:"none"} ${e.s!==void 0?e.s*100+"%":"none"} ${e.l!==void 0?e.l*100+"%":"none"}${e.alpha<1?` / ${e.alpha}`:""})`,interpolate:{h:{use:p,fixup:X},s:p,l:p,alpha:{use:p,fixup:y}},difference:{h:ee},average:{h:S}},_t=C0;function He({h:e,s:t,v:r,alpha:o}){e=k(e!==void 0?e:0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.abs(e/60%2-1),a;switch(Math.floor(e/60)){case 0:a={r,g:r*(1-t*n),b:r*(1-t)};break;case 1:a={r:r*(1-t*n),g:r,b:r*(1-t)};break;case 2:a={r:r*(1-t),g:r,b:r*(1-t*n)};break;case 3:a={r:r*(1-t),g:r*(1-t*n),b:r};break;case 4:a={r:r*(1-t*n),g:r*(1-t),b:r};break;case 5:a={r,g:r*(1-t),b:r*(1-t*n)};break;default:a={r:r*(1-t),g:r*(1-t),b:r*(1-t)}}return a.mode="rgb",o!==void 0&&(a.alpha=o),a}function Xe({r:e,g:t,b:r,alpha:o}){e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.max(e,t,r),a=Math.min(e,t,r),f={mode:"hsv",s:n===0?0:1-a/n,v:n};return n-a!==0&&(f.h=(n===e?(t-r)/(n-a)+(t1){let n=t+r;t/=n,r/=n}return He({h:e,s:r===1?1:1-t/(1-r),v:1-r,alpha:o})}function St(e){let t=Xe(e);if(t===void 0)return;let r=t.s!==void 0?t.s:0,o=t.v!==void 0?t.v:0,n={mode:"hwb",w:(1-r)*o,b:1-o};return t.h!==void 0&&(n.h=t.h),t.alpha!==void 0&&(n.alpha=t.alpha),n}function N0(e,t){if(!t||t[0]!=="hwb")return;let r={mode:"hwb"},[,o,n,a,f]=t;if(o.type!==d.None){if(o.type===d.Percentage)return;r.h=o.value}if(n.type!==d.None){if(n.type===d.Hue)return;r.w=n.value/100}if(a.type!==d.None){if(a.type===d.Hue)return;r.b=a.value/100}return f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var Rr=N0;var I0={mode:"hwb",toMode:{rgb:Xt},fromMode:{rgb:St},channels:["h","w","b","alpha"],ranges:{h:[0,360]},gamut:"rgb",parse:[Rr],serialize:e=>`hwb(${e.h!==void 0?e.h:"none"} ${e.w!==void 0?e.w*100+"%":"none"} ${e.b!==void 0?e.b*100+"%":"none"}${e.alpha<1?` / ${e.alpha}`:""})`,interpolate:{h:{use:p,fixup:X},w:p,b:p,alpha:{use:p,fixup:y}},difference:{h:Mr},average:{h:S}},Oo=I0;var Se=.1593017578125,Ao=78.84375,Pe=.8359375,Ce=18.8515625,$e=18.6875;function Pt(e){if(e<0)return 0;let t=Math.pow(e,1/Ao);return 1e4*Math.pow(Math.max(0,t-Pe)/(Ce-$e*t),1/Se)}function Ct(e){if(e<0)return 0;let t=Math.pow(e/1e4,Se);return Math.pow((Pe+Ce*t)/(1+$e*t),Ao)}var wr=e=>Math.max(e/203,0),A0=({i:e,t,p:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Pt(e+.008609037037932761*t+.11102962500302593*r),a=Pt(e-.00860903703793275*t-.11102962500302599*r),f=Pt(e+.5600313357106791*t-.32062717498731885*r),i={mode:"xyz65",x:wr(2.070152218389422*n-1.3263473389671556*a+.2066510476294051*f),y:wr(.3647385209748074*n+.680566024947227*a-.0453045459220346*f),z:wr(-.049747207535812*n-.0492609666966138*a+1.1880659249923042*f)};return o!==void 0&&(i.alpha=o),i},$t=A0;var _r=(e=0)=>Math.max(e*203,0),q0=({x:e,y:t,z:r,alpha:o})=>{let n=_r(e),a=_r(t),f=_r(r),i=Ct(.3592832590121217*n+.6976051147779502*a-.0358915932320289*f),l=Ct(-.1920808463704995*n+1.1004767970374323*a+.0753748658519118*f),m=Ct(.0070797844607477*n+.0748396662186366*a+.8433265453898765*f),u=.5*i+.5*l,s=1.61376953125*i-3.323486328125*l+1.709716796875*m,h=4.378173828125*i-4.24560546875*l-.132568359375*m,c={mode:"itp",i:u,t:s,p:h};return o!==void 0&&(c.alpha=o),c},Nt=q0;var D0={mode:"itp",channels:["i","t","p","alpha"],parse:["--ictcp"],serialize:"--ictcp",toMode:{xyz65:$t,rgb:e=>A($t(e))},fromMode:{xyz65:Nt,rgb:e=>Nt(O(e))},ranges:{i:[0,.581],t:[-.369,.272],p:[-.164,.331]},interpolate:{i:p,t:p,p,alpha:{use:p,fixup:y}}},qo=D0;var E0=134.03437499999998,J0=16295499532821565e-27,Hr=e=>{if(e<0)return 0;let t=Math.pow(e/1e4,Se);return Math.pow((Pe+Ce*t)/(1+$e*t),E0)},Xr=(e=0)=>Math.max(e*203,0),j0=({x:e,y:t,z:r,alpha:o})=>{e=Xr(e),t=Xr(t),r=Xr(r);let n=1.15*e-.15*r,a=.66*t+.34*e,f=Hr(.41478972*n+.579999*a+.014648*r),i=Hr(-.20151*n+1.120649*a+.0531008*r),l=Hr(-.0166008*n+.2648*a+.6684799*r),m=(f+i)/2,u={mode:"jab",j:.44*m/(1-.56*m)-J0,a:3.524*f-4.066708*i+.542708*l,b:.199076*f+1.096799*i-1.295875*l};return o!==void 0&&(u.alpha=o),u},We=j0;var Y0=134.03437499999998,Do=16295499532821565e-27,Sr=e=>{if(e<0)return 0;let t=Math.pow(e,1/Y0);return 1e4*Math.pow((Pe-t)/($e*t-Ce),1/Se)},Pr=e=>e/203,G0=({j:e,a:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=(e+Do)/(.44+.56*(e+Do)),a=Sr(n+.13860504*t+.058047316*r),f=Sr(n-.13860504*t-.058047316*r),i=Sr(n-.096019242*t-.8118919*r),l={mode:"xyz65",x:Pr(1.661373024652174*a-.914523081304348*f+.23136208173913045*i),y:Pr(-.3250758611844533*a+1.571847026732543*f-.21825383453227928*i),z:Pr(-.090982811*a-.31272829*f+1.5227666*i)};return o!==void 0&&(l.alpha=o),l},Ue=G0;var B0=e=>{let t=We(O(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},Ke=B0;var Z0=e=>A(Ue(e)),Qe=Z0;var F0={mode:"jab",channels:["j","a","b","alpha"],parse:["--jzazbz"],serialize:"--jzazbz",fromMode:{rgb:Ke,xyz65:We},toMode:{rgb:Qe,xyz65:Ue},ranges:{j:[0,.222],a:[-.109,.129],b:[-.185,.134]},interpolate:{j:p,a:p,b:p,alpha:{use:p,fixup:y}}},Eo=F0;var W0=({j:e,a:t,b:r,alpha:o})=>{t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.sqrt(t*t+r*r),a={mode:"jch",j:e,c:n};return n&&(a.h=k(Math.atan2(r,t)*180/Math.PI)),o!==void 0&&(a.alpha=o),a},It=W0;var U0=({j:e,c:t,h:r,alpha:o})=>{r===void 0&&(r=0);let n={mode:"jab",j:e,a:t?t*Math.cos(r/180*Math.PI):0,b:t?t*Math.sin(r/180*Math.PI):0};return o!==void 0&&(n.alpha=o),n},Ot=U0;var K0={mode:"jch",parse:["--jzczhz"],serialize:"--jzczhz",toMode:{jab:Ot,rgb:e=>Qe(Ot(e))},fromMode:{rgb:e=>It(Ke(e)),jab:It},channels:["j","c","h","alpha"],ranges:{j:[0,.221],c:[0,.19],h:[0,360]},interpolate:{h:{use:p,fixup:X},c:p,j:p,alpha:{use:p,fixup:y}},difference:{h:te},average:{h:S}},Jo=K0;var ue=Math.pow(29,3)/Math.pow(3,3),Ne=Math.pow(6,3)/Math.pow(29,3);var Cr=e=>Math.pow(e,3)>Ne?Math.pow(e,3):(116*e-16)/ue,Q0=({l:e,a:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=(e+16)/116,a=t/500+n,f=n-r/200,i={mode:"xyz50",x:Cr(a)*w.X,y:Cr(n)*w.Y,z:Cr(f)*w.Z};return o!==void 0&&(i.alpha=o),i},ge=Q0;var V0=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Y({r:e*3.1341359569958707-t*1.6173863321612538-.4906619460083532*r,g:e*-.978795502912089+t*1.916254567259524+.03344273116131949*r,b:e*.07195537988411677-t*.2289768264158322+1.405386058324125*r});return o!==void 0&&(n.alpha=o),n},W=V0;var ea=e=>W(ge(e)),Ve=ea;var ta=e=>{let{r:t,g:r,b:o,alpha:n}=j(e),a={mode:"xyz50",x:.436065742824811*t+.3851514688337912*r+.14307845442264197*o,y:.22249319175623702*t+.7168870538238823*r+.06061979053616537*o,z:.013923904500943465*t+.09708128566574634*r+.7140993584005155*o};return n!==void 0&&(a.alpha=n),a},U=ta;var $r=e=>e>Ne?Math.cbrt(e):(ue*e+16)/116,ra=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=$r(e/w.X),a=$r(t/w.Y),f=$r(r/w.Z),i={mode:"lab",l:116*a-16,a:500*(n-a),b:200*(a-f)};return o!==void 0&&(i.alpha=o),i},Me=ra;var oa=e=>{let t=Me(U(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},et=oa;function na(e,t){if(!t||t[0]!=="lab")return;let r={mode:"lab"},[,o,n,a,f]=t;if(!(o.type===d.Hue||n.type===d.Hue||a.type===d.Hue))return o.type!==d.None&&(r.l=Math.min(Math.max(0,o.value),100)),n.type!==d.None&&(r.a=n.type===d.Number?n.value:n.value*125/100),a.type!==d.None&&(r.b=a.type===d.Number?a.value:a.value*125/100),f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var Nr=na;var aa={mode:"lab",toMode:{xyz50:ge,rgb:Ve},fromMode:{xyz50:Me,rgb:et},channels:["l","a","b","alpha"],ranges:{l:[0,100],a:[-125,125],b:[-125,125]},parse:[Nr],serialize:e=>`lab(${e.l!==void 0?e.l:"none"} ${e.a!==void 0?e.a:"none"} ${e.b!==void 0?e.b:"none"}${e.alpha<1?` / ${e.alpha}`:""})`,interpolate:{l:p,a:p,b:p,alpha:{use:p,fixup:y}}},Ie=aa;var fa={...Ie,mode:"lab65",parse:["--lab-d65"],serialize:"--lab-d65",toMode:{xyz65:Be,rgb:re},fromMode:{xyz65:Ze,rgb:oe},ranges:{l:[0,100],a:[-125,125],b:[-125,125]}},jo=fa;function ia(e,t){if(!t||t[0]!=="lch")return;let r={mode:"lch"},[,o,n,a,f]=t;if(o.type!==d.None){if(o.type===d.Hue)return;r.l=Math.min(Math.max(0,o.value),100)}if(n.type!==d.None&&(r.c=Math.max(0,n.type===d.Number?n.value:n.value*150/100)),a.type!==d.None){if(a.type===d.Percentage)return;r.h=a.value}return f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var Ir=ia;var la={mode:"lch",toMode:{lab:J,rgb:e=>Ve(J(e))},fromMode:{rgb:e=>E(et(e)),lab:E},channels:["l","c","h","alpha"],ranges:{l:[0,100],c:[0,150],h:[0,360]},parse:[Ir],serialize:e=>`lch(${e.l!==void 0?e.l:"none"} ${e.c!==void 0?e.c:"none"} ${e.h!==void 0?e.h:"none"}${e.alpha<1?` / ${e.alpha}`:""})`,interpolate:{h:{use:p,fixup:X},c:p,l:p,alpha:{use:p,fixup:y}},difference:{h:te},average:{h:S}},Oe=la;var pa={...Oe,mode:"lch65",parse:["--lch-d65"],serialize:"--lch-d65",toMode:{lab65:e=>J(e,"lab65"),rgb:e=>re(J(e,"lab65"))},fromMode:{rgb:e=>E(oe(e),"lch65"),lab65:e=>E(e,"lch65")},ranges:{l:[0,100],c:[0,150],h:[0,360]}},Yo=pa;var da=({l:e,u:t,v:r,alpha:o})=>{t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.sqrt(t*t+r*r),a={mode:"lchuv",l:e,c:n};return n&&(a.h=k(Math.atan2(r,t)*180/Math.PI)),o!==void 0&&(a.alpha=o),a},At=da;var ua=({l:e,c:t,h:r,alpha:o})=>{r===void 0&&(r=0);let n={mode:"luv",l:e,u:t?t*Math.cos(r/180*Math.PI):0,v:t?t*Math.sin(r/180*Math.PI):0};return o!==void 0&&(n.alpha=o),n},qt=ua;var Go=(e,t,r)=>4*e/(e+15*t+3*r),Bo=(e,t,r)=>9*t/(e+15*t+3*r),ma=Go(w.X,w.Y,w.Z),sa=Bo(w.X,w.Y,w.Z),ca=e=>e<=Ne?ue*e:116*Math.cbrt(e)-16,ha=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=ca(t/w.Y),a=Go(e,t,r),f=Bo(e,t,r);!isFinite(a)||!isFinite(f)?n=a=f=0:(a=13*n*(a-ma),f=13*n*(f-sa));let i={mode:"luv",l:n,u:a,v:f};return o!==void 0&&(i.alpha=o),i},Ae=ha;var ba=(e,t,r)=>4*e/(e+15*t+3*r),xa=(e,t,r)=>9*t/(e+15*t+3*r),ga=ba(w.X,w.Y,w.Z),Ma=xa(w.X,w.Y,w.Z),va=({l:e,u:t,v:r,alpha:o})=>{if(e===void 0&&(e=0),e===0)return{mode:"xyz50",x:0,y:0,z:0};t===void 0&&(t=0),r===void 0&&(r=0);let n=t/(13*e)+ga,a=r/(13*e)+Ma,f=w.Y*(e<=8?e/ue:Math.pow((e+16)/116,3)),i=f*(9*n)/(4*a),l=f*(12-3*n-20*a)/(4*a),m={mode:"xyz50",x:i,y:f,z:l};return o!==void 0&&(m.alpha=o),m},qe=va;var ya=e=>At(Ae(U(e))),Ta=e=>W(qe(qt(e))),za={mode:"lchuv",toMode:{luv:qt,rgb:Ta},fromMode:{rgb:ya,luv:At},channels:["l","c","h","alpha"],parse:["--lchuv"],serialize:"--lchuv",ranges:{l:[0,100],c:[0,176.956],h:[0,360]},interpolate:{h:{use:p,fixup:X},c:p,l:p,alpha:{use:p,fixup:y}},difference:{h:te},average:{h:S}},Zo=za;var ka={...Z,mode:"lrgb",toMode:{rgb:Y},fromMode:{rgb:j},parse:["srgb-linear"],serialize:"srgb-linear"},Fo=ka;var La={mode:"luv",toMode:{xyz50:qe,rgb:e=>W(qe(e))},fromMode:{xyz50:Ae,rgb:e=>Ae(U(e))},channels:["l","u","v","alpha"],parse:["--luv"],serialize:"--luv",ranges:{l:[0,100],u:[-84.936,175.042],v:[-125.882,87.243]},interpolate:{l:p,u:p,v:p,alpha:{use:p,fixup:y}}},Wo=La;var Ra=({r:e,g:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.cbrt(.412221469470763*e+.5363325372617348*t+.0514459932675022*r),a=Math.cbrt(.2119034958178252*e+.6806995506452344*t+.1073969535369406*r),f=Math.cbrt(.0883024591900564*e+.2817188391361215*t+.6299787016738222*r),i={mode:"oklab",l:.210454268309314*n+.7936177747023054*a-.0040720430116193*f,a:1.9779985324311684*n-2.42859224204858*a+.450593709617411*f,b:.0259040424655478*n+.7827717124575296*a-.8086757549230774*f};return o!==void 0&&(i.alpha=o),i},tt=Ra;var wa=e=>{let t=tt(j(e));return e.r===e.b&&e.b===e.g&&(t.a=t.b=0),t},ne=wa;var _a=({l:e,a:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Math.pow(e+.3963377773761749*t+.2158037573099136*r,3),a=Math.pow(e-.1055613458156586*t-.0638541728258133*r,3),f=Math.pow(e-.0894841775298119*t-1.2914855480194092*r,3),i={mode:"lrgb",r:4.076741636075957*n-3.3077115392580616*a+.2309699031821044*f,g:-1.2684379732850317*n+2.6097573492876887*a-.3413193760026573*f,b:-.0041960761386756*n-.7034186179359362*a+1.7076146940746117*f};return o!==void 0&&(i.alpha=o),i},K=_a;var Ha=e=>Y(K(e)),ae=Ha;function rt(e){let o=1.170873786407767;return .5*(o*e-.206+Math.sqrt((o*e-.206)*(o*e-.206)+4*.03*o*e))}function ve(e){return(e*e+.206*e)/(1.170873786407767*(e+.03))}function Xa(e,t){let r,o,n,a,f,i,l,m;-1.88170328*e-.80936493*t>1?(r=1.19086277,o=1.76576728,n=.59662641,a=.75515197,f=.56771245,i=4.0767416621,l=-3.3077115913,m=.2309699292):1.81444104*e-1.19445276*t>1?(r=.73956515,o=-.45954404,n=.08285427,a=.1254107,f=.14503204,i=-1.2684380046,l=2.6097574011,m=-.3413193965):(r=1.35733652,o=-.00915799,n=-1.1513021,a=-.50559606,f=.00692167,i=-.0041960863,l=-.7034186147,m=1.707614701);let u=r+o*e+n*t+a*e*e+f*e*t,s=.3963377774*e+.2158037573*t,h=-.1055613458*e-.0638541728*t,c=-.0894841775*e-1.291485548*t;{let b=1+u*s,M=1+u*h,g=1+u*c,z=b*b*b,L=M*M*M,H=g*g*g,C=3*s*b*b,$=3*h*M*M,N=3*c*g*g,q=6*s*s*b,me=6*h*h*M,ie=6*c*c*g,B=i*z+l*L+m*H,Q=i*C+l*$+m*N,se=i*q+l*me+m*ie;u=u-B*Q/(Q*Q-.5*B*se)}return u}function Or(e,t){let r=Xa(e,t),o=K({l:1,a:r*e,b:r*t}),n=Math.cbrt(1/Math.max(o.r,o.g,o.b)),a=n*r;return[n,a]}function Sa(e,t,r,o,n,a=null){a||(a=Or(e,t));let f;if((r-n)*a[1]-(a[0]-n)*o<=0)f=a[1]*n/(o*a[0]+a[1]*(n-r));else{f=a[1]*(n-1)/(o*(a[0]-1)+a[1]*(n-r));{let i=r-n,l=o,m=.3963377774*e+.2158037573*t,u=-.1055613458*e-.0638541728*t,s=-.0894841775*e-1.291485548*t,h=i+l*m,c=i+l*u,b=i+l*s;{let M=n*(1-f)+f*r,g=f*o,z=M+g*m,L=M+g*u,H=M+g*s,C=z*z*z,$=L*L*L,N=H*H*H,q=3*h*z*z,me=3*c*L*L,ie=3*b*H*H,B=6*h*h*z,Q=6*c*c*L,se=6*b*b*H,ce=4.0767416621*C-3.3077115913*$+.2309699292*N-1,D=4.0767416621*q-3.3077115913*me+.2309699292*ie,dt=4.0767416621*B-3.3077115913*Q+.2309699292*se,ut=D/(D*D-.5*ce*dt),Ee=-ce*ut,Je=-1.2684380046*C+2.6097574011*$-.3413193965*N-1,ke=-1.2684380046*q+2.6097574011*me-.3413193965*ie,Vt=-1.2684380046*B+2.6097574011*Q-.3413193965*se,mt=ke/(ke*ke-.5*Je*Vt),je=-Je*mt,xo=-.0041960863*C-.7034186147*$+1.707614701*N-1,er=-.0041960863*q-.7034186147*me+1.707614701*ie,zn=-.0041960863*B-.7034186147*Q+1.707614701*se,go=er/(er*er-.5*xo*zn),tr=-xo*go;Ee=ut>=0?Ee:1e6,je=mt>=0?je:1e6,tr=go>=0?tr:1e6,f+=Math.min(Ee,Math.min(je,tr))}}}return f}function ot(e,t,r=null){r||(r=Or(e,t));let o=r[0],n=r[1];return[n/o,n/(1-o)]}function Dt(e,t,r){let o=Or(t,r),n=Sa(t,r,e,1,e,o),a=ot(t,r,o),f=.11516993+1/(7.4477897+4.1590124*r+t*(-2.19557347+1.75198401*r+t*(-2.13704948-10.02301043*r+t*(-4.24894561+5.38770819*r+4.69891013*t)))),i=.11239642+1/(1.6132032-.68124379*r+t*(.40370612+.90148123*r+t*(-.27087943+.6122399*r+t*(.00299215-.45399568*r-.14661872*t)))),l=n/Math.min(e*a[0],(1-e)*a[1]),m=e*f,u=(1-e)*i,s=.9*l*Math.sqrt(Math.sqrt(1/(1/(m*m*m*m)+1/(u*u*u*u))));return m=e*.4,u=(1-e)*.8,[Math.sqrt(1/(1/(m*m)+1/(u*u))),s,n]}function nt(e){let t=e.l!==void 0?e.l:0,r=e.a!==void 0?e.a:0,o=e.b!==void 0?e.b:0,n={mode:"okhsl",l:rt(t)};e.alpha!==void 0&&(n.alpha=e.alpha);let a=Math.sqrt(r*r+o*o);if(!a)return n.s=0,n;let[f,i,l]=Dt(t,r/a,o/a),m;if(ant(ne(e))},toMode:{oklab:at,rgb:e=>ae(at(e))}},Uo=Pa;function ft(e){let t=e.l!==void 0?e.l:0,r=e.a!==void 0?e.a:0,o=e.b!==void 0?e.b:0,n=Math.sqrt(r*r+o*o),a=n?r/n:1,f=n?o/n:1,[i,l]=ot(a,f),m=.5,u=1-m/i,s=l/(n+t*l),h=s*t,c=s*n,b=ve(h),M=c*b/h,g=K({l:b,a:a*M,b:f*M}),z=Math.cbrt(1/Math.max(g.r,g.g,g.b,0));t=t/z,n=n/z*rt(t)/t,t=rt(t);let L={mode:"okhsv",s:n?(m+l)*c/(l*m+l*u*c):0,v:t?t/h:0};return L.s&&(L.h=k(Math.atan2(o,r)*180/Math.PI)),e.alpha!==void 0&&(L.alpha=e.alpha),L}function it(e){let t={mode:"oklab"};e.alpha!==void 0&&(t.alpha=e.alpha);let r=e.h!==void 0?e.h:0,o=e.s!==void 0?e.s:0,n=e.v!==void 0?e.v:0,a=Math.cos(r/180*Math.PI),f=Math.sin(r/180*Math.PI),[i,l]=ot(a,f),m=.5,u=1-m/i,s=1-o*m/(m+l-l*u*o),h=o*l*m/(m+l-l*u*o),c=ve(s),b=h*c/s,M=K({l:c,a:a*b,b:f*b}),g=Math.cbrt(1/Math.max(M.r,M.g,M.b,0)),z=ve(n*s),L=h*z/s;return t.l=z*g,t.a=L*a*g,t.b=L*f*g,t}var Ca={...Ht,mode:"okhsv",channels:["h","s","v","alpha"],parse:["--okhsv"],serialize:"--okhsv",fromMode:{oklab:ft,rgb:e=>ft(ne(e))},toMode:{oklab:it,rgb:e=>ae(it(e))}},Ko=Ca;function $a(e,t){if(!t||t[0]!=="oklab")return;let r={mode:"oklab"},[,o,n,a,f]=t;if(!(o.type===d.Hue||n.type===d.Hue||a.type===d.Hue))return o.type!==d.None&&(r.l=Math.min(Math.max(0,o.type===d.Number?o.value:o.value/100),1)),n.type!==d.None&&(r.a=n.type===d.Number?n.value:n.value*.4/100),a.type!==d.None&&(r.b=a.type===d.Number?a.value:a.value*.4/100),f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var Ar=$a;var Na={...Ie,mode:"oklab",toMode:{lrgb:K,rgb:ae},fromMode:{lrgb:tt,rgb:ne},ranges:{l:[0,1],a:[-.4,.4],b:[-.4,.4]},parse:[Ar],serialize:e=>`oklab(${e.l!==void 0?e.l:"none"} ${e.a!==void 0?e.a:"none"} ${e.b!==void 0?e.b:"none"}${e.alpha<1?` / ${e.alpha}`:""})`},Qo=Na;function Ia(e,t){if(!t||t[0]!=="oklch")return;let r={mode:"oklch"},[,o,n,a,f]=t;if(o.type!==d.None){if(o.type===d.Hue)return;r.l=Math.min(Math.max(0,o.type===d.Number?o.value:o.value/100),1)}if(n.type!==d.None&&(r.c=Math.max(0,n.type===d.Number?n.value:n.value*.4/100)),a.type!==d.None){if(a.type===d.Percentage)return;r.h=a.value}return f.type!==d.None&&(r.alpha=Math.min(1,Math.max(0,f.type===d.Number?f.value:f.value/100))),r}var qr=Ia;var Oa={...Oe,mode:"oklch",toMode:{oklab:e=>J(e,"oklab"),rgb:e=>ae(J(e,"oklab"))},fromMode:{rgb:e=>E(ne(e),"oklch"),oklab:e=>E(e,"oklch")},parse:[qr],serialize:e=>`oklch(${e.l!==void 0?e.l:"none"} ${e.c!==void 0?e.c:"none"} ${e.h!==void 0?e.h:"none"}${e.alpha<1?` / ${e.alpha}`:""})`,ranges:{l:[0,1],c:[0,.4],h:[0,360]}},Vo=Oa;var Aa=e=>{let{r:t,g:r,b:o,alpha:n}=j(e),a={mode:"xyz65",x:.486570948648216*t+.265667693169093*r+.1982172852343625*o,y:.2289745640697487*t+.6917385218365062*r+.079286914093745*o,z:0*t+.0451133818589026*r+1.043944368900976*o};return n!==void 0&&(a.alpha=n),a},Et=Aa;var qa=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Y({r:e*2.4934969119414263-t*.9313836179191242-.402710784450717*r,g:e*-.8294889695615749+t*1.7626640603183465+.0236246858419436*r,b:e*.0358458302437845-t*.0761723892680418+.9568845240076871*r},"p3");return o!==void 0&&(n.alpha=o),n},Jt=qa;var Da={...Z,mode:"p3",parse:["display-p3"],serialize:"display-p3",fromMode:{rgb:e=>Jt(O(e)),xyz65:Jt},toMode:{rgb:e=>A(Et(e)),xyz65:Et}},en=Da;var Dr=e=>{let t=Math.abs(e);return t>=.001953125?Math.sign(e)*Math.pow(t,.5555555555555556):16*e},Ea=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"prophoto",r:Dr(e*1.3457868816471585-t*.2555720873797946-.0511018649755453*r),g:Dr(e*-.5446307051249019+t*1.5082477428451466+.0205274474364214*r),b:Dr(e*0+t*0+1.2119675456389452*r)};return o!==void 0&&(n.alpha=o),n},jt=Ea;var Er=(e=0)=>{let t=Math.abs(e);return t>=.03125?Math.sign(e)*Math.pow(t,1.8):e/16},Ja=e=>{let t=Er(e.r),r=Er(e.g),o=Er(e.b),n={mode:"xyz50",x:.7977666449006423*t+.1351812974005331*r+.0313477341283922*o,y:.2880748288194013*t+.7118352342418731*r+899369387256e-16*o,z:0*t+0*r+.8251046025104602*o};return e.alpha!==void 0&&(n.alpha=e.alpha),n},Yt=Ja;var ja={...Z,mode:"prophoto",parse:["prophoto-rgb"],serialize:"prophoto-rgb",fromMode:{xyz50:jt,rgb:e=>jt(U(e))},toMode:{xyz50:Yt,rgb:e=>W(Yt(e))}},tn=ja;var rn=1.09929682680944,Ya=.018053968510807,Jr=e=>{let t=Math.abs(e);return t>Ya?(Math.sign(e)||1)*(rn*Math.pow(t,.45)-(rn-1)):4.5*e},Ga=({x:e,y:t,z:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"rec2020",r:Jr(e*1.7166511879712683-t*.3556707837763925-.2533662813736599*r),g:Jr(e*-.6666843518324893+t*1.6164812366349395+.0157685458139111*r),b:Jr(e*.0176398574453108-t*.0427706132578085+.9421031212354739*r)};return o!==void 0&&(n.alpha=o),n},Gt=Ga;var on=1.09929682680944,Ba=.018053968510807,jr=(e=0)=>{let t=Math.abs(e);return t{let t=jr(e.r),r=jr(e.g),o=jr(e.b),n={mode:"xyz65",x:.6369580483012911*t+.1446169035862083*r+.1688809751641721*o,y:.262700212011267*t+.6779980715188708*r+.059301716469862*o,z:0*t+.0280726930490874*r+1.0609850577107909*o};return e.alpha!==void 0&&(n.alpha=e.alpha),n},Bt=Za;var Fa={...Z,mode:"rec2020",fromMode:{xyz65:Gt,rgb:e=>Gt(O(e))},toMode:{xyz65:Bt,rgb:e=>A(Bt(e))},parse:["rec2020"],serialize:"rec2020"},nn=Fa;var fe=.0037930732552754493,Zt=Math.cbrt(fe);var Yr=e=>Math.cbrt(e)-Zt,Wa=e=>{let{r:t,g:r,b:o,alpha:n}=j(e),a=Yr(.3*t+.622*r+.078*o+fe),f=Yr(.23*t+.692*r+.078*o+fe),i=Yr(.2434226892454782*t+.2047674442449682*r+.5518098665095535*o+fe),l={mode:"xyb",x:(a-f)/2,y:(a+f)/2,b:i-(a+f)/2};return n!==void 0&&(l.alpha=n),l},Gr=Wa;var Br=e=>Math.pow(e+Zt,3),Ua=({x:e,y:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n=Br(e+t)-fe,a=Br(t-e)-fe,f=Br(r+t)-fe,i=Y({r:11.031566904639861*n-9.866943908131562*a-.16462299650829934*f,g:-3.2541473810744237*n+4.418770377582723*a-.16462299650829934*f,b:-3.6588512867136815*n+2.7129230459360922*a+1.9459282407775895*f});return o!==void 0&&(i.alpha=o),i},Zr=Ua;var Ka={mode:"xyb",channels:["x","y","b","alpha"],parse:["--xyb"],serialize:"--xyb",toMode:{rgb:Zr},fromMode:{rgb:Gr},ranges:{x:[-.0154,.0281],y:[0,.8453],b:[-.2778,.388]},interpolate:{x:p,y:p,b:p,alpha:{use:p,fixup:y}}},an=Ka;var Qa={mode:"xyz50",parse:["xyz-d50"],serialize:"xyz-d50",toMode:{rgb:W,lab:Me},fromMode:{rgb:U,lab:ge},channels:["x","y","z","alpha"],ranges:{x:[0,.964],y:[0,.999],z:[0,.825]},interpolate:{x:p,y:p,z:p,alpha:{use:p,fixup:y}}},fn=Qa;var Va=e=>{let{x:t,y:r,z:o,alpha:n}=e;t===void 0&&(t=0),r===void 0&&(r=0),o===void 0&&(o=0);let a={mode:"xyz50",x:1.0479298208405488*t+.0229467933410191*r-.0501922295431356*o,y:.0296278156881593*t+.990434484573249*r-.0170738250293851*o,z:-.0092430581525912*t+.0150551448965779*r+.7518742899580008*o};return n!==void 0&&(a.alpha=n),a},Fr=Va;var ef=e=>{let{x:t,y:r,z:o,alpha:n}=e;t===void 0&&(t=0),r===void 0&&(r=0),o===void 0&&(o=0);let a={mode:"xyz65",x:.9554734527042182*t-.0230985368742614*r+.0632593086610217*o,y:-.0283697069632081*t+1.0099954580058226*r+.021041398966943*o,z:.0123140016883199*t-.0205076964334779*r+1.3303659366080753*o};return n!==void 0&&(a.alpha=n),a},Wr=ef;var tf={mode:"xyz65",toMode:{rgb:A,xyz50:Fr},fromMode:{rgb:O,xyz50:Wr},ranges:{x:[0,.95],y:[0,1],z:[0,1.088]},channels:["x","y","z","alpha"],parse:["xyz","xyz-d65"],serialize:"xyz-d65",interpolate:{x:p,y:p,z:p,alpha:{use:p,fixup:y}}},ln=tf;var rf=({r:e,g:t,b:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"yiq",y:.29889531*e+.58662247*t+.11448223*r,i:.59597799*e-.2741761*t-.32180189*r,q:.21147017*e-.52261711*t+.31114694*r};return o!==void 0&&(n.alpha=o),n},Ur=rf;var of=({y:e,i:t,q:r,alpha:o})=>{e===void 0&&(e=0),t===void 0&&(t=0),r===void 0&&(r=0);let n={mode:"rgb",r:e+.95608445*t+.6208885*r,g:e-.27137664*t-.6486059*r,b:e-1.10561724*t+1.70250126*r};return o!==void 0&&(n.alpha=o),n},Kr=of;var nf={mode:"yiq",toMode:{rgb:Kr},fromMode:{rgb:Ur},channels:["y","i","q","alpha"],parse:["--yiq"],serialize:"--yiq",ranges:{i:[-.595,.595],q:[-.522,.522]},interpolate:{y:p,i:p,q:p,alpha:{use:p,fixup:y}}},pn=nf;var af=(e,t)=>Math.round(e*(t=Math.pow(10,t)))/t,ff=(e=4)=>t=>typeof t=="number"?af(t,e):t,Qr=ff;var lt=Qr(2),pt=e=>Math.max(0,Math.min(1,e||0)),ye=e=>Math.round(pt(e)*255),Vr=v("rgb"),lf=v("hsl"),eo=e=>{if(e===void 0)return;let t=ye(e.r),r=ye(e.g),o=ye(e.b);return"#"+(1<<24|t<<16|r<<8|o).toString(16).slice(1)},dn=e=>{if(e===void 0)return;let t=ye(e.alpha!==void 0?e.alpha:1);return eo(e)+(256|t).toString(16).slice(1)},un=e=>{if(e===void 0)return;let t=ye(e.r),r=ye(e.g),o=ye(e.b);return e.alpha===void 0||e.alpha===1?`rgb(${t}, ${r}, ${o})`:`rgba(${t}, ${r}, ${o}, ${lt(pt(e.alpha))})`},mn=e=>{if(e===void 0)return;let t=lt(e.h||0),r=lt(pt(e.s)*100)+"%",o=lt(pt(e.l)*100)+"%";return e.alpha===void 0||e.alpha===1?`hsl(${t}, ${r}, ${o})`:`hsla(${t}, ${r}, ${o}, ${lt(pt(e.alpha))})`},pf=e=>{let t=P(e);if(!t)return;let r=R(t.mode);if(!r.serialize||typeof r.serialize=="string"){let o=`color(${r.serialize||`--${t.mode}`} `;return r.channels.forEach((n,a)=>{n!=="alpha"&&(o+=(a?" ":"")+(t[n]!==void 0?t[n]:"none"))}),t.alpha!==void 0&&t.alpha<1&&(o+=` / ${t.alpha}`),o+")"}if(typeof r.serialize=="function")return r.serialize(t)},df=e=>eo(Vr(e)),uf=e=>dn(Vr(e)),mf=e=>un(Vr(e)),sf=e=>mn(lf(e));var cf={normal:(e,t)=>t,multiply:(e,t)=>e*t,screen:(e,t)=>e+t-e*t,"hard-light":(e,t)=>t<.5?e*2*t:2*t*(1-e)-1,overlay:(e,t)=>e<.5?t*2*e:2*e*(1-t)-1,darken:(e,t)=>Math.min(e,t),lighten:(e,t)=>Math.max(e,t),"color-dodge":(e,t)=>e===0?0:t===1?1:Math.min(1,e/(1-t)),"color-burn":(e,t)=>e===1?1:t===0?0:1-Math.min(1,(1-e)/t),"soft-light":(e,t)=>t<.5?e-(1-2*t)*e*(1-e):e+(2*t-1)*((e<.25?((16*e-12)*e+4)*e:Math.sqrt(e))-e),difference:(e,t)=>Math.abs(e-t),exclusion:(e,t)=>e+t-2*e*t},hf=(e,t="normal",r="rgb")=>{let o=typeof t=="function"?t:cf[t],n=v(r),a=R(r).channels;return e.map(i=>{let l=n(i);return l.alpha===void 0&&(l.alpha=1),l}).reduce((i,l)=>{if(i===void 0)return l;let m=l.alpha+i.alpha*(1-l.alpha);return a.reduce((u,s)=>(s!=="alpha"&&(m===0?u[s]=0:(u[s]=l.alpha*(1-i.alpha)*l[s]+l.alpha*i.alpha*o(i[s],l[s])+(1-l.alpha)*i.alpha*i[s],u[s]=Math.max(0,Math.min(1,u[s]/m)))),u),{mode:r,alpha:m})})},bf=hf;var xf=([e,t])=>e+Math.random()*(t-e),gf=e=>Object.keys(e).reduce((t,r)=>{let o=e[r];return t[r]=Array.isArray(o)?o:[o,o],t},{}),Mf=(e="rgb",t={})=>{let r=R(e),o=gf(t);return r.channels.reduce((n,a)=>((o.alpha||a!=="alpha")&&(n[a]=xf(o[a]||r.ranges[a])),n),{mode:e})},vf=Mf;var Te=(e,t="rgb",r=!1)=>{let o=t?R(t).channels:null,n=t?v(t):P;return a=>{let f=n(a);if(!f)return;let i=(o||R(f.mode).channels).reduce((m,u)=>{let s=e(f[u],u,f,t);return s!==void 0&&!isNaN(s)&&(m[u]=s),m},{mode:f.mode});if(!r)return i;let l=P(a);return l&&l.mode!==i.mode?v(l.mode)(i):i}},to=(e,t,r)=>t!=="alpha"?(e||0)*(r.alpha!==void 0?r.alpha:1):e,ro=(e,t,r)=>t!=="alpha"&&r.alpha!==0?(e||0)/(r.alpha!==void 0?r.alpha:1):e,Ft=(e=1,t=0)=>(r,o)=>o!=="alpha"?r*e+t:r,yf=(e=1,t=1,r=0)=>(o,n)=>n!=="alpha"?e*Math.pow(o,t)+r:o;var Tf=e=>{e[0]===void 0&&(e[0]=0),e[e.length-1]===void 0&&(e[e.length-1]=1);let t=1,r,o,n,a;for(;tt=>e<=0?1:e>=1?0:Math.pow(t,Math.log(.5)/Math.log(e)),oo=zf;var Wt=e=>typeof e=="function",ze=e=>e&&typeof e=="object",cn=e=>typeof e=="number",hn=(e,t="rgb",r,o)=>{let n=R(t),a=v(t),f=[],i=[],l={};e.forEach(h=>{Array.isArray(h)?(f.push(a(h[0])),i.push(h[1])):cn(h)||Wt(h)?l[i.length]=h:(f.push(a(h)),i.push(void 0))}),sn(i);let m=n.channels.reduce((h,c)=>{let b;return ze(r)&&ze(r[c])&&r[c].fixup?b=r[c].fixup:ze(n.interpolate[c])&&n.interpolate[c].fixup?b=n.interpolate[c].fixup:b=M=>M,h[c]=b(f.map(M=>M[c])),h},{});if(o){let h=f.map((c,b)=>n.channels.reduce((M,g)=>(M[g]=m[g][b],M),{mode:t}));m=n.channels.reduce((c,b)=>(c[b]=h.map(M=>{let g=o(M[b],b,M,t);return isNaN(g)?void 0:g}),c),{})}let u=n.channels.reduce((h,c)=>{let b;return Wt(r)?b=r:ze(r)&&Wt(r[c])?b=r[c]:ze(r)&&ze(r[c])&&r[c].use?b=r[c].use:Wt(n.interpolate[c])?b=n.interpolate[c]:ze(n.interpolate[c])&&(b=n.interpolate[c].use),h[c]=b(m[c]),h},{}),s=f.length-1;return h=>{if(h=Math.min(Math.max(0,h),1),h<=i[0])return f[0];if(h>i[s])return f[s];let c=0;for(;i[c]{let $=u[C](L);return $!==void 0&&(H[C]=$),H},{mode:t})}},kf=(e,t="rgb",r)=>hn(e,t,r),bn=(e,t)=>(r,o="rgb",n)=>{let a=t?Te(t,o):void 0,f=hn(r,o,n,e);return a?i=>a(f(i)):f},Lf=bn(to,ro);var Ut=(e,t)=>(e+t)%t,xn=(e,t,r,o,n)=>{let a=n*n,f=a*n;return((1-3*n+3*a-f)*e+(4-6*a+3*f)*t+(1+3*n+3*a-3*f)*r+f*o)/6},no=e=>t=>{let r=e.length-1,o=t>=1?r-1:Math.max(0,Math.floor(t*r));return xn(o>0?e[o-1]:2*e[o]-e[o+1],e[o],e[o+1],ot=>{let r=e.length-1,o=Math.floor(t*r);return xn(e[Ut(o-1,e.length)],e[Ut(o,e.length)],e[Ut(o+1,e.length)],e[Ut(o+2,e.length)],(t-o/r)*r)};var gn=e=>{let t,r=e.length-1,o=new Array(r),n=new Array(r),a=new Array(r);for(o[1]=1/4,n[1]=(6*e[1]-e[0])/4,t=2;t0&&(a[r-1]=n[r-1]),t=r-2;t>0;--t)a[t]=n[t]-o[t]*a[t+1];return a},Rf=e=>no(gn(e)),wf=e=>ao(gn(e));var De=Math.sign,fo=Math.min,G=Math.abs,io=e=>{let t=e.length-1,r=[],o=[],n=[];for(let a=0;a0?.5*(e[a+1]-e[a-1])*t:void 0),n.push(a>0?(De(r[a-1])+De(r[a]))*fo(G(r[a-1]),G(r[a]),.5*G(o[a])):void 0);return[r,o,n]},lo=(e,t,r)=>{let o=e.length-1,n=o*o;return a=>{let f;a>=1?f=o-1:f=Math.max(0,Math.floor(a*o));let i=a-f/o,l=i*i,m=l*i;return(t[f]+t[f+1]-2*r[f])*n*m+(3*r[f]-2*t[f]-t[f+1])*o*l+t[f]*i+e[f]}},_f=e=>{if(e.length<3)return p(e);let t=e.length-1,[r,,o]=io(e);return o[0]=r[0],o[t]=r[t-1],lo(e,o,r)},Hf=e=>{if(e.length<3)return p(e);let t=e.length-1,[r,o,n]=io(e);return o[0]=(e[1]*2-e[0]*1.5-e[2]*.5)*t,o[t]=(e[t]*1.5-e[t-1]*2+e[t-2]*.5)*t,n[0]=o[0]*r[0]<=0?0:G(o[0])>2*G(r[0])?2*r[0]:o[0],n[t]=o[t]*r[t-1]<=0?0:G(o[t])>2*G(r[t-1])?2*r[t-1]:o[t],lo(e,n,r)},Xf=e=>{let t=e.length-1,[r,o,n]=io(e);o[0]=.5*(e[1]-e[t])*t,o[t]=.5*(e[0]-e[t-1])*t;let a=(e[0]-e[t])*t,f=a;return n[0]=(De(a)+De(r[0]))*fo(G(a),G(r[0]),.5*G(o[0])),n[t]=(De(r[t-1])+De(f))*fo(G(r[t-1]),G(f),.5*G(o[t])),lo(e,n,r)};var Sf=(e=1)=>e===1?t=>t:t=>Math.pow(t,e),po=Sf;var Pf=(e=2,t=1)=>{let r=po(t);if(e<2)return e<1?[]:[r(.5)];let o=[];for(let n=0;n{let t={mode:e.mode,r:Math.max(0,Math.min(e.r!==void 0?e.r:0,1)),g:Math.max(0,Math.min(e.g!==void 0?e.g:0,1)),b:Math.max(0,Math.min(e.b!==void 0?e.b:0,1))};return e.alpha!==void 0&&(t.alpha=e.alpha),t},yn=e=>vn(Mn(e)),Tn=e=>e!==void 0&&(e.r===void 0||e.r>=0&&e.r<=1)&&(e.g===void 0||e.g>=0&&e.g<=1)&&(e.b===void 0||e.b>=0&&e.b<=1);function uo(e){return Tn(Mn(e))}function Kt(e="rgb"){let{gamut:t}=R(e);if(!t)return o=>!0;let r=v(typeof t=="string"?t:e);return o=>Tn(r(o))}function $f(e){return e=P(e),e===void 0||uo(e)?e:v(e.mode)(yn(e))}function mo(e="rgb"){let{gamut:t}=R(e);if(!t)return a=>P(a);let r=typeof t=="string"?t:e,o=v(r),n=Kt(r);return a=>{let f=P(a);if(!f)return;let i=o(f);if(n(i))return f;let l=vn(i);return f.mode===l.mode?l:v(f.mode)(l)}}function Nf(e,t="lch",r="rgb"){e=P(e);let o=r==="rgb"?uo:Kt(r),n=r==="rgb"?yn:mo(r);if(e===void 0||o(e))return e;let a=v(e.mode);e=v(t)(e);let f={...e,c:0};if(!o(f))return a(n(f));let i=0,l=e.c!==void 0?e.c:0,m=R(t).ranges.c,u=(m[1]-m[0])/Math.pow(2,13),s=f.c;for(;l-i>u;)f.c=i+(l-i)*.5,o(f)?(s=f.c,i=f.c):l=f.c;return a(o(f)?f:{...f,c:s})}function If(e="rgb",t="oklch",r=pe("oklch"),o=.02){let n=v(e),a=R(e);if(!a.gamut)return u=>n(u);let f=Kt(e),i=mo(e),l=v(t),{ranges:m}=R(t);if(!m.l||!m.c)throw new Error("LCH-like space expected");return u=>{if(u=P(u),u===void 0)return;let s={...l(u)};if(s.l===void 0&&(s.l=0),s.c===void 0&&(s.c=0),s.l>=m.l[1]){let g={...a.white,mode:e};return u.alpha!==void 0&&(g.alpha=u.alpha),g}if(s.l<=m.l[0]){let g={...a.black,mode:e};return u.alpha!==void 0&&(g.alpha=u.alpha),g}if(f(s))return n(s);let h=0,c=s.c,b=(m.c[1]-m.c[0])/4e3,M=i(s);for(;c-h>b;)s.c=(h+c)*.5,M=i(s),f(s)||r&&o>0&&r(s,M)<=o?h=s.c:c=s.c;return n(f(s)?s:M)}}var Of=(e,t=pe(),r=o=>o)=>{let o=e.map((n,a)=>({color:r(n),i:a}));return(n,a=1,f=1/0)=>(isFinite(a)&&(a=Math.max(1,Math.min(a,o.length-1))),o.forEach(i=>{i.d=t(n,i.color)}),o.sort((i,l)=>i.d-l.d).slice(0,a).filter(i=>i.de[i.i]))},Af=Of;var so=e=>Math.max(e,0),co=e=>Math.max(Math.min(e,1),0),qf=(e,t,r)=>e===void 0||t===void 0?void 0:e+r*(t-e),Df=e=>{let t=1-co(e);return[.393+.607*t,.769-.769*t,.189-.189*t,0,.349-.349*t,.686+.314*t,.168-.168*t,0,.272-.272*t,.534-.534*t,.131+.869*t,0,0,0,0,1]},Ef=e=>{let t=so(e);return[.213+.787*t,.715-.715*t,.072-.072*t,0,.213-.213*t,.715+.285*t,.072-.072*t,0,.213-.213*t,.715-.715*t,.072+.928*t,0,0,0,0,1]},Jf=e=>{let t=1-co(e);return[.2126+.7874*t,.7152-.7152*t,.0722-.0722*t,0,.2126-.2126*t,.7152+.2848*t,.0722-.0722*t,0,.2126-.2126*t,.7152-.7152*t,.0722+.9278*t,0,0,0,0,1]},jf=e=>{let t=Math.PI*e/180,r=Math.cos(t),o=Math.sin(t);return[.213+r*.787-o*.213,.715-r*.715-o*.715,.072-r*.072+o*.928,0,.213-r*.213+o*.143,.715+r*.285+o*.14,.072-r*.072-o*.283,0,.213-r*.213-o*.787,.715-r*.715+o*.715,.072+r*.928+o*.072,0,0,0,0,1]},Qt=(e,t,r=!1)=>{let o=v(t),n=R(t).channels;return a=>{let f=o(a);if(!f)return;let i={mode:t},l,m=n.length;for(let s=0;s{let r=so(e);return Te(Ft(r),t,!0)},Gf=(e=1,t="rgb")=>{let r=so(e);return Te(Ft(r,(1-r)/2),t,!0)},Bf=(e=1,t="rgb")=>Qt(Df(e),t,!0),Zf=(e=1,t="rgb")=>Qt(Ef(e),t,!0),Ff=(e=1,t="rgb")=>Qt(Jf(e),t,!0),Wf=(e=1,t="rgb")=>{let r=co(e);return Te((o,n)=>n==="alpha"?o:qf(r,1-r,o),t,!0)},Uf=(e=0,t="rgb")=>Qt(jf(e),t,!0);var Kf=v("rgb"),Qf=[[1,0,-0,0,1,0,-0,-0,1],[.856167,.182038,-.038205,.029342,.955115,.015544,-.00288,-.001563,1.004443],[.734766,.334872,-.069637,.05184,.919198,.028963,-.004928,-.004209,1.009137],[.630323,.465641,-.095964,.069181,.890046,.040773,-.006308,-.007724,1.014032],[.539009,.579343,-.118352,.082546,.866121,.051332,-.007136,-.011959,1.019095],[.458064,.679578,-.137642,.092785,.846313,.060902,-.007494,-.016807,1.024301],[.38545,.769005,-.154455,.100526,.829802,.069673,-.007442,-.02219,1.029632],[.319627,.849633,-.169261,.106241,.815969,.07779,-.007025,-.028051,1.035076],[.259411,.923008,-.18242,.110296,.80434,.085364,-.006276,-.034346,1.040622],[.203876,.990338,-.194214,.112975,.794542,.092483,-.005222,-.041043,1.046265],[.152286,1.052583,-.204868,.114503,.786281,.099216,-.003882,-.048116,1.051998]],Vf=[[1,0,-0,0,1,0,-0,-0,1],[.866435,.177704,-.044139,.049567,.939063,.01137,-.003453,.007233,.99622],[.760729,.319078,-.079807,.090568,.889315,.020117,-.006027,.013325,.992702],[.675425,.43385,-.109275,.125303,.847755,.026942,-.00795,.018572,.989378],[.605511,.52856,-.134071,.155318,.812366,.032316,-.009376,.023176,.9862],[.547494,.607765,-.155259,.181692,.781742,.036566,-.01041,.027275,.983136],[.498864,.674741,-.173604,.205199,.754872,.039929,-.011131,.030969,.980162],[.457771,.731899,-.18967,.226409,.731012,.042579,-.011595,.034333,.977261],[.422823,.781057,-.203881,.245752,.709602,.044646,-.011843,.037423,.974421],[.392952,.82361,-.216562,.263559,.69021,.046232,-.01191,.040281,.97163],[.367322,.860646,-.227968,.280085,.672501,.047413,-.01182,.04294,.968881]],ei=[[1,0,-0,0,1,0,-0,-0,1],[.92667,.092514,-.019184,.021191,.964503,.014306,.008437,.054813,.93675],[.89572,.13333,-.02905,.029997,.9454,.024603,.013027,.104707,.882266],[.905871,.127791,-.033662,.026856,.941251,.031893,.01341,.148296,.838294],[.948035,.08949,-.037526,.014364,.946792,.038844,.010853,.193991,.795156],[1.017277,.027029,-.044306,-.006113,.958479,.047634,.006379,.248708,.744913],[1.104996,-.046633,-.058363,-.032137,.971635,.060503,.001336,.317922,.680742],[1.193214,-.109812,-.083402,-.058496,.97941,.079086,-.002346,.403492,.598854],[1.257728,-.139648,-.118081,-.078003,.975409,.102594,-.003316,.501214,.502102],[1.278864,-.125333,-.153531,-.084748,.957674,.127074,-989e-6,.601151,.399838],[1.255528,-.076749,-.178779,-.078411,.930809,.147602,.004733,.691367,.3039]],ho=(e,t)=>{let r=Math.max(0,Math.min(1,t)),o=Math.round(r/.1),n=Math.round(r%.1),a=e[o];if(n>0&&oV(a[l],f[l],n))}return f=>{let i=P(f);if(i===void 0)return;let{r:l,g:m,b:u}=Kf(i),s={mode:"rgb",r:a[0]*l+a[1]*m+a[2]*u,g:a[3]*l+a[4]*m+a[5]*u,b:a[6]*l+a[7]*m+a[8]*u};return i.alpha!==void 0&&(s.alpha=i.alpha),v(i.mode)(s)}},ti=(e=1)=>ho(Qf,e),ri=(e=1)=>ho(Vf,e),oi=(e=1)=>ho(ei,e);var ni=e=>e*e*(3-2*e),ai=e=>.5-Math.sin(Math.asin(1-2*e)/3);var fi=e=>e*e*e*(e*(e*6-15)+10),ii=fi;var li=e=>(1-Math.cos(e*Math.PI))/2,pi=li;function bo(e){let t=v("lrgb")(e);return .2126*t.r+.7152*t.g+.0722*t.b}function di(e,t){let r=bo(e),o=bo(t);return(Math.max(r,o)+.05)/(Math.min(r,o)+.05)}var R5=T(zo),w5=T(Xo),_5=T(Co),H5=T($o),X5=T(No),S5=T(_t),P5=T(Ht),C5=T(Oo),$5=T(qo),N5=T(Eo),I5=T(Jo),O5=T(Ie),A5=T(jo),q5=T(Oe),D5=T(Yo),E5=T(Zo),J5=T(Fo),j5=T(Wo),Y5=T(Uo),G5=T(Ko),B5=T(Qo),Z5=T(Vo),F5=T(en),W5=T(tn),U5=T(nn),K5=T(Z),Q5=T(an),V5=T(fn),e2=T(ln),t2=T(pn);export{R5 as a98,b0 as average,S as averageAngle,Ho as averageNumber,bf as blend,ur as blerp,Nf as clampChroma,mo as clampGamut,$f as clampRgb,rr as colorsNamed,gt as convertA98ToXyz65,gr as convertCubehelixToRgb,we as convertDlchToLab65,kt as convertHsiToRgb,Rt as convertHslToRgb,He as convertHsvToRgb,Xt as convertHwbToRgb,$t as convertItpToXyz65,It as convertJabToJch,Qe as convertJabToRgb,Ue as convertJabToXyz65,Ot as convertJchToJab,_e as convertLab65ToDlch,re as convertLab65ToRgb,Be as convertLab65ToXyz65,E as convertLabToLch,Ve as convertLabToRgb,ge as convertLabToXyz50,J as convertLchToLab,qt as convertLchuvToLuv,tt as convertLrgbToOklab,Y as convertLrgbToRgb,At as convertLuvToLchuv,qe as convertLuvToXyz50,at as convertOkhslToOklab,it as convertOkhsvToOklab,K as convertOklabToLrgb,nt as convertOklabToOkhsl,ft as convertOklabToOkhsv,ae as convertOklabToRgb,Et as convertP3ToXyz65,Yt as convertProphotoToXyz50,Bt as convertRec2020ToXyz65,xr as convertRgbToCubehelix,Lt as convertRgbToHsi,wt as convertRgbToHsl,Xe as convertRgbToHsv,St as convertRgbToHwb,Ke as convertRgbToJab,et as convertRgbToLab,oe as convertRgbToLab65,j as convertRgbToLrgb,ne as convertRgbToOklab,Gr as convertRgbToXyb,U as convertRgbToXyz50,O as convertRgbToXyz65,Ur as convertRgbToYiq,Zr as convertXybToRgb,Me as convertXyz50ToLab,Ae as convertXyz50ToLuv,jt as convertXyz50ToProphoto,W as convertXyz50ToRgb,Wr as convertXyz50ToXyz65,Mt as convertXyz65ToA98,Nt as convertXyz65ToItp,We as convertXyz65ToJab,Ze as convertXyz65ToLab65,Jt as convertXyz65ToP3,Gt as convertXyz65ToRec2020,A as convertXyz65ToRgb,Fr as convertXyz65ToXyz50,Kr as convertYiqToRgb,v as converter,w5 as cubehelix,p0 as differenceCie76,d0 as differenceCie94,u0 as differenceCiede2000,m0 as differenceCmc,pe as differenceEuclidean,te as differenceHueChroma,Mr as differenceHueNaive,ee as differenceHueSaturation,s0 as differenceHyab,h0 as differenceItp,c0 as differenceKotsarenkoRamos,uo as displayable,_5 as dlab,H5 as dlch,po as easingGamma,pi as easingInOutSine,oo as easingMidpoint,ii as easingSmootherstep,ni as easingSmoothstep,ai as easingSmoothstepInverse,Yf as filterBrightness,Gf as filterContrast,ri as filterDeficiencyDeuter,ti as filterDeficiencyProt,oi as filterDeficiencyTrit,Ff as filterGrayscale,Uf as filterHueRotate,Wf as filterInvert,Zf as filterSaturate,Bf as filterSepia,y as fixupAlpha,f0 as fixupHueDecreasing,a0 as fixupHueIncreasing,n0 as fixupHueLonger,X as fixupHueShorter,pf as formatCss,df as formatHex,uf as formatHex8,sf as formatHsl,mf as formatRgb,R as getMode,X5 as hsi,S5 as hsl,P5 as hsv,C5 as hwb,Kt as inGamut,kf as interpolate,bn as interpolateWith,Lf as interpolateWithPremultipliedAlpha,p as interpolatorLinear,mr as interpolatorPiecewise,no as interpolatorSplineBasis,ao as interpolatorSplineBasisClosed,_f as interpolatorSplineMonotone,Hf as interpolatorSplineMonotone2,Xf as interpolatorSplineMonotoneClosed,Rf as interpolatorSplineNatural,wf as interpolatorSplineNaturalClosed,$5 as itp,N5 as jab,I5 as jch,O5 as lab,A5 as lab65,q5 as lch,D5 as lch65,E5 as lchuv,V as lerp,J5 as lrgb,j5 as luv,ro as mapAlphaDivide,to as mapAlphaMultiply,yf as mapTransferGamma,Ft as mapTransferLinear,Te as mapper,zo as modeA98,Xo as modeCubehelix,Co as modeDlab,$o as modeDlch,No as modeHsi,_t as modeHsl,Ht as modeHsv,Oo as modeHwb,qo as modeItp,Eo as modeJab,Jo as modeJch,Ie as modeLab,jo as modeLab65,Oe as modeLch,Yo as modeLch65,Zo as modeLchuv,Fo as modeLrgb,Wo as modeLuv,Uo as modeOkhsl,Ko as modeOkhsv,Qo as modeOklab,Vo as modeOklch,en as modeP3,tn as modeProphoto,nn as modeRec2020,Z as modeRgb,an as modeXyb,fn as modeXyz50,ln as modeXyz65,pn as modeYiq,Af as nearest,Y5 as okhsl,G5 as okhsv,B5 as oklab,Z5 as oklch,F5 as p3,fr as parse,nr as parseHex,Lr as parseHsl,kr as parseHslLegacy,Rr as parseHwb,Nr as parseLab,Ir as parseLch,or as parseNamed,Ar as parseOklab,qr as parseOklch,pr as parseRgb,ar as parseRgbLegacy,dr as parseTransparent,W5 as prophoto,vf as random,U5 as rec2020,In as removeParser,K5 as rgb,Qr as round,Cf as samples,eo as serializeHex,dn as serializeHex8,mn as serializeHsl,un as serializeRgb,If as toGamut,Zn as trilerp,Bn as unlerp,T as useMode,yo as useParser,di as wcagContrast,bo as wcagLuminance,Q5 as xyb,V5 as xyz50,e2 as xyz65,t2 as yiq}; diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 790448fb..00000000 --- a/docs/api.md +++ /dev/null @@ -1,1907 +0,0 @@ ---- -layout: default.njk -title: API Reference -codebase: 'https://github.com/evercoder/culori/blob/main' ---- - -
-API table of contents - -
- -## Colors are plain objects - -Culori does not have a _Color_ class. Instead, it uses plain objects to represent colors: - -```js -/* 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](/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 - -# **parse**(_string_) → _color_ or _undefined_ - - [src/parse.js]({{codebase}}/src/parse.js) - -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`. - -```js -import { parse } from 'culori'; - -/* A named color */ -parse('red'); -// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' } - -/* A hex color */ -parse('#ff0000'); -// ⇒ { r: 1, g: 0, b: 0, mode: 'rgb' } - -/* A HSL color */ -parse('hsl(60 50% 10% / 100%)'); -// ⇒ { h: 60, s: 0.5, l: 0.1, alpha: 1, mode: 'hsl' } - -/* A Lab color */ -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()`](#converter), which accepts strings and color objects and returns objects in a predictable color space. - -# **converter**(_mode = "rgb"_) → _function (color or String)_ - - [src/converter.js]({{codebase}}/src/converter.js) - -Returns a _converter_: a function that can convert any color to the _mode_ color space. - -```js -import { converter } from 'culori'; - -let rgb = converter('rgb'); -let lab = 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 `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. - -## Formatting - -These methods serialize colors to strings, in various formats. - -# **formatHex**(_color_ or _string_) → _string_ - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Returns the hex string for the given 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. - -```js -import { formatHex } from 'culori'; - -formatHex('red'); -// ⇒ "#ff0000" -``` - -# **formatHex8**(_color_ or _string_) → _string_ - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Returns the 8-character hex string for the given 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. - -```js -import { formatHex8 } from 'culori'; - -formatHex8({ mode: 'rgb', r: 1, g: 0, b: 0, alpha: 0.5 }); -// ⇒ "#ff000080" -``` - -# **formatRgb**(_color_ or _string_) → _string_ - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Returns the `rgb(…)` / `rgba(…)` string for the given color. Fully opaque colors will be serialized as `rgb()`, and semi-transparent colors as `rgba()`, in accordance with the [CSSOM standard serialization](https://drafts.csswg.org/cssom/#serialize-a-css-component-value). Like in the case of `formatHex`, the red, green, and blue channels are clamped to the interval `[0, 255]`. - -```js -import { formatRgb } from 'culori'; - -formatRgb('lab(50 0 0 / 25%)'); -// ⇒ "rgba(119, 119, 119, 0.25)" -``` - -# **formatHsl**(_color_ or _string_) → _string_ - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Returns the `hsl(…)` / `hsla(…)` string for the given color. Fully opaque colors will be serialized as `hsl()`, and semi-transparent colors as `hsla()`. All values are rounded to a precision of two digits. The Saturation and Lightness are clamped to the interval `[0%, 100%]`. - -```js -import { formatHsl } from 'culori'; - -formatHsl('lab(50 0 0 / 25%)'); -// ⇒ 'hsla(194.33, 0%, 46.63%, 0.25)' -``` - -# **formatCss**(_color_ or _string_) → _string_ - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Returns a CSS string for the given color, based on the CSS Color Level 4 specification. A few color spaces, such as `hsl` or `lab`, have their own functional representation in CSS. We use that whenever possible; the `hsl` color space is represented as `hsl(h% s l / alpha)`. Predefined color spaces are represented using the `color()` notation with the appropriate identifier for the color space, e.g. `color(display-p3 r g b / alpha)`. All other colors spaces use the `color()` notation with a dashed identifier. For example, `jab` is represented as `color(--jzazbz j a b / alpha)`. - -You can find the exact string produced for each color space under the _Serialized as_ entry on the [Color Spaces](/color-spaces) page. - -Channel values are serialized as-is, with no change in the precision. To avoid compatibility issues, sRGB colors are represented as `color(srgb r g b / alpha)` rather than `rgb(r, g, b, alpha)`. For the latter, use the [`formatRgb()`](#formatRgb) method instead. - -An alpha of exactly `1` is omitted from the representation. - -**Note:** The strings returned by these methods are not widely supported in current browsers and should not be used in CSS as-is. - -```js -import { formatCss } from 'culori'; - -/* - A mode with its own function notation. -*/ -formatCss({ mode: 'hsl', h: 30, s: 1, l: 0.5, alpha: 0.5 }); -// ⇒ 'hsl(30 100% 50% / 0.5)' - -/* - A predefined color space. - */ -formatCss({ mode: 'p3', r: 0.5, s: 0.25, b: 1, alpha: 1 }); -// ⇒ 'color(display-p3 0.5 0.25 1)' - -/* - sRGB colors. - */ -formatCss({ mode: 'rgb', r: 0.5, g: 0.25, b: 1, alpha: 0.25 }); -// ⇒ 'color(srgb 0.5 0.25 1 / 0.25)' - -/* - A custom color space. - */ -formatCss({ mode: 'lrgb', r: 0.5, g: 0.25, b: 1, alpha: 0.25 }); -// ⇒ 'color(--srgb-linear 0.5 0.25 1 / 0.25)' -``` - -## Gamut mapping - -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. - -# **inGamut**(_mode = "rgb"_) → _function (color | string)_ - - [src/clamp.js]({{codebase}}/src/clamp.js) - -Given a color space, returns a function with which to check whether a particular color is within the gamut of that color space. - -This is meant to be used with RGB-based color spaces and their derivates (`hsl`, `hsv`, etc.). If the color space has no gamut limits, the function will always return `true`, regardless of the color passed to it. To find out which color spaces have gamut limits, see the [Color Spaces](/color-spaces/) page. - -```js -import { inGamut } from 'culori'; - -const inRgb = inGamut('rgb'); - -inRgb('red'); -// ⇒ true - -inRgb('color(srgb 1.1 0 0)'); -// ⇒ false -``` - -# **displayable**(_color_ or _string_) → _boolean_ - - [src/clamp.js]({{codebase}}/src/clamp.js) - -Checks whether a particular color fits inside the sRGB gamut. Equivalent to `inGamut('rgb')`. - -```js -import { displayable } from 'culori'; - -displayable('red'); -// ⇒ true - -displayable('color(srgb 1.1 0 0)'); -// ⇒ false -``` - -# **clampRgb**(_color_ or _string_) → _color_ - - [src/clamp.js]({{codebase}}/src/clamp.js) - -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. - -```js -import { clampRgb } from 'culori'; - -// RGB clamping: -clampRgb('lab(50% 100 100)'); -// ⇒ { mode: "lab", l: 54.29…, a: 80.81…, b: 69.88… } -``` - -# **clampGamut**(_mode = 'rgb'_) → _function(color | string)_ - -This function extends the functionality of `clampRgb` to other color spaces. Given a color space, it returns a function with which to obtain colors within the gamut of that color space. - -If the color space has no gamut limits, colors are returned unchanged. To find out which color spaces have gamut limits, see the [Color Spaces](/color-spaces/) page. - -The in-gamut color is always returned in the color space of the original color. - -```js -import { formatCss, clampGamut } from 'culori'; - -const crimson = 'color(display-p3 0.8 0.1 0.3)'; -const toRgb = clampGamut('rgb'); - -formatCss(toRgb(crimson)); -// ⇒ 'color(display-p3 0.801… 0.169… 0.302…)' -``` - - [src/clamp.js]({{codebase}}/src/clamp.js) - -# **clampChroma**(_color_ or _string_, _mode = 'lch'_, _rgbGamut = 'rgb'_) → _color_ - - [src/clamp.js]({{codebase}}/src/clamp.js) - -Obtains a displayable version of the color by converting it to a temporary color space containing a Chroma channel, then looking for the closest 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 displayable color returned by this function will be converted back to the original color space. - -```js -import { clampChroma } from 'culori'; - -clampChroma('lab(50% 100 100)'); -// ⇒ { mode: 'lab', l:50.00…, a: 63.11…, b: 63.11… } -``` - -By default, the color is converted to `lch` to perform the clamping, but any color space that contains a Chroma dimension can be used by sending an explicit `mode` argument. - -Likewise, the destination RGB gamut can be overriden with the corresponding parameter. - -```js -import { clampChroma } from 'culori'; - -clampChroma({ mode: 'oklch', l: 0.5, c: 0.16, h: 180 }, 'oklch'); -// ⇒ { mode: 'oklch', l: 0.5, c: 0.09, h: 180 } -``` - -In general, chroma clamping is more accurate and computationally simpler when performed in the color's original space, where possible. Here's some sample code that uses the color's own `mode` for color spaces containing a Chroma dimension, and `lch` otherwise: - -```js -import { clampChroma } from 'culori'; - -clampChroma(color, color.c !== undefined ? color.mode : 'lch'); -``` - -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](https://en.wikipedia.org/wiki/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](https://github.com/d3/d3-color/issues/33) for details. - -# **toGamut**(_dest = 'rgb'_, _mode = 'oklch'_, _delta = differenceEuclidean('oklch')_, _jnd = 0.02_) → _function (color | string)_ - - [src/clamp.js]({{codebase}}/src/clamp.js) - -Obtain a color that's in the `dest` gamut, by first converting it to the `mode` color space and then finding the largest chroma that's in gamut, similar to `clampChroma()`. - -The color returned is in the `dest` color space. - -```js -import { p3, toGamut } from 'culori'; - -const color = 'lch(80% 150 60)'; - -p3(color); -// ⇒ { mode: "p3", r: 1.229…, g: 0.547…, b: -0.073… } - -const toP3 = toGamut('p3'); -toP3(color); -// ⇒ { mode: "p3", r: 0.999…, g: 0.696…, b: 0.508… } -``` - -To address the shortcomings of `clampChroma`, which can sometimes produce colors more desaturated than necessary, the test used in the binary search is replaced with “is color is roughly in gamut”, by comparing the candidate to the clipped version (obtained with `clampGamut`). The test passes if the colors are not to dissimilar, judged by the `delta` color difference function and an associated `jnd` just-noticeable difference value. - -The default arguments for this function correspond to [the gamut mapping algorithm](https://drafts.csswg.org/css-color/#css-gamut-mapping) defined in the CSS Color Module Level 4 spec, but the algorithm itself is slightly different. - -The “roughly in gamut” aspect of the algorithm can be disabled by passing `null` for the `delta` color difference function: - -```js -import { toGamut } from 'culori'; -const clampToP3 = toGamut('p3', 'oklch', null); -``` - -The algorithm expects an LCH-like color space, containing lightness and chroma components, for the `mode` argument. - -## 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. - -# **interpolate**(_colors_, _mode = "rgb"_, _overrides_) - - [src/interpolate/interpolate.js]({{codebase}}/src/interpolate/interpolate.js) - -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. - -```js -import { interpolate } from 'culori'; - -let grays = 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()`. - -```js -import { interpolate, interpolatorSplineBasis } from 'culori'; - -let my_interpolator = interpolate(['blue', 'red'], 'lch', { - // spline instead of linear interpolation: - h: 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][css-images-4] specification: - -```js -import { interpolate } from '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]({{"/img/evenly-spaced-vs-positions.png" | url }}) - -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][css-images-4]: - -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: - -```js -import { interpolate } from 'culori'; - -const easeIn = t => t * t; -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: - -```js -import { interpolate } from 'culori'; - -const easeIn = t => t * t; - -// this form: -interpolate([easeIn, 'red', 'green', 'blue']); - -// is equivalent to: -interpolate(['red', easeIn, 'green', easeIn, 'blue']); -``` - -The easing function can alternatively be applied the hard way: - -```js -import { interpolate, interpolatorPiecewise, lerp } from 'culori'; - -const easeIn = t => t * t; - -interpolate( - ['red', 'green', 'blue'], - 'rgb', - interpolatorPiecewise((a, b, t) => lerp(a, b, easeIn(t))) -); -``` - -This formula can be helpful if you wanted to apply a different easing function per channel: - -```js -import { interpolate, interpolatorPiecewise, lerp } from 'culori'; -function piecewiseEasing(easingFn) { - return interpolatorPiecewise((a, b, t) => lerp(a, b, easingFn(t))); -} - -interpolate(['red', 'green', 'blue'], 'rgb', { - r: piecewiseEasing(easeIn), - g: piecewiseEasing(easeOut), - b: piecewiseEasing(easeInOut) -}); -``` - -Culori comes with [just a few](#built-in-easing-functions) easing functions, but you can find several online: - -- [some classic easing functions](https://gist.github.com/gre/1650294); -- [eases](https://github.com/mattdesl/eases) by Matt DesLauriers; -- [bezier-easing](https://github.com/gre/bezier-easing) by Gaëtan Renaudeau builds `cubic-bezier` easings as defined in the [CSS Easing Functions Level 1][css-easing-1] spec; -- [d3-scale](https://github.com/d3/d3-scale) lets you set the scale's domain and range to `[0, 1]`. - -### Interpolation hints - -Any number in the _colors_ array will be interpreted as an [interpolation hint](https://drafts.csswg.org/css-images-4/#color-stop-syntax): - -```js -import { interpolate } from 'culori'; - -// interpolation hint: -interpolate(['red', 0.25, 'green']); -``` - -As opposed to how current browsers implement the CSS spec ([see discussion](https://github.com/w3c/csswg-drafts/issues/3931)), interpolation hints _do not_ affect color stop positions in Culori. - -

Built-in easing functions

- -# **easingMidpoint**(_H = 0.5_) - - [src/easing/midpoint.js]({{codebase}}/src/easing/midpoint.js) - -[Proposed here][midpoint], the `midpoint` easing function lets you shift the midpoint of a gradient like in tools such as Adobe Photoshop. You can use it with [`interpolate()`](#interpolate) as an alternative to interpolation hints: - -```js -import { interpolate, easingMidpoint } from 'culori'; -// Explicit midpoint easing: -interpolate(['red', easingMidpoint(0.25), 'blue']); - -// ...is equivalent to: -interpolate(['red', 0.25, 'blue']); -``` - -# **easingSmoothstep** - - [src/easing/smoothstep.js]({{codebase}}/src/easing/smoothstep.js) - -The [Smoothstep][smoothstep] easing function. - -# **easingSmoothstepInverse** - - [src/easing/smoothstep.js]({{codebase}}/src/easing/smoothstep.js) - -The inverse of the [Smoothstep][smoothstep] easing function. - -# **easingSmootherstep** - - [src/easing/smootherstep.js]({{codebase}}/src/easing/smootherstep.js) - -Smootherstep is a variant of the [Smoothstep][smoothstep] easing function. - -# **easingInOutSine** - - [src/easing/inOutSine.js]({{codebase}}/src/easing/inOutSine.js) - -Sinusoidal in-out easing. Can be used to create, for example, a cosine interpolation [as described by Paul Bourke](http://paulbourke.net/miscellaneous/interpolation/): - -```js -import { interpolate, easingInOutSine } from 'culori'; -interpolate([easingInOutSine, 'red', 'green', 'blue']); -``` - -# **easingGamma**(_γ = 1_) → _function(t)_ - - [src/easing/gamma.js]({{codebase}}/src/easing/gamma.js) - -The [gamma](https://en.wikipedia.org/wiki/Gamma_correction) easing. - -```js -import { samples, easingGamma } from 'culori'; -samples(5).map(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. - -# **interpolatorLinear**(_values_) - - [src/interpolate/linear.js]({{codebase}}/src/interpolate/linear.js) - - - -A linear interpolator for values in a channel. - -#### Basis splines - -[Basis splines](https://en.wikipedia.org/wiki/B-spline) (also called _B-splines_) are available in the following variants: - -# **interpolatorSplineBasis**(_values_) - - [src/interpolate/splineBasis.js]({{codebase}}/src/interpolate/splineBasis.js) - - - -A basis spline which uses one-sided finite differences for the slopes at the boundaries. - -# **interpolatorSplineBasisClosed**(_values_) - - [src/interpolate/splineBasis.js]({{codebase}}/src/interpolate/splineBasis.js) - - - -A basis spline which considers the _values_ array to be periodic. - -#### Natural splines - -[Natural interpolating splines](https://en.wikipedia.org/wiki/Spline_interpolation) are related to basis splines, as explained in [this handout](https://www.math.ucla.edu/~baker/149.1.02w/handouts/dd_splines.pdf) by Kirby A. Baker (sections 4 and 5). - -# **interpolatorSplineNatural**(_values_) - - [src/interpolate/splineNatural.js]({{codebase}}/src/interpolate/splineNatural.js) - - - -A natural spline which uses one-sided finite differences for the slopes at the boundaries. - -# **interpolatorSplineNaturalClosed**(_values_) - - [src/interpolate/splineNatural.js]({{codebase}}/src/interpolate/splineNatural.js) - - - -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](https://github.com/d3/d3-shape)): - -> Steffen, M. [_"A simple method for monotonic interpolation in one dimension."_](https://ui.adsabs.harvard.edu/abs/1990A&A...239..443S) in Astronomy and Astrophysics, Vol. 239, p. 443-450 (Nov. 1990), Provided by the SAO/NASA Astrophysics Data System. - -The following variants are available: - -# **interpolatorSplineMonotone**(_values_) - - [src/interpolate/splineMonotone.js]({{codebase}}/src/interpolate/splineMonotone.js) - - - -A monotone spline that uses one-sided finite differences to find the slopes at the boundaries. - -# **interpolatorSplineMonotone2**(_values_) - - [src/interpolate/splineMonotone.js]({{codebase}}/src/interpolate/splineMonotone.js) - - - -A monotone spline for which we derive the slopes at the boundaries by tracing a parabola through the first/last three values. - -# **interpolatorSplineMonotoneClosed**(_values_) - - [src/interpolate/splineMonotone.js]({{codebase}}/src/interpolate/splineMonotone.js) - - - -A monotone spline which considers the _values_ array to be periodic. - -#### Custom piecewise interpolation - -# **interpolatorPiecewise**(_interpolator_) [src/interpolate/piecewise.js]({{codebase}}/src/interpolate/piecewise.js) - -Use a custom piecewise interpolator function in the form `function (a, b, t) => value`: - -```js -import { interpolate, interpolatorPiecewise } from 'culori'; - -let linear = (a, b, t) => (1 - t) * a + t * b; -interpolate(['red', 'green'], 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 [`interpolatorLinear()`](#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](https://github.com/w3c/csswg-drafts/issues/4735). - -# **fixupHueShorter**(_values_) → _Array_ - - [src/fixup/hue.js]({{codebase}}/src/fixup/hue.js) - -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: - -```js -/* --- 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: - -```js -import { interpolate } from 'culori'; - -let hsl_long = 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](https://github.com/w3c/csswg-drafts/issues/4735) mentioned earlier. - -# **fixupHueLonger**(_values_) → _Array_ - - [src/fixup/hue.js]({{codebase}}/src/fixup/hue.js) - -Adjusts the hues so that they are interpolated along the _longest path around the hue circle_. - -# **fixupHueIncreasing**(_values_) → _Array_ - - [src/fixup/hue.js]({{codebase}}/src/fixup/hue.js) - -Adjusts the hues so that every hue is larger than the previous. - -# **fixupHueDecreasing**(_values_) → _Array_ - - [src/fixup/hue.js]({{codebase}}/src/fixup/hue.js) - -Adjusts the hues so that every hue is smaller than the previous. - -#### Alpha fixup - -# **fixupAlpha**(_values_) → _Array_ - - [src/fixup/alpha.js]({{codebase}}/src/fixup/alpha.js) - -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 - -# **samples**(_n = 2_) - - [src/samples.js]({{codebase}}/src/samples.js) - -Returns an array of _n_ equally-spaced samples in the `[0, 1]` range, with `0` and `1` at the ends. - -```js -import { samples } from 'culori'; - -samples(3); -// ⇒ [0, 0.5, 1] - -samples(5); -// ⇒ [0, 0.25, 0.5, 0.75, 1] -``` - -The samples are useful for [`interpolate()`](#interpolate) to generate color scales: - -```js -import { samples, interpolate, formatHex } from 'culori'; - -let grays = interpolate(['#fff', '#000']); -samples(5).map(grays).map(formatHex); -// ⇒ ["#ffffff", "#bfbfbf", "#808080", "#404040", "#000000"] -``` - -As with the [`interpolate()`](#interpolate) method, you can map the samples through an easing function or scale to obtain a different distribution of the samples. - -```js -import { samples } from 'culori'; -import easing from 'bezier-easing'; - -// Bezier easing: -let bezier = easing(0, 0, 1, 0.5); -samples(10).map(bezier); - -// easeInQuad: -samples(10).map(t => t * t); -``` - -### Lerp - -# **lerp**(_a_, _b_, _t_) → _value_ - - [src/interpolate/lerp.js]({{codebase}}/src/interpolate/lerp.js) - -Interpolates between the values `a` and `b` at the point `t ∈ [0, 1]`. - -```js -import { lerp } from 'culori'; -lerp(5, 10, 0.5); -// ⇒ 7.5 -``` - -# **unlerp**(_a_, _b_, _v_) → _value_ - - [src/interpolate/lerp.js]({{codebase}}/src/interpolate/lerp.js) - -Returns the point `t` at which the value `v` is located between the values `a` and `b`. The inverse of `lerp`. - -# **blerp**(_a00_, _a01_, _a10_, _a11_, _tx_, _ty_) → _value_ - - [src/interpolate/lerp.js]({{codebase}}/src/interpolate/lerp.js) - -Perform the [bilinear interpolation](https://en.wikipedia.org/wiki/Bilinear_interpolation) of the four values `a00`, `a01`, `a10`, and `a11` at the point `(tx, ty)`, with `tx, ty ∈ [0, 1]`. This is the extension of `lerp` to two dimensions. - -# **trilerp**(_a000_, _a010_, _a100_, _a110_, _a001_, _a011_, _a101_, _a111_, _tx_, _ty_, _tz_) → _value_ - - [src/interpolate/lerp.js]({{codebase}}/src/interpolate/lerp.js) - -Perform the [trilinear interpolation](https://en.wikipedia.org/wiki/Trilinear_interpolation) of the eight values `a000`, `a010`, `a100`, `a110`, `a001`, `a011`, `a101`, and `a111` at the point `(tx, ty, tz)`, with `tx, ty, tz ∈ [0, 1]`. This is the extension of `lerp` to three dimensions. - -### Mappings - -# **mapper**(_fn_, _mode = "rgb"_) → _function (color | string)_ - - [src/map.js]({{codebase}}/src/map.js) - -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 necessary. - -The _mode_ parameter can be set to `null`, in which case the mapper will iterate through the channels in the color's original color space. - -The _fn_ callback has the following signature: - -**fn**(_value_, _channel_, _color_, _mode_) - -where: - -- `value` is the current value; -- `channel` is the current channel; -- `color` is a reference to the entire color object; -- `mode` is forwarded from the call to `mapper`. - -Here's the implementation of alpha premultiplication: - -```js -import { mapper } from 'culori'; - -const multiplyAlpha = 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, including `alpha`, are included in the mapping. You may want to handle the `alpha` channel differently in the callback function, like in the example above. - -Returning `undefined` or `NaN` from the callback function will omit that channel from the resulting color object. - -#### Built-in mappings - -# **mapAlphaMultiply** - - [src/map.js]({{codebase}}/src/map.js) - -Multiplies the color's alpha value into all its other channels: - -```js -import { mapper, mapAlphaMultiply } from 'culori'; - -let multiplyAlpha = mapper(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.). - -# **mapAlphaDivide** - - [src/map.js]({{codebase}}/src/map.js) - -Divides a color's other channels by its alpha value. It's the opposite of `mapAlphaMultiply`, and is used in interpolation with alpha premultiplication: - -```js -import { mapper, mapAlphaMultiply, mapAlphaDivide } from 'culori'; - -let multiplyAlpha = mapper(mapAlphaMultiply, 'rgb'); -let divideAlpha = mapper(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.). - -# **mapTransferLinear**(_slope = 1_, _intercept = 0_) - - [src/map.js]({{codebase}}/src/map.js) - -# **mapTransferGamma**(_amplitude = 1_, _exponent = 1_, _offset = 0_) - - [src/map.js]({{codebase}}/src/map.js) - -#### Interpolating with mappings - -# **interpolateWith**(_premap_, _postmap_) - - [src/interpolate.js]({{codebase}}/src/interpolate.js) - -Adds a _pre-mapping_ and a _post-mapping_ to an interpolation, to enable things like alpha premultiplication: - -```js -import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori'; - -let interpolateWithAlphaPremult = interpolateWith( - mapAlphaMultiply, - mapAlphaDivide -); - -interpolateWithAlphaPremult(['red', 'transparent', 'blue'])(0.25); -``` - -To chain more than one mapping: - -```js -import { interpolateWith, mapAlphaMultiply, mapAlphaDivide } from 'culori'; - -const mapChromaMultiply = (v, ch, c, mode) => { - // ... -}; - -const mapChromaDivide = (v, ch, c, mode) => { - // ... -}; - -let interpolateWithAlphaChromaPremult = interpolateWith( - (...args) => mapChromaMultiply(mapAlphaMultiply(...args)), - (...args) => mapAlphaDivide(mapChromaDivide(...args)) -); - -interpolateWithAlphaChromaPremult(['red', 'transparent', 'blue'])(0.25); -``` - -# **interpolateWithPremultipliedAlpha**(_colors_, _mode = "rgb"_, _overrides_) - - [src/interpolate/interpolate.js]({{codebase}}/src/interpolate/interpolate.js) - -Takes the same arguments as [`interpolate()`](#interpolate), but applies [alpha premultiplication](https://drafts.csswg.org/css-images-4/#premultiplied). - -```js -import { interpolate, interpolateWithPremultipliedAlpha } from 'culori'; -let colors = ['red', 'transparent', 'blue']; - -// alpha ignored for the R/G/B channels: -interpolate(colors, 'rgb'); - -// alpha premultiplied into the R/G/B channels: -interpolateWithPremultipliedAlpha(colors, 'rgb'); -``` - -## Color Difference - -These methods are concerned to finding the [distance between two colors](https://en.wikipedia.org/wiki/Color_difference) 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 - -# **differenceEuclidean**(_mode = 'rgb'_, _weights = [1, 1, 1, 0]_) - - [src/difference.js]({{codebase}}/src/difference.js) - -Returns a [Euclidean distance](https://en.wikipedia.org/wiki/Color_difference#Euclidean) 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](#differenceKotsarenkoRamos) or [ΔEITP](#differenceItp). - -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: - -# **differenceHueChroma**(_colorA_, _colorB_) - - [src/difference.js]({{codebase}}/src/difference.js) - -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`. - -# **differenceHueSaturation**(_colorA_, _colorB_) - - [src/difference.js]({{codebase}}/src/difference.js) - -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. - -# **differenceHueNaive**(_colorA_, _colorB_) - - [src/difference.js]({{codebase}}/src/difference.js) - -For remaining color spaces (HWB), we consider hues numbers, but apply a _shortest path around the hue circle_ (analogous to [`fixupHueShorter`](#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. - -# **differenceCie76**() - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [CIE76][cie76] ΔE\*ab color difference between the colors _a_ and _b_. The function is identical to [`differenceEuclidean('lab65')`](#differenceEuclidean). - -# **differenceCie94**(_kL = 1_, _K1 = 0.045_, _K2 = 0.015_) - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [CIE94][cie94] ΔE\*94 color difference between the colors _a_ and _b_. - -# **differenceCiede2000**(_Kl = 1_, _Kc = 1_, _Kh = 1_) - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [CIEDE2000][ciede2000] ΔE\*00 color difference between the colors _a_ and _b_ as implemented by [G. Sharma](http://www2.ece.rochester.edu/~gsharma/ciede2000/). - -# **differenceCmc**() - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [CMC l:c (1984)][cmc] Δ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 [`nearest()`](#nearest). - -# **differenceHyab**() - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the HyAB color difference between the colors _a_ and _b_, as proposed in: - -> Abasi S, Amani Tehran M, Fairchild MD. _Distance metrics for very large color differences._ Color Res Appl. 2019; 1–16. https://doi.org/10.1002/col.22451 ([PDF](http://markfairchild.org/PDFs/PAP40.pdf)) - -The HyAB formula combines the Euclidean and [city block](https://en.wikipedia.org/wiki/Taxicab_geometry) distance and has been experimentally shown to work better for large color differences than CIEDE2000, while still holding up well for smaller color differences, making it a _"good candidate formula for image processing and computer vision applications"_. - -### Other difference formulas - -# **differenceKotsarenkoRamos**() - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [Kotsarenko/Ramos][kotsarekno-ramos] color difference between the colors _a_ and _b_. This is a weighted Euclidean distance in the `yiq` color space. - -# **differenceItp**() - - [src/difference.js]({{codebase}}/src/difference.js) - -Computes the [ΔEITP][eitp-difference] color difference metric between the colors _a_ and _b_. This is a weighted Euclidean distance in the `itp` color space. The weights are chosen such that a difference of 1 corresponds to one Just Noticeable Difference (JND). - -### Nearest color(s) - -# **nearest**(_colors_, _metric = differenceEuclidean()_, _accessor = identity_) → _function(color, n = 1, τ = Infinity)_ - - [src/nearest.js]({{codebase}}/src/nearest.js) - -Takes a _colors_ array and a _metric_ color difference formula, and returns a function with which you can find _n_ colors nearest to _color_, with a maximum distance of _τ_. Use _n = Infinity_ to get all colors in the array with a maximum distance of _τ_. - -```js -/* - Example: get three CSS named colors closest to any color - */ -import { colorsNamed, nearest, differenceCiede2000 } from 'culori'; - -let colors = Object.keys(colorsNamed); -let nearestNamedColors = nearest(colors, differenceCiede2000()); - -nearestNamedColors('lch(50% 70 60)', 3); -// => ["chocolate", "sienna", "peru"] -``` - -By default, _colors_ needs to be an array of color values. If your array contains something other than a simple color value, you can provide the `accessor` argument to point to the color value associated with each item in the array. - -The example below shows a common data structure for a color palette: an object whose keys are the names and whose values are their associated color representations. - -```js -import { nearest, differenceEuclidean } from 'culori'; - -/* - Example: get the closest color from a palette - */ -let palette = { - Burgundy: '#914e72', - Blue: '#0078bf', - Green: '#00a95c', - 'Medium Blue': '#3255a4', - 'Bright Red': '#f15060' -}; - -let names = Object.keys(palette); - -let nearestColors = nearest( - names, - differenceEuclidean(), - name => palette[name] -); - -nearestColors('red', 1); -// => ["Bright Red"] -``` - -## Blending - -Culori makes available the separable blend modes defined in the W3C [Compositing and Blending Level 2](https://drafts.fxtf.org/compositing-2/) specification. - -# **blend**(_colors_, _type = 'normal'_, _mode = 'rgb'_) → _color_ - - [src/blend.js]({{codebase}}/src/blend.js) - -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: - -```js -import { blend } from '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: - -```js -import { blend } from '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 - -# **average**(_colors_, _mode = 'rgb'_, _overrides_) - - [src/average.js]({{codebase}}/src/average.js) - -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. - -```js -import { average } from 'culori'; - -average(['salmon', 'tomato'], 'lab'); -// ⇒ { 'mode': 'lab', l: 65.41…, a: 53.00…, b: 39.01… } -``` - -# **averageNumber**(_values_) - - [src/average.js]({{codebase}}/src/average.js) - -The arithmetic mean of values in the _values_ array. - -# **averageAngle**(_values_) - - [src/average.js]({{codebase}}/src/average.js) - -The function used by default to average hue values in all built-in color spaces, using the formula for [the mean of circular quantities](https://en.wikipedia.org/wiki/Mean_of_circular_quantities). - -## Random colors - -# **random**(_mode = 'rgb'_, _constraints = {}_) - - [src/random.js]({{codebase}}/src/random.js) - -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: - -```js -import { random } from '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: - -```js -import { random } from '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`: - -```js -import { random } from 'culori'; - -random('lrgb'); -// ⇒ { mode: 'lrgb', r: 0.74…, g: 0.15…, b: 0.34… } - -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]({{codebase}}/tools/ranges.js) 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 [`displayable()`](#displayable), and pass the color through a [`clamp*`](#clampRgb) function to obtain a displayable version. - -## WCAG utilities - -A couple of utility functions based on the [Web Content Acccessibility Guidelines 2.0 specification](https://www.w3.org/TR/WCAG20/). - -# **wcagLuminance**(_color_) - - [src/wcag.js]({{codebase}}/src/wcag.js) - -Computes the [relative luminance](https://www.w3.org/TR/WCAG20/#relativeluminancedef) of a color. - -```js -import { wcagLuminance } from 'culori'; - -wcagLuminance('red'); -// ⇒ 0.2126 -``` - -# **wcagContrast**(_colorA_, _colorB_) - - [src/wcag.js]({{codebase}}/src/wcag.js) - -Computes the [contrast ratio](https://www.w3.org/TR/WCAG20/#contrast-ratiodef) between two colors. - -```js -import { wcagContrast } from '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](https://drafts.fxtf.org/filter-effects-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. - -# **filterBrightness**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`brightness()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/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. - -```js -import { filterBrightness } from 'culori'; - -let brighten = filterBrightness(2, 'lrgb'); -brighten('salmon'); -// ⇒ { mode: 'rgb', r: 1.32…, g: 0.68…, b: 0.61… } -``` - -# **filterContrast**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`contrast()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/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. - -# **filterSepia**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`sepia()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/sepia) filter. An _amount_ of `0` leaves the color unchanged, and `1` applies the sepia effect fully. - -# **filterGrayscale**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`grayscale()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/grayscale) filter. An _amount_ of `0` leaves the color unchanged, and `1` makes the color fully achromatic. - -# **filterSaturate**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`saturate()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/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. - -# **filterInvert**(_amount = 1_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`invert()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/invert) filter. An _amount_ of `0` leaves the color unchanged, and `1` makes the color fully inverted. - -# **filterHueRotate**(_degrees = 0_, _mode = 'rgb'_) - - [src/filter.js]({{codebase}}/src/filter.js) - -The [`hue-rotate()`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/hue-rotate) filter. - -```js -import { samples, interpolate, filterSepia, formatHex } from 'culori'; - -samples(5) - .map(interpolate(['red', 'green', 'blue'])) - .map(filterSepia(0.5)) - .map(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](#filterHueRotate) 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). - -# **filterDeficiencyProt**(_severity = 1_) → _function (color)_ - - [src/deficiency.js]({{codebase}}/src/deficiency.js) - -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. - -# **filterDeficiencyDeuter**(_severity = 1_) → _function (color)_ - - [src/deficiency.js]({{codebase}}/src/deficiency.js) - -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. - -# **filterDeficiencyTrit**(_severity = 1_) → _function (color)_ - - [src/deficiency.js]({{codebase}}/src/deficiency.js) - -Simulate 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: - -```js -import { interpolate, filterDeficiencyProt, formatHex } from 'culori'; -culori - .samples(5) - .map(interpolate(['red', 'green', 'blue'])) - .map(filterDeficiencyProt(0.5)) - .map(formatHex); - -// ⇒ ["#751800", "#664200", "#576c00", "#1a3e82", "#0010ff"]; -``` - -Based on the work of Machado, Oliveira and Fernandes (2009), using [precomputed matrices](https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html) provided by the authors. References thanks to the [`colorspace` package for R](http://colorspace.r-forge.r-project.org/reference/simulate_cvd.html). - -> 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](https://doi.ieeecomputersociety.org/10.1109/TVCG.2009.113). - -## Miscellaneous - -# **colorsNamed** - - [src/colors/named.js]({{codebase}}/src/colors/named.js) - -An object whose keys are all the CSS named colors. - -# **round**(_n = 8_) - - [src/round.js]({{codebase}}/src/round.js) - -Returns a _rounder_: a function with which to round numbers to at most _n_ digits of precision. - -```js -import { round } from 'culori'; - -let approx = round(4); -approx(0.38393993); -// ⇒ 0.3839 -``` - -

- # - Available color spaces -

- -The default import (`culori`) comes with all the color spaces pre-registered into the library. For convenience, you can also import directly `mode` as a shortcut to [`converter(mode)`](#converter). For example, instead of [`converter('hsl')`](#converter), you can import `hsl`: - -```js -// Instead of this: -import { converter } from 'culori'; -const hsl = converter('hsl'); - -// You can do this: -import { hsl } from 'culori'; -``` - -On the other hand, when importing the [tree-shaken version](/guides/tree-shaking) (`culori/fn`), color spaces need to be registered manually with [`useMode()`](#useMode) based on their __definition object__: - -```js -import { useMode, modeHsl } from 'culori/fn'; -const hsl = useMode(modeHsl); -``` - -The table below summarizes two pieces of information: - -* What the `mode` is for a specific color space built into Culori, which is also the name of the shortcut to the converter for that color space. -* What the definition object for a color space is called. - -The available color spaces are discussed into more detail on the [Color Spaces](/color-spaces) page. - -Mode | Color space | Definition object ----- | ----------- | ----------------- -`a98` | A98 RGB color space, compatible with Adobe RGB (1998) | `modeA98` -`cubehelix` | Cubehelix color space | `modeCubehelix` -`dlab` | DIN99o Lab color space | `modeDlab` -`dlch` | DIN99o LCh color space | `modeDlch` -`hsi` | HSI color space | `modeHsi` -`hsl` | HSL color space | `modeHsl` -`hsv` | HSV color space | `modeHsv` -`hwb` | HWB color space | `modeHwb` -`itp` | ICtCp color space | `modeItp` -`jab` | Jzazbz color space | `modeJab` -`jch` | Jzazbz in cylindrical form | `modeJch` -`lab` | CIELAB color space (D50 Illuminant) | `modeLab` -`lab65` | CIELAB color space (D65 Illuminant) | `modeLab65` -`lch` | CIELCh color space (D50 Illuminant) | `modeLch` -`lch65` | CIELCh color space (D65 Illuminant) | `modeLch65` -`lchuv` | CIELCHuv color space (D50 Illuminant) | `modeLchuv` -`lrgb` | Linear-light sRGB color space | `modeLrgb` -`luv` | CIELUV color space (D50 Illuminant) | `modeLuv` -`oklab` | Oklab color space | `modeOklab` -`oklch` | Oklab color space, cylindrical form | `modeOklch` -`okhsl` | Okhsl color space | `modeOkhsl` -`okhsv` | Okhsv color space | `modeOkhsv` -`p3` | Display P3 color space | `modeP3` -`prophoto` | ProPhoto RGB color space | `modeProphoto` -`rec2020` | Rec. 2020 RGB color space | `modeRec2020` -`rgb` | sRGB color space | `modeRgb` -`xyb` | XYB color space | `modeXyb` -`xyz50` | XYZ with D50 white-point | `modeXyz50` -`xyz65` | XYZ with D65 white-point | `modeXyz65` -`yiq` | YIQ color space | `modeYiq` - -## Extending culori - -# **useMode**(_definition_) → _function_. - - [src/modes.js]({{codebase}}/src/modes.js) - -Defines a new color space based on its _definition_. See [Color mode definition](#color-mode-def) for the expected object shape. - -Returns a converter function for the newly defined mode. - -```js -import { useMode } from 'culori'; - -const hsl = useMode({ - mode: 'hsl' - // ... -}); - -hsl('hsl(50 100% 100% / 100)'); -``` - -# **getMode**(_mode_) - - [src/modes.js]({{codebase}}/src/modes.js) - -Returns the definition object for the _mode_ color space. - -

- # - Color mode definition -

- -The properties a definition needs are the following: - -#### `mode` (_string_) - -The string identifier for the color space. - -#### `channels` (_array_) - -A list of channels for the color space. - -#### `toMode` (_object_) - -A set of functions to convert from the color space we're defining to other color spaces. At least `rgb` needs to be included; in case a specific conversion pair between two color spaces is missing, RGB is used as the "buffer" for the conversion. - -#### `fromMode` (_object_) - -The opposite of `toMode`. A set of function to convert from various color spaces to the color space we're defining. At least `rgb` needs to be included. - -#### `ranges` (_object_, optional) - -The reference ranges for values in specific channels; if left unspecified, defaults to `[0, 1]`. - -#### `parse` (_array_, optional) - -Any parsers for the color space that can transform strings into colors. These can be either functions, or strings — the latter is used as the color space's identifier to parse the `color()` CSS syntax. - -#### `serialize` (_function_ or _string_, optional) - -Defines how to serialize the color space to a CSS string with [`formatCss()`](#formatCss). - -If you pass in a function, it receives a color object as its only argument, and should return a string that can be used in CSS. If you pass in a string, it's used as a color profile identifier, and the color is serialized using the `color()` CSS syntax. When omitted altogether, the default color profile identifier is `--${mode}`. - -#### `interpolate` - -The default interpolations for the color space, one for each channel. Each interpolation is defined by its interpolator (the `use` key) and its fixup function (the `fixup` key). When defined as a function, a channel interpolation is meant to define its interpolator, with the fixup being a no-op. - -#### `difference` - -The default Euclidean distance method for each channel in the color space; mostly used for the `h` channel in cylindrical color spaces. - -#### `average` - -The default average function for each channel in the color space; when left unspecified, defaults to [`averageNumber`](#averageNumber). - -All built-in color spaces follow these conventions in regards to the `channels` array follows: - -- there are four channels in the color space; -- the fourth channel is always `alpha`. - -This makes sure [`differenceEuclidean()`](#differenceEuclidean) works as expected, but there may be more hidden assumptions in the codebase. - -Here's a sample definition for the HSL color space: - -```js -{ - mode: 'hsl', - fromMode: { - rgb: convertRgbToHsl - }, - toMode: { - rgb: convertHslToRgb - }, - channels: ['h', 's', 'l', 'alpha'], - ranges: { - h: [0, 360] - }, - parse: [parseHsl], - serialize: serializeHsl, - interpolate: { - h: { - use: interpolatorLinear, - fixup: fixupHueShorter - }, - s: interpolatorLinear, - l: interpolatorLinear, - alpha: { - use: interpolatorLinear, - fixup: fixupAlpha - } - }, - difference: { - h: differenceHueSaturation - }, - average: { - h: averageAngle - } -}; -``` - -# **useParser**(_parser_, _mode_) - - [src/modes.js]({{codebase}}/src/modes.js) - -Register a new parser. The parser can be: - -* a function, in which case the _mode_ argument is not necessary. -* a string representing the identifier to match in the `color()` syntax, in which case the _mode_ argument is required. - -```js -import { useParser } from 'culori'; - -// Register custom parser -useParser(function(str) => { - let color = {}; - // parse the string - return color; -}); - -// Register `color(--oklab)` syntax -useParser('--oklab', 'oklab'); -``` - -# **removeParser**(_parser_) - - [src/modes.js]({{codebase}}/src/modes.js) - -Remove a previously registered parser function or string, including parsers registered by default. - -```js -import { parse, parseNamed, removeParser } from 'culori'; - -parse('tomato'); -// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 } - -removeParser(parseNamed); -parse('tomato'); -// ⇒ undefined -``` - -

- # - Low-level API -

- -

- # - Parsing functions -

- -# __parseHex__(_string_) → _color_ - -Parses hex strings of 3, 4, 6, or 8 characters, with or without the `#` prefix, and returns `rgb` color objects. - -```js -import { parseHex } from 'culori'; - -parseHex('#abc'); -parseHex('#abcd'); -parseHex('#abcdef'); -parseHex('#abcdef12'); -``` - -# __parseHsl__(_string_) → _color_ - -Parses `hsl(…)` strings in the modern format and returns `hsl` color objects. - -# __parseHslLegacy__(_string_) → _color_ - -Parses `hsl(…)` / `hsla(…)` strings in the legacy (comma-separated) format and returns `hsl` color objects. - -# __parseHwb__(_string_) → _color_ - -Parses `hwb(…)` strings and returns `hwb` color objects. - -# __parseLab__(_string_) → _color_ - -Parses `lab(…)` strings and returns `lab` color objects. - -# __parseLch__(_string_) → _color_ - -Parses `lch(…)` strings and returns `lch` color objects. - -# __parseNamed__(_string_) → _color_ - -Parses named CSS colors (eg. `tomato`) and returns `rgb` color objects. - -# __parseOklab__(_string_) → _color_ - -Parses `oklab(…)` strings and returns `oklab` color objects. - -# __parseOklch__(_string_) → _color_ - -Parses `oklch(…)` strings and returns `oklch` color objects. - -# __parseRgb__(_string_) → _color_ - -Parses `rgb(…)` strings in the modern syntax and returns `rgb` color objects. - -# __parseRgbLegacy__(_string_) → _color_ - -Parses `rgb(…)` / `rgba(…)` strings in the legacy (comma-separated) syntax and returns `rgb` color objects. - -#__parseTransparent__(_string_) → _color_ - -Parses the `transparent` string and returns a transparent black `rgb` color object. - -### Serialization functions - -#__serializeHex__(_color_) - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Serialize a `rgb` _color_ to a 6-character hex code. See [`formatHex()`](#formatHex) for details. - -#__serializeHex8__(_color_) - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Serialize a `rgb` _color_ to a 8-character hex code. See [`formatHex8()`](#formatHex8) for details. - -#__serializeHsl__(_color_) - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Serialize a `hsl` _color_ to a `hsl(…)` string. See [`formatHsl()`](#formatHsl) for details. - -#__serializeRgb__(_color_) - - [src/formatter.js]({{codebase}}/src/formatter.js) - -Serialize a `rgb` _color_ to a `rgb(…)` string. See [`formatRgb()`](#formatRgb) for details. - -### Conversion functions - -Function | Conversion --------- | ---------- -__convertA98ToXyz65__(_color_) → _color_ | `a98` → `xyz65` -__convertCubehelixToRgb__(_color_) → _color_ | `cubehelix` → `rgb` -__convertDlchToLab65__(_color_) → _color_ | `dlch` → `lab65` -__convertHsiToRgb__(_color_) → _color_ | `hsi` → `rgb` -__convertHslToRgb__(_color_) → _color_ | `hsl` → `rgb` -__convertHsvToRgb__(_color_) → _color_ | `hsv` → `rgb` -__convertHwbToRgb__(_color_) → _color_ | `hwb` → `rgb` -__convertJabToJch__(_color_) → _color_ | `jab` → `jch` -__convertJabToRgb__(_color_) → _color_ | `jab` → `rgb` -__convertJabToXyz65__(_color_) → _color_ | `jab` → `xyz65` -__convertJchToJab__(_color_) → _color_ | `jch` → `jab` -__convertLab65ToDlch__(_color_) → _color_ | `lab65` → `dlch` -__convertLab65ToRgb__(_color_) → _color_ | `lab65` → `rgb` -__convertLab65ToXyz65__(_color_) → _color_ | `lab65` → `xyz65` -__convertLabToLch__(_color_) → _color_ | `lab` → `lch` -__convertLabToRgb__(_color_) → _color_ | `lab` → `rgb` -__convertLabToXyz50__(_color_) → _color_ | `lab` → `xyz50` -__convertLchToLab__(_color_) → _color_ | `lch` → `lab` -__convertLchuvToLuv__(_color_) → _color_ | `lchuv` → `luv` -__convertLrgbToOklab__(_color_) → _color_ | `lrgb` → `oklab` -__convertLrgbToRgb__(_color_) → _color_ | `lrgb` → `rgb` -__convertLuvToLchuv__(_color_) → _color_ | `luv` → `lchuv` -__convertLuvToXyz50__(_color_) → _color_ | `luv` → `xyz50` -__convertOkhslToOklab__(_color_) → _color_ | `okhsl` → `oklab` -__convertOkhsvToOklab__(_color_) → _color_ | `okhsv` → `oklab` -__convertOklabToLrgb__(_color_) → _color_ | `oklab` → `lrgb` -__convertOklabToOkhsl__(_color_) → _color_ | `oklab` → `okhsl` -__convertOklabToOkhsv__(_color_) → _color_ | `oklab` → `okhsv` -__convertOklabToRgb__(_color_) → _color_ | `oklab` → `rgb` -__convertP3ToXyz65__(_color_) → _color_ | `p3` → `xyz65` -__convertProphotoToXyz50__(_color_) → _color_ | `prophoto` → `xyz50` -__convertRec2020ToXyz65__(_color_) → _color_ | `rec2020` → `xyz65` -__convertRgbToCubehelix__(_color_) → _color_ | `rgb` → `cubehelix` -__convertRgbToHsi__(_color_) → _color_ | `rgb` → `hsi` -__convertRgbToHsl__(_color_) → _color_ | `rgb` → `hsl` -__convertRgbToHsv__(_color_) → _color_ | `rgb` → `hsv` -__convertRgbToHwb__(_color_) → _color_ | `rgb` → `hwb` -__convertRgbToJab__(_color_) → _color_ | `rgb` → `jab` -__convertRgbToLab65__(_color_) → _color_ | `rgb` → `lab65` -__convertRgbToLab__(_color_) → _color_ | `rgb` → `lab` -__convertRgbToLrgb__(_color_) → _color_ | `rgb` → `lrgb` -__convertRgbToOklab__(_color_) → _color_ | `rgb` → `oklab` -__convertRgbToXyb__(_color_) → _color_ | `rgb` → `xyb` -__convertRgbToXyz50__(_color_) → _color_ | `rgb` → `xyz50` -__convertRgbToXyz65__(_color_) → _color_ | `rgb` → `xyz65` -__convertRgbToYiq__(_color_) → _color_ | `rgb` → `yiq` -__convertXybToRgb__(_color_) → _color_ | `xyb` → `rgb` -__convertXyz50ToLab__(_color_) → _color_ | `xyz50` → `lab` -__convertXyz50ToLuv__(_color_) → _color_ | `xyz50` → `luv` -__convertXyz50ToProphoto__(_color_) → _color_ | `xyz50` → `prophoto` -__convertXyz50ToRgb__(_color_) → _color_ | `xyz50` → `rgb` -__convertXyz50ToXyz65__(_color_) → _color_ | `xyz50` → `xyz65` -__convertXyz65ToA98__(_color_) → _color_ | `xyz65` → `a98` -__convertXyz65ToJab__(_color_) → _color_ | `xyz65` → `jab` -__convertXyz65ToLab65__(_color_) → _color_ | `xyz65` → `lab65` -__convertXyz65ToP3__(_color_) → _color_ | `xyz65` → `p3` -__convertXyz65ToRec2020__(_color_) → _color_ | `xyz65` → `rec2020` -__convertXyz65ToRgb__(_color_) → _color_ | `xyz65` → `rgb` -__convertXyz65ToXyz50__(_color_) → _color_ | `xyz65` → `xyz50` -__convertYiqToRgb__(_color_) → _color_ | `yiq` → `rgb` - - -[css-images-4]: https://drafts.csswg.org/css-images-4/#color-stop-syntax -[css-easing-1]: http://drafts.csswg.org/css-easing-1 -[smoothstep]: https://en.wikipedia.org/wiki/Smoothstep -[cie76]: https://en.wikipedia.org/wiki/Color_difference#CIE76 -[cie94]: https://en.wikipedia.org/wiki/Color_difference#CIE94 -[ciede2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000 -[cmc]: https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_(1984) -[kotsarekno-ramos]: http://www.progmat.uaem.mx:8080/artVol2Num2/Articulo3Vol2Num2.pdf -[din99ode]: https://de.wikipedia.org/wiki/DIN99-Farbraum#Farbabstandsformel -[midpoint]: https://github.com/w3c/csswg-drafts/issues/3935 -[eitp-difference]: https://www.itu.int/rec/R-REC-BT.2124/en diff --git a/docs/colophon.md b/docs/colophon.md deleted file mode 100644 index 7a86356a..00000000 --- a/docs/colophon.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: default.njk -title: Colophon ---- - -Culori is developed by [Dan Burzo](http://danburzo.ro) with the help of many collaborators, and released under the MIT license. - -The library has been inspired by Mike Bostock's [D3.js](https://github.com/d3) and Gregor Aisch's [chroma.js](https://github.com/gka/chroma.js): they have popularized color science in the browser and provided a blueprint for a color manipulation API. I have learned a tremendous amount by reading through the documentation the source code while developing Culori. D3, in particular, has been a treasure trove of ideas and pointers to academic references. - -I pronounce the name of the library as [[kuːlori]](http://ipa-reader.xyz/?text=ku%CB%90lori). Culori is the Romanian word for ‘colors’. The logo is typeset in [Hatch](https://pstypelab.com/hatchfont) by Mark Caneso. - -The code is formatted with [Prettier](https://prettier.io), upkept with [ESLint](https://eslint.org/), tested with [tape](https://github.com/ljharb/tape), and bundled with [esbuild](https://esbuild.github.io/). This website is statically generated using [Eleventy](https://11ty.dev) and published to GitHub Pages. diff --git a/docs/color-spaces.md b/docs/color-spaces.md deleted file mode 100644 index 83ba783a..00000000 --- a/docs/color-spaces.md +++ /dev/null @@ -1,482 +0,0 @@ ---- -layout: default.njk -title: Color Spaces ---- - -This is an overview of the color spaces built into Culori, listing their channels and expected ranges. - -
-A note on terminology - -A [color model](https://en.wikipedia.org/wiki/Color_model) is a way to describe colors along certain dimensions. RGB, for example, is a color model: color is expressed as a combination of red, green and blue. - -A color model, along with a precise description of how colors in the model are to be interpreted, makes a color _space_. sRGB, Display P3 and ProPhoto RGB are all color spaces that employ the RGB model: they describe colors as combination of red, green, and blue primaries; however, they have different notions of how these red, green, and blue primary colors look. - -In some color spaces, such as CIELAB or CIELCh, some channels don't have fixed ranges. For these channels, approximate ranges are obtained by converting all sRGB colors defined by `r, g, b ∈ ℕ ⋂ [0, 255]` to that specific color space. Whenever that's the case, the range is marked with the approximation symbol `≈`. - -In addition to the channels listed below, all color spaces also take an optional `alpha` channel with the range `[0, 1]`. - -
- -## Built-in color spaces - -### The RGB model - -The [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) describes colors as mixtures of red, green, and blue primaries. Culori implements several RGB color spaces, all sharing these channels: - -| Channel | Range | Description | -| ------- | -------- | ------------- | -| `r` | `[0, 1]` | Red channel | -| `g` | `[0, 1]` | Green channel | -| `b` | `[0, 1]` | Blue channel | - -#### `rgb` - -The [sRGB color space](https://en.wikipedia.org/wiki/SRGB), which most people refer to when talking about RGB colors. - -Serialized as `color(srgb r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `lrgb` - -The linear-light form of the sRGB color space. - -Serialized as `color(srgb-linear r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `a98` - -The A98 RGB color space, compatible with the [Adobe RGB (1998) color space](https://en.wikipedia.org/wiki/Adobe_RGB_color_space). - -Serialized as `color(a98-rgb r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `p3` - -The [Display P3 color space](https://en.wikipedia.org/wiki/DCI-P3#Display_P3). - -Serialized as `color(display-p3 r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `prophoto` - -The [ProPhoto RGB color space](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space). - -Serialized as `color(prophoto-rgb r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `rec2020` - -The [Rec. 2020 color space](https://en.wikipedia.org/wiki/Rec._2020). - -Serialized as `color(rec2020 r g b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -### The HSL/HSV/HSI family - -[HSL, HSV, and HSI](https://en.wikipedia.org/wiki/HSL_and_HSV) are alternative representations of the RGB color model, created in an attempt to provide a more intuitive way to specify colors. - -The _hue_ is identical across all color models in this family; however, the _saturaton_ is computed differently in each. The saturation in HSL is **not interchangeable** with the saturation from HSV, nor HSI. Achromatic colors (shades of gray) will have an `undefined` hue. - -As color spaces, they relate to the [`sRGB` color space](#rgb). - -#### `hsl` - -The HSL color space. - -| Channel | Range | Description | -| ------- | ---------- | ----------------- | -| `h` | `[0, 360)` | Hue | -| `s` | `[0, 1]` | Saturation in HSL | -| `l` | `[0, 1]` | Lightness | - -Serialized as `hsl(h s% l%)`. A missing hue is serialized as `0`, with the `none` keyword for any other missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `hsv` - -The HSV color space. - -| Channel | Range | Description | -| ------- | ---------- | ----------------- | -| `h` | `[0, 360)` | Hue | -| `s` | `[0, 1]` | Saturation in HSV | -| `v` | `[0, 1]` | Value | - -Serialized as `color(--hsv h s v)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -#### `hsi` - -The HSI color space. - -| Channel | Range | Description | -| ------- | ---------- | ----------------- | -| `h` | `[0, 360)` | Hue | -| `s` | `[0, 1]` | Saturation in HSI | -| `i` | `[0, 1]` | Intensity | - -Serialized as `color(--hsi h s i)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -### HWB - -[The HWB color model](https://en.wikipedia.org/wiki/HWB_color_model) was developed by Alvy Ray Smith, who also created the HSV color model. It's meant to be more intuitive for humans to use and faster to compute. - -| Channel | Range | Description | -| ------- | ---------- | ----------- | -| `h` | `[0, 360)` | Hue | -| `w` | `[0, 1]` | Whiteness | -| `b` | `[0, 1]` | Blackness | - -Serialized as `hwb(h w% b%)`. - -Serialized as `hwb(h w% b%)`. A missing hue is serialized as `0`, with the `none` keyword for any other missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Has gamut limits. - -> Smith, Alvy Ray (1996) — ["HWB — A More Intuitive Hue-Based Color Model"](http://alvyray.com/Papers/CG/HWB_JGTv208.pdf), Journal of Graphics, GPU and Game tools. - -### CIELAB - -The [CIELAB color space](https://en.wikipedia.org/wiki/CIELAB_color_space), also known as CIE 1976 L\*a\*b\*, in Cartesian (Lab) and cylindrical (LCh) forms. - -#### `lab` - -The CIELAB color space using the [D50 standard illuminant](https://en.wikipedia.org/wiki/Standard_illuminant) as the reference white, following the [CSS Color Module Level 4 specification](https://drafts.csswg.org/css-color/#lab-colors). - -| Channel | CSS Reference Range | Description | -| ------- | --------------------- | --------------------- | -| `l` | `[0, 100]` | Lightness | -| `a` | `[-125, 125]` | Green–red component | -| `b` | `[-125, 125]` | Blue–yellow component | - -Serialized as `lab(l a b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `lch` - -The CIELCh color space using the D50 standard illuminant. - -| Channel | CSS Reference Range | Description | -| ------- | --------------- | ----------- | -| `l` | `[0, 100]` | Lightness | -| `c` | `[0, 150]` | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `lch(l c h)`. A missing hue is serialized as `0`, with the `none` keyword for any other missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `lab65` - -CIELAB relative to the D65 standard illuminant. - -| Channel | Range | Description | -| ------- | -------------------- | --------------------- | -| `l` | `[0, 100]` | Lightness | -| `a` | `[-125, 125]` | Green–red component | -| `b` | `[-125, 125]` | Blue–yellow component | - -Serialized as `color(--lab-d65 l a b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `lch65` - -CIELCh relative to the D65 standard illuminant. - -| Channel | Range | Description | -| ------- | --------------- | ----------- | -| `l` | `[0, 100]` | Lightness | -| `c` | `[0, 150]` | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `color(--lch-d65 l c h)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### CIELUV - -The [CIELUV color space](https://en.wikipedia.org/wiki/CIELUV) in Cartesian (Luv) and cylindrical (LCh) forms, using the D50 standard illuminant. - -CIELuv has an effective Euclidean color difference function: - -```js -let deltaE_uv = culori.colorDifferenceEuclidean('luv'); -``` - -#### `luv` - -| Channel | Range | Description | -| ------- | --------------------- | --------------------- | -| `l` | `[0, 100]` | Lightness | -| `u` | `[-84.936, 175.042]`≈ | Green–red component | -| `v` | `[-125.882, 87.243]`≈ | Blue–yellow component | - -Serialized as `color(--luv l u v)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `lchuv` - -| Channel | Range | Description | -| ------- | --------------- | ----------- | -| `l` | `[0, 100]` | Lightness | -| `c` | `[0, 176.956]`≈ | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `color(--lchuv l c h)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### DIN99 Lab / LCh - -The [DIN99](https://de.wikipedia.org/wiki/DIN99-Farbraum) color space "squishes" the CIELAB D65 color space to obtain an [effective color difference](#culoriDifferenceDin99o) metric that can be expressed as a simple Euclidean distance. The latest iteration of the the standard, DIN99o, is available in Cartesian (`dlab`) and polar (`dlch`) form. - -> [_Industrial Color Physics_](https://search.worldcat.org/title/Industrial-color-physics/oclc/663097553), Georg A. Klein, Springer (2010) -> [DIN 6176](https://www.beuth.de/en/standard/din-6176/292836682), _Colorimetric determination of colour differences of object colours according to the DIN99o formula_ - -#### `dlab` - -The DIN99o color space in Cartesian form. - -| Channel | Range | Description | -| ------- | -------------------- | ----------- | -| `l` | `[0, 100]` | Lightness | -| `a` | `[-40.09, 45.501]`≈ | -| `b` | `[-40.469, 44.344]`≈ | - -Serialized as `color(--din99o-lab l a b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `dlch` - -The DIN99o color space in cylindrical form. - -| Channel | Range | Description | -| ------- | -------------- | ----------- | -| `l` | `[0, 100]` | Lightness | -| `c` | `[0, 51.484]`≈ | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `color(--din99o-lch l c h)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### Oklab, Oklch, Okhsl, Okhsv - -The [Oklab color space](https://bottosson.github.io/posts/oklab/), in Cartesian (Lab) and cylindrical (LCh) forms. It uses the D65 standard illuminant. - -See also: [Okhsl and Okhsv, two new color spaces for color picking](https://bottosson.github.io/posts/colorpicker/). - -#### `oklab` - -The Oklab color space in Cartesian form. - -| Channel | CSS Reference Range | Description | -| ------- | ------------------ | --------------------- | -| `l` | `[0, 1]` | Lightness | -| `a` | `[-0.4, 0.4]` | Green–red component | -| `b` | `[-0.4, 0.4]` | Blue–yellow component | - -Serialized as `oklab(l a b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `oklch` - -The Oklab color space in cylindrical form. - -| Channel | Range | Description | -| ------- | ------------- | ----------- | -| `l` | `[0, 1]` | Lightness | -| `c` | `[0, 0.4]` | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `oklch(l c h)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### `okhsl` - -| Channel | Range | Description | -| ------- | ---------- | ----------------- | -| `h` | `[0, 360)` | Hue | -| `s` | `[0, 1]` | Saturation (Okhsl) | -| `l` | `[0, 1]` | Lightness | - -Serialized as `color(--okhsl h s l)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### `okhsv` - -| Channel | Range | Description | -| ------- | ---------- | ----------------- | -| `h` | `[0, 360)` | Hue | -| `s` | `[0, 1]` | Saturation (Okhsv) | -| `v` | `[0, 1]` | Value | - -Serialized as `color(--okhsv h s v)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### Further reading - -* [An interactive review of Oklab](https://raphlinus.github.io/color/2021/01/18/oklab-critique.html) by Raph Levien -* [Notes on Oklab](https://github.com/svgeesus/svgeesus.github.io/blob/master/Color/OKLab-notes.md) and [Better than Lab? Gamut reduction CIE Lab & OKLab](https://www.w3.org/Graphics/Color/Workshop/slides/talk/lilley) by Chris Lilley -* [[css-color] Add OKLab, OKLCH (#6642)](https://github.com/w3c/csswg-drafts/issues/6642) - -### Jzazbz - -The Jzazbz color space, as defined by: - -> Muhammad Safdar, Guihua Cui, Youn Jin Kim, and Ming Ronnier Luo, [_"Perceptually uniform color space for image signals including high dynamic range and wide gamut"_](https://doi.org/10.1364/OE.25.015131), Opt. Express 25, 15131-15151 (2017) - -#### `jab` - -The Jzazbz color space in Cartesian form. - -| Channel | Range | Description | -| ------- | ------------------ | --------------------- | -| `j` | `[0, 0.222]`≈ | Lightness | -| `a` | `[-0.109, 0.129]`≈ | Green–red component | -| `b` | `[-0.185, 0.134]`≈ | Blue–yellow component | - -Serialized as `color(--jzazbz j a b)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `jch` - -The Jzazbz color space in cylindrical form. - -| Channel | Range | Description | -| ------- | ------------- | ----------- | -| `j` | `[0, 0.222]`≈ | Lightness | -| `c` | `[0, 0.190]`≈ | Chroma | -| `h` | `[0, 360)` | Hue | - -Serialized as `color(--jzczhz j c h)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### YIQ (`yiq`) - -[YIQ](https://en.wikipedia.org/wiki/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.595, 0.595]`≈ | In-phase (orange-blue axis) | -| `q` | `[-0.522, 0.522]`≈ | Quadrature (green-purple axis) | - -Serialized as `color(--yiq y i q)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -The conversion matrices between the sRGB and YIQ color spaces are taken from: - -> Yuriy Kotsarenko, Fernando Ramos, [_Measuring perceived color difference using YIQ NTSC transmission color space in mobile applications_](http://www.progmat.uaem.mx:8080/artVol2Num2/Articulo3Vol2Num2.pdf), Programación Matemática y Software (2010), Vol. 2, No 2. - -### CIE XYZ - -The [CIE XYZ color space](https://en.wikipedia.org/wiki/CIE_1931_color_space), also known as the CIE 1931 color space. - -#### `xyz50` - -The CIE XYZ color space in respect to the D50 standard illuminant. - -| Channel | Range | Description | -| ------- | ------------- | ----------- | -| `x` | `[0, 0.964]`≈ | ? | -| `y` | `[0, 0.999]`≈ | ? | -| `z` | `[0, 0.825]`≈ | ? | - -Serialized as `color(xyz-d50 x y z)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -#### `xyz65` - -The CIE XYZ color space in respect to the D65 standard illuminant. - -| Channel | Range | Description | -| ------- | ------------- | ----------- | -| `x` | `[0, 0.950]`≈ | ? | -| `y` | `[0, 1]`≈ | ? | -| `z` | `[0, 1.088]`≈ | ? | - -Serialized as `color(xyz-d65 x y z)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -### XYB - -The XYB color model is part of [the JPEG XL Image Coding System](https://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf), as an LMS-based colour model inspired by the human visual system, facilitating perceptually uniform quantization. It uses a gamma of 3 for computationally efficient decoding. - -#### `xyb` - -The default XYB color space, defined in relationship to sRGB. - -It has the default _Chroma from Luma_ adjustment applied (effectively Y is subtracted from B) so that colors with `{ x: 0, b: 0 }` coordinates are achromatic. - -| Channel | Range | Description | -| ------- | ------------- | ----------- | -| `x` | `[-0.0154, 0.0281]`≈ | Cyan-red component | -| `y` | `[0, 0.8453]`≈ | Luma | -| `b` | `[ -0.2778, 0.3880 ]`≈ | Blue-yellow component | - -Does not have gamut limits. - -### ICtCp - -ICtCp (or ITP) color space developed by [Dolby Laboratories](https://professional.dolby.com/siteassets/pdfs/ictcp_dolbywhitepaper_v071.pdf), as defined in [ITU-R Recommendation BT.2100](https://www.itu.int/rec/R-REC-BT.2100). It is included in the CSS [HDR Color Module Level 1](https://drafts.csswg.org/css-color-hdr/#ICtCp) specification. - -#### `itp` - -| Channel | Range | Description | -|---------|-------------------|-----------------------| -| `i` | `[0, 0.581]`≈ | Intensity | -| `t` | `[-0.282, 0.278]`≈ | Blue-yellow component (“tritanopia”) | -| `p` | `[-0.162, 0.279]`≈ | Green–red component (“protanopia”) | - -Serialized as `color(--ictcp i t p)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. - -The `itp` color space is the basis of the [ΔEITP color difference](/api/#differenceItp) metric defined in _Objective metric for the assessment of the potential visibility of colour differences in television_ ([Rec. ITU-R BT.2124](https://www.itu.int/rec/R-REC-BT.2124/en)). - -### Cubehelix - -[The Cubehelix color scheme](https://www.mrao.cam.ac.uk/~dag/CUBEHELIX/) was described by Dave Green in this paper: - -> Green, D. A., 2011, [_"A colour scheme for the display of astronomical intensity images"_](http://astron-soc.in/bulletin/11June/289392011.pdf), Bulletin of the Astronomical Society of India, 39, 289. ([2011BASI...39..289G](https://ui.adsabs.harvard.edu/#abs/2011BASI...39..289G) at [ADS](https://ui.adsabs.harvard.edu/)) - -It was expanded into a cylindrical color space by [Mike Bostock](https://en.wikipedia.org/wiki/Mike_Bostock) and [Jason Davies](https://www.jasondavies.com/) in [D3](https://github.com/d3/d3-color). - -#### `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.614]` | Saturation (Called _hue_ in op. cit.) | -| `l` | `[0, 1]` | Lightness | - -Serialized as `color(--cubehelix h s l)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`. - -Does not have gamut limits. diff --git a/docs/getting-started.md b/docs/getting-started.md deleted file mode 100644 index 95b35687..00000000 --- a/docs/getting-started.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: default.njk -title: "Getting Started with Culori" ---- - -## Install Culori from npm - -Culori is distributed [on npm](https://npmjs.com/package/culori) in a variety of formats. Install it with: - -```bash -npm install culori -``` - -Then start importing functions from [the API](/api/): - -```js -import { rgb } from 'culori'; - -rgb('tomato'); -// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 } -``` - -For code that runs in browsers, you may want to streamline the bundle to only include the parts of Culori you're using. See [Optimize bundle size with tree-shaking](/guides/tree-shaking/) for guidance on switching your imports to use `'culori/fn'` instead of `'culori'` once you're done prototyping. - -## Fetch Culori from a CDN - -You can use Culori from your favorite Content Delivery Network to create quick prototypes in HTML. Here are a few popular choices: - -CDN | URL ---- | --- -[unpkg](https://unpkg.com/) | `https://unpkg.com/{{pkg.name}}` -[jsDelivr](https://www.jsdelivr.com/) | `https://cdn.jsdelivr.net/npm/{{pkg.name}}` -[skypack](https://www.skypack.dev/) | `https://cdn.skypack.dev/{{pkg.name}}` - -Use it as an ES module: - -```html - -``` - -...or using a traditional ` - -``` - -## Use Culori online - -### In your browser's console - -The library is available on this website as the global variable `culori`, so can try the API in your browser's console as you read through the examples. - -### Observable - -If you prefer to see the results visually, [Observable](https://beta.observablehq.com) is a great place to tinker with the library. - -Add this cell and you're good to go: - -```js -culori = import('culori@{{pkg.version}}'); -``` - -It's often useful to pin the library to a specific version, to make sure your old notebooks don't break if the API changes in a new major version of the library. - -## Use Culori in Deno - -The library is published to [deno.land/x/culori](https://deno.land/x/culori) for usage in [Deno](https://deno.land/): - -```js -import { rgb } from 'https://deno.land/x/culori@v{{pkg.version}}/index.js'; - -rgb('tomato'); -``` diff --git a/docs/guides/guides.json b/docs/guides/guides.json deleted file mode 100644 index 4f41e337..00000000 --- a/docs/guides/guides.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "layout": "default.njk" -} diff --git a/docs/guides/index.md b/docs/guides/index.md deleted file mode 100644 index e7ab2032..00000000 --- a/docs/guides/index.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: 'Guides' ---- - -## Working with Culori - -### [Optimize bundle size with tree-shaking](./tree-shaking/) - -Once you're done prototyping and you're ready to ship an optimized bundle, switch your imports over to the fully tree-shakeable version. - -### [Migration guide](./migration/) - -This guide documents the breaking changes in each new major release of the library and how to address them in your code. - -## Elsewhere - -[Coloring With Code — A Programmatic Approach To Design](https://tympanus.net/codrops/2021/12/07/coloring-with-code-a-programmatic-approach-to-design/), a tutorial by George Francis. diff --git a/docs/guides/migration.md b/docs/guides/migration.md deleted file mode 100644 index 2adcd79c..00000000 --- a/docs/guides/migration.md +++ /dev/null @@ -1,179 +0,0 @@ ---- -title: 'Migration guide' ---- - -## From v2 to v3 - -### Custom identifiers removed from the `color()` syntax - -For better alignment with the CSS Color Level 4 specification, support for the following formats has changed: - -* `color(--oklab)` removed in favor of `oklab()` -* `color(--oklch)` removed in favor of `oklch()` -* `color(--srgb-linear)` removed in favor of `color(srgb-linear)` -* `color(--xyz-d50)` removed in favor of `color(xyz-d50)` -* `color(--xyz-d65)` removed in favor of `color(xyz)` and `color(xyz-d65)` - -The custom identifiers removed in this release can be re-registered with [`useParser()`](/api/#useParser), using string arguments: - -```js -import { useParser } from 'culori'; - -useParser('--oklab', 'oklab'); -useParser('--oklch', 'oklch'); -useParser('--srgb-linear', 'lrgb'); -useParser('--xyz-d50', 'xyz50'); -useParser('--xyz-d65', 'xyz65'); -``` - -### Parsing modern vs. legacy syntaxes for `rgb()` and `hsl()` - -The parsing of the [modern syntax](https://w3c.github.io/csswg-drafts/css-color-4/#color-syntax-modern) (space-separated) and the [legacy syntax](https://w3c.github.io/csswg-drafts/css-color-4/#color-syntax-legacy) (comma-separated) for the `rgb` and `hsl` color spaces has been split to separate functions: - -* `parseRgb()` now parses just the modern `rgb()` / `rgba()` syntax -* `parseHsl()` now parses just the modern `hsl()` / `hsla()` syntax -* `parseRgbLegacy()` has been added to the API to parse the legacy `rgb()` / `rgba()` syntax -* `parseHslLegacy()` has been added to the API to parse the legacy `hsl()` / `hsla()` syntax - -## From v1 to v2 - -* all color components in all CSS color syntaxes now accept the `none` keyword. ([w3c/csswg-drafts#6107](https://github.com/w3c/csswg-drafts/issues/6107)) - -### XYZ D50 vs. D65 - -The CSS Color Module Level 4 specification has been updated to include separate predefined color profiles for the XYZ color space for the two most common white points: `xyz-d50` for the D50 illuminant, and `xyz-d65` for the D65 illuminant. The `xyz` predefined color profile is now an alias for the D65-relative XYZ color space. ([w3c/csswg-drafts#6722](https://github.com/w3c/csswg-drafts/issues/6722)). - -In Culori, the D50-relative XYZ color space has been changed to use `mode: 'xyz50'` instead of `mode: 'xyz'`. __The latter mode is no longer supported.__ If you store color objects in your application, they will have to have their `mode` property updated accordingly: - -```js -import { formatHex } from 'culori'; - -let color = getFavoriteColor(); - -if (color.mode === 'xyz') { - color.mode = 'xyz50'; -} - -formatHex(color); -``` - -When parsing strings, CSS colors in the `color(xyz x y z)` format will be parsed as `mode: 'xyz65'` instead of `mode: 'xyz'`: - -```js -import { parse } from 'culori'; - -// In culori@1.x: -parse('color(xyz 0.5 0.25 1)'); -// ⇒ { mode: 'xyz', x: 0.5, y: 0.25, z: 1 } - -// In culori@2.0: -parse('color(xyz 0.5 0.25 1)'); -// ⇒ { mode: 'xyz65', x: 0.5, y: 0.25, z: 1 } -``` - -When serializing with the [`formatCss()`](/api#formatCss) method, the `xyz50` and `xyz65` modes now use the predefined identifiers `xyz-d50` and `xyz-d65`, as opposed to the custom identifiers `--xyz-d50` and `--xyx-d65` from 1.x: - -```js -import { formatCss } from 'culori'; - -// In culori@1.x: -formatCss({ mode: 'xyz65', x: 0.5, y: 0.25, z: 1 }); -// ⇒ color(--xyz-d65 0.5 0.25 1) - -// In culori@2.0: -formatCss({ mode: 'xyz65', x: 0.5, y: 0.25, z: 1 }); -// ⇒ color(xyz-d65 0.5 0.25 1) -``` - -The following exports have been renamed in [all applicable entry points](/guides/tree-shaking): - -* modeXyzmodeXyz50 -* xyzxyz50 -* convertLabToXyzconvertLabToXyz50 -* convertLuvToXyzconvertLuvToXyz50 -* convertProphotoToXyzconvertProphotoToXyz50 -* convertRgbToXyzconvertRgbToXyz50 -* convertXyz65ToXyzconvertXyz65ToXyz50 -* convertXyzToLabconvertXyz50ToLab -* convertXyzToLuvconvertXyz50ToLuv -* convertXyzToProphotoconvertXyz50ToProphoto -* convertXyzToRgbconvertXyz50ToRgb -* convertXyzToXyz65convertXyz50ToXyz65 - -### `` for L no longer valid in `lab()` and `lch()` - -CSS defines the `lab` syntax as `lab( )`, for example: - -```css -lab(75% 40 30) -``` - -...and the `lch` syntax as `lch( )`: - -```css -lch(75% 50 15deg) -``` - -Culori used to support `` as the L channel, so these colors used to be also valid: - -```css -lab(75 40 30) -lch(75 50 15deg) -``` - -In version 2.0, we drop this quirk and bring Culori in line with the specification. - - -## From v0.x to v1 - -### Using the library - -Starting with version 1.0, Culori will be published to npm as an ES Module (ESM) package, with `type: module` and a set of `exports` in its `package.json` file. - -If you're using Node.js 10, you should be able to continue using Culori as before. - -If you're on Node.js 12 or later, and you've switched to native ES modules (by adding `type: module` to your `package.json`), you need to use `import`: - -```js -import * as culori from 'culori'; -``` - -If you're on Node.js 12 or later, and you're still using CommonJS modules, there's a separate export that can be used with `require()` calls: - -```js -const culori = require('culori/require'); -``` - -### Removed methods - -The `clamp(method)` function has been removed. Use `clampRgb()` and `clampChroma()` instead. - -The `formatter(method)` function has been removed. Use `formatHex()`, `formatRgb()` etc. instead. - -The following undocumented interpolation functions have been removed (note the `interpolate` prefix): - -- `interpolateLinear()` -- `interpolateCosine()` -- `interpolateSplineBasis()` -- `interpolateSplineNatural()` -- `interpolateSplineMonotone()` - -The `differenceDino99()` color difference function has been removed. Use `differenceEuclidean('dlab')` instead. - -The methods `deficiencyProt`, `deficiencyDeuter`, and `deficiencyTrit` have been removed. Use `filterDeficiencyProt`, `filterDeficiencyDeuter`, and `filterDeficiencyTrit` instead. - -### Renamed methods - -The following methods have been renamed: - -- `defineMode()` → `useMode()`; -- `getModeDefinition()` → `getMode()`. - -### Color space definitions - -A few properties on the [color space definition object](/api#color-space-def) have been changed for better clarity. - -- `input` → `fromMode` -- `output` → `toMode` -- `parsers` → `parse` -- `serialize` as string now only includes the color profile identifier, without the `color(` prefix, eg. from `serialize: "color(--hsv "` to `serialize: "--hsv"`. diff --git a/docs/guides/tree-shaking.md b/docs/guides/tree-shaking.md deleted file mode 100644 index e58bc5dc..00000000 --- a/docs/guides/tree-shaking.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: 'Optimize bundle size with tree-shaking' ---- - -The default `culori` import comes with the full set of color spaces and functions to let you prototype quickly and with fewer errors. However, the way color spaces are initialized prevents the library from being tree-shaken, so when you use a bundler (such as [esbuild](https://esbuild.github.io/), [Vite](https://vitejs.dev/), or [Parcel](https://parceljs.org/)), the entire Culori library is bundled regardless of what you're actually using. - -This guides walks you through switching your imports to the tree-shakeable version once you're done prototyping and you're ready to ship an optimized bundle. - -## Using `culori/fn` - -To optimize the bundle size, you can opt into the tree-shakeable version by importing from `culori/fn` instead of `culori`: - -```js -import { …, …, … } from 'culori/fn'; -``` - -It works like the default import, with one key difference: __none of the color spaces are pre-registered into the library__. In fact, if you'd try to `parse()` a valid CSS color without having registered any color space, you'd get back `undefined`: - -```js -import { parse } from 'culori/fn'; - -parse('tomato'); -// ⇒ undefined -``` - -Instead, you need to import the definitions of the color spaces you want to support and register them manually with the `useMode()` method. - -Since none of the color spaces are pre-registered the shortcuts to `converter(mode)`, such as `culori.rgb` or `culori.hsl`, are also missing. They are, however, helpfully returned by `useMode()`: - -```js -import { useMode, modeRgb } from 'culori/fn'; - -const rgb = useMode(modeRgb); - -console.log(rgb('tomato')); -// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 } -``` - -To recreate a subset of Culori that only deals with valid CSS colors, you would use this: - -```js -import { - useMode, - modeA98, - modeHsl, - modeHwb, - modeLab, - modeLch, - modeOklab, - modeOklch, - modeP3, - modeProphoto, - modeRec2020, - modeRgb, - modeXyz50, - modeXyz65 -} from 'culori/fn'; - -const a98 = useMode(modeA98); -const hsl = useMode(modeHsl); -const hwb = useMode(modeHwb); -const lab = useMode(modeLab); -const lch = useMode(modeLch); -const oklab = useMode(modeOklab); -const oklch = useMode(modeOklch); -const p3 = useMode(modeP3); -const prophoto = useMode(modeProphoto); -const rec2020 = useMode(modeRec2020); -const rgb = useMode(modeRgb); -const xyz50 = useMode(modeXyz50); -const xyz65 = useMode(modeXyz65); -``` - -You don't need to do all of this manually. You can import the [CSS bundle](#culori-css) instead. - -### Using Culori without registering color spaces - -You can import and use the [low-level parsing and conversion functions](/api#low-level-api) without registering any color space beforehand: - -__hex-to-hsl-string.js__ -```js -import { parseHex, convertRgbToHsl, serializeHsl } from 'culori/fn'; - -console.log( - serializeHsl( - convertRgbToHsl( - parseHex('#ffcc00') - ) - ) -); -// ⇒ hsl(48, 100%, 50%) -``` - -When using a bundler such as [esbuild](https://esbuild.github.io/), a minified build fits on the back of a napkin. - -
-Show esbuild output for code above - -```bash -esbuild --bundle --minify hex-to-hsl-string.js -``` - -(()=>{var b=(e,o)=>{if(typeof e=="number"){if(o===3)return{mode:"rgb",r:(e>>8&15|e>>4&240)/255,g:(e>>4&15|e&240)/255,b:(e&15|e<<4&240)/255};if(o===4)return{mode:"rgb",r:(e>>12&15|e>>8&240)/255,g:(e>>8&15|e>>4&240)/255,b:(e>>4&15|e&240)/255,alpha:(e&15|e<<4&240)/255};if(o===6)return{mode:"rgb",r:(e>>16&255)/255,g:(e>>8&255)/255,b:(e&255)/255};if(o===8)return{mode:"rgb",r:(e>>24&255)/255,g:(e>>16&255)/255,b:(e>>8&255)/255,alpha:(e&255)/255}}},u=b;var c=/^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i,x=e=>{let o;return(o=e.match(c))?u(parseInt(o[1],16),o[1].length):void 0},l=x;function f({r:e,g:o,b:t,alpha:s}){let r=Math.max(e,o,t),a=Math.min(e,o,t),i={mode:"hsl",s:r===a?0:(r-a)/(1-Math.abs(r+a-1)),l:.5*(r+a)};return r-a!=0&&(i.h=(r===e?(o-t)/(r-a)+(otypeof o=="number"?Math.round(o*(e=Math.pow(10,e)))/e:o}var n=p(2),d=e=>Math.max(0,Math.min(1,e));var m=e=>{if(e===void 0)return;let o=n(e.h||0),t=n(d(e.s)*100),s=n(d(e.l)*100);return e.alpha===void 0||e.alpha===1?`hsl(${o}, ${t}%, ${s}%)`:`hsla(${o}, ${t}%, ${s}%, ${n(d(e.alpha))})`};console.log(m(f(l("#ffcc00"))));})(); - -
- -## Bootstrap packages - -For convenience, a couple of bootstrap packages are available. - -

culori/css

- -Bootstrap all the color spaces available in CSS, plus a handful of related ones we get for free, since they are used under the hood. - -It provides the following named exports: `a98`, `hsl`, `hsv`, `hwb`, `lab`, `lab65`, `lch`, `lch65`, `lrgb`, `oklab`, `oklch`, `p3`, `prophoto`, `rec2020`, `rgb`, `xyz50`, and `xyz65`. - -```js -import 'culori/css'; -import { interpolate } from 'culori/fn'; - -interpolate(['red', 'green'], 'lch'); -``` - -### `culori/all` - -Bootstrap all the color spaces available in Culori. - -It provides the following named exports: `a98`, `cubehelix`, `dlab`, `dlch`, `hsi`, `hsl`, `hsv`, `hwb`, `itp`, `jab`, `jch`, `lab`, `lab65`, `lch`, `lch65`, `lchuv`, `lrgb`, `luv`, `okhsl`, `okhsv`, `oklab`, `oklch`, `p3`, `prophoto`, `rec2020`, `rgb`, `xyb`, `xyz50`, `xyz65`, and `yiq`. - -```js -import 'culori/all'; -import { interpolate } from 'culori/fn'; - -interpolate(['red', 'green'], 'xyb'); -``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 379b1e03..00000000 --- a/docs/index.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Color functions for JavaScript -homepage: true -layout: default.njk ---- - -[css4-colors]: https://drafts.csswg.org/css-color/ -[css4-named-colors]: https://drafts.csswg.org/css-color/#named-colors -[din99o]: https://de.wikipedia.org/wiki/DIN99-Farbraum -[hex-colors]: https://drafts.csswg.org/css-color/#hex-notation -[rgb-colors]: https://drafts.csswg.org/css-color/#rgb-functions -[hsl-colors]: https://drafts.csswg.org/css-color/#the-hsl-notation -[hwb-colors]: https://drafts.csswg.org/css-color/#the-hwb-notation -[lab-colors]: https://drafts.csswg.org/css-color/#lab-colors - -Culori is a JavaScript color library that supports the conversion and manipulation of all formats defined in the [CSS Colors Level 4][css4-colors] specification, plus [additional color spaces](./color-spaces). It handles [color differences](https://en.wikipedia.org/wiki/Color_difference), interpolation, gradients, blend modes [and much more](/api/). - -```bash -npm install culori -``` - -Get started - -## What sets Culori apart? - -__A function-oriented API.__ Colors are represented as plain JavaScript objects you pass through a series of [functions](./api), which makes it super easy to extend. - -__Accurate alpha.__ On the `alpha` channel, the library doesn't equate an `undefined` value with an opaque color, but rather with a color for which we don't care about the opacity. This gives you the opportunity to interpret `undefined` as you see fit. The hex string #ff0000 _should_ probably be rendered as fully opaque red, but for running functions on colors it's useful to discern #ff0000 from #ff0000ff — the former has an implicit alpha of 1, while for the latter it's explicit. - -__Comprehensive functionality.__ Build advanced color tools with Culori's rich collection of color spaces and functions. - -__Tree-shakeable version available.__ When you're ready to optimize for bundle size, switch to [a tree-shakeable version of the library](./guides/tree-shaking). diff --git a/docs/resources.md b/docs/resources.md deleted file mode 100644 index b1ae751b..00000000 --- a/docs/resources.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: default.njk -title: Resources ---- - -## Culori extras - -These projects add more functionality to Culori, but they're separate as to keep the core bundle small: - -- [culori-scales](https://github.com/evercoder/culori-scales) — color scales (ColorBrewer, matplotlib, etc). -- [culori-names](https://github.com/evercoder/culori-names) — More named colors, from a variety of sources. -- [d3-color-difference](https://github.com/evercoder/d3-color-difference) — a separate D3.js plugin to compute color differences - -## Products using Culori - -- [Moqups](https://moqups.com) — all our color-manipulation functions are handled by Culori. -- [Schemist](https://github.com/felixgirault/schemist) — color schemes builder backed by Culori. -- [Color Name API](https://github.com/meodai/color-name-api) — a REST API for color names that makes use Culori's [differenceCiede2000](https://culorijs.org/api/#differenceCiede2000) to return a fitting name for every color. -- [Atmos](https://atmos.style) - a toolbox based on (OK)LCH color space for creating professional color palettes. Utilizing Culori for color manipulation allowed us to concentrate on our product's essence rather than getting bogged down in implementation details. -- [huetiful-js](https://github.com/prjctimg/huetiful) - Open source Typescript library for manipulating color and generating custom color scales based on Culori. With utilities for sorting, filtering and querying collections of colors, this library opens new ways of working with color programmatically. -- [oklch.com/](https://oklch.com/) - OKLCH picker with pretty plotting - -_Does your product/project use Culori? Create a PR and add yourself to this list._ - -## Similar projects - -- [d3-color](https://github.com/d3/d3-color) by [Mike Bostock](https://bost.ocks.org/mike/) (JavaScript) -- [chroma.js](https://github.com/gka/chroma.js) by [Gregor Aisch](https://driven-by-data.net/) (JavaScript) -- [colorjs.io](https://github.com/LeaVerou/color.js) by [Lea Verou](http://lea.verou.me/) and [Chris Lilley](https://svgees.us/) (JavaScript) -- [@texel/color](https://github.com/texel-org/color/) by [Matt DesLauriers](https://www.mattdesl.com/) (JavaScript) -- [Colour](https://www.colour-science.org/) (Python) -- [ColorAide](https://github.com/facelessuser/coloraide) by Isaac Muse (Python) -- [colorio](https://github.com/nschloe/colorio) by Nico Schlömer (Python) -- [`colorspace` package](http://colorspace.r-forge.r-project.org/) (R) -- [`Colors` package](https://github.com/JuliaGraphics/Colors.jl) (Julia) -- [`linebender/color`](https://github.com/linebender/color) by Raph Levien & collaborators (Rust) - -## Further reading - -### Specifications - -- [CSS Color Module Level 4](https://drafts.csswg.org/css-color-4/) [[changelog](https://github.com/w3c/csswg-drafts/commits/main/css-color-4), [issues](https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+label%3Acss-color-4)] -- [CSS Color Module Level 5](https://drafts.csswg.org/css-color-5/) [[changelog](https://github.com/w3c/csswg-drafts/commits/main/css-color-5), [issues](https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+label%3Acss-color-5)] -- [CSS Color HDR Module Level 1](https://drafts.csswg.org/css-color-hdr/) [[changelog](https://github.com/w3c/csswg-drafts/commits/main/css-color-hdr), [issues](https://github.com/w3c/csswg-drafts/issues?q=is%3Aissue+is%3Aopen+label%3Acss-color-hdr)] - -### Websites - -- David Briggs’s [The Dimensions of Color](http://www.huevaluechroma.com/) -- Bruce Lindbloom’s [Useful Color Math](http://www.brucelindbloom.com/index.html?Math.html) -- Rune Madsen’s [Programming Design Systems](https://programmingdesignsystems.com/) - -### Articles - -- [Color: From Hexcodes to Eyeballs](http://jamie-wong.com/post/color/) by Jamie Wong -- [Okay, color spaces](https://ericportis.com/posts/2024/okay-color-spaces/) by Eric Portis -- [How to generate color palettes for design systems](https://matthewstrom.com/writing/generating-color-palettes/) by Matthew Ström - -### Books - -- [Fairchild, Mark D.](http://markfairchild.org/) — [_Color Appearance Models_](https://www.wiley.com/en-us/Color+Appearance+Models%2C+3rd+Edition-p-9781119967033), 3rd Edition, Wiley, 2013. -- Green, Phil (ed.) — [_Fundamentals and Applications of Colour Engineering_](https://onlinelibrary.wiley.com/doi/book/10.1002/9781119827214), Wiley, 2023. -- Morovič, Ján — [_Color Gamut Mapping_](https://www.wiley.com/en-ie/Color+Gamut+Mapping-p-9780470030325), Wiley, 2008. -- [Poynton, Charles](http://poynton.ca/) — [_Digital Video and HD Algorithms and Interfaces_](https://www.elsevier.com/books/digital-video-and-hd/poynton/978-0-12-391926-7), 2nd Edition, Morgan Kaufmann, 2012. diff --git a/eleventy.config.cjs b/eleventy.config.cjs deleted file mode 100644 index 883bf443..00000000 --- a/eleventy.config.cjs +++ /dev/null @@ -1,28 +0,0 @@ -const highlight = require('@11ty/eleventy-plugin-syntaxhighlight'); - -module.exports = function (env) { - env.addPassthroughCopy('docs/img'); - env.addPassthroughCopy('docs/css'); - env.addPassthroughCopy({ 'docs/static': '.' }); - - env.addFilter('sortby', function (arr, key) { - return arr.slice().sort((a, b) => { - if (a.data[key] > b.data[key]) { - return 1; - } - if (a.data[key] < b.data[key]) { - return -1; - } - return 0; - }); - }); - env.addPlugin(highlight); - return { - pathPrefix: '/', - dir: { - input: 'docs', - output: 'www' - }, - htmlTemplateEngine: 'njk' - }; -}; diff --git a/docs/static/favicon.png b/favicon.png similarity index 100% rename from docs/static/favicon.png rename to favicon.png diff --git a/getting-started/index.html b/getting-started/index.html new file mode 100644 index 00000000..e549bb13 --- /dev/null +++ b/getting-started/index.html @@ -0,0 +1,153 @@ + + + + + + + Getting Started with Culori · culori + + + + + + +
+
+ + +
+
+ +
+
+

Getting Started with Culori

+

Install Culori from npm

+

Culori is distributed on npm in a variety of formats. Install it with:

+
npm install culori
+

Then start importing functions from the API:

+
import { rgb } from 'culori';
+
+rgb('tomato');
+// ⇒ Object { mode: "rgb", r: 1, g: 0.38823529411764707, b: 0.2784313725490196 }
+

For code that runs in browsers, you may want to streamline the bundle to only include the parts of Culori you're using. See Optimize bundle size with tree-shaking for guidance on switching your imports to use 'culori/fn' instead of 'culori' once you're done prototyping.

+

Fetch Culori from a CDN

+

You can use Culori from your favorite Content Delivery Network to create quick prototypes in HTML. Here are a few popular choices:

+ + + + + + + + + + + + + + + + + + + + + +
CDNURL
unpkghttps://unpkg.com/culori
jsDelivrhttps://cdn.jsdelivr.net/npm/culori
skypackhttps://cdn.skypack.dev/culori
+

Use it as an ES module:

+
<script type='module'>
+	import { rgb } from 'https://cdn.skypack.dev/culori';
+	console.log(rgb('salmon'));
+</script>
+

...or using a traditional <script> tag. The library will be made available under the culori global variable:

+
<script src="https://unpkg.com/culori"></script>
+<script>
+	console.log(culori.rgb('salmon'));
+</script>
+

Use Culori online

+

In your browser's console

+

The library is available on this website as the global variable culori, so can try the API in your browser's console as you read through the examples.

+

Observable

+

If you prefer to see the results visually, Observable is a great place to tinker with the library.

+

Add this cell and you're good to go:

+
culori = import('culori@4.0.2');
+

It's often useful to pin the library to a specific version, to make sure your old notebooks don't break if the API changes in a new major version of the library.

+

Use Culori in Deno

+

The library is published to deno.land/x/culori for usage in Deno:

+
import { rgb } from 'https://deno.land/x/culori@v4.0.2/index.js';
+
+rgb('tomato');
+ +
+
+ + + + + + diff --git a/docs/_includes/default.njk b/guides/index.html similarity index 62% rename from docs/_includes/default.njk rename to guides/index.html index 0cf75625..2b708106 100644 --- a/docs/_includes/default.njk +++ b/guides/index.html @@ -4,39 +4,38 @@ - {% if title %} {{ title }} · culori {% else %} culori {% - endif %} + Guides · culori - - + +