1 /*
2     This file is part of darktable,
3     Copyright (C) 2010-2021 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 #include "common/colorspaces.h"
20 #include "common/colormatrices.c"
21 #include "common/darktable.h"
22 #include "common/debug.h"
23 #include "common/file_location.h"
24 #include "common/math.h"
25 #include "common/srgb_tone_curve_values.h"
26 #include "common/utility.h"
27 #include "control/conf.h"
28 #include "control/control.h"
29 #include "develop/imageop.h"
30 #include "external/adobe_coeff.c"
31 
32 #include <strings.h>
33 
34 #ifdef USE_COLORDGTK
35 #include "colord-gtk.h"
36 #endif
37 
38 #if 0
39 #include <ApplicationServices/ApplicationServices.h>
40 #include <Carbon/Carbon.h>
41 #include <CoreServices/CoreServices.h>
42 #endif
43 
44 static const cmsCIEXYZ d65 = {0.95045471, 1.00000000, 1.08905029};
45 
46 //D65 (sRGB, AdobeRGB, Rec2020)
47 static const cmsCIExyY D65xyY = {0.312700492, 0.329000939, 1.0};
48 
49 //D60
50 //static const cmsCIExyY d60 = {0.32168, 0.33767, 1.0};
51 
52 //D50 (ProPhoto RGB)
53 static const cmsCIExyY D50xyY = {0.3457, 0.3585, 1.0};
54 
55 // D65:
56 static const cmsCIExyYTRIPLE sRGB_Primaries = {
57   {0.6400, 0.3300, 1.0}, // red
58   {0.3000, 0.6000, 1.0}, // green
59   {0.1500, 0.0600, 1.0}  // blue
60 };
61 
62 // D65:
63 static const cmsCIExyYTRIPLE Rec2020_Primaries = {
64   {0.7080, 0.2920, 1.0}, // red
65   {0.1700, 0.7970, 1.0}, // green
66   {0.1310, 0.0460, 1.0}  // blue
67 };
68 
69 // D65:
70 static const cmsCIExyYTRIPLE Rec709_Primaries = {
71   {0.6400, 0.3300, 1.0}, // red
72   {0.3000, 0.6000, 1.0}, // green
73   {0.1500, 0.0600, 1.0}  // blue
74 };
75 
76 // D65:
77 static const cmsCIExyYTRIPLE Adobe_Primaries = {
78   {0.6400, 0.3300, 1.0}, // red
79   {0.2100, 0.7100, 1.0}, // green
80   {0.1500, 0.0600, 1.0}  // blue
81 };
82 
83 // D65:
84 static const cmsCIExyYTRIPLE P3_Primaries = {
85   {0.680, 0.320, 1.0}, // red
86   {0.265, 0.690, 1.0}, // green
87   {0.150, 0.060, 1.0}  // blue
88 };
89 
90 // https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space
91 // D50:
92 static const cmsCIExyYTRIPLE ProPhoto_Primaries = {
93   /*       x,        y,       Y */
94   { 0.734699, 0.265301, 1.0000 }, /* red   */
95   { 0.159597, 0.840403, 1.0000 }, /* green */
96   { 0.036598, 0.000105, 1.0000 }, /* blue  */
97 };
98 
99 cmsCIEXYZTRIPLE Rec709_Primaries_Prequantized;
100 
101 #define generate_mat3inv_body(c_type, A, B)                                                                  \
102   int mat3inv_##c_type(c_type *const dst, const c_type *const src)                                           \
103   {                                                                                                          \
104                                                                                                              \
105     const c_type det = A(1, 1) * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3))                                     \
106                        - A(2, 1) * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3))                                   \
107                        + A(3, 1) * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3));                                  \
108                                                                                                              \
109     const c_type epsilon = 1e-7f;                                                                            \
110     if(fabs(det) < epsilon) return 1;                                                                        \
111                                                                                                              \
112     const c_type invDet = 1.0 / det;                                                                         \
113                                                                                                              \
114     B(1, 1) = invDet * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3));                                              \
115     B(1, 2) = -invDet * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3));                                             \
116     B(1, 3) = invDet * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3));                                              \
117                                                                                                              \
118     B(2, 1) = -invDet * (A(3, 3) * A(2, 1) - A(3, 1) * A(2, 3));                                             \
119     B(2, 2) = invDet * (A(3, 3) * A(1, 1) - A(3, 1) * A(1, 3));                                              \
120     B(2, 3) = -invDet * (A(2, 3) * A(1, 1) - A(2, 1) * A(1, 3));                                             \
121                                                                                                              \
122     B(3, 1) = invDet * (A(3, 2) * A(2, 1) - A(3, 1) * A(2, 2));                                              \
123     B(3, 2) = -invDet * (A(3, 2) * A(1, 1) - A(3, 1) * A(1, 2));                                             \
124     B(3, 3) = invDet * (A(2, 2) * A(1, 1) - A(2, 1) * A(1, 2));                                              \
125     return 0;                                                                                                \
126   }
127 
128 #define A(y, x) src[(y - 1) * 3 + (x - 1)]
129 #define B(y, x) dst[(y - 1) * 3 + (x - 1)]
130 /** inverts the given 3x3 matrix */
generate_mat3inv_body(float,A,B)131 generate_mat3inv_body(float, A, B)
132 
133     int mat3inv(float *const dst, const float *const src)
134 {
135   return mat3inv_float(dst, src);
136 }
137 
138 generate_mat3inv_body(double, A, B)
139 #undef B
140 #undef A
141 #undef generate_mat3inv_body
142 
143 
144 static const dt_colorspaces_color_profile_t *_get_profile(dt_colorspaces_t *self,
145                                                           dt_colorspaces_color_profile_type_t type,
146                                                           const char *filename,
147                                                           dt_colorspaces_profile_direction_t direction);
148 
dt_colorspaces_get_matrix_from_profile(cmsHPROFILE prof,float * matrix,float * lutr,float * lutg,float * lutb,const int lutsize,const int input)149 static int dt_colorspaces_get_matrix_from_profile(cmsHPROFILE prof, float *matrix, float *lutr, float *lutg,
150                                                   float *lutb, const int lutsize, const int input)
151 {
152   // create an OpenCL processable matrix + tone curves from an cmsHPROFILE:
153   // NOTE: may be invoked with matrix and LUT pointers set to null to find
154   // out if the profile can be created at all.
155 
156   // check this first:
157   if(!prof || !cmsIsMatrixShaper(prof)) return 1;
158 
159   // there are some profiles that contain both a color LUT for some specific
160   // intent and a generic matrix. in some cases the matrix might be
161   // deliberately wrong with swapped blue and red channels in order to easily
162   // detect if a color managed software is applying the LUT or the matrix.
163   // thus, if this profile contains LUT for any intent, it might also contain
164   // swapped matrix, so the only right way to handle it is to let LCMS apply it.
165   const int UsedDirection = input ? LCMS_USED_AS_INPUT : LCMS_USED_AS_OUTPUT;
166 
167   if(cmsIsCLUT(prof, INTENT_PERCEPTUAL, UsedDirection)
168      || cmsIsCLUT(prof, INTENT_RELATIVE_COLORIMETRIC, UsedDirection)
169      || cmsIsCLUT(prof, INTENT_ABSOLUTE_COLORIMETRIC, UsedDirection)
170      || cmsIsCLUT(prof, INTENT_SATURATION, UsedDirection))
171     return 1;
172 
173   cmsToneCurve *red_curve = cmsReadTag(prof, cmsSigRedTRCTag);
174   cmsToneCurve *green_curve = cmsReadTag(prof, cmsSigGreenTRCTag);
175   cmsToneCurve *blue_curve = cmsReadTag(prof, cmsSigBlueTRCTag);
176 
177   cmsCIEXYZ *red_color = cmsReadTag(prof, cmsSigRedColorantTag);
178   cmsCIEXYZ *green_color = cmsReadTag(prof, cmsSigGreenColorantTag);
179   cmsCIEXYZ *blue_color = cmsReadTag(prof, cmsSigBlueColorantTag);
180 
181   if(!red_curve || !green_curve || !blue_curve || !red_color || !green_color || !blue_color) return 2;
182 
183   float matrix_tmp[9];
184   matrix_tmp[0] = red_color->X;
185   matrix_tmp[1] = green_color->X;
186   matrix_tmp[2] = blue_color->X;
187   matrix_tmp[3] = red_color->Y;
188   matrix_tmp[4] = green_color->Y;
189   matrix_tmp[5] = blue_color->Y;
190   matrix_tmp[6] = red_color->Z;
191   matrix_tmp[7] = green_color->Z;
192   matrix_tmp[8] = blue_color->Z;
193 
194   // some camera ICC profiles claim to have color locations for red, green and blue base colors defined,
195   // but in fact these are all set to zero. we catch this case here.
196   float sum = 0.0f;
197   for(int k = 0; k < 9; k++) sum += matrix_tmp[k];
198   if(sum == 0.0f) return 3;
199 
200   if(input && lutr && lutg && lutb)
201   {
202     // mark as linear, if they are:
203     if(cmsIsToneCurveLinear(red_curve))
204       lutr[0] = -1.0f;
205     else
206       for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(red_curve, k / (lutsize - 1.0f));
207     if(cmsIsToneCurveLinear(green_curve))
208       lutg[0] = -1.0f;
209     else
210       for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(green_curve, k / (lutsize - 1.0f));
211     if(cmsIsToneCurveLinear(blue_curve))
212       lutb[0] = -1.0f;
213     else
214       for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(blue_curve, k / (lutsize - 1.0f));
215   }
216   else
217   {
218     // invert profile->XYZ matrix for output profiles
219     float tmp[9];
220     memcpy(tmp, matrix_tmp, sizeof(float) * 9);
221     if(mat3inv(matrix_tmp, tmp)) return 3;
222     // also need to reverse gamma, to apply reverse before matrix multiplication:
223     cmsToneCurve *rev_red = cmsReverseToneCurveEx(0x8000, red_curve);
224     cmsToneCurve *rev_green = cmsReverseToneCurveEx(0x8000, green_curve);
225     cmsToneCurve *rev_blue = cmsReverseToneCurveEx(0x8000, blue_curve);
226     if(!rev_red || !rev_green || !rev_blue)
227     {
228       cmsFreeToneCurve(rev_red);
229       cmsFreeToneCurve(rev_green);
230       cmsFreeToneCurve(rev_blue);
231       return 4;
232     }
233 
234     if(lutr && lutg && lutb)
235     {
236       // pass on tonecurves, in case lutsize > 0:
237       if(cmsIsToneCurveLinear(red_curve))
238         lutr[0] = -1.0f;
239       else
240         for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(rev_red, k / (lutsize - 1.0f));
241       if(cmsIsToneCurveLinear(green_curve))
242         lutg[0] = -1.0f;
243       else
244         for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(rev_green, k / (lutsize - 1.0f));
245       if(cmsIsToneCurveLinear(blue_curve))
246         lutb[0] = -1.0f;
247       else
248         for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(rev_blue, k / (lutsize - 1.0f));
249     }
250 
251     cmsFreeToneCurve(rev_red);
252     cmsFreeToneCurve(rev_green);
253     cmsFreeToneCurve(rev_blue);
254   }
255 
256   if(matrix) memcpy(matrix, matrix_tmp, sizeof(float) * 9);
257 
258   return 0;
259 }
260 
dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof,float * matrix,float * lutr,float * lutg,float * lutb,const int lutsize)261 int dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof, float *matrix, float *lutr, float *lutg,
262                                                  float *lutb, const int lutsize)
263 {
264   return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 1);
265 }
266 
dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof,float * matrix,float * lutr,float * lutg,float * lutb,const int lutsize)267 int dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof, float *matrix, float *lutr, float *lutg,
268                                                   float *lutb, const int lutsize)
269 {
270   return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 0);
271 }
272 
dt_colorspaces_create_lab_profile()273 static cmsHPROFILE dt_colorspaces_create_lab_profile()
274 {
275   return cmsCreateLab4Profile(cmsD50_xyY());
276 }
277 
_compute_prequantized_primaries(const cmsCIExyY * whitepoint,const cmsCIExyYTRIPLE * primaries,cmsCIEXYZTRIPLE * primaries_prequantized)278 static void _compute_prequantized_primaries(const cmsCIExyY* whitepoint,
279                                             const cmsCIExyYTRIPLE* primaries,
280                                             cmsCIEXYZTRIPLE *primaries_prequantized)
281 {
282   cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, NULL);
283 
284   cmsCIEXYZ *R = cmsReadTag(profile, cmsSigRedColorantTag);
285   cmsCIEXYZ *G = cmsReadTag(profile, cmsSigGreenColorantTag);
286   cmsCIEXYZ *B = cmsReadTag(profile, cmsSigBlueColorantTag);
287 
288   primaries_prequantized->Red.X   = (double)R->X;
289   primaries_prequantized->Red.Y   = (double)R->Y;
290   primaries_prequantized->Red.Z   = (double)R->Z;
291 
292   primaries_prequantized->Green.X = (double)G->X;
293   primaries_prequantized->Green.Y = (double)G->Y;
294   primaries_prequantized->Green.Z = (double)G->Z;
295 
296   primaries_prequantized->Blue.X  = (double)B->X;
297   primaries_prequantized->Blue.Y  = (double)B->Y;
298   primaries_prequantized->Blue.Z  = (double)B->Z;
299 
300   cmsCloseProfile(profile);
301 }
302 
_create_lcms_profile(const char * desc,const char * dmdd,const cmsCIExyY * whitepoint,const cmsCIExyYTRIPLE * primaries,cmsToneCurve * trc,gboolean v2)303 static cmsHPROFILE _create_lcms_profile(const char *desc, const char *dmdd,
304                                         const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsToneCurve *trc,
305                                         gboolean v2)
306 {
307   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
308   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
309   cmsMLU *mlu3 = cmsMLUalloc(NULL, 1);
310   cmsMLU *mlu4 = cmsMLUalloc(NULL, 1);
311 
312   cmsToneCurve *out_curves[3] = { trc, trc, trc };
313   cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, out_curves);
314 
315   if(v2) cmsSetProfileVersion(profile, 2.4);
316 
317   cmsSetHeaderFlags(profile, cmsEmbeddedProfileTrue);
318 
319   cmsMLUsetASCII(mlu1, "en", "US", "Public Domain");
320   cmsWriteTag(profile, cmsSigCopyrightTag, mlu1);
321 
322   cmsMLUsetASCII(mlu2, "en", "US", desc);
323   cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
324 
325   cmsMLUsetASCII(mlu3, "en", "US", dmdd);
326   cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu3);
327 
328   cmsMLUsetASCII(mlu4, "en", "US", "darktable");
329   cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu4);
330 
331   cmsMLUfree(mlu1);
332   cmsMLUfree(mlu2);
333   cmsMLUfree(mlu3);
334   cmsMLUfree(mlu4);
335 
336   return profile;
337 }
338 
339 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
340 // Perceptual Quantization / SMPTE standard ST.2084
_PQ_fct(double x)341 static double _PQ_fct(double x)
342 {
343   static const double M1 = 2610.0 / 16384.0;
344   static const double M2 = (2523.0 / 4096.0) * 128.0;
345   static const double C1 = 3424.0 / 4096.0;
346   static const double C2 = (2413.0 / 4096.0) * 32.0;
347   static const double C3 = (2392.0 / 4096.0) * 32.0;
348 
349   if (x == 0.0) return 0.0;
350   const double sign = x;
351   x = fabs(x);
352 
353   const double xpo = pow(x, 1.0 / M2);
354   const double num = MAX(xpo - C1, 0.0);
355   const double den = C2 - C3 * xpo;
356   const double res = pow(num / den, 1.0 / M1);
357 
358   return copysign(res, sign);
359 }
360 
361 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
362 // Hybrid Log-Gamma
_HLG_fct(double x)363 static double _HLG_fct(double x)
364 {
365   static const double Beta  = 0.04;
366   static const double RA    = 5.591816309728916; // 1.0 / A where A = 0.17883277
367   static const double B     = 0.28466892; // 1.0 - 4.0 * A
368   static const double C     = 0.5599107295; // 0,5 –aln(4a)
369 
370   double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
371 
372   if (e == 0.0) return 0.0;
373 
374   const double sign = e;
375   e = fabs(e);
376 
377   double res = 0.0;
378 
379   if (e <= 0.5)
380   {
381     res = e * e / 3.0;
382   }
383   else
384   {
385     res = (exp((e - C) * RA) + B) / 12.0;
386   }
387 
388   return copysign(res, sign);
389 }
390 
_colorspaces_create_transfer(int32_t size,double (* fct)(double))391 static cmsToneCurve* _colorspaces_create_transfer(int32_t size, double (*fct)(double))
392 {
393   float *values = g_malloc(sizeof(float) * size);
394 
395   for (int32_t i = 0; i < size; ++i)
396   {
397     const double x = (float)i / (size - 1);
398     const double y = MIN(fct(x), 1.0f);
399     values[i] = (float)y;
400   }
401 
402   cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(NULL, size, values);
403   g_free(values);
404   return result;
405 }
406 
_colorspaces_create_srgb_profile(gboolean v2)407 static cmsHPROFILE _colorspaces_create_srgb_profile(gboolean v2)
408 {
409   cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
410   cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
411 
412   cmsHPROFILE profile = _create_lcms_profile("sRGB", "sRGB",
413                                              &D65xyY, &sRGB_Primaries, transferFunction, v2);
414 
415   cmsFreeToneCurve(transferFunction);
416 
417   return profile;
418 }
419 
dt_colorspaces_create_srgb_profile()420 static cmsHPROFILE dt_colorspaces_create_srgb_profile()
421 {
422   return _colorspaces_create_srgb_profile(TRUE);
423 }
424 
dt_colorspaces_create_srgb_profile_v4()425 static cmsHPROFILE dt_colorspaces_create_srgb_profile_v4()
426 {
427   return _colorspaces_create_srgb_profile(FALSE);
428 }
429 
dt_colorspaces_create_brg_profile()430 static cmsHPROFILE dt_colorspaces_create_brg_profile()
431 {
432   cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
433   cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
434 
435   cmsCIExyYTRIPLE BRG_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Red, sRGB_Primaries.Green };
436 
437   cmsHPROFILE profile = _create_lcms_profile("BRG", "BRG",
438                                              &D65xyY, &BRG_Primaries, transferFunction, TRUE);
439 
440   cmsFreeToneCurve(transferFunction);
441 
442   return profile;
443 }
444 
dt_colorspaces_create_gamma_rec709_rgb_profile(void)445 static cmsHPROFILE dt_colorspaces_create_gamma_rec709_rgb_profile(void)
446 {
447   cmsFloat64Number srgb_parameters[5] = { 1/0.45, 1.0 / 1.099,  0.099 / 1.099, 1.0 / 4.5, 0.081 };
448   cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
449 
450   cmsHPROFILE profile = _create_lcms_profile("Gamma Rec709 RGB", "Gamma Rec709 RGB",
451                                              &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
452 
453   cmsFreeToneCurve(transferFunction);
454 
455   return profile;
456 }
457 
458 // Create the ICC virtual profile for adobe rgb space
dt_colorspaces_create_adobergb_profile(void)459 static cmsHPROFILE dt_colorspaces_create_adobergb_profile(void)
460 {
461   // AdobeRGB's "2.2" gamma is technically defined as 2 + 51/256
462   cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 2.19921875);
463 
464   cmsHPROFILE profile = _create_lcms_profile("Adobe RGB (compatible)", "Adobe RGB",
465                                              &D65xyY, &Adobe_Primaries, transferFunction, TRUE);
466 
467   cmsFreeToneCurve(transferFunction);
468 
469   return profile;
470 }
471 
dt_colorspaces_get_darktable_matrix(const char * makermodel,float * matrix)472 int dt_colorspaces_get_darktable_matrix(const char *makermodel, float *matrix)
473 {
474   dt_profiled_colormatrix_t *preset = NULL;
475   for(int k = 0; k < dt_profiled_colormatrix_cnt; k++)
476   {
477     if(!strcasecmp(makermodel, dt_profiled_colormatrices[k].makermodel))
478     {
479       preset = dt_profiled_colormatrices + k;
480       break;
481     }
482   }
483   if(!preset) return -1;
484 
485   const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
486   const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
487   const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
488   const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
489 
490   const float xn = preset->white[0] / wxyz;
491   const float yn = preset->white[1] / wxyz;
492   const float xr = preset->rXYZ[0] / rxyz;
493   const float yr = preset->rXYZ[1] / rxyz;
494   const float xg = preset->gXYZ[0] / gxyz;
495   const float yg = preset->gXYZ[1] / gxyz;
496   const float xb = preset->bXYZ[0] / bxyz;
497   const float yb = preset->bXYZ[1] / bxyz;
498 
499   const float primaries[9] = { xr, xg, xb, yr, yg, yb, 1.0f - xr - yr, 1.0f - xg - yg, 1.0f - xb - yb };
500 
501   float result[9];
502   if(mat3inv(result, primaries)) return -1;
503 
504   const float whitepoint[3] = { xn / yn, 1.0f, (1.0f - xn - yn) / yn };
505   float coeff[3];
506 
507   // get inverse primary whitepoint
508   mat3mulv(coeff, result, whitepoint);
509 
510 
511   float tmp[9] = { coeff[0] * xr, coeff[1] * xg, coeff[2] * xb, coeff[0] * yr, coeff[1] * yg, coeff[2] * yb,
512                    coeff[0] * (1.0f - xr - yr), coeff[1] * (1.0f - xg - yg), coeff[2] * (1.0f - xb - yb) };
513 
514   // input whitepoint[] in XYZ with Y normalized to 1.0f
515   const float dn[3]
516       = { preset->white[0] / (float)preset->white[1], 1.0f, preset->white[2] / (float)preset->white[1] };
517   const float lam_rigg[9] = { 0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296 };
518   const float d50[3] = { 0.9642, 1.0, 0.8249 };
519 
520 
521   // adapt to d50
522   float chad_inv[9];
523   if(mat3inv(chad_inv, lam_rigg)) return -1;
524 
525   float cone_src_rgb[3], cone_dst_rgb[3];
526   mat3mulv(cone_src_rgb, lam_rigg, dn);
527   mat3mulv(cone_dst_rgb, lam_rigg, d50);
528 
529   const float cone[9]
530       = { cone_dst_rgb[0] / cone_src_rgb[0], 0.0f, 0.0f, 0.0f, cone_dst_rgb[1] / cone_src_rgb[1], 0.0f, 0.0f,
531           0.0f, cone_dst_rgb[2] / cone_src_rgb[2] };
532 
533   float tmp2[9];
534   float bradford[9];
535   mat3mul(tmp2, cone, lam_rigg);
536   mat3mul(bradford, chad_inv, tmp2);
537 
538   mat3mul(matrix, bradford, tmp);
539   return 0;
540 }
541 
dt_colorspaces_create_alternate_profile(const char * makermodel)542 cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
543 {
544   dt_profiled_colormatrix_t *preset = NULL;
545   for(int k = 0; k < dt_alternate_colormatrix_cnt; k++)
546   {
547     if(!strcmp(makermodel, dt_alternate_colormatrices[k].makermodel))
548     {
549       preset = dt_alternate_colormatrices + k;
550       break;
551     }
552   }
553   if(!preset) return NULL;
554 
555   const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
556   const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
557   const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
558   const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
559   cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
560   cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
561                                    { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
562                                    { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
563   cmsToneCurve *Gamma[3];
564   cmsHPROFILE hp;
565 
566   Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
567 
568   hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
569   cmsFreeToneCurve(Gamma[0]);
570   if(hp == NULL) return NULL;
571 
572   char name[512];
573   snprintf(name, sizeof(name), "darktable alternate %s", makermodel);
574   cmsSetProfileVersion(hp, 2.1);
575   cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
576   cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
577   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
578   cmsMLUsetASCII(mlu1, "en", "US", name);
579   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
580   cmsMLUsetASCII(mlu2, "en", "US", name);
581   cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
582   cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
583   // this will only be displayed when the embedded profile is read by for example GIMP
584   cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
585   cmsMLUfree(mlu0);
586   cmsMLUfree(mlu1);
587   cmsMLUfree(mlu2);
588 
589   return hp;
590 }
591 
dt_colorspaces_create_vendor_profile(const char * makermodel)592 cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
593 {
594   dt_profiled_colormatrix_t *preset = NULL;
595   for(int k = 0; k < dt_vendor_colormatrix_cnt; k++)
596   {
597     if(!strcmp(makermodel, dt_vendor_colormatrices[k].makermodel))
598     {
599       preset = dt_vendor_colormatrices + k;
600       break;
601     }
602   }
603   if(!preset) return NULL;
604 
605   const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
606   const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
607   const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
608   const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
609   cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
610   cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
611                                    { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
612                                    { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
613   cmsToneCurve *Gamma[3];
614   cmsHPROFILE hp;
615 
616   Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
617 
618   hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
619   cmsFreeToneCurve(Gamma[0]);
620   if(hp == NULL) return NULL;
621 
622   char name[512];
623   snprintf(name, sizeof(name), "darktable vendor %s", makermodel);
624   cmsSetProfileVersion(hp, 2.1);
625   cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
626   cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
627   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
628   cmsMLUsetASCII(mlu1, "en", "US", name);
629   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
630   cmsMLUsetASCII(mlu2, "en", "US", name);
631   cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
632   cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
633   // this will only be displayed when the embedded profile is read by for example GIMP
634   cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
635   cmsMLUfree(mlu0);
636   cmsMLUfree(mlu1);
637   cmsMLUfree(mlu2);
638 
639   return hp;
640 }
641 
dt_colorspaces_create_darktable_profile(const char * makermodel)642 cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
643 {
644   dt_profiled_colormatrix_t *preset = NULL;
645   for(int k = 0; k < dt_profiled_colormatrix_cnt; k++)
646   {
647     if(!strcasecmp(makermodel, dt_profiled_colormatrices[k].makermodel))
648     {
649       preset = dt_profiled_colormatrices + k;
650       break;
651     }
652   }
653   if(!preset) return NULL;
654 
655   const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
656   const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
657   const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
658   const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
659   cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
660   cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
661                                    { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
662                                    { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
663   cmsToneCurve *Gamma[3];
664   cmsHPROFILE hp;
665 
666   Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
667 
668   hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
669   cmsFreeToneCurve(Gamma[0]);
670   if(hp == NULL) return NULL;
671 
672   char name[512];
673   snprintf(name, sizeof(name), "darktable profiled %s", makermodel);
674   cmsSetProfileVersion(hp, 2.1);
675   cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
676   cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
677   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
678   cmsMLUsetASCII(mlu1, "en", "US", name);
679   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
680   cmsMLUsetASCII(mlu2, "en", "US", name);
681   cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
682   cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
683   // this will only be displayed when the embedded profile is read by for example GIMP
684   cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
685   cmsMLUfree(mlu0);
686   cmsMLUfree(mlu1);
687   cmsMLUfree(mlu2);
688 
689   return hp;
690 }
691 
dt_colorspaces_create_xyz_profile(void)692 static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
693 {
694   cmsHPROFILE hXYZ = cmsCreateXYZProfile();
695   cmsSetPCS(hXYZ, cmsSigXYZData);
696   cmsSetHeaderRenderingIntent(hXYZ, INTENT_PERCEPTUAL);
697 
698   if(hXYZ == NULL) return NULL;
699 
700   cmsSetProfileVersion(hXYZ, 2.1);
701   cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
702   cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
703   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
704   cmsMLUsetASCII(mlu1, "en", "US", "linear XYZ");
705   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
706   cmsMLUsetASCII(mlu2, "en", "US", "darktable linear XYZ");
707   cmsWriteTag(hXYZ, cmsSigDeviceMfgDescTag, mlu0);
708   cmsWriteTag(hXYZ, cmsSigDeviceModelDescTag, mlu1);
709   // this will only be displayed when the embedded profile is read by for example GIMP
710   cmsWriteTag(hXYZ, cmsSigProfileDescriptionTag, mlu2);
711   cmsMLUfree(mlu0);
712   cmsMLUfree(mlu1);
713   cmsMLUfree(mlu2);
714 
715   return hXYZ;
716 }
717 
dt_colorspaces_create_linear_rec709_rgb_profile(void)718 static cmsHPROFILE dt_colorspaces_create_linear_rec709_rgb_profile(void)
719 {
720   cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
721 
722   cmsHPROFILE profile = _create_lcms_profile("Linear Rec709 RGB", "Linear Rec709 RGB",
723                                              &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
724 
725   cmsFreeToneCurve(transferFunction);
726 
727   return profile;
728 }
729 
dt_colorspaces_create_linear_rec2020_rgb_profile(void)730 static cmsHPROFILE dt_colorspaces_create_linear_rec2020_rgb_profile(void)
731 {
732   cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
733 
734   cmsHPROFILE profile = _create_lcms_profile("Linear Rec2020 RGB", "Linear Rec2020 RGB",
735                                              &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
736 
737   cmsFreeToneCurve(transferFunction);
738 
739   return profile;
740 }
741 
dt_colorspaces_create_pq_rec2020_rgb_profile(void)742 static cmsHPROFILE dt_colorspaces_create_pq_rec2020_rgb_profile(void)
743 {
744   cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
745 
746   cmsHPROFILE profile = _create_lcms_profile("PQ Rec2020 RGB", "PQ Rec2020 RGB",
747                                              &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
748 
749   cmsFreeToneCurve(transferFunction);
750 
751   return profile;
752 }
753 
dt_colorspaces_create_hlg_rec2020_rgb_profile(void)754 static cmsHPROFILE dt_colorspaces_create_hlg_rec2020_rgb_profile(void)
755 {
756   cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
757 
758   cmsHPROFILE profile = _create_lcms_profile("HLG Rec2020 RGB", "HLG Rec2020 RGB",
759                                              &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
760 
761   cmsFreeToneCurve(transferFunction);
762 
763   return profile;
764 }
765 
dt_colorspaces_create_pq_p3_rgb_profile(void)766 static cmsHPROFILE dt_colorspaces_create_pq_p3_rgb_profile(void)
767 {
768   cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
769 
770   cmsHPROFILE profile = _create_lcms_profile("PQ P3 RGB", "PQ P3 RGB",
771                                              &D65xyY, &P3_Primaries, transferFunction, TRUE);
772 
773   cmsFreeToneCurve(transferFunction);
774 
775   return profile;
776 }
777 
dt_colorspaces_create_hlg_p3_rgb_profile(void)778 static cmsHPROFILE dt_colorspaces_create_hlg_p3_rgb_profile(void)
779 {
780   cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
781 
782   cmsHPROFILE profile = _create_lcms_profile("HLG P3 RGB", "HLG P3 RGB",
783                                              &D65xyY, &P3_Primaries, transferFunction, TRUE);
784 
785   cmsFreeToneCurve(transferFunction);
786 
787   return profile;
788 }
789 
dt_colorspaces_create_linear_prophoto_rgb_profile(void)790 static cmsHPROFILE dt_colorspaces_create_linear_prophoto_rgb_profile(void)
791 {
792   cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
793 
794   cmsHPROFILE profile = _create_lcms_profile("Linear ProPhoto RGB", "Linear ProPhoto RGB",
795                                              &D50xyY,  &ProPhoto_Primaries, transferFunction, TRUE);
796 
797   cmsFreeToneCurve(transferFunction);
798 
799   return profile;
800 }
801 
dt_colorspaces_create_linear_infrared_profile(void)802 static cmsHPROFILE dt_colorspaces_create_linear_infrared_profile(void)
803 {
804   cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
805 
806   // linear rgb with r and b swapped:
807   cmsCIExyYTRIPLE BGR_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Green, sRGB_Primaries.Red };
808 
809   cmsHPROFILE profile = _create_lcms_profile("Linear Infrared BGR", "darktable Linear Infrared BGR",
810                                              &D65xyY, &BGR_Primaries, transferFunction, FALSE);
811 
812   cmsFreeToneCurve(transferFunction);
813 
814   return profile;
815 }
816 
dt_colorspaces_get_work_profile(const int imgid)817 const dt_colorspaces_color_profile_t *dt_colorspaces_get_work_profile(const int imgid)
818 {
819   // find the colorin module -- the pointer stays valid until darktable shuts down
820   static const dt_iop_module_so_t *colorin = NULL;
821   if(colorin == NULL)
822   {
823     for(const GList *modules = darktable.iop; modules; modules = g_list_next(modules))
824     {
825       const dt_iop_module_so_t *module = (const dt_iop_module_so_t *)(modules->data);
826       if(!strcmp(module->op, "colorin"))
827       {
828         colorin = module;
829         break;
830       }
831     }
832   }
833 
834   const dt_colorspaces_color_profile_t *p = NULL;
835 
836   if(colorin && colorin->get_p)
837   {
838     // get the profile assigned from colorin
839     // FIXME: does this work when using JPEG thumbs and the image was never opened?
840     sqlite3_stmt *stmt;
841     DT_DEBUG_SQLITE3_PREPARE_V2(
842       dt_database_get(darktable.db),
843       "SELECT op_params FROM main.history WHERE imgid=?1 AND operation='colorin' ORDER BY num DESC LIMIT 1", -1,
844       &stmt, NULL);
845     DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
846     if(sqlite3_step(stmt) == SQLITE_ROW)
847     {
848       // use introspection to get the profile name from the binary params blob
849       const void *params = sqlite3_column_blob(stmt, 0);
850       dt_colorspaces_color_profile_type_t *type = colorin->get_p(params, "type_work");
851       char *filename = colorin->get_p(params, "filename_work");
852 
853       if(type && filename) p = dt_colorspaces_get_profile(*type, filename,
854                                                           DT_PROFILE_DIRECTION_WORK);
855     }
856     sqlite3_finalize(stmt);
857   }
858 
859   // if all else fails -> fall back to linear Rec2020 RGB
860   if(!p) p = dt_colorspaces_get_profile(DT_COLORSPACE_LIN_REC2020, "", DT_PROFILE_DIRECTION_WORK);
861 
862   return p;
863 }
864 
dt_colorspaces_get_output_profile(const int imgid,dt_colorspaces_color_profile_type_t over_type,const char * over_filename)865 const dt_colorspaces_color_profile_t *dt_colorspaces_get_output_profile(const int imgid,
866                                                                         dt_colorspaces_color_profile_type_t over_type,
867                                                                         const char *over_filename)
868 {
869   // find the colorout module -- the pointer stays valid until darktable shuts down
870   static const dt_iop_module_so_t *colorout = NULL;
871   if(colorout == NULL)
872   {
873     for(const GList *modules = darktable.iop; modules; modules = g_list_next(modules))
874     {
875       const dt_iop_module_so_t *module = (const dt_iop_module_so_t *)(modules->data);
876       if(!strcmp(module->op, "colorout"))
877       {
878         colorout = module;
879         break;
880       }
881     }
882   }
883 
884   const dt_colorspaces_color_profile_t *p = NULL;
885 
886   if(over_type != DT_COLORSPACE_NONE)
887   {
888     // return the profile specified in export.
889     // we have that in here to get rid of the if() check in all places calling this function.
890     p = dt_colorspaces_get_profile(over_type, over_filename, DT_PROFILE_DIRECTION_OUT | DT_PROFILE_DIRECTION_DISPLAY);
891   }
892   else if(colorout && colorout->get_p)
893   {
894     // get the profile assigned from colorout
895     // FIXME: does this work when using JPEG thumbs and the image was never opened?
896     sqlite3_stmt *stmt;
897     DT_DEBUG_SQLITE3_PREPARE_V2(
898       dt_database_get(darktable.db),
899       "SELECT op_params FROM main.history WHERE imgid=?1 AND operation='colorout' ORDER BY num DESC LIMIT 1", -1,
900       &stmt, NULL);
901     DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
902     if(sqlite3_step(stmt) == SQLITE_ROW)
903     {
904       // use introspection to get the profile name from the binary params blob
905       const void *params = sqlite3_column_blob(stmt, 0);
906       dt_colorspaces_color_profile_type_t *type = colorout->get_p(params, "type");
907       char *filename = colorout->get_p(params, "filename");
908 
909       if(type && filename) p = dt_colorspaces_get_profile(*type, filename,
910                                                           DT_PROFILE_DIRECTION_OUT | DT_PROFILE_DIRECTION_DISPLAY);
911     }
912     sqlite3_finalize(stmt);
913   }
914 
915   // if all else fails -> fall back to sRGB
916   if(!p) p = dt_colorspaces_get_profile(DT_COLORSPACE_SRGB, "", DT_PROFILE_DIRECTION_OUT);
917 
918   return p;
919 }
920 
921 #if 0
922 static void dt_colorspaces_create_cmatrix(float cmatrix[4][3], float mat[3][3])
923 {
924   // sRGB D65, the linear part:
925   const float rgb_to_xyz[3][3] = { { 0.4124564, 0.3575761, 0.1804375 },
926                                    { 0.2126729, 0.7151522, 0.0721750 },
927                                    { 0.0193339, 0.1191920, 0.9503041 } };
928 
929   for(int c = 0; c < 3; c++)
930   {
931     for(int j = 0; j < 3; j++)
932     {
933       mat[c][j] = 0.0f;
934       for(int k = 0; k < 3; k++)
935       {
936         mat[c][j] += rgb_to_xyz[k][j] * cmatrix[c][k];
937       }
938     }
939   }
940 }
941 #endif
942 
dt_colorspaces_create_xyzmatrix_profile(float mat[3][3])943 static cmsHPROFILE dt_colorspaces_create_xyzmatrix_profile(float mat[3][3])
944 {
945   // mat: cam -> xyz
946   float x[3], y[3];
947   for(int k = 0; k < 3; k++)
948   {
949     const float norm = mat[0][k] + mat[1][k] + mat[2][k];
950     x[k] = mat[0][k] / norm;
951     y[k] = mat[1][k] / norm;
952   }
953   cmsCIExyYTRIPLE CameraPrimaries = { { x[0], y[0], 1.0 }, { x[1], y[1], 1.0 }, { x[2], y[2], 1.0 } };
954   cmsHPROFILE profile;
955 
956   cmsCIExyY D65;
957   cmsXYZ2xyY(&D65, &d65);
958 
959   cmsToneCurve *Gamma[3];
960   Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
961   profile = cmsCreateRGBProfile(&D65, &CameraPrimaries, Gamma);
962   cmsFreeToneCurve(Gamma[0]);
963   if(profile == NULL) return NULL;
964 
965   cmsSetProfileVersion(profile, 2.1);
966   cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
967   cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
968   cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
969   cmsMLUsetASCII(mlu1, "en", "US", "color matrix built-in");
970   cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
971   cmsMLUsetASCII(mlu2, "en", "US", "color matrix built-in");
972   cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu0);
973   cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu1);
974   // this will only be displayed when the embedded profile is read by for example GIMP
975   cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
976   cmsMLUfree(mlu0);
977   cmsMLUfree(mlu1);
978   cmsMLUfree(mlu2);
979 
980   return profile;
981 }
982 
dt_colorspaces_create_xyzimatrix_profile(float mat[3][3])983 cmsHPROFILE dt_colorspaces_create_xyzimatrix_profile(float mat[3][3])
984 {
985   // mat: xyz -> cam
986   float imat[3][3];
987   mat3inv((float *)imat, (float *)mat);
988   return dt_colorspaces_create_xyzmatrix_profile(imat);
989 }
990 
_ensure_rgb_profile(cmsHPROFILE profile)991 static cmsHPROFILE _ensure_rgb_profile(cmsHPROFILE profile)
992 {
993   if(profile && cmsGetColorSpace(profile) == cmsSigGrayData)
994   {
995     cmsToneCurve *trc = cmsReadTag(profile, cmsSigGrayTRCTag);
996     cmsCIEXYZ *wtpt = cmsReadTag(profile, cmsSigMediaWhitePointTag);
997     cmsCIEXYZ *bkpt = cmsReadTag(profile, cmsSigMediaBlackPointTag);
998     cmsCIEXYZ *chad = cmsReadTag(profile, cmsSigChromaticAdaptationTag);
999 
1000     cmsMLU *cprt = cmsReadTag(profile, cmsSigCopyrightTag);
1001     cmsMLU *desc = cmsReadTag(profile, cmsSigProfileDescriptionTag);
1002     cmsMLU *dmnd = cmsReadTag(profile, cmsSigDeviceMfgDescTag);
1003     cmsMLU *dmdd = cmsReadTag(profile, cmsSigDeviceModelDescTag);
1004 
1005     cmsHPROFILE rgb_profile = cmsCreateProfilePlaceholder(0);
1006 
1007     cmsSetDeviceClass(rgb_profile, cmsSigDisplayClass);
1008     cmsSetColorSpace(rgb_profile, cmsSigRgbData);
1009     cmsSetPCS(rgb_profile, cmsSigXYZData);
1010 
1011     cmsWriteTag(rgb_profile, cmsSigCopyrightTag, cprt);
1012     cmsWriteTag(rgb_profile, cmsSigProfileDescriptionTag, desc);
1013     cmsWriteTag(rgb_profile, cmsSigDeviceMfgDescTag, dmnd);
1014     cmsWriteTag(rgb_profile, cmsSigDeviceModelDescTag, dmdd);
1015 
1016     cmsWriteTag(rgb_profile, cmsSigMediaBlackPointTag, bkpt);
1017     cmsWriteTag(rgb_profile, cmsSigMediaWhitePointTag, wtpt);
1018     cmsWriteTag(rgb_profile, cmsSigChromaticAdaptationTag, chad);
1019     cmsSetColorSpace(rgb_profile, cmsSigRgbData);
1020     cmsSetPCS(rgb_profile, cmsSigXYZData);
1021 
1022     // TODO: we still use prequantized primaries here, we will probably want to rework this
1023     // part to create a profile using cmsCreateRGBProfile() as done in _create_lcms_profile().
1024     cmsWriteTag(rgb_profile, cmsSigRedColorantTag, (void *)&Rec709_Primaries_Prequantized.Red);
1025     cmsWriteTag(rgb_profile, cmsSigGreenColorantTag, (void *)&Rec709_Primaries_Prequantized.Green);
1026     cmsWriteTag(rgb_profile, cmsSigBlueColorantTag, (void *)&Rec709_Primaries_Prequantized.Blue);
1027 
1028     cmsWriteTag(rgb_profile, cmsSigRedTRCTag, (void *)trc);
1029     cmsLinkTag(rgb_profile, cmsSigGreenTRCTag, cmsSigRedTRCTag);
1030     cmsLinkTag(rgb_profile, cmsSigBlueTRCTag, cmsSigRedTRCTag);
1031 
1032     cmsCloseProfile(profile);
1033     profile = rgb_profile;
1034   }
1035 
1036   return profile;
1037 }
1038 
dt_colorspaces_get_rgb_profile_from_mem(uint8_t * data,uint32_t size)1039 cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
1040 {
1041   cmsHPROFILE profile = _ensure_rgb_profile(cmsOpenProfileFromMem(data, size));
1042 
1043   return profile;
1044 }
1045 
dt_colorspaces_cleanup_profile(cmsHPROFILE p)1046 void dt_colorspaces_cleanup_profile(cmsHPROFILE p)
1047 {
1048   if(!p) return;
1049   cmsCloseProfile(p);
1050 }
1051 
dt_colorspaces_get_profile_name(cmsHPROFILE p,const char * language,const char * country,char * name,size_t len)1052 void dt_colorspaces_get_profile_name(cmsHPROFILE p, const char *language, const char *country, char *name,
1053                                      size_t len)
1054 {
1055   cmsUInt32Number size;
1056   gchar *buf = NULL;
1057   wchar_t *wbuf = NULL;
1058   gchar *utf8 = NULL;
1059 
1060   size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, NULL, 0);
1061   if(size == 0) goto error;
1062 
1063   buf = (char *)calloc(size + 1, sizeof(char));
1064   size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, buf, size);
1065   if(size == 0) goto error;
1066 
1067   // most unix like systems should work with this, but at least Windows doesn't
1068   if(sizeof(wchar_t) != 4 || g_utf8_validate(buf, -1, NULL))
1069     g_strlcpy(name, buf, len); // better a little weird than totally borked
1070   else
1071   {
1072     wbuf = (wchar_t *)calloc(size + 1, sizeof(wchar_t));
1073     size = cmsGetProfileInfo(p, cmsInfoDescription, language, country, wbuf, sizeof(wchar_t) * size);
1074     if(size == 0) goto error;
1075     utf8 = g_ucs4_to_utf8((gunichar *)wbuf, -1, NULL, NULL, NULL);
1076     if(!utf8) goto error;
1077     g_strlcpy(name, utf8, len);
1078   }
1079 
1080   free(buf);
1081   free(wbuf);
1082   g_free(utf8);
1083   return;
1084 
1085 error:
1086   if(buf)
1087     g_strlcpy(name, buf, len); // better a little weird than totally borked
1088   else
1089     *name = '\0'; // nothing to do here
1090   free(buf);
1091   free(wbuf);
1092   g_free(utf8);
1093 }
1094 
rgb2hsl(const float rgb[3],float * h,float * s,float * l)1095 void rgb2hsl(const float rgb[3], float *h, float *s, float *l)
1096 {
1097   const float r = rgb[0], g = rgb[1], b = rgb[2];
1098   const float pmax = fmaxf(r, fmax(g, b));
1099   const float pmin = fminf(r, fmin(g, b));
1100   const float delta = (pmax - pmin);
1101 
1102   float hv = 0, sv = 0, lv = (pmin + pmax) / 2.0;
1103 
1104   if(delta != 0.0f)
1105   {
1106     sv = lv < 0.5 ? delta / fmaxf(pmax + pmin, 1.52587890625e-05f)
1107                   : delta / fmaxf(2.0 - pmax - pmin, 1.52587890625e-05f);
1108 
1109     if(pmax == r)
1110       hv = (g - b) / delta;
1111     else if(pmax == g)
1112       hv = 2.0 + (b - r) / delta;
1113     else if(pmax == b)
1114       hv = 4.0 + (r - g) / delta;
1115     hv /= 6.0;
1116     if(hv < 0.0)
1117       hv += 1.0;
1118     else if(hv > 1.0)
1119       hv -= 1.0;
1120   }
1121   *h = hv;
1122   *s = sv;
1123   *l = lv;
1124 }
1125 
1126 // for efficiency, 'hue' must be pre-scaled to be in 0..6
hue2rgb(float m1,float m2,float hue)1127 static inline float hue2rgb(float m1, float m2, float hue)
1128 {
1129   // compute the value for one of the RGB channels from the hue angle.
1130   // If 1 <= angle < 3, return m2; if 4 <= angle <= 6, return m1; otherwise, linearly interpolate between m1 and m2.
1131   if(hue < 1.0f)
1132     return (m1 + (m2 - m1) * hue);
1133   else if(hue < 3.0f)
1134     return m2;
1135   else
1136     return hue < 4.0f ? (m1 + (m2 - m1) * (4.0f - hue)) : m1;
1137 }
1138 
hsl2rgb(float rgb[3],float h,float s,float l)1139 void hsl2rgb(float rgb[3], float h, float s, float l)
1140 {
1141   float m1, m2;
1142   if(s == 0)
1143   {
1144     rgb[0] = rgb[1] = rgb[2] = l;
1145     return;
1146   }
1147   m2 = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
1148   m1 = (2.0 * l - m2);
1149   h *= 6.0f;  // pre-scale hue angle
1150   rgb[0] = hue2rgb(m1, m2, h < 4.0f ? h + 2.0f : h - 4.0f);
1151   rgb[1] = hue2rgb(m1, m2, h);
1152   rgb[2] = hue2rgb(m1, m2, h > 2.0f ? h - 2.0f : h + 4.0f);
1153 }
1154 
_create_profile(dt_colorspaces_color_profile_type_t type,cmsHPROFILE profile,const char * name,int in_pos,int out_pos,int display_pos,int category_pos,int work_pos,int display2_pos)1155 static dt_colorspaces_color_profile_t *_create_profile(dt_colorspaces_color_profile_type_t type,
1156                                                        cmsHPROFILE profile, const char *name, int in_pos,
1157                                                        int out_pos, int display_pos, int category_pos,
1158                                                        int work_pos, int display2_pos)
1159 {
1160   dt_colorspaces_color_profile_t *prof;
1161   prof = (dt_colorspaces_color_profile_t *)calloc(1, sizeof(dt_colorspaces_color_profile_t));
1162   prof->type = type;
1163   g_strlcpy(prof->name, name, sizeof(prof->name));
1164   prof->profile = profile;
1165   prof->in_pos = in_pos;
1166   prof->out_pos = out_pos;
1167   prof->display_pos = display_pos;
1168   prof->category_pos = category_pos;
1169   prof->work_pos = work_pos;
1170   prof->display2_pos = display2_pos;
1171   return prof;
1172 }
1173 
1174 // this function is basically thread safe, at least when not called on the global darktable.color_profiles
_update_display_transforms(dt_colorspaces_t * self)1175 static void _update_display_transforms(dt_colorspaces_t *self)
1176 {
1177   if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
1178   self->transform_srgb_to_display = NULL;
1179 
1180   if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
1181   self->transform_adobe_rgb_to_display = NULL;
1182 
1183   const dt_colorspaces_color_profile_t *display_dt_profile = _get_profile(self, self->display_type,
1184                                                                           self->display_filename,
1185                                                                           DT_PROFILE_DIRECTION_DISPLAY);
1186   if(!display_dt_profile) return;
1187   cmsHPROFILE display_profile = display_dt_profile->profile;
1188   if(!display_profile) return;
1189 
1190   self->transform_srgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_SRGB, "",
1191                                                                     DT_PROFILE_DIRECTION_DISPLAY)->profile,
1192                                                        TYPE_RGBA_8,
1193                                                        display_profile,
1194                                                        TYPE_BGRA_8,
1195                                                        self->display_intent,
1196                                                        0);
1197 
1198   self->transform_adobe_rgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_ADOBERGB, "",
1199                                                                          DT_PROFILE_DIRECTION_DISPLAY)->profile,
1200                                                             TYPE_RGBA_8,
1201                                                             display_profile,
1202                                                             TYPE_BGRA_8,
1203                                                             self->display_intent,
1204                                                             0);
1205 }
1206 
_update_display2_transforms(dt_colorspaces_t * self)1207 static void _update_display2_transforms(dt_colorspaces_t *self)
1208 {
1209   if(self->transform_srgb_to_display2) cmsDeleteTransform(self->transform_srgb_to_display2);
1210   self->transform_srgb_to_display2 = NULL;
1211 
1212   if(self->transform_adobe_rgb_to_display2) cmsDeleteTransform(self->transform_adobe_rgb_to_display2);
1213   self->transform_adobe_rgb_to_display2 = NULL;
1214 
1215   const dt_colorspaces_color_profile_t *display2_dt_profile
1216       = _get_profile(self, self->display2_type, self->display2_filename, DT_PROFILE_DIRECTION_DISPLAY2);
1217   if(!display2_dt_profile) return;
1218   cmsHPROFILE display2_profile = display2_dt_profile->profile;
1219   if(!display2_profile) return;
1220 
1221   self->transform_srgb_to_display2
1222       = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_SRGB, "", DT_PROFILE_DIRECTION_DISPLAY2)->profile,
1223                            TYPE_RGBA_8, display2_profile, TYPE_BGRA_8, self->display2_intent, 0);
1224 
1225   self->transform_adobe_rgb_to_display2
1226       = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_ADOBERGB, "", DT_PROFILE_DIRECTION_DISPLAY2)->profile,
1227                            TYPE_RGBA_8, display2_profile, TYPE_BGRA_8, self->display2_intent, 0);
1228 }
1229 
1230 // update cached transforms for color management of thumbnails
1231 // make sure that darktable.color_profiles->xprofile_lock is held when calling this!
dt_colorspaces_update_display_transforms()1232 void dt_colorspaces_update_display_transforms()
1233 {
1234   _update_display_transforms(darktable.color_profiles);
1235 }
1236 
dt_colorspaces_update_display2_transforms()1237 void dt_colorspaces_update_display2_transforms()
1238 {
1239   _update_display2_transforms(darktable.color_profiles);
1240 }
1241 
1242 // make sure that darktable.color_profiles->xprofile_lock is held when calling this!
_update_display_profile(guchar * tmp_data,gsize size,char * name,size_t name_size)1243 static void _update_display_profile(guchar *tmp_data, gsize size, char *name, size_t name_size)
1244 {
1245   g_free(darktable.color_profiles->xprofile_data);
1246   darktable.color_profiles->xprofile_data = tmp_data;
1247   darktable.color_profiles->xprofile_size = size;
1248 
1249   cmsHPROFILE profile = cmsOpenProfileFromMem(tmp_data, size);
1250   if(profile)
1251   {
1252     for(GList *iter = darktable.color_profiles->profiles; iter; iter = g_list_next(iter))
1253     {
1254       dt_colorspaces_color_profile_t *p = (dt_colorspaces_color_profile_t *)iter->data;
1255       if(p->type == DT_COLORSPACE_DISPLAY)
1256       {
1257         if(p->profile) dt_colorspaces_cleanup_profile(p->profile);
1258         p->profile = profile;
1259         if(name)
1260           dt_colorspaces_get_profile_name(profile, "en", "US", name, name_size);
1261 
1262         // update cached transforms for color management of thumbnails
1263         dt_colorspaces_update_display_transforms();
1264 
1265         break;
1266       }
1267     }
1268   }
1269 }
1270 
_update_display2_profile(guchar * tmp_data,gsize size,char * name,size_t name_size)1271 static void _update_display2_profile(guchar *tmp_data, gsize size, char *name, size_t name_size)
1272 {
1273   g_free(darktable.color_profiles->xprofile_data2);
1274   darktable.color_profiles->xprofile_data2 = tmp_data;
1275   darktable.color_profiles->xprofile_size2 = size;
1276 
1277   cmsHPROFILE profile = cmsOpenProfileFromMem(tmp_data, size);
1278   if(profile)
1279   {
1280     for(GList *iter = darktable.color_profiles->profiles; iter; iter = g_list_next(iter))
1281     {
1282       dt_colorspaces_color_profile_t *p = (dt_colorspaces_color_profile_t *)iter->data;
1283       if(p->type == DT_COLORSPACE_DISPLAY2)
1284       {
1285         if(p->profile) dt_colorspaces_cleanup_profile(p->profile);
1286         p->profile = profile;
1287         if(name) dt_colorspaces_get_profile_name(profile, "en", "US", name, name_size);
1288 
1289         // update cached transforms for color management of thumbnails
1290         dt_colorspaces_update_display2_transforms();
1291 
1292         break;
1293       }
1294     }
1295   }
1296 }
1297 
cms_error_handler(cmsContext ContextID,cmsUInt32Number ErrorCode,const char * text)1298 static void cms_error_handler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *text)
1299 {
1300   fprintf(stderr, "[lcms2] error %d: %s\n", ErrorCode, text);
1301 }
1302 
_sort_profiles(gconstpointer a,gconstpointer b)1303 static gint _sort_profiles(gconstpointer a, gconstpointer b)
1304 {
1305   const dt_colorspaces_color_profile_t *profile_a = (dt_colorspaces_color_profile_t *)a;
1306   const dt_colorspaces_color_profile_t *profile_b = (dt_colorspaces_color_profile_t *)b;
1307 
1308   gchar *name_a = g_utf8_casefold(profile_a->name, -1);
1309   gchar *name_b = g_utf8_casefold(profile_b->name, -1);
1310 
1311   gint result = g_strcmp0(name_a, name_b);
1312 
1313   g_free(name_a);
1314   g_free(name_b);
1315 
1316   return result;
1317 }
1318 
load_profile_from_dir(const char * subdir)1319 static GList *load_profile_from_dir(const char *subdir)
1320 {
1321   GList *temp_profiles = NULL;
1322   const gchar *d_name;
1323   char datadir[PATH_MAX] = { 0 };
1324   char confdir[PATH_MAX] = { 0 };
1325   dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1326   dt_loc_get_datadir(datadir, sizeof(datadir));
1327   char *lang = getenv("LANG");
1328   if(!lang) lang = "en_US";
1329 
1330   char *dirname = g_build_filename(confdir, "color", subdir, NULL);
1331   if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
1332   {
1333     g_free(dirname);
1334     dirname = g_build_filename(datadir, "color", subdir, NULL);
1335   }
1336   GDir *dir = g_dir_open(dirname, 0, NULL);
1337   if(dir)
1338   {
1339     while((d_name = g_dir_read_name(dir)))
1340     {
1341       char *filename = g_build_filename(dirname, d_name, NULL);
1342       const char *cc = filename + strlen(filename);
1343       for(; *cc != '.' && cc > filename; cc--)
1344         ;
1345       if(!g_ascii_strcasecmp(cc, ".icc") || !g_ascii_strcasecmp(cc, ".icm"))
1346       {
1347         size_t end;
1348         char *icc_content = dt_read_file(filename, &end);
1349         if(!icc_content) goto icc_loading_done;
1350 
1351         // TODO: add support for grayscale profiles, then remove _ensure_rgb_profile() from here
1352         cmsHPROFILE tmpprof = _ensure_rgb_profile(cmsOpenProfileFromMem(icc_content, sizeof(char) * end));
1353         if(tmpprof)
1354         {
1355           dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)calloc(1, sizeof(dt_colorspaces_color_profile_t));
1356           dt_colorspaces_get_profile_name(tmpprof, lang, lang + 3, prof->name, sizeof(prof->name));
1357 
1358           g_strlcpy(prof->filename, filename, sizeof(prof->filename));
1359           prof->type = DT_COLORSPACE_FILE;
1360           prof->profile = tmpprof;
1361           // these will be set after sorting!
1362           prof->in_pos = -1;
1363           prof->out_pos = -1;
1364           prof->display_pos = -1;
1365           prof->display2_pos = -1;
1366           prof->category_pos = -1;
1367           prof->work_pos = -1;
1368           temp_profiles = g_list_prepend(temp_profiles, prof);
1369         }
1370 
1371 icc_loading_done:
1372         if(icc_content) free(icc_content);
1373       }
1374       g_free(filename);
1375     }
1376     g_dir_close(dir);
1377     temp_profiles = g_list_sort(temp_profiles, _sort_profiles);
1378   }
1379   g_free(dirname);
1380   return temp_profiles;
1381 }
1382 
dt_colorspaces_init()1383 dt_colorspaces_t *dt_colorspaces_init()
1384 {
1385   cmsSetLogErrorHandler(cms_error_handler);
1386 
1387   dt_colorspaces_t *res = (dt_colorspaces_t *)calloc(1, sizeof(dt_colorspaces_t));
1388 
1389   _compute_prequantized_primaries(&D65xyY, &Rec709_Primaries, &Rec709_Primaries_Prequantized);
1390 
1391   pthread_rwlock_init(&res->xprofile_lock, NULL);
1392 
1393   int in_pos = -1,
1394       out_pos = -1,
1395       display_pos = -1,
1396       display2_pos = -1,
1397       category_pos = -1,
1398       work_pos = -1;
1399 
1400   // init the category profile with NULL profile, the actual profile must be retrieved dynamically by the caller
1401   res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_WORK, NULL, _("work profile"), -1, -1,
1402                                                                -1, ++category_pos, -1, -1));
1403 
1404   res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_EXPORT, NULL, _("export profile"), -1,
1405                                                                -1, -1, ++category_pos, -1, -1));
1406 
1407   res->profiles
1408       = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_SOFTPROOF, NULL, _("softproof profile"), -1, -1,
1409                                                      -1, ++category_pos, -1, -1));
1410 
1411   // init the display profile with srgb so some stupid code that runs before the real profile could be fetched has something to work with
1412   res->profiles = g_list_append(
1413       res->profiles, _create_profile(DT_COLORSPACE_DISPLAY, dt_colorspaces_create_srgb_profile(),
1414                                      _("system display profile"), -1, -1, ++display_pos, ++category_pos, -1, -1));
1415   res->profiles = g_list_append(
1416       res->profiles, _create_profile(DT_COLORSPACE_DISPLAY2, dt_colorspaces_create_srgb_profile(),
1417                                      _("system display profile (second window)"), -1, -1, -1, ++category_pos, -1, ++display2_pos));
1418   // we want a v4 with parametric curve for input and a v2 with point trc for output
1419   // see http://ninedegreesbelow.com/photography/lcms-make-icc-profiles.html#profile-variants-and-versions
1420   // TODO: what about display?
1421   res->profiles
1422       = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_SRGB, dt_colorspaces_create_srgb_profile_v4(),
1423                                                      _("sRGB (e.g. JPG)"), ++in_pos, -1, -1, -1, -1, -1));
1424 
1425   res->profiles
1426       = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_SRGB, dt_colorspaces_create_srgb_profile(),
1427                                                      _("sRGB (web-safe)"), -1, ++out_pos, ++display_pos,
1428                                                      ++category_pos, ++work_pos, ++display2_pos));
1429 
1430   res->profiles = g_list_append(res->profiles,
1431                                 _create_profile(DT_COLORSPACE_ADOBERGB, dt_colorspaces_create_adobergb_profile(),
1432                                                 _("Adobe RGB (compatible)"), ++in_pos, ++out_pos, ++display_pos,
1433                                                 ++category_pos, ++work_pos, ++display2_pos));
1434 
1435   res->profiles = g_list_append(
1436       res->profiles, _create_profile(DT_COLORSPACE_LIN_REC709, dt_colorspaces_create_linear_rec709_rgb_profile(),
1437                                      _("linear Rec709 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1438                                      ++work_pos, ++display2_pos));
1439 
1440   res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_REC709, dt_colorspaces_create_gamma_rec709_rgb_profile(),
1441                                      _("Rec709 RGB"), ++in_pos, ++out_pos, -1, -1,
1442                                      ++work_pos, -1));
1443 
1444   res->profiles = g_list_append(
1445       res->profiles, _create_profile(DT_COLORSPACE_LIN_REC2020, dt_colorspaces_create_linear_rec2020_rgb_profile(),
1446                                      _("linear Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1447                                      ++work_pos, ++display2_pos));
1448 
1449   res->profiles = g_list_append(
1450       res->profiles, _create_profile(DT_COLORSPACE_PQ_REC2020, dt_colorspaces_create_pq_rec2020_rgb_profile(),
1451                                      _("PQ Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1452                                      ++work_pos, ++display2_pos));
1453 
1454   res->profiles = g_list_append(
1455       res->profiles, _create_profile(DT_COLORSPACE_HLG_REC2020, dt_colorspaces_create_hlg_rec2020_rgb_profile(),
1456                                      _("HLG Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1457                                      ++work_pos, ++display2_pos));
1458 
1459   res->profiles = g_list_append(
1460       res->profiles, _create_profile(DT_COLORSPACE_PQ_P3, dt_colorspaces_create_pq_p3_rgb_profile(),
1461                                      _("PQ P3 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1462                                      ++work_pos, ++display2_pos));
1463 
1464   res->profiles = g_list_append(
1465       res->profiles, _create_profile(DT_COLORSPACE_HLG_P3, dt_colorspaces_create_hlg_p3_rgb_profile(),
1466                                      _("HLG P3 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1467                                      ++work_pos, ++display2_pos));
1468 
1469   res->profiles = g_list_append(
1470      res->profiles, _create_profile(DT_COLORSPACE_PROPHOTO_RGB, dt_colorspaces_create_linear_prophoto_rgb_profile(),
1471                                     _("linear ProPhoto RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1472                                     ++work_pos, ++display2_pos));
1473 
1474   res->profiles = g_list_append(
1475       res->profiles,
1476       _create_profile(DT_COLORSPACE_XYZ, dt_colorspaces_create_xyz_profile(), _("linear XYZ"), ++in_pos,
1477                       dt_conf_get_bool("allow_lab_output") ? ++out_pos : -1, -1, -1, -1, -1));
1478 
1479   res->profiles = g_list_append(
1480       res->profiles, _create_profile(DT_COLORSPACE_LAB, dt_colorspaces_create_lab_profile(), _("Lab"), ++in_pos,
1481                                      dt_conf_get_bool("allow_lab_output") ? ++out_pos : -1, -1, -1, -1, -1));
1482 
1483   res->profiles = g_list_append(
1484       res->profiles, _create_profile(DT_COLORSPACE_INFRARED, dt_colorspaces_create_linear_infrared_profile(),
1485                                      _("linear infrared BGR"), ++in_pos, -1, -1, -1, -1, -1));
1486 
1487   res->profiles
1488       = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_BRG, dt_colorspaces_create_brg_profile(),
1489                                                      _("BRG (for testing)"), ++in_pos, ++out_pos, ++display_pos,
1490                                                      -1, -1, ++display2_pos));
1491 
1492   // init display profile and softproof/gama checking from conf
1493   res->display_type = dt_conf_get_int("ui_last/color/display_type");
1494   res->display2_type = dt_conf_get_int("ui_last/color/display2_type");
1495   res->softproof_type = dt_conf_get_int("ui_last/color/softproof_type");
1496   res->histogram_type = dt_conf_get_int("ui_last/color/histogram_type");
1497   char *tmp = dt_conf_get_string("ui_last/color/display_filename");
1498   g_strlcpy(res->display_filename, tmp, sizeof(res->display_filename));
1499   g_free(tmp);
1500   tmp = dt_conf_get_string("ui_last/color/display2_filename");
1501   g_strlcpy(res->display2_filename, tmp, sizeof(res->display2_filename));
1502   g_free(tmp);
1503   tmp = dt_conf_get_string("ui_last/color/softproof_filename");
1504   g_strlcpy(res->softproof_filename, tmp, sizeof(res->softproof_filename));
1505   g_free(tmp);
1506   tmp = dt_conf_get_string("ui_last/color/histogram_filename");
1507   g_strlcpy(res->histogram_filename, tmp, sizeof(res->histogram_filename));
1508   g_free(tmp);
1509   res->display_intent = dt_conf_get_int("ui_last/color/display_intent");
1510   res->display2_intent = dt_conf_get_int("ui_last/color/display2_intent");
1511   res->softproof_intent = dt_conf_get_int("ui_last/color/softproof_intent");
1512   res->mode = dt_conf_get_int("ui_last/color/mode");
1513 
1514   // sanity checks to ensure the profile filenames are present
1515 
1516   if((unsigned int)res->display_type >= DT_COLORSPACE_LAST
1517      || (res->display_type == DT_COLORSPACE_FILE
1518          && (!res->display_filename[0] || !g_file_test(res->display_filename, G_FILE_TEST_IS_REGULAR))))
1519     res->display_type = DT_COLORSPACE_DISPLAY;
1520 
1521   if((unsigned int)res->display2_type >= DT_COLORSPACE_LAST
1522      || (res->display2_type == DT_COLORSPACE_FILE
1523          && (!res->display2_filename[0] || !g_file_test(res->display2_filename, G_FILE_TEST_IS_REGULAR))))
1524     res->display2_type = DT_COLORSPACE_DISPLAY2;
1525 
1526   if((unsigned int)res->softproof_type >= DT_COLORSPACE_LAST
1527      || (res->softproof_type == DT_COLORSPACE_FILE
1528          && (!res->softproof_filename[0] || !g_file_test(res->softproof_filename, G_FILE_TEST_IS_REGULAR))))
1529     res->softproof_type = DT_COLORSPACE_SRGB;
1530 
1531   if((unsigned int)res->histogram_type >= DT_COLORSPACE_LAST
1532      || (res->histogram_type == DT_COLORSPACE_FILE
1533          && (!res->histogram_filename[0] || !g_file_test(res->histogram_filename, G_FILE_TEST_IS_REGULAR))))
1534     res->histogram_type = DT_COLORSPACE_SRGB;
1535 
1536   // temporary list of profiles to be added, we keep this separate to be able to sort it before adding
1537   GList *temp_profiles;
1538 
1539   // read {userconfig,datadir}/color/in/*.icc, in this order.
1540   temp_profiles = load_profile_from_dir("in");
1541   for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1542   {
1543     dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)iter->data;
1544     prof->in_pos = ++in_pos;
1545   }
1546   res->profiles = g_list_concat(res->profiles, temp_profiles);
1547 
1548   // read {conf,data}dir/color/out/*.icc
1549   temp_profiles = load_profile_from_dir("out");
1550   for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1551   {
1552     dt_colorspaces_color_profile_t *prof = (dt_colorspaces_color_profile_t *)iter->data;
1553     // FIXME: do want to filter out non-RGB profiles for cases besides histogram profile? colorin is OK with RGB or XYZ, print is OK with anything which LCMS likes, otherwise things are more choosey
1554     const cmsColorSpaceSignature color_space = cmsGetColorSpace(prof->profile);
1555     // The histogram profile is used for histogram, clipping indicators and the global color picker.
1556     // Some of these also assume a matrix profile. LUT profiles don't make much sense in these applications
1557     // so filter out any profile that doesn't implement the relative colorimetric intent as a matrix (+ TRC).
1558     // For discussion, see e.g.
1559     // https://github.com/darktable-org/darktable/issues/7660#issuecomment-760143437
1560     // For the working profile we also require a matrix profile.
1561     const gboolean is_valid_matrix_profile
1562         = dt_colorspaces_get_matrix_from_output_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0
1563           && dt_colorspaces_get_matrix_from_input_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0;
1564     prof->out_pos = ++out_pos;
1565     prof->display_pos = ++display_pos;
1566     prof->display2_pos = ++display2_pos;
1567     if(is_valid_matrix_profile)
1568     {
1569       prof->category_pos = ++category_pos;
1570       prof->work_pos = ++work_pos;
1571     }
1572     else
1573     {
1574       dt_print(DT_DEBUG_DEV,
1575                "output profile `%s' color space `%c%c%c%c' not supported for work or histogram profile\n",
1576                prof->name, (char)(color_space >> 24), (char)(color_space >> 16), (char)(color_space >> 8),
1577                (char)(color_space));
1578 
1579       if(res->histogram_type == prof->type
1580          && (prof->type != DT_COLORSPACE_FILE
1581              || dt_colorspaces_is_profile_equal(prof->filename, res->histogram_filename)))
1582       {
1583         // bad histogram profile selected, we must reset it to sRGB
1584         const char *name = dt_colorspaces_get_name(prof->type, prof->filename);
1585         dt_control_log(_("profile `%s' not usable as histogram profile. it has been replaced by sRGB!"), name);
1586         fprintf(stderr,
1587                 "[colorspaces] profile `%s' not usable as histogram profile. it has been replaced by sRGB!\n",
1588                 name);
1589         res->histogram_type = DT_COLORSPACE_SRGB;
1590         res->histogram_filename[0] = '\0';
1591       }
1592     }
1593   }
1594   res->profiles = g_list_concat(res->profiles, temp_profiles);
1595 
1596 
1597   if((unsigned int)res->mode > DT_PROFILE_GAMUTCHECK) res->mode = DT_PROFILE_NORMAL;
1598 
1599   _update_display_transforms(res);
1600   _update_display2_transforms(res);
1601 
1602   return res;
1603 }
1604 
dt_colorspaces_cleanup(dt_colorspaces_t * self)1605 void dt_colorspaces_cleanup(dt_colorspaces_t *self)
1606 {
1607   // remember display profile and softproof/gama checking from conf
1608   dt_conf_set_int("ui_last/color/display_type", self->display_type);
1609   dt_conf_set_int("ui_last/color/display2_type", self->display2_type);
1610   dt_conf_set_int("ui_last/color/softproof_type", self->softproof_type);
1611   dt_conf_set_int("ui_last/color/histogram_type", self->histogram_type);
1612   dt_conf_set_string("ui_last/color/display_filename", self->display_filename);
1613   dt_conf_set_string("ui_last/color/display2_filename", self->display2_filename);
1614   dt_conf_set_string("ui_last/color/softproof_filename", self->softproof_filename);
1615   dt_conf_set_string("ui_last/color/histogram_filename", self->histogram_filename);
1616   dt_conf_set_int("ui_last/color/display_intent", self->display_intent);
1617   dt_conf_set_int("ui_last/color/display2_intent", self->display2_intent);
1618   dt_conf_set_int("ui_last/color/softproof_intent", self->softproof_intent);
1619   dt_conf_set_int("ui_last/color/mode", self->mode);
1620 
1621   if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
1622   self->transform_srgb_to_display = NULL;
1623 
1624   if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
1625   self->transform_adobe_rgb_to_display = NULL;
1626 
1627   if(self->transform_srgb_to_display2) cmsDeleteTransform(self->transform_srgb_to_display2);
1628   self->transform_srgb_to_display2 = NULL;
1629 
1630   if(self->transform_adobe_rgb_to_display2) cmsDeleteTransform(self->transform_adobe_rgb_to_display2);
1631   self->transform_adobe_rgb_to_display2 = NULL;
1632 
1633   for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
1634   {
1635     dt_colorspaces_color_profile_t *p = (dt_colorspaces_color_profile_t *)iter->data;
1636     dt_colorspaces_cleanup_profile(p->profile);
1637   }
1638   g_list_free_full(self->profiles, free);
1639 
1640   pthread_rwlock_destroy(&self->xprofile_lock);
1641   g_free(self->colord_profile_file);
1642   g_free(self->xprofile_data);
1643 
1644   g_free(self->colord_profile_file2);
1645   g_free(self->xprofile_data2);
1646 
1647   free(self);
1648 }
1649 
dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type,const char * filename)1650 const char *dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type,
1651                                     const char *filename)
1652 {
1653   switch (type)
1654   {
1655      case DT_COLORSPACE_NONE:
1656        return NULL;
1657      case DT_COLORSPACE_FILE:
1658        return filename;
1659      case DT_COLORSPACE_SRGB:
1660        return _("sRGB");
1661      case DT_COLORSPACE_ADOBERGB:
1662        return _("Adobe RGB (compatible)");
1663      case DT_COLORSPACE_LIN_REC709:
1664        return _("linear Rec709 RGB");
1665      case DT_COLORSPACE_LIN_REC2020:
1666        return _("linear Rec2020 RGB");
1667      case DT_COLORSPACE_XYZ:
1668        return _("linear XYZ");
1669      case DT_COLORSPACE_LAB:
1670        return _("Lab");
1671      case DT_COLORSPACE_INFRARED:
1672        return _("linear infrared BGR");
1673      case DT_COLORSPACE_DISPLAY:
1674        return _("system display profile");
1675      case DT_COLORSPACE_EMBEDDED_ICC:
1676        return _("embedded ICC profile");
1677      case DT_COLORSPACE_EMBEDDED_MATRIX:
1678        return _("embedded matrix");
1679      case DT_COLORSPACE_STANDARD_MATRIX:
1680        return _("standard color matrix");
1681      case DT_COLORSPACE_ENHANCED_MATRIX:
1682        return _("enhanced color matrix");
1683      case DT_COLORSPACE_VENDOR_MATRIX:
1684        return _("vendor color matrix");
1685      case DT_COLORSPACE_ALTERNATE_MATRIX:
1686        return _("alternate color matrix");
1687      case DT_COLORSPACE_BRG:
1688        return _("BRG (experimental)");
1689      case DT_COLORSPACE_EXPORT:
1690        return _("export profile");
1691      case DT_COLORSPACE_SOFTPROOF:
1692        return _("softproof profile");
1693      case DT_COLORSPACE_WORK:
1694        return _("work profile");
1695      case DT_COLORSPACE_DISPLAY2:
1696        return _("system display profile (second window)");
1697      case DT_COLORSPACE_REC709:
1698        return _("Rec709 RGB");
1699      case DT_COLORSPACE_PROPHOTO_RGB:
1700        return _("linear ProPhoto RGB");
1701      case DT_COLORSPACE_PQ_REC2020:
1702        return _("PQ Rec2020");
1703      case DT_COLORSPACE_HLG_REC2020:
1704        return _("HLG Rec2020");
1705      case DT_COLORSPACE_PQ_P3:
1706        return _("PQ P3");
1707      case DT_COLORSPACE_HLG_P3:
1708        return _("HLG P3");
1709      case DT_COLORSPACE_LAST:
1710        break;
1711   }
1712 
1713   return NULL;
1714 }
1715 
1716 #ifdef USE_COLORDGTK
dt_colorspaces_get_display_profile_colord_callback(GObject * source,GAsyncResult * res,gpointer user_data)1717 static void dt_colorspaces_get_display_profile_colord_callback(GObject *source, GAsyncResult *res, gpointer user_data)
1718 {
1719   const dt_colorspaces_color_profile_type_t profile_type
1720       = (dt_colorspaces_color_profile_type_t)GPOINTER_TO_INT(user_data);
1721 
1722   pthread_rwlock_wrlock(&darktable.color_profiles->xprofile_lock);
1723 
1724   int profile_changed = 0;
1725   CdWindow *window = CD_WINDOW(source);
1726   GError *error = NULL;
1727   CdProfile *profile = cd_window_get_profile_finish(window, res, &error);
1728   if(error == NULL && profile != NULL)
1729   {
1730     const gchar *filename = cd_profile_get_filename(profile);
1731     if(filename)
1732     {
1733       if((profile_type == DT_COLORSPACE_DISPLAY2
1734           && g_strcmp0(filename, darktable.color_profiles->colord_profile_file2))
1735          || (profile_type != DT_COLORSPACE_DISPLAY2
1736              && g_strcmp0(filename, darktable.color_profiles->colord_profile_file)))
1737       {
1738         /* the profile has changed (either because the user changed the colord settings or because we are on a
1739          * different screen now) */
1740         // update darktable.color_profiles->colord_profile_file
1741         if(profile_type == DT_COLORSPACE_DISPLAY2)
1742         {
1743           g_free(darktable.color_profiles->colord_profile_file2);
1744           darktable.color_profiles->colord_profile_file2 = g_strdup(filename);
1745         }
1746         else
1747         {
1748           g_free(darktable.color_profiles->colord_profile_file);
1749           darktable.color_profiles->colord_profile_file = g_strdup(filename);
1750         }
1751         // read the file
1752         guchar *tmp_data = NULL;
1753         gsize size;
1754         g_file_get_contents(filename, (gchar **)&tmp_data, &size, NULL);
1755         if(profile_type == DT_COLORSPACE_DISPLAY2)
1756         {
1757           profile_changed = size > 0 && (darktable.color_profiles->xprofile_size2 != size
1758                                          || memcmp(darktable.color_profiles->xprofile_data2, tmp_data, size) != 0);
1759         }
1760         else
1761         {
1762           profile_changed = size > 0 && (darktable.color_profiles->xprofile_size != size
1763                                          || memcmp(darktable.color_profiles->xprofile_data, tmp_data, size) != 0);
1764         }
1765         if(profile_changed)
1766         {
1767           if(profile_type == DT_COLORSPACE_DISPLAY2)
1768             _update_display2_profile(tmp_data, size, NULL, 0);
1769           else
1770             _update_display_profile(tmp_data, size, NULL, 0);
1771           dt_print(DT_DEBUG_CONTROL,
1772                    "[color profile] colord gave us a new screen profile: '%s' (size: %zu)\n", filename, size);
1773         }
1774         else
1775         {
1776           g_free(tmp_data);
1777         }
1778       }
1779     }
1780   }
1781   if(profile) g_object_unref(profile);
1782   g_object_unref(window);
1783 
1784   pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1785 
1786   if(profile_changed) DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_CHANGED);
1787 }
1788 #endif
1789 
1790 #if GTK_CHECK_VERSION(3, 22, 0) && defined GDK_WINDOWING_X11
_gtk_get_monitor_num(GdkMonitor * monitor)1791 static int _gtk_get_monitor_num(GdkMonitor *monitor)
1792 {
1793   GdkDisplay *display;
1794   int n_monitors, i;
1795 
1796   display = gdk_monitor_get_display(monitor);
1797   n_monitors = gdk_display_get_n_monitors(display);
1798   for(i = 0; i < n_monitors; i++)
1799   {
1800     if(gdk_display_get_monitor(display, i) == monitor) return i;
1801   }
1802 
1803   return -1;
1804 }
1805 #endif
1806 
1807 // Get the display ICC profile of the monitor associated with the widget.
1808 // For X display, uses the ICC profile specifications version 0.2 from
1809 // http://burtonini.com/blog/computers/xicc
1810 // Based on code from Gimp's modules/cdisplay_lcms.c
dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type)1811 void dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type)
1812 {
1813   if(!dt_control_running()) return;
1814   // make sure that no one gets a broken profile
1815   // FIXME: benchmark if the try is really needed when moving/resizing the window. Maybe we can just lock it
1816   // and block
1817   if(pthread_rwlock_trywrlock(&darktable.color_profiles->xprofile_lock))
1818     return; // we are already updating the profile. Or someone is reading right now. Too bad we can't
1819             // distinguish that. Whatever ...
1820 
1821   guint8 *buffer = NULL;
1822   gint buffer_size = 0;
1823   gchar *profile_source = NULL;
1824 
1825 #if defined GDK_WINDOWING_X11
1826 
1827   // we will use the xatom no matter what configured when compiled without colord
1828   gboolean use_xatom = TRUE;
1829 #if defined USE_COLORDGTK
1830   gboolean use_colord = TRUE;
1831   gchar *display_profile_source = (profile_type == DT_COLORSPACE_DISPLAY2)
1832                                       ? dt_conf_get_string("ui_last/display2_profile_source")
1833                                       : dt_conf_get_string("ui_last/display_profile_source");
1834   if(display_profile_source)
1835   {
1836     if(!strcmp(display_profile_source, "xatom"))
1837       use_colord = FALSE;
1838     else if(!strcmp(display_profile_source, "colord"))
1839       use_xatom = FALSE;
1840     g_free(display_profile_source);
1841   }
1842 #endif
1843 
1844   /* let's have a look at the xatom, just in case ... */
1845   if(use_xatom)
1846   {
1847     GtkWidget *widget = (profile_type == DT_COLORSPACE_DISPLAY2) ? darktable.develop->second_window.second_wnd
1848                                                                  : dt_ui_center(darktable.gui->ui);
1849     GdkWindow *window = gtk_widget_get_window(widget);
1850     GdkScreen *screen = gtk_widget_get_screen(widget);
1851     if(screen == NULL) screen = gdk_screen_get_default();
1852 
1853 #if GTK_CHECK_VERSION(3, 22, 0)
1854     GdkDisplay *display = gtk_widget_get_display(widget);
1855     int monitor = _gtk_get_monitor_num(gdk_display_get_monitor_at_window(display, window));
1856 #else
1857     int monitor = gdk_screen_get_monitor_at_window(screen, window);
1858 #endif
1859 
1860     char *atom_name;
1861     if(monitor > 0)
1862       atom_name = g_strdup_printf("_ICC_PROFILE_%d", monitor);
1863     else
1864       atom_name = g_strdup("_ICC_PROFILE");
1865 
1866     profile_source = g_strdup_printf("xatom %s", atom_name);
1867 
1868     GdkAtom type = GDK_NONE;
1869     gint format = 0;
1870     gdk_property_get(gdk_screen_get_root_window(screen), gdk_atom_intern(atom_name, FALSE), GDK_NONE, 0,
1871                      64 * 1024 * 1024, FALSE, &type, &format, &buffer_size, &buffer);
1872     g_free(atom_name);
1873   }
1874 
1875 #ifdef USE_COLORDGTK
1876   /* also try to get the profile from colord. this will set the value asynchronously! */
1877   if(use_colord)
1878   {
1879     CdWindow *window = cd_window_new();
1880     GtkWidget *center_widget = (profile_type == DT_COLORSPACE_DISPLAY2)
1881                                    ? darktable.develop->second_window.second_wnd
1882                                    : dt_ui_center(darktable.gui->ui);
1883     cd_window_get_profile(window, center_widget, NULL, dt_colorspaces_get_display_profile_colord_callback,
1884                           GINT_TO_POINTER(profile_type));
1885   }
1886 #endif
1887 
1888 #elif defined GDK_WINDOWING_QUARTZ
1889 #if 0
1890   GtkWidget *widget = (profile_type == DT_COLORSPACE_DISPLAY2) ? darktable.develop->second_window.second_wnd : dt_ui_center(darktable.gui->ui);
1891   GdkScreen *screen = gtk_widget_get_screen(widget);
1892   if(screen == NULL) screen = gdk_screen_get_default();
1893   int monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(widget));
1894 
1895   CGDirectDisplayID ids[monitor + 1];
1896   uint32_t total_ids;
1897   CMProfileRef prof = NULL;
1898   if(CGGetOnlineDisplayList(monitor + 1, &ids[0], &total_ids) == kCGErrorSuccess && total_ids == monitor + 1)
1899     CMGetProfileByAVID(ids[monitor], &prof);
1900   if(prof != NULL)
1901   {
1902     CFDataRef data;
1903     data = CMProfileCopyICCData(NULL, prof);
1904     CMCloseProfile(prof);
1905 
1906     UInt8 *tmp_buffer = (UInt8 *)g_malloc(CFDataGetLength(data));
1907     CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), tmp_buffer);
1908 
1909     buffer = (guint8 *)tmp_buffer;
1910     buffer_size = CFDataGetLength(data);
1911 
1912     CFRelease(data);
1913   }
1914   profile_source = g_strdup("osx color profile api");
1915 #endif
1916 #elif defined G_OS_WIN32
1917   HDC hdc = GetDC(NULL);
1918   if(hdc != NULL)
1919   {
1920     DWORD len = 0;
1921     GetICMProfile(hdc, &len, NULL);
1922     wchar_t *wpath = g_new(wchar_t, len);
1923 
1924     if(GetICMProfileW(hdc, &len, wpath))
1925     {
1926       gchar *path = g_utf16_to_utf8(wpath, -1, NULL, NULL, NULL);
1927       if(path)
1928       {
1929         gsize size;
1930         g_file_get_contents(path, (gchar **)&buffer, &size, NULL);
1931         buffer_size = size;
1932         g_free(path);
1933       }
1934     }
1935     g_free(wpath);
1936     ReleaseDC(NULL, hdc);
1937   }
1938   profile_source = g_strdup("windows color profile api");
1939 #endif
1940 
1941   int profile_changed = 0;
1942   if(profile_type == DT_COLORSPACE_DISPLAY2)
1943   {
1944     profile_changed
1945         = buffer_size > 0 && (darktable.color_profiles->xprofile_size2 != buffer_size
1946                               || memcmp(darktable.color_profiles->xprofile_data2, buffer, buffer_size) != 0);
1947   }
1948   else
1949   {
1950     profile_changed
1951         = buffer_size > 0 && (darktable.color_profiles->xprofile_size != buffer_size
1952                               || memcmp(darktable.color_profiles->xprofile_data, buffer, buffer_size) != 0);
1953   }
1954   if(profile_changed)
1955   {
1956     char name[512] = { 0 };
1957     if(profile_type == DT_COLORSPACE_DISPLAY2)
1958       _update_display2_profile(buffer, buffer_size, name, sizeof(name));
1959     else
1960       _update_display_profile(buffer, buffer_size, name, sizeof(name));
1961     dt_print(DT_DEBUG_CONTROL, "[color profile] we got a new screen profile `%s' from the %s (size: %d)\n",
1962              *name ? name : "(unknown)", profile_source, buffer_size);
1963   }
1964   else
1965   {
1966     g_free(buffer);
1967   }
1968   pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
1969   if(profile_changed) DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_CONTROL_PROFILE_CHANGED);
1970   g_free(profile_source);
1971 }
1972 
_colorspaces_is_base_name(const char * profile)1973 static gboolean _colorspaces_is_base_name(const char *profile)
1974 {
1975   const char *f = profile;
1976   while(*f != '\0')
1977   {
1978     if(*f == '/' || *f == '\\') return FALSE;
1979     f++;
1980   }
1981   return TRUE;
1982 }
1983 
_colorspaces_get_base_name(const char * profile)1984 static const char *_colorspaces_get_base_name(const char *profile)
1985 {
1986   const char* f = profile + strlen(profile);
1987   for (; f >= profile; f--)
1988   {
1989     if(*f == '/' || *f == '\\')
1990       return ++f;   // path separator found - return the filename only, without the leading separator
1991   }
1992   return f;         // no separator found - consider profile_name to be a "base" one
1993 }
1994 
dt_colorspaces_is_profile_equal(const char * fullname,const char * filename)1995 gboolean dt_colorspaces_is_profile_equal(const char *fullname, const char *filename)
1996 {
1997   // for backward compatibility we need to also ensure that we check
1998   // for basename, indeed filename parameter may be in fact just a
1999   // basename as recorded in an iop.
2000   return _colorspaces_is_base_name(filename)
2001     ? !strcmp(_colorspaces_get_base_name(fullname), filename)
2002     : !strcmp(_colorspaces_get_base_name(fullname), _colorspaces_get_base_name(filename));
2003 }
2004 
_get_profile(dt_colorspaces_t * self,dt_colorspaces_color_profile_type_t type,const char * filename,dt_colorspaces_profile_direction_t direction)2005 static const dt_colorspaces_color_profile_t *_get_profile(dt_colorspaces_t *self,
2006                                                           dt_colorspaces_color_profile_type_t type,
2007                                                           const char *filename,
2008                                                           dt_colorspaces_profile_direction_t direction)
2009 {
2010   for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
2011   {
2012     dt_colorspaces_color_profile_t *p = (dt_colorspaces_color_profile_t *)iter->data;
2013     if(((direction & DT_PROFILE_DIRECTION_IN && p->in_pos > -1)
2014         || (direction & DT_PROFILE_DIRECTION_OUT && p->out_pos > -1)
2015         || (direction & DT_PROFILE_DIRECTION_WORK && p->work_pos > -1)
2016         || (direction & DT_PROFILE_DIRECTION_DISPLAY && p->display_pos > -1)
2017         || (direction & DT_PROFILE_DIRECTION_DISPLAY2 && p->display2_pos > -1))
2018        && (p->type == type
2019            && (type != DT_COLORSPACE_FILE || dt_colorspaces_is_profile_equal(p->filename, filename))))
2020     {
2021       return p;
2022     }
2023   }
2024 
2025   return NULL;
2026 }
2027 
dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type,const char * filename,dt_colorspaces_profile_direction_t direction)2028 const dt_colorspaces_color_profile_t *dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type,
2029                                                                  const char *filename,
2030                                                                  dt_colorspaces_profile_direction_t direction)
2031 {
2032   return _get_profile(darktable.color_profiles, type, filename, direction);
2033 }
2034 
2035 // Copied from dcraw's pseudoinverse()
dt_colorspaces_pseudoinverse(double (* in)[3],double (* out)[3],int size)2036 static void dt_colorspaces_pseudoinverse(double (*in)[3], double (*out)[3], int size)
2037 {
2038   double work[3][6];
2039 
2040   for(int i = 0; i < 3; i++) {
2041     for(int j = 0; j < 6; j++)
2042       work[i][j] = j == i+3;
2043     for(int j = 0; j < 3; j++)
2044       for(int k = 0; k < size; k++)
2045         work[i][j] += in[k][i] * in[k][j];
2046   }
2047   for(int i = 0; i < 3; i++) {
2048     double num = work[i][i];
2049     for(int j = 0; j < 6; j++)
2050       work[i][j] /= num;
2051     for(int k = 0; k < 3; k++) {
2052       if(k==i) continue;
2053       num = work[k][i];
2054       for(int j = 0; j < 6; j++)
2055         work[k][j] -= work[i][j] * num;
2056     }
2057   }
2058   for(int i = 0; i < size; i++)
2059     for(int j = 0; j < 3; j++)
2060     {
2061       out[i][j] = 0.0f;
2062       for(int k = 0; k < 3; k++)
2063         out[i][j] += work[j][k+3] * in[i][k];
2064     }
2065 }
2066 
dt_colorspaces_conversion_matrices_xyz(const char * name,float in_XYZ_to_CAM[9],double XYZ_to_CAM[4][3],double CAM_to_XYZ[3][4])2067 int dt_colorspaces_conversion_matrices_xyz(const char *name, float in_XYZ_to_CAM[9], double XYZ_to_CAM[4][3], double CAM_to_XYZ[3][4])
2068 {
2069   if(!isnan(in_XYZ_to_CAM[0]))
2070   {
2071     for(int i = 0; i < 9; i++)
2072         XYZ_to_CAM[i/3][i%3] = (double) in_XYZ_to_CAM[i];
2073     for(int i = 0; i < 3; i++)
2074       XYZ_to_CAM[3][i] = 0.0f;
2075   }
2076   else
2077   {
2078     float adobe_XYZ_to_CAM[4][3];
2079     adobe_XYZ_to_CAM[0][0] = NAN;
2080 
2081     dt_dcraw_adobe_coeff(name, (float(*)[12])adobe_XYZ_to_CAM);
2082     if(isnan(adobe_XYZ_to_CAM[0][0]))
2083       return FALSE;
2084 
2085     for(int i = 0; i < 4; i++)
2086       for(int j = 0; j < 3; j++)
2087         XYZ_to_CAM[i][j] = (double) adobe_XYZ_to_CAM[i][j];
2088   }
2089 
2090   // Invert the matrix
2091   double inverse[4][3];
2092   dt_colorspaces_pseudoinverse (XYZ_to_CAM, inverse, 4);
2093   for(int i = 0; i < 3; i++)
2094     for(int j = 0; j < 4; j++)
2095       CAM_to_XYZ[i][j] = inverse[j][i];
2096 
2097   return TRUE;
2098 }
2099 
2100 // Converted from dcraw's cam_xyz_coeff()
dt_colorspaces_conversion_matrices_rgb(const char * name,double out_RGB_to_CAM[4][3],double out_CAM_to_RGB[3][4],const float * embedded_matrix,double mul[4])2101 int dt_colorspaces_conversion_matrices_rgb(const char *name,
2102                                            double out_RGB_to_CAM[4][3], double out_CAM_to_RGB[3][4],
2103                                            const float *embedded_matrix,
2104                                            double mul[4])
2105 {
2106   double RGB_to_CAM[4][3];
2107 
2108   float XYZ_to_CAM[4][3];
2109   XYZ_to_CAM[0][0] = NAN;
2110 
2111   if(embedded_matrix == NULL || isnan(embedded_matrix[0]))
2112   {
2113     dt_dcraw_adobe_coeff(name, (float(*)[12])XYZ_to_CAM);
2114   }
2115   else
2116   {
2117     // keep in sync with reload_defaults from colorin.c
2118     // embedded matrix is used with higher priority than standard one
2119     XYZ_to_CAM[0][0] = embedded_matrix[0];
2120     XYZ_to_CAM[0][1] = embedded_matrix[1];
2121     XYZ_to_CAM[0][2] = embedded_matrix[2];
2122 
2123     XYZ_to_CAM[1][0] = embedded_matrix[3];
2124     XYZ_to_CAM[1][1] = embedded_matrix[4];
2125     XYZ_to_CAM[1][2] = embedded_matrix[5];
2126 
2127     XYZ_to_CAM[2][0] = embedded_matrix[6];
2128     XYZ_to_CAM[2][1] = embedded_matrix[7];
2129     XYZ_to_CAM[2][2] = embedded_matrix[8];
2130   }
2131 
2132 
2133   if(isnan(XYZ_to_CAM[0][0]))
2134     return FALSE;
2135 
2136   const double RGB_to_XYZ[3][3] = {
2137   // sRGB D65
2138     { 0.412453, 0.357580, 0.180423 },
2139     { 0.212671, 0.715160, 0.072169 },
2140     { 0.019334, 0.119193, 0.950227 },
2141   };
2142 
2143   // Multiply RGB matrix
2144   for(int i = 0; i < 4; i++)
2145     for(int j = 0; j < 3; j++)
2146     {
2147       RGB_to_CAM[i][j] = 0.0f;
2148       for(int k = 0; k < 3; k++)
2149         RGB_to_CAM[i][j] += XYZ_to_CAM[i][k] * RGB_to_XYZ[k][j];
2150     }
2151 
2152   // Normalize cam_rgb so that cam_rgb * (1,1,1) is (1,1,1,1)
2153   for(int i = 0; i < 4; i++) {
2154     double num = 0.0f;
2155     for(int j = 0; j < 3; j++)
2156       num += RGB_to_CAM[i][j];
2157     for(int j = 0; j < 3; j++)
2158        RGB_to_CAM[i][j] /= num;
2159     if(mul) mul[i] = 1.0f / num;
2160   }
2161 
2162   if(out_RGB_to_CAM)
2163     for(int i = 0; i < 4; i++)
2164       for(int j = 0; j < 3; j++)
2165         out_RGB_to_CAM[i][j] = RGB_to_CAM[i][j];
2166 
2167   if(out_CAM_to_RGB)
2168   {
2169     // Invert the matrix
2170     double inverse[4][3];
2171     dt_colorspaces_pseudoinverse (RGB_to_CAM, inverse, 4);
2172     for(int i = 0; i < 3; i++)
2173       for(int j = 0; j < 4; j++)
2174         out_CAM_to_RGB[i][j] = inverse[j][i];
2175   }
2176 
2177   return TRUE;
2178 }
2179 
dt_colorspaces_cygm_apply_coeffs_to_rgb(float * out,const float * in,int num,double RGB_to_CAM[4][3],double CAM_to_RGB[3][4],float coeffs[4])2180 void dt_colorspaces_cygm_apply_coeffs_to_rgb(float *out, const float *in, int num, double RGB_to_CAM[4][3], double CAM_to_RGB[3][4], float coeffs[4])
2181 {
2182   // Create the CAM to RGB with applied WB matrix
2183   double CAM_to_RGB_WB[3][4];
2184   for (int a=0; a<3; a++)
2185     for (int b=0; b<4; b++)
2186       CAM_to_RGB_WB[a][b] = CAM_to_RGB[a][b] * coeffs[b];
2187 
2188   // Create the RGB->RGB+WB matrix
2189   double RGB_to_RGB_WB[3][3];
2190   for (int a=0; a<3; a++)
2191     for (int b=0; b<3; b++) {
2192       RGB_to_RGB_WB[a][b] = 0.0f;
2193       for (int c=0; c<4; c++)
2194         RGB_to_RGB_WB[a][b] += CAM_to_RGB_WB[a][c] * RGB_to_CAM[c][b];
2195     }
2196 
2197 #ifdef _OPENMP
2198 #pragma omp parallel for default(none) shared(in, out, num, RGB_to_RGB_WB) schedule(static)
2199 #endif
2200   for(int i = 0; i < num; i++)
2201   {
2202     const float *inpos = &in[i*4];
2203     float *outpos = &out[i*4];
2204     outpos[0]=outpos[1]=outpos[2] = 0.0f;
2205     for (int a=0; a<3; a++)
2206       for (int b=0; b<3; b++)
2207         outpos[a] += RGB_to_RGB_WB[a][b] * inpos[b];
2208   }
2209 }
2210 
dt_colorspaces_cygm_to_rgb(float * out,int num,double CAM_to_RGB[3][4])2211 void dt_colorspaces_cygm_to_rgb(float *out, int num, double CAM_to_RGB[3][4])
2212 {
2213 #ifdef _OPENMP
2214 #pragma omp parallel for default(none) shared(out, num, CAM_to_RGB) schedule(static)
2215 #endif
2216   for(int i = 0; i < num; i++)
2217   {
2218     float *in = &out[i*4];
2219     float o[3] = {0.0f,0.0f,0.0f};
2220     for(int c = 0; c < 3; c++)
2221       for(int k = 0; k < 4; k++)
2222         o[c] += CAM_to_RGB[c][k] * in[k];
2223     for(int c = 0; c < 3; c++)
2224       in[c] = o[c];
2225   }
2226 }
2227 
dt_colorspaces_rgb_to_cygm(float * out,int num,double RGB_to_CAM[4][3])2228 void dt_colorspaces_rgb_to_cygm(float *out, int num, double RGB_to_CAM[4][3])
2229 {
2230 #ifdef _OPENMP
2231 #pragma omp parallel for default(none) shared(out, num, RGB_to_CAM) schedule(static)
2232 #endif
2233   for(int i = 0; i < num; i++)
2234   {
2235     float *in = &out[i*3];
2236     float o[4] = {0.0f,0.0f,0.0f,0.0f};
2237     for(int c = 0; c < 4; c++)
2238       for(int k = 0; k < 3; k++)
2239         o[c] += RGB_to_CAM[c][k] * in[k];
2240     for(int c = 0; c < 4; c++)
2241       in[c] = o[c];
2242   }
2243 }
2244 
2245 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
2246 // vim: shiftwidth=2 expandtab tabstop=2 cindent
2247 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2248