Skip to content

Commit

Permalink
New plugin: CIELAB color space
Browse files Browse the repository at this point in the history
  • Loading branch information
omgovich authored and Vlad Shilov committed Apr 16, 2021
1 parent 34e0869 commit 6e64ce3
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 4 deletions.
41 changes: 38 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ colord("hsl(0, 50%, 50%)").darken(0.25).toHex(); // "#602020"
- HSV objects
- Color names ([via plugin](#plugins))
- HWB objects ([via plugin](#plugins))
- LAB objects ([via plugin](#plugins))
- XYZ objects ([via plugin](#plugins))
- LCH (coming soon)

Expand Down Expand Up @@ -222,6 +223,23 @@ colord("#555aaa").toHwb(); // { h: 236, w: 33, b: 33, a: 1 }

</details>

<details>
<summary><b><code>toLab()</code></b> (<b>lab</b> plugin)</summary>

Converts a color to [CIE LAB](https://en.wikipedia.org/wiki/CIELAB_color_space) color space. The conversion logic is ported from [CSS Color Module Level 4 Specification](https://www.w3.org/TR/css-color-4/#color-conversion-code).

```js
import { colord, extend } from "colord";
import labPlugin from "colord/plugins/lab";

extend([labPlugin]);

colord("#ffffff").toLab(); // { l: 100, a: 0, b: 0, alpha: 1 }
colord("#33221180").toLab(); // { l: 14.89, a: 5.77, b: 14.41, alpha: 0.5 }
```

</details>

<details>
<summary><b><code>toXyz()</code></b> (<b>xyz</b> plugin)</summary>

Expand Down Expand Up @@ -473,9 +491,9 @@ colord("#e60000").isReadable("#ffff47", { level: "AAA", size: "large" }); // tru
</details>

<details>
<summary><b><code>hwb</code> (Hue-Whiteness-Blackness color model)</b> <i>0.5 KB</i></summary>
<summary><b><code>hwb</code> (HWB color model)</b> <i>0.5 KB</i></summary>

Adds support of [HWB (Hue-Whiteness-Blackness)](https://en.wikipedia.org/wiki/HWB_color_model) color model.
Adds support of [Hue-Whiteness-Blackness](https://en.wikipedia.org/wiki/HWB_color_model) color model.

```js
import { colord, extend } from "colord";
Expand All @@ -492,6 +510,23 @@ colord({ h: 0, w: 100, b: 0, a: 1 }).toHex(); // "#ffffff"

</details>

<details>
<summary><b><code>lab</code> (CIE LAB color space)</b> <i>0.78 KB</i></summary>

Adds support of [CIE LAB](https://en.wikipedia.org/wiki/CIELAB_color_space) color model. The conversion logic is ported from [CSS Color Module Level 4 Specification](https://www.w3.org/TR/css-color-4/#color-conversion-code).

```js
import { colord, extend } from "colord";
import labPlugin from "colord/plugins/lab";

extend([labPlugin]);

colord({ l: 53.24, a: 80.09, b: 67.2 }).toHex(); // "#ff0000"
colord("#ffffff").toLab(); // { l: 100, a: 0, b: 0, alpha: 1 }
```

</details>

<details>
<summary><b><code>names</code> (CSS color keywords)</b> <i>1.29 KB</i></summary>

Expand Down Expand Up @@ -561,7 +596,7 @@ const bar: RgbColor = { r: 0, g: 0, v: 0 }; // ERROR
- [x] A11y and contrast utils (via plugin)
- [x] XYZ color space (via plugin)
- [x] [HWB](https://drafts.csswg.org/css-color/#the-hwb-notation) color space (via plugin)
- [ ] [LAB](https://www.w3.org/TR/css-color-4/#resolving-lab-lch-values) color space (via plugin)
- [x] [LAB](https://www.w3.org/TR/css-color-4/#resolving-lab-lch-values) color space (via plugin)
- [ ] [LCH](https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/) color space (via plugin)
- [ ] CMYK color space (via plugin)
- [ ] Mix colors (via plugin)
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
"path": "dist/plugins/hwb.mjs",
"limit": "1 KB"
},
{
"path": "dist/plugins/lab.mjs",
"limit": "1 KB"
},
{
"path": "dist/plugins/names.mjs",
"limit": "1.5 KB"
Expand Down
79 changes: 79 additions & 0 deletions src/colorModels/lab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { RgbaColor, LabaColor, InputObject } from "../types";
import { clamp, isPresent, round } from "../helpers";
import { rgbaToXyza, xyzaToRgba } from "./xyz";

// Conversion factors from https://en.wikipedia.org/wiki/CIELAB_color_space
const e = 0.0088564517;
const k = 7.787037037;

/**
* Limits LAB axis values.
* http://colorizer.org/
* https://www.nixsensor.com/free-color-converter/
*/
export const clampLaba = (laba: LabaColor): LabaColor => ({
l: clamp(laba.l, 0, 100),
a: clamp(laba.a, -128, 128),
b: clamp(laba.b, -128, 128),
alpha: clamp(laba.alpha),
});

export const roundLaba = (laba: LabaColor): LabaColor => ({
l: round(laba.l, 2),
a: round(laba.a, 2),
b: round(laba.b, 2),
alpha: round(laba.alpha, 2),
});

export const parseLaba = ({ l, a, b, alpha = 1 }: InputObject): RgbaColor | null => {
if (!isPresent(l) || !isPresent(a) || !isPresent(b)) return null;

const laba = clampLaba({
l: Number(l),
a: Number(a),
b: Number(b),
alpha: Number(alpha),
});

return labaToRgba(laba);
};

/**
* Performs RGB → CIEXYZ → LAB color conversion
* https://www.w3.org/TR/css-color-4/#color-conversion-code
*/
export const rgbaToLaba = (rgba: RgbaColor): LabaColor => {
// Compute XYZ scaled relative to D65 reference white
const xyza = rgbaToXyza(rgba);
let x = xyza.x / 95.047;
let y = xyza.y / 100;
let z = xyza.z / 108.883;

x = x > e ? Math.cbrt(x) : k * x + 16 / 116;
y = y > e ? Math.cbrt(y) : k * y + 16 / 116;
z = z > e ? Math.cbrt(z) : k * z + 16 / 116;

return {
l: 116 * y - 16,
a: 500 * (x - y),
b: 200 * (y - z),
alpha: xyza.a,
};
};

/**
* Performs LAB → CIEXYZ → RGB color conversion
* https://www.w3.org/TR/css-color-4/#color-conversion-code
*/
export const labaToRgba = (laba: LabaColor): RgbaColor => {
const y = (laba.l + 16) / 116;
const x = laba.a / 500 + y;
const z = y - laba.b / 200;

return xyzaToRgba({
x: (x ** 3 > e ? x ** 3 : (x - 16 / 116) / k) * 95.047,
y: (y ** 3 > e ? y ** 3 : (y - 16 / 116) / k) * 100,
z: (z ** 3 > e ? z ** 3 : (z - 16 / 116) / k) * 108.883,
a: laba.alpha,
});
};
27 changes: 27 additions & 0 deletions src/plugins/lab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LabaColor } from "../types";
import { Plugin } from "../extend";
import { parseLaba, roundLaba, rgbaToLaba } from "../colorModels/lab";

declare module "../colord" {
interface Colord {
/**
* Converts a color to CIELAB color space and returns an object.
* The object always includes `alpha` value [0—1].
*/
toLab(): LabaColor;
}
}

/**
* A plugin adding support for CIELAB color space.
* https://en.wikipedia.org/wiki/CIELAB_color_space
*/
const labPlugin: Plugin = (ColordClass, parsers): void => {
ColordClass.prototype.toLab = function () {
return roundLaba(rgbaToLaba(this.rgba));
};

parsers.object.push(parseLaba);
};

export default labPlugin;
11 changes: 10 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,19 @@ export interface XyzColor {
z: number;
}

export interface LabColor {
l: number;
a: number;
b: number;
}

type WithAlpha<O> = O & { a: number };
export type RgbaColor = WithAlpha<RgbColor>;
export type HslaColor = WithAlpha<HslColor>;
export type HsvaColor = WithAlpha<HsvColor>;
export type HwbaColor = WithAlpha<HwbColor>;
export type XyzaColor = WithAlpha<XyzColor>; // Naming is the hardest part https://stackoverflow.com/a/2464027
export type LabaColor = LabColor & { alpha: number };

export type ObjectColor =
| RgbColor
Expand All @@ -45,7 +52,9 @@ export type ObjectColor =
| HwbColor
| HwbaColor
| XyzColor
| XyzaColor;
| XyzaColor
| LabColor
| LabaColor;

export type AnyColor = string | ObjectColor;

Expand Down
26 changes: 26 additions & 0 deletions tests/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { colord, extend } from "../src/";
import a11yPlugin from "../src/plugins/a11y";
import hwbPlugin from "../src/plugins/hwb";
import labPlugin from "../src/plugins/lab";
import namesPlugin from "../src/plugins/names";
import xyzPlugin from "../src/plugins/xyz";

Expand Down Expand Up @@ -70,6 +71,31 @@ describe("hwb", () => {
});
});

describe("lab", () => {
extend([labPlugin]);

it("Parses CIE LAB color object", () => {
// https://www.easyrgb.com/en/convert.php
expect(colord({ l: 100, a: 0, b: 0 }).toHex()).toBe("#ffffff");
expect(colord({ l: 0, a: 0, b: 0 }).toHex()).toBe("#000000");
expect(colord({ l: 53.24, a: 80.09, b: 67.2 }).toHex()).toBe("#ff0000");
expect(colord({ l: 14.89, a: 5.77, b: 14.41, alpha: 0.5 }).toHex()).toBe("#33221180");
expect(colord({ l: 50.48, a: 65.85, b: -7.51, alpha: 1 }).toHex()).toBe("#d53987");
});

it("Converts a color to CIE LAB object", () => {
// https://www.easyrgb.com/en/convert.php
expect(colord("#ffffff").toLab()).toMatchObject({ l: 100, a: 0, b: 0, alpha: 1 });
expect(colord("#00000000").toLab()).toMatchObject({ l: 0, a: 0, b: 0, alpha: 0 });
expect(colord("#ff0000").toLab()).toMatchObject({ l: 53.24, a: 80.09, b: 67.2, alpha: 1 });
expect(colord("#00ff00").toLab()).toMatchObject({ l: 87.73, a: -86.18, b: 83.18, alpha: 1 });
expect(colord("#ffff00").toLab()).toMatchObject({ l: 97.14, a: -21.55, b: 94.48, alpha: 1 });
expect(colord("#aabbcc").toLab()).toMatchObject({ l: 75.1, a: -2.29, b: -10.53, alpha: 1 });
expect(colord("#33221180").toLab()).toMatchObject({ l: 14.89, a: 5.77, b: 14.41, alpha: 0.5 });
expect(colord("#d53987").toLab()).toMatchObject({ l: 50.48, a: 65.85, b: -7.51, alpha: 1 });
});
});

describe("names", () => {
extend([namesPlugin]);

Expand Down

0 comments on commit 6e64ce3

Please sign in to comment.