[NUI] Introduce NUI Palette APIs
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Utility / ColorUtils.cs
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 /*
19  * Copyright (C) 2017 The Android Open Source Project
20  *
21  * Modified by Woochan Lee(wc0917.lee@samsung.com)
22  */
23
24 using System;
25
26 namespace Tizen.NUI
27 {
28     internal sealed class ColorUtils
29     {
30         private const int minAlphaSearchMaxIterations = 10;
31         private const int minAlphaSearchPrecision = 1;
32
33         /// <summary>
34         /// Convert the ARGB color to its CIE XYZ representative components.
35         ///
36         /// The resulting XYZ representation will use the D65 illuminant and the CIE
37         /// 2° Standard Observer (1931).
38         ///
39         /// outXyz[0] is X [0 ...95.047)
40         /// outXyz[1] is Y [0...100)
41         /// outXyz[2] is Z [0...108.883)
42         ///
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
45         /// </summary>
46         public static void ColorToXyz(int color, double[] outXyz)
47         {
48             RgbToXyz((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, outXyz);
49         }
50
51         /// <summary>
52         /// Convert RGB components to its CIE XYZ representative components.
53         ///
54         /// The resulting XYZ representation will use the D65 illuminant and the CIE
55         /// 2° Standard Observer (1931).
56         ///
57         /// outXyz[0] is X [0 ...95.047)
58         /// outXyz[1] is Y [0...100)
59         /// outXyz[2] is Z [0...108.883)
60         ///
61         /// r      red component value [0..255]
62         /// g      green component value [0..255]
63         /// b      blue component value [0..255]
64         /// outXyz 3-element array which holds the resulting XYZ components
65         /// </summary>
66         public static void RgbToXyz(int red, int green, int blue, double[] outXyz)
67         {
68             if (outXyz.Length != 3)
69             {
70                 throw new ArgumentException("Array legnth must be 3", nameof(outXyz));
71             }
72
73             double floatRed = red / 255.0;
74             floatRed = floatRed < 0.04045 ? floatRed / 12.92 : Math.Pow((floatRed + 0.055) / 1.055, 2.4);
75             double floatGreen = green / 255.0;
76             floatGreen = floatGreen < 0.04045 ? floatGreen / 12.92 : Math.Pow((floatGreen + 0.055) / 1.055, 2.4);
77             double floatBlue = blue / 255.0;
78             floatBlue = floatBlue < 0.04045 ? floatBlue / 12.92 : Math.Pow((floatBlue + 0.055) / 1.055, 2.4);
79
80             outXyz[0] = 100 * (floatRed * 0.4124 + floatGreen * 0.3576 + floatBlue * 0.1805);
81             outXyz[1] = 100 * (floatRed * 0.2126 + floatGreen * 0.7152 + floatBlue * 0.0722);
82             outXyz[2] = 100 * (floatRed * 0.0193 + floatGreen * 0.1192 + floatBlue * 0.9505);
83         }
84
85         /// <summary>
86         /// Returns the luminance of a color as a float between 0.0 and 1.0
87         /// Defined as the Y component in the XYZ representation of color.
88         /// </summary>
89         public static double CalculateLuminance(int color)
90         {
91             double[] result = new double[3];
92             ColorToXyz(color, result);
93             // Luminance is the Y component
94             return result[1] / 100;
95         }
96
97         /// <summary>
98         /// Composite two potentially translucent colors over each other and returns the result.
99         /// </summary>
100         public static int CompositeColors(int foreground, int background)
101         {
102             int bgAlpha = (background >> 24) & 0xff;
103             int fgAlpha = (foreground >> 24) & 0xff;
104
105             int alpha = CompositeAlpha(fgAlpha, bgAlpha);
106
107             int red = CompositeComponent((foreground >> 16) & 0xff, fgAlpha,
108                     (background >> 16) & 0xff, bgAlpha, alpha);
109             int green = CompositeComponent((foreground >> 8) & 0xff, fgAlpha,
110                     (background >> 8) & 0xff, bgAlpha, alpha);
111             int blue = CompositeComponent(foreground & 0xff, fgAlpha,
112                     background & 0xff, bgAlpha, alpha);
113
114             return ((alpha & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
115         }
116
117         /// <summary>
118         /// Returns the contrast ratio between foreground and background.
119         /// background must be opaque.
120         ///
121         /// Formula defined
122         /// <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef">here</a>.
123         /// </summary>
124         public static double CalculateContrast(int foreground, int background)
125         {
126             if (((background >> 24) & 0xff) != 255)
127             {
128                 throw new ArgumentException("background can not be translucent.");
129             }
130             if (((foreground >> 24) & 0xff) < 255)
131             {
132                 // If the foreground is translucent, composite the foreground over the background
133                 foreground = CompositeColors(foreground, background);
134             }
135
136             double luminance1 = CalculateLuminance(foreground) + 0.05;
137             double luminance2 = CalculateLuminance(background) + 0.05;
138
139             // Now return the lighter luminance divided by the darker luminance
140             return Math.Max(luminance1, luminance2) / Math.Min(luminance1, luminance2);
141         }
142
143         /// <summary>
144         /// Set the alpha component of color to be alpha.
145         /// </summary>
146         public static int SetAlphaComponent(int color, int alpha)
147         {
148             if (alpha < 0 || alpha > 255)
149             {
150                 throw new ArgumentException("alpha must be between 0 and 255.");
151             }
152
153             return (color & 0x00ffffff) | (alpha << 24);
154         }
155
156         /// <summary>
157         /// Calculates the minimum alpha value which can be applied to foreground so that would
158         /// have a contrast value of at least minContrastRatio when compared to
159         /// background.
160         ///
161         /// param foreground       the foreground color
162         /// param background       the opaque background color
163         /// param minContrastRatio the minimum contrast ratio
164         /// return the alpha value in the range 0-255, or -1 if no value could be calculated
165         /// </summary>
166         public static int CalculateMinimumAlpha(int foreground, int background, float minContrastRatio)
167         {
168             if (((background >> 24) & 0xff) != 255)
169             {
170                 throw new ArgumentException("background can not be translucent");
171             }
172
173             // First lets check that a fully opaque foreground has sufficient contrast
174             int testForeground = SetAlphaComponent(foreground, 255);
175             double testRatio = CalculateContrast(testForeground, background);
176
177             if (testRatio < minContrastRatio)
178             {
179                 // Fully opaque foreground does not have sufficient contrast, return error
180                 return -1;
181             }
182
183             // Binary search to find a value with the minimum value which provides sufficient contrast
184             int numIterations = 0;
185             int minAlpha = 0;
186             int maxAlpha = 255;
187
188             while (numIterations <= minAlphaSearchMaxIterations &&
189                     (maxAlpha - minAlpha) > minAlphaSearchPrecision)
190             {
191                 int testAlpha = (minAlpha + maxAlpha) / 2;
192
193                 testForeground = SetAlphaComponent(foreground, testAlpha);
194                 testRatio = CalculateContrast(testForeground, background);
195                 if (testRatio < minContrastRatio)
196                 {
197                     minAlpha = testAlpha;
198                 }
199                 else
200                 {
201                     maxAlpha = testAlpha;
202                 }
203
204                 numIterations++;
205             }
206
207             // Conservatively return the max of the range of possible alphas, which is known to pass.
208             return maxAlpha;
209         }
210
211         public static void RgbToHsl(int red, int green, int blue, float[] hsl)
212         {
213             float floatRed = red / 255f;
214             float floatGreen = green / 255f;
215             float floatBlue = blue / 255f;
216             float max = Math.Max(floatRed, Math.Max(floatGreen, floatBlue));
217             float min = Math.Min(floatRed, Math.Min(floatGreen, floatBlue));
218             float deltaMaxMin = max - min;
219             float hue, saturation;
220             float lightness = (max + min) / 2f;
221             if (max == min)
222             {
223                 // Monochromatic
224                 hue = saturation = 0f;
225             }
226             else
227             {
228                 if (max == floatRed)
229                 {
230                     hue = ((floatGreen - floatBlue) / deltaMaxMin) % 6f;
231                 }
232                 else if (max == floatGreen)
233                 {
234                     hue = ((floatBlue - floatRed) / deltaMaxMin) + 2f;
235                 }
236                 else
237                 {
238                     hue = ((floatRed - floatGreen) / deltaMaxMin) + 4f;
239                 }
240                 saturation = deltaMaxMin / (1f - Math.Abs(2f * lightness - 1f));
241             }
242             hsl[0] = ((hue * 60f) + 360f) % 360f;
243             hsl[1] = saturation;
244             hsl[2] = lightness;
245         }
246
247         public static int HslToRgb(float[] hsl)
248         {
249             float hue = hsl[0];
250             float saturation = hsl[1];
251             float lightness = hsl[2];
252             float constC = (1f - Math.Abs(2 * lightness - 1f)) * saturation;
253             float constM = lightness - 0.5f * constC;
254             float constX = constC * (1f - Math.Abs((hue / 60f % 2f) - 1f));
255             int hueSegment = (int)hue / 60;
256             int red = 0, green = 0, blue = 0;
257             switch (hueSegment)
258             {
259                 case 0:
260                     red = (int)Math.Round(255 * (constC + constM));
261                     green = (int)Math.Round(255 * (constX + constM));
262                     blue = (int)Math.Round(255 * constM);
263                     break;
264                 case 1:
265                     red = (int)Math.Round(255 * (constX + constM));
266                     green = (int)Math.Round(255 * (constC + constM));
267                     blue = (int)Math.Round(255 * constM);
268                     break;
269                 case 2:
270                     red = (int)Math.Round(255 * constM);
271                     green = (int)Math.Round(255 * (constC + constM));
272                     blue = (int)Math.Round(255 * (constX + constM));
273                     break;
274                 case 3:
275                     red = (int)Math.Round(255 * constM);
276                     green = (int)Math.Round(255 * (constX + constM));
277                     blue = (int)Math.Round(255 * (constC + constM));
278                     break;
279                 case 4:
280                     red = (int)Math.Round(255 * (constX + constM));
281                     green = (int)Math.Round(255 * constM);
282                     blue = (int)Math.Round(255 * (constC + constM));
283                     break;
284                 case 5:
285                 case 6:
286                     red = (int)Math.Round(255 * (constC + constM));
287                     green = (int)Math.Round(255 * constM);
288                     blue = (int)Math.Round(255 * (constX + constM));
289                     break;
290             }
291             red = Math.Max(0, Math.Min(255, red));
292             green = Math.Max(0, Math.Min(255, green));
293             blue = Math.Max(0, Math.Min(255, blue));
294
295             //ARGB Encoding
296             return (255 << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
297         }
298
299         public static uint GetBytesPerPixel(PixelFormat pixelFormat)
300         {
301             switch (pixelFormat)
302             {
303                 case PixelFormat.L8:
304                 case PixelFormat.A8:
305                 {
306                     return 1;
307                 }
308
309                 case PixelFormat.LA88:
310                 case PixelFormat.RGB565:
311                 case PixelFormat.RGBA4444:
312                 case PixelFormat.RGBA5551:
313                 case PixelFormat.BGR565:
314                 case PixelFormat.BGRA4444:
315                 case PixelFormat.BGRA5551:
316                 {
317                     return 2;
318                 }
319
320                 case PixelFormat.RGB888:
321                 {
322                     return 3;
323                 }
324
325                 case PixelFormat.RGB8888:
326                 case PixelFormat.BGR8888:
327                 case PixelFormat.RGBA8888:
328                 case PixelFormat.BGRA8888:
329                 {
330                     return 4;
331                 }
332                 default:
333                     Tizen.Log.Error("Palette", "Invalied PixelFormat(" + pixelFormat + ") has been givien \n");
334                     return 0;
335             }
336         }
337
338         /// <summary>
339         /// return luma value according to to XYZ color space in the range 0.0 - 1.0
340         /// </summary>
341         private static int CompositeAlpha(int foregroundAlpha, int backgroundAlpha)
342         {
343             return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
344         }
345
346         private static int CompositeComponent(int fgC, int fgA, int bgC, int bgA, int alpha)
347         {
348             if (alpha == 0) return 0;
349             return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (alpha * 0xFF);
350         }
351     }
352 }