|
@@ -0,0 +1,219 @@
|
|
|
+using System.Drawing;
|
|
|
+
|
|
|
+namespace Masuit.Tools.Media;
|
|
|
+
|
|
|
+public static class ColorDeltaE
|
|
|
+{
|
|
|
+ // CIE1976 (ΔE*ab)
|
|
|
+ public static double CIE1976(Color color1, Color color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(CMYColor color1, CMYColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(CMYKColor color1, CMYKColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(HSLColor color1, HSLColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(LabColor color1, LabColor color2) => CalculateDeltaE1976(color1, color2);
|
|
|
+
|
|
|
+ public static double CIE1976(LCHColor color1, LCHColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(XYZColor color1, XYZColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE1976(YXZColor color1, YXZColor color2) => CalculateDeltaE1976(color1.YxzToLab(), color2.YxzToLab());
|
|
|
+
|
|
|
+ // CIE1994 (ΔE94)
|
|
|
+ public static double CIE1994(Color color1, Color color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(CMYColor color1, CMYColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(CMYKColor color1, CMYKColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(HSLColor color1, HSLColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(LabColor color1, LabColor color2, bool textile = false) => CalculateDeltaE1994(color1, color2, textile);
|
|
|
+
|
|
|
+ public static double CIE1994(LCHColor color1, LCHColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(XYZColor color1, XYZColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
|
|
|
+
|
|
|
+ public static double CIE1994(YXZColor color1, YXZColor color2, bool textile = false) => CalculateDeltaE1994(color1.YxzToLab(), color2.YxzToLab(), textile);
|
|
|
+
|
|
|
+ // CIE2000 (ΔE00)
|
|
|
+ public static double CIE2000(Color color1, Color color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(CMYColor color1, CMYColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(CMYKColor color1, CMYKColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(HSLColor color1, HSLColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(LabColor color1, LabColor color2) => CalculateDeltaE2000(color1, color2);
|
|
|
+
|
|
|
+ public static double CIE2000(LCHColor color1, LCHColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(XYZColor color1, XYZColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
|
|
|
+
|
|
|
+ public static double CIE2000(YXZColor color1, YXZColor color2) => CalculateDeltaE2000(color1.YxzToLab(), color2.YxzToLab());
|
|
|
+
|
|
|
+ // CMC (ΔEcmc)
|
|
|
+ public static double CMC(Color color1, Color color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(CMYColor color1, CMYColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(CMYKColor color1, CMYKColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(HSLColor color1, HSLColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(LabColor color1, LabColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1, color2, l, c);
|
|
|
+
|
|
|
+ public static double CMC(LCHColor color1, LCHColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(XYZColor color1, XYZColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
|
|
|
+
|
|
|
+ public static double CMC(YXZColor color1, YXZColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.YxzToLab(), color2.YxzToLab(), l, c);
|
|
|
+
|
|
|
+ #region Core Calculation Methods
|
|
|
+
|
|
|
+ private static double CalculateDeltaE1976(LabColor lab1, LabColor lab2)
|
|
|
+ {
|
|
|
+ double deltaL = lab1.L - lab2.L;
|
|
|
+ double deltaA = lab1.a - lab2.a;
|
|
|
+ double deltaB = lab1.b - lab2.b;
|
|
|
+ return Math.Sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double CalculateDeltaE1994(LabColor lab1, LabColor lab2, bool textile)
|
|
|
+ {
|
|
|
+ double kL = textile ? 2.0 : 1.0;
|
|
|
+ double kC = 1.0;
|
|
|
+ double kH = 1.0;
|
|
|
+ double k1 = textile ? 0.048 : 0.045;
|
|
|
+ double k2 = textile ? 0.014 : 0.015;
|
|
|
+
|
|
|
+ double deltaL = lab1.L - lab2.L;
|
|
|
+ double c1 = Math.Sqrt(lab1.a * lab1.a + lab1.b * lab1.b);
|
|
|
+ double c2 = Math.Sqrt(lab2.a * lab2.a + lab2.b * lab2.b);
|
|
|
+ double deltaC = c1 - c2;
|
|
|
+
|
|
|
+ double deltaA = lab1.a - lab2.a;
|
|
|
+ double deltaB = lab1.b - lab2.b;
|
|
|
+ double deltaH = Math.Sqrt(Math.Max(0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
|
|
|
+
|
|
|
+ double sl = 1.0;
|
|
|
+ double sc = 1.0 + k1 * c1;
|
|
|
+ double sh = 1.0 + k2 * c1;
|
|
|
+
|
|
|
+ double termL = deltaL / (kL * sl);
|
|
|
+ double termC = deltaC / (kC * sc);
|
|
|
+ double termH = deltaH / (kH * sh);
|
|
|
+
|
|
|
+ return Math.Sqrt(termL * termL + termC * termC + termH * termH);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double CalculateDeltaE2000(LabColor lab1, LabColor lab2)
|
|
|
+ {
|
|
|
+ const double kL = 1.0;
|
|
|
+ const double kC = 1.0;
|
|
|
+ const double kH = 1.0;
|
|
|
+
|
|
|
+ double l1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
|
|
|
+ double l2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
|
|
|
+
|
|
|
+ double c1 = Math.Sqrt(a1 * a1 + b1 * b1);
|
|
|
+ double c2 = Math.Sqrt(a2 * a2 + b2 * b2);
|
|
|
+ double meanC = (c1 + c2) / 2.0;
|
|
|
+
|
|
|
+ double meanC7 = Math.Pow(meanC, 7);
|
|
|
+ double g = 0.5 * (1 - Math.Sqrt(meanC7 / (meanC7 + 6103515625.0)));
|
|
|
+
|
|
|
+ double a1Prime = a1 * (1 + g);
|
|
|
+ double a2Prime = a2 * (1 + g);
|
|
|
+
|
|
|
+ double c1Prime = Math.Sqrt(a1Prime * a1Prime + b1 * b1);
|
|
|
+ double c2Prime = Math.Sqrt(a2Prime * a2Prime + b2 * b2);
|
|
|
+
|
|
|
+ double h1Prime = (Math.Atan2(b1, a1Prime) * 180.0 / Math.PI + 360) % 360;
|
|
|
+ double h2Prime = (Math.Atan2(b2, a2Prime) * 180.0 / Math.PI + 360) % 360;
|
|
|
+
|
|
|
+ double deltaLPrime = l2 - l1;
|
|
|
+ double deltaCPrime = c2Prime - c1Prime;
|
|
|
+
|
|
|
+ double deltaHPrime;
|
|
|
+ if (Math.Abs(h2Prime - h1Prime) <= 180)
|
|
|
+ {
|
|
|
+ deltaHPrime = h2Prime - h1Prime;
|
|
|
+ }
|
|
|
+ else if (h2Prime <= h1Prime)
|
|
|
+ {
|
|
|
+ deltaHPrime = h2Prime - h1Prime + 360;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ deltaHPrime = h2Prime - h1Prime - 360;
|
|
|
+ }
|
|
|
+
|
|
|
+ double deltaH = 2 * Math.Sqrt(c1Prime * c2Prime) * Math.Sin(deltaHPrime * Math.PI / 360.0);
|
|
|
+ double meanLPrime = (l1 + l2) / 2.0;
|
|
|
+ double meanCPrime = (c1Prime + c2Prime) / 2.0;
|
|
|
+ double meanHPrime;
|
|
|
+ if (Math.Abs(h1Prime - h2Prime) > 180)
|
|
|
+ {
|
|
|
+ meanHPrime = (h1Prime + h2Prime + 360) / 2.0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ meanHPrime = (h1Prime + h2Prime) / 2.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ meanHPrime %= 360;
|
|
|
+
|
|
|
+ double t = 1 - 0.17 * Math.Cos((meanHPrime - 30) * Math.PI / 180) + 0.24 * Math.Cos(2 * meanHPrime * Math.PI / 180) + 0.32 * Math.Cos((3 * meanHPrime + 6) * Math.PI / 180) - 0.20 * Math.Cos((4 * meanHPrime - 63) * Math.PI / 180);
|
|
|
+
|
|
|
+ double sl = 1 + (0.015 * Math.Pow(meanLPrime - 50, 2)) / Math.Sqrt(20 + Math.Pow(meanLPrime - 50, 2));
|
|
|
+ double sc = 1 + 0.045 * meanCPrime;
|
|
|
+ double sh = 1 + 0.015 * meanCPrime * t;
|
|
|
+
|
|
|
+ double meanCPrime7 = Math.Pow(meanCPrime, 7);
|
|
|
+ double rc = 2 * Math.Sqrt(meanCPrime7 / (meanCPrime7 + 6103515625.0));
|
|
|
+ double deltaTheta = 30 * Math.Exp(-Math.Pow((meanHPrime - 275) / 25, 2));
|
|
|
+ double rt = -Math.Sin(2 * deltaTheta * Math.PI / 180) * rc;
|
|
|
+
|
|
|
+ double term1 = deltaLPrime / (kL * sl);
|
|
|
+ double term2 = deltaCPrime / (kC * sc);
|
|
|
+ double term3 = deltaH / (kH * sh);
|
|
|
+
|
|
|
+ return Math.Sqrt(term1 * term1 + term2 * term2 + term3 * term3 + rt * term2 * term3);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double CalculateDeltaECMC(LabColor lab1, LabColor lab2, double l, double c)
|
|
|
+ {
|
|
|
+ double deltaL = lab1.L - lab2.L;
|
|
|
+ double c1 = Math.Sqrt(lab1.a * lab1.a + lab1.b * lab1.b);
|
|
|
+ double c2 = Math.Sqrt(lab2.a * lab2.a + lab2.b * lab2.b);
|
|
|
+ double deltaC = c1 - c2;
|
|
|
+ double deltaA = lab1.a - lab2.a;
|
|
|
+ double deltaB = lab1.b - lab2.b;
|
|
|
+ double deltaH = Math.Sqrt(Math.Max(0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
|
|
|
+ double h1 = (Math.Atan2(lab1.b, lab1.a) * 180.0 / Math.PI + 360) % 360;
|
|
|
+ double f = Math.Sqrt(Math.Pow(c1, 4) / (Math.Pow(c1, 4) + 1900));
|
|
|
+ double t;
|
|
|
+ if (h1 is >= 164 and <= 345)
|
|
|
+ {
|
|
|
+ t = 0.56 + Math.Abs(0.2 * Math.Cos((h1 + 168) * Math.PI / 180));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ t = 0.36 + Math.Abs(0.4 * Math.Cos((h1 + 35) * Math.PI / 180));
|
|
|
+ }
|
|
|
+
|
|
|
+ double sl = (lab1.L < 16) ? 0.511 : (0.040975 * lab1.L) / (1 + 0.01765 * lab1.L);
|
|
|
+ double sc = (0.0638 * c1) / (1 + 0.0131 * c1) + 0.638;
|
|
|
+ double sh = sc * (t * f + 1 - f);
|
|
|
+ double termL = deltaL / (l * sl);
|
|
|
+ double termC = deltaC / (c * sc);
|
|
|
+ double termH = deltaH / sh;
|
|
|
+ return Math.Sqrt(termL * termL + termC * termC + termH * termH);
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion Core Calculation Methods
|
|
|
+}
|