Line data Source code
1 : /**
2 : Copyright (c) 2023-2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #ifndef CORE_GEOM_SPCOLORCAM16_H_
24 : #define CORE_GEOM_SPCOLORCAM16_H_
25 :
26 : #include "SPColor.h"
27 :
28 : namespace STAPPLER_VERSIONIZED stappler::geom {
29 :
30 : // Использовать float или double для расчётов Cam16
31 : using Cam16Float = float;
32 :
33 : struct ViewingConditions {
34 : static const ViewingConditions DEFAULT;
35 :
36 : using Float = Cam16Float;
37 :
38 612667 : static constexpr Float YFromLstar(Float lstar) {
39 612667 : if (lstar > 8.0) {
40 589255 : Float cube_root = (lstar + 16.0) / 116.0;
41 589255 : Float cube = cube_root * cube_root * cube_root;
42 589255 : return cube * 100.0;
43 : } else {
44 23412 : return lstar / (24389.0 / 27.0) * 100.0;
45 : }
46 : }
47 :
48 : static constexpr ViewingConditions create(const Float _white_point[3], const Float _adapting_luminance, const Float _background_lstar,
49 : const Float _surround, const bool _discounting_illuminant) {
50 : const Float background_lstar_corrected = (_background_lstar < 30.0) ? 30.0 : _background_lstar;
51 : const Float rgb_w[3] = {
52 : Float(0.401288 * _white_point[0] + 0.650173 * _white_point[1] - 0.051461 * _white_point[2]),
53 : Float(-0.250268 * _white_point[0] + 1.204414 * _white_point[1] + 0.045854 * _white_point[2]),
54 : Float(-0.002079 * _white_point[0] + 0.048952 * _white_point[1] + 0.953127 * _white_point[2])
55 : };
56 : const Float f = 0.8 + (_surround / 10.0);
57 : const Float _c = f >= 0.9
58 : ? std::lerp(Float(0.59), Float(0.69), Float((f - 0.9) * 10.0))
59 : : std::lerp(Float(0.525), Float(0.59), Float((f - 0.8) * 10.0));
60 : Float d = _discounting_illuminant ? 1.0 : f * (1.0 - ((1.0 / 3.6) * std::exp(Float((-_adapting_luminance - 42.0) / 92.0))));
61 : d = d > 1.0 ? 1.0 : d < 0.0 ? 0.0 : d;
62 : const Float nc = f;
63 : const Float _rgb_d[3] = {
64 : Float(d * (100.0 / rgb_w[0]) + 1.0 - d),
65 : Float(d * (100.0 / rgb_w[1]) + 1.0 - d),
66 : Float(d * (100.0 / rgb_w[2]) + 1.0 - d)
67 : };
68 :
69 : const Float k = 1.0 / (5.0 * _adapting_luminance + 1.0);
70 : const Float k4 = k * k * k * k;
71 : const Float k4f = 1.0 - k4;
72 : const Float _fl = (k4 * _adapting_luminance) + (0.1 * k4f * k4f * std::pow(Float(5.0 * _adapting_luminance), Float(1.0 / 3.0)));
73 : const Float _fl_root = std::pow(Float(_fl), Float(0.25));
74 : const Float n = YFromLstar(background_lstar_corrected) / _white_point[1];
75 : const Float _z = 1.48 + std::sqrt(Float(n));
76 : const Float _nbb = 0.725 / std::pow(Float(n), Float(0.2));
77 : const Float _ncb = _nbb;
78 : const Float rgb_a_factors[3] = {
79 : std::pow(Float(_fl * _rgb_d[0] * rgb_w[0] / 100.0), Float(0.42)),
80 : std::pow(Float(_fl * _rgb_d[1] * rgb_w[1] / 100.0), Float(0.42)),
81 : std::pow(Float(_fl * _rgb_d[2] * rgb_w[2] / 100.0), Float(0.42))
82 : };
83 : const Float rgb_a[3] = {
84 : Float(400.0 * rgb_a_factors[0] / (rgb_a_factors[0] + 27.13)),
85 : Float(400.0 * rgb_a_factors[1] / (rgb_a_factors[1] + 27.13)),
86 : Float(400.0 * rgb_a_factors[2] / (rgb_a_factors[2] + 27.13))
87 : };
88 : const Float _aw = (40.0 * rgb_a[0] + 20.0 * rgb_a[1] + rgb_a[2]) / 20.0 * _nbb;
89 : return ViewingConditions{
90 : _adapting_luminance, background_lstar_corrected, _surround, _discounting_illuminant,
91 : n, _aw, _nbb, _ncb, _c, nc, _fl, _fl_root, _z,
92 : { _white_point[0], _white_point[1], _white_point[2] },
93 : { _rgb_d[0], _rgb_d[1], _rgb_d[2] }
94 : };
95 : }
96 :
97 : Float adapting_luminance = 0.0;
98 : Float background_lstar = 0.0;
99 : Float surround = 0.0;
100 : bool discounting_illuminant = false;
101 : Float background_y_to_white_point_y = 0.0;
102 : Float aw = 0.0;
103 : Float nbb = 0.0;
104 : Float ncb = 0.0;
105 : Float c = 0.0;
106 : Float n_c = 0.0;
107 : Float fl = 0.0;
108 : Float fl_root = 0.0;
109 : Float z = 0.0;
110 : Float white_point[3] = { 0.0, 0.0, 0.0 };
111 : Float rgb_d[3] = { 0.0, 0.0, 0.0 };
112 : };
113 :
114 : #ifndef __LCC__
115 :
116 : constexpr ViewingConditions ViewingConditions::DEFAULT = {
117 : 11.725676537, 50.000000000, 2.000000000,
118 : false,
119 : 0.184186503, 29.981000900, 1.016919255, 1.016919255, 0.689999998,
120 : 1.000000000, 0.388481468, 0.789482653, 1.909169555,
121 : { 95.047, 100.0, 108.883 },
122 : { 1.021177769, 0.986307740, 0.933960497 }
123 : };
124 :
125 : #endif
126 :
127 : struct Cam16 {
128 : using Float = ViewingConditions::Float;
129 :
130 : static constexpr Float linearized(const int rgb_component) {
131 : Float normalized = rgb_component / 255.0;
132 : if (normalized <= 0.040449936) {
133 : return normalized / 12.92 * 100.0;
134 : } else {
135 : return std::pow(Float((normalized + 0.055) / 1.055), Float(2.4)) * 100.0;
136 : }
137 : }
138 :
139 675 : static constexpr Float linearized(const Float normalized) {
140 675 : if (normalized <= 0.040449936) {
141 25 : return normalized / 12.92 * 100.0;
142 : } else {
143 650 : return std::pow(Float((normalized + 0.055) / 1.055), Float(2.4)) * 100.0;
144 : }
145 : }
146 :
147 958077 : static constexpr Float sanitizeDegrees(const Float degrees) {
148 958077 : if (degrees < 0.0) {
149 125 : return std::fmod(degrees, Float(360.0)) + 360;
150 957952 : } else if (degrees >= 360.0) {
151 6368 : return std::fmod(degrees, Float(360.0));
152 : } else {
153 951584 : return degrees;
154 : }
155 : }
156 :
157 17668450 : static constexpr int signum(Float num) {
158 17668450 : if (num < 0) {
159 16341 : return -1;
160 17652109 : } else if (num == 0) {
161 25 : return 0;
162 : } else {
163 17652084 : return 1;
164 : }
165 : }
166 :
167 : static constexpr Cam16 create(const Color4F &color, const ViewingConditions &viewing_conditions) {
168 : const Float red_l = linearized(color.a);
169 : const Float green_l = linearized(color.g);
170 : const Float blue_l = linearized(color.b);
171 :
172 : const Float x = 0.41233895 * red_l + 0.35762064 * green_l + 0.18051042 * blue_l;
173 : const Float y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l;
174 : const Float z = 0.01932141 * red_l + 0.11916382 * green_l + 0.95034478 * blue_l;
175 :
176 : // Convert XYZ to 'cone'/'rgb' responses
177 : const Float r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z;
178 : const Float g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z;
179 : const Float b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z;
180 :
181 : // Discount illuminant.
182 : const Float r_d = viewing_conditions.rgb_d[0] * r_c;
183 : const Float g_d = viewing_conditions.rgb_d[1] * g_c;
184 : const Float b_d = viewing_conditions.rgb_d[2] * b_c;
185 :
186 : // Chromatic adaptation.
187 : const Float r_af = std::pow(Float(viewing_conditions.fl * std::fabs(r_d) / 100.0), Float(0.42));
188 : const Float g_af = std::pow(Float(viewing_conditions.fl * std::fabs(g_d) / 100.0), Float(0.42));
189 : const Float b_af = std::pow(Float(viewing_conditions.fl * std::fabs(b_d) / 100.0), Float(0.42));
190 : const Float r_a = signum(r_d) * 400.0 * r_af / (r_af + 27.13);
191 : const Float g_a = signum(g_d) * 400.0 * g_af / (g_af + 27.13);
192 : const Float b_a = signum(b_d) * 400.0 * b_af / (b_af + 27.13);
193 :
194 : // Redness-greenness
195 : const Float a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0;
196 : const Float b = (r_a + g_a - 2.0 * b_a) / 9.0;
197 : const Float u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0;
198 : const Float p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0;
199 :
200 : const Float radians = std::atan2(b, a);
201 : const Float degrees = radians * 180.0 / numbers::pi;
202 : const Float _hue = sanitizeDegrees(degrees);
203 : const Float hue_radians = _hue * numbers::pi / 180.0;
204 : const Float ac = p2 * viewing_conditions.nbb;
205 :
206 : const Float _j = 100.0 * std::pow(ac / viewing_conditions.aw, viewing_conditions.c * viewing_conditions.z);
207 : const Float _q = (4.0 / viewing_conditions.c) * std::sqrt(Float(_j / 100.0)) * (viewing_conditions.aw + 4.0) * viewing_conditions.fl_root;
208 : const Float hue_prime = _hue < 20.14 ? _hue + 360 : _hue;
209 : const Float e_hue = 0.25 * (std::cos(Float(hue_prime * numbers::pi / 180.0 + 2.0) + 3.8));
210 : const Float p1 = 50000.0 / 13.0 * e_hue * viewing_conditions.n_c * viewing_conditions.ncb;
211 : const Float t = p1 * std::sqrt(a * a + b * b) / (u + Float(0.305));
212 : const Float alpha = std::pow(t, Float(0.9)) * std::pow(Float(1.64) - std::pow(Float(0.29), viewing_conditions.background_y_to_white_point_y), Float(0.73));
213 : const Float c = alpha * std::sqrt(_j / Float(100.0));
214 : const Float _m = c * viewing_conditions.fl_root;
215 : const Float _s = 50.0 * sqrt((alpha * viewing_conditions.c) / (viewing_conditions.aw + 4.0));
216 : const Float _jstar = (1.0 + 100.0 * 0.007) * _j / (1.0 + 0.007 * _j);
217 : const Float mstar = 1.0 / 0.0228 * std::log(Float(1.0 + 0.0228 * _m));
218 : const Float _astar = mstar * std::cos(hue_radians);
219 : const Float _bstar = mstar * std::sin(hue_radians);
220 : return Cam16{_hue, c, _j, _q, _m, _s, _jstar, _astar, _bstar};
221 : }
222 :
223 125 : static constexpr Float LstarFromY(Float y) {
224 125 : Float yNormalized = y / 100.0;
225 125 : if (yNormalized <= 216.0 / 24389.0) {
226 25 : return (24389.0 / 27.0) * yNormalized;
227 : } else {
228 100 : return 116.0 * std::pow(Float(yNormalized), Float(1.0 / 3.0)) - 16.0;
229 : }
230 : }
231 :
232 100 : static constexpr Float LstarFromColor4F(const Color4F &color) {
233 100 : const Float red_l = linearized(color.r);
234 100 : const Float green_l = linearized(color.g);
235 100 : const Float blue_l = linearized(color.b);
236 100 : const Float y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l;
237 100 : return LstarFromY(y);
238 : }
239 :
240 : // ViewingConditions::DEFAULT is constexpr, some calculation may be optimized
241 :
242 125 : static Cam16 create(const Color4F &color) {
243 125 : const Float red_l = linearized(color.r);
244 125 : const Float green_l = linearized(color.g);
245 125 : const Float blue_l = linearized(color.b);
246 :
247 125 : const Float x = 0.41233895 * red_l + 0.35762064 * green_l + 0.18051042 * blue_l;
248 125 : const Float y = 0.2126 * red_l + 0.7152 * green_l + 0.0722 * blue_l;
249 125 : const Float z = 0.01932141 * red_l + 0.11916382 * green_l + 0.95034478 * blue_l;
250 :
251 : // Convert XYZ to 'cone'/'rgb' responses
252 125 : const Float r_c = 0.401288 * x + 0.650173 * y - 0.051461 * z;
253 125 : const Float g_c = -0.250268 * x + 1.204414 * y + 0.045854 * z;
254 125 : const Float b_c = -0.002079 * x + 0.048952 * y + 0.953127 * z;
255 :
256 : // Discount illuminant.
257 125 : const Float r_d = ViewingConditions::DEFAULT.rgb_d[0] * r_c;
258 125 : const Float g_d = ViewingConditions::DEFAULT.rgb_d[1] * g_c;
259 125 : const Float b_d = ViewingConditions::DEFAULT.rgb_d[2] * b_c;
260 :
261 : // Chromatic adaptation.
262 125 : const Float r_af = std::pow(Float(ViewingConditions::DEFAULT.fl * std::fabs(r_d) / 100.0), Float(0.42));
263 125 : const Float g_af = std::pow(Float(ViewingConditions::DEFAULT.fl * std::fabs(g_d) / 100.0), Float(0.42));
264 125 : const Float b_af = std::pow(Float(ViewingConditions::DEFAULT.fl * std::fabs(b_d) / 100.0), Float(0.42));
265 125 : const Float r_a = signum(r_d) * 400.0 * r_af / (r_af + 27.13);
266 125 : const Float g_a = signum(g_d) * 400.0 * g_af / (g_af + 27.13);
267 125 : const Float b_a = signum(b_d) * 400.0 * b_af / (b_af + 27.13);
268 :
269 : // Redness-greenness
270 125 : const Float a = (11.0 * r_a + -12.0 * g_a + b_a) / 11.0;
271 125 : const Float b = (r_a + g_a - 2.0 * b_a) / 9.0;
272 125 : const Float u = (20.0 * r_a + 20.0 * g_a + 21.0 * b_a) / 20.0;
273 125 : const Float p2 = (40.0 * r_a + 20.0 * g_a + b_a) / 20.0;
274 :
275 125 : const Float radians = std::atan2(b, a);
276 125 : const Float degrees = radians * 180.0 / numbers::pi;
277 125 : const Float _hue = sanitizeDegrees(degrees);
278 125 : const Float hue_radians = _hue * numbers::pi / 180.0;
279 125 : const Float ac = p2 * ViewingConditions::DEFAULT.nbb;
280 :
281 125 : const Float _j = 100.0 * std::pow(ac / ViewingConditions::DEFAULT.aw, ViewingConditions::DEFAULT.c * ViewingConditions::DEFAULT.z);
282 125 : const Float _q = (4.0 / ViewingConditions::DEFAULT.c) * std::sqrt(Float(_j / 100.0)) * (ViewingConditions::DEFAULT.aw + 4.0) * ViewingConditions::DEFAULT.fl_root;
283 125 : const Float hue_prime = _hue < 20.14 ? _hue + 360 : _hue;
284 125 : const Float e_hue = 0.25 * (std::cos(Float(hue_prime * numbers::pi / 180.0 + 2.0)) + 3.8);
285 125 : const Float p1 = 50000.0 / 13.0 * e_hue * ViewingConditions::DEFAULT.n_c * ViewingConditions::DEFAULT.ncb;
286 125 : const Float t = p1 * std::sqrt(a * a + b * b) / (u + Float(0.305));
287 125 : const Float tmpA = std::pow(Float(1.64) - std::pow(Float(0.29), ViewingConditions::DEFAULT.background_y_to_white_point_y), Float(0.73));
288 125 : const Float tmpB = std::pow(t, Float(0.9));
289 125 : const Float alpha = tmpB * tmpA;
290 125 : const Float c = alpha * std::sqrt(_j / Float(100.0));
291 125 : const Float _m = c * ViewingConditions::DEFAULT.fl_root;
292 125 : const Float _s = 50.0 * sqrt((alpha * ViewingConditions::DEFAULT.c) / (ViewingConditions::DEFAULT.aw + 4.0));
293 125 : const Float _jstar = (1.0 + 100.0 * 0.007) * _j / (1.0 + 0.007 * _j);
294 125 : const Float mstar = 1.0 / 0.0228 * std::log(Float(1.0 + 0.0228 * _m));
295 125 : const Float _astar = mstar * std::cos(hue_radians);
296 125 : const Float _bstar = mstar * std::sin(hue_radians);
297 125 : return Cam16{_hue, c, _j, _q, _m, _s, _jstar, _astar, _bstar};
298 : }
299 :
300 : Float hue = 0.0;
301 : Float chroma = 0.0;
302 : Float j = 0.0;
303 : Float q = 0.0;
304 : Float m = 0.0;
305 : Float s = 0.0;
306 :
307 : Float jstar = 0.0;
308 : Float astar = 0.0;
309 : Float bstar = 0.0;
310 : };
311 :
312 : }
313 :
314 : #endif /* CORE_GEOM_SPCOLORCAM16_H_ */
|