deltaE00 function

double deltaE00(
  1. LabColor lab1,
  2. LabColor lab2, [
  3. Weights weights = const Weights()
])

Since the 1994 definition did not adequately resolve the perceptual uniformity issue, the CIE refined their definition, adding five corrections:

  • A hue rotation term, to deal with the problematic blue region (hue angles in the neighborhood of 275°)
  • Compensation for neutral colors (the primed values in the LCh differences)
  • Compensation for lightness
  • Compensation for chroma
  • Compensation for hue

Source: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000

Implementation

double deltaE00(LabColor lab1, LabColor lab2,
    [Weights weights = const Weights()]) {
  // Delta L Prime
  double deltaLPrime = lab2.l - lab1.l;
  // L Bar
  double lBar = (lab1.l + lab2.l) / 2;
  // C1 & C2
  double c1 = sqrt(pow(lab1.a, 2) + pow(lab1.b, 2)),
      c2 = sqrt(pow(lab2.a, 2) + pow(lab2.b, 2));
  // C Bar
  double cBar = (c1 + c2) / 2;
  // A Prime 1
  double aPrime1 = lab1.a +
      (lab1.a / 2) * (1 - sqrt(pow(cBar, 7) / (pow(cBar, 7) + pow(25, 7))));
  // A Prime 2
  double aPrime2 = lab2.a +
      (lab2.a / 2) * (1 - sqrt(pow(cBar, 7) / (pow(cBar, 7) + pow(25, 7))));
  // C Prime 1
  double cPrime1 = sqrt(pow(aPrime1, 2) + pow(lab1.b, 2));
  // C Prime 2
  double cPrime2 = sqrt(pow(aPrime2, 2) + pow(lab2.b, 2));
  // C Bar Prime
  double cBarPrime = (cPrime1 + cPrime2) / 2;
  // Delta C Prime
  double deltaCPrime = cPrime2 - cPrime1;
  // S sub L
  double sSubL =
      1 + ((0.015 * pow(lBar - 50, 2)) / sqrt(20 + pow(lBar - 50, 2)));
  // S sub C
  double sSubC = 1 + 0.045 * cBarPrime;
  // h Primes
  double hPrime1 = _getPrimeFn(lab1.b, aPrime1),
      hPrime2 = _getPrimeFn(lab2.b, aPrime2);
  // Delta h Prime
  double deltahPrime;
  // - When either C′1 or C′2 is zero, then Δh′ is irrelevant and may be set to zero.
  if (c1 == 0 || c2 == 0) {
    deltahPrime = 0.0;
  } else if ((hPrime1 - hPrime2).abs() <= 180.0) {
    deltahPrime = hPrime2 - hPrime1;
  } else if (hPrime2 <= hPrime1) {
    deltahPrime = hPrime2 - hPrime1 + 360.0;
  } else {
    deltahPrime = hPrime2 - hPrime1 - 360.0;
  }
  // Delta H Prime
  double deltaHPrime =
      2 * sqrt(cPrime1 * cPrime2) * sin(_degreesToRadians(deltahPrime) / 2);
  // H Bar Prime
  double hBarPrime;
  if ((hPrime1 - hPrime2).abs() > 180) {
    hBarPrime = (hPrime1 + hPrime2 + 360) / 2;
  } else {
    hBarPrime = (hPrime1 + hPrime2) / 2;
  }
  // T
  double t = 1 -
      0.17 * cos(_degreesToRadians(hBarPrime - 30)) +
      0.24 * cos(_degreesToRadians(2 * hBarPrime)) +
      0.32 * cos(_degreesToRadians(3 * hBarPrime + 6)) -
      0.20 * cos(_degreesToRadians(4 * hBarPrime - 63));
  // S sub H
  double sSubH = 1 + 0.015 * cBarPrime * t;
  // R sub T
  double rSubT = -2 *
      sqrt(pow(cBarPrime, 7) / (pow(cBarPrime, 7) + pow(25, 7))) *
      sin(_degreesToRadians(60 * exp(-pow((hBarPrime - 275) / 25, 2))));
  // Lab
  double lightness = deltaLPrime / (weights.l * sSubL);
  double chroma = deltaCPrime / (weights.a * sSubC);
  double hue = deltaHPrime / (weights.b * sSubH);

  return sqrt(
      pow(lightness, 2) + pow(chroma, 2) + pow(hue, 2) + rSubT * chroma * hue);
}