Skip to content

Commit

Permalink
feat: add support for oklab and oklch color space
Browse files Browse the repository at this point in the history
  • Loading branch information
gka committed Feb 3, 2022
1 parent d4d60e6 commit a8939a8
Show file tree
Hide file tree
Showing 27 changed files with 2,220 additions and 1,036 deletions.
352 changes: 242 additions & 110 deletions chroma-light.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion chroma-light.min.js

Large diffs are not rendered by default.

836 changes: 531 additions & 305 deletions chroma.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion chroma.min.js

Large diffs are not rendered by default.

374 changes: 238 additions & 136 deletions docs/index.html

Large diffs are not rendered by default.

836 changes: 531 additions & 305 deletions docs/libs/chroma.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/libs/chroma.min.js

Large diffs are not rendered by default.

359 changes: 218 additions & 141 deletions docs/src/footer.inc.html

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,25 @@ chroma.hsl(330, 1, 0.6)
### chroma.lab
#### (Lightness, a, b)

CIELAB color space

```js
chroma.lab(40,-20,50);
chroma.lab(50,-20,50);
chroma.lab(80,-20,50);
```

### chroma.oklab
#### (Lightness, a, b)

[Oklab color space](https://bottosson.github.io/posts/oklab/)

```js
chroma.oklab(0.4,-0.2,0.5);
chroma.oklab(0.5,-0.2,0.5);
chroma.oklab(0.8,-0.2,0.5);
```

### chroma.lch
#### (Lightness, chroma, hue)

Expand All @@ -134,6 +153,14 @@ chroma.hcl(130, 40, 80);
chroma(130, 40, 80, 'hcl');
```

### chroma.oklch
#### (Lightness, chromacity, hue)

```js
chroma.oklch(0.5, 0.2, 240);
chroma(0.8, 0.12, 60, 'oklch');
```

### chroma.cmyk
#### (cyan, magenta, yellow, black)

Expand Down Expand Up @@ -543,6 +570,14 @@ Returns an array with the **L**, **a**, and **b** components.
chroma('orange').lab()
```

### color.oklab

Returns an array with the **L**, **a**, and **b** components.

```js
chroma('orange').oklab()
```


### color.lch

Expand All @@ -560,6 +595,14 @@ Alias of [lch](#color-lch), but with the components in reverse order.
chroma('skyblue').hcl()
```

### color.oklch

Returns an array with the **Lightness**, **chroma**, and **hue** components.

```js
chroma('skyblue').oklch()
```

### color.num

Returns the numeric representation of the hexadecimal RGB color.
Expand Down
2 changes: 2 additions & 0 deletions index-light.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require('./src/io/css');
require('./src/io/hex');
require('./src/io/hsl');
require('./src/io/lab');
require('./src/io/oklab');
require('./src/io/rgb');

// operators --> modify existing Colors
Expand All @@ -19,6 +20,7 @@ require('./src/ops/set');

// interpolators
require('./src/interpolator/lrgb');
require('./src/interpolator/oklab');

// generators -- > create new colors
chroma.mix = chroma.interpolate = require('./src/generator/mix');
Expand Down
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ require('./src/io/named');
require('./src/io/num');
require('./src/io/rgb');
require('./src/io/temp');
require('./src/io/oklab');
require('./src/io/oklch');

// operators --> modify existing Colors
require('./src/ops/alpha');
Expand All @@ -40,6 +42,8 @@ require('./src/interpolator/hcg');
require('./src/interpolator/hsi');
require('./src/interpolator/hsl');
require('./src/interpolator/hsv');
require('./src/interpolator/oklab');
require('./src/interpolator/oklch');

// generators -- > create new colors
chroma.average = require('./src/generator/average');
Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,19 @@
"test"
]
},
"prettier": {
"arrowParens": "avoid",
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "none",
"svelteSortOrder": "options-scripts-styles-markup"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint",
"pre-push": "npm test"
}
}
}
}
18 changes: 10 additions & 8 deletions src/interpolator/_hsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ module.exports = (col1, col2, f, m) => {
m = 'hcl';
xyz0 = col1.hcl();
xyz1 = col2.hcl();
} else if (m === 'oklch') {
xyz0 = col1.oklch().reverse();
xyz1 = col2.oklch().reverse();
}

let hue0, hue1, sat0, sat1, lbv0, lbv1;
if (m.substr(0, 1) === 'h') {
if (m.substr(0, 1) === 'h' || m === 'oklch') {
[hue0, sat0, lbv0] = xyz0;
[hue1, sat1, lbv1] = xyz1;
}
Expand All @@ -31,10 +34,10 @@ module.exports = (col1, col2, f, m) => {
if (!isNaN(hue0) && !isNaN(hue1)) {
// both colors have hue
if (hue1 > hue0 && hue1 - hue0 > 180) {
dh = hue1-(hue0+360);
dh = hue1 - (hue0 + 360);
} else if (hue1 < hue0 && hue0 - hue1 > 180) {
dh = hue1+360-hue0;
} else{
dh = hue1 + 360 - hue0;
} else {
dh = hue1 - hue0;
}
hue = hue0 + f * dh;
Expand All @@ -49,7 +52,6 @@ module.exports = (col1, col2, f, m) => {
}

if (sat === undefined) sat = sat0 + f * (sat1 - sat0);
lbv = lbv0 + f * (lbv1-lbv0)
return new Color([hue, sat, lbv], m);
}

lbv = lbv0 + f * (lbv1 - lbv0);
return m === 'oklch' ? new Color([lbv, sat, hue], m) : new Color([hue, sat, lbv], m);
};
19 changes: 19 additions & 0 deletions src/interpolator/oklab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require('../io/oklab');

const Color = require('../Color');

const oklab = (col1, col2, f) => {
const xyz0 = col1.oklab();
const xyz1 = col2.oklab();
return new Color(
xyz0[0] + f * (xyz1[0] - xyz0[0]),
xyz0[1] + f * (xyz1[1] - xyz0[1]),
xyz0[2] + f * (xyz1[2] - xyz0[2]),
'oklab'
);
};

// register interpolator
require('./index').oklab = oklab;

module.exports = oklab;
11 changes: 11 additions & 0 deletions src/interpolator/oklch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require('../io/lch');
const interpolate_hsx = require('./_hsx');

const oklch = (col1, col2, f) => {
return interpolate_hsx(col1, col2, f, 'oklch');
};

// register interpolator
require('./index').oklch = oklch;

module.exports = oklch;
24 changes: 24 additions & 0 deletions src/io/oklab/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { unpack, type } = require('../../utils');
const chroma = require('../../chroma');
const Color = require('../../Color');
const input = require('../input');

const rgb2oklab = require('./rgb2oklab');

Color.prototype.oklab = function () {
return rgb2oklab(this._rgb);
};

chroma.oklab = (...args) => new Color(...args, 'oklab');

input.format.oklab = require('./oklab2rgb');

input.autodetect.push({
p: 3,
test: (...args) => {
args = unpack(args, 'oklab');
if (type(args) === 'array' && args.length === 3) {
return 'oklab';
}
}
});
33 changes: 33 additions & 0 deletions src/io/oklab/oklab2rgb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { unpack } = require('../../utils');
const { pow, sign } = Math;

/*
* L* [0..100]
* a [-100..100]
* b [-100..100]
*/
const oklab2rgb = (...args) => {
args = unpack(args, 'lab');
const [L, a, b] = args;

const l = pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
const m = pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
const s = pow(L - 0.0894841775 * a - 1.291485548 * b, 3);

return [
255 * lrgb2rgb(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
255 * lrgb2rgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
255 * lrgb2rgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
args.length > 3 ? args[3] : 1
];
};

module.exports = oklab2rgb;

function lrgb2rgb(c) {
const abs = Math.abs(c);
if (abs > 0.0031308) {
return (sign(c) || 1) * (1.055 * pow(abs, 1 / 2.4) - 0.055);
}
return c * 12.92;
}
28 changes: 28 additions & 0 deletions src/io/oklab/rgb2oklab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { unpack } = require('../../utils');
const { cbrt, pow, sign } = Math;

const rgb2oklab = (...args) => {
// OKLab color space implementation taken from
// https://bottosson.github.io/posts/oklab/
const [r, g, b] = unpack(args, 'rgb');
const [lr, lg, lb] = [rgb2lrgb(r / 255), rgb2lrgb(g / 255), rgb2lrgb(b / 255)];
const l = cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
const m = cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
const s = cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);

return [
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
];
};

module.exports = rgb2oklab;

function rgb2lrgb(c) {
const abs = Math.abs(c);
if (abs < 0.04045) {
return c / 12.92;
}
return (sign(c) || 1) * pow((abs + 0.055) / 1.055, 2.4);
}
24 changes: 24 additions & 0 deletions src/io/oklch/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { unpack, type } = require('../../utils');
const chroma = require('../../chroma');
const Color = require('../../Color');
const input = require('../input');

const rgb2oklch = require('./rgb2oklch');

Color.prototype.oklch = function () {
return rgb2oklch(this._rgb);
};

chroma.oklch = (...args) => new Color(...args, 'oklch');

input.format.oklch = require('./oklch2rgb');

input.autodetect.push({
p: 3,
test: (...args) => {
args = unpack(args, 'oklch');
if (type(args) === 'array' && args.length === 3) {
return 'oklch';
}
}
});
13 changes: 13 additions & 0 deletions src/io/oklch/oklch2rgb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { unpack } = require('../../utils');
const lch2lab = require('../lch/lch2lab');
const oklab2rgb = require('../oklab/oklab2rgb');

const oklch2rgb = (...args) => {
args = unpack(args, 'lch');
const [l, c, h] = args;
const [L, a, b_] = lch2lab(l, c, h);
const [r, g, b] = oklab2rgb(L, a, b_);
return [r, g, b, args.length > 3 ? args[3] : 1];
};

module.exports = oklch2rgb;
11 changes: 11 additions & 0 deletions src/io/oklch/rgb2oklch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { unpack } = require('../../utils');
const rgb2oklab = require('../oklab/rgb2oklab');
const lab2lch = require('../lch/lab2lch');

const rgb2oklch = (...args) => {
const [r, g, b] = unpack(args, 'rgb');
const [l, a, b_] = rgb2oklab(r, g, b);
return lab2lch(l, a, b_);
};

module.exports = rgb2oklch;
25 changes: 15 additions & 10 deletions test/converters.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

require('es6-shim');

const vows = require('vows');
Expand All @@ -11,32 +10,38 @@ for (let k in chroma.colors) {
test.addBatch({
k: {
topic: chroma.colors[k],
'to hsl and back': function(t) {
'to hsl and back': function (t) {
assert.equal(chroma.hsl(chroma(t).hsl()).hex(), t);
},
'to cmyk and back': function(t) {
'to cmyk and back': function (t) {
assert.equal(chroma.cmyk(chroma(t).cmyk()).hex(), t);
},
'to css and back': function(t) {
'to css and back': function (t) {
assert.equal(chroma.css(chroma(t).css()).hex(), t);
},
'to hsi and back': function(t) {
'to hsi and back': function (t) {
assert.equal(chroma.hsi(chroma(t).hsi()).hex(), t);
},
'to hsv and back': function(t) {
'to hsv and back': function (t) {
assert.equal(chroma.hsv(chroma(t).hsv()).hex(), t);
},
'to lab and back': function(t) {
'to lab and back': function (t) {
assert.equal(chroma.lab(chroma(t).lab()).hex(), t);
},
'to lch and back': function(t) {
'to oklab and back': function (t) {
assert.equal(chroma.oklab(chroma(t).oklab()).hex(), t);
},
'to lch and back': function (t) {
assert.equal(chroma.lch(chroma(t).lch()).hex(), t);
},
'to num and back': function(t) {
'to oklch and back': function (t) {
assert.equal(chroma.oklch(chroma(t).oklch()).hex(), t);
},
'to num and back': function (t) {
assert.equal(chroma.num(chroma(t).num()).hex(), t);
}
}
});
}

test["export"](module);
test['export'](module);
Loading

0 comments on commit a8939a8

Please sign in to comment.