Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion chroma-light.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
};

chroma.Color = Color_1;
chroma.version = '2.1.0';
chroma.version = '2.1.1';

var chroma_1 = chroma;

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

Large diffs are not rendered by default.

73 changes: 47 additions & 26 deletions chroma.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
};

chroma.Color = Color_1;
chroma.version = '2.1.0';
chroma.version = '2.1.1';

var chroma_1 = chroma;

Expand Down Expand Up @@ -2998,17 +2998,29 @@
};

var sqrt$4 = Math.sqrt;
var pow$8 = Math.pow;
var min$2 = Math.min;
var max$2 = Math.max;
var atan2$2 = Math.atan2;
var abs$1 = Math.abs;
var cos$4 = Math.cos;
var sin$3 = Math.sin;
var exp = Math.exp;
var PI$2 = Math.PI;

var deltaE = function(a, b, L, C) {
if ( L === void 0 ) L=1;
if ( C === void 0 ) C=1;
var deltaE = function(a, b, Kl, Kc, Kh) {
if ( Kl === void 0 ) Kl=1;
if ( Kc === void 0 ) Kc=1;
if ( Kh === void 0 ) Kh=1;

// Delta E (CMC)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
// Delta E (CIE 2000)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
var rad2deg = function(rad) {
return 360 * rad / (2 * PI$2);
};
var deg2rad = function(deg) {
return (2 * PI$2 * deg) / 360;
};
a = new Color_1(a);
b = new Color_1(b);
var ref = Array.from(a.lab());
Expand All @@ -3019,26 +3031,35 @@
var L2 = ref$1[0];
var a2 = ref$1[1];
var b2 = ref$1[2];
var c1 = sqrt$4((a1 * a1) + (b1 * b1));
var c2 = sqrt$4((a2 * a2) + (b2 * b2));
var sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + (0.01765 * L1));
var sc = ((0.0638 * c1) / (1.0 + (0.0131 * c1))) + 0.638;
var h1 = c1 < 0.000001 ? 0.0 : (atan2$2(b1, a1) * 180.0) / PI$2;
while (h1 < 0) { h1 += 360; }
while (h1 >= 360) { h1 -= 360; }
var t = (h1 >= 164.0) && (h1 <= 345.0) ? (0.56 + abs$1(0.2 * cos$4((PI$2 * (h1 + 168.0)) / 180.0))) : (0.36 + abs$1(0.4 * cos$4((PI$2 * (h1 + 35.0)) / 180.0)));
var c4 = c1 * c1 * c1 * c1;
var f = sqrt$4(c4 / (c4 + 1900.0));
var sh = sc * (((f * t) + 1.0) - f);
var delL = L1 - L2;
var delC = c1 - c2;
var delA = a1 - a2;
var delB = b1 - b2;
var dH2 = ((delA * delA) + (delB * delB)) - (delC * delC);
var v1 = delL / (L * sl);
var v2 = delC / (C * sc);
var v3 = sh;
return sqrt$4((v1 * v1) + (v2 * v2) + (dH2 / (v3 * v3)));
var avgL = (L1 + L2)/2;
var C1 = sqrt$4(pow$8(a1, 2) + pow$8(b1, 2));
var C2 = sqrt$4(pow$8(a2, 2) + pow$8(b2, 2));
var avgC = (C1 + C2)/2;
var G = 0.5*(1-sqrt$4(pow$8(avgC, 7)/(pow$8(avgC, 7) + pow$8(25, 7))));
var a1p = a1*(1+G);
var a2p = a2*(1+G);
var C1p = sqrt$4(pow$8(a1p, 2) + pow$8(b1, 2));
var C2p = sqrt$4(pow$8(a2p, 2) + pow$8(b2, 2));
var avgCp = (C1p + C2p)/2;
var arctan1 = rad2deg(atan2$2(b1, a1p));
var arctan2 = rad2deg(atan2$2(b2, a2p));
var h1p = arctan1 >= 0 ? arctan1 : arctan1 + 360;
var h2p = arctan2 >= 0 ? arctan2 : arctan2 + 360;
var avgHp = abs$1(h1p - h2p) > 180 ? (h1p + h2p + 360)/2 : (h1p + h2p)/2;
var T = 1 - 0.17*cos$4(deg2rad(avgHp - 30)) + 0.24*cos$4(deg2rad(2*avgHp)) + 0.32*cos$4(deg2rad(3*avgHp + 6)) - 0.2*cos$4(deg2rad(4*avgHp - 63));
var deltaHp = h2p - h1p;
deltaHp = abs$1(deltaHp) <= 180 ? deltaHp : h2p <= h1p ? deltaHp + 360 : deltaHp - 360;
deltaHp = 2*sqrt$4(C1p*C2p)*sin$3(deg2rad(deltaHp)/2);
var deltaL = L2 - L1;
var deltaCp = C2p - C1p;
var sl = 1 + (0.015*pow$8(avgL - 50, 2))/sqrt$4(20 + pow$8(avgL - 50, 2));
var sc = 1 + 0.045*avgCp;
var sh = 1 + 0.015*avgCp*T;
var deltaTheta = 30*exp(-pow$8((avgHp - 275)/25, 2));
var Rc = 2*sqrt$4(pow$8(avgCp, 7)/(pow$8(avgCp, 7) + pow$8(25, 7)));
var Rt = -Rc*sin$3(2*deg2rad(deltaTheta));
var result = sqrt$4(pow$8(deltaL/(Kl*sl), 2) + pow$8(deltaCp/(Kc*sc), 2) + pow$8(deltaHp/(Kh*sh), 2) + Rt*(deltaCp/(Kc*sc))*(deltaHp/(Kh*sh)));
return max$2(0, min$2(100, result));
};

