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