CIEDE2000 implementation in JavaScript
| Number of visits | 939 |
|---|---|
| Number of files viewed | 928 + 376 |
This page presents a reference implementation of the CIEDE2000 color difference formula in JavaScript. If you want to ensure perfect compatibility (to the tenth decimal place) with certain third-party implementations, it may be necessary to modify the comments in the source code; the following link automates this operation for you.
The ΔE2000 function in JavaScript
Letβs consider the more common and academic (Sharma, 2005) of the two formulations.
// This function written in JavaScript is not affiliated with the CIE (International Commission on Illumination),
// and is released into the public domain. It is provided "as is" without any warranty, express or implied.
// The classic CIE ΞE2000 implementation, which operates on two L*a*b* colors, and returns their difference.
// "l" ranges from 0 to 100, while "a" and "b" are unbounded and commonly clamped to the range of -128 to 127.
function ciede_2000(l_1, a_1, b_1, l_2, a_2, b_2, k_l, k_c, k_h, canonical) {
"use strict"
// Working in JavaScript with the CIEDE2000 color-difference formula.
// k_l, k_c, k_h are parametric factors to be adjusted according to
// different viewing parameters such as textures, backgrounds...
var n = (Math.sqrt(a_1 * a_1 + b_1 * b_1) + Math.sqrt(a_2 * a_2 + b_2 * b_2)) * 0.5;
n = n * n * n * n * n * n * n;
// A factor involving chroma raised to the power of 7 designed to make
// the influence of chroma on the total color difference more accurate.
n = 1.0 + 0.5 * (1.0 - Math.sqrt(n / (n + 6103515625.0)));
// Application of the chroma correction factor.
var c_1 = Math.sqrt(a_1 * a_1 * n * n + b_1 * b_1);
var c_2 = Math.sqrt(a_2 * a_2 * n * n + b_2 * b_2);
// atan2 is preferred over atan because it accurately computes the angle of
// a point (x, y) in all quadrants, handling the signs of both coordinates.
var h_1 = Math.atan2(b_1, a_1 * n), h_2 = Math.atan2(b_2, a_2 * n);
h_1 += 2.0 * Math.PI * (h_1 < 0.0);
h_2 += 2.0 * Math.PI * (h_2 < 0.0);
n = Math.abs(h_2 - h_1);
// Cross-implementation consistent rounding.
if (Math.PI - 1E-14 < n && n < Math.PI + 1E-14)
n = Math.PI;
// When the hue angles lie in different quadrants, the straightforward
// average can produce a mean that incorrectly suggests a hue angle in
// the wrong quadrant, the next lines handle this issue.
var h_m = (h_1 + h_2) * 0.5, h_d = (h_2 - h_1) * 0.5;
if (Math.PI < n) {
h_d += Math.PI;
if (canonical) // Sharmaβs implementation, OpenJDK, ...
h_m += h_m < Math.PI ? Math.PI : -Math.PI;
else // Lindbloomβs implementation, Netflixβs VMAF, ...
h_m += Math.PI;
}
var p = 36.0 * h_m - 55.0 * Math.PI;
n = (c_1 + c_2) * 0.5;
n = n * n * n * n * n * n * n;
// The hue rotation correction term is designed to account for the
// non-linear behavior of hue differences in the blue region.
var r_t = -2.0 * Math.sqrt(n / (n + 6103515625.0))
* Math.sin(Math.PI / 3.0 * Math.exp(p * p / (-25.0 * Math.PI * Math.PI)));
n = (l_1 + l_2) * 0.5;
n = (n - 50.0) * (n - 50.0);
// Lightness.
var l = (l_2 - l_1) / ((k_l || 1.0) * (1.0 + 0.015 * n / Math.sqrt(20.0 + n)));
// These coefficients adjust the impact of different harmonic
// components on the hue difference calculation.
var t = 1.0 + 0.24 * Math.sin(2.0 * h_m + Math.PI * 0.5)
+ 0.32 * Math.sin(3.0 * h_m + 8.0 * Math.PI / 15.0)
- 0.17 * Math.sin(h_m + Math.PI / 3.0)
- 0.20 * Math.sin(4.0 * h_m + 3.0 * Math.PI / 20.0);
n = c_1 + c_2;
// Hue.
var h = 2.0 * Math.sqrt(c_1 * c_2) * Math.sin(h_d) / ((k_h || 1.0) * (1.0 + 0.0075 * n * t));
// Chroma.
var c = (c_2 - c_1) / ((k_c || 1.0) * (1.0 + 0.0225 * n));
// Returning the square root ensures that dE00 accurately reflects the
// geometric distance in color space, which can range from 0 to around 185.
return Math.sqrt(l * l + h * h + c * c + c * h * r_t);
}
// GitHub Project : https://github.com/michel-leonard/ciede2000-color-matching
// Online Tests : https://michel-leonard.github.io/ciede2000-color-matching
// L1 = 54.8 a1 = 23.5 b1 = 2.0
// L2 = 53.3 a2 = 29.5 b2 = -2.2
// CIE ΞE00 = 4.1622955128 (Bruce Lindbloom, Netflixβs VMAF, ...)
// CIE ΞE00 = 4.1622807178 (Gaurav Sharma, OpenJDK, ...)
// Deviation between implementations β 1.5e-5
// See the source code comments for easy switching between these two widely used ΞE*00 implementation variants.Your performance test
k_l, k_c and k_h parameters
The parameters k_l, k_c and k_h in the CIEDE2000 formula are weighting factors applied to the brightness (ΞL*), chroma (ΞC*) and hue (ΞH*) components respectively. In the source code, they are defined as constants with a default value of 1, corresponding to the standard observation conditions laid down by the International Commission on Illumination (CIE). In practice, you might need to adjust these coefficients to reflect specific conditions: for example, k_l = 2 is sometimes used to give more weight to differences in brightness (a common occurrence in the textile industry), while k_c or k_h can be reduced to increase tolerance for variations in saturation or hue, depending on the requirements. Depending on the context, these coefficients typically range from 0.5 to 2.
Source code accuracy and reliability
The difference between Sharmaβs academic formulation and Lindbloomβs simplified formulation does not exceed Β±0.0003 on the final ΞE2000. This corresponds to the difference usually measured between two 32-bit implementations and is imperceptible to the human eye. Our 64-bit implementations, all consistent with each other, guarantee at least 10 correct decimal places, so the choice of one formulation over the other is a technical detail. The default formula on this page is the one most often presented in the community, it is slightly easier to vectorize.
How do you convert RGB colors to L*a*b*?
You will need to use the XYZ intermediate color space for the conversion, and if you need help, the source code is provided at the bottom of this page (using the D65 white point formalized in 1964).
CIELAB value ranges and interpretation of the ΞE2000
In the CIELAB color space, the L* component represents lightness and typically ranges from 0 (black) to 100 (white). The a* and b* components represent color axes: a* goes from green to red, while b* goes from blue to yellow. In practice, a* and b* values usually fall between -128 and +127, although they can slightly exceed these limits depending on the color conversion.
| Color 1 | Color 2 | Value of ΞE2000 |
|---|---|---|
| 1 | ||
| 2 | ||
| 3 |
| Color 1 | Color 2 | Value of ΞE2000 |
|---|---|---|
| 5 | ||
| 10 | ||
| 15 |
ΞE2000 (CIEDE2000) measures the perceived difference between two colors: 0 means the colors are identical, and higher values (up to around 185 in extreme cases) indicate a larger difference. For example, a ΞE2000 value around 5 means the colors are close, while a value around 15 means they are clearly different.
Example of use in JavaScript
// Define color 1 in L*a*b* space
const l1 = 43.2, a1 = 31.2, b1 = 4.1;
// Define color 2 in L*a*b* space
const l2 = 43.0, a2 = 35.5, b2 = -4.3;
// Calculate ΔE2000 color difference
const deltaE = ciede_2000(l1, a1, b1, l2, a2, b2);
console.log(deltaE);
// .................................................. This shows a ΔE2000 of 5.2895865658
// As explained in the comments, compliance with Gaurav Sharma would display 5.2895721943Test results
The driver, written in the C99 language and featuring 250 precise static tests, has proved that this JavaScript function is interoperable with the CIEDE2000 function available in other programming languages.
CIEDE2000 Verification Summary :
First Verified Line : 19.8,86.4,126,87,48.32,-70.5,86.66139766452334
Duration : 36.48 s
Successes : 10000000
Errors : 0
Average Delta E : 62.9463
Average Deviation : 6.0717893379802488e-15
Maximum Deviation : 2.2737367544323206e-13β‘ Comparing RGB and hexadecimal colors for the Web
Just 3KB β Simple. Fast. This compact JavaScript function accepts two colors in RGB or hexadecimal format and returns the CIE ΞE2000 color difference using the standard illuminant D65.
// This function written in JavaScript is not affiliated with the CIE (International Commission on Illumination),
// and is released into the public domain. It is provided "as is" without any warranty, express or implied.
// This function accepts both RGB and hexadecimal color formats and computes the color difference using the CIE ΔE2000 formula.
// Examples of use :
// - ciede_2000('#00F', '#483D8B')
// - ciede_2000('#483d8b', 75,0,130)
// - ciede_2000(75, 0, 130, '#00008b')
// - ciede_2000(0, 0, 139, 0, 0, 128)
// GitHub : https://github.com/michel-leonard/ciede2000-color-matching
;function ciede_2000(a,b,c,d,e,f){"use strict";var k_l=1.0,k_c=1.0,k_h=1.0,g,h,i,j,k,l,m,n,o,p,q,r,s=0.040448236277105097;if(typeof a=='string'){g=parseInt((a.length===4?a[0]+a[1]+a[1]+a[2]+a[2]+a[3]+a[3]:a).substring(1),16);if(typeof b=='string'){h=parseInt((b.length===4?b[0]+b[1]+b[1]+b[2]+b[2]+b[3]+b[3]:b).substring(1),16);d=h>>16&0xff;e=h>>8&0xff;f=h&0xff;}else{f=d;e=c;d=b;}a=g>>16&0xff;b=g>>8&0xff;c=g&0xff}else if(typeof d=='string'){g=parseInt((d.length===4?d[0]+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]:d).substring(1),16);d=g>>16&0xff;e=g>>8&0xff;f=g&0xff;}a/=255.0;b/=255.0;c/=255.0;a=a<s?a/12.92:Math.pow((a+0.055)/1.055,2.4);b=b<s?b/12.92:Math.pow((b+0.055)/1.055,2.4);c=c<s?c/12.92:Math.pow((c+0.055)/1.055,2.4);g=a*41.24564390896921145+b*35.75760776439090507+c*18.04374830853290341;h=a*21.26728514056222474+b*71.51521552878181013+c*7.21749933075596513;i=a*1.93338955823293176+b*11.91919550818385936+c*95.03040770337479886;a=g/95.047;b=h/100.0;c=i/108.883;a=a<216.0/24389.0?((841.0/108.0)*a)+(4.0/29.0):Math.cbrt(a);b=b<216.0/24389.0?((841.0/108.0)*b)+(4.0/29.0):Math.cbrt(b);c=c<216.0/24389.0?((841.0/108.0)*c)+(4.0/29.0):Math.cbrt(c);g=(116.0*b)-16.0;h=500.0*(a-b);i=200.0*(b-c);d/=255.0;e/=255.0;f/=255.0;d=d<s?d/12.92:Math.pow((d+0.055)/1.055,2.4);e=e<s?e/12.92:Math.pow((e+0.055)/1.055,2.4);f=f<s?f/12.92:Math.pow((f+0.055)/1.055,2.4);j=d*41.24564390896921145+e*35.75760776439090507+f*18.04374830853290341;k=d*21.26728514056222474+e*71.51521552878181013+f*7.21749933075596513;l=d*1.93338955823293176+e*11.91919550818385936+f*95.03040770337479886;d=j/95.047;e=k/100.0;f=l/108.883;d=d<216.0/24389.0?((841.0/108.0)*d)+(4.0/29.0):Math.cbrt(d);e=e<216.0/24389.0?((841.0/108.0)*e)+(4.0/29.0):Math.cbrt(e);f=f<216.0/24389.0?((841.0/108.0)*f)+(4.0/29.0):Math.cbrt(f);j=(116.0*e)-16.0;k=500.0*(d-e);l=200.0*(e-f);d=(Math.sqrt(h*h+i*i)+Math.sqrt(k*k+l*l))*0.5;d=d*d*d*d*d*d*d;d=1.0+0.5*(1.0-Math.sqrt(d/(d+6103515625.0)));m=Math.sqrt(h*h*d*d+i*i);n=Math.sqrt(k*k*d*d+l*l);o=Math.atan2(i,h*d);p=Math.atan2(l,k*d);o+=2.0*Math.PI*(o<0.0);p+=2.0*Math.PI*(p<0.0);d=Math.abs(p-o);if(Math.PI-1E-14<d&&d<Math.PI+1E-14)d=Math.PI;q=(o+p)*0.5;r=(p-o)*0.5;if(Math.PI<d){r+=Math.PI;q+=Math.PI;}e=36.0*q-55.0*Math.PI;d=(m+n)*0.5;d=d*d*d*d*d*d*d;s=-2.0*Math.sqrt(d/(d+6103515625.0))*Math.sin(Math.PI/3.0*Math.exp(e*e/(-25.0*Math.PI*Math.PI)));d=(g+j)*0.5;d=(d-50.0)*(d-50.0);f=(j-g)/(k_l*(1.0+0.015*d/Math.sqrt(20.0+d)));a=1.0+0.24*Math.sin(2.0*q+Math.PI*0.5)+0.32*Math.sin(3.0*q+8.0*Math.PI/15.0)-0.17*Math.sin(q+Math.PI/3.0)-0.20*Math.sin(4.0*q+3.0*Math.PI/20.0);d=m+n;b=2.0*Math.sqrt(m*n)*Math.sin(r)/(k_h*(1.0+0.0075*d*a));c=(n-m)/(k_c*(1.0+0.0225*d));return Math.sqrt(f*f+b*b+c*c+c*b*s);};
Files to download
A file below supports arbitrary precision calculations in JavaScript (useful if youβre dealing with ΞE2000 in metrology). Feel free to use these files provided by Michel, even for commercial purposes.
| File | Size | Number of clicks |
|---|---|---|
| ciede-2000.js | 4 KB | 132 |
| ciede-2000-driver.js | 7 KB | 107 |
| ciede-2000-multiprecision.js | 37 KB | 111 |
| ciede-2000-random.js | 6 KB | 96 |
| compare-hex-colors.js | 8 KB | 97 |
| compare-rgb-colors.js | 8 KB | 101 |
| test-js-multiprecision.yml | 7 KB | 74 |
| test-js.yml | 3 KB | 70 |
| vs-chroma.yml | 4 KB | 71 |
| vs-npm-delta-e.yml | 4 KB | 69 |
| reference-dataset.txt | 4 KB | 376 |
| Click on js.zip to receive all these files in an archive. | ||
Community
What do you think of this source code or CIEDE2000? Your opinion is important to us! The guestbook already contains 9 messages - including 1 in English. Take a look and share your opinion.