Extended Color: ColorMatrix should support smaller matrices and be constexpr
[WebKit-https.git] / Source / WebCore / platform / graphics / ColorUtilities.cpp
1 /*
2  * Copyright (C) 2017, 2020 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "ColorUtilities.h"
28
29 #include "ColorComponents.h"
30 #include "ColorMatrix.h"
31
32 namespace WebCore {
33
34 bool areEssentiallyEqual(const ColorComponents<float>& a, const ColorComponents<float>& b)
35 {
36     return WTF::areEssentiallyEqual(a.components[0], b.components[0])
37         && WTF::areEssentiallyEqual(a.components[1], b.components[1])
38         && WTF::areEssentiallyEqual(a.components[2], b.components[2])
39         && WTF::areEssentiallyEqual(a.components[3], b.components[3]);
40 }
41
42 // These are the standard sRGB <-> linearRGB conversion functions (https://en.wikipedia.org/wiki/SRGB).
43 float linearToRGBColorComponent(float c)
44 {
45     if (c < 0.0031308f)
46         return 12.92f * c;
47
48     return clampTo<float>(1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f, 0, 1);
49 }
50
51 float rgbToLinearColorComponent(float c)
52 {
53     if (c <= 0.04045f)
54         return c / 12.92f;
55
56     return clampTo<float>(std::pow((c + 0.055f) / 1.055f, 2.4f), 0, 1);
57 }
58
59 ColorComponents<float> rgbToLinearComponents(const ColorComponents<float>& RGBColor)
60 {
61     return {
62         rgbToLinearColorComponent(RGBColor.components[0]),
63         rgbToLinearColorComponent(RGBColor.components[1]),
64         rgbToLinearColorComponent(RGBColor.components[2]),
65         RGBColor.components[3]
66     };
67 }
68
69 ColorComponents<float> linearToRGBComponents(const ColorComponents<float>& linearRGB)
70 {
71     return {
72         linearToRGBColorComponent(linearRGB.components[0]),
73         linearToRGBColorComponent(linearRGB.components[1]),
74         linearToRGBColorComponent(linearRGB.components[2]),
75         linearRGB.components[3]
76     };
77 }
78
79 static ColorComponents<float> xyzToLinearSRGB(const ColorComponents<float>& XYZComponents)
80 {
81     // https://en.wikipedia.org/wiki/SRGB
82     // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
83     constexpr ColorMatrix<3, 3> xyzToLinearSRGBMatrix {
84          3.2404542f, -1.5371385f, -0.4985314f,
85         -0.9692660f,  1.8760108f,  0.0415560f,
86          0.0556434f, -0.2040259f,  1.0572252f
87     };
88     return xyzToLinearSRGBMatrix.transformedColorComponents(XYZComponents);
89 }
90
91 static ColorComponents<float> linearSRGBToXYZ(const ColorComponents<float>& XYZComponents)
92 {
93     // https://en.wikipedia.org/wiki/SRGB
94     // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
95     constexpr ColorMatrix<3, 3> linearSRGBToXYZMatrix {
96         0.4124564f,  0.3575761f,  0.1804375f,
97         0.2126729f,  0.7151522f,  0.0721750f,
98         0.0193339f,  0.1191920f,  0.9503041f
99     };
100     return linearSRGBToXYZMatrix.transformedColorComponents(XYZComponents);
101 }
102
103 static ColorComponents<float> XYZToLinearP3(const ColorComponents<float>& XYZComponents)
104 {
105     // https://drafts.csswg.org/css-color/#color-conversion-code
106     constexpr ColorMatrix<3, 3> xyzToLinearSRGBMatrix {
107          2.493496911941425f,  -0.9313836179191239f, -0.4027107844507168f,
108         -0.8294889695615747f,  1.7626640603183463f,  0.0236246858419436f,
109          0.0358458302437845f, -0.0761723892680418f,  0.9568845240076872f
110     };
111     return xyzToLinearSRGBMatrix.transformedColorComponents(XYZComponents);
112 }
113
114 static ColorComponents<float> linearP3ToXYZ(const ColorComponents<float>& XYZComponents)
115 {
116     // https://drafts.csswg.org/css-color/#color-conversion-code
117     constexpr ColorMatrix<3, 3> linearP3ToXYZMatrix {
118         0.4865709486482162f, 0.2656676931690931f, 0.198217285234363f,
119         0.2289745640697488f, 0.6917385218365064f, 0.079286914093745f,
120         0.0f,                0.0451133818589026f, 1.043944368900976f
121     };
122     return linearP3ToXYZMatrix.transformedColorComponents(XYZComponents);
123 }
124
125 ColorComponents<float> p3ToSRGB(const ColorComponents<float>& p3)
126 {
127     auto linearP3 = rgbToLinearComponents(p3);
128     auto xyz = linearP3ToXYZ(linearP3);
129     auto linearSRGB = xyzToLinearSRGB(xyz);
130     return linearToRGBComponents(linearSRGB);
131 }
132
133 ColorComponents<float> sRGBToP3(const ColorComponents<float>& sRGB)
134 {
135     auto linearSRGB = rgbToLinearComponents(sRGB);
136     auto xyz = linearSRGBToXYZ(linearSRGB);
137     auto linearP3 = XYZToLinearP3(xyz);
138     return linearToRGBComponents(linearP3);
139 }
140
141 float lightness(const ColorComponents<float>& sRGBCompontents)
142 {
143     auto [r, g, b, a] = sRGBCompontents;
144
145     float max = std::max({ r, g, b });
146     float min = std::min({ r, g, b });
147
148     return 0.5f * (max + min);
149 }
150
151 // This is similar to sRGBToLinearColorComponent but for some reason
152 // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
153 // doesn't use the standard sRGB -> linearRGB threshold of 0.04045.
154 static float sRGBToLinearColorComponentForLuminance(float c)
155 {
156     if (c <= 0.03928f)
157         return c / 12.92f;
158
159     return clampTo<float>(std::pow((c + 0.055f) / 1.055f, 2.4f), 0, 1);
160 }
161
162 float luminance(const ColorComponents<float>& sRGBComponents)
163 {
164     // Values from https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
165     return 0.2126f * sRGBToLinearColorComponentForLuminance(sRGBComponents.components[0])
166         + 0.7152f * sRGBToLinearColorComponentForLuminance(sRGBComponents.components[1])
167         + 0.0722f * sRGBToLinearColorComponentForLuminance(sRGBComponents.components[2]);
168 }
169
170 float contrastRatio(const ColorComponents<float>& componentsA, const ColorComponents<float>& componentsB)
171 {
172     // Uses the WCAG 2.0 definition of contrast ratio.
173     // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
174     float lighterLuminance = luminance(componentsA);
175     float darkerLuminance = luminance(componentsB);
176
177     if (lighterLuminance < darkerLuminance)
178         std::swap(lighterLuminance, darkerLuminance);
179
180     return (lighterLuminance + 0.05) / (darkerLuminance + 0.05);
181 }
182
183 ColorComponents<float> sRGBToHSL(const ColorComponents<float>& sRGBCompontents)
184 {
185     // http://en.wikipedia.org/wiki/HSL_color_space.
186     auto [r, g, b, alpha] = sRGBCompontents;
187
188     float max = std::max({ r, g, b });
189     float min = std::min({ r, g, b });
190     float chroma = max - min;
191
192     float hue;
193     if (!chroma)
194         hue = 0;
195     else if (max == r)
196         hue = (60.0f * ((g - b) / chroma)) + 360.0f;
197     else if (max == g)
198         hue = (60.0f * ((b - r) / chroma)) + 120.0f;
199     else
200         hue = (60.0f * ((r - g) / chroma)) + 240.0f;
201
202     if (hue >= 360.0f)
203         hue -= 360.0f;
204
205     hue /= 360.0f;
206
207     float lightness = 0.5f * (max + min);
208     float saturation;
209     if (!chroma)
210         saturation = 0;
211     else if (lightness <= 0.5f)
212         saturation = (chroma / (max + min));
213     else
214         saturation = (chroma / (2.0f - (max + min)));
215
216     return {
217         hue,
218         saturation,
219         lightness,
220         alpha
221     };
222 }
223
224 // Hue is in the range 0-6, other args in 0-1.
225 static float calcHue(float temp1, float temp2, float hueVal)
226 {
227     if (hueVal < 0.0f)
228         hueVal += 6.0f;
229     else if (hueVal >= 6.0f)
230         hueVal -= 6.0f;
231     if (hueVal < 1.0f)
232         return temp1 + (temp2 - temp1) * hueVal;
233     if (hueVal < 3.0f)
234         return temp2;
235     if (hueVal < 4.0f)
236         return temp1 + (temp2 - temp1) * (4.0f - hueVal);
237     return temp1;
238 }
239
240 // Explanation of this algorithm can be found in the CSS Color 4 Module
241 // specification at https://drafts.csswg.org/css-color-4/#hsl-to-rgb with
242 // further explanation available at http://en.wikipedia.org/wiki/HSL_color_space
243 ColorComponents<float> hslToSRGB(const ColorComponents<float>& hslColor)
244 {
245     float hue = hslColor.components[0];
246     float saturation = hslColor.components[1];
247     float lightness = hslColor.components[2];
248
249     // Convert back to RGB.
250     if (!saturation) {
251         return {
252             lightness,
253             lightness,
254             lightness,
255             hslColor.components[3]
256         };
257     }
258     
259     float temp2 = lightness <= 0.5f ? lightness * (1.0f + saturation) : lightness + saturation - lightness * saturation;
260     float temp1 = 2.0f * lightness - temp2;
261     
262     hue *= 6.0f; // calcHue() wants hue in the 0-6 range.
263     return {
264         calcHue(temp1, temp2, hue + 2.0f),
265         calcHue(temp1, temp2, hue),
266         calcHue(temp1, temp2, hue - 2.0f),
267         hslColor.components[3]
268     };
269 }
270
271 ColorComponents<float> premultiplied(const ColorComponents<float>& sRGBComponents)
272 {
273     auto [r, g, b, a] = sRGBComponents;
274     return {
275         r * a,
276         g * a,
277         b * a,
278         a
279     };
280 }
281
282 } // namespace WebCore