1 /*
2 This file is part of darktable,
3 Copyright (C) 2020 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #pragma once
20
21 #include "common/math.h"
22
23 typedef enum dt_adaptation_t
24 {
25 DT_ADAPTATION_LINEAR_BRADFORD = 0, // $DESCRIPTION: "linear Bradford (ICC v4)"
26 DT_ADAPTATION_CAT16 = 1, // $DESCRIPTION: "CAT16 (CIECAM16)"
27 DT_ADAPTATION_FULL_BRADFORD = 2, // $DESCRIPTION: "non-linear Bradford"
28 DT_ADAPTATION_XYZ = 3, // $DESCRIPTION: "XYZ "
29 DT_ADAPTATION_RGB = 4, // $DESCRIPTION: "none (bypass)"
30 DT_ADAPTATION_LAST
31 } dt_adaptation_t;
32
33
34 // modified LMS cone response space for Bradford transform
35 // explanation here : https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119021780.app3
36 // but coeffs are wrong in the above, so they come from :
37 // http://www2.cmp.uea.ac.uk/Research/compvis/Papers/FinSuss_COL00.pdf
38 // At any time, ensure XYZ_to_LMS is the exact matrice inverse of LMS_to_XYZ
39 const float DT_ALIGNED_ARRAY XYZ_to_Bradford_LMS[3][4] = { { 0.8951f, 0.2664f, -0.1614f, 0.f },
40 { -0.7502f, 1.7135f, 0.0367f, 0.f },
41 { 0.0389f, -0.0685f, 1.0296f, 0.f } };
42
43 const float DT_ALIGNED_ARRAY Bradford_LMS_to_XYZ[3][4] = { { 0.9870f, -0.1471f, 0.1600f, 0.f },
44 { 0.4323f, 0.5184f, 0.0493f, 0.f },
45 { -0.0085f, 0.0400f, 0.9685f, 0.f } };
46
47 #ifdef _OPENMP
48 #pragma omp declare simd aligned(XYZ, LMS:16)
49 #endif
convert_XYZ_to_bradford_LMS(const float XYZ[4],float LMS[4])50 static inline void convert_XYZ_to_bradford_LMS(const float XYZ[4], float LMS[4])
51 {
52 // Warning : needs XYZ normalized with Y - you need to downscale before
53 dot_product(XYZ, XYZ_to_Bradford_LMS, LMS);
54 }
55
56 #ifdef _OPENMP
57 #pragma omp declare simd aligned(XYZ, LMS:16)
58 #endif
convert_bradford_LMS_to_XYZ(const float LMS[4],float XYZ[4])59 static inline void convert_bradford_LMS_to_XYZ(const float LMS[4], float XYZ[4])
60 {
61 // Warning : output XYZ normalized with Y - you need to upscale later
62 dot_product(LMS, Bradford_LMS_to_XYZ, XYZ);
63 }
64
65
66 // modified LMS cone response for CAT16, from CIECAM16
67 // reference : https://ntnuopen.ntnu.no/ntnu-xmlui/bitstream/handle/11250/2626317/CCIW-23.pdf?sequence=1
68 // At any time, ensure XYZ_to_LMS is the exact matrice inverse of LMS_to_XYZ
69 const float DT_ALIGNED_ARRAY XYZ_to_CAT16_LMS[3][4] = { { 0.401288f, 0.650173f, -0.051461f, 0.f },
70 { -0.250268f, 1.204414f, 0.045854f, 0.f },
71 { -0.002079f, 0.048952f, 0.953127f, 0.f } };
72
73 const float DT_ALIGNED_ARRAY CAT16_LMS_to_XYZ[3][4] = { { 1.862068f, -1.011255f, 0.149187f, 0.f },
74 { 0.38752f , 0.621447f, -0.008974f, 0.f },
75 { -0.015841f, -0.034123f, 1.049964f, 0.f } };
76
77 #ifdef _OPENMP
78 #pragma omp declare simd aligned(XYZ, LMS:16)
79 #endif
convert_XYZ_to_CAT16_LMS(const float XYZ[4],float LMS[4])80 static inline void convert_XYZ_to_CAT16_LMS(const float XYZ[4], float LMS[4])
81 {
82 // Warning : needs XYZ normalized with Y - you need to downscale before
83 dot_product(XYZ, XYZ_to_CAT16_LMS, LMS);
84 }
85
86 #ifdef _OPENMP
87 #pragma omp declare simd aligned(XYZ, LMS:16)
88 #endif
convert_CAT16_LMS_to_XYZ(const float LMS[4],float XYZ[4])89 static inline void convert_CAT16_LMS_to_XYZ(const float LMS[4], float XYZ[4])
90 {
91 // Warning : output XYZ normalized with Y - you need to upscale later
92 dot_product(LMS, CAT16_LMS_to_XYZ, XYZ);
93 }
94
95
96 #ifdef _OPENMP
97 #pragma omp declare simd aligned(XYZ, LMS:16) uniform(kind)
98 #endif
convert_any_LMS_to_XYZ(const float LMS[4],float XYZ[4],const dt_adaptation_t kind)99 static inline void convert_any_LMS_to_XYZ(const float LMS[4], float XYZ[4], const dt_adaptation_t kind)
100 {
101 // helper function switching internally to the proper conversion
102
103 switch(kind)
104 {
105 case DT_ADAPTATION_FULL_BRADFORD:
106 case DT_ADAPTATION_LINEAR_BRADFORD:
107 {
108 convert_bradford_LMS_to_XYZ(LMS, XYZ);
109 break;
110 }
111 case DT_ADAPTATION_CAT16:
112 {
113 convert_CAT16_LMS_to_XYZ(LMS, XYZ);
114 break;
115 }
116 case DT_ADAPTATION_XYZ:
117 case DT_ADAPTATION_RGB:
118 case DT_ADAPTATION_LAST:
119 {
120 // special case : just pass through.
121 XYZ[0] = LMS[0];
122 XYZ[1] = LMS[1];
123 XYZ[2] = LMS[2];
124 break;
125 }
126 }
127 }
128
129
130 #ifdef _OPENMP
131 #pragma omp declare simd aligned(XYZ, LMS:16) uniform(kind)
132 #endif
convert_any_XYZ_to_LMS(const float XYZ[4],float LMS[4],dt_adaptation_t kind)133 static inline void convert_any_XYZ_to_LMS(const float XYZ[4], float LMS[4], dt_adaptation_t kind)
134 {
135 // helper function switching internally to the proper conversion
136
137 switch(kind)
138 {
139 case DT_ADAPTATION_FULL_BRADFORD:
140 case DT_ADAPTATION_LINEAR_BRADFORD:
141 {
142 convert_XYZ_to_bradford_LMS(XYZ, LMS);
143 break;
144 }
145 case DT_ADAPTATION_CAT16:
146 {
147 convert_XYZ_to_CAT16_LMS(XYZ, LMS);
148 break;
149 }
150 case DT_ADAPTATION_XYZ:
151 case DT_ADAPTATION_RGB:
152 case DT_ADAPTATION_LAST:
153 {
154 // special case : just pass through.
155 LMS[0] = XYZ[0];
156 LMS[1] = XYZ[1];
157 LMS[2] = XYZ[2];
158 break;
159 }
160 }
161 }
162
163
164 #ifdef _OPENMP
165 #pragma omp declare simd aligned(RGB, LMS:16) uniform(kind)
166 #endif
convert_any_LMS_to_RGB(const float LMS[4],float RGB[4],dt_adaptation_t kind)167 static inline void convert_any_LMS_to_RGB(const float LMS[4], float RGB[4], dt_adaptation_t kind)
168 {
169 // helper function switching internally to the proper conversion
170 float DT_ALIGNED_PIXEL XYZ[4] = { 0.f };
171 convert_any_LMS_to_XYZ(LMS, XYZ, kind);
172
173 // Fixme : convert to RGB display space instead of sRGB but first the display profile should be global in dt,
174 // not confined to colorout where it gets created/destroyed all the time.
175 dt_XYZ_to_Rec709_D65(XYZ, RGB);
176
177 // Handle gamut clipping
178 float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
179 for(int c = 0; c < 3; c++) RGB[c] = fmaxf(RGB[c] / max_RGB, 0.f);
180
181 }
182
183
184 /* Bradford adaptations pre-computed for D50 and D65 outputs */
185
186 #ifdef _OPENMP
187 #pragma omp declare simd uniform(origin_illuminant) \
188 aligned(lms_in, lms_out, origin_illuminant:16)
189 #endif
bradford_adapt_D65(const float lms_in[4],const float origin_illuminant[4],const float p,const int full,float lms_out[4])190 static inline void bradford_adapt_D65(const float lms_in[4],
191 const float origin_illuminant[4],
192 const float p, const int full,
193 float lms_out[4])
194 {
195 // Bradford chromatic adaptation from origin to target D65 illuminant in LMS space
196 // p = powf(origin_illuminant[2] / D65[2], 0.0834f) needs to be precomputed for performance,
197 // since it is independent from current pixel values
198 // origin illuminant need also to be precomputed to LMS
199
200 // Precomputed D65 primaries in Bradford LMS for camera WB adjustment
201 const float DT_ALIGNED_PIXEL D65[4] = { 0.941238f, 1.040633f, 1.088932f, 0.f };
202
203
204 float DT_ALIGNED_PIXEL temp[4] = { lms_in[0] / origin_illuminant[0],
205 lms_in[1] / origin_illuminant[1],
206 lms_in[2] / origin_illuminant[2],
207 0.f };
208
209 // use linear Bradford if B is negative
210 if(full) temp[2] = (temp[2] > 0.f) ? powf(temp[2], p) : temp[2];
211
212 lms_out[0] = D65[0] * temp[0];
213 lms_out[1] = D65[1] * temp[1];
214 lms_out[2] = D65[2] * temp[2];
215 }
216
217
218 #ifdef _OPENMP
219 #pragma omp declare simd uniform(origin_illuminant) \
220 aligned(lms_in, lms_out, origin_illuminant:16)
221 #endif
bradford_adapt_D50(const float lms_in[4],const float origin_illuminant[4],const float p,const int full,float lms_out[4])222 static inline void bradford_adapt_D50(const float lms_in[4],
223 const float origin_illuminant[4],
224 const float p, const int full,
225 float lms_out[4])
226 {
227 // Bradford chromatic adaptation from origin to target D50 illuminant in LMS space
228 // p = powf(origin_illuminant[2] / D50[2], 0.0834f) needs to be precomputed for performance,
229 // since it is independent from current pixel values
230 // origin illuminant need also to be precomputed to LMS
231
232 // Precomputed D50 primaries in Bradford LMS for ICC transforms
233 const float DT_ALIGNED_PIXEL D50[4] = { 0.996078f, 1.020646f, 0.818155f, 0.f };
234
235
236 float DT_ALIGNED_PIXEL temp[4] = { lms_in[0] / origin_illuminant[0],
237 lms_in[1] / origin_illuminant[1],
238 lms_in[2] / origin_illuminant[2],
239 0.f };
240
241 // use linear Bradford if B is negative
242 if(full) temp[2] = (temp[2] > 0.f) ? powf(temp[2], p) : temp[2];
243
244 lms_out[0] = D50[0] * temp[0];
245 lms_out[1] = D50[1] * temp[1];
246 lms_out[2] = D50[2] * temp[2];
247 }
248
249
250 /* CAT16 adaptations pre-computed for D50 and D65 outputs */
251
252 #ifdef _OPENMP
253 #pragma omp declare simd uniform(origin_illuminant) \
254 aligned(lms_in, lms_out, origin_illuminant:16)
255 #endif
CAT16_adapt_D65(const float lms_in[4],const float origin_illuminant[4],const float D,const int full,float lms_out[4])256 static inline void CAT16_adapt_D65(const float lms_in[4],
257 const float origin_illuminant[4],
258 const float D, const int full,
259 float lms_out[4])
260 {
261 // CAT16 chromatic adaptation from origin to target D65 illuminant in LMS space
262 // D is the coefficient of adaptation, depending of the surround lighting
263 // origin illuminant need also to be precomputed to LMS
264
265 // Precomputed D65 primaries in CAT16 LMS for camera WB adjustment
266 const float DT_ALIGNED_PIXEL D65[4] = { 0.97553267f, 1.01647859f, 1.0848344f, 0.f };
267
268 if(full)
269 {
270 lms_out[0] = lms_in[0] * D65[0] / origin_illuminant[0];
271 lms_out[1] = lms_in[1] * D65[1] / origin_illuminant[1];
272 lms_out[2] = lms_in[2] * D65[2] / origin_illuminant[2];
273 }
274 else
275 {
276 lms_out[0] = lms_in[0] * (D * D65[0] / origin_illuminant[0] + 1.f - D);
277 lms_out[1] = lms_in[1] * (D * D65[1] / origin_illuminant[1] + 1.f - D);
278 lms_out[2] = lms_in[2] * (D * D65[2] / origin_illuminant[2] + 1.f - D);
279 }
280 }
281
282
283 #ifdef _OPENMP
284 #pragma omp declare simd uniform(origin_illuminant) \
285 aligned(lms_in, lms_out, origin_illuminant:16)
286 #endif
CAT16_adapt_D50(const float lms_in[4],const float origin_illuminant[4],const float D,const int full,float lms_out[4])287 static inline void CAT16_adapt_D50(const float lms_in[4],
288 const float origin_illuminant[4],
289 const float D, const int full,
290 float lms_out[4])
291 {
292 // CAT16 chromatic adaptation from origin to target D50 illuminant in LMS space
293 // D is the coefficient of adaptation, depending of the surround lighting
294 // origin illuminant need also to be precomputed to LMS
295
296 // Precomputed D50 primaries in CAT16 LMS for ICC transforms
297 const float DT_ALIGNED_PIXEL D50[4] = { 0.994535f, 1.000997f, 0.833036f, 0.f };
298
299 if(full)
300 {
301 lms_out[0] = lms_in[0] * D50[0] / origin_illuminant[0];
302 lms_out[1] = lms_in[1] * D50[1] / origin_illuminant[1];
303 lms_out[2] = lms_in[2] * D50[2] / origin_illuminant[2];
304 }
305 else
306 {
307 lms_out[0] = lms_in[0] * (D * D50[0] / origin_illuminant[0] + 1.f - D);
308 lms_out[1] = lms_in[1] * (D * D50[1] / origin_illuminant[1] + 1.f - D);
309 lms_out[2] = lms_in[2] * (D * D50[2] / origin_illuminant[2] + 1.f - D);
310 }
311 }
312
313 /* XYZ adaptations pre-computed for D50 and D65 outputs */
314
315 #ifdef _OPENMP
316 #pragma omp declare simd uniform(origin_illuminant) \
317 aligned(lms_in, lms_out, origin_illuminant:16)
318 #endif
XYZ_adapt_D65(const float lms_in[4],const float origin_illuminant[4],float lms_out[4])319 static inline void XYZ_adapt_D65(const float lms_in[4],
320 const float origin_illuminant[4],
321 float lms_out[4])
322 {
323 // XYZ chromatic adaptation from origin to target D65 illuminant in XYZ space
324 // origin illuminant need also to be precomputed to XYZ
325
326 // Precomputed D65 primaries in XYZ for camera WB adjustment
327 const float DT_ALIGNED_PIXEL D65[4] = { 0.9504285453771807f, 1.0f, 1.0889003707981277f, 0.f };
328
329 lms_out[0] = lms_in[0] * D65[0] / origin_illuminant[0];
330 lms_out[1] = lms_in[1] * D65[1] / origin_illuminant[1];
331 lms_out[2] = lms_in[2] * D65[2] / origin_illuminant[2];
332 }
333
334 #ifdef _OPENMP
335 #pragma omp declare simd uniform(origin_illuminant) \
336 aligned(lms_in, lms_out, origin_illuminant:16)
337 #endif
XYZ_adapt_D50(const float lms_in[4],const float origin_illuminant[4],float lms_out[4])338 static inline void XYZ_adapt_D50(const float lms_in[4],
339 const float origin_illuminant[4],
340 float lms_out[4])
341 {
342 // XYZ chromatic adaptation from origin to target D65 illuminant in XYZ space
343 // origin illuminant need also to be precomputed to XYZ
344
345 // Precomputed D50 primaries in XYZ for camera WB adjustment
346 const float DT_ALIGNED_PIXEL D50[4] = { 0.9642119944211994f, 1.0f, 0.8251882845188288f, 0.f };
347
348 lms_out[0] = lms_in[0] * D50[0] / origin_illuminant[0];
349 lms_out[1] = lms_in[1] * D50[1] / origin_illuminant[1];
350 lms_out[2] = lms_in[2] * D50[2] / origin_illuminant[2];
351 }
352
353 /* Pre-solved matrices to adjust white point for triplets in CIE XYZ 1931 2° observer */
354
355 const float DT_ALIGNED_ARRAY XYZ_D50_to_D65_CAT16[3][4]
356 = { { 9.89466254e-01f, -4.00304626e-02f, 4.40530317e-02f, 0.f },
357 { -5.40518733e-03f, 1.00666069e+00f, -1.75551955e-03f, 0.f },
358 { -4.03920992e-04f, 1.50768030e-02f, 1.30210211e+00f, 0.f } };
359
360 const float DT_ALIGNED_ARRAY XYZ_D50_to_D65_Bradford[3][4]
361 = { { 0.95547342f, -0.02309845f, 0.06325924f, 0.f },
362 { -0.02836971f, 1.00999540f, 0.02104144f, 0.f },
363 { 0.01231401f, -0.02050765f, 1.33036593f, 0.f } };
364
365 const float DT_ALIGNED_ARRAY XYZ_D65_to_D50_CAT16[3][4]
366 = { { 1.01085433e+00f, 4.07086103e-02f, -3.41445825e-02f, 0.f },
367 { 5.42814201e-03f, 9.93581926e-01f, 1.15592039e-03f, 0.f },
368 { 2.50722468e-04f, -1.14918759e-02f, 7.67964947e-01f, 0.f } };
369
370 const float DT_ALIGNED_ARRAY XYZ_D65_to_D50_Bradford[3][4]
371 = { { 1.04792979f, 0.02294687f, -0.05019227f, 0.f },
372 { 0.02962781f, 0.99043443f, -0.0170738f, 0.f },
373 { -0.00924304f, 0.01505519f, 0.75187428f, 0.f } };
374
375 #ifdef _OPENMP
376 #pragma omp declare simd aligned(XYZ_in, XYZ_out:16)
377 #endif
XYZ_D50_to_D65(const float XYZ_in[4],float XYZ_out[4])378 static inline void XYZ_D50_to_D65(const float XYZ_in[4], float XYZ_out[4])
379 {
380 dot_product(XYZ_in, XYZ_D50_to_D65_CAT16, XYZ_out);
381 }
382
383 #ifdef _OPENMP
384 #pragma omp declare simd aligned(XYZ_in, XYZ_out:16)
385 #endif
XYZ_D65_to_D50(const float XYZ_in[4],float XYZ_out[4])386 static inline void XYZ_D65_to_D50(const float XYZ_in[4], float XYZ_out[4])
387 {
388 dot_product(XYZ_in, XYZ_D65_to_D50_CAT16, XYZ_out);
389 }
390