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 –aln(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