Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorspaces.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Alex Chateau.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2010-2013, 2016-2017 johannes hanika.
6 Copyright (C) 2010 José Carlos García Sogo.
7 Copyright (C) 2010, 2012-2014 Pascal de Bruijn.
8 Copyright (C) 2011 Antony Dovgal.
9 Copyright (C) 2011 Bruce Guenter.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011-2018 Tobias Ellinghaus.
12 Copyright (C) 2011, 2013-2014 Ulrich Pegelow.
13 Copyright (C) 2012 Christian Tellefsen.
14 Copyright (C) 2012 Jérémy Rosen.
15 Copyright (C) 2012 Richard Wonka.
16 Copyright (C) 2014-2016 Pedro Côrte-Real.
17 Copyright (C) 2014-2016 Roman Lebedev.
18 Copyright (C) 2015 parafin.
19 Copyright (C) 2016 Peter Budai.
20 Copyright (C) 2018-2019 Edgardo Hoszowski.
21 Copyright (C) 2019 Andreas Schneider.
22 Copyright (C) 2019-2020 Heiko Bauke.
23 Copyright (C) 2019 jakubfi.
24 Copyright (C) 2019 luzpaz.
25 Copyright (C) 2019 Matthias Vogelgesang.
26 Copyright (C) 2019-2022 Pascal Obry.
27 Copyright (C) 2019 Philippe Weyland.
28 Copyright (C) 2020 a.
29 Copyright (C) 2020, 2022-2026 Aurélien PIERRE.
30 Copyright (C) 2020 Dan Torop.
31 Copyright (C) 2020 Hubert Kowalski.
32 Copyright (C) 2020-2021 Miloš Komarčević.
33 Copyright (C) 2020-2021 Ralf Brown.
34 Copyright (C) 2021 Sakari Kapanen.
35 Copyright (C) 2022 Martin Bařinka.
36 Copyright (C) 2023 Alynx Zhou.
37 Copyright (C) 2023 Luca Zulberti.
38
39 darktable is free software: you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation, either version 3 of the License, or
42 (at your option) any later version.
43
44 darktable is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with darktable. If not, see <http://www.gnu.org/licenses/>.
51*/
52
53#include "common/colorspaces.h"
55#include "common/darktable.h"
56#include "common/debug.h"
57#include "common/image_cache.h"
59#include "common/math.h"
60#include "common/matrices.h"
62#include "common/utility.h"
63#include "control/conf.h"
64#include "control/control.h"
65#include "develop/imageop.h"
66
67#include <strings.h>
68
69#ifdef USE_COLORDGTK
70#include "colord-gtk.h"
71#endif
72
73#ifdef _WIN32
74#include <dwmapi.h>
75#include <gdk/gdkwin32.h>
76#endif
77
78#ifdef HAVE_OPENJPEG
79#include "common/imageio_j2k.h"
80#endif
81#include "common/imageio_jpeg.h"
82#include "common/imageio_png.h"
83#include "common/imageio_tiff.h"
84#ifdef HAVE_LIBAVIF
85#include "common/imageio_avif.h"
86#endif
87#ifdef HAVE_LIBHEIF
88#include "common/imageio_heif.h"
89#endif
90
91#if 0
92#include <ApplicationServices/ApplicationServices.h>
93#include <Carbon/Carbon.h>
94#include <CoreServices/CoreServices.h>
95#endif
96
98 cmsHPROFILE profile, const char *name, int in_pos,
99 int out_pos, int display_pos, int category_pos,
100 int work_pos);
101
102static const cmsCIEXYZ d65 = {0.95045471, 1.00000000, 1.08905029};
103
104//D65 (sRGB, AdobeRGB, Rec2020)
105static const cmsCIExyY D65xyY = {0.312700492, 0.329000939, 1.0};
106
107//D60
108//static const cmsCIExyY d60 = {0.32168, 0.33767, 1.0};
109
110//D50 (ProPhoto RGB)
111static const cmsCIExyY D50xyY = {0.3457, 0.3585, 1.0};
112
113// D65:
114static const cmsCIExyYTRIPLE sRGB_Primaries = {
115 {0.6400, 0.3300, 1.0}, // red
116 {0.3000, 0.6000, 1.0}, // green
117 {0.1500, 0.0600, 1.0} // blue
118};
119
120// D65:
121static const cmsCIExyYTRIPLE Rec2020_Primaries = {
122 {0.7080, 0.2920, 1.0}, // red
123 {0.1700, 0.7970, 1.0}, // green
124 {0.1310, 0.0460, 1.0} // blue
125};
126
127// D65:
128static const cmsCIExyYTRIPLE Rec709_Primaries = {
129 {0.6400, 0.3300, 1.0}, // red
130 {0.3000, 0.6000, 1.0}, // green
131 {0.1500, 0.0600, 1.0} // blue
132};
133
134// D65:
135static const cmsCIExyYTRIPLE Adobe_Primaries = {
136 {0.6400, 0.3300, 1.0}, // red
137 {0.2100, 0.7100, 1.0}, // green
138 {0.1500, 0.0600, 1.0} // blue
139};
140
141// D65:
142static const cmsCIExyYTRIPLE P3_Primaries = {
143 {0.680, 0.320, 1.0}, // red
144 {0.265, 0.690, 1.0}, // green
145 {0.150, 0.060, 1.0} // blue
146};
147
148// https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space
149// D50:
150static const cmsCIExyYTRIPLE ProPhoto_Primaries = {
151 /* x, y, Y */
152 { 0.734699, 0.265301, 1.0000 }, /* red */
153 { 0.159597, 0.840403, 1.0000 }, /* green */
154 { 0.036598, 0.000105, 1.0000 }, /* blue */
155};
156
158
159#define generate_mat3inv_body(c_type, A, B) \
160 int mat3inv_##c_type(c_type *const dst, const c_type *const src) \
161 { \
162 \
163 const c_type det = A(1, 1) * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3)) \
164 - A(2, 1) * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3)) \
165 + A(3, 1) * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3)); \
166 \
167 const c_type epsilon = 1e-7f; \
168 if(fabs(det) < epsilon) return 1; \
169 \
170 const c_type invDet = 1.0 / det; \
171 \
172 B(1, 1) = invDet * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3)); \
173 B(1, 2) = -invDet * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3)); \
174 B(1, 3) = invDet * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3)); \
175 \
176 B(2, 1) = -invDet * (A(3, 3) * A(2, 1) - A(3, 1) * A(2, 3)); \
177 B(2, 2) = invDet * (A(3, 3) * A(1, 1) - A(3, 1) * A(1, 3)); \
178 B(2, 3) = -invDet * (A(2, 3) * A(1, 1) - A(2, 1) * A(1, 3)); \
179 \
180 B(3, 1) = invDet * (A(3, 2) * A(2, 1) - A(3, 1) * A(2, 2)); \
181 B(3, 2) = -invDet * (A(3, 2) * A(1, 1) - A(3, 1) * A(1, 2)); \
182 B(3, 3) = invDet * (A(2, 2) * A(1, 1) - A(2, 1) * A(1, 2)); \
183 return 0; \
184 }
185
186#define A(y, x) src[(y - 1) * 3 + (x - 1)]
187#define B(y, x) dst[(y - 1) * 3 + (x - 1)]
190
191 int mat3inv(float *const dst, const float *const src)
192{
193 return mat3inv_float(dst, src);
194}
195
197#undef B
198#undef A
199#undef generate_mat3inv_body
200
201
204 const char *filename,
206
208static int dt_colorspaces_get_matrix_from_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
209 float *lutb, const int lutsize, const int input)
210{
211 // create an OpenCL processable matrix + tone curves from an cmsHPROFILE:
212 // NOTE: may be invoked with matrix and LUT pointers set to null to find
213 // out if the profile can be created at all.
214
215 // check this first:
216 if(IS_NULL_PTR(prof) || !cmsIsMatrixShaper(prof)) return 1;
217
218 // there are some profiles that contain both a color LUT for some specific
219 // intent and a generic matrix. in some cases the matrix might be
220 // deliberately wrong with swapped blue and red channels in order to easily
221 // detect if a color managed software is applying the LUT or the matrix.
222 // thus, if this profile contains LUT for any intent, it might also contain
223 // swapped matrix, so the only right way to handle it is to let LCMS apply it.
224 const int UsedDirection = input ? LCMS_USED_AS_INPUT : LCMS_USED_AS_OUTPUT;
225
226 if(cmsIsCLUT(prof, INTENT_PERCEPTUAL, UsedDirection)
227 || cmsIsCLUT(prof, INTENT_RELATIVE_COLORIMETRIC, UsedDirection)
228 || cmsIsCLUT(prof, INTENT_ABSOLUTE_COLORIMETRIC, UsedDirection)
229 || cmsIsCLUT(prof, INTENT_SATURATION, UsedDirection))
230 return 1;
231
232 cmsToneCurve *red_curve = cmsReadTag(prof, cmsSigRedTRCTag);
233 cmsToneCurve *green_curve = cmsReadTag(prof, cmsSigGreenTRCTag);
234 cmsToneCurve *blue_curve = cmsReadTag(prof, cmsSigBlueTRCTag);
235
236 cmsCIEXYZ *red_color = cmsReadTag(prof, cmsSigRedColorantTag);
237 cmsCIEXYZ *green_color = cmsReadTag(prof, cmsSigGreenColorantTag);
238 cmsCIEXYZ *blue_color = cmsReadTag(prof, cmsSigBlueColorantTag);
239
240 if(IS_NULL_PTR(red_curve) || IS_NULL_PTR(green_curve) || IS_NULL_PTR(blue_curve) || IS_NULL_PTR(red_color) || IS_NULL_PTR(green_color) || IS_NULL_PTR(blue_color)) return 2;
241
242 dt_colormatrix_t matrix_tmp = { { red_color->X, green_color->X, blue_color->X },
243 { red_color->Y, green_color->Y, blue_color->Y },
244 { red_color->Z, green_color->Z, blue_color->Z } };
245
246 // some camera ICC profiles claim to have color locations for red, green and blue base colors defined,
247 // but in fact these are all set to zero. we catch this case here.
248 float sum = 0.0f;
249 for(int k1 = 0; k1 < 3; k1++)
250 for(int k2 = 0; k2 < 3; k2++)
251 sum += matrix_tmp[k1][k2];
252 if(sum == 0.0f) return 3;
253
254 if(input && lutr && lutg && lutb)
255 {
256 // mark as linear, if they are:
257 if(cmsIsToneCurveLinear(red_curve))
258 lutr[0] = -1.0f;
259 else
260 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(red_curve, k / (lutsize - 1.0f));
261 if(cmsIsToneCurveLinear(green_curve))
262 lutg[0] = -1.0f;
263 else
264 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(green_curve, k / (lutsize - 1.0f));
265 if(cmsIsToneCurveLinear(blue_curve))
266 lutb[0] = -1.0f;
267 else
268 for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(blue_curve, k / (lutsize - 1.0f));
269 }
270 else
271 {
272 // invert profile->XYZ matrix for output profiles
274 memcpy(tmp, matrix_tmp, sizeof(dt_colormatrix_t));
275 if(mat3SSEinv(matrix_tmp, tmp))
276 return 3;
277 // also need to reverse gamma, to apply reverse before matrix multiplication:
278 cmsToneCurve *rev_red = cmsReverseToneCurveEx(0x8000, red_curve);
279 cmsToneCurve *rev_green = cmsReverseToneCurveEx(0x8000, green_curve);
280 cmsToneCurve *rev_blue = cmsReverseToneCurveEx(0x8000, blue_curve);
281 if(IS_NULL_PTR(rev_red) || IS_NULL_PTR(rev_green) || IS_NULL_PTR(rev_blue))
282 {
283 cmsFreeToneCurve(rev_red);
284 cmsFreeToneCurve(rev_green);
285 cmsFreeToneCurve(rev_blue);
286 return 4;
287 }
288
289 if(lutr && lutg && lutb)
290 {
291 // pass on tonecurves, in case lutsize > 0:
292 if(cmsIsToneCurveLinear(red_curve))
293 lutr[0] = -1.0f;
294 else
295 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(rev_red, k / (lutsize - 1.0f));
296 if(cmsIsToneCurveLinear(green_curve))
297 lutg[0] = -1.0f;
298 else
299 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(rev_green, k / (lutsize - 1.0f));
300 if(cmsIsToneCurveLinear(blue_curve))
301 lutb[0] = -1.0f;
302 else
303 for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(rev_blue, k / (lutsize - 1.0f));
304 }
305
306 cmsFreeToneCurve(rev_red);
307 cmsFreeToneCurve(rev_green);
308 cmsFreeToneCurve(rev_blue);
309 }
310
311 if(matrix)
312 memcpy(matrix, matrix_tmp, sizeof(dt_colormatrix_t));
313
314 return 0;
315}
316
317int dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
318 float *lutb, const int lutsize)
319{
320 return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 1);
321}
322
323int dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
324 float *lutb, const int lutsize)
325{
326 return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 0);
327}
328
330{
331 return cmsCreateLab4Profile(cmsD50_xyY());
332}
333
334static void _compute_prequantized_primaries(const cmsCIExyY* whitepoint,
335 const cmsCIExyYTRIPLE* primaries,
336 cmsCIEXYZTRIPLE *primaries_prequantized)
337{
338 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, NULL);
339
340 cmsCIEXYZ *R = cmsReadTag(profile, cmsSigRedColorantTag);
341 cmsCIEXYZ *G = cmsReadTag(profile, cmsSigGreenColorantTag);
342 cmsCIEXYZ *B = cmsReadTag(profile, cmsSigBlueColorantTag);
343
344 primaries_prequantized->Red.X = (double)R->X;
345 primaries_prequantized->Red.Y = (double)R->Y;
346 primaries_prequantized->Red.Z = (double)R->Z;
347
348 primaries_prequantized->Green.X = (double)G->X;
349 primaries_prequantized->Green.Y = (double)G->Y;
350 primaries_prequantized->Green.Z = (double)G->Z;
351
352 primaries_prequantized->Blue.X = (double)B->X;
353 primaries_prequantized->Blue.Y = (double)B->Y;
354 primaries_prequantized->Blue.Z = (double)B->Z;
355
356 cmsCloseProfile(profile);
357}
358
359static cmsHPROFILE _create_lcms_profile(const char *desc, const char *dmdd,
360 const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsToneCurve *trc,
361 gboolean v2)
362{
363 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
364 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
365 cmsMLU *mlu3 = cmsMLUalloc(NULL, 1);
366 cmsMLU *mlu4 = cmsMLUalloc(NULL, 1);
367
368 cmsToneCurve *out_curves[3] = { trc, trc, trc };
369 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, out_curves);
370
371 if(v2) cmsSetProfileVersion(profile, 2.4);
372
373 cmsSetHeaderFlags(profile, cmsEmbeddedProfileTrue);
374
375 cmsMLUsetASCII(mlu1, "en", "US", "Public Domain");
376 cmsWriteTag(profile, cmsSigCopyrightTag, mlu1);
377
378 cmsMLUsetASCII(mlu2, "en", "US", desc);
379 cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
380
381 cmsMLUsetASCII(mlu3, "en", "US", dmdd);
382 cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu3);
383
384 cmsMLUsetASCII(mlu4, "en", "US", "darktable");
385 cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu4);
386
387 cmsMLUfree(mlu1);
388 cmsMLUfree(mlu2);
389 cmsMLUfree(mlu3);
390 cmsMLUfree(mlu4);
391
392 return profile;
393}
394
395// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
396// Perceptual Quantization / SMPTE standard ST.2084
397static double _PQ_fct(double x)
398{
399 static const double M1 = 2610.0 / 16384.0;
400 static const double M2 = (2523.0 / 4096.0) * 128.0;
401 static const double C1 = 3424.0 / 4096.0;
402 static const double C2 = (2413.0 / 4096.0) * 32.0;
403 static const double C3 = (2392.0 / 4096.0) * 32.0;
404
405 if (x == 0.0) return 0.0;
406 const double sign = x;
407 x = fabs(x);
408
409 const double xpo = pow(x, 1.0 / M2);
410 const double num = MAX(xpo - C1, 0.0);
411 const double den = C2 - C3 * xpo;
412 const double res = pow(num / den, 1.0 / M1);
413
414 return copysign(res, sign);
415}
416
417// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
418// Hybrid Log-Gamma
419static double _HLG_fct(double x)
420{
421 static const double A = 0.17883277;
422 static const double B = 0.28466892;
423 static const double C = 0.55991073;
424
431 const double sign = x;
432 const double e = fabs(x);
433
434 if(e <= 0.5)
435 return copysign((e * e) / 3.0, sign);
436
437 return copysign((exp((e - C) / A) + B) / 12.0, sign);
438}
439
440static cmsToneCurve* _colorspaces_create_transfer(int32_t size, double (*fct)(double))
441{
442 float *values = g_malloc(sizeof(float) * size);
443
444 for (int32_t i = 0; i < size; ++i)
445 {
446 const double x = (float)i / (size - 1);
447 const double y = MIN(fct(x), 1.0f);
448 values[i] = (float)y;
449 }
450
451 cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(NULL, size, values);
452 dt_free(values);
453 return result;
454}
455
456static cmsHPROFILE _colorspaces_create_srgb_profile(gboolean v2)
457{
458 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
459 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
460
461 cmsHPROFILE profile = _create_lcms_profile("sRGB", "sRGB",
462 &D65xyY, &sRGB_Primaries, transferFunction, v2);
463
464 cmsFreeToneCurve(transferFunction);
465
466 return profile;
467}
468
473
478
480{
481 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
482 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
483
484 cmsCIExyYTRIPLE BRG_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Red, sRGB_Primaries.Green };
485
486 cmsHPROFILE profile = _create_lcms_profile("BRG", "BRG",
487 &D65xyY, &BRG_Primaries, transferFunction, TRUE);
488
489 cmsFreeToneCurve(transferFunction);
490
491 return profile;
492}
493
495{
496 cmsFloat64Number srgb_parameters[5] = { 1/0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
497 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
498
499 cmsHPROFILE profile = _create_lcms_profile("Gamma Rec709 RGB", "Gamma Rec709 RGB",
500 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
501
502 cmsFreeToneCurve(transferFunction);
503
504 return profile;
505}
506
508{
509 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1886-0-201103-I!!PDF-E.pdf
510 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 2.19921875);
511
512 cmsHPROFILE profile = _create_lcms_profile("ITU-R BT.1886 (gamma 2.4 Rec709)", "ITU-R BT.1886 (gamma 2.4 Rec709)",
513 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
514
515 cmsFreeToneCurve(transferFunction);
516
517 return profile;
518}
519
520
521// Create the ICC virtual profile for adobe rgb space
523{
524 // AdobeRGB's "2.2" gamma is technically defined as 2 + 51/256
525 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 2.19921875);
526
527 cmsHPROFILE profile = _create_lcms_profile("Adobe RGB (compatible)", "Adobe RGB",
528 &D65xyY, &Adobe_Primaries, transferFunction, TRUE);
529
530 cmsFreeToneCurve(transferFunction);
531
532 return profile;
533}
534
535
536cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
537{
539 for(int k = 0; k < dt_alternate_colormatrix_cnt; k++)
540 {
541 if(!strcmp(makermodel, dt_alternate_colormatrices[k].makermodel))
542 {
544 break;
545 }
546 }
547 if(IS_NULL_PTR(preset)) return NULL;
548
549 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
550 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
551 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
552 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
553 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
554 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
555 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
556 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
557 cmsToneCurve *Gamma[3];
558 cmsHPROFILE hp;
559
560 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
561
562 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
563 cmsFreeToneCurve(Gamma[0]);
564 if(IS_NULL_PTR(hp)) return NULL;
565
566 char name[512];
567 snprintf(name, sizeof(name), "darktable alternate %s", makermodel);
568 cmsSetProfileVersion(hp, 2.1);
569 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
570 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
571 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
572 cmsMLUsetASCII(mlu1, "en", "US", name);
573 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
574 cmsMLUsetASCII(mlu2, "en", "US", name);
575 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
576 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
577 // this will only be displayed when the embedded profile is read by for example GIMP
578 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
579 cmsMLUfree(mlu0);
580 cmsMLUfree(mlu1);
581 cmsMLUfree(mlu2);
582
583 return hp;
584}
585
586cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
587{
589 for(int k = 0; k < dt_vendor_colormatrix_cnt; k++)
590 {
591 if(!strcmp(makermodel, dt_vendor_colormatrices[k].makermodel))
592 {
594 break;
595 }
596 }
597 if(IS_NULL_PTR(preset)) return NULL;
598
599 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
600 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
601 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
602 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
603 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
604 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
605 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
606 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
607 cmsToneCurve *Gamma[3];
608 cmsHPROFILE hp;
609
610 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
611
612 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
613 cmsFreeToneCurve(Gamma[0]);
614 if(IS_NULL_PTR(hp)) return NULL;
615
616 char name[512];
617 snprintf(name, sizeof(name), "darktable vendor %s", makermodel);
618 cmsSetProfileVersion(hp, 2.1);
619 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
620 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
621 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
622 cmsMLUsetASCII(mlu1, "en", "US", name);
623 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
624 cmsMLUsetASCII(mlu2, "en", "US", name);
625 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
626 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
627 // this will only be displayed when the embedded profile is read by for example GIMP
628 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
629 cmsMLUfree(mlu0);
630 cmsMLUfree(mlu1);
631 cmsMLUfree(mlu2);
632
633 return hp;
634}
635
636cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
637{
639 for(int k = 0; k < dt_profiled_colormatrix_cnt; k++)
640 {
641 if(!strcasecmp(makermodel, dt_profiled_colormatrices[k].makermodel))
642 {
644 break;
645 }
646 }
647 if(IS_NULL_PTR(preset)) return NULL;
648
649 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
650 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
651 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
652 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
653 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
654 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
655 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
656 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
657 cmsToneCurve *Gamma[3];
658 cmsHPROFILE hp;
659
660 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
661
662 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
663 cmsFreeToneCurve(Gamma[0]);
664 if(IS_NULL_PTR(hp)) return NULL;
665
666 char name[512];
667 snprintf(name, sizeof(name), "darktable profiled %s", makermodel);
668 cmsSetProfileVersion(hp, 2.1);
669 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
670 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
671 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
672 cmsMLUsetASCII(mlu1, "en", "US", name);
673 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
674 cmsMLUsetASCII(mlu2, "en", "US", name);
675 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
676 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
677 // this will only be displayed when the embedded profile is read by for example GIMP
678 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
679 cmsMLUfree(mlu0);
680 cmsMLUfree(mlu1);
681 cmsMLUfree(mlu2);
682
683 return hp;
684}
685
686static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
687{
688 cmsHPROFILE hXYZ = cmsCreateXYZProfile();
689 cmsSetPCS(hXYZ, cmsSigXYZData);
690 cmsSetHeaderRenderingIntent(hXYZ, INTENT_PERCEPTUAL);
691
692 if(IS_NULL_PTR(hXYZ)) return NULL;
693
694 cmsSetProfileVersion(hXYZ, 2.1);
695 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
696 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
697 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
698 cmsMLUsetASCII(mlu1, "en", "US", "linear XYZ");
699 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
700 cmsMLUsetASCII(mlu2, "en", "US", "darktable linear XYZ");
701 cmsWriteTag(hXYZ, cmsSigDeviceMfgDescTag, mlu0);
702 cmsWriteTag(hXYZ, cmsSigDeviceModelDescTag, mlu1);
703 // this will only be displayed when the embedded profile is read by for example GIMP
704 cmsWriteTag(hXYZ, cmsSigProfileDescriptionTag, mlu2);
705 cmsMLUfree(mlu0);
706 cmsMLUfree(mlu1);
707 cmsMLUfree(mlu2);
708
709 return hXYZ;
710}
711
713{
714 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
715
716 cmsHPROFILE profile = _create_lcms_profile("Linear Rec709 RGB", "Linear Rec709 RGB",
717 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
718
719 cmsFreeToneCurve(transferFunction);
720
721 return profile;
722}
723
725{
726 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
727
728 cmsHPROFILE profile = _create_lcms_profile("Linear Rec2020 RGB", "Linear Rec2020 RGB",
729 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
730
731 cmsFreeToneCurve(transferFunction);
732
733 return profile;
734}
735
737{
738 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
739
740 cmsHPROFILE profile = _create_lcms_profile("PQ Rec2020 RGB", "PQ Rec2020 RGB",
741 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
742
743 cmsFreeToneCurve(transferFunction);
744
745 return profile;
746}
747
749{
750 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
751
752 cmsHPROFILE profile = _create_lcms_profile("HLG Rec2020 RGB", "HLG Rec2020 RGB",
753 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
754
755 cmsFreeToneCurve(transferFunction);
756
757 return profile;
758}
759
761{
762 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
763
764 cmsHPROFILE profile = _create_lcms_profile("PQ P3 RGB", "PQ P3 RGB",
765 &D65xyY, &P3_Primaries, transferFunction, TRUE);
766
767 cmsFreeToneCurve(transferFunction);
768
769 return profile;
770}
771
773{
774 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
775
776 cmsHPROFILE profile = _create_lcms_profile("HLG P3 RGB", "HLG P3 RGB",
777 &D65xyY, &P3_Primaries, transferFunction, TRUE);
778
779 cmsFreeToneCurve(transferFunction);
780
781 return profile;
782}
783
785{
786 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
787 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
788
789 cmsHPROFILE profile = _create_lcms_profile("Display P3 RGB", "Display P3 RGB",
790 &D65xyY, &P3_Primaries, transferFunction, TRUE);
791
792 cmsFreeToneCurve(transferFunction);
793
794 return profile;
795}
796
798{
799 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
800
801 cmsHPROFILE profile = _create_lcms_profile("Linear ProPhoto RGB", "Linear ProPhoto RGB",
802 &D50xyY, &ProPhoto_Primaries, transferFunction, TRUE);
803
804 cmsFreeToneCurve(transferFunction);
805
806 return profile;
807}
808
810{
811 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
812
813 // linear rgb with r and b swapped:
814 cmsCIExyYTRIPLE BGR_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Green, sRGB_Primaries.Red };
815
816 cmsHPROFILE profile = _create_lcms_profile("Linear Infrared BGR", "darktable Linear Infrared BGR",
817 &D65xyY, &BGR_Primaries, transferFunction, FALSE);
818
819 cmsFreeToneCurve(transferFunction);
820
821 return profile;
822}
823
825{
826 // find the colorin module -- the pointer stays valid until darktable shuts down
827 static const dt_iop_module_so_t *colorin = NULL;
828 if(IS_NULL_PTR(colorin))
829 {
830 for(const GList *modules = darktable.iop; modules; modules = g_list_next(modules))
831 {
832 const dt_iop_module_so_t *module = (const dt_iop_module_so_t *)(modules->data);
833 if(!strcmp(module->op, "colorin"))
834 {
835 colorin = module;
836 break;
837 }
838 }
839 }
840
841 const dt_colorspaces_color_profile_t *p = NULL;
842
843 if(colorin && colorin->get_p)
844 {
845 // get the profile assigned from colorin
846 // FIXME: does this work when using JPEG thumbs and the image was never opened?
847 sqlite3_stmt *stmt;
848 // clang-format off
851 "SELECT op_params FROM main.history WHERE imgid=?1 AND operation='colorin' ORDER BY num DESC LIMIT 1", -1,
852 &stmt, NULL);
853 // clang-format on
854 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
855 if(sqlite3_step(stmt) == SQLITE_ROW)
856 {
857 // use introspection to get the profile name from the binary params blob
858 const void *params = sqlite3_column_blob(stmt, 0);
859 dt_colorspaces_color_profile_type_t *type = colorin->get_p(params, "type_work");
860 char *filename = colorin->get_p(params, "filename_work");
861
862 if(type && filename) p = dt_colorspaces_get_profile(*type, filename,
864 }
865 sqlite3_finalize(stmt);
866 }
867
868 // if all else fails -> fall back to linear Rec2020 RGB
870
871 return p;
872}
873
874dt_colorspaces_color_profile_type_t dt_image_find_best_color_profile(int32_t imgid, cmsHPROFILE *output, gboolean *new_profile)
875{
876 // Note : when the image has already been opened from cache on the current session,
877 // the embedded color profile is already inited and stored in img->profile.
878
879 // Untagged images should be assumed to be sRGB.
881 *new_profile = FALSE;
882
883 // Fetch filename for extension retrieval
884 char filename[PATH_MAX] = { 0 };
885 gboolean from_cache = TRUE;
886 dt_image_full_path(imgid, filename, sizeof(filename), &from_cache, __FUNCTION__);
887
888 const gchar *cc = filename + strlen(filename);
889 for(; *cc != '.' && cc > filename; cc--);
890 gchar *ext = g_ascii_strdown(cc + 1, -1);
891
892 // Fetch actual image
894 if(IS_NULL_PTR(img)) goto finish;
895
896 // Image codecs doing their own colorspace detection should set this to TRUE
897 gboolean already_set = FALSE;
898
899 dt_print(DT_DEBUG_COLORPROFILE, "Color profile type for %s: \n", filename);
900
901 if(img->profile && img->profile_size > 0
903 {
904 // Fast path : we already extracted ICC before. ICC profile is already inside.
905 color_profile = DT_COLORSPACE_EMBEDDED_ICC;
906 if(!IS_NULL_PTR(output))
907 {
909 *new_profile = TRUE;
910 }
911 dt_print(DT_DEBUG_COLORPROFILE, "Embedded ICC profile (inline)\n");
912 }
913 else if(!isnan(img->d65_color_matrix[0])
915 {
916 // DNG and others : matrix inside EXIF
917 color_profile = DT_COLORSPACE_EMBEDDED_MATRIX;
918 if(!IS_NULL_PTR(output))
919 {
921 *new_profile = TRUE;
922 }
923 dt_print(DT_DEBUG_COLORPROFILE, "Embedded EXIF matrix\n");
924 }
925 else if(dt_image_is_monochrome(img))
926 {
927 // Monochrome RAW - colorspace doesn't matter
928 color_profile = DT_COLORSPACE_LIN_REC709;
929 if(!IS_NULL_PTR(output))
931 dt_print(DT_DEBUG_COLORPROFILE, "Monochrome RAW\n");
932 }
934 {
935 // Color RAW
936 color_profile = DT_COLORSPACE_STANDARD_MATRIX;
937 if(!IS_NULL_PTR(output))
938 {
940 *new_profile = TRUE;
941 }
942 dt_print(DT_DEBUG_COLORPROFILE, "Typical RAW\n");
943 }
944 else if(img->flags & DT_IMAGE_4BAYER)
945 {
946 // 4Bayer images have been pre-converted to rec2020
947 color_profile = DT_COLORSPACE_LIN_REC2020;
948 if(!IS_NULL_PTR(output))
950 dt_print(DT_DEBUG_COLORPROFILE, "4Bayer RAW\n");
951 }
952 else if(img->colorspace == DT_IMAGE_COLORSPACE_SRGB)
953 {
954 // Images tagged explicitely with sRGB flag
955 color_profile = DT_COLORSPACE_SRGB;
956 dt_print(DT_DEBUG_COLORPROFILE, "Raster image tagged with sRGB\n");
957 }
959 {
960 // Images tagged explicitely with Adobe RGB flag
961 color_profile = DT_COLORSPACE_ADOBERGB;
962 if(!IS_NULL_PTR(output))
964 dt_print(DT_DEBUG_COLORPROFILE, "Raster image tagged with Adobe RGB\n");
965 }
966 else if(!strcmp(ext, "pfm"))
967 {
968 // PFM have no embedded color profile nor ICC tag, we can't know the color space
969 // but we can assume the are linear since it's a floating point format
970 color_profile = DT_COLORSPACE_LIN_REC709;
971 if(!IS_NULL_PTR(output))
973 dt_print(DT_DEBUG_COLORPROFILE, "PFM untagged image\n");
974 }
975 else
976 {
977 // Images that need codecs.
978
979 // First, extract embedded profiles from headers.
980 // Done only once : if everything goes well, the next time we access this image from cache,
981 // we will read img->profile directly (first branch here).
982
983 if(!strcmp(ext, "jpg") || !strcmp(ext, "jpeg"))
984 {
986 if(!dt_imageio_jpeg_read_header(filename, &jpg))
988 }
989#ifdef HAVE_OPENJPEG
990 else if(!strcmp(ext, "jp2") || !strcmp(ext, "j2k") || !strcmp(ext, "j2c") || !strcmp(ext, "jpc"))
991 {
992 img->profile_size = dt_imageio_j2k_read_profile(filename, &img->profile);
993 }
994#endif
995 else if((!strcmp(ext, "tif") || !strcmp(ext, "tiff")))
996 {
997 img->profile_size = dt_imageio_tiff_read_profile(filename, &img->profile);
998 }
999 else if(!strcmp(ext, "png"))
1000 {
1001 img->profile_size = dt_imageio_png_read_profile(filename, &img->profile);
1002 }
1003#ifdef HAVE_LIBAVIF
1004 else if(!strcmp(ext, "avif"))
1005 {
1007 img->profile_size = dt_imageio_avif_read_profile(filename, &img->profile, &cicp);
1008
1009 // try the nclx box before falling back to any ICC profile
1010 color_profile = dt_colorspaces_cicp_to_type(&cicp, filename);
1011
1012 // If we found a basic RGB colorspace from private AVIF metadata,
1013 // bypass generic LCMS2 reading below
1014 if(color_profile != DT_COLORSPACE_NONE) already_set = TRUE;
1015 }
1016#endif
1017#ifdef HAVE_LIBHEIF
1018 else if(!strcmp(ext, "heif") || !strcmp(ext, "heic") || !strcmp(ext, "hif"))
1019 {
1021 img->profile_size = dt_imageio_heif_read_profile(filename, &img->profile, &cicp);
1022
1023 // try the nclx box before falling back to any ICC profile
1024 color_profile = dt_colorspaces_cicp_to_type(&cicp, filename);
1025
1026 // If we found a basic RGB colorspace from private AVIF metadata,
1027 // bypass generic LCMS2 reading below
1028 if(color_profile != DT_COLORSPACE_NONE) already_set = TRUE;
1029 }
1030#endif
1031
1032 // Finally, read the prepared embedded profile
1033 if(!already_set && img->profile && img->profile_size > 0
1035 {
1036 color_profile = DT_COLORSPACE_EMBEDDED_ICC;
1037 if(!IS_NULL_PTR(output))
1038 {
1040 *new_profile = TRUE;
1041 }
1042 dt_print(DT_DEBUG_COLORPROFILE, "Embedded ICC (extracted)\n");
1043 }
1044 else if(already_set && img->profile && img->profile_size > 0)
1045 {
1046 // This happens when AVIF/HEIF found a basic color profile into CICP fields
1047 if(!IS_NULL_PTR(output))
1048 *output = dt_colorspaces_get_profile(color_profile, "", DT_PROFILE_DIRECTION_IN)->profile;
1049 dt_print(DT_DEBUG_COLORPROFILE, "Embedded ICC (extracted)\n");
1050 }
1051 }
1052
1053 // Handle the fallback to sRGB space
1054 if(color_profile == DT_COLORSPACE_NONE) color_profile = DT_COLORSPACE_SRGB;
1055 if(color_profile == DT_COLORSPACE_SRGB && !IS_NULL_PTR(output))
1057
1058finish:
1060 dt_free(ext);
1061 return color_profile;
1062}
1063
1065 int32_t imgid,
1067 cmsHPROFILE *output,
1068 gboolean *new_profile)
1069{
1070 if(output) *output = NULL;
1071 if(new_profile) *new_profile = FALSE;
1072
1073 if(requested == DT_COLORSPACE_NONE)
1074 return dt_image_find_best_color_profile(imgid, output, new_profile);
1075
1076 if(requested != DT_COLORSPACE_EMBEDDED_ICC
1077 && requested != DT_COLORSPACE_EMBEDDED_MATRIX
1078 && requested != DT_COLORSPACE_STANDARD_MATRIX)
1079 return DT_COLORSPACE_NONE;
1080
1081 const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
1082 if(IS_NULL_PTR(img)) return DT_COLORSPACE_NONE;
1083
1085 {
1087 return dt_image_find_best_color_profile(imgid, output, new_profile);
1088 }
1089
1090 gboolean have_embedded_icc = (img->profile && img->profile_size > 0);
1092
1093 if(requested == DT_COLORSPACE_EMBEDDED_ICC && !have_embedded_icc)
1094 {
1095 // Try to extract embedded ICC into cache if needed.
1096 gboolean dummy_new_profile = FALSE;
1097 dt_image_find_best_color_profile(imgid, NULL, &dummy_new_profile);
1098 }
1099
1100 img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
1101 if(IS_NULL_PTR(img)) return DT_COLORSPACE_NONE;
1102
1103 cmsHPROFILE profile = NULL;
1105
1107 {
1108 if(img->profile && img->profile_size > 0)
1109 {
1111 if(profile)
1112 {
1114 goto finish;
1115 }
1116 }
1118 }
1119
1121 {
1122 if(!isnan(img->d65_color_matrix[0]))
1123 {
1125 if(profile)
1126 {
1128 goto finish;
1129 }
1130 }
1132 }
1133
1135 {
1136 if(!isnan(img->adobe_XYZ_to_CAM[0][0]))
1137 {
1139 if(profile)
1140 {
1142 goto finish;
1143 }
1144 }
1145 }
1146
1148
1149finish:
1151
1152 if(profile)
1153 {
1154 if(output)
1155 {
1156 *output = profile;
1157 if(new_profile) *new_profile = TRUE;
1158 }
1159 else
1160 {
1162 }
1163 }
1164
1165 return type;
1166}
1167
1168
1169const cmsHPROFILE dt_colorspaces_get_embedded_profile(const int32_t imgid, dt_colorspaces_color_profile_type_t *type, gboolean *new_profile)
1170{
1171 cmsHPROFILE output;
1172 *type = dt_image_find_best_color_profile(imgid, &output, new_profile);
1173 return output;
1174}
1175
1176
1178{
1179 gboolean new_profile;
1180 cmsHPROFILE profile = dt_colorspaces_get_embedded_profile(imgid, type, &new_profile);
1181
1182 // create a dt profile object. -1 in all indices ensures it's hidden from GUI
1183 dt_colorspaces_color_profile_t *container = _create_profile(*type, profile, "", -1, -1, -1, -1, -1);
1184
1185 if(profile && container && new_profile)
1186 {
1187 // Set the name string for the profile
1188 char *lang = getenv("LANG");
1189 if(IS_NULL_PTR(lang)) lang = "en_US";
1190 dt_colorspaces_get_profile_name(profile, lang, lang + 3, container->name, sizeof(container->name));
1191
1192 // add it to the stack of dt profiles so it gets freed properly when we don't need it anymore
1194 }
1195
1197}
1198
1199
1202 const char *over_filename)
1203{
1204
1205 const dt_colorspaces_color_profile_t *p = NULL;
1206
1207 // Special case if output is undefined or uses private image color spaces : use the embedded profile if any.
1208 // We need to read it from the original image and create it on-the-fly.
1209 // Note: we don't allow export with deprecated vendor/enhanced/alternate matrices
1210 if(*over_type == DT_COLORSPACE_NONE ||
1211 *over_type == DT_COLORSPACE_EMBEDDED_ICC ||
1212 *over_type == DT_COLORSPACE_STANDARD_MATRIX ||
1213 *over_type == DT_COLORSPACE_EMBEDDED_MATRIX)
1214 {
1215 p = _build_embedded_profile(imgid, over_type);
1216 }
1217 else
1218 {
1219 // return a pointer to the profile specified in export.
1220 // we have that in here to get rid of the if() check in all places calling this function.
1222 }
1223
1224 // if all else fails -> fall back to sRGB
1225 if(IS_NULL_PTR(p))
1226 {
1228 *over_type = DT_COLORSPACE_SRGB;
1229 }
1230
1231 return p;
1232}
1233
1234#if 0
1235static void dt_colorspaces_create_cmatrix(float cmatrix[4][3], float mat[3][3])
1236{
1237 // sRGB D65, the linear part:
1238 static const dt_colormatrix_t rgb_to_xyz = { { 0.4124564f, 0.3575761f, 0.1804375f, 0.0f },
1239 { 0.2126729f, 0.7151522f, 0.0721750f, 0.0f },
1240 { 0.0193339f, 0.1191920f, 0.9503041f, 0.0f } };
1241
1242 for(int c = 0; c < 3; c++)
1243 {
1244 for(int j = 0; j < 3; j++)
1245 {
1246 mat[c][j] = 0.0f;
1247 for(int k = 0; k < 3; k++)
1248 {
1249 mat[c][j] += rgb_to_xyz[k][j] * cmatrix[c][k];
1250 }
1251 }
1252 }
1253}
1254#endif
1255
1256static cmsHPROFILE dt_colorspaces_create_xyzmatrix_profile(const float mat[3][3])
1257{
1258 // mat: cam -> xyz
1260 for(int k = 0; k < 3; k++)
1261 {
1262 const float norm = mat[0][k] + mat[1][k] + mat[2][k];
1263 x[k] = mat[0][k] / norm;
1264 y[k] = mat[1][k] / norm;
1265 }
1266 cmsCIExyYTRIPLE CameraPrimaries = { { x[0], y[0], 1.0 }, { x[1], y[1], 1.0 }, { x[2], y[2], 1.0 } };
1267 cmsHPROFILE profile;
1268
1269 cmsCIExyY D65;
1270 cmsXYZ2xyY(&D65, &d65);
1271
1272 cmsToneCurve *Gamma[3];
1273 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
1274 profile = cmsCreateRGBProfile(&D65, &CameraPrimaries, Gamma);
1275 cmsFreeToneCurve(Gamma[0]);
1276 if(IS_NULL_PTR(profile)) return NULL;
1277
1278 cmsSetProfileVersion(profile, 2.1);
1279 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
1280 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
1281 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
1282 cmsMLUsetASCII(mlu1, "en", "US", "color matrix built-in");
1283 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
1284 cmsMLUsetASCII(mlu2, "en", "US", "color matrix built-in");
1285 cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu0);
1286 cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu1);
1287 // this will only be displayed when the embedded profile is read by for example GIMP
1288 cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
1289 cmsMLUfree(mlu0);
1290 cmsMLUfree(mlu1);
1291 cmsMLUfree(mlu2);
1292
1293 return profile;
1294}
1295
1297{
1298 // mat: xyz -> cam
1299 float imat[3][3];
1300 mat3inv((float *)imat, (float *)mat);
1302}
1303
1304static cmsHPROFILE _ensure_rgb_profile(cmsHPROFILE profile)
1305{
1306 if(profile && cmsGetColorSpace(profile) == cmsSigGrayData)
1307 {
1308 cmsToneCurve *trc = cmsReadTag(profile, cmsSigGrayTRCTag);
1309 cmsCIEXYZ *wtpt = cmsReadTag(profile, cmsSigMediaWhitePointTag);
1310 cmsCIEXYZ *bkpt = cmsReadTag(profile, cmsSigMediaBlackPointTag);
1311 cmsCIEXYZ *chad = cmsReadTag(profile, cmsSigChromaticAdaptationTag);
1312
1313 cmsMLU *cprt = cmsReadTag(profile, cmsSigCopyrightTag);
1314 cmsMLU *desc = cmsReadTag(profile, cmsSigProfileDescriptionTag);
1315 cmsMLU *dmnd = cmsReadTag(profile, cmsSigDeviceMfgDescTag);
1316 cmsMLU *dmdd = cmsReadTag(profile, cmsSigDeviceModelDescTag);
1317
1318 cmsHPROFILE rgb_profile = cmsCreateProfilePlaceholder(0);
1319
1320 cmsSetDeviceClass(rgb_profile, cmsSigDisplayClass);
1321 cmsSetColorSpace(rgb_profile, cmsSigRgbData);
1322 cmsSetPCS(rgb_profile, cmsSigXYZData);
1323
1324 cmsWriteTag(rgb_profile, cmsSigCopyrightTag, cprt);
1325 cmsWriteTag(rgb_profile, cmsSigProfileDescriptionTag, desc);
1326 cmsWriteTag(rgb_profile, cmsSigDeviceMfgDescTag, dmnd);
1327 cmsWriteTag(rgb_profile, cmsSigDeviceModelDescTag, dmdd);
1328
1329 cmsWriteTag(rgb_profile, cmsSigMediaBlackPointTag, bkpt);
1330 cmsWriteTag(rgb_profile, cmsSigMediaWhitePointTag, wtpt);
1331 cmsWriteTag(rgb_profile, cmsSigChromaticAdaptationTag, chad);
1332 cmsSetColorSpace(rgb_profile, cmsSigRgbData);
1333 cmsSetPCS(rgb_profile, cmsSigXYZData);
1334
1335 // TODO: we still use prequantized primaries here, we will probably want to rework this
1336 // part to create a profile using cmsCreateRGBProfile() as done in _create_lcms_profile().
1337 cmsWriteTag(rgb_profile, cmsSigRedColorantTag, (void *)&Rec709_Primaries_Prequantized.Red);
1338 cmsWriteTag(rgb_profile, cmsSigGreenColorantTag, (void *)&Rec709_Primaries_Prequantized.Green);
1339 cmsWriteTag(rgb_profile, cmsSigBlueColorantTag, (void *)&Rec709_Primaries_Prequantized.Blue);
1340
1341 cmsWriteTag(rgb_profile, cmsSigRedTRCTag, (void *)trc);
1342 cmsLinkTag(rgb_profile, cmsSigGreenTRCTag, cmsSigRedTRCTag);
1343 cmsLinkTag(rgb_profile, cmsSigBlueTRCTag, cmsSigRedTRCTag);
1344
1345 cmsCloseProfile(profile);
1346 profile = rgb_profile;
1347 }
1348
1349 return profile;
1350}
1351
1352cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
1353{
1354 cmsHPROFILE profile = _ensure_rgb_profile(cmsOpenProfileFromMem(data, size));
1355
1356 return profile;
1357}
1358
1360{
1361 if(IS_NULL_PTR(p)) return;
1362 cmsCloseProfile(p);
1363}
1364
1365void dt_colorspaces_get_profile_name(cmsHPROFILE p, const char *language, const char *country, char *name,
1366 size_t len)
1367{
1368 cmsUInt32Number size;
1369 gchar *buf = NULL;
1370 wchar_t *wbuf = NULL;
1371 gchar *utf8 = NULL;
1372
1373 size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, NULL, 0);
1374 if(size == 0) goto error;
1375
1376 buf = (char *)calloc(size + 1, sizeof(char));
1377 size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, buf, size);
1378 if(size == 0) goto error;
1379
1380 // most unix like systems should work with this, but at least Windows doesn't
1381 if(sizeof(wchar_t) != 4 || g_utf8_validate(buf, -1, NULL))
1382 g_strlcpy(name, buf, len); // better a little weird than totally borked
1383 else
1384 {
1385 wbuf = (wchar_t *)calloc(size + 1, sizeof(wchar_t));
1386 size = cmsGetProfileInfo(p, cmsInfoDescription, language, country, wbuf, sizeof(wchar_t) * size);
1387 if(size == 0) goto error;
1388 utf8 = g_ucs4_to_utf8((gunichar *)wbuf, -1, NULL, NULL, NULL);
1389 if(IS_NULL_PTR(utf8)) goto error;
1390 g_strlcpy(name, utf8, len);
1391 }
1392
1393 dt_free(buf);
1394 dt_free(wbuf);
1395 dt_free(utf8);
1396 return;
1397
1398error:
1399 if(buf)
1400 g_strlcpy(name, buf, len); // better a little weird than totally borked
1401 else
1402 *name = '\0'; // nothing to do here
1403 dt_free(buf);
1404 dt_free(wbuf);
1405 dt_free(utf8);
1406}
1407
1408void rgb2hsl(const dt_aligned_pixel_t rgb, float *h, float *s, float *l)
1409{
1410 const float r = rgb[0], g = rgb[1], b = rgb[2];
1411 const float pmax = fmaxf(r, fmax(g, b));
1412 const float pmin = fminf(r, fmin(g, b));
1413 const float delta = (pmax - pmin);
1414
1415 float hv = 0, sv = 0, lv = (pmin + pmax) / 2.0;
1416
1417 if(delta != 0.0f)
1418 {
1419 sv = lv < 0.5 ? delta / fmaxf(pmax + pmin, 1.52587890625e-05f)
1420 : delta / fmaxf(2.0 - pmax - pmin, 1.52587890625e-05f);
1421
1422 if(pmax == r)
1423 hv = (g - b) / delta;
1424 else if(pmax == g)
1425 hv = 2.0 + (b - r) / delta;
1426 else if(pmax == b)
1427 hv = 4.0 + (r - g) / delta;
1428 hv /= 6.0;
1429 if(hv < 0.0)
1430 hv += 1.0;
1431 else if(hv > 1.0)
1432 hv -= 1.0;
1433 }
1434 *h = hv;
1435 *s = sv;
1436 *l = lv;
1437}
1438
1439// for efficiency, 'hue' must be pre-scaled to be in 0..6
1440static inline __attribute__((always_inline)) float hue2rgb(float m1, float m2, float hue)
1441{
1442 // compute the value for one of the RGB channels from the hue angle.
1443 // If 1 <= angle < 3, return m2; if 4 <= angle <= 6, return m1; otherwise, linearly interpolate between m1 and m2.
1444 if(hue < 1.0f)
1445 return (m1 + (m2 - m1) * hue);
1446 else if(hue < 3.0f)
1447 return m2;
1448 else
1449 return hue < 4.0f ? (m1 + (m2 - m1) * (4.0f - hue)) : m1;
1450}
1451
1452void hsl2rgb(dt_aligned_pixel_t rgb, float h, float s, float l)
1453{
1454 float m1, m2;
1455 if(s == 0)
1456 {
1457 rgb[0] = rgb[1] = rgb[2] = l;
1458 return;
1459 }
1460 m2 = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
1461 m1 = (2.0 * l - m2);
1462 h *= 6.0f; // pre-scale hue angle
1463 rgb[0] = hue2rgb(m1, m2, h < 4.0f ? h + 2.0f : h - 4.0f);
1464 rgb[1] = hue2rgb(m1, m2, h);
1465 rgb[2] = hue2rgb(m1, m2, h > 2.0f ? h - 2.0f : h + 4.0f);
1466}
1467
1469 cmsHPROFILE profile, const char *name, int in_pos,
1470 int out_pos, int display_pos, int category_pos,
1471 int work_pos)
1472{
1475 prof->type = type;
1476 g_strlcpy(prof->name, name, sizeof(prof->name));
1477 prof->profile = profile;
1478 prof->in_pos = in_pos;
1479 prof->out_pos = out_pos;
1480 prof->display_pos = display_pos;
1481 prof->category_pos = category_pos;
1482 prof->work_pos = work_pos;
1483 return prof;
1484}
1485
1486// this function is basically thread safe, at least when not called on the global darktable.color_profiles
1488{
1489 if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
1490 self->transform_srgb_to_display = NULL;
1491
1492 if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
1493 self->transform_adobe_rgb_to_display = NULL;
1494
1495 if(self->transform_xyz_to_display) cmsDeleteTransform(self->transform_xyz_to_display);
1496 self->transform_xyz_to_display = NULL;
1497
1498 if(self->transform_display_to_adobe_rgb) cmsDeleteTransform(self->transform_display_to_adobe_rgb);
1499 self->transform_display_to_adobe_rgb = NULL;
1500
1501 const dt_colorspaces_color_profile_t *display_dt_profile = _get_profile(self, self->display_type,
1502 self->display_filename,
1504 if(IS_NULL_PTR(display_dt_profile)) return;
1505 cmsHPROFILE display_profile = display_dt_profile->profile;
1506 if(IS_NULL_PTR(display_profile)) return;
1507
1508 self->transform_srgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_SRGB, "",
1510 TYPE_RGBA_8,
1511 display_profile,
1512 TYPE_BGRA_8,
1513 self->display_intent,
1514 0);
1515
1516 self->transform_xyz_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_XYZ, "",
1517 DT_PROFILE_DIRECTION_IN)->profile,
1519 display_profile,
1520 TYPE_RGBA_FLT,
1521 self->display_intent,
1522 0);
1523
1524 self->transform_adobe_rgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_ADOBERGB, "",
1526 TYPE_RGBA_8,
1527 display_profile,
1528 TYPE_BGRA_8,
1529 self->display_intent,
1530 0);
1531
1532 self->transform_display_to_adobe_rgb = cmsCreateTransform(display_profile,
1533 TYPE_BGRA_8,
1536 TYPE_RGBA_8,
1537 self->display_intent,
1538 0);
1539}
1540
1541// update cached transforms for color management of thumbnails
1542// make sure that darktable.color_profiles->xprofile_lock is held when calling this!
1547
1548void dt_colorspaces_transform_rgba_float_row(const cmsHTRANSFORM transform, const float *in, float *out,
1549 const int width)
1550{
1551 cmsDoTransform(transform, in, out, width);
1552}
1553
1555void dt_colorspaces_transform_rgba_float_image(const cmsHTRANSFORM transform, const float *image_in, float *image_out,
1556 const int width, const int height)
1557{
1558 if(IS_NULL_PTR(transform) || IS_NULL_PTR(image_in) || IS_NULL_PTR(image_out) || width <= 0 || height <= 0) return;
1559
1560 /* Share the aliased LCMS transform explicitly. Do not read it indirectly
1561 * through module state inside the loop body. */
1563 for(int y = 0; y < height; y++)
1564 {
1565 const float *const in = image_in + (size_t)y * width * 4;
1566 float *const out = image_out + (size_t)y * width * 4;
1568 }
1569}
1570
1571void dt_colorspaces_transform_rgba8_to_bgra8(const cmsHTRANSFORM transform, const uint8_t *image_in, uint8_t *image_out,
1572 const int width, const int height)
1573{
1574 if(IS_NULL_PTR(image_in) || IS_NULL_PTR(image_out) || width <= 0 || height <= 0) return;
1575
1576 /* Same threading rule as float transforms: pass an aliased transform handle
1577 * into the helper and share only that stable local state. */
1579 for(int y = 0; y < height; y++)
1580 {
1581 const uint8_t *const restrict in = image_in + (size_t)y * width * 4u;
1582 uint8_t *const restrict out = image_out + (size_t)y * width * 4u;
1583
1584 if(transform)
1585 {
1586 cmsDoTransform(transform, in, out, width);
1587 for(int x = 0; x < width; x++) out[4 * x + 3] = UINT8_MAX;
1588 }
1589 else
1590 {
1591 for(int x = 0; x < width; x++)
1592 {
1593 out[4 * x + 0] = in[4 * x + 2];
1594 out[4 * x + 1] = in[4 * x + 1];
1595 out[4 * x + 2] = in[4 * x + 0];
1596 out[4 * x + 3] = UINT8_MAX;
1597 }
1598 }
1599 }
1600}
1601
1602// make sure that darktable.color_profiles->xprofile_lock is held when calling this!
1603static void _update_display_profile(guchar *tmp_data, gsize size, char *name, size_t name_size)
1604{
1608
1609 cmsHPROFILE profile = cmsOpenProfileFromMem(tmp_data, size);
1610 if(profile)
1611 {
1612 for(GList *iter = darktable.color_profiles->profiles; iter; iter = g_list_next(iter))
1613 {
1615 if(p->type == DT_COLORSPACE_DISPLAY)
1616 {
1617 if(p->profile) dt_colorspaces_cleanup_profile(p->profile);
1618 p->profile = profile;
1619 if(name)
1620 dt_colorspaces_get_profile_name(profile, "en", "US", name, name_size);
1621
1622 // update cached transforms for color management of thumbnails
1624
1625 break;
1626 }
1627 }
1628 }
1629}
1630
1631static void cms_error_handler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *text)
1632{
1633 dt_print(DT_DEBUG_COLORPROFILE, "[lcms2] error %d: %s\n", ErrorCode, text);
1634}
1635
1636static gint _sort_profiles(gconstpointer a, gconstpointer b)
1637{
1640
1641 gchar *name_a = g_utf8_casefold(profile_a->name, -1);
1642 gchar *name_b = g_utf8_casefold(profile_b->name, -1);
1643
1644 gint result = g_strcmp0(name_a, name_b);
1645
1646 dt_free(name_a);
1647 dt_free(name_b);
1648
1649 return result;
1650}
1651
1652static GList *load_profile_from_dir(const char *subdir)
1653{
1654 GList *temp_profiles = NULL;
1655 const gchar *d_name;
1656 char datadir[PATH_MAX] = { 0 };
1657 char confdir[PATH_MAX] = { 0 };
1658 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1659 dt_loc_get_datadir(datadir, sizeof(datadir));
1660 char *lang = getenv("LANG");
1661 if(IS_NULL_PTR(lang)) lang = "en_US";
1662
1663 char *dirname = g_build_filename(confdir, "color", subdir, NULL);
1664 if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
1665 {
1666 dt_free(dirname);
1667 dirname = g_build_filename(datadir, "color", subdir, NULL);
1668 }
1669 GDir *dir = g_dir_open(dirname, 0, NULL);
1670 if(dir)
1671 {
1672 while((d_name = g_dir_read_name(dir)))
1673 {
1674 char *filename = g_build_filename(dirname, d_name, NULL);
1675 const char *cc = filename + strlen(filename);
1676 for(; *cc != '.' && cc > filename; cc--)
1677 ;
1678 if(!g_ascii_strcasecmp(cc, ".icc") || !g_ascii_strcasecmp(cc, ".icm"))
1679 {
1680 size_t end;
1681 char *icc_content = dt_read_file(filename, &end);
1682 if(IS_NULL_PTR(icc_content)) goto icc_loading_done;
1683
1684 // TODO: add support for grayscale profiles, then remove _ensure_rgb_profile() from here
1685 cmsHPROFILE tmpprof = _ensure_rgb_profile(cmsOpenProfileFromMem(icc_content, sizeof(char) * end));
1686 if(tmpprof)
1687 {
1689 dt_colorspaces_get_profile_name(tmpprof, lang, lang + 3, prof->name, sizeof(prof->name));
1690 if(prof->name[0] == '\0')
1691 g_strlcpy(prof->name, _("(unknown name)"), sizeof(prof->name));
1692
1693 g_strlcpy(prof->filename, filename, sizeof(prof->filename));
1694 prof->type = DT_COLORSPACE_FILE;
1695 prof->profile = tmpprof;
1696 // these will be set after sorting!
1697 prof->in_pos = -1;
1698 prof->out_pos = -1;
1699 prof->display_pos = -1;
1700 prof->category_pos = -1;
1701 prof->work_pos = -1;
1702 temp_profiles = g_list_prepend(temp_profiles, prof);
1703 }
1704
1705icc_loading_done:
1706 dt_free(icc_content);
1707 }
1708 dt_free(filename);
1709 }
1710 g_dir_close(dir);
1711 temp_profiles = g_list_sort(temp_profiles, _sort_profiles);
1712 }
1713 dt_free(dirname);
1714 return temp_profiles;
1715}
1716
1718{
1719 cmsSetLogErrorHandler(cms_error_handler);
1720
1721 dt_colorspaces_t *res = (dt_colorspaces_t *)calloc(1, sizeof(dt_colorspaces_t));
1722
1724
1725 pthread_rwlock_init(&res->xprofile_lock, NULL);
1726
1727 int in_pos = -1,
1728 out_pos = -1,
1729 display_pos = -1,
1730 category_pos = -1,
1731 work_pos = -1;
1732
1733 // init the category profile with NULL profile, the actual profile must be retrieved dynamically by the caller
1734 res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_WORK, NULL, _("work profile"), -1, -1,
1735 -1, ++category_pos, -1));
1736
1737 res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_EXPORT, NULL, _("export profile"), -1,
1738 -1, -1, ++category_pos, -1));
1739
1740 res->profiles
1741 = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_SOFTPROOF, NULL, _("softproof profile"), -1, -1,
1742 -1, ++category_pos, -1));
1743
1744 // init the display profile with srgb so some stupid code that runs before the real profile could be fetched has something to work with
1745 res->profiles = g_list_append(
1747 _("System display profile (recommended)"), -1, -1, ++display_pos, ++category_pos, -1));
1748
1749 // we want a v4 with parametric curve for input and a v2 with point trc for output
1750 // see http://ninedegreesbelow.com/photography/lcms-make-icc-profiles.html#profile-variants-and-versions
1751 // TODO: what about display?
1752 res->profiles
1754 _("sRGB (e.g. JPG)"), ++in_pos, -1, -1, -1, -1));
1755
1756 res->profiles
1758 _("sRGB"), -1, ++out_pos, ++display_pos,
1759 ++category_pos, ++work_pos));
1760
1761 res->profiles = g_list_append(res->profiles,
1763 _("Adobe RGB (compatible)"), ++in_pos, ++out_pos, ++display_pos,
1764 ++category_pos, ++work_pos));
1765
1766 res->profiles = g_list_append(
1768 _("linear Rec709 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1769 ++work_pos));
1770
1772 _("gamma Rec709 RGB"), ++in_pos, ++out_pos, -1, -1,
1773 ++work_pos));
1774
1776 _("ITU-R BT.1886 (gamma 2.4 Rec709)"), ++in_pos, ++out_pos, -1, -1,
1777 ++work_pos));
1778
1779 res->profiles = g_list_append(
1781 _("linear Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1782 ++work_pos));
1783
1784 res->profiles = g_list_append(
1786 _("PQ Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1787 ++work_pos));
1788
1789 res->profiles = g_list_append(
1791 _("HLG Rec2020 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1792 ++work_pos));
1793
1794 res->profiles = g_list_append(
1796 _("PQ P3 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1797 ++work_pos));
1798
1799 res->profiles = g_list_append(
1801 _("HLG P3 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1802 ++work_pos));
1803
1804 res->profiles = g_list_append(
1806 _("Display P3 RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1807 ++work_pos));
1808
1809 res->profiles = g_list_append(
1811 _("linear ProPhoto RGB"), ++in_pos, ++out_pos, ++display_pos, ++category_pos,
1812 ++work_pos));
1813
1814 res->profiles = g_list_append(
1815 res->profiles,
1817 dt_conf_get_bool("allow_lab_output") ? ++out_pos : -1, -1, -1, -1));
1818
1819 res->profiles = g_list_append(
1821 dt_conf_get_bool("allow_lab_output") ? ++out_pos : -1, -1, -1, -1));
1822
1823 res->profiles = g_list_append(
1825 _("linear infrared BGR"), ++in_pos, -1, -1, -1, -1));
1826
1827 res->profiles
1829 _("BRG (for testing)"), ++in_pos, ++out_pos, ++display_pos,
1830 -1, -1));
1831
1832 // init display profile and softproof/gama checking from conf
1833 res->display_type = dt_conf_get_int("ui_last/color/display_type");
1834 res->softproof_type = dt_conf_get_int("ui_last/color/softproof_type");
1835 const char *tmp = dt_conf_get_string_const("ui_last/color/display_filename");
1836 g_strlcpy(res->display_filename, tmp, sizeof(res->display_filename));
1837 tmp = dt_conf_get_string_const("ui_last/color/softproof_filename");
1838 g_strlcpy(res->softproof_filename, tmp, sizeof(res->softproof_filename));
1839 res->display_intent = dt_conf_get_int("ui_last/color/display_intent");
1840 res->softproof_intent = dt_conf_get_int("ui_last/color/softproof_intent");
1841 res->mode = dt_conf_get_int("ui_last/color/mode");
1842
1843 // sanity checks to ensure the profile filenames are present
1844
1845 if((unsigned int)res->display_type >= DT_COLORSPACE_LAST
1847 && (!res->display_filename[0] || !g_file_test(res->display_filename, G_FILE_TEST_IS_REGULAR))))
1849
1850 if((unsigned int)res->softproof_type >= DT_COLORSPACE_LAST
1852 && (!res->softproof_filename[0] || !g_file_test(res->softproof_filename, G_FILE_TEST_IS_REGULAR))))
1854
1855 // temporary list of profiles to be added, we keep this separate to be able to sort it before adding
1856 GList *temp_profiles;
1857
1858 // read {userconfig,datadir}/color/in/*.icc, in this order.
1859 temp_profiles = load_profile_from_dir("in");
1860 for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1861 {
1863 prof->in_pos = ++in_pos;
1864 }
1865 res->profiles = g_list_concat(res->profiles, temp_profiles);
1866
1867 // read {conf,data}dir/color/out/*.icc
1868 temp_profiles = load_profile_from_dir("out");
1869 for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1870 {
1872 // 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
1873 const cmsColorSpaceSignature color_space = cmsGetColorSpace(prof->profile);
1874 // The histogram profile is used for histogram, clipping indicators and the global color picker.
1875 // Some of these also assume a matrix profile. LUT profiles don't make much sense in these applications
1876 // so filter out any profile that doesn't implement the relative colorimetric intent as a matrix (+ TRC).
1877 // For discussion, see e.g.
1878 // https://github.com/darktable-org/darktable/issues/7660#issuecomment-760143437
1879 // For the working profile we also require a matrix profile.
1880 const gboolean is_valid_matrix_profile
1881 = dt_colorspaces_get_matrix_from_output_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0
1882 && dt_colorspaces_get_matrix_from_input_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0;
1883 prof->out_pos = ++out_pos;
1884 prof->display_pos = ++display_pos;
1885 if(is_valid_matrix_profile)
1886 {
1887 prof->category_pos = ++category_pos;
1888 prof->work_pos = ++work_pos;
1889 }
1890 else
1891 {
1893 "output profile `%s' color space `%c%c%c%c' not supported for work or histogram profile\n",
1894 prof->name, (char)(color_space >> 24), (char)(color_space >> 16), (char)(color_space >> 8),
1895 (char)(color_space));
1896 }
1897 }
1898 res->profiles = g_list_concat(res->profiles, temp_profiles);
1899
1900
1901 if((unsigned int)res->mode > DT_PROFILE_GAMUTCHECK) res->mode = DT_PROFILE_NORMAL;
1902
1904
1905 return res;
1906}
1907
1909{
1910 // remember display profile and softproof/gama checking from conf
1911 dt_conf_set_int("ui_last/color/display_type", self->display_type);
1912 dt_conf_set_int("ui_last/color/softproof_type", self->softproof_type);
1913 dt_conf_set_string("ui_last/color/display_filename", self->display_filename);
1914 dt_conf_set_string("ui_last/color/softproof_filename", self->softproof_filename);
1915 dt_conf_set_int("ui_last/color/display_intent", self->display_intent);
1916 dt_conf_set_int("ui_last/color/softproof_intent", self->softproof_intent);
1917 dt_conf_set_int("ui_last/color/mode", self->mode);
1918
1919 if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
1920 self->transform_srgb_to_display = NULL;
1921
1922 if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
1923 self->transform_adobe_rgb_to_display = NULL;
1924
1925 if(self->transform_display_to_adobe_rgb) cmsDeleteTransform(self->transform_display_to_adobe_rgb);
1926 self->transform_display_to_adobe_rgb = NULL;
1927
1928 if(self->transform_xyz_to_display) cmsDeleteTransform(self->transform_xyz_to_display);
1929 self->transform_xyz_to_display = NULL;
1930
1931 for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
1932 {
1934 if(p) dt_colorspaces_cleanup_profile(p->profile);
1935 }
1936 g_list_free_full(self->profiles, dt_free_gpointer);
1937 self->profiles = NULL;
1938
1939 pthread_rwlock_destroy(&self->xprofile_lock);
1941 dt_free(self->xprofile_data);
1942
1943 dt_free(self);
1944}
1945
1947 const char *filename)
1948{
1949 switch (type)
1950 {
1951 case DT_COLORSPACE_NONE:
1952 return NULL;
1953 case DT_COLORSPACE_FILE:
1954 return filename;
1955 case DT_COLORSPACE_SRGB:
1956 return _("sRGB");
1958 return _("Adobe RGB (compatible)");
1960 return _("linear Rec709 RGB");
1962 return _("linear Rec2020 RGB");
1963 case DT_COLORSPACE_XYZ:
1964 return _("linear XYZ");
1965 case DT_COLORSPACE_LAB:
1966 return _("Lab");
1968 return _("linear infrared BGR");
1970 return _("System display profile (recommended)");
1972 return _("embedded ICC profile");
1974 return _("embedded matrix");
1976 return _("standard color matrix");
1978 return _("enhanced color matrix");
1980 return _("vendor color matrix");
1982 return _("alternate color matrix");
1983 case DT_COLORSPACE_BRG:
1984 return _("BRG (experimental)");
1986 return _("export profile");
1988 return _("softproof profile");
1989 case DT_COLORSPACE_WORK:
1990 return _("work profile");
1992 return _("Not used. Shouldn't be here.");
1994 return _("Rec709 RGB");
1996 return _("linear ProPhoto RGB");
1998 return _("PQ Rec2020");
2000 return _("HLG Rec2020");
2002 return _("PQ P3");
2004 return _("HLG P3");
2006 return _("Display P3");
2008 return _("ITU-R BT.1886");
2009 case DT_COLORSPACE_LAST:
2010 break;
2011 }
2012
2013 return NULL;
2014}
2015
2016#ifdef USE_COLORDGTK
2017static void dt_colorspaces_get_display_profile_colord_callback(GObject *source, GAsyncResult *res, gpointer user_data)
2018{
2019 pthread_rwlock_wrlock(&darktable.color_profiles->xprofile_lock);
2020
2021 int profile_changed = 0;
2022 CdWindow *window = CD_WINDOW(source);
2023 GError *error = NULL;
2024 CdProfile *profile = cd_window_get_profile_finish(window, res, &error);
2025 if(IS_NULL_PTR(error) && !IS_NULL_PTR(profile))
2026 {
2027 const gchar *filename = cd_profile_get_filename(profile);
2028 if(filename)
2029 {
2030 if(g_strcmp0(filename, darktable.color_profiles->colord_profile_file))
2031 {
2032 /* the profile has changed (either because the user changed the colord settings or because we are on a
2033 * different screen now) */
2034 // update darktable.color_profiles->colord_profile_file
2036 darktable.color_profiles->colord_profile_file = g_strdup(filename);
2037
2038 // read the file
2039 guchar *tmp_data = NULL;
2040 gsize size;
2041 g_file_get_contents(filename, (gchar **)&tmp_data, &size, NULL);
2043 || memcmp(darktable.color_profiles->xprofile_data, tmp_data, size) != 0);
2044
2045 if(profile_changed)
2046 {
2047 _update_display_profile(tmp_data, size, NULL, 0);
2049 "[color profile] colord gave us a new screen profile: '%s' (size: %" G_GSIZE_FORMAT ")\n", filename, size);
2050 }
2051 else
2052 {
2053 dt_free(tmp_data);
2054 }
2055 }
2056 }
2057 }
2058 if(profile) g_object_unref(profile);
2059 g_object_unref(window);
2060
2061 pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
2062
2064}
2065#endif
2066
2067#if defined GDK_WINDOWING_X11
2068static int _gtk_get_monitor_num(GdkMonitor *monitor)
2069{
2070 GdkDisplay *display;
2071 int n_monitors, i;
2072
2073 display = gdk_monitor_get_display(monitor);
2074 n_monitors = gdk_display_get_n_monitors(display);
2075 for(i = 0; i < n_monitors; i++)
2076 {
2077 if(gdk_display_get_monitor(display, i) == monitor) return i;
2078 }
2079
2080 return -1;
2081}
2082#endif
2083
2084// Get the display ICC profile of the monitor associated with the widget.
2085// For X display, uses the ICC profile specifications version 0.2 from
2086// http://burtonini.com/blog/computers/xicc
2087// Based on code from Gimp's modules/cdisplay_lcms.c
2089{
2090 if(!dt_control_running()) return;
2091 // make sure that no one gets a broken profile
2092 // FIXME: benchmark if the try is really needed when moving/resizing the window. Maybe we can just lock it
2093 // and block
2094 if(pthread_rwlock_trywrlock(&darktable.color_profiles->xprofile_lock))
2095 return; // we are already updating the profile. Or someone is reading right now. Too bad we can't
2096 // distinguish that. Whatever ...
2097
2098 guint8 *buffer = NULL;
2099 gint buffer_size = 0;
2100 gchar *profile_source = NULL;
2101
2102#if defined GDK_WINDOWING_X11
2103
2104 // we will use the xatom no matter what configured when compiled without colord
2105 gboolean use_xatom = TRUE;
2106#if defined USE_COLORDGTK
2107 gboolean use_colord = TRUE;
2108 const char *display_profile_source = dt_conf_get_string_const("ui_last/display_profile_source");
2109
2110 if(display_profile_source)
2111 {
2112 if(!strcmp(display_profile_source, "xatom"))
2113 use_colord = FALSE;
2114 else if(!strcmp(display_profile_source, "colord"))
2115 use_xatom = FALSE;
2116 }
2117#endif
2118
2119 /* let's have a look at the xatom, just in case ... */
2120 if(use_xatom)
2121 {
2123 GdkWindow *window = gtk_widget_get_window(widget);
2124 GdkScreen *screen = gtk_widget_get_screen(widget);
2125 if(IS_NULL_PTR(screen)) screen = gdk_screen_get_default();
2126
2127 GdkDisplay *display = gtk_widget_get_display(widget);
2128 int monitor = _gtk_get_monitor_num(gdk_display_get_monitor_at_window(display, window));
2129
2130 char *atom_name;
2131 if(monitor > 0)
2132 atom_name = g_strdup_printf("_ICC_PROFILE_%d", monitor);
2133 else
2134 atom_name = g_strdup("_ICC_PROFILE");
2135
2136 profile_source = g_strdup_printf("xatom %s", atom_name);
2137
2138 GdkAtom type = GDK_NONE;
2139 gint format = 0;
2140 gdk_property_get(gdk_screen_get_root_window(screen), gdk_atom_intern(atom_name, FALSE), GDK_NONE, 0,
2141 64 * 1024 * 1024, FALSE, &type, &format, &buffer_size, &buffer);
2142 dt_free(atom_name);
2143 }
2144
2145#ifdef USE_COLORDGTK
2146 /* also try to get the profile from colord. this will set the value asynchronously! */
2147 if(use_colord)
2148 {
2149 CdWindow *window = cd_window_new();
2150 GtkWidget *center_widget = dt_ui_center(darktable.gui->ui);
2151 cd_window_get_profile(window, center_widget, NULL, dt_colorspaces_get_display_profile_colord_callback,
2152 GINT_TO_POINTER(profile_type));
2153 }
2154#endif
2155
2156#elif defined GDK_WINDOWING_QUARTZ
2157#if 0
2158 GtkWidget *widget = (profile_type == DT_COLORSPACE_DISPLAY2) ? darktable.develop->second_window.second_wnd : dt_ui_center(darktable.gui->ui);
2159 GdkScreen *screen = gtk_widget_get_screen(widget);
2160 if(IS_NULL_PTR(screen)) screen = gdk_screen_get_default();
2161 int monitor = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(widget));
2162
2163 CGDirectDisplayID ids[monitor + 1];
2164 uint32_t total_ids;
2165 CMProfileRef prof = NULL;
2166 if(CGGetOnlineDisplayList(monitor + 1, &ids[0], &total_ids) == kCGErrorSuccess && total_ids == monitor + 1)
2167 CMGetProfileByAVID(ids[monitor], &prof);
2168 if(!IS_NULL_PTR(prof))
2169 {
2170 CFDataRef data;
2171 data = CMProfileCopyICCData(NULL, prof);
2172 CMCloseProfile(prof);
2173
2174 UInt8 *tmp_buffer = (UInt8 *)g_malloc(CFDataGetLength(data));
2175 CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), tmp_buffer);
2176
2177 buffer = (guint8 *)tmp_buffer;
2178 buffer_size = CFDataGetLength(data);
2179
2180 CFRelease(data);
2181 }
2182 profile_source = g_strdup("osx color profile api");
2183#endif
2184#elif defined G_OS_WIN32
2185 //HDC hdc = GetDC(NULL);
2187 GdkWindow *window = gtk_widget_get_window(widget);
2188 HWND hwnd = (HWND)gdk_win32_window_get_handle(window); // get window handle
2189 HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); // get monitor handle
2190 if(IS_NULL_PTR(hMonitor)){ return;} //TODO log error
2191 MONITORINFOEX monitorInfo;
2192 monitorInfo.cbSize = sizeof(MONITORINFOEX);
2193 if(!GetMonitorInfoW(hMonitor,(LPMONITORINFO) &monitorInfo)) { return;} //get monitor info , TODO log error
2194 HDC hdc = CreateIC(L"MONITOR",monitorInfo.szDevice,NULL,NULL); // get device-info context of the monitor
2195 if(!IS_NULL_PTR(hdc))
2196 {
2197 DWORD len = 0;
2198 GetICMProfile(hdc, &len, NULL);
2199 wchar_t *wpath = g_new(wchar_t, len);
2200
2201 if(GetICMProfileW(hdc, &len, wpath))
2202 {
2203 gchar *path = g_utf16_to_utf8(wpath, -1, NULL, NULL, NULL);
2204 if(path)
2205 {
2206 gsize size;
2207 g_file_get_contents(path, (gchar **)&buffer, &size, NULL);
2208 buffer_size = size;
2209 dt_free(path);
2210 }
2211 }
2212 dt_free(wpath);
2213 DeleteDC(hdc);
2214 }
2215 profile_source = g_strdup("windows color profile api");
2216#endif
2217
2218 int profile_changed = buffer_size > 0 && (darktable.color_profiles->xprofile_size != buffer_size
2219 || memcmp(darktable.color_profiles->xprofile_data, buffer, buffer_size) != 0);
2220
2221 if(profile_changed)
2222 {
2223 char name[512] = { 0 };
2224 _update_display_profile(buffer, buffer_size, name, sizeof(name));
2225 dt_print(DT_DEBUG_CONTROL, "[color profile] we got a new screen profile `%s' from the %s (size: %d)\n",
2226 *name ? name : "(unknown)", profile_source, buffer_size);
2227 }
2228 else
2229 {
2230 dt_free(buffer);
2231 }
2232 pthread_rwlock_unlock(&darktable.color_profiles->xprofile_lock);
2234 dt_free(profile_source);
2235}
2236
2237static gboolean _colorspaces_is_base_name(const char *profile)
2238{
2239 const char *f = profile;
2240 while(*f != '\0')
2241 {
2242 if(*f == '/' || *f == '\\') return FALSE;
2243 f++;
2244 }
2245 return TRUE;
2246}
2247
2248static const char *_colorspaces_get_base_name(const char *profile)
2249{
2250 const char* f = profile + strlen(profile);
2251 for (; f >= profile; f--)
2252 {
2253 if(*f == '/' || *f == '\\')
2254 return ++f; // path separator found - return the filename only, without the leading separator
2255 }
2256 return f; // no separator found - consider profile_name to be a "base" one
2257}
2258
2259gboolean dt_colorspaces_is_profile_equal(const char *fullname, const char *filename)
2260{
2261 // for backward compatibility we need to also ensure that we check
2262 // for basename, indeed filename parameter may be in fact just a
2263 // basename as recorded in an iop.
2264 return _colorspaces_is_base_name(filename)
2265 ? !strcmp(_colorspaces_get_base_name(fullname), filename)
2266 : !strcmp(_colorspaces_get_base_name(fullname), _colorspaces_get_base_name(filename));
2267}
2268
2270{
2271 switch(cicp->color_primaries)
2272 {
2273 /* Give up immediately if unspecified */
2277 return DT_COLORSPACE_NONE;
2278 break; /* unspecified */
2279
2280 /* REC709 */
2282
2283 switch(cicp->transfer_characteristics)
2284 {
2285 /* SRGB */
2287
2288 switch(cicp->matrix_coefficients)
2289 {
2290 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2293 case DT_CICP_MATRIX_COEFFICIENTS_REC601: /* support equivalents just in case of mistagging */
2294 case DT_CICP_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL: /* support incorrectly tagged files */
2296 return DT_COLORSPACE_SRGB;
2297 default:
2298 break;
2299 }
2300
2301 break; /* SRGB */
2302
2303 /* REC709 */
2305 case DT_CICP_TRANSFER_CHARACTERISTICS_REC601: /* support equivalents just in case of mistagging */
2306 case DT_CICP_TRANSFER_CHARACTERISTICS_REC2020_10B: /* support equivalents just in case of mistagging */
2307 case DT_CICP_TRANSFER_CHARACTERISTICS_REC2020_12B: /* support equivalents just in case of mistagging */
2308
2309 switch(cicp->matrix_coefficients)
2310 {
2311 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2315 return DT_COLORSPACE_REC709;
2316 default:
2317 break;
2318 }
2319
2320 break; /* REC709 */
2321
2322 /* LINEAR REC709 */
2324
2325 switch(cicp->matrix_coefficients)
2326 {
2327 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2332 default:
2333 break;
2334 }
2335
2336 break; /* LINEAR REC709 */
2337
2338 default:
2339 break;
2340 }
2341
2342 break; /* REC709 */
2343
2344 /* REC2020 */
2346
2347 switch(cicp->transfer_characteristics)
2348 {
2349 /* LINEAR REC2020 */
2351
2352 switch(cicp->matrix_coefficients)
2353 {
2354 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2359 default:
2360 break;
2361 }
2362
2363 break; /* LINEAR REC2020 */
2364
2365 /* PQ REC2020 */
2367
2368 switch(cicp->matrix_coefficients)
2369 {
2370 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2375 default:
2376 break;
2377 }
2378
2379 break; /* PQ REC2020 */
2380
2381 /* HLG REC2020 */
2383
2384 switch(cicp->matrix_coefficients)
2385 {
2386 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2391 default:
2392 break;
2393 }
2394
2395 break; /* HLG REC2020 */
2396
2397 default:
2398 break;
2399 }
2400
2401 break; /* REC2020 */
2402
2403 /* P3 */
2405
2406 switch(cicp->transfer_characteristics)
2407 {
2408 /* PQ P3 */
2410
2411 switch(cicp->matrix_coefficients)
2412 {
2413 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2419 return DT_COLORSPACE_PQ_P3;
2420 default:
2421 break;
2422 }
2423
2424 break; /* PQ P3 */
2425
2426 /* HLG P3 */
2428
2429 switch(cicp->matrix_coefficients)
2430 {
2431 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2437 return DT_COLORSPACE_HLG_P3;
2438 default:
2439 break;
2440 }
2441
2442 break; /* HLG P3 */
2443
2444 /* Display P3 */
2446
2447 switch(cicp->matrix_coefficients)
2448 {
2449 case DT_CICP_MATRIX_COEFFICIENTS_IDENTITY: /* support RGB (4:4:4 or lossless) */
2456 default:
2457 break;
2458 }
2459
2460 break; /* Display P3 */
2461
2462 default:
2463 break;
2464 }
2465
2466 break; /* P3 */
2467
2468 /* XYZ */
2470
2471 switch(cicp->transfer_characteristics)
2472 {
2473 /* LINEAR XYZ */
2475
2476 switch(cicp->matrix_coefficients)
2477 {
2480 return DT_COLORSPACE_XYZ;
2481 default:
2482 break;
2483 }
2484
2485 break; /* LINEAR XYZ */
2486
2487 default:
2488 break;
2489 }
2490
2491 break; /* XYZ */
2492
2493 default:
2494 break;
2495 }
2496
2497 if(!IS_NULL_PTR(filename))
2498 dt_print(DT_DEBUG_IMAGEIO, "[colorin] unsupported CICP color profile for `%s': %d/%d/%d\n", filename,
2500
2501 return DT_COLORSPACE_NONE;
2502}
2503
2506 const char *filename,
2508{
2509 for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
2510 {
2512 if(((direction & DT_PROFILE_DIRECTION_IN && p->in_pos > -1)
2513 || (direction & DT_PROFILE_DIRECTION_OUT && p->out_pos > -1)
2514 || (direction & DT_PROFILE_DIRECTION_WORK && p->work_pos > -1)
2515 || (direction & DT_PROFILE_DIRECTION_DISPLAY && p->display_pos > -1))
2516 && (p->type == type
2517 && (type != DT_COLORSPACE_FILE || dt_colorspaces_is_profile_equal(p->filename, filename))))
2518 {
2519 return p;
2520 }
2521 }
2522
2523 return NULL;
2524}
2525
2532
2533// Copied from dcraw's pseudoinverse()
2535static void dt_colorspaces_pseudoinverse(double (*in)[3], double (*out)[3], int size)
2536{
2537 double work[3][6];
2538
2539 for(int i = 0; i < 3; i++) {
2540 for(int j = 0; j < 6; j++)
2541 work[i][j] = j == i+3;
2542 for(int j = 0; j < 3; j++)
2543 for(int k = 0; k < size; k++)
2544 work[i][j] += in[k][i] * in[k][j];
2545 }
2546 for(int i = 0; i < 3; i++) {
2547 double num = work[i][i];
2548 for(int j = 0; j < 6; j++)
2549 work[i][j] /= num;
2550 for(int k = 0; k < 3; k++) {
2551 if(k==i) continue;
2552 num = work[k][i];
2553 for(int j = 0; j < 6; j++)
2554 work[k][j] -= work[i][j] * num;
2555 }
2556 }
2557 for(int i = 0; i < size; i++)
2558 for(int j = 0; j < 3; j++)
2559 {
2560 out[i][j] = 0.0f;
2561 for(int k = 0; k < 3; k++)
2562 out[i][j] += work[j][k+3] * in[i][k];
2563 }
2564}
2565
2566int dt_colorspaces_conversion_matrices_xyz(const float adobe_XYZ_to_CAM[4][3], float in_XYZ_to_CAM[9], double XYZ_to_CAM[4][3], double CAM_to_XYZ[3][4])
2567{
2568 if(!isnan(in_XYZ_to_CAM[0]))
2569 {
2570 for(int i = 0; i < 9; i++)
2571 XYZ_to_CAM[i/3][i%3] = (double) in_XYZ_to_CAM[i];
2572 for(int i = 0; i < 3; i++)
2573 XYZ_to_CAM[3][i] = 0.0f;
2574 }
2575 else
2576 {
2577 if(isnan(adobe_XYZ_to_CAM[0][0]))
2578 return FALSE;
2579
2580 for(int i = 0; i < 4; i++)
2581 for(int j = 0; j < 3; j++)
2582 XYZ_to_CAM[i][j] = (double)adobe_XYZ_to_CAM[i][j];
2583 }
2584
2585 // Invert the matrix
2586 double inverse[4][3];
2587 dt_colorspaces_pseudoinverse (XYZ_to_CAM, inverse, 4);
2588 for(int i = 0; i < 3; i++)
2589 for(int j = 0; j < 4; j++)
2590 CAM_to_XYZ[i][j] = inverse[j][i];
2591
2592 return TRUE;
2593}
2594
2595// Converted from dcraw's cam_xyz_coeff()
2596// Build the camera RGB to sRGB conversion matrix
2598int dt_colorspaces_conversion_matrices_rgb(const float adobe_XYZ_to_CAM[4][3],
2599 double out_RGB_to_CAM[4][3], double out_CAM_to_RGB[3][4],
2600 const float *embedded_matrix,
2601 double mul[4])
2602{
2603 double RGB_to_CAM[4][3];
2604
2605 float XYZ_to_CAM[4][3];
2606 XYZ_to_CAM[0][0] = NAN;
2607
2608 if(IS_NULL_PTR(embedded_matrix) || isnan(embedded_matrix[0]))
2609 {
2610 for(int k=0; k<4; k++)
2611 for(int i=0; i<3; i++)
2612 XYZ_to_CAM[k][i] = adobe_XYZ_to_CAM[k][i];
2613 }
2614 else
2615 {
2616 // keep in sync with reload_defaults from colorin.c
2617 // embedded matrix is used with higher priority than standard one
2618 XYZ_to_CAM[0][0] = embedded_matrix[0];
2619 XYZ_to_CAM[0][1] = embedded_matrix[1];
2620 XYZ_to_CAM[0][2] = embedded_matrix[2];
2621
2622 XYZ_to_CAM[1][0] = embedded_matrix[3];
2623 XYZ_to_CAM[1][1] = embedded_matrix[4];
2624 XYZ_to_CAM[1][2] = embedded_matrix[5];
2625
2626 XYZ_to_CAM[2][0] = embedded_matrix[6];
2627 XYZ_to_CAM[2][1] = embedded_matrix[7];
2628 XYZ_to_CAM[2][2] = embedded_matrix[8];
2629 }
2630
2631 if(isnan(XYZ_to_CAM[0][0]))
2632 return FALSE;
2633
2634 const double RGB_to_XYZ[3][3] = {
2635 // sRGB D65
2636 { 0.412453, 0.357580, 0.180423 },
2637 { 0.212671, 0.715160, 0.072169 },
2638 { 0.019334, 0.119193, 0.950227 },
2639 };
2640
2641 // Multiply RGB matrix
2642 for(int i = 0; i < 4; i++)
2643 for(int j = 0; j < 3; j++)
2644 {
2645 RGB_to_CAM[i][j] = 0.0f;
2646 for(int k = 0; k < 3; k++)
2647 RGB_to_CAM[i][j] += XYZ_to_CAM[i][k] * RGB_to_XYZ[k][j];
2648 }
2649
2650 // Normalize cam_rgb so that cam_rgb * (1,1,1) is (1,1,1,1)
2651 for(int i = 0; i < 4; i++) {
2652 double num = 0.0f;
2653 for(int j = 0; j < 3; j++)
2654 num += RGB_to_CAM[i][j];
2655 for(int j = 0; j < 3; j++)
2656 RGB_to_CAM[i][j] /= num;
2657 if(mul) mul[i] = 1.0f / num;
2658 }
2659
2660 if(out_RGB_to_CAM)
2661 for(int i = 0; i < 4; i++)
2662 for(int j = 0; j < 3; j++)
2663 out_RGB_to_CAM[i][j] = RGB_to_CAM[i][j];
2664
2665 if(out_CAM_to_RGB)
2666 {
2667 // Invert the matrix
2668 double inverse[4][3];
2669 dt_colorspaces_pseudoinverse (RGB_to_CAM, inverse, 4);
2670 for(int i = 0; i < 3; i++)
2671 for(int j = 0; j < 4; j++)
2672 out_CAM_to_RGB[i][j] = inverse[j][i];
2673 }
2674
2675 return TRUE;
2676}
2677
2678void dt_colorspaces_cygm_apply_coeffs_to_rgb(float *out, const float *in, int num, double RGB_to_CAM[4][3],
2679 double CAM_to_RGB[3][4], dt_aligned_pixel_t coeffs)
2680{
2681 // Create the CAM to RGB with applied WB matrix
2682 double CAM_to_RGB_WB[3][4];
2683 for (int a=0; a<3; a++)
2684 for (int b=0; b<4; b++)
2685 CAM_to_RGB_WB[a][b] = CAM_to_RGB[a][b] * coeffs[b];
2686
2687 // Create the RGB->RGB+WB matrix
2688 double RGB_to_RGB_WB[3][3];
2689 for (int a=0; a<3; a++)
2690 for (int b=0; b<3; b++) {
2691 RGB_to_RGB_WB[a][b] = 0.0f;
2692 for (int c=0; c<4; c++)
2693 RGB_to_RGB_WB[a][b] += CAM_to_RGB_WB[a][c] * RGB_to_CAM[c][b];
2694 }
2696 for(int i = 0; i < num; i++)
2697 {
2698 const float *inpos = &in[i*4];
2699 float *outpos = &out[i*4];
2700 outpos[0]=outpos[1]=outpos[2] = 0.0f;
2701 for (int a=0; a<3; a++)
2702 for (int b=0; b<3; b++)
2703 outpos[a] += RGB_to_RGB_WB[a][b] * inpos[b];
2704 }
2705}
2706
2708void dt_colorspaces_cygm_to_rgb(float *out, int num, double CAM_to_RGB[3][4])
2709{
2711 for(int i = 0; i < num; i++)
2712 {
2713 float *in = &out[i*4];
2714 dt_aligned_pixel_t o = {0.0f,0.0f,0.0f};
2715 for(int c = 0; c < 3; c++)
2716 for(int k = 0; k < 4; k++)
2717 o[c] += CAM_to_RGB[c][k] * in[k];
2718 for(int c = 0; c < 3; c++)
2719 in[c] = o[c];
2720 }
2721}
2722
2723void dt_colorspaces_rgb_to_cygm(float *out, int num, double RGB_to_CAM[4][3])
2724{
2726 for(int i = 0; i < num; i++)
2727 {
2728 float *in = &out[i*3];
2729 dt_aligned_pixel_t o = {0.0f,0.0f,0.0f,0.0f};
2730 for(int c = 0; c < 4; c++)
2731 for(int k = 0; k < 3; k++)
2732 o[c] += RGB_to_CAM[c][k] * in[k];
2733 for(int c = 0; c < 4; c++)
2734 in[c] = o[c];
2735 }
2736}
2737
2738// clang-format off
2739// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2740// vim: shiftwidth=2 expandtab tabstop=2 cindent
2741// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2742// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:482
static void profile_changed(GtkWidget *widget, gpointer user_data)
Definition colorin.c:511
static dt_profiled_colormatrix_t dt_profiled_colormatrices[]
static dt_profiled_colormatrix_t dt_vendor_colormatrices[]
static dt_profiled_colormatrix_t dt_alternate_colormatrices[]
static const int dt_vendor_colormatrix_cnt
static const int dt_alternate_colormatrix_cnt
static const int dt_profiled_colormatrix_cnt
static cmsHPROFILE dt_colorspaces_create_gamma_rec709_rgb_profile(void)
static const cmsCIExyYTRIPLE Rec2020_Primaries
static const char * _colorspaces_get_base_name(const char *profile)
cmsHPROFILE dt_colorspaces_create_xyzimatrix_profile(float mat[3][3])
static cmsHPROFILE dt_colorspaces_create_linear_rec709_rgb_profile(void)
#define generate_mat3inv_body(c_type, A, B)
#define B(y, x)
__DT_CLONE_TARGETS__ int dt_colorspaces_conversion_matrices_rgb(const float adobe_XYZ_to_CAM[4][3], double out_RGB_to_CAM[4][3], double out_CAM_to_RGB[3][4], const float *embedded_matrix, double mul[4])
#define A(y, x)
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_direction_t direction)
static cmsHPROFILE dt_colorspaces_create_itur_bt1886_rgb_profile(void)
void dt_colorspaces_rgb_to_cygm(float *out, int num, double RGB_to_CAM[4][3])
dt_colorspaces_color_profile_type_t dt_colorspaces_get_input_profile_from_image(int32_t imgid, dt_colorspaces_color_profile_type_t requested, cmsHPROFILE *output, gboolean *new_profile)
Resolve an embedded/matrix input profile for a given image, honoring the requested type when possible...
static const dt_colorspaces_color_profile_t * _get_profile(dt_colorspaces_t *self, dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_direction_t direction)
static const cmsCIExyYTRIPLE ProPhoto_Primaries
void rgb2hsl(const dt_aligned_pixel_t rgb, float *h, float *s, float *l)
static cmsHPROFILE dt_colorspaces_create_lab_profile()
static void _update_display_profile(guchar *tmp_data, gsize size, char *name, size_t name_size)
static cmsHPROFILE dt_colorspaces_create_pq_rec2020_rgb_profile(void)
static const cmsCIExyY D65xyY
static cmsHPROFILE dt_colorspaces_create_xyzmatrix_profile(const float mat[3][3])
static const cmsCIExyYTRIPLE Rec709_Primaries
const dt_colorspaces_color_profile_t * _build_embedded_profile(const int32_t imgid, dt_colorspaces_color_profile_type_t *type)
const dt_colorspaces_color_profile_t * dt_colorspaces_get_output_profile(const int32_t imgid, dt_colorspaces_color_profile_type_t *over_type, const char *over_filename)
__DT_CLONE_TARGETS__ void dt_colorspaces_transform_rgba_float_image(const cmsHTRANSFORM transform, const float *image_in, float *image_out, const int width, const int height)
int dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg, float *lutb, const int lutsize)
cmsCIEXYZTRIPLE Rec709_Primaries_Prequantized
void dt_colorspaces_update_display_transforms()
static cmsHPROFILE dt_colorspaces_create_linear_infrared_profile(void)
static cmsHPROFILE dt_colorspaces_create_hlg_p3_rgb_profile(void)
static cmsHPROFILE _create_lcms_profile(const char *desc, const char *dmdd, const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsToneCurve *trc, gboolean v2)
static dt_colorspaces_color_profile_t * _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)
static cmsHPROFILE dt_colorspaces_create_srgb_profile()
static cmsHPROFILE dt_colorspaces_create_hlg_rec2020_rgb_profile(void)
const cmsHPROFILE dt_colorspaces_get_embedded_profile(const int32_t imgid, dt_colorspaces_color_profile_type_t *type, gboolean *new_profile)
static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
static cmsHPROFILE dt_colorspaces_create_display_p3_rgb_profile(void)
cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
static double _PQ_fct(double x)
int dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg, float *lutb, const int lutsize)
static void cms_error_handler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *text)
static cmsHPROFILE dt_colorspaces_create_linear_prophoto_rgb_profile(void)
static gboolean _colorspaces_is_base_name(const char *profile)
int mat3inv(float *const dst, const float *const src)
static __DT_CLONE_TARGETS__ void dt_colorspaces_pseudoinverse(double(*in)[3], double(*out)[3], int size)
void dt_colorspaces_cleanup(dt_colorspaces_t *self)
cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
int dt_colorspaces_conversion_matrices_xyz(const float adobe_XYZ_to_CAM[4][3], float in_XYZ_to_CAM[9], double XYZ_to_CAM[4][3], double CAM_to_XYZ[3][4])
__DT_CLONE_TARGETS__ void dt_colorspaces_cygm_to_rgb(float *out, int num, double CAM_to_RGB[3][4])
static gint _sort_profiles(gconstpointer a, gconstpointer b)
static const cmsCIExyY D50xyY
void dt_colorspaces_cleanup_profile(cmsHPROFILE p)
dt_colorspaces_color_profile_type_t dt_image_find_best_color_profile(int32_t imgid, cmsHPROFILE *output, gboolean *new_profile)
Best effort to find a suitable (input) color profile for a given image, using embedded ICC or EXIF wh...
const dt_colorspaces_color_profile_t * dt_colorspaces_get_work_profile(const int32_t imgid)
static cmsHPROFILE dt_colorspaces_create_adobergb_profile(void)
static cmsHPROFILE dt_colorspaces_create_srgb_profile_v4()
static cmsToneCurve * _colorspaces_create_transfer(int32_t size, double(*fct)(double))
dt_colorspaces_t * dt_colorspaces_init()
cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
static cmsHPROFILE dt_colorspaces_create_linear_rec2020_rgb_profile(void)
static cmsHPROFILE dt_colorspaces_create_pq_p3_rgb_profile(void)
const char * dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type, const char *filename)
static GList * load_profile_from_dir(const char *subdir)
static void _update_display_transforms(dt_colorspaces_t *self)
static const cmsCIExyYTRIPLE P3_Primaries
static cmsHPROFILE _colorspaces_create_srgb_profile(gboolean v2)
void dt_colorspaces_get_profile_name(cmsHPROFILE p, const char *language, const char *country, char *name, size_t len)
static const cmsCIExyYTRIPLE sRGB_Primaries
static __DT_CLONE_TARGETS__ int dt_colorspaces_get_matrix_from_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg, float *lutb, const int lutsize, const int input)
void dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type)
cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
static const cmsCIExyYTRIPLE Adobe_Primaries
void hsl2rgb(dt_aligned_pixel_t rgb, float h, float s, float l)
static const cmsCIEXYZ d65
gboolean dt_colorspaces_is_profile_equal(const char *fullname, const char *filename)
static cmsHPROFILE _ensure_rgb_profile(cmsHPROFILE profile)
dt_colorspaces_color_profile_type_t dt_colorspaces_cicp_to_type(const dt_colorspaces_cicp_t *cicp, const char *filename)
void dt_colorspaces_transform_rgba8_to_bgra8(const cmsHTRANSFORM transform, const uint8_t *image_in, uint8_t *image_out, const int width, const int height)
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], dt_aligned_pixel_t coeffs)
int mat3inv_float(float *const dst, const float *const src)
static double _HLG_fct(double x)
void dt_colorspaces_transform_rgba_float_row(const cmsHTRANSFORM transform, const float *in, float *out, const int width)
static cmsHPROFILE dt_colorspaces_create_brg_profile()
static void _compute_prequantized_primaries(const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsCIEXYZTRIPLE *primaries_prequantized)
#define TYPE_XYZA_FLT
Definition colorspaces.h:53
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_ADOBERGB
Definition colorspaces.h:85
@ DT_COLORSPACE_PROPHOTO_RGB
@ DT_COLORSPACE_EMBEDDED_MATRIX
Definition colorspaces.h:93
@ DT_COLORSPACE_EMBEDDED_ICC
Definition colorspaces.h:92
@ DT_COLORSPACE_FILE
Definition colorspaces.h:83
@ DT_COLORSPACE_DISPLAY
Definition colorspaces.h:91
@ DT_COLORSPACE_SRGB
Definition colorspaces.h:84
@ DT_COLORSPACE_INFRARED
Definition colorspaces.h:90
@ DT_COLORSPACE_EXPORT
Definition colorspaces.h:99
@ DT_COLORSPACE_LAB
Definition colorspaces.h:89
@ DT_COLORSPACE_REC709
@ DT_COLORSPACE_HLG_P3
@ DT_COLORSPACE_PQ_P3
@ DT_COLORSPACE_PQ_REC2020
@ DT_COLORSPACE_DISPLAY2
@ DT_COLORSPACE_NONE
Definition colorspaces.h:82
@ DT_COLORSPACE_HLG_REC2020
@ DT_COLORSPACE_WORK
@ DT_COLORSPACE_LAST
@ DT_COLORSPACE_ENHANCED_MATRIX
Definition colorspaces.h:95
@ DT_COLORSPACE_LIN_REC2020
Definition colorspaces.h:87
@ DT_COLORSPACE_DISPLAY_P3
@ DT_COLORSPACE_STANDARD_MATRIX
Definition colorspaces.h:94
@ DT_COLORSPACE_XYZ
Definition colorspaces.h:88
@ DT_COLORSPACE_BRG
Definition colorspaces.h:98
@ DT_COLORSPACE_VENDOR_MATRIX
Definition colorspaces.h:96
@ DT_COLORSPACE_SOFTPROOF
@ DT_COLORSPACE_ITUR_BT1886
@ DT_COLORSPACE_LIN_REC709
Definition colorspaces.h:86
@ DT_COLORSPACE_ALTERNATE_MATRIX
Definition colorspaces.h:97
dt_colorspaces_profile_direction_t
@ DT_PROFILE_DIRECTION_IN
@ DT_PROFILE_DIRECTION_OUT
@ DT_PROFILE_DIRECTION_DISPLAY
@ DT_PROFILE_DIRECTION_WORK
@ DT_CICP_TRANSFER_CHARACTERISTICS_UNSPECIFIED
@ DT_CICP_TRANSFER_CHARACTERISTICS_LINEAR
@ DT_CICP_TRANSFER_CHARACTERISTICS_HLG
@ DT_CICP_TRANSFER_CHARACTERISTICS_REC2020_12B
@ DT_CICP_TRANSFER_CHARACTERISTICS_REC709
@ DT_CICP_TRANSFER_CHARACTERISTICS_PQ
@ DT_CICP_TRANSFER_CHARACTERISTICS_REC2020_10B
@ DT_CICP_TRANSFER_CHARACTERISTICS_REC601
@ DT_CICP_TRANSFER_CHARACTERISTICS_SRGB
@ DT_PROFILE_GAMUTCHECK
@ DT_PROFILE_NORMAL
@ DT_CICP_COLOR_PRIMARIES_REC2020
@ DT_CICP_COLOR_PRIMARIES_UNSPECIFIED
@ DT_CICP_COLOR_PRIMARIES_P3
@ DT_CICP_COLOR_PRIMARIES_REC709
@ DT_CICP_COLOR_PRIMARIES_XYZ
@ DT_CICP_MATRIX_COEFFICIENTS_REC601
@ DT_CICP_MATRIX_COEFFICIENTS_REC2020_NCL
@ DT_CICP_MATRIX_COEFFICIENTS_UNSPECIFIED
@ DT_CICP_MATRIX_COEFFICIENTS_IDENTITY
@ DT_CICP_MATRIX_COEFFICIENTS_SYCC
@ DT_CICP_MATRIX_COEFFICIENTS_REC709
@ DT_CICP_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL
static dt_aligned_pixel_t rgb
const dt_aligned_pixel_t f
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
const dt_colormatrix_t matrix
const float delta
gboolean dt_image_is_matrix_correction_supported(const dt_image_t *img)
gboolean dt_image_is_monochrome(const dt_image_t *img)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
int type
char * name
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
int dt_control_running()
Definition control.c:423
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_CONTROL
Definition darktable.h:716
@ DT_DEBUG_COLORPROFILE
Definition darktable.h:744
@ DT_DEBUG_IMAGEIO
Definition darktable.h:733
@ DT_DEBUG_DEV
Definition darktable.h:717
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
static const dt_aligned_pixel_simd_t sign
Definition darktable.h:551
#define dt_free(ptr)
Definition darktable.h:456
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define PATH_MAX
Definition darktable.h:1062
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
@ DT_IMAGE_COLORSPACE_ADOBE_RGB
Definition image.h:181
@ DT_IMAGE_COLORSPACE_SRGB
Definition image.h:180
@ DT_IMAGE_4BAYER
Definition image.h:127
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
void dt_image_cache_write_release(dt_image_cache_t *cache, dt_image_t *img, dt_image_cache_write_mode_t mode)
@ DT_IMAGE_CACHE_RELAXED
Definition image_cache.h:51
int dt_imageio_avif_read_profile(const char *filename, uint8_t **out, dt_colorspaces_cicp_t *cicp)
int dt_imageio_heif_read_profile(const char *filename, uint8_t **out, dt_colorspaces_cicp_t *cicp)
int dt_imageio_j2k_read_profile(const char *filename, uint8_t **out)
int dt_imageio_jpeg_read_profile(dt_imageio_jpeg_t *jpg, uint8_t **out)
int dt_imageio_jpeg_read_header(const char *filename, dt_imageio_jpeg_t *jpg)
int dt_imageio_png_read_profile(const char *filename, uint8_t **out)
int dt_imageio_tiff_read_profile(const char *filename, uint8_t **out)
struct dt_iop_tonecurve_params_t preset
static const float x
const float const int lutsize
float *const restrict const size_t k
#define R
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:36
size_t size
Definition mipmap_cache.c:3
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
float dt_aligned_pixel_t[4]
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_CONTROL_PROFILE_CHANGED
This signal is raised when the screen profile has changed no param, no returned value.
Definition signal.h:236
struct _GtkWidget GtkWidget
Definition splash.h:29
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_colorspaces_t * color_profiles
Definition darktable.h:788
GList * iop
Definition darktable.h:761
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_image_cache_t * image_cache
Definition darktable.h:777
struct dt_develop_t * develop
Definition darktable.h:770
dt_colorspaces_cicp_matrix_coefficients_t matrix_coefficients
dt_colorspaces_cicp_transfer_characteristics_t transfer_characteristics
dt_colorspaces_cicp_color_primaries_t color_primaries
dt_colorspaces_color_profile_type_t type
cmsHTRANSFORM transform_adobe_rgb_to_display
dt_colorspaces_color_profile_type_t softproof_type
gchar * colord_profile_file
cmsHTRANSFORM transform_display_to_adobe_rgb
cmsHTRANSFORM transform_srgb_to_display
cmsHTRANSFORM transform_xyz_to_display
pthread_rwlock_t xprofile_lock
dt_colorspaces_color_mode_t mode
char softproof_filename[512]
uint8_t * xprofile_data
dt_colorspaces_color_profile_type_t display_type
char display_filename[512]
dt_iop_color_intent_t softproof_intent
dt_iop_color_intent_t display_intent
dt_ui_t * ui
Definition gtk.h:164
int32_t flags
Definition image.h:319
uint32_t profile_size
Definition image.h:341
float d65_color_matrix[9]
Definition image.h:339
dt_image_colorspace_t colorspace
Definition image.h:342
float adobe_XYZ_to_CAM[4][3]
Definition image.h:362
uint8_t * profile
Definition image.h:340
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
char * dt_read_file(const char *const filename, size_t *filesize)
Definition utility.c:893