1
1

ColorDeltaE.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System.Drawing;
  2. namespace Masuit.Tools.Media;
  3. public static class ColorDeltaE
  4. {
  5. // CIE1976 (ΔE*ab)
  6. public static double CIE1976(this Color color1, Color color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  7. public static double CIE1976(this CMYColor color1, CMYColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  8. public static double CIE1976(this CMYKColor color1, CMYKColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  9. public static double CIE1976(this HSLColor color1, HSLColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  10. public static double CIE1976(this LabColor color1, LabColor color2) => CalculateDeltaE1976(color1, color2);
  11. public static double CIE1976(this LCHColor color1, LCHColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  12. public static double CIE1976(this XYZColor color1, XYZColor color2) => CalculateDeltaE1976(color1.ToLab(), color2.ToLab());
  13. public static double CIE1976(this YXZColor color1, YXZColor color2) => CalculateDeltaE1976(color1.YxzToLab(), color2.YxzToLab());
  14. // CIE1994 (ΔE94)
  15. public static double CIE1994(this Color color1, Color color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  16. public static double CIE1994(this CMYColor color1, CMYColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  17. public static double CIE1994(this CMYKColor color1, CMYKColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  18. public static double CIE1994(this HSLColor color1, HSLColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  19. public static double CIE1994(this LabColor color1, LabColor color2, bool textile = false) => CalculateDeltaE1994(color1, color2, textile);
  20. public static double CIE1994(this LCHColor color1, LCHColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  21. public static double CIE1994(this XYZColor color1, XYZColor color2, bool textile = false) => CalculateDeltaE1994(color1.ToLab(), color2.ToLab(), textile);
  22. public static double CIE1994(this YXZColor color1, YXZColor color2, bool textile = false) => CalculateDeltaE1994(color1.YxzToLab(), color2.YxzToLab(), textile);
  23. // CIE2000 (ΔE00)
  24. public static double CIE2000(this Color color1, Color color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  25. public static double CIE2000(this CMYColor color1, CMYColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  26. public static double CIE2000(this CMYKColor color1, CMYKColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  27. public static double CIE2000(this HSLColor color1, HSLColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  28. public static double CIE2000(this LabColor color1, LabColor color2) => CalculateDeltaE2000(color1, color2);
  29. public static double CIE2000(this LCHColor color1, LCHColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  30. public static double CIE2000(this XYZColor color1, XYZColor color2) => CalculateDeltaE2000(color1.ToLab(), color2.ToLab());
  31. public static double CIE2000(this YXZColor color1, YXZColor color2) => CalculateDeltaE2000(color1.YxzToLab(), color2.YxzToLab());
  32. // CMC (ΔEcmc)
  33. public static double CMC(this Color color1, Color color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  34. public static double CMC(this CMYColor color1, CMYColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  35. public static double CMC(this CMYKColor color1, CMYKColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  36. public static double CMC(this HSLColor color1, HSLColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  37. public static double CMC(this LabColor color1, LabColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1, color2, l, c);
  38. public static double CMC(this LCHColor color1, LCHColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  39. public static double CMC(this XYZColor color1, XYZColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.ToLab(), color2.ToLab(), l, c);
  40. public static double CMC(this YXZColor color1, YXZColor color2, double l = 2.0, double c = 1.0) => CalculateDeltaECMC(color1.YxzToLab(), color2.YxzToLab(), l, c);
  41. #region Core Calculation Methods
  42. private static double CalculateDeltaE1976(LabColor lab1, LabColor lab2)
  43. {
  44. double deltaL = lab1.L - lab2.L;
  45. double deltaA = lab1.a - lab2.a;
  46. double deltaB = lab1.b - lab2.b;
  47. return Math.Sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
  48. }
  49. private static double CalculateDeltaE1994(LabColor lab1, LabColor lab2, bool textile)
  50. {
  51. double kL = textile ? 2.0 : 1.0;
  52. double kC = 1.0;
  53. double kH = 1.0;
  54. double k1 = textile ? 0.048 : 0.045;
  55. double k2 = textile ? 0.014 : 0.015;
  56. double deltaL = lab1.L - lab2.L;
  57. double c1 = Math.Sqrt(lab1.a * lab1.a + lab1.b * lab1.b);
  58. double c2 = Math.Sqrt(lab2.a * lab2.a + lab2.b * lab2.b);
  59. double deltaC = c1 - c2;
  60. double deltaA = lab1.a - lab2.a;
  61. double deltaB = lab1.b - lab2.b;
  62. double deltaH = Math.Sqrt(Math.Max(0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
  63. double sl = 1.0;
  64. double sc = 1.0 + k1 * c1;
  65. double sh = 1.0 + k2 * c1;
  66. double termL = deltaL / (kL * sl);
  67. double termC = deltaC / (kC * sc);
  68. double termH = deltaH / (kH * sh);
  69. return Math.Sqrt(termL * termL + termC * termC + termH * termH);
  70. }
  71. private static double CalculateDeltaE2000(LabColor lab1, LabColor lab2)
  72. {
  73. const double kL = 1.0;
  74. const double kC = 1.0;
  75. const double kH = 1.0;
  76. double l1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
  77. double l2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
  78. double c1 = Math.Sqrt(a1 * a1 + b1 * b1);
  79. double c2 = Math.Sqrt(a2 * a2 + b2 * b2);
  80. double meanC = (c1 + c2) / 2.0;
  81. double meanC7 = Math.Pow(meanC, 7);
  82. double g = 0.5 * (1 - Math.Sqrt(meanC7 / (meanC7 + 6103515625.0)));
  83. double a1Prime = a1 * (1 + g);
  84. double a2Prime = a2 * (1 + g);
  85. double c1Prime = Math.Sqrt(a1Prime * a1Prime + b1 * b1);
  86. double c2Prime = Math.Sqrt(a2Prime * a2Prime + b2 * b2);
  87. double h1Prime = (Math.Atan2(b1, a1Prime) * 180.0 / Math.PI + 360) % 360;
  88. double h2Prime = (Math.Atan2(b2, a2Prime) * 180.0 / Math.PI + 360) % 360;
  89. double deltaLPrime = l2 - l1;
  90. double deltaCPrime = c2Prime - c1Prime;
  91. double deltaHPrime;
  92. if (Math.Abs(h2Prime - h1Prime) <= 180)
  93. {
  94. deltaHPrime = h2Prime - h1Prime;
  95. }
  96. else if (h2Prime <= h1Prime)
  97. {
  98. deltaHPrime = h2Prime - h1Prime + 360;
  99. }
  100. else
  101. {
  102. deltaHPrime = h2Prime - h1Prime - 360;
  103. }
  104. double deltaH = 2 * Math.Sqrt(c1Prime * c2Prime) * Math.Sin(deltaHPrime * Math.PI / 360.0);
  105. double meanLPrime = (l1 + l2) / 2.0;
  106. double meanCPrime = (c1Prime + c2Prime) / 2.0;
  107. double meanHPrime;
  108. if (Math.Abs(h1Prime - h2Prime) > 180)
  109. {
  110. meanHPrime = (h1Prime + h2Prime + 360) / 2.0;
  111. }
  112. else
  113. {
  114. meanHPrime = (h1Prime + h2Prime) / 2.0;
  115. }
  116. meanHPrime %= 360;
  117. 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);
  118. double sl = 1 + (0.015 * Math.Pow(meanLPrime - 50, 2)) / Math.Sqrt(20 + Math.Pow(meanLPrime - 50, 2));
  119. double sc = 1 + 0.045 * meanCPrime;
  120. double sh = 1 + 0.015 * meanCPrime * t;
  121. double meanCPrime7 = Math.Pow(meanCPrime, 7);
  122. double rc = 2 * Math.Sqrt(meanCPrime7 / (meanCPrime7 + 6103515625.0));
  123. double deltaTheta = 30 * Math.Exp(-Math.Pow((meanHPrime - 275) / 25, 2));
  124. double rt = -Math.Sin(2 * deltaTheta * Math.PI / 180) * rc;
  125. double term1 = deltaLPrime / (kL * sl);
  126. double term2 = deltaCPrime / (kC * sc);
  127. double term3 = deltaH / (kH * sh);
  128. return Math.Sqrt(term1 * term1 + term2 * term2 + term3 * term3 + rt * term2 * term3);
  129. }
  130. private static double CalculateDeltaECMC(LabColor lab1, LabColor lab2, double l, double c)
  131. {
  132. double deltaL = lab1.L - lab2.L;
  133. double c1 = Math.Sqrt(lab1.a * lab1.a + lab1.b * lab1.b);
  134. double c2 = Math.Sqrt(lab2.a * lab2.a + lab2.b * lab2.b);
  135. double deltaC = c1 - c2;
  136. double deltaA = lab1.a - lab2.a;
  137. double deltaB = lab1.b - lab2.b;
  138. double deltaH = Math.Sqrt(Math.Max(0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
  139. double h1 = (Math.Atan2(lab1.b, lab1.a) * 180.0 / Math.PI + 360) % 360;
  140. double f = Math.Sqrt(Math.Pow(c1, 4) / (Math.Pow(c1, 4) + 1900));
  141. double t;
  142. if (h1 is >= 164 and <= 345)
  143. {
  144. t = 0.56 + Math.Abs(0.2 * Math.Cos((h1 + 168) * Math.PI / 180));
  145. }
  146. else
  147. {
  148. t = 0.36 + Math.Abs(0.4 * Math.Cos((h1 + 35) * Math.PI / 180));
  149. }
  150. double sl = (lab1.L < 16) ? 0.511 : (0.040975 * lab1.L) / (1 + 0.01765 * lab1.L);
  151. double sc = (0.0638 * c1) / (1 + 0.0131 * c1) + 0.638;
  152. double sh = sc * (t * f + 1 - f);
  153. double termL = deltaL / (l * sl);
  154. double termC = deltaC / (c * sc);
  155. double termH = deltaH / sh;
  156. return Math.Sqrt(termL * termL + termC * termC + termH * termH);
  157. }
  158. #endregion Core Calculation Methods
  159. }