Last active
May 23, 2018 06:55
-
-
Save ttchengcheng/23877c8f319024f6cbe87112c949b23d to your computer and use it in GitHub Desktop.
using CIEDE2000 to calculate color difference
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// reference: https://zh.wikipedia.org/wiki/%E9%A2%9C%E8%89%B2%E5%B7%AE%E5%BC%82 | |
// 1. CIEDE2000 uses Lab which is a color format that better to tell the difference by human vision. | |
// 2. There is no directly conversion from RGB to Lab, but RGB to XYZ to Lab. | |
// https://github.com/gfiumara/CIEDE2000/blob/master/CIEDE2000.cpp | |
constexpr double | |
CIEDE2000::deg2Rad( | |
const double deg) | |
{ | |
return (deg * (M_PI / 180.0)); | |
} | |
constexpr double | |
CIEDE2000::rad2Deg( | |
const double rad) | |
{ | |
return ((180.0 / M_PI) * rad); | |
} | |
double | |
CIEDE2000::CIEDE2000( | |
const LAB &lab1, | |
const LAB &lab2) | |
{ | |
/* | |
* "For these and all other numerical/graphical delta E00 values | |
* reported in this article, we set the parametric weighting factors | |
* to unity(i.e., k_L = k_C = k_H = 1.0)." (Page 27). | |
*/ | |
const double k_L = 1.0, k_C = 1.0, k_H = 1.0; | |
const double deg360InRad = CIEDE2000::deg2Rad(360.0); | |
const double deg180InRad = CIEDE2000::deg2Rad(180.0); | |
const double pow25To7 = 6103515625.0; /* pow(25, 7) */ | |
/* | |
* Step 1 | |
*/ | |
/* Equation 2 */ | |
double C1 = sqrt((lab1.a * lab1.a) + (lab1.b * lab1.b)); | |
double C2 = sqrt((lab2.a * lab2.a) + (lab2.b * lab2.b)); | |
/* Equation 3 */ | |
double barC = (C1 + C2) / 2.0; | |
/* Equation 4 */ | |
double G = 0.5 * (1 - sqrt(pow(barC, 7) / (pow(barC, 7) + pow25To7))); | |
/* Equation 5 */ | |
double a1Prime = (1.0 + G) * lab1.a; | |
double a2Prime = (1.0 + G) * lab2.a; | |
/* Equation 6 */ | |
double CPrime1 = sqrt((a1Prime * a1Prime) + (lab1.b * lab1.b)); | |
double CPrime2 = sqrt((a2Prime * a2Prime) + (lab2.b * lab2.b)); | |
/* Equation 7 */ | |
double hPrime1; | |
if (lab1.b == 0 && a1Prime == 0) | |
hPrime1 = 0.0; | |
else { | |
hPrime1 = atan2(lab1.b, a1Prime); | |
/* | |
* This must be converted to a hue angle in degrees between 0 | |
* and 360 by addition of 2 to negative hue angles. | |
*/ | |
if (hPrime1 < 0) | |
hPrime1 += deg360InRad; | |
} | |
double hPrime2; | |
if (lab2.b == 0 && a2Prime == 0) | |
hPrime2 = 0.0; | |
else { | |
hPrime2 = atan2(lab2.b, a2Prime); | |
/* | |
* This must be converted to a hue angle in degrees between 0 | |
* and 360 by addition of 2 to negative hue angles. | |
*/ | |
if (hPrime2 < 0) | |
hPrime2 += deg360InRad; | |
} | |
/* | |
* Step 2 | |
*/ | |
/* Equation 8 */ | |
double deltaLPrime = lab2.l - lab1.l; | |
/* Equation 9 */ | |
double deltaCPrime = CPrime2 - CPrime1; | |
/* Equation 10 */ | |
double deltahPrime; | |
double CPrimeProduct = CPrime1 * CPrime2; | |
if (CPrimeProduct == 0) | |
deltahPrime = 0; | |
else { | |
/* Avoid the fabs() call */ | |
deltahPrime = hPrime2 - hPrime1; | |
if (deltahPrime < -deg180InRad) | |
deltahPrime += deg360InRad; | |
else if (deltahPrime > deg180InRad) | |
deltahPrime -= deg360InRad; | |
} | |
/* Equation 11 */ | |
double deltaHPrime = 2.0 * sqrt(CPrimeProduct) * | |
sin(deltahPrime / 2.0); | |
/* | |
* Step 3 | |
*/ | |
/* Equation 12 */ | |
double barLPrime = (lab1.l + lab2.l) / 2.0; | |
/* Equation 13 */ | |
double barCPrime = (CPrime1 + CPrime2) / 2.0; | |
/* Equation 14 */ | |
double barhPrime, hPrimeSum = hPrime1 + hPrime2; | |
if (CPrime1 * CPrime2 == 0) { | |
barhPrime = hPrimeSum; | |
} else { | |
if (fabs(hPrime1 - hPrime2) <= deg180InRad) | |
barhPrime = hPrimeSum / 2.0; | |
else { | |
if (hPrimeSum < deg360InRad) | |
barhPrime = (hPrimeSum + deg360InRad) / 2.0; | |
else | |
barhPrime = (hPrimeSum - deg360InRad) / 2.0; | |
} | |
} | |
/* Equation 15 */ | |
double T = 1.0 - (0.17 * cos(barhPrime - CIEDE2000::deg2Rad(30.0))) + | |
(0.24 * cos(2.0 * barhPrime)) + | |
(0.32 * cos((3.0 * barhPrime) + CIEDE2000::deg2Rad(6.0))) - | |
(0.20 * cos((4.0 * barhPrime) - CIEDE2000::deg2Rad(63.0))); | |
/* Equation 16 */ | |
double deltaTheta = CIEDE2000::deg2Rad(30.0) * | |
exp(-pow((barhPrime - deg2Rad(275.0)) / deg2Rad(25.0), 2.0)); | |
/* Equation 17 */ | |
double R_C = 2.0 * sqrt(pow(barCPrime, 7.0) / | |
(pow(barCPrime, 7.0) + pow25To7)); | |
/* Equation 18 */ | |
double S_L = 1 + ((0.015 * pow(barLPrime - 50.0, 2.0)) / | |
sqrt(20 + pow(barLPrime - 50.0, 2.0))); | |
/* Equation 19 */ | |
double S_C = 1 + (0.045 * barCPrime); | |
/* Equation 20 */ | |
double S_H = 1 + (0.015 * barCPrime * T); | |
/* Equation 21 */ | |
double R_T = (-sin(2.0 * deltaTheta)) * R_C; | |
/* Equation 22 */ | |
double deltaE = sqrt( | |
pow(deltaLPrime / (k_L * S_L), 2.0) + | |
pow(deltaCPrime / (k_C * S_C), 2.0) + | |
pow(deltaHPrime / (k_H * S_H), 2.0) + | |
(R_T * (deltaCPrime / (k_C * S_C)) * (deltaHPrime / (k_H * S_H)))); | |
return (deltaE); | |
} | |
// RGB <--> LAB | |
// https://github.com/berendeanicolae/ColorSpace/blob/master/src/Conversion.cpp | |
// RGB -> XYZ -> LAB | |
void XyzConverter::ToColorSpace(Rgb *color, Xyz *item) { | |
double r = color->r / 255.0; | |
double g = color->g / 255.0; | |
double b = color->b / 255.0; | |
r = ((r > 0.04045) ? pow((r + 0.055) / 1.055, 2.4) : (r / 12.92)) * 100.0; | |
g = ((g > 0.04045) ? pow((g + 0.055) / 1.055, 2.4) : (g / 12.92)) * 100.0; | |
b = ((b > 0.04045) ? pow((b + 0.055) / 1.055, 2.4) : (b / 12.92)) * 100.0; | |
item->x = r*0.4124564 + g*0.3575761 + b*0.1804375; | |
item->y = r*0.2126729 + g*0.7151522 + b*0.0721750; | |
item->z = r*0.0193339 + g*0.1191920 + b*0.9503041; | |
} | |
void LabConverter::ToColorSpace(Rgb *color, Lab *item) { | |
Xyz xyz; | |
XyzConverter::ToColorSpace(color, &xyz); | |
double x = xyz.x / 95.047; | |
double y = xyz.y / 100.00; | |
double z = xyz.z / 108.883; | |
x = (x > 0.008856) ? cbrt(x) : (7.787 * x + 16.0 / 116.0); | |
y = (y > 0.008856) ? cbrt(y) : (7.787 * y + 16.0 / 116.0); | |
z = (z > 0.008856) ? cbrt(z) : (7.787 * z + 16.0 / 116.0); | |
item->l = (116.0 * y) - 16; | |
item->a = 500 * (x - y); | |
item->b = 200 * (y - z); | |
} | |
// LAB -> XYZ -> RGB | |
void XyzConverter::ToColor(Rgb *color, Xyz *item) { | |
double x = item->x / 100.0; | |
double y = item->y / 100.0; | |
double z = item->z / 100.0; | |
double r = x * 3.2404542 + y * -1.5371385 + z * -0.4985314; | |
double g = x * -0.9692660 + y * 1.8760108 + z * 0.0415560; | |
double b = x * 0.0556434 + y * -0.2040259 + z * 1.0572252; | |
r = ((r > 0.0031308) ? (1.055*pow(r, 1 / 2.4) - 0.055) : (12.92*r)) * 255.0; | |
g = ((g > 0.0031308) ? (1.055*pow(g, 1 / 2.4) - 0.055) : (12.92*g)) * 255.0; | |
b = ((b > 0.0031308) ? (1.055*pow(b, 1 / 2.4) - 0.055) : (12.92*b)) * 255.0; | |
color->r = r; | |
color->g = g; | |
color->b = b; | |
} | |
void LabConverter::ToColor(Rgb *color, Lab *item) { | |
double y = (item->l + 16.0) / 116.0; | |
double x = item->a / 500.0 + y; | |
double z = y - item->b / 200.0; | |
double x3 = POW3(x); | |
double y3 = POW3(y); | |
double z3 = POW3(z); | |
x = ((x3 > 0.008856) ? x3 : ((x - 16.0 / 116.0) / 7.787)) * 95.047; | |
y = ((y3 > 0.008856) ? y3 : ((y - 16.0 / 116.0) / 7.787)) * 100.0; | |
z = ((z3 > 0.008856) ? z3 : ((z - 16.0 / 116.0) / 7.787)) * 108.883; | |
Xyz xyz(x, y, z); | |
XyzConverter::ToColor(color, &xyz); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment