1+ // deltaE2000 is a statistically significant improvement
2+ // over deltaE76 and deltaE94,
3+ // and is recommended by the CIE and Idealliance
4+ // especially for color differences less than 10 deltaE76
5+ // but is wicked complicated
6+ // and many implementations have small errors!
7+
8+
9+ function deltaE2000 ( reference , sample ) {
10+
11+ // Given a reference and a sample color,
12+ // both in CIE Lab,
13+ // calculate deltaE 2000.
14+
15+ // This implementation assumes the parametric
16+ // weighting factors kL, kC and kH
17+ // (for the influence of viewing conditions)
18+ // are all 1, as seems typical.
19+
20+ let [ L1 , a1 , b1 ] = reference ;
21+ let [ L2 , a2 , b2 ] = sample ;
22+ let C1 = Math . sqrt ( a1 ** 2 + b1 ** 2 ) ;
23+ let C2 = Math . sqrt ( a2 ** 2 + b2 ** 2 ) ;
24+
25+ let Cbar = ( C1 + C2 ) / 2 ; // mean Chroma
26+
27+ // calculate a-axis asymmetry factor from mean Chroma
28+ // this turns JND ellipses for near-neutral colors back into circles
29+ let C7 = Math . pow ( Cbar , 7 ) ;
30+ const Gfactor = Math . pow ( 25 , 7 ) ;
31+ let G = 0.5 * ( 1 - Math . sqrt ( C7 / ( C7 + Gfactor ) ) ) ;
32+
33+ // scale a axes by asymmetry factor
34+ // this by the way is why there is no Lab2000 colorspace
35+ let adash1 = ( 1 + G ) * a1 ;
36+ let adash2 = ( 1 + G ) * a2 ;
37+
38+ // calculate new Chroma from scaled a and original b axes
39+ let Cdash1 = Math . sqrt ( adash1 ** 2 + b1 ** 2 ) ;
40+ let Cdash2 = Math . sqrt ( adash2 ** 2 + b2 ** 2 ) ;
41+
42+ // calculate new hues, with zero hue for true neutrals
43+ // and in degrees, not radians
44+ const π = Math . PI ;
45+ const r2d = 180 / π ;
46+ const d2r = π / 180 ;
47+ let h1 = ( adash1 === 0 && b1 === 0 ) ? 0 : Math . atan2 ( b1 , adash1 ) ;
48+ let h2 = ( adash2 === 0 && b2 === 0 ) ? 0 : Math . atan2 ( b2 , adash2 ) ;
49+
50+ if ( h1 < 0 ) {
51+ h1 += 2 * π ;
52+ }
53+ if ( h2 < 0 ) {
54+ h2 += 2 * π ;
55+ }
56+
57+ h1 *= r2d ;
58+ h2 *= r2d ;
59+
60+ // Lightness and Chroma differences; sign matters
61+ let ΔL = L2 - L1 ;
62+ let ΔC = Cdash2 - Cdash1 ;
63+
64+ // Hue difference, taking care to get the sign correct
65+ let hdiff = h2 - h1 ;
66+ let hsum = h1 + h2 ;
67+ let habs = Math . abs ( hdiff ) ;
68+ let Δh ;
69+
70+ if ( Cdash1 == 0 && Cdash2 == 0 ) {
71+ Δh = 0 ;
72+ }
73+ else if ( habs <= 180 ) {
74+ Δh = hdiff ;
75+ }
76+ else if ( hdiff > 180 ) {
77+ Δh = hdiff - 360 ;
78+ }
79+ else if ( hdiff < - 180 ) {
80+ Δh = hdiff + 360 ;
81+ }
82+ else {
83+ console . log ( "the unthinkable has happened" ) ;
84+ }
85+
86+ // weighted Hue difference, more for larger Chroma
87+ let ΔH = 2 * Math . sqrt ( Cdash2 * Cdash1 ) * Math . sin ( Δh * d2r / 2 ) ;
88+
89+ // calculate mean Lightness and Chroma
90+ let Ldash = ( L1 + L2 ) / 2 ;
91+ let Cdash = ( Cdash1 + Cdash2 ) / 2 ;
92+ let Cdash7 = Math . pow ( Cdash , 7 ) ;
93+
94+ // Compensate for non-linearity in the blue region of Lab.
95+ // Four possibilities for hue weighting factor,
96+ // depending on the angles, to get the correct sign
97+ let hdash ;
98+ if ( Cdash1 == 0 && Cdash2 == 0 ) {
99+ hdash = hsum ; // which should be zero
100+ }
101+ else if ( habs <= 180 ) {
102+ hdash = hsum / 2 ;
103+ }
104+ else if ( hsum < 360 ) {
105+ hdash = ( hsum + 360 ) / 2 ;
106+ }
107+ else {
108+ hdash = ( hsum - 360 ) / 2 ;
109+ }
110+
111+ // positional corrections to the lack of uniformity of CIELAB
112+ // These are all trying to make JND ellipsoids more like spheres
113+
114+ // SL Lightness crispening factor
115+ // a background with L=50 is assumed
116+ let lsq = ( Ldash - 50 ) ** 2 ;
117+ let SL = 1 + ( ( 0.015 * lsq ) / Math . sqrt ( 20 + lsq ) ) ;
118+
119+ // SC Chroma factor, similar to those in CMC and deltaE 94 formulae
120+ let SC = 1 + 0.045 * Cdash ;
121+
122+ // Cross term T for blue non-linearity
123+ let T = 1 ;
124+ T -= ( 0.17 * Math . cos ( ( hdash - 30 ) * d2r ) ) ;
125+ T += ( 0.24 * Math . cos ( 2 * hdash * d2r ) ) ;
126+ T += ( 0.32 * Math . cos ( ( ( 3 * hdash ) + 6 ) * d2r ) ) ;
127+ T -= ( 0.20 * Math . cos ( ( ( 4 * hdash ) - 63 ) * d2r ) ) ;
128+
129+ // SH Hue factor depends on Chroma,
130+ // as well as adjusted hue angle like deltaE94.
131+ let SH = 1 + 0.015 * Cdash * T ;
132+
133+ // RT Hue rotation term compensates for rotation of JND ellipses
134+ // and Munsell constant hue lines
135+ // in the medium-high Chroma blue region
136+ // (Hue 225 to 315)
137+ let Δθ = 30 * Math . exp ( - 1 * ( ( ( hdash - 275 ) / 25 ) ** 2 ) ) ;
138+ let RC = 2 * Math . sqrt ( Cdash7 / ( Cdash7 + Gfactor ) ) ;
139+ let RT = - 1 * Math . sin ( 2 * Δθ * d2r ) * RC ;
140+
141+ // Finally calculate the deltaE, term by term as root sum of squares
142+ let dE = ( ΔL / SL ) ** 2 ;
143+ dE += ( ΔC / SC ) ** 2 ;
144+ dE += ( ΔH / SH ) ** 2 ;
145+ dE += RT * ( ΔC / SC ) * ( ΔH / SH ) ;
146+ return Math . sqrt ( dE ) ;
147+ // Yay!!!
148+ } ;
0 commit comments