2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 * Copyright (C) 2017 The Android Open Source Project
21 * Modified by Woochan Lee(wc0917.lee@samsung.com)
28 internal sealed class ColorUtils
30 private const int minAlphaSearchMaxIterations = 10;
31 private const int minAlphaSearchPrecision = 1;
34 /// Convert the ARGB color to its CIE XYZ representative components.
36 /// The resulting XYZ representation will use the D65 illuminant and the CIE
37 /// 2° Standard Observer (1931).
39 /// outXyz[0] is X [0 ...95.047)
40 /// outXyz[1] is Y [0...100)
41 /// outXyz[2] is Z [0...108.883)
43 /// param color the ARGB color to convert. The alpha component is ignored
44 /// param outXyz 3-element array which holds the resulting LAB components
46 public static void ColorToXyz(int color, double[] outXyz)
48 System.Drawing.Color rgbColor = System.Drawing.Color.FromArgb(color);
49 RgbToXyz(rgbColor.R, rgbColor.G, rgbColor.B, outXyz);
53 /// Convert RGB components to its CIE XYZ representative components.
55 /// The resulting XYZ representation will use the D65 illuminant and the CIE
56 /// 2° Standard Observer (1931).
58 /// outXyz[0] is X [0 ...95.047)
59 /// outXyz[1] is Y [0...100)
60 /// outXyz[2] is Z [0...108.883)
62 /// r red component value [0..255]
63 /// g green component value [0..255]
64 /// b blue component value [0..255]
65 /// outXyz 3-element array which holds the resulting XYZ components
67 public static void RgbToXyz(int red, int green, int blue, double[] outXyz)
69 if (outXyz.Length != 3)
71 throw new ArgumentException("Array legnth must be 3", nameof(outXyz));
74 double floatRed = red / 255.0;
75 floatRed = floatRed < 0.04045 ? floatRed / 12.92 : Math.Pow((floatRed + 0.055) / 1.055, 2.4);
76 double floatGreen = green / 255.0;
77 floatGreen = floatGreen < 0.04045 ? floatGreen / 12.92 : Math.Pow((floatGreen + 0.055) / 1.055, 2.4);
78 double floatBlue = blue / 255.0;
79 floatBlue = floatBlue < 0.04045 ? floatBlue / 12.92 : Math.Pow((floatBlue + 0.055) / 1.055, 2.4);
81 outXyz[0] = 100 * (floatRed * 0.4124 + floatGreen * 0.3576 + floatBlue * 0.1805);
82 outXyz[1] = 100 * (floatRed * 0.2126 + floatGreen * 0.7152 + floatBlue * 0.0722);
83 outXyz[2] = 100 * (floatRed * 0.0193 + floatGreen * 0.1192 + floatBlue * 0.9505);
87 /// Returns the luminance of a color as a float between 0.0 and 1.0
88 /// Defined as the Y component in the XYZ representation of color.
90 public static double CalculateLuminance(int color)
92 double[] result = new double[3];
93 ColorToXyz(color, result);
94 // Luminance is the Y component
95 return result[1] / 100;
99 /// Composite two potentially translucent colors over each other and returns the result.
101 public static int CompositeColors(int foreground, int background)
103 System.Drawing.Color foreColor = System.Drawing.Color.FromArgb(foreground);
104 System.Drawing.Color backColor = System.Drawing.Color.FromArgb(background);
105 int bgAlpha = backColor.A;
106 int fgAlpha = foreColor.A;
108 int alpha = CompositeAlpha(fgAlpha, bgAlpha);
110 int red = CompositeComponent(foreColor.R, fgAlpha,
111 backColor.R, bgAlpha, alpha);
112 int green = CompositeComponent(foreColor.G, fgAlpha,
113 backColor.G, bgAlpha, alpha);
114 int blue = CompositeComponent(foreColor.B, fgAlpha,
115 backColor.B, bgAlpha, alpha);
117 return ((alpha & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
121 /// Returns the contrast ratio between foreground and background.
122 /// background must be opaque.
125 /// <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
127 public static double CalculateContrast(int foreground, int background)
129 if (((background >> 24) & 0xff) != 255)
131 throw new ArgumentException("background can not be translucent.");
133 if (((foreground >> 24) & 0xff) < 255)
135 System.Drawing.Color rgbColor = System.Drawing.Color.FromArgb(foreground);
137 // If the foreground is translucent, composite the foreground over the background
138 foreground = CompositeColors(foreground, background);
139 rgbColor = System.Drawing.Color.FromArgb(foreground);
142 double luminance1 = CalculateLuminance(foreground) + 0.05;
143 double luminance2 = CalculateLuminance(background) + 0.05;
145 // Now return the lighter luminance divided by the darker luminance
146 return Math.Max(luminance1, luminance2) / Math.Min(luminance1, luminance2);
150 /// Set the alpha component of color to be alpha.
152 public static int SetAlphaComponent(int color, int alpha)
154 if (alpha < 0 || alpha > 255)
156 throw new ArgumentException("alpha must be between 0 and 255.");
159 return (color & 0x00ffffff) | (alpha << 24);
163 /// Calculates the minimum alpha value which can be applied to foreground so that would
164 /// have a contrast value of at least minContrastRatio when compared to
167 /// param foreground the foreground color
168 /// param background the opaque background color
169 /// param minContrastRatio the minimum contrast ratio
170 /// return the alpha value in the range 0-255, or -1 if no value could be calculated
172 public static int CalculateMinimumAlpha(int foreground, int background,
173 float minContrastRatio)
175 if (((background >> 24) & 0xff) != 255)
177 throw new ArgumentException("background can not be translucent");
180 // First lets check that a fully opaque foreground has sufficient contrast
181 int testForeground = SetAlphaComponent(foreground, 255);
182 System.Drawing.Color rgbColor = System.Drawing.Color.FromArgb(testForeground);
183 double testRatio = CalculateContrast(testForeground, background);
185 if (testRatio < minContrastRatio)
187 // Fully opaque foreground does not have sufficient contrast, return error
191 // Binary search to find a value with the minimum value which provides sufficient contrast
192 int numIterations = 0;
196 while (numIterations <= minAlphaSearchMaxIterations &&
197 (maxAlpha - minAlpha) > minAlphaSearchPrecision)
199 int testAlpha = (minAlpha + maxAlpha) / 2;
201 testForeground = SetAlphaComponent(foreground, testAlpha);
202 rgbColor = System.Drawing.Color.FromArgb(testForeground);
203 testRatio = CalculateContrast(testForeground, background);
204 if (testRatio < minContrastRatio)
206 minAlpha = testAlpha;
210 maxAlpha = testAlpha;
216 // Conservatively return the max of the range of possible alphas, which is known to pass.
220 public static void RgbToHsl(int red, int green, int blue, float[] hsl)
222 float floatRed = red / 255f;
223 float floatGreen = green / 255f;
224 float floatBlue = blue / 255f;
225 float max = Math.Max(floatRed, Math.Max(floatGreen, floatBlue));
226 float min = Math.Min(floatRed, Math.Min(floatGreen, floatBlue));
227 float deltaMaxMin = max - min;
228 float hue, saturation;
229 float lightness = (max + min) / 2f;
233 hue = saturation = 0f;
239 hue = ((floatGreen - floatBlue) / deltaMaxMin) % 6f;
241 else if (max == floatGreen)
243 hue = ((floatBlue - floatRed) / deltaMaxMin) + 2f;
247 hue = ((floatRed - floatGreen) / deltaMaxMin) + 4f;
249 saturation = deltaMaxMin / (1f - Math.Abs(2f * lightness - 1f));
251 hsl[0] = ((hue * 60f) + 360f) % 360f;
256 public static int HslToRgb(float[] hsl)
259 float saturation = hsl[1];
260 float lightness = hsl[2];
261 float constC = (1f - Math.Abs(2 * lightness - 1f)) * saturation;
262 float constM = lightness - 0.5f * constC;
263 float constX = constC * (1f - Math.Abs((hue / 60f % 2f) - 1f));
264 int hueSegment = (int)hue / 60;
265 int red = 0, green = 0, blue = 0;
269 red = (int)Math.Round(255 * (constC + constM));
270 green = (int)Math.Round(255 * (constX + constM));
271 blue = (int)Math.Round(255 * constM);
274 red = (int)Math.Round(255 * (constX + constM));
275 green = (int)Math.Round(255 * (constC + constM));
276 blue = (int)Math.Round(255 * constM);
279 red = (int)Math.Round(255 * constM);
280 green = (int)Math.Round(255 * (constC + constM));
281 blue = (int)Math.Round(255 * (constX + constM));
284 red = (int)Math.Round(255 * constM);
285 green = (int)Math.Round(255 * (constX + constM));
286 blue = (int)Math.Round(255 * (constC + constM));
289 red = (int)Math.Round(255 * (constX + constM));
290 green = (int)Math.Round(255 * constM);
291 blue = (int)Math.Round(255 * (constC + constM));
295 red = (int)Math.Round(255 * (constC + constM));
296 green = (int)Math.Round(255 * constM);
297 blue = (int)Math.Round(255 * (constX + constM));
300 red = Math.Max(0, Math.Min(255, red));
301 green = Math.Max(0, Math.Min(255, green));
302 blue = Math.Max(0, Math.Min(255, blue));
305 return (255 << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
309 /// return luma value according to to XYZ color space in the range 0.0 - 1.0
311 private static int CompositeAlpha(int foregroundAlpha, int backgroundAlpha)
313 return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
316 private static int CompositeComponent(int fgC, int fgA, int bgC, int bgA, int alpha)
318 if (alpha == 0) return 0;
319 return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (alpha * 0xFF);