Skip to content

Commit

Permalink
Parse and serialize color(...) CSS syntax (#138)
Browse files Browse the repository at this point in the history
* Implement parse('color(...)')
* Provide color() parsing for all color spaces
* add 'serialize' property to channel definiton, implement formatCss() method
* serialize xyz as color(--xyz-d50)
* Provide default serialize prefix 'color(--mode '
* Adds custom serializer function for hsl() / hwb() / lab() / lch()
  • Loading branch information
danburzo authored Aug 5, 2021
1 parent fbfb04b commit 7f9d27f
Show file tree
Hide file tree
Showing 62 changed files with 840 additions and 124 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

<a href="https://www.npmjs.org/package/culori"><img src="https://img.shields.io/npm/v/culori.svg?style=flat-square&labelColor=d84f4c&color=black" alt="npm version"></a> <a href="https://bundlephobia.com/result?p=culori"><img src="https://img.shields.io/bundlephobia/minzip/culori?style=flat-square&labelColor=d84f4c&color=black" alt="npm version"></a>

culori is a color library for JavaScript that works across many color spaces to offer conversion, interpolation, color difference formulas, blending functions, and more.
Culori is a color library for JavaScript that works across many color spaces to offer conversion, interpolation, color difference formulas, blending functions, and more.

[**Read the documentation**](https://culorijs.org)
```bash
npm install culori
```

[Read the documentation](https://culorijs.org)
4 changes: 2 additions & 2 deletions docs/_includes/layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ <h1>{{ title }}</h1>
<script type="text/javascript">
document.body.style.setProperty(
'--random-1',
culori.formatHex(culori.random('lch', { l: [10, 50] }))
culori.formatHex(culori.random('lch', { l: [30, 70] }))
);
document.body.style.setProperty(
'--random-2',
culori.formatHex(culori.random('lch', { l: [10, 50] }))
culori.formatHex(culori.random('lch', { l: [30, 70] }))
);
</script>
</body>
Expand Down
53 changes: 48 additions & 5 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ These methods serialize colors to strings, in various formats.

<a name="formatHex" href="#formatHex">#</a> culori.**formatHex**(_color_ or _string_) → _string_ &middot; [Source](https://github.com/evercoder/culori/blob/main/src/formatter.js)

Returns the hex string for a color. The color's `alpha` channel is omitted, and the red, green, and blue channels are clamped to the the interval `[0, 255]`, i.e. colors that are not displayable are serialized as if they'd been passed through the `clampRgb` method.
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
culori.formatHex('red');
Expand All @@ -110,7 +110,7 @@ culori.formatHex('red');

<a name="formatHex8" href="#formatHex8">#</a> culori.**formatHex8**(_color_ or _string_) → _string_ &middot; [Source](https://github.com/evercoder/culori/blob/main/src/formatter.js)

Returns the 8-character hex string for a color. The red, green, blue, and alpha channels are clamped to the the interval `[0, 255]`, i.e. colors that are not displayable are serialized as if they'd been passed through the `clampRgb` method.
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
culori.formatHex8({ mode: 'rgb', r: 1, g: 0, b: 0, alpha: 0.5 });
Expand All @@ -119,7 +119,7 @@ culori.formatHex8({ mode: 'rgb', r: 1, g: 0, b: 0, alpha: 0.5 });

<a name="formatRgb" href="#formatRgb">#</a> culori.**formatRgb**(_color_ or _string_) → _string_ &middot; [Source](https://github.com/evercoder/culori/blob/main/src/formatter.js)

Returns the `rgb(…)` / `rgba(…)` string for a color. Fully opaque colors will be serialized as `rgb()`, and semi-transparent colors as `rgba()`, in accordance with the [CSSOM standard serialization](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]`.
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
culori.formatRgb('lab(50 0 0 / 25%)');
Expand All @@ -128,13 +128,51 @@ culori.formatRgb('lab(50 0 0 / 25%)');

<a name="formatHsl" href="#formatHsl">#</a> culori.**formatHsl**(_color_ or _string_) → _string_ &middot; [Source](https://github.com/evercoder/culori/blob/main/src/formatter.js)

Returns the `hsl(…)` / `hsla(…)` string for a 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%]`.
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
culori.formatHsl('lab(50 0 0 / 25%)');
// ⇒ 'hsla(194.33, 0%, 46.63%, 0.25)'
```

<a name="formatCss" href="#formatCss">#</a> culori.**formatCss**(_color_ or _string_) → _string_ &middot; [Source](https://github.com/evercoder/culori/blob/main/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](/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
/*
A mode with its own function notation.
*/
culori.formatCss({ mode: 'hsl', h: 30, s: 1, l: 0.5, alpha: 0.5 });
// ⇒ 'hsl(30 100% 50% / 0.5)'

/*
A predefined color space.
*/
culori.formatCss({ mode: 'p3', r: 0.5, s: 0.25, b: 1, alpha: 1 });
// ⇒ 'color(display-p3 0.5 0.25 1)'

/*
sRGB colors.
*/
culori.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.
*/
culori.formatCss({ mode: 'lrgb', r: 0.5, s: 0.25, b: 1, alpha: 0.25 });
// ⇒ 'color(--srgb-linear 0.5 0.25 1 / 0.25)'
```

## Clamping

Some color spaces (Lab and LCh in particular) allow you to express colors that can't be displayed on-screen. The methods below allow you to identify when that's the case and to produce displayable versions of the colors.
Expand Down Expand Up @@ -1003,6 +1041,10 @@ Defines a new color space through a _definition_ object. Here's the full definit
h: [0, 360]
},
parsers: [parseHsl],
serialize: c =>
`hsl(${c.h} ${c.s * 100}% ${c.l * 100}%${
c.alpha < 1 ? ` / ${c.alpha}` : ''
})`,
interpolate: {
h: {
use: interpolatorLinear,
Expand Down Expand Up @@ -1031,7 +1073,8 @@ The properties a definition needs are the following:
- `input`: opposite of `output`; a set of function to convert from various color spaces to the color space we're defining. At least `rgb` needs to be included.
- `channels`: a list of channels for the color space.
- `ranges`: the ranges for values in specific channels; if left unspecified, defaults to `[0, 1]`.
- `parsers`: any parsers for the color space that can transform strings into colors
- `parsers`: 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`: when a string is provided, it's used as the prefix when producing a string with `culori.formatCss`; when missing, it defaults to `color(--${color.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).
Expand Down
60 changes: 56 additions & 4 deletions docs/color-spaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,37 @@ The [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) describes c

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 / alpha)`.

#### `lrgb`

The linear-light form of the sRGB color space.

Serialized as `color(--srgb-linear r g b / alpha)`.

#### `a98`

The A98 RGB color space, compatible with the [Adobe RGB (1998) color space](https://en.wikipedia.org/wiki/Adobe_RGB_color_space). It's called `a98-rgb` in the CSS Color Level 4 specification.
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 / alpha)`.

#### `p3`

The [Display P3 color space](https://en.wikipedia.org/wiki/DCI-P3#Display_P3). It's called `display-p3` in the CSS Color Level 4 specification.
The [Display P3 color space](https://en.wikipedia.org/wiki/DCI-P3#Display_P3).

Serialized as `color(display-p3 r g b / alpha)`.

#### `prophoto`

The [ProPhoto RGB color space](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space). It's called `prophoto-rgb` in the CSS Color Level 4 specification.
The [ProPhoto RGB color space](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space).

Serialized as `color(prophoto-rgb r g b / alpha)`.

#### `rec2020`

The [Rec. 2020 color space](https://en.wikipedia.org/wiki/Rec._2020). It's called `display-p3` in the CSS Color Level 4 specification.
The [Rec. 2020 color space](https://en.wikipedia.org/wiki/Rec._2020).

Serialized as `color(rec2020 r g b / alpha)`.

### The HSL/HSV/HSI family

Expand All @@ -70,6 +82,8 @@ The HSL color space.
| `s` | `[0, 1]` | Saturation in HSL |
| `l` | `[0, 1]` | Lightness |

Serialized as `hsl(h s% l% / alpha)`.

#### `hsv`

The HSV color space.
Expand All @@ -80,6 +94,8 @@ The HSV color space.
| `s` | `[0, 1]` | Saturation in HSV |
| `v` | `[0, 1]` | Value |

Serialized as `color(--hsv h s v / alpha)`.

#### `hsi`

The HSI color space.
Expand All @@ -90,6 +106,8 @@ The HSI color space.
| `s` | `[0, 1]` | Saturation in HSI |
| `i` | `[0, 1]` | Intensity |

Serialized as `color(--hsi h s i / alpha)`.

### 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.
Expand All @@ -100,6 +118,8 @@ The HSI color space.
| `w` | `[0, 1]` | Whiteness |
| `b` | `[0, 1]` | Blackness |

Serialized as `hwb(h w% b% / alpha)`.

> 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
Expand All @@ -116,6 +136,8 @@ The CIELAB color space using the [D50 standard illuminant](https://en.wikipedia.
| `a` | `[-79.287, 93.55]`| Green–red component |
| `b` | `[-112.029, 93.388]`| Blue–yellow component |

Serialized as `lab(l% a b / alpha)`.

#### `lch`

The CIELCh color space using the D50 standard illuminant.
Expand All @@ -126,6 +148,8 @@ The CIELCh color space using the D50 standard illuminant.
| `c` | `[0, 131.207]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `lch(l% c h / alpha)`.

#### `lab65`

CIELAB relative to the D65 standard illuminant.
Expand All @@ -136,6 +160,8 @@ CIELAB relative to the D65 standard illuminant.
| `a` | `[-86.183, 98.234]`| Green–red component |
| `b` | `[-107.86, 94.478]`| Blue–yellow component |

Serialized as `color(--lab-d65 l a b / alpha)`.

#### `lch65`

CIELCh relative to the D65 standard illuminant.
Expand All @@ -146,6 +172,8 @@ CIELCh relative to the D65 standard illuminant.
| `c` | `[0, 133.807]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `color(--lch-d65 l c h / alpha)`.

### CIELUV

The [CIELUV color space](https://en.wikipedia.org/wiki/CIELUV) in Cartesian (Luv) and cylindrical (LCh) forms, using the D50 standard illuminant.
Expand All @@ -164,6 +192,8 @@ let deltaE_uv = culori.colorDifferenceEuclidean('luv');
| `u` | `[-84.936, 175.042]`| Green–red component |
| `v` | `[-125.882, 87.243]`| Blue–yellow component |

Serialized as `color(--luv l u v / alpha)`.

#### `lchuv`

| Channel | Range | Description |
Expand All @@ -172,6 +202,8 @@ let deltaE_uv = culori.colorDifferenceEuclidean('luv');
| `c` | `[0, 176.956]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `color(--lchuv l c h / alpha)`.

### DIN99 Lab / LCh

The [DIN99][din99o] 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 plar (`dlch`) form.
Expand All @@ -188,6 +220,8 @@ The DIN99o color space in Cartesian form.
| `a` | `[-40.09, 45.501]`|
| `b` | `[-40.469, 44.344]`|

Serialized as `color(--din99o-lab l a b / alpha)`.

#### `dlch`

The DIN99o color space in cylindrical form.
Expand All @@ -198,6 +232,8 @@ The DIN99o color space in cylindrical form.
| `c` | `[0, 51.484]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `color(--din99o-lch l c h / alpha)`.

### Oklab

The [Oklab color space](https://bottosson.github.io/posts/oklab/), in Cartesian (Lab) and cylindrical (LCh) forms. It uses the D65 standard illuminant.
Expand All @@ -212,6 +248,8 @@ The Oklab color space in Cartesian form.
| `a` | `[-0.233, 0.276]`| Green–red component |
| `b` | `[-0.311, 0.198]`| Blue–yellow component |

Serialized as `color(--oklab l a b / alpha)`.

#### `oklch`

The Oklab color space in cylindrical form.
Expand All @@ -222,6 +260,8 @@ The Oklab color space in cylindrical form.
| `c` | `[0, 0.322]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `color(--oklch l c h / alpha)`.

### J<sub>z</sub>a<sub>z</sub>b<sub>z</sub>

The J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space, as defined by:
Expand All @@ -238,6 +278,8 @@ The J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space in Cartesian form.
| `a` | `[-0.109, 0.129]`| Green–red component |
| `b` | `[-0.185, 0.134]`| Blue–yellow component |

Serialized as `color(--jzazbz j a b / alpha)`.

#### `jch`

The J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space in cylindrical form.
Expand All @@ -248,6 +290,8 @@ The J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space in cylindrical form.
| `c` | `[0, 0.190]`| Chroma |
| `h` | `[0, 360)` | Hue |

Serialized as `color(--jzczhz j c h / alpha)`.

### YIQ

[YIQ][yiq] is the color space used by the NTSC color TV system. It contains the following channels:
Expand All @@ -258,6 +302,8 @@ The J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space in cylindrical form.
| 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 / alpha)`.

### CIE XYZ

The [CIE XYZ color space](https://en.wikipedia.org/wiki/CIE_1931_color_space), also known as the CIE 1931 color space.
Expand All @@ -272,6 +318,8 @@ The CIE XYZ color space in respect to the D50 standard illuminant.
| Y | `[0, 0.999]`| ? |
| Z | `[0, 0.825]`| ? |

Serialized as `color(--xyz-d50 x y z / alpha)`.

#### `xyz65`

The CIE XYZ color space in respect to the D65 standard illuminant.
Expand All @@ -282,6 +330,8 @@ The CIE XYZ color space in respect to the D65 standard illuminant.
| Y | `[0, 1]`| ? |
| Z | `[0, 1.088]`| ? |

Serialized as `color(--xyz-d65 x y z / alpha)`.

### Cubehelix

[The Cubehelix color scheme](https://www.mrao.cam.ac.uk/~dag/CUBEHELIX/) was described by Dave Green in this paper:
Expand All @@ -300,5 +350,7 @@ The channels in the `cubehelix` color space maintain the conventions from D3, na
| `s` | `[0, 4.614]` | Saturation (Called _hue_ in op. cit.) |
| `l` | `[0, 1]` | Lightness |

Serialized as `color(--cubehelix h s l / alpha)`.

[din99o]: https://de.wikipedia.org/wiki/DIN99-Farbraum
[yiq]: https://en.wikipedia.org/wiki/YIQ
4 changes: 2 additions & 2 deletions docs/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ h1 {
h1 {
background: linear-gradient(
to right,
var(--random-1, black),
var(--random-2, black)
var(--random-1, #999),
var(--random-2, #999)
);
background-clip: text;
color: transparent;
Expand Down
4 changes: 2 additions & 2 deletions src/a98/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import convertXyz65ToRgb from '../xyz65/convertXyz65ToRgb';
const definition = {
...rgb,
mode: 'a98',
alias: ['a98-rgb'],
parsers: [],
parsers: ['a98-rgb'],
serialize: 'color(a98-rgb ',

input: {
rgb: color => convertXyz65ToA98(convertRgbToXyz65(color)),
Expand Down
2 changes: 2 additions & 0 deletions src/cubehelix/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import { averageAngle } from '../average';
const definition = {
mode: 'cubehelix',
channels: ['h', 's', 'l', 'alpha'],
parsers: ['--cubehelix'],
serialize: 'color(--cubehelix ',

ranges: {
h: [0, 360],
Expand Down
3 changes: 3 additions & 0 deletions src/dlab/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { fixupAlpha } from '../fixup/alpha';
const definition = {
mode: 'dlab',

parsers: ['--din99o-lab'],
serialize: 'color(--din99o-lab ',

output: {
lab65: convertDlabToLab65,
rgb: c => convertLab65ToRgb(convertDlabToLab65(c))
Expand Down
3 changes: 3 additions & 0 deletions src/dlch/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { averageAngle } from '../average';
const definition = {
mode: 'dlch',

parsers: ['--din99o-lch'],
serialize: 'color(--din99o-lch ',

output: {
lab65: convertDlchToLab65,
dlab: convertDlchToDlab,
Expand Down
Loading

0 comments on commit 7f9d27f

Please sign in to comment.