// simple Euclidean distance
Expand Down
2 changes: 1 addition & 1 deletion chroma.min.js

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,15 @@ <h4 id="-color1-color2-mode-lab-">(color1, color2, mode=&#39;lab&#39;)</h4>
chroma.distance(&#39;#fff&#39;, &#39;#f0f&#39;);
</code></pre>
<h3 id="chroma-deltae">chroma.deltaE</h3>
<h4 id="-reference-sample-l-1-c-1-">(reference, sample, L=1, C=1)</h4>
<p>Computes <a href="https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_.281984.29">color difference</a> as developed by the Colour Measurement Committee of the Society of Dyers and Colourists (CMC) in 1984. The implementation is adapted from <a href="https://web.archive.org/web/20160306044036/http://www.brucelindbloom.com/javascript/ColorDiff.js">Bruce Lindbloom</a>. The parameters L and C are weighting factors for lightness and chromaticity.</p>
<pre><code class="lang-js">chroma.deltaE(&#39;#ededee&#39;, &#39;#edeeed&#39;);
<h4 id="-color1-color2-kl-1-kc-1-kh-1-">(color1, color2, Kl=1, Kc=1, Kh=1)</h4>
<p>Computes <a href="https://en.wikipedia.org/wiki/Color_difference#CIEDE2000">color difference</a> as developed by the International Commission on Illumination (CIE) in 2000. The implementation is based on the formula from <a href="http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html">Bruce Lindbloom</a>. Resulting values range from 0 (no difference) to 100 (maximum difference), and are a metric for how the human eye percieves color difference. The optional parameters Kl, Kc, and Kh may be used to adjust weightings of lightness, chroma, and hue.</p>
<pre><code class="lang-js">chroma.deltaE(&#39;#ededee&#39;, &#39;#ededee&#39;);
chroma.deltaE(&#39;#ededee&#39;, &#39;#edeeed&#39;);
chroma.deltaE(&#39;#ececee&#39;, &#39;#eceeec&#39;);
chroma.deltaE(&#39;#e9e9ee&#39;, &#39;#e9eee9&#39;);
chroma.deltaE(&#39;#e4e4ee&#39;, &#39;#e4eee4&#39;);
chroma.deltaE(&#39;#e0e0ee&#39;, &#39;#e0eee0&#39;);
chroma.deltaE(&#39;#000000&#39;, &#39;#ffffff&#39;);

</code></pre>
<h3 id="chroma-brewer">chroma.brewer</h3>
Expand Down
2 changes: 1 addition & 1 deletion docs/libs/chroma-light.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
};

chroma.Color = Color_1;
chroma.version = '2.1.0';
chroma.version = '2.1.1';

var chroma_1 = chroma;

Expand Down
2 changes: 1 addition & 1 deletion docs/libs/chroma-light.min.js

Large diffs are not rendered by default.

73 changes: 47 additions & 26 deletions docs/libs/chroma.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@
};

chroma.Color = Color_1;
chroma.version = '2.1.0';
chroma.version = '2.1.1';

var chroma_1 = chroma;

Expand Down Expand Up @@ -2998,17 +2998,29 @@
};

var sqrt$4 = Math.sqrt;
var pow$8 = Math.pow;
var min$2 = Math.min;
var max$2 = Math.max;
var atan2$2 = Math.atan2;
var abs$1 = Math.abs;
var cos$4 = Math.cos;
var sin$3 = Math.sin;
var exp = Math.exp;
var PI$2 = Math.PI;

var deltaE = function(a, b, L, C) {
if ( L === void 0 ) L=1;
if ( C === void 0 ) C=1;
var deltaE = function(a, b, Kl, Kc, Kh) {
if ( Kl === void 0 ) Kl=1;
if ( Kc === void 0 ) Kc=1;
if ( Kh === void 0 ) Kh=1;

// Delta E (CMC)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
// Delta E (CIE 2000)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
var rad2deg = function(rad) {
return 360 * rad / (2 * PI$2);
};
var deg2rad = function(deg) {
return (2 * PI$2 * deg) / 360;
};
a = new Color_1(a);
b = new Color_1(b);
var ref = Array.from(a.lab());
Expand All @@ -3019,26 +3031,35 @@
var L2 = ref$1[0];
var a2 = ref$1[1];
var b2 = ref$1[2];
var c1 = sqrt$4((a1 * a1) + (b1 * b1));
var c2 = sqrt$4((a2 * a2) + (b2 * b2));
var sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + (0.01765 * L1));
var sc = ((0.0638 * c1) / (1.0 + (0.0131 * c1))) + 0.638;
var h1 = c1 < 0.000001 ? 0.0 : (atan2$2(b1, a1) * 180.0) / PI$2;
while (h1 < 0) { h1 += 360; }
while (h1 >= 360) { h1 -= 360; }
var t = (h1 >= 164.0) && (h1 <= 345.0) ? (0.56 + abs$1(0.2 * cos$4((PI$2 * (h1 + 168.0)) / 180.0))) : (0.36 + abs$1(0.4 * cos$4((PI$2 * (h1 + 35.0)) / 180.0)));
var c4 = c1 * c1 * c1 * c1;
var f = sqrt$4(c4 / (c4 + 1900.0));
var sh = sc * (((f * t) + 1.0) - f);
var delL = L1 - L2;
var delC = c1 - c2;
var delA = a1 - a2;
var delB = b1 - b2;
var dH2 = ((delA * delA) + (delB * delB)) - (delC * delC);
var v1 = delL / (L * sl);
var v2 = delC / (C * sc);
var v3 = sh;
return sqrt$4((v1 * v1) + (v2 * v2) + (dH2 / (v3 * v3)));
var avgL = (L1 + L2)/2;
var C1 = sqrt$4(pow$8(a1, 2) + pow$8(b1, 2));
var C2 = sqrt$4(pow$8(a2, 2) + pow$8(b2, 2));
var avgC = (C1 + C2)/2;
var G = 0.5*(1-sqrt$4(pow$8(avgC, 7)/(pow$8(avgC, 7) + pow$8(25, 7))));
var a1p = a1*(1+G);
var a2p = a2*(1+G);
var C1p = sqrt$4(pow$8(a1p, 2) + pow$8(b1, 2));
var C2p = sqrt$4(pow$8(a2p, 2) + pow$8(b2, 2));
var avgCp = (C1p + C2p)/2;
var arctan1 = rad2deg(atan2$2(b1, a1p));
var arctan2 = rad2deg(atan2$2(b2, a2p));
var h1p = arctan1 >= 0 ? arctan1 : arctan1 + 360;
var h2p = arctan2 >= 0 ? arctan2 : arctan2 + 360;
var avgHp = abs$1(h1p - h2p) > 180 ? (h1p + h2p + 360)/2 : (h1p + h2p)/2;
var T = 1 - 0.17*cos$4(deg2rad(avgHp - 30)) + 0.24*cos$4(deg2rad(2*avgHp)) + 0.32*cos$4(deg2rad(3*avgHp + 6)) - 0.2*cos$4(deg2rad(4*avgHp - 63));
var deltaHp = h2p - h1p;
deltaHp = abs$1(deltaHp) <= 180 ? deltaHp : h2p <= h1p ? deltaHp + 360 : deltaHp - 360;
deltaHp = 2*sqrt$4(C1p*C2p)*sin$3(deg2rad(deltaHp)/2);
var deltaL = L2 - L1;
var deltaCp = C2p - C1p;
var sl = 1 + (0.015*pow$8(avgL - 50, 2))/sqrt$4(20 + pow$8(avgL - 50, 2));
var sc = 1 + 0.045*avgCp;
var sh = 1 + 0.015*avgCp*T;
var deltaTheta = 30*exp(-pow$8((avgHp - 275)/25, 2));
var Rc = 2*sqrt$4(pow$8(avgCp, 7)/(pow$8(avgCp, 7) + pow$8(25, 7)));
var Rt = -Rc*sin$3(2*deg2rad(deltaTheta));
var result = sqrt$4(pow$8(deltaL/(Kl*sl), 2) + pow$8(deltaCp/(Kc*sc), 2) + pow$8(deltaHp/(Kh*sh), 2) + Rt*(deltaCp/(Kc*sc))*(deltaHp/(Kh*sh)));
return max$2(0, min$2(100, result));
};

// simple Euclidean distance
Expand Down
2 changes: 1 addition & 1 deletion docs/libs/chroma.min.js

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,18 @@ chroma.distance('#fff', '#f0f');
```

### chroma.deltaE
#### (reference, sample, L=1, C=1)
#### (color1, color2, Kl=1, Kc=1, Kh=1)

Computes [color difference](https://en.wikipedia.org/wiki/Color_difference#CMC_l:c_.281984.29) as developed by the Colour Measurement Committee of the Society of Dyers and Colourists (CMC) in 1984. The implementation is adapted from [Bruce Lindbloom](https://web.archive.org/web/20160306044036/http://www.brucelindbloom.com/javascript/ColorDiff.js). The parameters L and C are weighting factors for lightness and chromaticity.
Computes [color difference](https://en.wikipedia.org/wiki/Color_difference#CIEDE2000) as developed by the International Commission on Illumination (CIE) in 2000. The implementation is based on the formula from [Bruce Lindbloom](http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html). Resulting values range from 0 (no difference) to 100 (maximum difference), and are a metric for how the human eye percieves color difference. The optional parameters Kl, Kc, and Kh may be used to adjust weightings of lightness, chroma, and hue.

```js
chroma.deltaE('#ededee', '#ededee');
chroma.deltaE('#ededee', '#edeeed');
chroma.deltaE('#ececee', '#eceeec');
chroma.deltaE('#e9e9ee', '#e9eee9');
chroma.deltaE('#e4e4ee', '#e4eee4');
chroma.deltaE('#e0e0ee', '#e0eee0');
chroma.deltaE('#000000', '#ffffff');

```

Expand Down
64 changes: 39 additions & 25 deletions src/utils/delta-e.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
const Color = require('../Color');
const {sqrt, atan2, abs, cos, PI} = Math;
const {sqrt, pow, min, max, atan2, abs, cos, sin, exp, PI} = Math;

module.exports = function(a, b, L=1, C=1) {
// Delta E (CMC)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
module.exports = function(a, b, Kl=1, Kc=1, Kh=1) {
// Delta E (CIE 2000)
// see http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
var rad2deg = function(rad) {
return 360 * rad / (2 * PI);
};
var deg2rad = function(deg) {
return (2 * PI * deg) / 360;
};
a = new Color(a);
b = new Color(b);
const [L1,a1,b1] = Array.from(a.lab());
const [L2,a2,b2] = Array.from(b.lab());
const c1 = sqrt((a1 * a1) + (b1 * b1));
const c2 = sqrt((a2 * a2) + (b2 * b2));
const sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + (0.01765 * L1));
const sc = ((0.0638 * c1) / (1.0 + (0.0131 * c1))) + 0.638;
let h1 = c1 < 0.000001 ? 0.0 : (atan2(b1, a1) * 180.0) / PI;
while (h1 < 0) { h1 += 360; }
while (h1 >= 360) { h1 -= 360; }
const t = (h1 >= 164.0) && (h1 <= 345.0) ? (0.56 + abs(0.2 * cos((PI * (h1 + 168.0)) / 180.0))) : (0.36 + abs(0.4 * cos((PI * (h1 + 35.0)) / 180.0)));
const c4 = c1 * c1 * c1 * c1;
const f = sqrt(c4 / (c4 + 1900.0));
const sh = sc * (((f * t) + 1.0) - f);
const delL = L1 - L2;
const delC = c1 - c2;
const delA = a1 - a2;
const delB = b1 - b2;
const dH2 = ((delA * delA) + (delB * delB)) - (delC * delC);
const v1 = delL / (L * sl);
const v2 = delC / (C * sc);
const v3 = sh;
return sqrt((v1 * v1) + (v2 * v2) + (dH2 / (v3 * v3)));
const avgL = (L1 + L2)/2;
const C1 = sqrt(pow(a1, 2) + pow(b1, 2));
const C2 = sqrt(pow(a2, 2) + pow(b2, 2));
const avgC = (C1 + C2)/2;
const G = 0.5*(1-sqrt(pow(avgC, 7)/(pow(avgC, 7) + pow(25, 7))));
const a1p = a1*(1+G);
const a2p = a2*(1+G);
const C1p = sqrt(pow(a1p, 2) + pow(b1, 2));
const C2p = sqrt(pow(a2p, 2) + pow(b2, 2));
const avgCp = (C1p + C2p)/2;
const arctan1 = rad2deg(atan2(b1, a1p));
const arctan2 = rad2deg(atan2(b2, a2p));
const h1p = arctan1 >= 0 ? arctan1 : arctan1 + 360;
const h2p = arctan2 >= 0 ? arctan2 : arctan2 + 360;
const avgHp = abs(h1p - h2p) > 180 ? (h1p + h2p + 360)/2 : (h1p + h2p)/2;
const T = 1 - 0.17*cos(deg2rad(avgHp - 30)) + 0.24*cos(deg2rad(2*avgHp)) + 0.32*cos(deg2rad(3*avgHp + 6)) - 0.2*cos(deg2rad(4*avgHp - 63));
let deltaHp = h2p - h1p;
deltaHp = abs(deltaHp) <= 180 ? deltaHp : h2p <= h1p ? deltaHp + 360 : deltaHp - 360;
deltaHp = 2*sqrt(C1p*C2p)*sin(deg2rad(deltaHp)/2);
const deltaL = L2 - L1;
const deltaCp = C2p - C1p;
const sl = 1 + (0.015*pow(avgL - 50, 2))/sqrt(20 + pow(avgL - 50, 2));
const sc = 1 + 0.045*avgCp;
const sh = 1 + 0.015*avgCp*T;
const deltaTheta = 30*exp(-pow((avgHp - 275)/25, 2));
const Rc = 2*sqrt(pow(avgCp, 7)/(pow(avgCp, 7) + pow(25, 7)));
const Rt = -Rc*sin(2*deg2rad(deltaTheta));
const result = sqrt(pow(deltaL/(Kl*sl), 2) + pow(deltaCp/(Kc*sc), 2) + pow(deltaHp/(Kh*sh), 2) + Rt*(deltaCp/(Kc*sc))*(deltaHp/(Kh*sh)));
return max(0, min(100, result));
};


44 changes: 44 additions & 0 deletions test/delta-e.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const vows = require('vows')
const assert = require('assert');
require('es6-shim');

const deltaE = require('../src/utils/delta-e');

// due to floating-point arithmetic on different devices, differences in decimals may be found.
// Running http://www.brucelindbloom.com/index.html?ColorDifferenceCalc.html JS code locally
// on the same device as the delta-e code in this library will provide the exact same results.
const tests = {
nodifference: {in: [0x000000, 0x000000], out: 0},
maxdifference: {in: [0xFFFFFF, 0x000000], out: 100},
redgreen: {in: [0xff0000, 0x00ff00], out: 86.6082374535373},
greenred: {in: [0x00ff00, 0xff0000], out: 86.6082374535373},
beef: {in: [0x00beef, 0xbeef00], out: 56.75641476716213},
similar: {in: [0xededee, 0xedeeed], out: 1.3211081906645834},
similarish: {in: [0xececee, 0xeceeec], out: 2.601879624602976},
lesssimilar: {in: [0xe9e9ee, 0xe9eee9], out: 6.220878841368716},
lesssimilarish: {in: [0xe4e4ee, 0xe4eee4], out: 11.598175546813964},
notverysimilar: {in: [0xe0e0ee, 0xe0eee0], out: 15.391371803506503},
};

const batch = {};

Object.keys(tests).forEach(key => {
batch[`delta-e ${key}`] = {
topic: tests[key],
num(topic) {
// checks if result is within 1% of "out" value.
// This is done because results may be slightly
// off depending on device setup due to floating
// point arithmetic. If "out" is 0, and result was 0,
// avoid divide by zero and set to true.
let result = deltaE(topic.in[0], topic.in[1]);
let percent = Math.abs(result-topic.out)/topic.out;
assert.deepEqual((topic.out == 0 && result == 0) || percent < 0.01, true);
}
}
})

vows
.describe('Testing delta-e color delta (dE00)')
.addBatch(batch)
.export(module);