Skip to content

Instantly share code, notes, and snippets.

@ttchengcheng
Last active May 23, 2018 06:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ttchengcheng/23877c8f319024f6cbe87112c949b23d to your computer and use it in GitHub Desktop.
Save ttchengcheng/23877c8f319024f6cbe87112c949b23d to your computer and use it in GitHub Desktop.
using CIEDE2000 to calculate color difference
// 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