This source code is not affiliated with the CIE (International Commission on Illumination), has not been validated by it, and is released into the public domain. It is provided "as is" without any warranty.
Choose your programming language, and it’s ready to be deployed in production environments :
- Compliance with Bruce Lindbloom’s (Netflix’s VMAF, ...) implementation to 10-12 tested on billions of cases.
- Compliance with Gaurav Sharma’s (OpenJDK, ...) implementation to 10-12 tested on billions of cases.
This document describes the CIE ΔE2000 v1.0.0 functions, all accurate to 10 decimal places, released in March 2025.
Overview : The formula is an internationally recognized measure for comparing two colors in the CIELAB color space. It improves on its CIE94 predecessor by incorporating new adjustments, enabling more accurate color comparisons.
Purpose : The ΔE00 algorithm, which is notorious for exhibiting discontinuities, is designed to reflect the difficulty of distinguishing between two colors, rather than being a locally smooth and uniform Euclidean metric.
Nomenclature : The terms CIE2000, CIEDE2000, ΔE00, ΔE*00, and ΔE2000 are used interchangeably in this repository.
Engineered for precision and portability, this reference implementation of the DeltaE 2000 formula achieves color difference calculations without dependencies, and supports a wide range of programming languages :
Paradigm / Ecosystem | Programming Languages |
---|---|
Low-Level / Systems | Ada … C … C++ … D … Go … Rust … Swift … Zig |
General Purpose / Scripting | Dart … JavaScript … Lua … PHP … Perl … PowerShell … Python Ruby … TCL … TypeScript … Wren |
Functional / Declarative | Haskell … Haxe … Nim … Prolog … Racket |
VM-Based | C# … Elixir … F# … Java … Kotlin … Scala … VBA |
Scientific / Analytical | bc … Excel … Julia … MATLAB … Mathematica … R |
Legacy / Query | ASP … AWK … Fortran … Pascal … SQL |
Quality Assurance : These implementations are validated to an accuracy of 10-10 against calculations performed by Netflix’s Emmy Awarded VMAF library and the trusted npm/chroma-js, calculations performed by Fogra (a reputed institution in the graphics and printing industry), and, for example, also with the California Institute of Technology’s ΔE00 calculations — guaranteeing their scientific rigor and practical reliability in the face of the zero errors observed.
Note : Each source contains the definition of the two main variants (both correct in practice) of the ΔE*00 algorithm. For example, OpenJDK uses a function named cie00
, which corresponds to the line currently commented on.
Explain precisely what differs in the source code between the two main variants !
Online, we can see a live difference of ±0.0002 in the ΔE2000 between the two variants. Turning to the technical side, the modification, here in Java language, to switch from one variant of the ΔE*00 algorithm to the other is as follows.
- h_m += Math.PI; // Bruce Lindbloom, Netflix’s VMAF, ...
+ h_m += h_m < Math.PI ? Math.PI : -Math.PI; // Gaurav Sharma, OpenJDK, ...
✅ Applying this predefined modification results in ΔE2000 compliance with Gaurav Sharma with a tolerance of 10-12.
The current variant is suitable for production workflows, the other one is clearer for metrological applications. Historically, these two variants are the result of a coding simplification inherited from the first implementations. The choice to prefer one over the other depends on the desired interoperability, but they differ by only ±0.0003 (same as the typical 32-bit float precision), which underlines the sufficiency of the 32-bit
ciede_2000
functions.
Just 3kb - Simple. Fast. Easy to use. This JavaScript function accepts both RGB and hexadecimal color formats and computes the color difference using the CIE ΔE 2000 formula and 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.
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);}
For reference, the biggest color difference between lime green (#9f0) and dark navy (#006) yields a ΔE00 value of 119. Lower values indicate more similar colors, making it easy to determine the closest match to a given color.
These examples with CDN show how to compare different combinations of RGB and Hex color formats :
// black compared to white
var delta_e = ciede_2000('#000', '#FFF')
// ΔE2000 = 100.0 — classical reference
// darkslateblue compared to indigo
var delta_e = ciede_2000('#483d8b', 75,0,130)
// ΔE2000 ≈ 12.19 — distinct difference
// indigo compared to darkblue
var delta_e = ciede_2000(75, 0, 130, '#00008b')
// ΔE2000 ≈ 7.72 — moderate difference
// darkblue compared to navy
var delta_e = ciede_2000(0, 0, 139, 0, 0, 128)
// ΔE2000 ≈ 1.56 — slight difference
Master colors with ease right now with this ΔE2000 JavaScript function that accepts RGB and hexadecimal colors, and performs around 1,000,000 comparisons in ~500 ms on modern browsers, making it suitable for real-time tasks.
- Precision: Medical image processing (shade differences between healthy and diseased tissues).
- Efficiency: Machine vision (color-based quality control).
- Everywhere: Leading the way in innovative color science and multi-language integration solutions.
The textile industry usually adjusts k_l
to 2.0
in the source code for its needs.
Based on our JavaScript implementation, you can see the CIEDE2000 color difference formula in action here :
- Generators :
- A discovery generator for quick, small-scale testing and exploration.
- A large-scale generator and validator used to test new implementations.
- A ΔE-to-RGB pairs generator that shows RGB color pairs sharing a given ΔE2000 value (±0.05).
- Calculators:
- A simple calculator that shows in 10 steps how to compute ΔE2000 between two L*a*b* colors.
- A pickers-based calculator for computing ΔE2000 between two RGB or Hex colors.
- Other :
- A tool that identify the name of the selected color based on a picture.
To ensure accurate color evaluation, extensive tests involving hundreds of billions of colors were carried out, including precise comparisons with major libraries. The consistency of the ΔE*00 function across languages is fundamental here.
- Test cases : Each programming language was tested on hundreds of millions of L*a*b* color pairs.
- Tolerance : All programming languages are capable of producing the correct results with a tolerance of
1e-10
. - Workflow : All programming languages have their own dedicated test involving 20M new colors every month.
In summary, the absolute value of the deviation in ΔE2000 between two implementations never exceeds 10-10.
Correctness : Today, the world’s leading companies rely on the CIE2000 formula, and are encouraged, along with software developers, to carry out tests that reveal the inconsistencies between different implementations.
To confirm its exceptional accuracy, the JavaScript implementation was subjected to an endurance test against an old and reliable reference. No deviation greater than 10-12 was detected in ΔE00s over the 80,000,000,000,000 random color pairs tested. Minor differences were attributed to degree-to-radian conversions in the reference calculations.
Finally, you've here the right implementation of ΔE2000, which completely eliminates degree/radian conversions.
The professional approach in software is to use radians for mathematical calculations, as angle conversions, while theoretically valid, result in a loss of precision due to rounding errors in floating-point numbers. Here, only radians are used, without conversion, but this can be a source of inconsistency for an external implementation.
For example, we rely on value > π
in radians, which is the same as value > 180
in degrees. Due to conversions, an implementation using degrees may obtain 180.00000000000003
, while its equivalent in radians would have given exactly π = 3.141592653589793
, leading to different branching and discrepancies in the calculated ΔE CIE2000.
In most environments, when a* and b* are both zero, atan2(0, 0)
correctly evaluates to 0
, following mathematical convention, as in JavaScript. However, some programming languages may instead throw an exception or return NaN
or NULL
, and so patches are in place to ensure totally reliable implementations. In addition, during automated tests, the C driver generates cases that confirm in each programming language that, although the result of atan2(0, 0)
differs from that of atan2(0, -0)
, this has no influence on the final CIE ΔE*00 color difference.
Shows ambiguous cases related to angular calculations where the color difference is unstable !
These discontinuities occur when the a1 / b1
equals a2 / b2
and the signs of a1
and a2
are opposite, then an infinitesimal variation in these components can produce a large variation in ΔE2000, as can be seen in the links.
Lower value | Upper value | Non-continuous change in ΔE2000 |
---|---|---|
0.69 | 0.693 | 0.5% |
1.39 | 1.404 | 1% |
2.79 | 2.85 | 2% |
5.6 | 5.8 | 4% |
11.3 | 12.3 | 9% |
16.1 | 43.2 | 168% |
22.7 | 52.4 | 131% |
66.6 | 125.6 | 89% |
90.5 | 154.6 | 71% |
Minor discrepancies can arise between programming languages, for instance, atan2(-49.2, -34.9)
evaluates to -2.1877696633888672
in Python and -2.1877696633888677
in JavaScript, while -2.187769663388867475...
is correct. Here, tolerated deviation for a cross-language exact color match is set to 1e-10
, linking sufficiency and achievability.
The formula ΔE2000 involves numerically sensitive calculations, particularly those related to angles and π. In 32-bit floating point, the rounding at each step can, when comparing two implementations, cause the conditional logic to move to a different branch of execution. Once in a different branch, the result may diverge, but this is more a natural consequence of the formula’s structure than a bug. These are not really errors, but rather expected discrepancies due to precision limits, which occur under specific conditions that are not usually encountered.
The original functions favor 64-bit precision, but 32-bit functions, which are also available, can give virtually identical results to 64-bit, while being faster with a lighter footprint. Let’s look at this with randomly generated L*a*b* values rounded to the nearest tenth (otherwise no "rare" deviation is noticed even when doing it with many more colors).
Condition | Common deviation (most cases) | Rare deviation (1 in ≥1M cases) |
---|---|---|
ΔE2000 < 5 |
0.00005 | 0.02 |
ΔE2000 < 10 |
0.00005 | 0.07 |
ΔE2000 < 15 |
0.00005 | 0.3 |
ΔE2000 < 20 |
0.00005 | 2.3 |
ΔE2000 < 40 |
0.00005 | 9.0 |
The exact conditions for triggering a "rare" deviation between 32 and 64 bits are unlikely to occur naturally, and are improbable in real data streams from sensors or color space conversions. This experiment, based on 100,000,000 color pairs meeting the condition, showed that 32-bit ciede_2000
functions are suitable for serious applications.
The bc and Julia programming languages have an example showing that they can perform the ΔE*00 calculations without precision limits, which can be useful for metrology applications or for checking other implementations.
Rounding L*a*b* components and ΔE 2000 to 4 decimal places can be a solution for realistic color comparisons.
Runtimes were recorded while calculating 100 million iterations of the color difference formula ΔE 2000.
Language | Duration (mm:ss) | Performance factor compared to C |
---|---|---|
C | 00:13 | Reference |
Kotlin | 00:15 | 1.09× slower |
VBA | 00:15 | 1.14× slower |
Lua | 00:15 | 1.15× slower |
Dart | 00:16 | 1.19× slower |
Rust | 00:17 | 1.28× slower |
Nim | 00:18 | 1.34× slower |
Go | 00:19 | 1.40× slower |
Haxe | 00:19 | 1.42× slower |
F# | 00:19 | 1.42× slower |
D | 00:21 | 1.54× slower |
Java | 00:23 | 1.76× slower |
Julia | 00:24 | 1.82× slower |
C++ | 00:24 | 1.83× slower |
JavaScript | 00:31 | 2.30× slower |
C# | 00:32 | 2.37× slower |
MATLAB | 00:33 | 2.49× slower |
Swift | 00:38 | 2.84× slower |
Fortran | 00:40 | 2.97× slower |
PHP | 00:44 | 3.32× slower |
Pascal | 00:49 | 3.67× slower |
Haskell | 01:08 | 5.09× slower |
Ruby | 03:21 | 15.11× slower |
Perl | 03:48 | 17.15× slower |
Python | 03:59 | 17.98× slower |
SQL | 05:39 | 25.45× slower |
AWK | 07:18 | 32.88× slower |
bc | 20:44:43 | 5612.28× slower |
Here are some examples of programming languages that could be used to expand the ciede_2000
function :
- OCaml
- Crystal
- V
To ensure consistency across implementations, please follow these guidelines :
- Base your implementation on an existing one, copy-pasting and adapting is encouraged.
- Validate correctness basically using the discovery generator, and formally using the large-scale generator :
- Generate 1,000,000 samples, or 10,000 if you encounter technical limitations.
- Verify that the computed ΔE 2000 values do not deviate by more than 1e-10 from reference values.
- Submit a pull request with your implementation.
To enhance your contribution, consider writing documentation, as done for other programming languages. Your source code, along with the others, will then be reviewed and made available in this public domain repository.
Note
If the atan2
function is not available to you, a polyfill is provided in the bc version.
Purchase the original CIE Technical Report 142-2001. This document, without which this repository would not exist, specifically presents and formalizes the ΔE 2000 formula, providing guidelines on how to implement it.
Here is the C99 source code from Michel Leonard to implement the CIE ΔE 2000 function :
// This function written in C 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.
#include <math.h>
// Expressly defining pi ensures that the code works on different platforms.
#ifndef M_PI
#define M_PI 3.14159265358979323846264338328
#endif
// 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.
static double ciede_2000(const double l_1, const double a_1, const double b_1, const double l_2, const double a_2, const double b_2) {
// Working in C 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...
const double k_l = 1.0;
const double k_c = 1.0;
const double k_h = 1.0;
double n = (sqrt(a_1 * a_1 + b_1 * b_1) + 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 - sqrt(n / (n + 6103515625.0)));
// Application of the chroma correction factor.
const double c_1 = sqrt(a_1 * a_1 * n * n + b_1 * b_1);
const double c_2 = 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.
double h_1 = atan2(b_1, a_1 * n);
double h_2 = atan2(b_2, a_2 * n);
h_1 += (h_1 < 0.0) * 2.0 * M_PI;
h_2 += (h_2 < 0.0) * 2.0 * M_PI;
n = fabs(h_2 - h_1);
// Cross-implementation consistent rounding.
if (M_PI - 1E-14 < n && n < M_PI + 1E-14)
n = M_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.
double h_m = (h_1 + h_2) * 0.5;
double h_d = (h_2 - h_1) * 0.5;
h_d += (M_PI < n) * ((0.0 < h_d) - (h_d <= 0.0)) * M_PI;
// 📜 Sharma’s formulation doesn’t use the next line, but the one after it,
// and these two variants differ by ±0.0003 on the final color differences.
h_m += (M_PI < n) * M_PI;
// h_m += (M_PI < n) * ((h_m < M_PI) - (M_PI <= h_m)) * M_PI;
const double p = 36.0 * h_m - 55.0 * M_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.
const double r_t = -2.0 * sqrt(n / (n + 6103515625.0))
* sin(M_PI / 3.0 * exp(p * p / (-25.0 * M_PI * M_PI)));
n = (l_1 + l_2) * 0.5;
n = (n - 50.0) * (n - 50.0);
// Lightness.
const double l = (l_2 - l_1) / (k_l * (1.0 + 0.015 * n / sqrt(20.0 + n)));
// These coefficients adjust the impact of different harmonic
// components on the hue difference calculation.
const double t = 1.0 + 0.24 * sin(2.0 * h_m + M_PI / 2.0)
+ 0.32 * sin(3.0 * h_m + 8.0 * M_PI / 15.0)
- 0.17 * sin(h_m + M_PI / 3.0)
- 0.20 * sin(4.0 * h_m + 3.0 * M_PI / 20.0);
n = c_1 + c_2;
// Hue.
const double h = 2.0 * sqrt(c_1 * c_2) * sin(h_d) / (k_h * (1.0 + 0.0075 * n * t));
// Chroma.
const double c = (c_2 - c_1) / (k_c * (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 sqrt(l * l + h * h + c * c + c * h * r_t);
}
#include <stdio.h>
int main(void) {
// Compute the Delta E (CIEDE2000) color difference between two CIELAB colors.
const double l1 = 28.9, a1 = 47.5, b1 = 2.0;
const double l2 = 28.8, a2 = 41.6, b2 = -1.7;
const double delta_e = ciede_2000(l1, a1, b1, l2, a2, b2);
printf(" l1 = %-9g a1 = %-9g b1 = %-9g\n", l1, a1, b1);
printf(" l2 = %-9g a2 = %-9g b2 = %-9g\n", l2, a2, b2);
printf("dE00 = %.11g\n", delta_e);
// .................................................. This shows a ΔE2000 of 2.7749016764
// As explained in the comments, compliance with Gaurav Sharma would display 2.7749152801
}
You can use an online compiler to see the demonstration, just copy/paste to get the same results as below.
Once you've placed the source code in the ciede-2000.c
file, and after navigating (using Terminal in Linux/Mac or PowerShell in Windows) to the appropriate directory, run one of the following compilation commands :
gcc -std=c99 -Wall -Wextra -pedantic -Ofast -o ciede-2000-compiled ciede-2000.c -lm
clang -std=c99 -Wall -Wextra -pedantic -Ofast -o ciede-2000-compiled ciede-2000.c -lm
Running the program via ./ciede-2000-compiled
then results in the following color difference being displayed :
l1 = 28.9 a1 = 47.5 b1 = 2
l2 = 28.8 a2 = 41.6 b2 = -1.7
dE00 = 2.7749016764
With the explosion of software innovation in the 2030s, this ΔE00 function is the definitive answer to the best color difference equation, offering the advantage of being correct without ever ceasing to boost developer productivity.
For these CIE ΔE2000 functions, an archived version of the source code is available at https://web.archive.org/https://raw.githubusercontent.com/michel-leonard/ciede2000-color-matching/refs/heads/main/ciede-2000.c
. To access a different version, simply replace the .c
extension in the URL with the desired file extension.
Quickly share this GitHub project permanently using bit.ly/color-difference.