1 /*
2 * Copyright (c) 2019-2020 Kevin Townsend (KTOWN)
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <math.h>
8 #include <errno.h>
9 #include <string.h>
10 #include <zsl/colorimetry.h>
11
12 #ifndef M_PI
13 #define M_PI (3.14159265358979323846)
14 #endif
15
16 /**
17 * 7x7 matrix used for conversion from (x,y) or (u',v') to CCT, Duv.
18 *
19 * Source: "Calculation of CCT and Duv and Practical Conversion Formulae",
20 * Yoshi Ohno, Presentation CORM 2011.
21 */
22 static const zsl_real_t zsl_clr_conv_xyy_cct_ohno_2011_data[7][7] = {
23 /** kiO..ki6[0] */
24 { -1.77348E-01,
25 1.115559E+00,
26 -1.5008606E+00,
27 9.750013E-01,
28 -3.307009E-01,
29 5.6061400E-02,
30 -3.7146000E-03 },
31
32 /** ki0..ki6[1] */
33 { 5.308409E-04,
34 2.1595434E-03,
35 -4.3534788E-03,
36 3.6196568E-03,
37 -1.589747E-03,
38 3.5700160E-04,
39 -3.2325500E-05 },
40
41 /** ki0..ki6[2] */
42 { -8.58308927E-01,
43 1.964980251E+00,
44 -1.873907584E+00,
45 9.53570888E-01,
46 -2.73172022E-01,
47 4.17781315E-02,
48 -2.6653835E-03 },
49
50 /** ki0..ki6[3] */
51 { -2.3275027E+02,
52 1.49284136E+03,
53 -2.7966888E+03,
54 2.51170136E+03,
55 -1.1785121E+03,
56 2.7183365E+02,
57 -2.3524950E+01 },
58
59 /** ki0..ki6[4] */
60 { -5.926850606E+08,
61 1.34488160614E+09,
62 -1.27141290956E+09,
63 6.40976356945E+08,
64 -1.81749963507E+08,
65 2.7482732935E+07,
66 -1.731364909E+06 },
67
68 /** ki0..ki6[5] */
69 { -2.3758158E+06,
70 3.89561742E+06,
71 -2.65299138E+06,
72 9.60532935E+05,
73 -1.9500061E+05,
74 2.10468274E+04,
75 -9.4353083E+02 },
76
77 /** ki0..ki6[6] */
78 { 2.8151771E+06,
79 -4.11436958E+06,
80 2.48526954E+06,
81 -7.93406005E+05,
82 1.4101538E+05,
83 -1.321007E+04,
84 5.0857956E+02 }
85 };
86
87 /**
88 * 7x1 matrix used for conversion from (x,y) or (u',v') to CCT, Duv.
89 *
90 * Source: Yoshi Ohno (2014)
91 * Practical Use and Calculation of CCT and Duv
92 * LEUKOS, 10:1, 47-55, DOI: 10.1080/15502724.2014.839020
93 */
94 static const zsl_real_t zsl_clr_conv_xyy_cct_ohno_2014_data[7] = {
95 /** kO..k6 */
96 -0.471106,
97 1.925865,
98 -2.4243787,
99 1.5317403,
100 -0.5179722,
101 0.0893944,
102 -0.00616793,
103 };
104
105 /**
106 * Color temperature to (u,v) chromaticity lookup table for OHNO2014.
107 *
108 * 1000.00 K to 20000.00 K CT to (u,v) in 1.000% steps.
109 *
110 * NOTE: This table could be removed by calculating these values on
111 * demand, though at the expense of extra processing power.
112 */
113 static const zsl_real_t zsl_clr_conv_ct_uv_ohno_2014_data[304][3] = {
114 { 990.000000, 0.45042258, 0.35439942 },
115 { 1000.000000, 0.44806562, 0.35460468 },
116 { 1010.000000, 0.44573364, 0.35480622 },
117 { 1020.100000, 0.44340355, 0.35500602 },
118 { 1030.301000, 0.44107575, 0.35520396 },
119 { 1040.604010, 0.43875059, 0.35539995 },
120 { 1051.010050, 0.43642845, 0.35559388 },
121 { 1061.520151, 0.43410968, 0.35578566 },
122 { 1072.135352, 0.43179464, 0.35597519 },
123 { 1082.856706, 0.42948368, 0.35616235 },
124 { 1093.685273, 0.42717713, 0.35634706 },
125 { 1104.622125, 0.42487534, 0.35652920 },
126 { 1115.668347, 0.42257863, 0.35670867 },
127 { 1126.825030, 0.42028732, 0.35688537 },
128 { 1138.093280, 0.41800174, 0.35705919 },
129 { 1149.474213, 0.41572219, 0.35723003 },
130 { 1160.968955, 0.41344897, 0.35739778 },
131 { 1172.578645, 0.41118239, 0.35756233 },
132 { 1184.304431, 0.40892273, 0.35772358 },
133 { 1196.147476, 0.40667028, 0.35788143 },
134 { 1208.108950, 0.40442532, 0.35803576 },
135 { 1220.190040, 0.40218812, 0.35818647 },
136 { 1232.391940, 0.39995895, 0.35833346 },
137 { 1244.715860, 0.39773806, 0.35847662 },
138 { 1257.163018, 0.39552571, 0.35861583 },
139 { 1269.734649, 0.39332214, 0.35875101 },
140 { 1282.431995, 0.39112759, 0.35888203 },
141 { 1295.256315, 0.38894231, 0.35900881 },
142 { 1308.208878, 0.38676651, 0.35913122 },
143 { 1321.290967, 0.38460041, 0.35924918 },
144 { 1334.503877, 0.38244424, 0.35936257 },
145 { 1347.848915, 0.38029821, 0.35947130 },
146 { 1361.327404, 0.37816251, 0.35957526 },
147 { 1374.940679, 0.37603734, 0.35967435 },
148 { 1388.690085, 0.37392290, 0.35976848 },
149 { 1402.576986, 0.37181937, 0.35985754 },
150 { 1416.602756, 0.36972693, 0.35994144 },
151 { 1430.768784, 0.36764576, 0.36002008 },
152 { 1445.076471, 0.36557603, 0.36009337 },
153 { 1459.527236, 0.36351789, 0.36016121 },
154 { 1474.122509, 0.36147152, 0.36022352 },
155 { 1488.863734, 0.35943705, 0.36028020 },
156 { 1503.752371, 0.35741464, 0.36033117 },
157 { 1518.789895, 0.35540443, 0.36037634 },
158 { 1533.977794, 0.35340656, 0.36041562 },
159 { 1549.317572, 0.35142115, 0.36044894 },
160 { 1564.810747, 0.34944835, 0.36047620 },
161 { 1580.458855, 0.34748826, 0.36049735 },
162 { 1596.263443, 0.34554101, 0.36051229 },
163 { 1612.226078, 0.34360671, 0.36052096 },
164 { 1628.348338, 0.34168547, 0.36052328 },
165 { 1644.631822, 0.33977738, 0.36051918 },
166 { 1661.078140, 0.33788256, 0.36050861 },
167 { 1677.688921, 0.33600109, 0.36049149 },
168 { 1694.465811, 0.33413306, 0.36046777 },
169 { 1711.410469, 0.33227857, 0.36043738 },
170 { 1728.524573, 0.33043768, 0.36040027 },
171 { 1745.809819, 0.32861049, 0.36035639 },
172 { 1763.267917, 0.32679705, 0.36030570 },
173 { 1780.900597, 0.32499745, 0.36024813 },
174 { 1798.709603, 0.32321174, 0.36018365 },
175 { 1816.696699, 0.32143998, 0.36011222 },
176 { 1834.863666, 0.31968225, 0.36003381 },
177 { 1853.212302, 0.31793857, 0.35994837 },
178 { 1871.744425, 0.31620902, 0.35985588 },
179 { 1890.461869, 0.31449363, 0.35975630 },
180 { 1909.366488, 0.31279244, 0.35964963 },
181 { 1928.460153, 0.31110549, 0.35953583 },
182 { 1947.744755, 0.30943283, 0.35941488 },
183 { 1967.222202, 0.30777447, 0.35928678 },
184 { 1986.894424, 0.30613045, 0.35915151 },
185 { 2006.763368, 0.30450080, 0.35900906 },
186 { 2026.831002, 0.30288553, 0.35885943 },
187 { 2047.099312, 0.30128466, 0.35870263 },
188 { 2067.570305, 0.29969821, 0.35853865 },
189 { 2088.246008, 0.29812618, 0.35836750 },
190 { 2109.128468, 0.29656860, 0.35818918 },
191 { 2130.219753, 0.29502545, 0.35800373 },
192 { 2151.521951, 0.29349675, 0.35781114 },
193 { 2173.037170, 0.29198250, 0.35761144 },
194 { 2194.767542, 0.29048268, 0.35740465 },
195 { 2216.715217, 0.28899730, 0.35719081 },
196 { 2238.882369, 0.28752635, 0.35696993 },
197 { 2261.271193, 0.28606980, 0.35674206 },
198 { 2283.883905, 0.28462765, 0.35650723 },
199 { 2306.722744, 0.28319988, 0.35626548 },
200 { 2329.789971, 0.28178646, 0.35601685 },
201 { 2353.087871, 0.28038738, 0.35576139 },
202 { 2376.618750, 0.27900261, 0.35549915 },
203 { 2400.384937, 0.27763212, 0.35523019 },
204 { 2424.388787, 0.27627588, 0.35495455 },
205 { 2448.632675, 0.27493385, 0.35467229 },
206 { 2473.119001, 0.27360600, 0.35438348 },
207 { 2497.850191, 0.27229230, 0.35408818 },
208 { 2522.828693, 0.27099269, 0.35378646 },
209 { 2548.056980, 0.26970714, 0.35347839 },
210 { 2573.537550, 0.26843560, 0.35316403 },
211 { 2599.272926, 0.26717802, 0.35284347 },
212 { 2625.265655, 0.26593436, 0.35251678 },
213 { 2651.518311, 0.26470457, 0.35218403 },
214 { 2678.033494, 0.26348858, 0.35184532 },
215 { 2704.813829, 0.26228634, 0.35150073 },
216 { 2731.861968, 0.26109779, 0.35115034 },
217 { 2759.180587, 0.25992289, 0.35079423 },
218 { 2786.772393, 0.25876155, 0.35043251 },
219 { 2814.640117, 0.25761372, 0.35006527 },
220 { 2842.786518, 0.25647934, 0.34969259 },
221 { 2871.214384, 0.25535833, 0.34931457 },
222 { 2899.926527, 0.25425063, 0.34893132 },
223 { 2928.925793, 0.25315617, 0.34854293 },
224 { 2958.215051, 0.25207487, 0.34814950 },
225 { 2987.797201, 0.25100666, 0.34775114 },
226 { 3017.675173, 0.24995147, 0.34734794 },
227 { 3047.851925, 0.24890922, 0.34694002 },
228 { 3078.330444, 0.24787982, 0.34652748 },
229 { 3109.113749, 0.24686321, 0.34611043 },
230 { 3140.204886, 0.24585929, 0.34568897 },
231 { 3171.606935, 0.24486800, 0.34526322 },
232 { 3203.323004, 0.24388923, 0.34483329 },
233 { 3235.356234, 0.24292292, 0.34439929 },
234 { 3267.709797, 0.24196897, 0.34396133 },
235 { 3300.386895, 0.24102729, 0.34351952 },
236 { 3333.390764, 0.24009781, 0.34307398 },
237 { 3366.724671, 0.23918042, 0.34262482 },
238 { 3400.391918, 0.23827504, 0.34217215 },
239 { 3434.395837, 0.23738158, 0.34171610 },
240 { 3468.739795, 0.23649995, 0.34125677 },
241 { 3503.427193, 0.23563005, 0.34079429 },
242 { 3538.461465, 0.23477179, 0.34032876 },
243 { 3573.846080, 0.23392507, 0.33986030 },
244 { 3609.584541, 0.23308981, 0.33938903 },
245 { 3645.680386, 0.23226590, 0.33891506 },
246 { 3682.137190, 0.23145325, 0.33843851 },
247 { 3718.958562, 0.23065176, 0.33795950 },
248 { 3756.148148, 0.22986134, 0.33747814 },
249 { 3793.709629, 0.22908188, 0.33699454 },
250 { 3831.646725, 0.22831329, 0.33650882 },
251 { 3869.963193, 0.22755546, 0.33602110 },
252 { 3908.662824, 0.22680831, 0.33553148 },
253 { 3947.749453, 0.22607172, 0.33504009 },
254 { 3987.226947, 0.22534561, 0.33454702 },
255 { 4027.099217, 0.22462986, 0.33405241 },
256 { 4067.370209, 0.22392438, 0.33355635 },
257 { 4108.043911, 0.22322907, 0.33305896 },
258 { 4149.124350, 0.22254382, 0.33256035 },
259 { 4190.615594, 0.22186854, 0.33206062 },
260 { 4232.521750, 0.22120312, 0.33155989 },
261 { 4274.846967, 0.22054746, 0.33105826 },
262 { 4317.595437, 0.21990146, 0.33055584 },
263 { 4360.771391, 0.21926502, 0.33005274 },
264 { 4404.379105, 0.21863804, 0.32954906 },
265 { 4448.422896, 0.21802040, 0.32904489 },
266 { 4492.907125, 0.21741202, 0.32854036 },
267 { 4537.836196, 0.21681280, 0.32803555 },
268 { 4583.214558, 0.21622261, 0.32753057 },
269 { 4629.046704, 0.21564138, 0.32702551 },
270 { 4675.337171, 0.21506899, 0.32652048 },
271 { 4722.090543, 0.21450535, 0.32601557 },
272 { 4769.311448, 0.21395035, 0.32551087 },
273 { 4817.004562, 0.21340389, 0.32500648 },
274 { 4865.174608, 0.21286588, 0.32450249 },
275 { 4913.826354, 0.21233620, 0.32399900 },
276 { 4962.964618, 0.21181477, 0.32349609 },
277 { 5012.594264, 0.21130147, 0.32299385 },
278 { 5062.720206, 0.21079622, 0.32249236 },
279 { 5113.347409, 0.21029892, 0.32199172 },
280 { 5164.480883, 0.20980945, 0.32149201 },
281 { 5216.125691, 0.20932773, 0.32099330 },
282 { 5268.286948, 0.20885366, 0.32049569 },
283 { 5320.969818, 0.20838714, 0.31999925 },
284 { 5374.179516, 0.20792807, 0.31950405 },
285 { 5427.921311, 0.20747636, 0.31901018 },
286 { 5482.200524, 0.20703191, 0.31851771 },
287 { 5537.022530, 0.20659462, 0.31802671 },
288 { 5592.392755, 0.20616440, 0.31753726 },
289 { 5648.316682, 0.20574115, 0.31704942 },
290 { 5704.799849, 0.20532479, 0.31656326 },
291 { 5761.847848, 0.20491521, 0.31607885 },
292 { 5819.466326, 0.20451232, 0.31559626 },
293 { 5877.660989, 0.20411603, 0.31511555 },
294 { 5936.437599, 0.20372626, 0.31463678 },
295 { 5995.801975, 0.20334290, 0.31416000 },
296 { 6055.759995, 0.20296587, 0.31368529 },
297 { 6116.317595, 0.20259507, 0.31321269 },
298 { 6177.480771, 0.20223043, 0.31274227 },
299 { 6239.255579, 0.20187184, 0.31227407 },
300 { 6301.648135, 0.20151923, 0.31180815 },
301 { 6364.664616, 0.20117250, 0.31134456 },
302 { 6428.311262, 0.20083156, 0.31088335 },
303 { 6492.594375, 0.20049635, 0.31042456 },
304 { 6557.520318, 0.20016675, 0.30996824 },
305 { 6623.095522, 0.19984270, 0.30951444 },
306 { 6689.326477, 0.19952411, 0.30906320 },
307 { 6756.219742, 0.19921090, 0.30861455 },
308 { 6823.781939, 0.19890298, 0.30816854 },
309 { 6892.019758, 0.19860028, 0.30772521 },
310 { 6960.939956, 0.19830271, 0.30728459 },
311 { 7030.549355, 0.19801019, 0.30684672 },
312 { 7100.854849, 0.19772265, 0.30641163 },
313 { 7171.863398, 0.19744000, 0.30597935 },
314 { 7243.582032, 0.19716217, 0.30554991 },
315 { 7316.017852, 0.19688909, 0.30512334 },
316 { 7389.178030, 0.19662067, 0.30469967 },
317 { 7463.069811, 0.19635685, 0.30427892 },
318 { 7537.700509, 0.19609754, 0.30386113 },
319 { 7613.077514, 0.19584268, 0.30344630 },
320 { 7689.208289, 0.19559220, 0.30303447 },
321 { 7766.100372, 0.19534601, 0.30262565 },
322 { 7843.761376, 0.19510406, 0.30221986 },
323 { 7922.198989, 0.19486627, 0.30181712 },
324 { 8001.420979, 0.19463257, 0.30141745 },
325 { 8081.435189, 0.19440289, 0.30102086 },
326 { 8162.249541, 0.19417718, 0.30062736 },
327 { 8243.872036, 0.19395535, 0.30023697 },
328 { 8326.310757, 0.19373735, 0.29984970 },
329 { 8409.573864, 0.19352312, 0.29946555 },
330 { 8493.669603, 0.19331258, 0.29908454 },
331 { 8578.606299, 0.19310567, 0.29870668 },
332 { 8664.392362, 0.19290234, 0.29833196 },
333 { 8751.036286, 0.19270251, 0.29796040 },
334 { 8838.546648, 0.19250614, 0.29759200 },
335 { 8926.932115, 0.19231316, 0.29722677 },
336 { 9016.201436, 0.19212352, 0.29686470 },
337 { 9106.363450, 0.19193714, 0.29650580 },
338 { 9197.427085, 0.19175399, 0.29615006 },
339 { 9289.401356, 0.19157400, 0.29579749 },
340 { 9382.295369, 0.19139711, 0.29544808 },
341 { 9476.118323, 0.19122327, 0.29510184 },
342 { 9570.879506, 0.19105243, 0.29475875 },
343 { 9666.588301, 0.19088454, 0.29441882 },
344 { 9763.254184, 0.19071954, 0.29408203 },
345 { 9860.886726, 0.19055738, 0.29374839 },
346 { 9959.495593, 0.19039801, 0.29341788 },
347 { 10059.090549, 0.19024139, 0.29309050 },
348 { 10159.681455, 0.19008745, 0.29276624 },
349 { 10261.278269, 0.18993616, 0.29244509 },
350 { 10363.891052, 0.18978747, 0.29212704 },
351 { 10467.529963, 0.18964133, 0.29181207 },
352 { 10572.205262, 0.18949769, 0.29150019 },
353 { 10677.927315, 0.18935650, 0.29119137 },
354 { 10784.706588, 0.18921774, 0.29088560 },
355 { 10892.553654, 0.18908134, 0.29058288 },
356 { 11001.479190, 0.18894728, 0.29028318 },
357 { 11111.493982, 0.18881549, 0.28998650 },
358 { 11222.608922, 0.18868596, 0.28969281 },
359 { 11334.835011, 0.18855862, 0.28940211 },
360 { 11448.183361, 0.18843345, 0.28911437 },
361 { 11562.665195, 0.18831040, 0.28882959 },
362 { 11678.291847, 0.18818944, 0.28854774 },
363 { 11795.074766, 0.18807052, 0.28826881 },
364 { 11913.025513, 0.18795362, 0.28799277 },
365 { 12032.155768, 0.18783868, 0.28771962 },
366 { 12152.477326, 0.18772569, 0.28744934 },
367 { 12274.002099, 0.18761459, 0.28718190 },
368 { 12396.742120, 0.18750536, 0.28691728 },
369 { 12520.709541, 0.18739797, 0.28665548 },
370 { 12645.916637, 0.18729237, 0.28639646 },
371 { 12772.375803, 0.18718854, 0.28614022 },
372 { 12900.099561, 0.18708644, 0.28588672 },
373 { 13029.100557, 0.18698604, 0.28563595 },
374 { 13159.391562, 0.18688731, 0.28538789 },
375 { 13290.985478, 0.18679022, 0.28514251 },
376 { 13423.895333, 0.18669474, 0.28489980 },
377 { 13558.134286, 0.18660084, 0.28465974 },
378 { 13693.715629, 0.18650850, 0.28442231 },
379 { 13830.652785, 0.18641767, 0.28418747 },
380 { 13968.959313, 0.18632834, 0.28395522 },
381 { 14108.648906, 0.18624047, 0.28372553 },
382 { 14249.735395, 0.18615405, 0.28349838 },
383 { 14392.232749, 0.18606904, 0.28327375 },
384 { 14536.155077, 0.18598542, 0.28305161 },
385 { 14681.516628, 0.18590316, 0.28283194 },
386 { 14828.331794, 0.18582223, 0.28261473 },
387 { 14976.615112, 0.18574262, 0.28239994 },
388 { 15126.381263, 0.18566430, 0.28218756 },
389 { 15277.645076, 0.18558725, 0.28197757 },
390 { 15430.421526, 0.18551143, 0.28176994 },
391 { 15584.725742, 0.18543684, 0.28156465 },
392 { 15740.572999, 0.18536344, 0.28136168 },
393 { 15897.978729, 0.18529122, 0.28116100 },
394 { 16056.958516, 0.18522016, 0.28096260 },
395 { 16217.528101, 0.18515022, 0.28076646 },
396 { 16379.703382, 0.18508140, 0.28057254 },
397 { 16543.500416, 0.18501368, 0.28038083 },
398 { 16708.935420, 0.18494702, 0.28019131 },
399 { 16876.024775, 0.18488142, 0.28000395 },
400 { 17044.785022, 0.18481685, 0.27981873 },
401 { 17215.232873, 0.18475330, 0.27963564 },
402 { 17387.385201, 0.18469075, 0.27945464 },
403 { 17561.259053, 0.18462918, 0.27927573 },
404 { 17736.871644, 0.18456856, 0.27909886 },
405 { 17914.240360, 0.18450890, 0.27892404 },
406 { 18093.382764, 0.18445015, 0.27875122 },
407 { 18274.316592, 0.18439233, 0.27858040 },
408 { 18457.059757, 0.18433539, 0.27841154 },
409 { 18641.630355, 0.18427934, 0.27824464 },
410 { 18828.046659, 0.18422414, 0.27807967 },
411 { 19016.327125, 0.18416980, 0.27791660 },
412 { 19206.490396, 0.18411629, 0.27775542 },
413 { 19398.555300, 0.18406359, 0.27759611 },
414 { 19592.540853, 0.18401170, 0.27743864 },
415 { 19788.466262, 0.18396060, 0.27728300 },
416 { 19986.350925, 0.18391028, 0.27712917 },
417 { 20186.214434, 0.18386072, 0.27697712 }
418 };
419
420 #define OHNO2014_LOOKUP_RECS (sizeof(zsl_clr_conv_ct_uv_ohno_2014_data) / \
421 (sizeof(zsl_clr_conv_ct_uv_ohno_2014_data[0])))
422
423 int
zsl_clr_conv_spd_xyz(const struct zsl_clr_spd * spd,enum zsl_clr_obs obs,struct zsl_clr_xyz * xyz)424 zsl_clr_conv_spd_xyz(const struct zsl_clr_spd *spd, enum zsl_clr_obs obs,
425 struct zsl_clr_xyz *xyz)
426 {
427 int rc;
428 int matches;
429 unsigned int nm_idx;
430 const struct zsl_clr_obs_data *obs_data;
431
432 /* Clear the output values (used for internal manipulation). */
433 memset(xyz, 0, sizeof(*xyz));
434 matches = 0;
435
436 /*
437 * Realistically we should have at least 3 vals, but technically the
438 * calculations can be made with only one.
439 */
440 if (spd->size < 1) {
441 rc = -EINVAL;
442 goto err;
443 }
444
445 /* Get a reference to the standard observer CMF dataset. */
446 zsl_clr_obs_get(obs, &obs_data);
447
448 /* Sum contents of the spd. Only accept values from 360 to 830 nm. */
449 for (int i = 0; i < spd->size; i++) {
450 if ((spd->comps[i].nm > 359) && (spd->comps[i].nm < 831)) {
451 /* Round nm to the nearest 5 nm interval. */
452 nm_idx = ((spd->comps[i].nm - 360) / 5);
453 /* Accumulate tristimulus values. */
454 xyz->xyz_x += spd->comps[i].value *
455 obs_data->data[nm_idx].xyz_x;
456 xyz->xyz_y += spd->comps[i].value *
457 obs_data->data[nm_idx].xyz_y;
458 xyz->xyz_z += spd->comps[i].value *
459 obs_data->data[nm_idx].xyz_z;
460 matches++;
461 }
462 }
463
464 /* Avoid divide by zero error if no matches found. */
465 if (!matches) {
466 rc = -EINVAL;
467 goto err;
468 }
469
470 /* Divide the results by the number of valid components. */
471 xyz->xyz_x /= matches;
472 xyz->xyz_y /= matches;
473 xyz->xyz_z /= matches;
474
475 /* Scale output to Y=1.0 */
476 xyz->xyz_x /= xyz->xyz_y;
477 xyz->xyz_z /= xyz->xyz_y;
478 xyz->xyz_y = 1.0;
479
480 /* Set the observer model. */
481 xyz->observer = obs;
482
483 return 0;
484 err:
485 xyz->x_invalid = 1;
486 xyz->y_invalid = 1;
487 xyz->z_invalid = 1;
488 return rc;
489 }
490
491 int
zsl_clr_conv_xyy_xyz(struct zsl_clr_xyy * xyy,struct zsl_clr_xyz * xyz)492 zsl_clr_conv_xyy_xyz(struct zsl_clr_xyy *xyy, struct zsl_clr_xyz *xyz)
493 {
494 /*
495 * X = xY / y
496 * Y = Y
497 * Z = (1 - x - y) * Y
498 * ---------------
499 * y
500 */
501 xyz->xyz_x = xyy->xyy_x * xyy->xyy_Y / xyy->xyy_y;
502 xyz->xyz_y = xyy->xyy_Y;
503 xyz->xyz_z = (1.0 - xyy->xyy_x - xyy->xyy_y) * xyy->xyy_Y / xyy->xyy_y;
504 xyz->observer = xyy->observer;
505 xyz->illuminant = xyy->illuminant;
506
507 return 0;
508 }
509
510 int
zsl_clr_conv_xyz_xyy(struct zsl_clr_xyz * xyz,struct zsl_clr_xyy * xyy)511 zsl_clr_conv_xyz_xyy(struct zsl_clr_xyz *xyz, struct zsl_clr_xyy *xyy)
512 {
513 /*
514 * x = X/(X+Y+Z)
515 * y = Y/(X+Y+Z)
516 * Y = Y
517 */
518 xyy->xyy_x = xyz->xyz_x / (xyz->xyz_x + xyz->xyz_y + xyz->xyz_z);
519 xyy->xyy_y = xyz->xyz_y / (xyz->xyz_x + xyz->xyz_y + xyz->xyz_z);
520 xyy->xyy_Y = xyz->xyz_y;
521 xyy->observer = xyz->observer;
522 xyy->illuminant = xyz->illuminant;
523
524 return 0;
525 }
526
527 int
zsl_clr_conv_xyy_uv60(struct zsl_clr_xyy * xyy,struct zsl_clr_uv60 * uv)528 zsl_clr_conv_xyy_uv60(struct zsl_clr_xyy *xyy, struct zsl_clr_uv60 *uv)
529 {
530 /*
531 * u = 4x / (-2x + 12y + 3)
532 * v = 6y / (-2x + 12y + 3)
533 */
534 uv->uv60_u = 4 * xyy->xyy_x / ((2 * xyy->xyy_x * -1.0) +
535 (12 * xyy->xyy_y) + 3);
536 uv->uv60_v = 6 * xyy->xyy_y / ((2 * xyy->xyy_x * -1.0) +
537 (12 * xyy->xyy_y) + 3);
538 uv->observer = xyy->observer;
539 uv->illuminant = xyy->illuminant;
540
541 return 0;
542 }
543
544 int
zsl_clr_conv_xyz_uv60(struct zsl_clr_xyz * xyz,struct zsl_clr_uv60 * uv)545 zsl_clr_conv_xyz_uv60(struct zsl_clr_xyz *xyz, struct zsl_clr_uv60 *uv)
546 {
547 struct zsl_clr_xyy xyy;
548
549 /* First convert XYZ to the equivalent CIE 1931 xyY value. */
550 zsl_clr_conv_xyz_xyy(xyz, &xyy);
551
552 /* Now convert xyY to an equivalent CIE 1960 uv chromaticity. */
553 zsl_clr_conv_xyy_uv60(&xyy, uv);
554
555 return 0;
556 }
557
558 int
zsl_clr_conv_uv60_xyz(struct zsl_clr_uv60 * uv,struct zsl_clr_xyz * xyz)559 zsl_clr_conv_uv60_xyz(struct zsl_clr_uv60 *uv, struct zsl_clr_xyz *xyz)
560 {
561 struct zsl_clr_xyy xyy;
562
563 /* First convert to xyY. */
564 zsl_clr_conv_uv60_xyy(uv, &xyy);
565
566 /* Then convert xyY to XYZ. */
567 zsl_clr_conv_xyy_xyz(&xyy, xyz);
568
569 return 0;
570 }
571
572 int
zsl_clr_conv_uv60_xyy(struct zsl_clr_uv60 * uv,struct zsl_clr_xyy * xyy)573 zsl_clr_conv_uv60_xyy(struct zsl_clr_uv60 *uv, struct zsl_clr_xyy *xyy)
574 {
575 memset(xyy, 0, sizeof *xyy);
576
577 xyy->xyy_x = (3.0 * uv->uv60_u) / (2.0 * uv->uv60_u -
578 8.0 * uv->uv60_v + 4.0);
579 xyy->xyy_y = (2.0 * uv->uv60_v) / (2.0 * uv->uv60_u -
580 8.0 * uv->uv60_v + 4.0);
581 xyy->xyy_Y = 1.0;
582 xyy->observer = uv->observer;
583 xyy->illuminant = uv->illuminant;
584
585 return 0;
586 }
587
588 int
zsl_clr_conv_uv60_uv76(struct zsl_clr_uv60 * uv60,struct zsl_clr_uv76 * uv76)589 zsl_clr_conv_uv60_uv76(struct zsl_clr_uv60 *uv60, struct zsl_clr_uv76 *uv76)
590 {
591 uv76->uv76_u = uv60->uv60_u;
592 uv76->uv76_v = uv60->uv60_v * 1.5;
593 uv76->observer = uv60->observer;
594 uv76->illuminant = uv60->illuminant;
595 uv76->u_invalid = uv60->u_invalid;
596 uv76->v_invalid = uv60->v_invalid;
597
598 return 0;
599 }
600
601 int
zsl_clr_conv_uv76_uv60(struct zsl_clr_uv76 * uv76,struct zsl_clr_uv60 * uv60)602 zsl_clr_conv_uv76_uv60(struct zsl_clr_uv76 *uv76, struct zsl_clr_uv60 *uv60)
603 {
604 uv60->uv60_u = uv76->uv76_u;
605 uv60->uv60_v = uv76->uv76_v / 1.5;
606 uv60->observer = uv76->observer;
607 uv60->illuminant = uv76->illuminant;
608 uv60->u_invalid = uv76->u_invalid;
609 uv60->v_invalid = uv76->v_invalid;
610
611 return 0;
612 }
613
614 int
zsl_clr_conv_ct_uv60(zsl_real_t ct,enum zsl_clr_obs obs,struct zsl_clr_uv60 * uv)615 zsl_clr_conv_ct_uv60(zsl_real_t ct, enum zsl_clr_obs obs, struct zsl_clr_uv60 *uv)
616 {
617 int status;
618 struct zsl_clr_xyz xyz;
619
620 /* First get the XYZ equivalent for the current color temp. */
621 status = zsl_clr_conv_ct_xyz(ct, obs, &xyz);
622 if (status) {
623 goto err;
624 }
625
626 /* Convert XYZ tristimulus to (u,v) chromaticity. */
627 status = zsl_clr_conv_xyz_uv60(&xyz, uv);
628 if (status) {
629 goto err;
630 }
631
632 return 0;
633 err:
634 return status;
635 }
636
637 int
zsl_clr_conv_ct_xyz(zsl_real_t ct,enum zsl_clr_obs obs,struct zsl_clr_xyz * xyz)638 zsl_clr_conv_ct_xyz(zsl_real_t ct, enum zsl_clr_obs obs, struct zsl_clr_xyz *xyz)
639 {
640 zsl_real_t c1;
641 zsl_real_t c2;
642 zsl_real_t d_wl_m = 0.0;
643 zsl_real_t d_wl_m5 = 0.0;
644 zsl_real_t bbody = 0.0;
645 const struct zsl_clr_obs_data *obs_data;
646
647 /* TODO: Validate input range! */
648
649 /*
650 * C1 and C2 constants for Planck's radiation law, where:
651 *
652 * c1 = 3.74183 x 10^-16W m^2
653 * c2 = 1.4388 x 10^-2 m K
654 *
655 * Note: Values are scaled further in algorithm below. */
656 c1 = 374.183162616761619;
657 c2 = 14.387863142323088;
658
659 /* Clear the output placeholder. */
660 memset(xyz, 0, sizeof *xyz);
661
662 /* Get a reference to the standard observer CMF dataset. */
663 zsl_clr_obs_get(obs, &obs_data);
664
665 /* Calculate emittance at given wavelength using Planck's radiation
666 * law and the specified 5nm standard observer lookup table. */
667 for (int nm = 360; nm <= 830; nm += 5) {
668 int i = (nm - 360) / 5;
669 /* Scale wavelength to micrometers. */
670 d_wl_m = nm * 1.0e-3;
671 /* d_wl_m^5. */
672 d_wl_m5 = d_wl_m * d_wl_m * d_wl_m * d_wl_m * d_wl_m;
673 /* Calculate black-body value. Source: Bruce Lindbloom */
674 bbody = c1 / (d_wl_m5 * 1.0e-12 *
675 (expm1(c2 / (ct * d_wl_m * 1.0e-3))));
676 /* Calculate XYZ tristimulus using the std observer model. */
677 xyz->xyz_x += (bbody * obs_data->data[i].xyz_x);
678 xyz->xyz_y += (bbody * obs_data->data[i].xyz_y);
679 xyz->xyz_z += (bbody * obs_data->data[i].xyz_z);
680 }
681
682 /* Normalise XYZ tristimulus for y = 1.0. */
683 xyz->xyz_x /= xyz->xyz_y;
684 xyz->xyz_z /= xyz->xyz_y;
685 xyz->xyz_y = 1.0;
686
687 /* Reference the standard observer model used. */
688 xyz->observer = obs;
689
690 return 0;
691 }
692
693 int
zsl_clr_conv_ct_rgb8(zsl_real_t ct,enum zsl_clr_obs obs,struct zsl_mtx * mtx,struct zsl_clr_rgb8 * rgb)694 zsl_clr_conv_ct_rgb8(zsl_real_t ct, enum zsl_clr_obs obs, struct zsl_mtx *mtx,
695 struct zsl_clr_rgb8 *rgb)
696 {
697 int rc;
698 struct zsl_clr_xyz xyz;
699
700 memset(&xyz, 0, sizeof xyz);
701
702 /* Convert color temperature to an XYZ tristimulus. */
703 rc = zsl_clr_conv_ct_xyz(ct, obs, &xyz);
704 if (rc) {
705 goto err;
706 }
707
708 /* Convert XYZ to RGBA using 'zsl_clr_conv_xyz_rgb8'. */
709 return zsl_clr_conv_xyz_rgb8(&xyz, mtx, rgb);
710 err:
711 rgb->r_invalid = 1;
712 rgb->g_invalid = 1;
713 rgb->b_invalid = 1;
714 rgb->a_invalid = 1;
715 return rc;
716 }
717
718 int
zsl_clr_conv_ct_rgbf(zsl_real_t ct,enum zsl_clr_obs obs,struct zsl_mtx * mtx,struct zsl_clr_rgbf * rgb)719 zsl_clr_conv_ct_rgbf(zsl_real_t ct, enum zsl_clr_obs obs, struct zsl_mtx *mtx,
720 struct zsl_clr_rgbf *rgb)
721 {
722 int rc;
723 struct zsl_clr_xyz xyz;
724
725 memset(&xyz, 0, sizeof xyz);
726
727 /* Convert color temperature to an XYZ tristimulus. */
728 rc = zsl_clr_conv_ct_xyz(ct, obs, &xyz);
729 if (rc) {
730 goto err;
731 }
732
733 /* Convert XYZ to RGBA using 'zsl_clr_conv_xyz_rgbf'. */
734 return zsl_clr_conv_xyz_rgbf(&xyz, mtx, rgb);
735 err:
736 rgb->r_invalid = 1;
737 rgb->g_invalid = 1;
738 rgb->b_invalid = 1;
739 rgb->a_invalid = 1;
740 return rc;
741 }
742
743 int
zsl_clr_conv_cct_xyy(struct zsl_clr_cct * cct,enum zsl_clr_obs obs,struct zsl_clr_xyy * xyy)744 zsl_clr_conv_cct_xyy(struct zsl_clr_cct *cct, enum zsl_clr_obs obs, struct zsl_clr_xyy *xyy)
745 {
746 int rc;
747 struct zsl_clr_xyz xyz;
748 struct zsl_clr_uv60 delta;
749 struct zsl_clr_uv60 uv0;
750 struct zsl_clr_uv60 uv1;
751 struct zsl_clr_uv60 final;
752
753 /* Clear the xyY placeholder */
754 memset(xyy, 0, sizeof *xyy);
755
756 /*
757 * Calculate (u0,v0) of the Planckian radiator at T(K).
758 */
759 rc = zsl_clr_conv_ct_xyz(cct->cct, obs, &xyz);
760 if (rc) {
761 goto err;
762 }
763 rc = zsl_clr_conv_xyz_uv60(&xyz, &uv0);
764 if (rc) {
765 goto err;
766 }
767
768 /*
769 * Calculate (u1,v1) of the Planckian radiator at T+dT(K) where dT=0.01.
770 */
771 rc = zsl_clr_conv_ct_xyz(cct->cct + 0.01, obs, &xyz);
772 if (rc) {
773 goto err;
774 }
775 rc = zsl_clr_conv_xyz_uv60(&xyz, &uv1);
776 if (rc) {
777 goto err;
778 }
779
780 /*
781 * Calculate the slope angle theta of the Planckian locus at T(K):
782 *
783 * du = u0 - u1
784 * dv = v0 - v1
785 * u = u0 - Duv * sin(theta)
786 * v = v0 + Duv * cos(theta)
787 *
788 * where:
789 *
790 * sin(theta) = dv / sqrt(du^2 + dv^2)
791 * cos(theta) = du / sqrt(du^2 + dv^2)
792 */
793 delta.uv60_u = uv0.uv60_u - uv1.uv60_u;
794 delta.uv60_v = uv0.uv60_v - uv1.uv60_v;
795 final.uv60_u = uv0.uv60_u -
796 cct->duv * (delta.uv60_v /
797 sqrt(delta.uv60_u * delta.uv60_u +
798 delta.uv60_v * delta.uv60_v));
799 final.uv60_v = uv0.uv60_v +
800 cct->duv * (delta.uv60_u /
801 sqrt(delta.uv60_u * delta.uv60_u +
802 delta.uv60_v * delta.uv60_v));
803
804 /*
805 * Then the CIE1960 (u,v) value (final) can be converted to CIE 1973
806 * (u',v') and a CIE 1931 (x,y) chromaticity via:
807 *
808 * u' = u
809 * v' = 1.5v
810 *
811 * x = 9u' / (6u' - 16v' + 12)
812 * y = 2v' / (3u' - 8v' + 6)
813 */
814 xyy->xyy_x = 9.0 * final.uv60_u / (6.0 * final.uv60_u - 16.0 *
815 final.uv60_v * 1.5 + 12.0);
816 xyy->xyy_y = 2.0 * final.uv60_v * 1.5 / (3.0 * final.uv60_u - 8.0 *
817 final.uv60_v * 1.5 + 6.0);
818 xyy->xyy_Y = 1.0;
819 xyy->observer = obs;
820
821 return 0;
822 err:
823 xyy->x_invalid = 1;
824 xyy->y_invalid = 1;
825 xyy->Y_invalid = 1;
826 return rc;
827 }
828
829 int
zsl_clr_conv_cct_xyz(struct zsl_clr_cct * cct,enum zsl_clr_obs obs,struct zsl_clr_xyz * xyz)830 zsl_clr_conv_cct_xyz(struct zsl_clr_cct *cct, enum zsl_clr_obs obs, struct zsl_clr_xyz *xyz)
831 {
832 int rc;
833 struct zsl_clr_xyy xyy;
834
835 /* Perform the initial CCT+Duv to xyY conversion */
836 rc = zsl_clr_conv_cct_xyy(cct, obs, &xyy);
837 if (rc) {
838 goto err;
839 }
840
841 /* Convert the xyY chromaticity to the equivalent XYZ tristimulus */
842 rc = zsl_clr_conv_xyy_xyz(&xyy, xyz);
843 if (rc) {
844 goto err;
845 }
846
847 return 0;
848 err:
849 xyz->x_invalid = 1;
850 xyz->y_invalid = 1;
851 xyz->z_invalid = 1;
852 return rc;
853 }
854
855 static int
zsl_clr_conv_uv60_cct_mccamy(struct zsl_clr_uv60 * uv,struct zsl_clr_cct * cct)856 zsl_clr_conv_uv60_cct_mccamy(struct zsl_clr_uv60 *uv, struct zsl_clr_cct *cct)
857 {
858 int rc;
859 zsl_real_t n;
860 struct zsl_clr_xyy xyy;
861
862 /*
863 * CCT(x, y) = 449 * n^3 + 3525 * n^2 + 6823.3 * n + 5520.33
864 *
865 * Where:
866 *
867 * n = (x − xe) / (ye - y)
868 * xe = 0.3320
869 * ye = 0.1858
870 */
871
872 /* Basic input validation. */
873 if ((uv->u_invalid) || (uv->v_invalid)) {
874 rc = -EINVAL;
875 goto err;
876 }
877
878 /* Duv is not calculated as part of McCamy's aproximation. */
879 cct->duv = 0.0;
880 cct->duv_invalid = 1;
881
882 /* Calculate cct using McCamy's approximation. */
883 rc = zsl_clr_conv_uv60_xyy(uv, &xyy);
884 n = (xyy.xyy_x - 0.3320) / (0.1858 - xyy.xyy_y);
885 cct->cct = (449.0 * pow(n, 3) + 3525.0 * pow(n, 2) +
886 6823.3 * n + 5520.33);
887
888 return 0;
889 err:
890 cct->cct_invalid = 1;
891 cct->duv_invalid = 1;
892 return rc;
893 }
894
895 static int
zsl_clr_conv_uv60_cct_ohno2011(struct zsl_clr_uv60 * uv,struct zsl_clr_cct * cct)896 zsl_clr_conv_uv60_cct_ohno2011(struct zsl_clr_uv60 *uv, struct zsl_clr_cct *cct)
897 {
898 int rc;
899 zsl_real_t l_fp; /* L = lightness (aka spectral radiance) */
900 zsl_real_t l_bb;
901 zsl_real_t l_p;
902 zsl_real_t a_1;
903 zsl_real_t a;
904 zsl_real_t t1;
905 zsl_real_t t2;
906 zsl_real_t dt_c1;
907 zsl_real_t dt_c2;
908 zsl_real_t c;
909
910 /* Basic input validation. */
911 if ((uv->u_invalid) || (uv->v_invalid)) {
912 rc = -EINVAL;
913 goto err;
914 }
915
916 /* Clear CCT before starting. */
917 memset(cct, 0, sizeof *cct);
918
919 /* Calculate L_fp. */
920 l_fp = sqrt((uv->uv60_u - 0.292) * (uv->uv60_u - 0.292) +
921 (uv->uv60_v - 0.24) * (uv->uv60_v - 0.24));
922
923 /* Calculate the black-body spectral radiance using k[0] constants.
924 * Note: This formula uses an approximation since we don't know
925 * the CCT. */
926 a_1 = atan((uv->uv60_v - 0.24) / (uv->uv60_u - 0.292));
927 a = a_1 >= 0.0 ? a_1 : a_1 + M_PI;
928 l_bb = zsl_clr_conv_xyy_cct_ohno_2011_data[0][6] * pow(a, 6) +
929 zsl_clr_conv_xyy_cct_ohno_2011_data[0][5] * pow(a, 5) +
930 zsl_clr_conv_xyy_cct_ohno_2011_data[0][4] * pow(a, 4) +
931 zsl_clr_conv_xyy_cct_ohno_2011_data[0][3] * pow(a, 3) +
932 zsl_clr_conv_xyy_cct_ohno_2011_data[0][2] * pow(a, 2) +
933 zsl_clr_conv_xyy_cct_ohno_2011_data[0][1] * a +
934 zsl_clr_conv_xyy_cct_ohno_2011_data[0][0];
935
936 /* Set the Duv value. */
937 cct->duv = l_fp - l_bb;
938
939 /* TODO: Determine proper value of L_p! */
940 l_p = l_fp;
941
942 /* Calculate T1 and DT_c1 delta depending on the value of a. */
943 if (a < 2.54) {
944 t1 = 1 / (zsl_clr_conv_xyy_cct_ohno_2011_data[1][6] * pow(a, 6) +
945 zsl_clr_conv_xyy_cct_ohno_2011_data[1][5] * pow(a, 5) +
946 zsl_clr_conv_xyy_cct_ohno_2011_data[1][4] * pow(a, 4) +
947 zsl_clr_conv_xyy_cct_ohno_2011_data[1][3] * pow(a, 3) +
948 zsl_clr_conv_xyy_cct_ohno_2011_data[1][2] * pow(a, 2) +
949 zsl_clr_conv_xyy_cct_ohno_2011_data[1][1] * a +
950 zsl_clr_conv_xyy_cct_ohno_2011_data[1][0]);
951 dt_c1 = (zsl_clr_conv_xyy_cct_ohno_2011_data[3][6] * pow(a, 6) +
952 zsl_clr_conv_xyy_cct_ohno_2011_data[3][5] * pow(a, 5) +
953 zsl_clr_conv_xyy_cct_ohno_2011_data[3][4] * pow(a, 4) +
954 zsl_clr_conv_xyy_cct_ohno_2011_data[3][3] * pow(a, 3) +
955 zsl_clr_conv_xyy_cct_ohno_2011_data[3][2] * pow(a, 2) +
956 zsl_clr_conv_xyy_cct_ohno_2011_data[3][1] * a +
957 zsl_clr_conv_xyy_cct_ohno_2011_data[3][0]) *
958 (l_bb + 0.01) / l_p * cct->duv / 0.01;
959 } else {
960 t1 = 1 / (zsl_clr_conv_xyy_cct_ohno_2011_data[2][6] * pow(a, 6) +
961 zsl_clr_conv_xyy_cct_ohno_2011_data[2][5] * pow(a, 5) +
962 zsl_clr_conv_xyy_cct_ohno_2011_data[2][4] * pow(a, 4) +
963 zsl_clr_conv_xyy_cct_ohno_2011_data[2][3] * pow(a, 3) +
964 zsl_clr_conv_xyy_cct_ohno_2011_data[2][2] * pow(a, 2) +
965 zsl_clr_conv_xyy_cct_ohno_2011_data[2][1] * a +
966 zsl_clr_conv_xyy_cct_ohno_2011_data[2][0]);
967 dt_c1 = (zsl_clr_conv_xyy_cct_ohno_2011_data[4][6] * pow(a, 6) +
968 zsl_clr_conv_xyy_cct_ohno_2011_data[4][5] * pow(a, 5) +
969 zsl_clr_conv_xyy_cct_ohno_2011_data[4][4] * pow(a, 4) +
970 zsl_clr_conv_xyy_cct_ohno_2011_data[4][3] * pow(a, 3) +
971 zsl_clr_conv_xyy_cct_ohno_2011_data[4][2] * pow(a, 2) +
972 zsl_clr_conv_xyy_cct_ohno_2011_data[4][1] * a +
973 zsl_clr_conv_xyy_cct_ohno_2011_data[4][0]) *
974 (l_bb + 0.01) / l_p * cct->duv / 0.01;
975 }
976
977 /* Calculate T2 and c. */
978 t2 = t1 - dt_c1;
979 c = log10(t2);
980
981 /* Calculate DT_c2 depending on positive or negative Duv. */
982 if (cct->duv >= 0.0) {
983 dt_c2 = (zsl_clr_conv_xyy_cct_ohno_2011_data[5][6] * pow(c, 6) +
984 zsl_clr_conv_xyy_cct_ohno_2011_data[5][5] * pow(c, 5) +
985 zsl_clr_conv_xyy_cct_ohno_2011_data[5][4] * pow(c, 4) +
986 zsl_clr_conv_xyy_cct_ohno_2011_data[5][3] * pow(c, 3) +
987 zsl_clr_conv_xyy_cct_ohno_2011_data[5][2] * pow(c, 2) +
988 zsl_clr_conv_xyy_cct_ohno_2011_data[5][1] * c +
989 zsl_clr_conv_xyy_cct_ohno_2011_data[5][0]);
990 } else {
991 dt_c2 = (zsl_clr_conv_xyy_cct_ohno_2011_data[6][6] * pow(c, 6) +
992 zsl_clr_conv_xyy_cct_ohno_2011_data[6][5] * pow(c, 5) +
993 zsl_clr_conv_xyy_cct_ohno_2011_data[6][4] * pow(c, 4) +
994 zsl_clr_conv_xyy_cct_ohno_2011_data[6][3] * pow(c, 3) +
995 zsl_clr_conv_xyy_cct_ohno_2011_data[6][2] * pow(c, 2) +
996 zsl_clr_conv_xyy_cct_ohno_2011_data[6][1] * c +
997 zsl_clr_conv_xyy_cct_ohno_2011_data[6][0]) *
998 pow(cct->duv / 0.03, 2);
999 }
1000
1001 /* Assign the final correlated color temperature. */
1002 cct->cct = t2 - dt_c2;
1003
1004 return 0;
1005 err:
1006 cct->cct_invalid = 1;
1007 cct->duv_invalid = 1;
1008 return rc;
1009 }
1010
1011 // /**
1012 // * @brief Calculates the distance between the supplied (u,v) chromaticity
1013 // * (uv) and the equivalent (u,v) chromaticity for the specified color
1014 // * temperature. Used to determine how far the supplied uv parameter is
1015 // * from the supplied color temperature.
1016 // *
1017 // * @param ct The ref. color temperature to convert to a (u,v) chromaticity.
1018 // * @param obs The CIE standard observer model to use for the conversion.
1019 // * @param uv Pointer to the (u,v) chromaticity to compare against ct.
1020 // * @param d Pointer to the placeholder for the calculated distance.
1021 // *
1022 // * @return The distance between uv and ct's (u,v) equivalent.
1023 // */
1024 // static int
1025 // zsl_clr_conv_calc_dist_uv60(zsl_real_t ct, enum zsl_clr_obs obs,
1026 // struct zsl_clr_uv60 *uv, zsl_real_t *d)
1027 // {
1028 // int status;
1029 // struct zsl_clr_uv60 ct_uv;
1030 //
1031 // status = zsl_clr_conv_ct_uv60(ct, obs, &ct_uv);
1032 // if (status) {
1033 // goto err;
1034 // }
1035 //
1036 // *d = ((ct_uv.uv60_u - uv->uv60_u) * (ct_uv.uv60_u - uv->uv60_u) +
1037 // (ct_uv.uv60_v - uv->uv60_v) * (ct_uv.uv60_v - uv->uv60_v));
1038 //
1039 // return 0;
1040 // err:
1041 // return status;
1042 // }
1043
1044 static int
zsl_clr_conv_uv60_cct_ohno2014(struct zsl_clr_uv60 * uv,struct zsl_clr_cct * cct)1045 zsl_clr_conv_uv60_cct_ohno2014(struct zsl_clr_uv60 *uv, struct zsl_clr_cct *cct)
1046 {
1047 int rc;
1048 zsl_real_t l_fp;
1049 zsl_real_t l_bb;
1050 zsl_real_t a;
1051 zsl_real_t d_cur;
1052 zsl_real_t d_best; /* d */
1053 zsl_real_t d_prev; /* d m-1 */
1054 zsl_real_t d_next; /* d m+1 */
1055 zsl_real_t x; /* Distance from d_best to d m+1. */
1056 zsl_real_t l; /* Total width of d m-1 to d m+1. */
1057 int match_idx; /* Lookup index for d_best values. */
1058
1059 /* Basic input validation. */
1060 if ((uv->u_invalid) || (uv->v_invalid)) {
1061 rc = -EINVAL;
1062 goto err;
1063 }
1064
1065 /* Clear values before starting. */
1066 memset(cct, 0, sizeof *cct);
1067
1068 /* Triangular solution for CCT. */
1069 d_best = 1.0;
1070
1071 /* Search for closest match for uv from 1% CT to (u,v) lookup.
1072 * Note: the static lookup table could be removed at the cost of a
1073 * performance hit by using the following function to calculate
1074 * distance on demand:
1075 * zsl_clr_conv_calc_dist_uv60(ct_cur, ZSL_CLR_OBS_2_DEG, uv, &d_cur);
1076 */
1077 match_idx = 0;
1078 for (size_t i = 0; i < OHNO2014_LOOKUP_RECS; i++) {
1079 /* Calculate distance of CT (u,v) to ref (u,v) chromaticity. */
1080 d_cur = ((zsl_clr_conv_ct_uv_ohno_2014_data[i][1] - uv->uv60_u) *
1081 (zsl_clr_conv_ct_uv_ohno_2014_data[i][1] - uv->uv60_u) +
1082 (zsl_clr_conv_ct_uv_ohno_2014_data[i][2] - uv->uv60_v) *
1083 (zsl_clr_conv_ct_uv_ohno_2014_data[i][2] - uv->uv60_v));
1084 /* Track best match. */
1085 if (d_cur < d_best) {
1086 d_best = d_cur;
1087 match_idx = i;
1088 }
1089 }
1090
1091 /* Make sure we're within the 1000 K to 20000 K range. */
1092 if ((zsl_clr_conv_ct_uv_ohno_2014_data[match_idx][0] <
1093 zsl_clr_conv_ct_uv_ohno_2014_data[1][0]) ||
1094 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx][0] >
1095 zsl_clr_conv_ct_uv_ohno_2014_data[OHNO2014_LOOKUP_RECS - 2][0])) {
1096 return -EINVAL;
1097 }
1098
1099 /* Calculate prev distance. */
1100 d_prev = ((zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][1] -
1101 uv->uv60_u) *
1102 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][1] -
1103 uv->uv60_u) +
1104 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][2] -
1105 uv->uv60_v) *
1106 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][2] -
1107 uv->uv60_v));
1108
1109 /* Calculate next distance. */
1110 d_next = ((zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][1] -
1111 uv->uv60_u) *
1112 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][1] -
1113 uv->uv60_u) +
1114 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][2] -
1115 uv->uv60_v) *
1116 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][2] -
1117 uv->uv60_v));
1118
1119 l = ((zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][1] -
1120 zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][1]) *
1121 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][1] -
1122 zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][1])) +
1123 ((zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][2] -
1124 zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][2]) *
1125 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][2] -
1126 zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][2]));
1127 l = sqrt(l);
1128
1129 x = ((d_prev * d_prev) - (d_next * d_next) + (l * l)) / (2.0 * l);
1130
1131 cct->cct = zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][0] +
1132 (zsl_clr_conv_ct_uv_ohno_2014_data[match_idx + 1][0] -
1133 zsl_clr_conv_ct_uv_ohno_2014_data[match_idx - 1][0]) *
1134 (x / l);
1135
1136 /* Calculate Duv. */
1137 l_fp = sqrt((uv->uv60_u - 0.292) * (uv->uv60_u - 0.292) +
1138 (uv->uv60_v - 0.24) * (uv->uv60_v - 0.24));
1139 a = acos((uv->uv60_u - 0.292) / l_fp);
1140 l_bb = zsl_clr_conv_xyy_cct_ohno_2014_data[6] * pow(a, 6) +
1141 zsl_clr_conv_xyy_cct_ohno_2014_data[5] * pow(a, 5) +
1142 zsl_clr_conv_xyy_cct_ohno_2014_data[4] * pow(a, 4) +
1143 zsl_clr_conv_xyy_cct_ohno_2014_data[3] * pow(a, 3) +
1144 zsl_clr_conv_xyy_cct_ohno_2014_data[2] * pow(a, 2) +
1145 zsl_clr_conv_xyy_cct_ohno_2014_data[1] * a +
1146 zsl_clr_conv_xyy_cct_ohno_2014_data[0];
1147
1148 /* Set the Duv value. */
1149 cct->duv = l_fp - l_bb;
1150
1151 return 0;
1152 err:
1153 cct->cct_invalid = 1;
1154 cct->duv_invalid = 1;
1155 return rc;
1156 }
1157
1158 int
zsl_clr_conv_uv60_cct(enum zsl_clr_uv_cct_method method,struct zsl_clr_uv60 * uv,struct zsl_clr_cct * cct)1159 zsl_clr_conv_uv60_cct(enum zsl_clr_uv_cct_method method, struct zsl_clr_uv60 *uv,
1160 struct zsl_clr_cct *cct)
1161 {
1162 /* Use the specified algorithm for the conversion. */
1163 switch (method) {
1164 case ZSL_CLR_UV_CCT_METHOD_MCCAMY:
1165 return zsl_clr_conv_uv60_cct_mccamy(uv, cct);
1166 case ZSL_CLR_UV_CCT_METHOD_OHNO2011:
1167 return zsl_clr_conv_uv60_cct_ohno2011(uv, cct);
1168 case ZSL_CLR_UV_CCT_METHOD_OHNO2014:
1169 return zsl_clr_conv_uv60_cct_ohno2014(uv, cct);
1170 default:
1171 return zsl_clr_conv_uv60_cct_ohno2014(uv, cct);
1172 }
1173 }
1174
1175 int
zsl_clr_conv_xyz_rgb8(struct zsl_clr_xyz * xyz,struct zsl_mtx * mtx,struct zsl_clr_rgb8 * rgb)1176 zsl_clr_conv_xyz_rgb8(struct zsl_clr_xyz *xyz, struct zsl_mtx *mtx,
1177 struct zsl_clr_rgb8 *rgb)
1178 {
1179 int rc;
1180 struct zsl_clr_rgbf rgbf;
1181
1182 /* Use the more precise floating point version, then convert. */
1183 rc = zsl_clr_conv_xyz_rgbf(xyz, mtx, &rgbf);
1184 if (rc) {
1185 goto err;
1186 }
1187
1188 /* Flag out of gamut colors. */
1189 rgb->r_invalid = rgbf.r_invalid;
1190 rgb->g_invalid = rgbf.g_invalid;
1191 rgb->b_invalid = rgbf.b_invalid;
1192
1193 /* We need to cap out of gamut colors before converting to 8-bit. */
1194 #if !CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1195 if (rgbf.r > 1.0) {
1196 rgbf.r = 1.0;
1197 }
1198 if (rgbf.r < 0.0) {
1199 rgbf.r = 0.0;
1200 }
1201 if (rgbf.g > 1.0) {
1202 rgbf.g = 1.0;
1203 }
1204 if (rgbf.g < 0.0) {
1205 rgbf.g = 0.0;
1206 }
1207 if (rgbf.b > 1.0) {
1208 rgbf.b = 1.0;
1209 }
1210 if (rgbf.b < 0.0) {
1211 rgbf.b = 0.0;
1212 }
1213 #endif
1214
1215 rgb->r = (uint8_t)(rgbf.r * 255.0);
1216 rgb->g = (uint8_t)(rgbf.g * 255.0);
1217 rgb->b = (uint8_t)(rgbf.b * 255.0);
1218 rgb->a = 0xFF;
1219
1220 return 0;
1221 err:
1222 rgb->r_invalid = 1;
1223 rgb->g_invalid = 1;
1224 rgb->b_invalid = 1;
1225 rgb->a_invalid = 1;
1226 return rc;
1227 }
1228
1229 int
zsl_clr_conv_xyz_rgbf(struct zsl_clr_xyz * xyz,struct zsl_mtx * mtx,struct zsl_clr_rgbf * rgb)1230 zsl_clr_conv_xyz_rgbf(struct zsl_clr_xyz *xyz, struct zsl_mtx *mtx,
1231 struct zsl_clr_rgbf *rgb)
1232 {
1233 int rc;
1234
1235 /* Actually vectors, but declaring as a matrix makes life easier. */
1236 ZSL_MATRIX_DEF(xyz_mtx, 3, 1);
1237 ZSL_MATRIX_DEF(rgb_mtx, 3, 1);
1238 zsl_mtx_init(&xyz_mtx, NULL);
1239 zsl_mtx_init(&rgb_mtx, NULL);
1240
1241 /* Clear the RGB placeholder and the interim XYZ tristimulus. */
1242 memset(rgb, 0, sizeof *rgb);
1243
1244 /* Assign the XYZ values to the temp matrix. */
1245 xyz_mtx.data[0] = xyz->xyz_x;
1246 xyz_mtx.data[1] = xyz->xyz_y;
1247 xyz_mtx.data[2] = xyz->xyz_z;
1248
1249 /* Convert XYZ to RGBF using the specified correlation matrix. */
1250 rc = zsl_mtx_mult(mtx, &xyz_mtx, &rgb_mtx);
1251 if (rc) {
1252 goto err;
1253 }
1254
1255 /* Correct for out of gamut colors, marking them as invalid. */
1256 /* Red underflow correction. */
1257 if (rgb_mtx.data[0] < 0.0) {
1258 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1259 rgb_mtx.data[0] = 0.0;
1260 #endif
1261 rgb->r_invalid = 1;
1262 }
1263 /* Green underflow correction. */
1264 if (rgb_mtx.data[1] < 0.0) {
1265 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1266 rgb_mtx.data[1] = 0.0;
1267 #endif
1268 rgb->g_invalid = 1;
1269 }
1270 /* Blue underflow correction. */
1271 if (rgb_mtx.data[2] < 0.0) {
1272 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1273 rgb_mtx.data[2] = 0.0;
1274 #endif
1275 rgb->b_invalid = 1;
1276 }
1277
1278 /* Red overflow correction. */
1279 if (rgb_mtx.data[0] >= 1.0) {
1280 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1281 rgb->r = 1.0;
1282 #else
1283 rgb->r = rgb_mtx.data[0];
1284 #endif
1285 rgb->r_invalid = 1;
1286 } else {
1287 rgb->r = rgb_mtx.data[0];
1288 }
1289 /* Green overflow correction. */
1290 if (rgb_mtx.data[1] >= 1.0) {
1291 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1292 rgb->g = 1.0;
1293 #else
1294 rgb->g = rgb_mtx.data[1];
1295 #endif
1296 rgb->g_invalid = 1;
1297 } else {
1298 rgb->g = rgb_mtx.data[1];
1299 }
1300 /* Blue overflow correction. */
1301 if (rgb_mtx.data[2] >= 1.0) {
1302 #if CONFIG_ZSL_CLR_RGBF_BOUND_CAP
1303 rgb->b = 1.0;
1304 #else
1305 rgb->b = rgb_mtx.data[2];
1306 #endif
1307 rgb->b_invalid = 1;
1308 } else {
1309 rgb->b = rgb_mtx.data[2];
1310 }
1311
1312 /* Alpha channel. */
1313 rgb->a = 1.0;
1314
1315 return 0;
1316 err:
1317 rgb->r_invalid = 1;
1318 rgb->g_invalid = 1;
1319 rgb->b_invalid = 1;
1320 rgb->a_invalid = 1;
1321 return rc;
1322 }
1323