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