1 /*
2 * Copyright (c) 2019, 2020 Kevin Townsend (KTOWN)
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zsl/colorimetry.h>
8
9 /**
10 * CIE 1988 Photopic Luminous Efficiency Function.
11 * 380nm to 780nm in 5nm increments (81 values).
12 */
13 static const struct zsl_clr_spd zsl_clr_conv_lef_cie88_photopic_5nm = {
14 .size = 81,
15 .comps =
16 {
17 { .nm = 380, .value = 0.00020000 },
18 { .nm = 385, .value = 0.00039600 },
19 { .nm = 390, .value = 0.00080000 },
20 { .nm = 395, .value = 0.00155000 },
21 { .nm = 400, .value = 0.00280000 },
22 { .nm = 405, .value = 0.00466000 },
23 { .nm = 410, .value = 0.00740000 },
24 { .nm = 415, .value = 0.01180000 },
25 { .nm = 420, .value = 0.01750000 },
26 { .nm = 425, .value = 0.02270000 },
27 { .nm = 430, .value = 0.02730000 },
28 { .nm = 435, .value = 0.03260000 },
29 { .nm = 440, .value = 0.03790000 },
30 { .nm = 445, .value = 0.04240000 },
31 { .nm = 450, .value = 0.04680000 },
32 { .nm = 455, .value = 0.05210000 },
33 { .nm = 460, .value = 0.06000000 },
34 { .nm = 465, .value = 0.07390000 },
35 { .nm = 470, .value = 0.09100000 },
36 { .nm = 475, .value = 0.11300000 },
37 { .nm = 480, .value = 0.13900000 },
38 { .nm = 485, .value = 0.16900000 },
39 { .nm = 490, .value = 0.20800000 },
40 { .nm = 495, .value = 0.25900000 },
41 { .nm = 500, .value = 0.32300000 },
42 { .nm = 505, .value = 0.40730000 },
43 { .nm = 510, .value = 0.50300000 },
44 { .nm = 515, .value = 0.60820000 },
45 { .nm = 520, .value = 0.71000000 },
46 { .nm = 525, .value = 0.79320000 },
47 { .nm = 530, .value = 0.86200000 },
48 { .nm = 535, .value = 0.91485000 },
49 { .nm = 540, .value = 0.95400000 },
50 { .nm = 545, .value = 0.98030000 },
51 { .nm = 550, .value = 0.99495000 },
52 { .nm = 555, .value = 1.00000000 },
53 { .nm = 560, .value = 0.99500000 },
54 { .nm = 565, .value = 0.97860000 },
55 { .nm = 570, .value = 0.95200000 },
56 { .nm = 575, .value = 0.91540000 },
57 { .nm = 580, .value = 0.87000000 },
58 { .nm = 585, .value = 0.81630000 },
59 { .nm = 590, .value = 0.75700000 },
60 { .nm = 595, .value = 0.69490000 },
61 { .nm = 600, .value = 0.63100000 },
62 { .nm = 605, .value = 0.56680000 },
63 { .nm = 610, .value = 0.50300000 },
64 { .nm = 615, .value = 0.44120000 },
65 { .nm = 620, .value = 0.38100000 },
66 { .nm = 625, .value = 0.32000000 },
67 { .nm = 630, .value = 0.26500000 },
68 { .nm = 635, .value = 0.21700000 },
69 { .nm = 640, .value = 0.17500000 },
70 { .nm = 645, .value = 0.13820000 },
71 { .nm = 650, .value = 0.10700000 },
72 { .nm = 655, .value = 0.08160000 },
73 { .nm = 660, .value = 0.06100000 },
74 { .nm = 665, .value = 0.04460000 },
75 { .nm = 670, .value = 0.03200000 },
76 { .nm = 675, .value = 0.02320000 },
77 { .nm = 680, .value = 0.01700000 },
78 { .nm = 685, .value = 0.01190000 },
79 { .nm = 690, .value = 0.00821000 },
80 { .nm = 695, .value = 0.00572000 },
81 { .nm = 700, .value = 0.00410000 },
82 { .nm = 705, .value = 0.00293000 },
83 { .nm = 710, .value = 0.00209000 },
84 { .nm = 715, .value = 0.00148000 },
85 { .nm = 720, .value = 0.00105000 },
86 { .nm = 725, .value = 0.00074000 },
87 { .nm = 730, .value = 0.00052000 },
88 { .nm = 735, .value = 0.00036100 },
89 { .nm = 740, .value = 0.00024900 },
90 { .nm = 745, .value = 0.00017200 },
91 { .nm = 750, .value = 0.00012000 },
92 { .nm = 755, .value = 0.00008480 },
93 { .nm = 760, .value = 0.00006000 },
94 { .nm = 765, .value = 0.00004240 },
95 { .nm = 770, .value = 0.00003000 },
96 { .nm = 775, .value = 0.00002120 },
97 { .nm = 780, .value = 0.00001500 }
98 }
99 };
100
101 /**
102 * CIE 1951 Scotopic Luminous Efficiency Function.
103 * 380nm to 780nm in 5nm increments (81 values).
104 */
105 static const struct zsl_clr_spd zsl_clr_conv_lef_cie51_scotopic_5nm = {
106 .size = 81,
107 .comps =
108 {
109 { .nm = 380, .value = 5.89E-01 },
110 { .nm = 385, .value = 1.108E+00 },
111 { .nm = 390, .value = 2.209E+00 },
112 { .nm = 395, .value = 4.53E+00 },
113 { .nm = 400, .value = 9.29E+00 },
114 { .nm = 405, .value = 1.852E+01 },
115 { .nm = 410, .value = 3.484E+01 },
116 { .nm = 415, .value = 6.04E+01 },
117 { .nm = 420, .value = 9.66E+01 },
118 { .nm = 425, .value = 1.436E+02 },
119 { .nm = 430, .value = 1.998E+02 },
120 { .nm = 435, .value = 2.625E+02 },
121 { .nm = 440, .value = 3.281E+02 },
122 { .nm = 445, .value = 3.931E+02 },
123 { .nm = 450, .value = 4.55E+02 },
124 { .nm = 455, .value = 5.13E+02 },
125 { .nm = 460, .value = 5.67E+02 },
126 { .nm = 465, .value = 6.2E+02 },
127 { .nm = 470, .value = 6.76E+02 },
128 { .nm = 475, .value = 7.34E+02 },
129 { .nm = 480, .value = 7.93E+02 },
130 { .nm = 485, .value = 8.51E+02 },
131 { .nm = 490, .value = 9.04E+02 },
132 { .nm = 495, .value = 9.49E+02 },
133 { .nm = 500, .value = 9.82E+02 },
134 { .nm = 505, .value = 9.98E+02 },
135 { .nm = 510, .value = 9.97E+02 },
136 { .nm = 515, .value = 9.75E+02 },
137 { .nm = 520, .value = 9.35E+02 },
138 { .nm = 525, .value = 8.8E+02 },
139 { .nm = 530, .value = 8.11E+02 },
140 { .nm = 535, .value = 7.33E+02 },
141 { .nm = 540, .value = 6.5E+02 },
142 { .nm = 545, .value = 5.64E+02 },
143 { .nm = 550, .value = 4.81E+02 },
144 { .nm = 555, .value = 4.02E+02 },
145 { .nm = 560, .value = 3.288E+02 },
146 { .nm = 565, .value = 2.639E+02 },
147 { .nm = 570, .value = 2.076E+02 },
148 { .nm = 575, .value = 1.602E+02 },
149 { .nm = 580, .value = 1.212E+02 },
150 { .nm = 585, .value = 8.99E+01 },
151 { .nm = 590, .value = 6.55E+01 },
152 { .nm = 595, .value = 4.69E+01 },
153 { .nm = 600, .value = 3.315E+01 },
154 { .nm = 605, .value = 2.312E+01 },
155 { .nm = 610, .value = 1.593E+01 },
156 { .nm = 615, .value = 1.088E+01 },
157 { .nm = 620, .value = 7.37E+00 },
158 { .nm = 625, .value = 4.97E+00 },
159 { .nm = 630, .value = 3.335E+00 },
160 { .nm = 635, .value = 2.235E+00 },
161 { .nm = 640, .value = 1.497E+00 },
162 { .nm = 645, .value = 1.005E+00 },
163 { .nm = 650, .value = 6.77E-01 },
164 { .nm = 655, .value = 4.59E-01 },
165 { .nm = 660, .value = 3.129E-01 },
166 { .nm = 665, .value = 2.146E-01 },
167 { .nm = 670, .value = 1.48E-01 },
168 { .nm = 675, .value = 1.026E-01 },
169 { .nm = 680, .value = 7.15E-02 },
170 { .nm = 685, .value = 5.01E-02 },
171 { .nm = 690, .value = 3.533E-02 },
172 { .nm = 695, .value = 2.501E-02 },
173 { .nm = 700, .value = 1.78E-02 },
174 { .nm = 705, .value = 1.273E-02 },
175 { .nm = 710, .value = 9.14E-03 },
176 { .nm = 715, .value = 6.6E-03 },
177 { .nm = 720, .value = 4.78E-03 },
178 { .nm = 725, .value = 3.482E-03 },
179 { .nm = 730, .value = 2.546E-03 },
180 { .nm = 735, .value = 1.87E-03 },
181 { .nm = 740, .value = 1.379E-03 },
182 { .nm = 745, .value = 1.022E-03 },
183 { .nm = 750, .value = 7.6E-04 },
184 { .nm = 755, .value = 5.67E-04 },
185 { .nm = 760, .value = 4.25E-04 },
186 { .nm = 765, .value = 3.196E-04 },
187 { .nm = 770, .value = 2.413E-04 },
188 { .nm = 775, .value = 1.829E-04 },
189 { .nm = 780, .value = 1.39E-04 }
190 }
191 };
192
193 void
zsl_clr_lef_get(enum zsl_clr_lef lef,const struct zsl_clr_spd ** data)194 zsl_clr_lef_get(enum zsl_clr_lef lef, const struct zsl_clr_spd **data)
195 {
196 switch (lef) {
197 case ZSL_CLR_LEF_CIE51_SCOTOPIC:
198 *data = &zsl_clr_conv_lef_cie51_scotopic_5nm;
199 break;
200 case ZSL_CLR_LEF_CIE88_PHOTOPIC:
201 default:
202 *data = &zsl_clr_conv_lef_cie88_photopic_5nm;
203 break;
204 }
205 }
206
207 int
zsl_clr_lef_lerp(enum zsl_clr_lef lef,unsigned int nm,zsl_real_t * val)208 zsl_clr_lef_lerp(enum zsl_clr_lef lef, unsigned int nm, zsl_real_t *val)
209 {
210 const struct zsl_clr_spd *lef_data;
211 size_t lower, upper;
212 zsl_real_t coeff;
213
214 /* Get a reference to the LEF data. */
215 zsl_clr_lef_get(lef, &lef_data);
216
217 /* Over/Underflow check. */
218 if ((lef_data->comps[0].nm > nm) ||
219 (lef_data->comps[lef_data->size - 1].nm < nm)) {
220 *val = 0.0;
221 return 0;
222 }
223
224 /* Find the closest matching value. */
225 upper = lower = 0;
226 for (size_t i = 0; i < lef_data->size; i++) {
227 /* Exact match */
228 if (nm == lef_data->comps[i].nm) {
229 *val = lef_data->comps[i].value;
230 return 0;
231 }
232 /* Check if upper boundary found. */
233 if (lef_data->comps[i].nm > nm) {
234 upper = i;
235 lower = i ? i - 1 : 0;
236 break;
237 }
238 }
239
240 /* Calculate the interpolation coefficient per nm interval.
241 * Since LEF data is in 5 nm steps, we can take some shortcuts here. */
242 coeff = lef_data->comps[upper].value - lef_data->comps[lower].value;
243 coeff /= 5.0;
244 *val = coeff * (nm - lef_data->comps[lower].nm);
245 *val += lef_data->comps[lower].value;
246
247 return 0;
248 }
249