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