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
54#include "common/paths.h" // DT_PATH_MAX
55#include "colorprofiles/iop_profile.h" // dt_colorspaces_invalidate_display_profile_memo()
56
57#include <stddef.h> // offsetof(), for the startup self-test
58
59/* dt_iop_color_intent_t is spelled with literal values in profile_types.h so that header
60 * needs no <lcms2.h>. The values are fixed by the ICC specification and are serialised into
61 * iop params, so they cannot change on either side -- but this is the one place that sees
62 * both definitions, so it is the place to say so out loud. */
63_Static_assert(DT_INTENT_PERCEPTUAL == INTENT_PERCEPTUAL, "ICC intent renumbered by lcms2");
64_Static_assert(DT_INTENT_RELATIVE_COLORIMETRIC == INTENT_RELATIVE_COLORIMETRIC, "ICC intent renumbered by lcms2");
65_Static_assert(DT_INTENT_SATURATION == INTENT_SATURATION, "ICC intent renumbered by lcms2");
66_Static_assert(DT_INTENT_ABSOLUTE_COLORIMETRIC == INTENT_ABSOLUTE_COLORIMETRIC, "ICC intent renumbered by lcms2");
70#include "math/matrices.h"
71#include "common/utility.h"
72#include "common/conf.h"
73#include "common/logging.h"
74
75#include <strings.h>
76
77#ifdef USE_COLORDGTK
78#include "colord-gtk.h"
79#endif
80
81#ifdef _WIN32
82#include <dwmapi.h>
83#include <gdk/gdkwin32.h>
84#endif
85
88#include <glib/gi18n.h>
89
90#if 0
91#include <ApplicationServices/ApplicationServices.h>
92#include <Carbon/Carbon.h>
93#include <CoreServices/CoreServices.h>
94#endif
95
96/* The module's single instance. Private: nothing outside src/colorprofiles/ names it. */
98
100 cmsHPROFILE profile, const char *name,
102
103static const cmsCIEXYZ d65 = {0.95045471, 1.00000000, 1.08905029};
104
105//D65 (sRGB, AdobeRGB, Rec2020)
106static const cmsCIExyY D65xyY = {0.312700492, 0.329000939, 1.0};
107
108//D60
109//static const cmsCIExyY d60 = {0.32168, 0.33767, 1.0};
110
111//D50 (ProPhoto RGB)
112static const cmsCIExyY D50xyY = {0.3457, 0.3585, 1.0};
113
114// D65:
115static const cmsCIExyYTRIPLE sRGB_Primaries = {
116 {0.6400, 0.3300, 1.0}, // red
117 {0.3000, 0.6000, 1.0}, // green
118 {0.1500, 0.0600, 1.0} // blue
119};
120
121// D65:
122static const cmsCIExyYTRIPLE Rec2020_Primaries = {
123 {0.7080, 0.2920, 1.0}, // red
124 {0.1700, 0.7970, 1.0}, // green
125 {0.1310, 0.0460, 1.0} // blue
126};
127
128// D65:
129static const cmsCIExyYTRIPLE Rec709_Primaries = {
130 {0.6400, 0.3300, 1.0}, // red
131 {0.3000, 0.6000, 1.0}, // green
132 {0.1500, 0.0600, 1.0} // blue
133};
134
135// D65:
136static const cmsCIExyYTRIPLE Adobe_Primaries = {
137 {0.6400, 0.3300, 1.0}, // red
138 {0.2100, 0.7100, 1.0}, // green
139 {0.1500, 0.0600, 1.0} // blue
140};
141
142// D65:
143static const cmsCIExyYTRIPLE P3_Primaries = {
144 {0.680, 0.320, 1.0}, // red
145 {0.265, 0.690, 1.0}, // green
146 {0.150, 0.060, 1.0} // blue
147};
148
149// https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space
150// D50:
151static const cmsCIExyYTRIPLE ProPhoto_Primaries = {
152 /* x, y, Y */
153 { 0.734699, 0.265301, 1.0000 }, /* red */
154 { 0.159597, 0.840403, 1.0000 }, /* green */
155 { 0.036598, 0.000105, 1.0000 }, /* blue */
156};
157
159
160/* Someone to tell when the display profile changes. The application puts it on its signal bus;
161 * this module has no bus and no business knowing there is a control loop. Unregistered, the
162 * notification is dropped -- correct for a headless run, where nothing is watching a monitor. */
164
165/* Defined next to the counter it advances, in the settings block below. */
166static void _advance_settings_generation(void);
167
172
173static void _notify_profile_changed(void)
174{
175 /* The monitor profile just changed, so anything derived from the old one is stale.
176 * Nothing dropped the memoised DISPLAY entry before this, so a session kept the previous
177 * monitor's matrices and tone curves indefinitely -- silently, since every hash and ROI
178 * in the chain stayed consistent. */
180
181 /* The DISPLAY entry's identity -- DT_COLORSPACE_DISPLAY, no filename -- is exactly what it
182 * was a moment ago; only the bytes behind that name changed. So a consumer keyed on the
183 * profile's NAME cannot tell that anything happened, and would go on serving pixels rendered
184 * through the previous monitor's profile. The generation is the one thing that can say
185 * "same name, different profile", which is why a prepared conversion folds it into its
186 * identity (colorprofiles/conversion.c) and why the counter is advanced here and not only
187 * by the setters. */
189
191}
192
193#define generate_mat3inv_body(c_type, A, B) \
194 int mat3inv_##c_type(c_type *const dst, const c_type *const src) \
195 { \
196 \
197 const c_type det = A(1, 1) * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3)) \
198 - A(2, 1) * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3)) \
199 + A(3, 1) * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3)); \
200 \
201 const c_type epsilon = 1e-7f; \
202 if(fabs(det) < epsilon) return 1; \
203 \
204 const c_type invDet = 1.0 / det; \
205 \
206 B(1, 1) = invDet * (A(3, 3) * A(2, 2) - A(3, 2) * A(2, 3)); \
207 B(1, 2) = -invDet * (A(3, 3) * A(1, 2) - A(3, 2) * A(1, 3)); \
208 B(1, 3) = invDet * (A(2, 3) * A(1, 2) - A(2, 2) * A(1, 3)); \
209 \
210 B(2, 1) = -invDet * (A(3, 3) * A(2, 1) - A(3, 1) * A(2, 3)); \
211 B(2, 2) = invDet * (A(3, 3) * A(1, 1) - A(3, 1) * A(1, 3)); \
212 B(2, 3) = -invDet * (A(2, 3) * A(1, 1) - A(2, 1) * A(1, 3)); \
213 \
214 B(3, 1) = invDet * (A(3, 2) * A(2, 1) - A(3, 1) * A(2, 2)); \
215 B(3, 2) = -invDet * (A(3, 2) * A(1, 1) - A(3, 1) * A(1, 2)); \
216 B(3, 3) = invDet * (A(2, 2) * A(1, 1) - A(2, 1) * A(1, 2)); \
217 return 0; \
218 }
219
220#define A(y, x) src[(y - 1) * 3 + (x - 1)]
221#define B(y, x) dst[(y - 1) * 3 + (x - 1)]
224
225 int mat3inv(float *const dst, const float *const src)
226{
227 return mat3inv_float(dst, src);
228}
229
231#undef B
232#undef A
233#undef generate_mat3inv_body
234
235
238 const char *filename,
240
242static int dt_colorspaces_get_matrix_from_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
243 float *lutb, const int lutsize, const int input)
244{
245 // create an OpenCL processable matrix + tone curves from an cmsHPROFILE:
246 // NOTE: may be invoked with matrix and LUT pointers set to null to find
247 // out if the profile can be created at all.
248
249 // check this first:
250 if(IS_NULL_PTR(prof) || !cmsIsMatrixShaper(prof)) return 1;
251
252 // there are some profiles that contain both a color LUT for some specific
253 // intent and a generic matrix. in some cases the matrix might be
254 // deliberately wrong with swapped blue and red channels in order to easily
255 // detect if a color managed software is applying the LUT or the matrix.
256 // thus, if this profile contains LUT for any intent, it might also contain
257 // swapped matrix, so the only right way to handle it is to let LCMS apply it.
258 const int UsedDirection = input ? LCMS_USED_AS_INPUT : LCMS_USED_AS_OUTPUT;
259
260 if(cmsIsCLUT(prof, INTENT_PERCEPTUAL, UsedDirection)
261 || cmsIsCLUT(prof, INTENT_RELATIVE_COLORIMETRIC, UsedDirection)
262 || cmsIsCLUT(prof, INTENT_ABSOLUTE_COLORIMETRIC, UsedDirection)
263 || cmsIsCLUT(prof, INTENT_SATURATION, UsedDirection))
264 return 1;
265
266 cmsToneCurve *red_curve = cmsReadTag(prof, cmsSigRedTRCTag);
267 cmsToneCurve *green_curve = cmsReadTag(prof, cmsSigGreenTRCTag);
268 cmsToneCurve *blue_curve = cmsReadTag(prof, cmsSigBlueTRCTag);
269
270 cmsCIEXYZ *red_color = cmsReadTag(prof, cmsSigRedColorantTag);
271 cmsCIEXYZ *green_color = cmsReadTag(prof, cmsSigGreenColorantTag);
272 cmsCIEXYZ *blue_color = cmsReadTag(prof, cmsSigBlueColorantTag);
273
274 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;
275
276 dt_colormatrix_t matrix_tmp = { { red_color->X, green_color->X, blue_color->X },
277 { red_color->Y, green_color->Y, blue_color->Y },
278 { red_color->Z, green_color->Z, blue_color->Z } };
279
280 // some camera ICC profiles claim to have color locations for red, green and blue base colors defined,
281 // but in fact these are all set to zero. we catch this case here.
282 float sum = 0.0f;
283 for(int k1 = 0; k1 < 3; k1++)
284 for(int k2 = 0; k2 < 3; k2++)
285 sum += matrix_tmp[k1][k2];
286 if(sum == 0.0f) return 3;
287
288 if(input)
289 {
290 // mark as linear, if they are:
291 if(lutr && lutg && lutb)
292 {
293 if(cmsIsToneCurveLinear(red_curve))
294 lutr[0] = -1.0f;
295 else
296 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(red_curve, k / (lutsize - 1.0f));
297 if(cmsIsToneCurveLinear(green_curve))
298 lutg[0] = -1.0f;
299 else
300 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(green_curve, k / (lutsize - 1.0f));
301 if(cmsIsToneCurveLinear(blue_curve))
302 lutb[0] = -1.0f;
303 else
304 for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(blue_curve, k / (lutsize - 1.0f));
305 }
306 }
307 else
308 {
309 // invert profile->XYZ matrix for output profiles
311 memcpy(tmp, matrix_tmp, sizeof(dt_colormatrix_t));
312 if(mat3SSEinv(matrix_tmp, tmp))
313 return 3;
314 // also need to reverse gamma, to apply reverse before matrix multiplication:
315 cmsToneCurve *rev_red = cmsReverseToneCurveEx(0x8000, red_curve);
316 cmsToneCurve *rev_green = cmsReverseToneCurveEx(0x8000, green_curve);
317 cmsToneCurve *rev_blue = cmsReverseToneCurveEx(0x8000, blue_curve);
318 if(IS_NULL_PTR(rev_red) || IS_NULL_PTR(rev_green) || IS_NULL_PTR(rev_blue))
319 {
320 cmsFreeToneCurve(rev_red);
321 cmsFreeToneCurve(rev_green);
322 cmsFreeToneCurve(rev_blue);
323 return 4;
324 }
325
326 if(lutr && lutg && lutb)
327 {
328 // pass on tonecurves, in case lutsize > 0:
329 if(cmsIsToneCurveLinear(red_curve))
330 lutr[0] = -1.0f;
331 else
332 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(rev_red, k / (lutsize - 1.0f));
333 if(cmsIsToneCurveLinear(green_curve))
334 lutg[0] = -1.0f;
335 else
336 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(rev_green, k / (lutsize - 1.0f));
337 if(cmsIsToneCurveLinear(blue_curve))
338 lutb[0] = -1.0f;
339 else
340 for(int k = 0; k < lutsize; k++) lutb[k] = cmsEvalToneCurveFloat(rev_blue, k / (lutsize - 1.0f));
341 }
342
343 cmsFreeToneCurve(rev_red);
344 cmsFreeToneCurve(rev_green);
345 cmsFreeToneCurve(rev_blue);
346 }
347
348 if(matrix)
349 memcpy(matrix, matrix_tmp, sizeof(dt_colormatrix_t));
350
351 return 0;
352}
353
354int dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
355 float *lutb, const int lutsize)
356{
357 return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 1);
358}
359
360int dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg,
361 float *lutb, const int lutsize)
362{
363 return dt_colorspaces_get_matrix_from_profile(prof, matrix, lutr, lutg, lutb, lutsize, 0);
364}
365
367{
368 return cmsCreateLab4Profile(cmsD50_xyY());
369}
370
371static void _compute_prequantized_primaries(const cmsCIExyY* whitepoint,
372 const cmsCIExyYTRIPLE* primaries,
373 cmsCIEXYZTRIPLE *primaries_prequantized)
374{
375 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, NULL);
376
377 cmsCIEXYZ *R = cmsReadTag(profile, cmsSigRedColorantTag);
378 cmsCIEXYZ *G = cmsReadTag(profile, cmsSigGreenColorantTag);
379 cmsCIEXYZ *B = cmsReadTag(profile, cmsSigBlueColorantTag);
380
381 primaries_prequantized->Red.X = (double)R->X;
382 primaries_prequantized->Red.Y = (double)R->Y;
383 primaries_prequantized->Red.Z = (double)R->Z;
384
385 primaries_prequantized->Green.X = (double)G->X;
386 primaries_prequantized->Green.Y = (double)G->Y;
387 primaries_prequantized->Green.Z = (double)G->Z;
388
389 primaries_prequantized->Blue.X = (double)B->X;
390 primaries_prequantized->Blue.Y = (double)B->Y;
391 primaries_prequantized->Blue.Z = (double)B->Z;
392
393 cmsCloseProfile(profile);
394}
395
396static cmsHPROFILE _create_lcms_profile(const char *desc, const char *dmdd,
397 const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsToneCurve *trc,
398 gboolean v2)
399{
400 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
401 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
402 cmsMLU *mlu3 = cmsMLUalloc(NULL, 1);
403 cmsMLU *mlu4 = cmsMLUalloc(NULL, 1);
404
405 cmsToneCurve *out_curves[3] = { trc, trc, trc };
406 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, out_curves);
407
408 if(v2) cmsSetProfileVersion(profile, 2.4);
409
410 cmsSetHeaderFlags(profile, cmsEmbeddedProfileTrue);
411
412 cmsMLUsetASCII(mlu1, "en", "US", "Public Domain");
413 cmsWriteTag(profile, cmsSigCopyrightTag, mlu1);
414
415 cmsMLUsetASCII(mlu2, "en", "US", desc);
416 cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
417
418 cmsMLUsetASCII(mlu3, "en", "US", dmdd);
419 cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu3);
420
421 cmsMLUsetASCII(mlu4, "en", "US", "darktable");
422 cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu4);
423
424 cmsMLUfree(mlu1);
425 cmsMLUfree(mlu2);
426 cmsMLUfree(mlu3);
427 cmsMLUfree(mlu4);
428
429 return profile;
430}
431
432// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
433// Perceptual Quantization / SMPTE standard ST.2084
434static double _PQ_fct(double x)
435{
436 static const double M1 = 2610.0 / 16384.0;
437 static const double M2 = (2523.0 / 4096.0) * 128.0;
438 static const double C1 = 3424.0 / 4096.0;
439 static const double C2 = (2413.0 / 4096.0) * 32.0;
440 static const double C3 = (2392.0 / 4096.0) * 32.0;
441
442 if (x == 0.0) return 0.0;
443 const double sign = x;
444 x = fabs(x);
445
446 const double xpo = pow(x, 1.0 / M2);
447 const double num = MAX(xpo - C1, 0.0);
448 const double den = C2 - C3 * xpo;
449 const double res = pow(num / den, 1.0 / M1);
450
451 return copysign(res, sign);
452}
453
454// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
455// Hybrid Log-Gamma
456static double _HLG_fct(double x)
457{
458 static const double A = 0.17883277;
459 static const double B = 0.28466892;
460 static const double C = 0.55991073;
461
468 const double sign = x;
469 const double e = fabs(x);
470
471 if(e <= 0.5)
472 return copysign((e * e) / 3.0, sign);
473
474 return copysign((exp((e - C) / A) + B) / 12.0, sign);
475}
476
477static cmsToneCurve* _colorspaces_create_transfer(int32_t size, double (*fct)(double))
478{
479 float *values = g_malloc(sizeof(float) * size);
480
481 for (int32_t i = 0; i < size; ++i)
482 {
483 const double x = (float)i / (size - 1);
484 const double y = MIN(fct(x), 1.0f);
485 values[i] = (float)y;
486 }
487
488 cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(NULL, size, values);
489 dt_free(values);
490 return result;
491}
492
493static cmsHPROFILE _colorspaces_create_srgb_profile(gboolean v2)
494{
495 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
496 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
497
498 cmsHPROFILE profile = _create_lcms_profile("sRGB", "sRGB",
499 &D65xyY, &sRGB_Primaries, transferFunction, v2);
500
501 cmsFreeToneCurve(transferFunction);
502
503 return profile;
504}
505
510
515
517{
518 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
519 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
520
521 cmsCIExyYTRIPLE BRG_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Red, sRGB_Primaries.Green };
522
523 cmsHPROFILE profile = _create_lcms_profile("BRG", "BRG",
524 &D65xyY, &BRG_Primaries, transferFunction, TRUE);
525
526 cmsFreeToneCurve(transferFunction);
527
528 return profile;
529}
530
532{
533 cmsFloat64Number srgb_parameters[5] = { 1/0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
534 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
535
536 cmsHPROFILE profile = _create_lcms_profile("Gamma Rec709 RGB", "Gamma Rec709 RGB",
537 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
538
539 cmsFreeToneCurve(transferFunction);
540
541 return profile;
542}
543
545{
546 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1886-0-201103-I!!PDF-E.pdf
547 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 2.19921875);
548
549 cmsHPROFILE profile = _create_lcms_profile("ITU-R BT.1886 (gamma 2.4 Rec709)", "ITU-R BT.1886 (gamma 2.4 Rec709)",
550 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
551
552 cmsFreeToneCurve(transferFunction);
553
554 return profile;
555}
556
557
558// Create the ICC virtual profile for adobe rgb space
560{
561 // AdobeRGB's "2.2" gamma is technically defined as 2 + 51/256
562 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 2.19921875);
563
564 cmsHPROFILE profile = _create_lcms_profile("Adobe RGB (compatible)", "Adobe RGB",
565 &D65xyY, &Adobe_Primaries, transferFunction, TRUE);
566
567 cmsFreeToneCurve(transferFunction);
568
569 return profile;
570}
571
572
573cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
574{
575 const dt_profiled_colormatrix_t *preset = NULL;
576 for(int k = 0; k < dt_alternate_colormatrix_cnt; k++)
577 {
578 if(!strcmp(makermodel, dt_alternate_colormatrices[k].makermodel))
579 {
581 break;
582 }
583 }
584 if(IS_NULL_PTR(preset)) return NULL;
585
586 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
587 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
588 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
589 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
590 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
591 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
592 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
593 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
594 cmsToneCurve *Gamma[3];
595 cmsHPROFILE hp;
596
597 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
598
599 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
600 cmsFreeToneCurve(Gamma[0]);
601 if(IS_NULL_PTR(hp)) return NULL;
602
603 char name[512];
604 snprintf(name, sizeof(name), "darktable alternate %s", makermodel);
605 cmsSetProfileVersion(hp, 2.1);
606 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
607 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
608 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
609 cmsMLUsetASCII(mlu1, "en", "US", name);
610 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
611 cmsMLUsetASCII(mlu2, "en", "US", name);
612 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
613 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
614 // this will only be displayed when the embedded profile is read by for example GIMP
615 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
616 cmsMLUfree(mlu0);
617 cmsMLUfree(mlu1);
618 cmsMLUfree(mlu2);
619
620 return hp;
621}
622
623cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
624{
625 const dt_profiled_colormatrix_t *preset = NULL;
626 for(int k = 0; k < dt_vendor_colormatrix_cnt; k++)
627 {
628 if(!strcmp(makermodel, dt_vendor_colormatrices[k].makermodel))
629 {
631 break;
632 }
633 }
634 if(IS_NULL_PTR(preset)) return NULL;
635
636 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
637 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
638 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
639 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
640 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
641 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
642 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
643 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
644 cmsToneCurve *Gamma[3];
645 cmsHPROFILE hp;
646
647 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
648
649 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
650 cmsFreeToneCurve(Gamma[0]);
651 if(IS_NULL_PTR(hp)) return NULL;
652
653 char name[512];
654 snprintf(name, sizeof(name), "darktable vendor %s", makermodel);
655 cmsSetProfileVersion(hp, 2.1);
656 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
657 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
658 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
659 cmsMLUsetASCII(mlu1, "en", "US", name);
660 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
661 cmsMLUsetASCII(mlu2, "en", "US", name);
662 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
663 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
664 // this will only be displayed when the embedded profile is read by for example GIMP
665 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
666 cmsMLUfree(mlu0);
667 cmsMLUfree(mlu1);
668 cmsMLUfree(mlu2);
669
670 return hp;
671}
672
673cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
674{
675 const dt_profiled_colormatrix_t *preset = NULL;
676 for(int k = 0; k < dt_profiled_colormatrix_cnt; k++)
677 {
678 if(!strcasecmp(makermodel, dt_profiled_colormatrices[k].makermodel))
679 {
681 break;
682 }
683 }
684 if(IS_NULL_PTR(preset)) return NULL;
685
686 const float wxyz = preset->white[0] + preset->white[1] + preset->white[2];
687 const float rxyz = preset->rXYZ[0] + preset->rXYZ[1] + preset->rXYZ[2];
688 const float gxyz = preset->gXYZ[0] + preset->gXYZ[1] + preset->gXYZ[2];
689 const float bxyz = preset->bXYZ[0] + preset->bXYZ[1] + preset->bXYZ[2];
690 cmsCIExyY WP = { preset->white[0] / wxyz, preset->white[1] / wxyz, 1.0 };
691 cmsCIExyYTRIPLE XYZPrimaries = { { preset->rXYZ[0] / rxyz, preset->rXYZ[1] / rxyz, 1.0 },
692 { preset->gXYZ[0] / gxyz, preset->gXYZ[1] / gxyz, 1.0 },
693 { preset->bXYZ[0] / bxyz, preset->bXYZ[1] / bxyz, 1.0 } };
694 cmsToneCurve *Gamma[3];
695 cmsHPROFILE hp;
696
697 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
698
699 hp = cmsCreateRGBProfile(&WP, &XYZPrimaries, Gamma);
700 cmsFreeToneCurve(Gamma[0]);
701 if(IS_NULL_PTR(hp)) return NULL;
702
703 char name[512];
704 snprintf(name, sizeof(name), "darktable profiled %s", makermodel);
705 cmsSetProfileVersion(hp, 2.1);
706 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
707 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
708 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
709 cmsMLUsetASCII(mlu1, "en", "US", name);
710 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
711 cmsMLUsetASCII(mlu2, "en", "US", name);
712 cmsWriteTag(hp, cmsSigDeviceMfgDescTag, mlu0);
713 cmsWriteTag(hp, cmsSigDeviceModelDescTag, mlu1);
714 // this will only be displayed when the embedded profile is read by for example GIMP
715 cmsWriteTag(hp, cmsSigProfileDescriptionTag, mlu2);
716 cmsMLUfree(mlu0);
717 cmsMLUfree(mlu1);
718 cmsMLUfree(mlu2);
719
720 return hp;
721}
722
723static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
724{
725 cmsHPROFILE hXYZ = cmsCreateXYZProfile();
726 cmsSetPCS(hXYZ, cmsSigXYZData);
727 cmsSetHeaderRenderingIntent(hXYZ, INTENT_PERCEPTUAL);
728
729 if(IS_NULL_PTR(hXYZ)) return NULL;
730
731 cmsSetProfileVersion(hXYZ, 2.1);
732 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
733 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
734 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
735 cmsMLUsetASCII(mlu1, "en", "US", "linear XYZ");
736 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
737 cmsMLUsetASCII(mlu2, "en", "US", "darktable linear XYZ");
738 cmsWriteTag(hXYZ, cmsSigDeviceMfgDescTag, mlu0);
739 cmsWriteTag(hXYZ, cmsSigDeviceModelDescTag, mlu1);
740 // this will only be displayed when the embedded profile is read by for example GIMP
741 cmsWriteTag(hXYZ, cmsSigProfileDescriptionTag, mlu2);
742 cmsMLUfree(mlu0);
743 cmsMLUfree(mlu1);
744 cmsMLUfree(mlu2);
745
746 return hXYZ;
747}
748
750{
751 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
752
753 cmsHPROFILE profile = _create_lcms_profile("Linear Rec709 RGB", "Linear Rec709 RGB",
754 &D65xyY, &Rec709_Primaries, transferFunction, TRUE);
755
756 cmsFreeToneCurve(transferFunction);
757
758 return profile;
759}
760
762{
763 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
764
765 cmsHPROFILE profile = _create_lcms_profile("Linear Rec2020 RGB", "Linear Rec2020 RGB",
766 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
767
768 cmsFreeToneCurve(transferFunction);
769
770 return profile;
771}
772
774{
775 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
776
777 cmsHPROFILE profile = _create_lcms_profile("PQ Rec2020 RGB", "PQ Rec2020 RGB",
778 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
779
780 cmsFreeToneCurve(transferFunction);
781
782 return profile;
783}
784
786{
787 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
788
789 cmsHPROFILE profile = _create_lcms_profile("HLG Rec2020 RGB", "HLG Rec2020 RGB",
790 &D65xyY, &Rec2020_Primaries, transferFunction, TRUE);
791
792 cmsFreeToneCurve(transferFunction);
793
794 return profile;
795}
796
798{
799 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _PQ_fct);
800
801 cmsHPROFILE profile = _create_lcms_profile("PQ P3 RGB", "PQ P3 RGB",
802 &D65xyY, &P3_Primaries, transferFunction, TRUE);
803
804 cmsFreeToneCurve(transferFunction);
805
806 return profile;
807}
808
810{
811 cmsToneCurve *transferFunction = _colorspaces_create_transfer(4096, _HLG_fct);
812
813 cmsHPROFILE profile = _create_lcms_profile("HLG P3 RGB", "HLG P3 RGB",
814 &D65xyY, &P3_Primaries, transferFunction, TRUE);
815
816 cmsFreeToneCurve(transferFunction);
817
818 return profile;
819}
820
822{
823 cmsFloat64Number srgb_parameters[5] = { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
824 cmsToneCurve *transferFunction = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
825
826 cmsHPROFILE profile = _create_lcms_profile("Display P3 RGB", "Display P3 RGB",
827 &D65xyY, &P3_Primaries, transferFunction, TRUE);
828
829 cmsFreeToneCurve(transferFunction);
830
831 return profile;
832}
833
835{
836 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
837
838 cmsHPROFILE profile = _create_lcms_profile("Linear ProPhoto RGB", "Linear ProPhoto RGB",
839 &D50xyY, &ProPhoto_Primaries, transferFunction, TRUE);
840
841 cmsFreeToneCurve(transferFunction);
842
843 return profile;
844}
845
847{
848 cmsToneCurve *transferFunction = cmsBuildGamma(NULL, 1.0);
849
850 // linear rgb with r and b swapped:
851 cmsCIExyYTRIPLE BGR_Primaries = { sRGB_Primaries.Blue, sRGB_Primaries.Green, sRGB_Primaries.Red };
852
853 cmsHPROFILE profile = _create_lcms_profile("Linear Infrared BGR", "darktable Linear Infrared BGR",
854 &D65xyY, &BGR_Primaries, transferFunction, FALSE);
855
856 cmsFreeToneCurve(transferFunction);
857
858 return profile;
859}
860
861
862
863
864
865
866
869{
870 // No role: a profile belonging to one image has no place in any combo box, so no
871 // enumeration and no lookup can ever reach it.
873 if(container) container->owns_profile = owns_profile;
874 return container;
875}
876
878{
879 if(IS_NULL_PTR(profile)) return;
880 // Only close what this container created; a borrowed profile belongs to the application list.
881 if(profile->owns_profile) dt_colorspaces_cleanup_profile(profile->profile);
883}
884
885
886#if 0
887static void dt_colorspaces_create_cmatrix(float cmatrix[4][3], float mat[3][3])
888{
889 // sRGB D65, the linear part:
890 static const dt_colormatrix_t rgb_to_xyz = { { 0.4124564f, 0.3575761f, 0.1804375f, 0.0f },
891 { 0.2126729f, 0.7151522f, 0.0721750f, 0.0f },
892 { 0.0193339f, 0.1191920f, 0.9503041f, 0.0f } };
893
894 for(int c = 0; c < 3; c++)
895 {
896 for(int j = 0; j < 3; j++)
897 {
898 mat[c][j] = 0.0f;
899 for(int k = 0; k < 3; k++)
900 {
901 mat[c][j] += rgb_to_xyz[k][j] * cmatrix[c][k];
902 }
903 }
904 }
905}
906#endif
907
908static cmsHPROFILE dt_colorspaces_create_xyzmatrix_profile(const float mat[3][3])
909{
910 // mat: cam -> xyz
912 for(int k = 0; k < 3; k++)
913 {
914 const float norm = mat[0][k] + mat[1][k] + mat[2][k];
915 x[k] = mat[0][k] / norm;
916 y[k] = mat[1][k] / norm;
917 }
918 cmsCIExyYTRIPLE CameraPrimaries = { { x[0], y[0], 1.0 }, { x[1], y[1], 1.0 }, { x[2], y[2], 1.0 } };
919 cmsHPROFILE profile;
920
921 cmsCIExyY D65;
922 cmsXYZ2xyY(&D65, &d65);
923
924 cmsToneCurve *Gamma[3];
925 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
926 profile = cmsCreateRGBProfile(&D65, &CameraPrimaries, Gamma);
927 cmsFreeToneCurve(Gamma[0]);
928 if(IS_NULL_PTR(profile)) return NULL;
929
930 cmsSetProfileVersion(profile, 2.1);
931 cmsMLU *mlu0 = cmsMLUalloc(NULL, 1);
932 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
933 cmsMLU *mlu1 = cmsMLUalloc(NULL, 1);
934 cmsMLUsetASCII(mlu1, "en", "US", "color matrix built-in");
935 cmsMLU *mlu2 = cmsMLUalloc(NULL, 1);
936 cmsMLUsetASCII(mlu2, "en", "US", "color matrix built-in");
937 cmsWriteTag(profile, cmsSigDeviceMfgDescTag, mlu0);
938 cmsWriteTag(profile, cmsSigDeviceModelDescTag, mlu1);
939 // this will only be displayed when the embedded profile is read by for example GIMP
940 cmsWriteTag(profile, cmsSigProfileDescriptionTag, mlu2);
941 cmsMLUfree(mlu0);
942 cmsMLUfree(mlu1);
943 cmsMLUfree(mlu2);
944
945 return profile;
946}
947
949{
950 // mat: xyz -> cam
951 float imat[3][3];
952 mat3inv((float *)imat, (float *)mat);
954}
955
956static cmsHPROFILE _ensure_rgb_profile(cmsHPROFILE profile)
957{
958 if(profile && cmsGetColorSpace(profile) == cmsSigGrayData)
959 {
960 cmsToneCurve *trc = cmsReadTag(profile, cmsSigGrayTRCTag);
961 cmsCIEXYZ *wtpt = cmsReadTag(profile, cmsSigMediaWhitePointTag);
962 cmsCIEXYZ *bkpt = cmsReadTag(profile, cmsSigMediaBlackPointTag);
963 cmsCIEXYZ *chad = cmsReadTag(profile, cmsSigChromaticAdaptationTag);
964
965 cmsMLU *cprt = cmsReadTag(profile, cmsSigCopyrightTag);
966 cmsMLU *desc = cmsReadTag(profile, cmsSigProfileDescriptionTag);
967 cmsMLU *dmnd = cmsReadTag(profile, cmsSigDeviceMfgDescTag);
968 cmsMLU *dmdd = cmsReadTag(profile, cmsSigDeviceModelDescTag);
969
970 cmsHPROFILE rgb_profile = cmsCreateProfilePlaceholder(0);
971
972 cmsSetDeviceClass(rgb_profile, cmsSigDisplayClass);
973 cmsSetColorSpace(rgb_profile, cmsSigRgbData);
974 cmsSetPCS(rgb_profile, cmsSigXYZData);
975
976 cmsWriteTag(rgb_profile, cmsSigCopyrightTag, cprt);
977 cmsWriteTag(rgb_profile, cmsSigProfileDescriptionTag, desc);
978 cmsWriteTag(rgb_profile, cmsSigDeviceMfgDescTag, dmnd);
979 cmsWriteTag(rgb_profile, cmsSigDeviceModelDescTag, dmdd);
980
981 cmsWriteTag(rgb_profile, cmsSigMediaBlackPointTag, bkpt);
982 cmsWriteTag(rgb_profile, cmsSigMediaWhitePointTag, wtpt);
983 cmsWriteTag(rgb_profile, cmsSigChromaticAdaptationTag, chad);
984 cmsSetColorSpace(rgb_profile, cmsSigRgbData);
985 cmsSetPCS(rgb_profile, cmsSigXYZData);
986
987 // TODO: we still use prequantized primaries here, we will probably want to rework this
988 // part to create a profile using cmsCreateRGBProfile() as done in _create_lcms_profile().
989 cmsWriteTag(rgb_profile, cmsSigRedColorantTag, (void *)&Rec709_Primaries_Prequantized.Red);
990 cmsWriteTag(rgb_profile, cmsSigGreenColorantTag, (void *)&Rec709_Primaries_Prequantized.Green);
991 cmsWriteTag(rgb_profile, cmsSigBlueColorantTag, (void *)&Rec709_Primaries_Prequantized.Blue);
992
993 cmsWriteTag(rgb_profile, cmsSigRedTRCTag, (void *)trc);
994 cmsLinkTag(rgb_profile, cmsSigGreenTRCTag, cmsSigRedTRCTag);
995 cmsLinkTag(rgb_profile, cmsSigBlueTRCTag, cmsSigRedTRCTag);
996
997 cmsCloseProfile(profile);
998 profile = rgb_profile;
999 }
1000
1001 return profile;
1002}
1003
1004cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
1005{
1006 cmsHPROFILE profile = _ensure_rgb_profile(cmsOpenProfileFromMem(data, size));
1007
1008 return profile;
1009}
1010
1012{
1013 if(IS_NULL_PTR(p)) return;
1014 cmsCloseProfile(p);
1015}
1016
1017void dt_colorspaces_get_profile_name(cmsHPROFILE p, const char *language, const char *country, char *name,
1018 size_t len)
1019{
1020 cmsUInt32Number size;
1021 gchar *buf = NULL;
1022 wchar_t *wbuf = NULL;
1023 gchar *utf8 = NULL;
1024
1025 size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, NULL, 0);
1026 if(size == 0) goto error;
1027
1028 buf = (char *)calloc(size + 1, sizeof(char));
1029 size = cmsGetProfileInfoASCII(p, cmsInfoDescription, language, country, buf, size);
1030 if(size == 0) goto error;
1031
1032 // most unix like systems should work with this, but at least Windows doesn't
1033 if(sizeof(wchar_t) != 4 || g_utf8_validate(buf, -1, NULL))
1034 g_strlcpy(name, buf, len); // better a little weird than totally borked
1035 else
1036 {
1037 wbuf = (wchar_t *)calloc(size + 1, sizeof(wchar_t));
1038 size = cmsGetProfileInfo(p, cmsInfoDescription, language, country, wbuf, sizeof(wchar_t) * size);
1039 if(size == 0) goto error;
1040 utf8 = g_ucs4_to_utf8((gunichar *)wbuf, -1, NULL, NULL, NULL);
1041 if(IS_NULL_PTR(utf8)) goto error;
1042 g_strlcpy(name, utf8, len);
1043 }
1044
1045 dt_free(buf);
1046 dt_free(wbuf);
1047 dt_free(utf8);
1048 return;
1049
1050error:
1051 if(buf)
1052 g_strlcpy(name, buf, len); // better a little weird than totally borked
1053 else
1054 *name = '\0'; // nothing to do here
1055 dt_free(buf);
1056 dt_free(wbuf);
1057 dt_free(utf8);
1058}
1059
1060void rgb2hsl(const dt_aligned_pixel_t rgb, float *h, float *s, float *l)
1061{
1062 const float r = rgb[0], g = rgb[1], b = rgb[2];
1063 const float pmax = fmaxf(r, fmax(g, b));
1064 const float pmin = fminf(r, fmin(g, b));
1065 const float delta = (pmax - pmin);
1066
1067 float hv = 0, sv = 0, lv = (pmin + pmax) / 2.0;
1068
1069 if(delta != 0.0f)
1070 {
1071 sv = lv < 0.5 ? delta / fmaxf(pmax + pmin, 1.52587890625e-05f)
1072 : delta / fmaxf(2.0 - pmax - pmin, 1.52587890625e-05f);
1073
1074 if(pmax == r)
1075 hv = (g - b) / delta;
1076 else if(pmax == g)
1077 hv = 2.0 + (b - r) / delta;
1078 else if(pmax == b)
1079 hv = 4.0 + (r - g) / delta;
1080 hv /= 6.0;
1081 if(hv < 0.0)
1082 hv += 1.0;
1083 else if(hv > 1.0)
1084 hv -= 1.0;
1085 }
1086 *h = hv;
1087 *s = sv;
1088 *l = lv;
1089}
1090
1091// for efficiency, 'hue' must be pre-scaled to be in 0..6
1092static inline __attribute__((always_inline)) float hue2rgb(float m1, float m2, float hue)
1093{
1094 // compute the value for one of the RGB channels from the hue angle.
1095 // If 1 <= angle < 3, return m2; if 4 <= angle <= 6, return m1; otherwise, linearly interpolate between m1 and m2.
1096 if(hue < 1.0f)
1097 return (m1 + (m2 - m1) * hue);
1098 else if(hue < 3.0f)
1099 return m2;
1100 else
1101 return hue < 4.0f ? (m1 + (m2 - m1) * (4.0f - hue)) : m1;
1102}
1103
1104void hsl2rgb(dt_aligned_pixel_t rgb, float h, float s, float l)
1105{
1106 float m1, m2;
1107 if(s == 0)
1108 {
1109 rgb[0] = rgb[1] = rgb[2] = l;
1110 return;
1111 }
1112 m2 = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
1113 m1 = (2.0 * l - m2);
1114 h *= 6.0f; // pre-scale hue angle
1115 rgb[0] = hue2rgb(m1, m2, h < 4.0f ? h + 2.0f : h - 4.0f);
1116 rgb[1] = hue2rgb(m1, m2, h);
1117 rgb[2] = hue2rgb(m1, m2, h > 2.0f ? h - 2.0f : h + 4.0f);
1118}
1119
1121 cmsHPROFILE profile, const char *name,
1123{
1126 pthread_rwlock_init(&prof->lock, NULL);
1127 prof->type = type;
1128 g_strlcpy(prof->name, name, sizeof(prof->name));
1129 prof->profile = profile;
1130 prof->roles = roles;
1131 return prof;
1132}
1133
1134// this function is basically thread safe, at least when not called on the global color profiles
1135/* cmsFLAGS_NOCACHE on every transform built here, and it is not an optimisation choice.
1136 *
1137 * lcms2 gives each transform a 1-pixel memoisation cache, ENABLED when flags are 0. That
1138 * cache is mutable state inside the transform, and lcms2 only sanctions sharing a
1139 * transform between threads when it is inhibited. These four are built once and then
1140 * driven by several threads at a time from the __OMP_PARALLEL_FOR__ loops below, so with
1141 * the cache left on they are a data race on lcms2's internals.
1142 *
1143 * iop/colorout.c already sets the flag on its proofing transform for the same reason. The
1144 * cache only pays on runs of identical adjacent pixels, which photographic data does not
1145 * have, so nothing is lost. */
1147{
1148 if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
1149 self->transform_srgb_to_display = NULL;
1150
1151 if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
1152 self->transform_adobe_rgb_to_display = NULL;
1153
1154 if(self->transform_xyz_to_display) cmsDeleteTransform(self->transform_xyz_to_display);
1155 self->transform_xyz_to_display = NULL;
1156
1157 if(self->transform_display_to_adobe_rgb) cmsDeleteTransform(self->transform_display_to_adobe_rgb);
1158 self->transform_display_to_adobe_rgb = NULL;
1159
1160 const dt_colorspaces_color_profile_t *display_dt_profile = _get_profile(self, self->display_type,
1161 self->display_filename,
1163 if(IS_NULL_PTR(display_dt_profile)) return;
1164 cmsHPROFILE display_profile = display_dt_profile->profile;
1165 if(IS_NULL_PTR(display_profile)) return;
1166
1167 self->transform_srgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_SRGB, "",
1169 TYPE_RGBA_8,
1170 display_profile,
1171 TYPE_BGRA_8,
1172 self->display_intent,
1173 cmsFLAGS_NOCACHE);
1174
1175 self->transform_xyz_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_XYZ, "",
1178 display_profile,
1179 TYPE_RGBA_FLT,
1180 self->display_intent,
1181 cmsFLAGS_NOCACHE);
1182
1183 self->transform_adobe_rgb_to_display = cmsCreateTransform(_get_profile(self, DT_COLORSPACE_ADOBERGB, "",
1185 TYPE_RGBA_8,
1186 display_profile,
1187 TYPE_BGRA_8,
1188 self->display_intent,
1189 cmsFLAGS_NOCACHE);
1190
1191 self->transform_display_to_adobe_rgb = cmsCreateTransform(display_profile,
1192 TYPE_BGRA_8,
1195 TYPE_RGBA_8,
1196 self->display_intent,
1197 cmsFLAGS_NOCACHE);
1198}
1199
1200// update cached transforms for color management of thumbnails
1201// caller holds _transforms_lock for writing
1206
1207/* ---------------------------------------------------------------------------
1208 * Display and soft-proofing settings.
1209 *
1210 * Seven fields the GUI writes and the pipeline reads: the display profile identity
1211 * and intent, the soft-proof identity and intent, and the proofing mode. They were
1212 * read and written through the global struct with no lock of any kind, one field at
1213 * a time — so a reader could see a new display_type paired with the previous
1214 * display_filename, and a 512-byte filename being g_strlcpy'd concurrently is a torn
1215 * string rather than a merely stale one.
1216 *
1217 * They cross the boundary only as a whole struct now, copied under one lock, so a
1218 * group can never be observed half-updated. `generation` advances on every accepted
1219 * change: a pipeline module can fold that single number into its hash instead of the
1220 * individual fields.
1221 *
1222 * LOCK ORDER, where both are involved: _transforms_lock OUTER, _settings_lock INNER.
1223 * The display setters need both, because changing the display profile also rebuilds
1224 * the four prepared transforms. Nothing takes them the other way round.
1225 * ------------------------------------------------------------------------- */
1226
1227/* The module's single instance. It used to hang off darktable_t, which meant the whole
1228 * application could reach in and read, write and lock what is this module's private
1229 * business. It is file-static now: dt_colorprofiles_init() builds it, dt_colorprofiles_
1230 * cleanup() destroys it, and nothing outside this file has a way to name it.
1231 *
1232 * dt_colorspaces_get_global() survives ONLY as an internal shorthand while the remaining
1233 * consumers are migrated to the query API; it is no longer declared in the public header
1234 * and will disappear with the last of them. */
1236
1237/* The four prepared display transforms, and the byte cache the monitor refresh compares
1238 * against, are module-wide: they belong to no single profile, so they cannot be covered by
1239 * a per-entry lock. This is that lock.
1240 *
1241 * Separate from the per-entry locks on purpose. A thumbnail conversion holds this for the
1242 * duration of a whole image; a caller deriving from an unrelated profile must not queue
1243 * behind it, and a monitor-profile change must contend only with users of the display
1244 * entry and of these transforms -- not with everything that touches colour.
1245 *
1246 * LOCK ORDER where a writer needs both: the profile ENTRY lock first, then this one.
1247 * Readers take exactly one. */
1248static pthread_rwlock_t _transforms_lock = PTHREAD_RWLOCK_INITIALIZER;
1249
1251static void _colorspaces_destroy(dt_colorspaces_t *self);
1252
1254{
1255 if(!IS_NULL_PTR(_colorprofiles)) return;
1257}
1258
1260{
1261 if(IS_NULL_PTR(_colorprofiles)) return;
1262
1263 // the derived matrix/LUT memo is built from these profiles; it goes first
1265
1267 _colorprofiles = NULL;
1268}
1269
1270/* Module-internal shorthand. It is static now: nothing outside this directory names the
1271 * module's state, which is the whole point of the exercise. */
1273{
1274 return _colorprofiles;
1275}
1276
1277static pthread_rwlock_t _settings_lock = PTHREAD_RWLOCK_INITIALIZER;
1279
1280/* Both callers of _notify_profile_changed() release _transforms_lock before calling it, so this
1281 * takes no lock but its own -- and the settings lock is the INNER one either way. */
1283{
1284 pthread_rwlock_wrlock(&_settings_lock);
1286 pthread_rwlock_unlock(&_settings_lock);
1287}
1288
1290{
1291 if(IS_NULL_PTR(out)) return;
1292
1293 const dt_colorspaces_t *const self = dt_colorspaces_get_global();
1294
1295 pthread_rwlock_rdlock(&_settings_lock);
1296 out->mode = self->mode;
1297 out->display_type = self->display_type;
1298 g_strlcpy(out->display_filename, self->display_filename, sizeof(out->display_filename));
1299 out->display_intent = self->display_intent;
1300 out->softproof_type = self->softproof_type;
1301 g_strlcpy(out->softproof_filename, self->softproof_filename, sizeof(out->softproof_filename));
1302 out->softproof_intent = self->softproof_intent;
1303 out->generation = _settings_generation;
1304 pthread_rwlock_unlock(&_settings_lock);
1305}
1306
1307/* Did (type, filename) differ from what is stored at (cur_type, cur_filename)?
1308 * Caller holds _settings_lock. filename is only meaningful for DT_COLORSPACE_FILE. */
1310 const char *const cur_filename,
1312 const char *const filename)
1313{
1314 if(cur_type != type) return TRUE;
1315 if(type != DT_COLORSPACE_FILE) return FALSE;
1316 return strcmp(cur_filename, IS_NULL_PTR(filename) ? "" : filename) != 0;
1317}
1318
1320 const char *const filename)
1321{
1323
1324 pthread_rwlock_wrlock(&_transforms_lock);
1325 pthread_rwlock_wrlock(&_settings_lock);
1326
1327 const gboolean changed = _profile_choice_differs(self->display_type, self->display_filename, type, filename);
1328 if(changed)
1329 {
1330 self->display_type = type;
1331 g_strlcpy(self->display_filename, IS_NULL_PTR(filename) ? "" : filename, sizeof(self->display_filename));
1333 }
1334 pthread_rwlock_unlock(&_settings_lock);
1335
1336 // Still under the transforms lock: the new identity and the transforms built from it land together.
1337 if(changed) _update_display_transforms(self);
1338 pthread_rwlock_unlock(&_transforms_lock);
1339
1340 return changed;
1341}
1342
1344{
1346
1347 pthread_rwlock_wrlock(&_transforms_lock);
1348 pthread_rwlock_wrlock(&_settings_lock);
1349
1350 const gboolean changed = (self->display_intent != intent);
1351 if(changed)
1352 {
1353 self->display_intent = intent;
1355 }
1356 pthread_rwlock_unlock(&_settings_lock);
1357
1358 if(changed) _update_display_transforms(self);
1359 pthread_rwlock_unlock(&_transforms_lock);
1360
1361 return changed;
1362}
1363
1364/* The soft-proof settings feed transforms that iop/colorout.c builds per commit_params;
1365 * nothing cached in this module derives from them, so no rebuild here. */
1367 const char *const filename)
1368{
1370
1371 pthread_rwlock_wrlock(&_settings_lock);
1372 const gboolean changed = _profile_choice_differs(self->softproof_type, self->softproof_filename, type, filename);
1373 if(changed)
1374 {
1375 self->softproof_type = type;
1376 g_strlcpy(self->softproof_filename, IS_NULL_PTR(filename) ? "" : filename, sizeof(self->softproof_filename));
1378 }
1379 pthread_rwlock_unlock(&_settings_lock);
1380
1381 return changed;
1382}
1383
1385{
1387
1388 pthread_rwlock_wrlock(&_settings_lock);
1389 const gboolean changed = (self->softproof_intent != intent);
1390 if(changed)
1391 {
1392 self->softproof_intent = intent;
1394 }
1395 pthread_rwlock_unlock(&_settings_lock);
1396
1397 return changed;
1398}
1399
1401{
1403
1404 pthread_rwlock_wrlock(&_settings_lock);
1405 const gboolean changed = (self->mode != mode);
1406 if(changed)
1407 {
1408 self->mode = mode;
1410 }
1411 pthread_rwlock_unlock(&_settings_lock);
1412
1413 return changed;
1414}
1415
1417{
1419
1420 /* One locked read-modify-write. The two toggle buttons each open-coded
1421 * "read mode, compare, write the opposite", which is not atomic: two accelerator
1422 * presses in flight could both read DT_PROFILE_NORMAL and leave soft-proof and
1423 * gamut-check disagreeing about which of them is on. */
1424 pthread_rwlock_wrlock(&_settings_lock);
1425 const dt_colorspaces_color_mode_t now = (self->mode == mode) ? DT_PROFILE_NORMAL : mode;
1426 if(self->mode != now)
1427 {
1428 self->mode = now;
1430 }
1431 pthread_rwlock_unlock(&_settings_lock);
1432
1433 return now;
1434}
1435
1436void dt_colorspaces_transform_rgba_float_row(const cmsHTRANSFORM transform, const float *in, float *out,
1437 const int width)
1438{
1439 cmsDoTransform(transform, in, out, width);
1440}
1441
1443void dt_colorspaces_transform_rgba_float_image(const cmsHTRANSFORM transform, const float *image_in, float *image_out,
1444 const int width, const int height)
1445{
1446 if(IS_NULL_PTR(transform) || IS_NULL_PTR(image_in) || IS_NULL_PTR(image_out) || width <= 0 || height <= 0) return;
1447
1448 /* Share the aliased LCMS transform explicitly. Do not read it indirectly
1449 * through module state inside the loop body. */
1451 for(int y = 0; y < height; y++)
1452 {
1453 const float *const in = image_in + (size_t)y * width * 4;
1454 float *const out = image_out + (size_t)y * width * 4;
1456 }
1457}
1458
1459/* Byte swap + optional colour conversion over a whole 8-bit plane. Private: the only
1460 * callers are the prepared-transform entry points below, which own the locking. */
1461static void _transform_rgba8_to_bgra8(const cmsHTRANSFORM transform, const uint8_t *image_in, uint8_t *image_out,
1462 const int width, const int height)
1463{
1464 if(IS_NULL_PTR(image_in) || IS_NULL_PTR(image_out) || width <= 0 || height <= 0) return;
1465
1466 /* Same threading rule as float transforms: pass an aliased transform handle
1467 * into the helper and share only that stable local state. */
1469 for(int y = 0; y < height; y++)
1470 {
1471 /* NOT restrict: callers pass the same buffer for both (common/mipmap_cache.c converts
1472 * a thumbnail in place), so promising the compiler these do not overlap is a lie it is
1473 * entitled to vectorise on. */
1474 const uint8_t *const in = image_in + (size_t)y * width * 4u;
1475 uint8_t *const out = image_out + (size_t)y * width * 4u;
1476
1477 if(transform)
1478 {
1479 // lcms2 permits in == out when the two formats have the same pixel size; both are 4 bytes.
1480 cmsDoTransform(transform, in, out, width);
1481 for(int x = 0; x < width; x++) out[4 * x + 3] = UINT8_MAX;
1482 }
1483 else
1484 {
1485 for(int x = 0; x < width; x++)
1486 {
1487 /* Read the whole pixel before writing any of it. Storing straight through --
1488 * out[0] = in[2]; out[1] = in[1]; out[2] = in[0]; -- loses the red channel when
1489 * in == out, because the first store overwrites in[0] before the third reads it,
1490 * leaving R and B both holding the original blue. */
1491 const uint8_t r = in[4 * x + 0];
1492 const uint8_t g = in[4 * x + 1];
1493 const uint8_t b = in[4 * x + 2];
1494
1495 out[4 * x + 0] = b;
1496 out[4 * x + 1] = g;
1497 out[4 * x + 2] = r;
1498 out[4 * x + 3] = UINT8_MAX;
1499 }
1500 }
1501 }
1502}
1503
1504/* ---------------------------------------------------------------------------
1505 * Prepared display transforms.
1506 *
1507 * The four cached cmsHTRANSFORMs are rebuilt whenever the monitor profile or the
1508 * display intent changes, so a handle handed to a caller can be freed under it. The
1509 * functions below are therefore the only way to use them: each takes the read lock,
1510 * aliases the handle to a local, runs, and releases. No cmsHTRANSFORM crosses the
1511 * module boundary.
1512 *
1513 * Holding the read lock across the pixel work is deliberate and is what the previous
1514 * caller-side code already did — it is what keeps the handle alive for the duration
1515 * of the conversion.
1516 * ------------------------------------------------------------------------- */
1517
1519{
1521
1522 pthread_rwlock_rdlock(&_transforms_lock);
1523 const cmsHTRANSFORM transform = self->transform_xyz_to_display;
1524 if(transform)
1525 cmsDoTransform(transform, XYZ, RGB, 1);
1526 pthread_rwlock_unlock(&_transforms_lock);
1527
1528 /* No display profile resolved yet (startup, or a monitor whose profile could not be
1529 * read): fall back to sRGB rather than dereferencing NULL, which is what the two
1530 * open-coded copies of this function did. */
1532}
1533
1534gboolean dt_colorprofiles_rgba8_to_display_bgra8(const uint8_t *const in, uint8_t *const out,
1535 const int width, const int height,
1537{
1539 cmsHTRANSFORM transform = NULL;
1540 gboolean owned = FALSE;
1541 gboolean managed = TRUE;
1542
1543 pthread_rwlock_rdlock(&_transforms_lock);
1544
1545 if(src_space == DT_COLORSPACE_SRGB)
1546 {
1548 }
1549 else if(src_space == DT_COLORSPACE_ADOBERGB)
1550 {
1552 }
1553 else if(src_space == DT_COLORSPACE_DISPLAY)
1554 {
1555 // already in display space: pass through, swapping R <-> B (transform stays NULL)
1556 }
1557 else
1558 {
1559 const dt_colorspaces_color_profile_t *const from
1560 = _get_profile(self, src_space, "", DT_PROFILE_ROLE_MONITOR);
1561 const dt_colorspaces_color_profile_t *const to
1563
1564 /* Not every colorspace has a profile registered for the MONITOR role (a thumbnail
1565 * cached with an exotic tag). Fall back to the same passthrough as DT_COLORSPACE_DISPLAY
1566 * instead of dereferencing NULL in cmsCreateTransform(). */
1567 if(!IS_NULL_PTR(from) && !IS_NULL_PTR(to))
1568 {
1569 transform = cmsCreateTransform(from->profile, TYPE_RGBA_8, to->profile, TYPE_BGRA_8,
1570 INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
1571 owned = TRUE;
1572 }
1573 }
1574
1575 /* DT_COLORSPACE_DISPLAY needs no transform and is not a failure; every other space
1576 * reaching the swap-only path means we could not colour-manage it. */
1577 if(IS_NULL_PTR(transform) && src_space != DT_COLORSPACE_DISPLAY) managed = FALSE;
1578
1580
1581 if(owned && transform) cmsDeleteTransform(transform);
1582 pthread_rwlock_unlock(&_transforms_lock);
1583
1584 return managed;
1585}
1586
1587gboolean dt_colorprofiles_bgra8_to_adobergb_rgba8(const uint8_t *const in, uint8_t *const out,
1588 const int width, const int height,
1590{
1592 cmsHTRANSFORM transform = NULL;
1593 gboolean owned = FALSE;
1594
1595 pthread_rwlock_rdlock(&_transforms_lock);
1596
1597 if(src_space == DT_COLORSPACE_DISPLAY)
1598 {
1600 }
1601 else
1602 {
1603 const dt_colorspaces_color_profile_t *const from
1604 = _get_profile(self, src_space, "", DT_PROFILE_ROLE_MONITOR);
1605 const dt_colorspaces_color_profile_t *const to
1607 if(!IS_NULL_PTR(from) && !IS_NULL_PTR(to))
1608 {
1609 transform = cmsCreateTransform(from->profile, TYPE_BGRA_8, to->profile, TYPE_RGBA_8,
1610 INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
1611 owned = TRUE;
1612 }
1613 }
1614
1615 const gboolean managed = !IS_NULL_PTR(transform);
1616
1617 /* With no transform this only swaps R <-> B, which is what turns the BGRA input back
1618 * into RGBA. The helper name says bgra8, but it is the same byte swap either way. */
1620
1621 if(owned && transform) cmsDeleteTransform(transform);
1622 pthread_rwlock_unlock(&_transforms_lock);
1623
1624 return managed;
1625}
1626
1627/* One row of a strided, packed-RGB(A) buffer: widen to RGBA8, convert, write back
1628 * narrowed and R <-> B swapped. `row_in`/`row_out` are width*4 scratch. */
1629static void _srgb_to_display_row(const cmsHTRANSFORM transform, uint8_t *const src, const int width,
1630 const int n_channels, const gboolean has_alpha,
1631 uint8_t *const row_in, uint8_t *const row_out)
1632{
1633 for(int x = 0; x < width; x++)
1634 {
1635 const int s = x * n_channels;
1636 const int d = x * 4;
1637 row_in[d + 0] = src[s + 0];
1638 row_in[d + 1] = src[s + 1];
1639 row_in[d + 2] = src[s + 2];
1640 row_in[d + 3] = has_alpha ? src[s + 3] : UINT8_MAX;
1641 }
1642
1643 cmsDoTransform(transform, row_in, row_out, width);
1644
1645 for(int x = 0; x < width; x++)
1646 {
1647 const int s = x * 4;
1648 const int d = x * n_channels;
1649 src[d + 0] = row_out[s + 2];
1650 src[d + 1] = row_out[s + 1];
1651 src[d + 2] = row_out[s + 0];
1652 if(has_alpha) src[d + 3] = row_out[s + 3];
1653 }
1654}
1655
1656gboolean dt_colorprofiles_srgb_to_display_strided(uint8_t *const pixels, const int width, const int height,
1657 const int rowstride, const int n_channels,
1658 const gboolean has_alpha)
1659{
1660 if(IS_NULL_PTR(pixels) || width <= 0 || height <= 0 || n_channels < 3) return FALSE;
1661
1663
1664 pthread_rwlock_rdlock(&_transforms_lock);
1665 const cmsHTRANSFORM transform = self->transform_srgb_to_display;
1667 {
1668 pthread_rwlock_unlock(&_transforms_lock);
1669 return FALSE;
1670 }
1671
1672 /* Two width*4 scratch rows per thread, in ONE allocation made before the parallel
1673 * region: a per-thread allocation that could fail would put the worksharing loop
1674 * behind a condition some threads take and others do not, which hangs. */
1675 const size_t row_bytes = (size_t)width * 4u;
1676 const int nthreads = MAX(dt_get_num_openmp_threads(), 1);
1677 uint8_t *const scratch = g_try_malloc((size_t)nthreads * 2u * row_bytes);
1678
1679 if(IS_NULL_PTR(scratch))
1680 {
1681 pthread_rwlock_unlock(&_transforms_lock);
1682 return FALSE;
1683 }
1684
1686 {
1687 uint8_t *const row_in = scratch + (size_t)2 * dt_get_thread_num() * row_bytes;
1688 uint8_t *const row_out = row_in + row_bytes;
1689
1690 __OMP_FOR__()
1691 for(int y = 0; y < height; y++)
1692 _srgb_to_display_row(transform, pixels + (size_t)y * rowstride, width, n_channels, has_alpha,
1693 row_in, row_out);
1694 }
1695
1696 g_free(scratch);
1697 pthread_rwlock_unlock(&_transforms_lock);
1698
1699 return TRUE;
1700}
1701
1702// caller holds _transforms_lock for writing
1703static void _update_display_profile(guchar *tmp_data, gsize size, char *name, size_t name_size)
1704{
1705 dt_colorspaces_t *color_profiles = dt_colorspaces_get_global();
1706
1707 dt_free(color_profiles->xprofile_data);
1708 color_profiles->xprofile_data = tmp_data;
1709 color_profiles->xprofile_size = size;
1710
1711 cmsHPROFILE profile = cmsOpenProfileFromMem(tmp_data, size);
1712 if(profile)
1713 {
1714 for(GList *iter = color_profiles->profiles; iter; iter = g_list_next(iter))
1715 {
1717 if(p->type == DT_COLORSPACE_DISPLAY)
1718 {
1719 /* This is the ONE handle in the list that is replaced at runtime, and the reason
1720 * every entry carries a lock. Take this entry's WRITE lock across the swap, so a
1721 * caller that resolved this profile and is deriving from it under the read lock
1722 * cannot have the handle closed underneath it.
1723 *
1724 * The caller already holds _transforms_lock for writing; entry lock inside it,
1725 * per the lock-order note there. */
1726 pthread_rwlock_wrlock(&p->lock);
1727
1728 if(p->profile) dt_colorspaces_cleanup_profile(p->profile);
1729 p->profile = profile;
1730
1731 pthread_rwlock_unlock(&p->lock);
1732
1733 if(name)
1734 dt_colorspaces_get_profile_name(profile, "en", "US", name, name_size);
1735
1736 // update cached transforms for color management of thumbnails
1738
1739 break;
1740 }
1741 }
1742 }
1743}
1744
1745
1746static void cms_error_handler(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *text)
1747{
1748 dt_print(DT_DEBUG_COLORPROFILE, "[lcms2] error %d: %s\n", ErrorCode, text);
1749}
1750
1751static gint _sort_profiles(gconstpointer a, gconstpointer b)
1752{
1755
1756 gchar *name_a = g_utf8_casefold(profile_a->name, -1);
1757 gchar *name_b = g_utf8_casefold(profile_b->name, -1);
1758
1759 gint result = g_strcmp0(name_a, name_b);
1760
1761 dt_free(name_a);
1762 dt_free(name_b);
1763
1764 return result;
1765}
1766
1767static GList *load_profile_from_dir(const char *subdir)
1768{
1769 GList *temp_profiles = NULL;
1770 const gchar *d_name;
1771 char datadir[DT_PATH_MAX] = { 0 };
1772 char confdir[DT_PATH_MAX] = { 0 };
1773 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1774 dt_loc_get_datadir(datadir, sizeof(datadir));
1775 char *lang = getenv("LANG");
1776 if(IS_NULL_PTR(lang)) lang = "en_US";
1777
1778 char *dirname = g_build_filename(confdir, "color", subdir, NULL);
1779 if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
1780 {
1781 dt_free(dirname);
1782 dirname = g_build_filename(datadir, "color", subdir, NULL);
1783 }
1784 GDir *dir = g_dir_open(dirname, 0, NULL);
1785 if(dir)
1786 {
1787 while((d_name = g_dir_read_name(dir)))
1788 {
1789 char *filename = g_build_filename(dirname, d_name, NULL);
1790 const char *cc = filename + strlen(filename);
1791 for(; *cc != '.' && cc > filename; cc--)
1792 ;
1793 if(!g_ascii_strcasecmp(cc, ".icc") || !g_ascii_strcasecmp(cc, ".icm"))
1794 {
1795 size_t end;
1796 char *icc_content = dt_read_file(filename, &end);
1797 if(IS_NULL_PTR(icc_content)) goto icc_loading_done;
1798
1799 // TODO: add support for grayscale profiles, then remove _ensure_rgb_profile() from here
1800 cmsHPROFILE tmpprof = _ensure_rgb_profile(cmsOpenProfileFromMem(icc_content, sizeof(char) * end));
1801 if(tmpprof)
1802 {
1804 dt_colorspaces_get_profile_name(tmpprof, lang, lang + 3, prof->name, sizeof(prof->name));
1805 if(prof->name[0] == '\0')
1806 g_strlcpy(prof->name, _("(unknown name)"), sizeof(prof->name));
1807
1808 g_strlcpy(prof->filename, filename, sizeof(prof->filename));
1809 prof->type = DT_COLORSPACE_FILE;
1810 prof->profile = tmpprof;
1811 // roles are assigned by the caller, after sorting, from the directory it came from
1812 prof->roles = 0;
1813 temp_profiles = g_list_prepend(temp_profiles, prof);
1814 }
1815
1816icc_loading_done:
1817 dt_free(icc_content);
1818 }
1820 }
1821 g_dir_close(dir);
1822 temp_profiles = g_list_sort(temp_profiles, _sort_profiles);
1823 }
1824 dt_free(dirname);
1825 return temp_profiles;
1826}
1827
1829{
1830 cmsSetLogErrorHandler(cms_error_handler);
1831
1832 dt_colorspaces_t *res = (dt_colorspaces_t *)calloc(1, sizeof(dt_colorspaces_t));
1833
1835
1836
1837 // init the category profile with NULL profile, the actual profile must be retrieved dynamically by the caller
1838 res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_WORK, NULL, _("work profile"), 0));
1839
1840 res->profiles = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_EXPORT, NULL, _("export profile"), 0));
1841
1842 res->profiles
1843 = g_list_append(res->profiles, _create_profile(DT_COLORSPACE_SOFTPROOF, NULL, _("softproof profile"), 0));
1844
1845 // init the display profile with srgb so some stupid code that runs before the real profile could be fetched has something to work with
1846 res->profiles = g_list_append(
1848 _("System display profile (recommended)"), DT_PROFILE_ROLE_MONITOR));
1849
1850 // we want a v4 with parametric curve for input and a v2 with point trc for output
1851 // see http://ninedegreesbelow.com/photography/lcms-make-icc-profiles.html#profile-variants-and-versions
1852 // TODO: what about display?
1853 res->profiles
1855 _("sRGB (e.g. JPG)"), DT_PROFILE_ROLE_INPUT));
1856
1857 res->profiles
1860
1861 res->profiles = g_list_append(res->profiles,
1864
1865 res->profiles = g_list_append(
1868
1871
1873 _("ITU-R BT.1886 (gamma 2.4 Rec709)"), DT_PROFILE_ROLE_INPUT | DT_PROFILE_ROLE_OUTPUT | DT_PROFILE_ROLE_WORKING));
1874
1875 res->profiles = g_list_append(
1878
1879 res->profiles = g_list_append(
1882
1883 res->profiles = g_list_append(
1886
1887 res->profiles = g_list_append(
1890
1891 res->profiles = g_list_append(
1894
1895 res->profiles = g_list_append(
1898
1899 res->profiles = g_list_append(
1902
1903 res->profiles = g_list_append(
1904 res->profiles,
1906 DT_PROFILE_ROLE_INPUT | (dt_conf_get_bool("allow_lab_output") ? DT_PROFILE_ROLE_OUTPUT : 0)));
1907
1908 res->profiles = g_list_append(
1910 DT_PROFILE_ROLE_INPUT | (dt_conf_get_bool("allow_lab_output") ? DT_PROFILE_ROLE_OUTPUT : 0)));
1911
1912 res->profiles = g_list_append(
1914 _("linear infrared BGR"), DT_PROFILE_ROLE_INPUT));
1915
1916 res->profiles
1919
1920 // init display profile and softproof/gama checking from conf
1921 res->display_type = dt_conf_get_int("ui_last/color/display_type");
1922 res->softproof_type = dt_conf_get_int("ui_last/color/softproof_type");
1923 const char *tmp = dt_conf_get_string_const("ui_last/color/display_filename");
1924 g_strlcpy(res->display_filename, tmp, sizeof(res->display_filename));
1925 tmp = dt_conf_get_string_const("ui_last/color/softproof_filename");
1926 g_strlcpy(res->softproof_filename, tmp, sizeof(res->softproof_filename));
1927 res->display_intent = dt_conf_get_int("ui_last/color/display_intent");
1928 res->softproof_intent = dt_conf_get_int("ui_last/color/softproof_intent");
1929 res->mode = dt_conf_get_int("ui_last/color/mode");
1930
1931 // sanity checks to ensure the profile filenames are present
1932
1933 if((unsigned int)res->display_type >= DT_COLORSPACE_LAST
1934 || (res->display_type == DT_COLORSPACE_FILE
1935 && (!res->display_filename[0] || !g_file_test(res->display_filename, G_FILE_TEST_IS_REGULAR))))
1936 res->display_type = DT_COLORSPACE_DISPLAY;
1937
1938 if((unsigned int)res->softproof_type >= DT_COLORSPACE_LAST
1939 || (res->softproof_type == DT_COLORSPACE_FILE
1940 && (!res->softproof_filename[0] || !g_file_test(res->softproof_filename, G_FILE_TEST_IS_REGULAR))))
1941 res->softproof_type = DT_COLORSPACE_SRGB;
1942
1943 // temporary list of profiles to be added, we keep this separate to be able to sort it before adding
1944 GList *temp_profiles;
1945
1946 // read {userconfig,datadir}/color/in/*.icc, in this order.
1947 temp_profiles = load_profile_from_dir("in");
1948 for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1949 {
1952 }
1953 res->profiles = g_list_concat(res->profiles, temp_profiles);
1954
1955 // read {conf,data}dir/color/out/*.icc
1956 temp_profiles = load_profile_from_dir("out");
1957 for(GList *iter = temp_profiles; iter; iter = g_list_next(iter))
1958 {
1960 // 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
1961 const cmsColorSpaceSignature color_space = cmsGetColorSpace(prof->profile);
1962 // The histogram profile is used for histogram, clipping indicators and the global color picker.
1963 // Some of these also assume a matrix profile. LUT profiles don't make much sense in these applications
1964 // so filter out any profile that doesn't implement the relative colorimetric intent as a matrix (+ TRC).
1965 // For discussion, see e.g.
1966 // https://github.com/darktable-org/darktable/issues/7660#issuecomment-760143437
1967 // For the working profile we also require a matrix profile.
1968 const gboolean is_valid_matrix_profile
1969 = dt_colorspaces_get_matrix_from_output_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0
1970 && dt_colorspaces_get_matrix_from_input_profile(prof->profile, NULL, NULL, NULL, NULL, 0) == 0;
1972 if(is_valid_matrix_profile)
1973 {
1975 }
1976 else
1977 {
1979 "output profile `%s' color space `%c%c%c%c' not supported for work or histogram profile\n",
1980 prof->name, (char)(color_space >> 24), (char)(color_space >> 16), (char)(color_space >> 8),
1981 (char)(color_space));
1982 }
1983 }
1984 res->profiles = g_list_concat(res->profiles, temp_profiles);
1985
1986
1987 if((unsigned int)res->mode > DT_PROFILE_GAMUTCHECK) res->mode = DT_PROFILE_NORMAL;
1988
1990
1991 return res;
1992}
1993
1995{
1996 // remember display profile and softproof/gama checking from conf
1997 dt_conf_set_int("ui_last/color/display_type", self->display_type);
1998 dt_conf_set_int("ui_last/color/softproof_type", self->softproof_type);
1999 dt_conf_set_string("ui_last/color/display_filename", self->display_filename);
2000 dt_conf_set_string("ui_last/color/softproof_filename", self->softproof_filename);
2001 dt_conf_set_int("ui_last/color/display_intent", self->display_intent);
2002 dt_conf_set_int("ui_last/color/softproof_intent", self->softproof_intent);
2003 dt_conf_set_int("ui_last/color/mode", self->mode);
2004
2005 if(self->transform_srgb_to_display) cmsDeleteTransform(self->transform_srgb_to_display);
2006 self->transform_srgb_to_display = NULL;
2007
2008 if(self->transform_adobe_rgb_to_display) cmsDeleteTransform(self->transform_adobe_rgb_to_display);
2009 self->transform_adobe_rgb_to_display = NULL;
2010
2011 if(self->transform_display_to_adobe_rgb) cmsDeleteTransform(self->transform_display_to_adobe_rgb);
2012 self->transform_display_to_adobe_rgb = NULL;
2013
2014 if(self->transform_xyz_to_display) cmsDeleteTransform(self->transform_xyz_to_display);
2015 self->transform_xyz_to_display = NULL;
2016
2017 for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
2018 {
2020 if(p)
2021 {
2023 pthread_rwlock_destroy(&p->lock);
2024 }
2025 }
2026 g_list_free_full(self->profiles, dt_free_gpointer);
2027 self->profiles = NULL;
2028
2030 dt_free(self->xprofile_data);
2031
2032 dt_free(self);
2033}
2034
2036 const char *filename)
2037{
2038 switch (type)
2039 {
2040 case DT_COLORSPACE_NONE:
2041 return NULL;
2042 case DT_COLORSPACE_FILE:
2043 return filename;
2044 case DT_COLORSPACE_SRGB:
2045 return _("sRGB");
2047 return _("Adobe RGB (compatible)");
2049 return _("linear Rec709 RGB");
2051 return _("linear Rec2020 RGB");
2052 case DT_COLORSPACE_XYZ:
2053 return _("linear XYZ");
2054 case DT_COLORSPACE_LAB:
2055 return _("Lab");
2057 return _("linear infrared BGR");
2059 return _("System display profile (recommended)");
2061 return _("embedded ICC profile");
2063 return _("embedded matrix");
2065 return _("standard color matrix");
2067 return _("enhanced color matrix");
2069 return _("vendor color matrix");
2071 return _("alternate color matrix");
2072 case DT_COLORSPACE_BRG:
2073 return _("BRG (experimental)");
2075 return _("export profile");
2077 return _("softproof profile");
2078 case DT_COLORSPACE_WORK:
2079 return _("work profile");
2081 return _("Not used. Shouldn't be here.");
2083 return _("Rec709 RGB");
2085 return _("linear ProPhoto RGB");
2087 return _("PQ Rec2020");
2089 return _("HLG Rec2020");
2091 return _("PQ P3");
2093 return _("HLG P3");
2095 return _("Display P3");
2097 return _("ITU-R BT.1886");
2098 case DT_COLORSPACE_LAST:
2099 break;
2100 }
2101
2102 return NULL;
2103}
2104
2105#ifdef USE_COLORDGTK
2106static void dt_colorspaces_get_display_profile_colord_callback(GObject *source, GAsyncResult *res, gpointer user_data)
2107{
2108 dt_colorspaces_t *color_profiles = dt_colorspaces_get_global();
2109
2110 /* Writer: takes the DISPLAY entry's own lock (its cmsHPROFILE is replaced) and then the
2111 * transforms lock (all four are rebuilt from it). Entry before transforms -- see the
2112 * lock-order note on _transforms_lock. */
2113 pthread_rwlock_wrlock(&_transforms_lock);
2114
2115 int profile_changed = 0;
2116 CdWindow *window = CD_WINDOW(source);
2117 GError *error = NULL;
2118 CdProfile *profile = cd_window_get_profile_finish(window, res, &error);
2120 {
2121 const gchar *filename = cd_profile_get_filename(profile);
2122 if(filename)
2123 {
2124 if(g_strcmp0(filename, color_profiles->colord_profile_file))
2125 {
2126 /* the profile has changed (either because the user changed the colord settings or because we are on a
2127 * different screen now) */
2128 // update the cached colord profile file
2129 dt_free(color_profiles->colord_profile_file);
2130 color_profiles->colord_profile_file = g_strdup(filename);
2131
2132 // read the file
2133 guchar *tmp_data = NULL;
2134 gsize size;
2135 g_file_get_contents(filename, (gchar **)&tmp_data, &size, NULL);
2136 profile_changed = size > 0 && (color_profiles->xprofile_size != size
2137 || memcmp(color_profiles->xprofile_data, tmp_data, size) != 0);
2138
2139 if(profile_changed)
2140 {
2141 _update_display_profile(tmp_data, size, NULL, 0);
2143 "[color profile] colord gave us a new screen profile: '%s' (size: %" G_GSIZE_FORMAT ")\n", filename, size);
2144 }
2145 else
2146 {
2147 dt_free(tmp_data);
2148 }
2149 }
2150 }
2151 }
2152 if(profile) g_object_unref(profile);
2153 g_object_unref(window);
2154
2155 pthread_rwlock_unlock(&_transforms_lock);
2156
2158}
2159#endif
2160
2161#if defined GDK_WINDOWING_X11
2162#endif
2163
2164// Get the display ICC profile of the monitor associated with the widget.
2165// For X display, uses the ICC profile specifications version 0.2 from
2166// http://burtonini.com/blog/computers/xicc
2167// Based on code from Gimp's modules/cdisplay_lcms.c
2169 GtkWidget *widget)
2170{
2171 if(IS_NULL_PTR(widget)) return;
2172
2173 dt_colorspaces_t *color_profiles = dt_colorspaces_get_global();
2174
2175 // make sure that no one gets a broken profile
2176 // FIXME: benchmark if the try is really needed when moving/resizing the window. Maybe we can just lock it
2177 // and block
2178 /* trywrlock, not wrlock, and this is load-bearing: this runs from a configure-event
2179 * handler, i.e. on every tick of a window drag. Blocking the GUI thread behind a
2180 * thumbnail conversion would stutter the drag, so a refresh that cannot get the lock is
2181 * dropped and the next event retries. */
2182 if(pthread_rwlock_trywrlock(&_transforms_lock))
2183 return; // we are already updating the profile. Or someone is reading right now. Too bad we can't
2184 // distinguish that. Whatever ...
2185
2186 guint8 *buffer = NULL;
2187 gint buffer_size = 0;
2188 gchar *profile_source = NULL;
2189
2190#if defined GDK_WINDOWING_X11
2191
2192 // we will use the xatom no matter what configured when compiled without colord
2193 gboolean use_xatom = TRUE;
2194#if defined USE_COLORDGTK
2195 gboolean use_colord = TRUE;
2196 const char *display_profile_source = dt_conf_get_string_const("ui_last/display_profile_source");
2197
2198 if(display_profile_source)
2199 {
2200 if(!strcmp(display_profile_source, "xatom"))
2201 use_colord = FALSE;
2202 else if(!strcmp(display_profile_source, "colord"))
2203 use_xatom = FALSE;
2204 }
2205#endif
2206
2207 /* let's have a look at the xatom, just in case ... */
2208 if(use_xatom)
2209 dt_display_profile_read(widget, &buffer, &buffer_size, &profile_source);
2210
2211#ifdef USE_COLORDGTK
2212 /* also try to get the profile from colord. this will set the value asynchronously!
2213 * Stays here rather than in system/: the callback writes this module's own state. */
2214 if(use_colord)
2215 {
2216 CdWindow *window = cd_window_new();
2217 cd_window_get_profile(window, widget, NULL, dt_colorspaces_get_display_profile_colord_callback,
2218 GINT_TO_POINTER(profile_type));
2219 }
2220#endif
2221
2222#else // every non-X11 platform: no xatom/colord choice to make
2223 dt_display_profile_read(widget, &buffer, &buffer_size, &profile_source);
2224#endif
2225
2226 int profile_changed = buffer_size > 0 && (color_profiles->xprofile_size != buffer_size
2227 || memcmp(color_profiles->xprofile_data, buffer, buffer_size) != 0);
2228
2229 if(profile_changed)
2230 {
2231 char name[512] = { 0 };
2232 _update_display_profile(buffer, buffer_size, name, sizeof(name));
2233 dt_print(DT_DEBUG_CONTROL, "[color profile] we got a new screen profile `%s' from the %s (size: %d)\n",
2234 *name ? name : "(unknown)", profile_source, buffer_size);
2235 }
2236 else
2237 {
2238 dt_free(buffer);
2239 }
2240 pthread_rwlock_unlock(&_transforms_lock);
2242 dt_free(profile_source);
2243}
2244
2245static gboolean _colorspaces_is_base_name(const char *profile)
2246{
2247 const char *f = profile;
2248 while(*f != '\0')
2249 {
2250 if(*f == '/' || *f == '\\') return FALSE;
2251 f++;
2252 }
2253 return TRUE;
2254}
2255
2256static const char *_colorspaces_get_base_name(const char *profile)
2257{
2258 // Walk backwards from the last character, never past the first one. The previous form
2259 // started at the NUL and decremented while `f >= profile`, so a name with NO separator
2260 // ran the loop one final time at f == profile and left with f == profile - 1: forming
2261 // that pointer is undefined behaviour (only one-past-the-END is legal), and the "base
2262 // name" it returned carried one byte of whatever preceded the string. Which is exactly
2263 // the case this function exists to serve -- old iops that stored a bare basename.
2264 for(const char *f = profile + strlen(profile); f > profile;)
2265 {
2266 f--;
2267 if(*f == '/' || *f == '\\')
2268 return f + 1; // path separator found - return the filename only, without the leading separator
2269 }
2270 return profile; // no separator found - consider profile_name to be a "base" one
2271}
2272
2273gboolean dt_colorspaces_is_profile_equal(const char *fullname, const char *filename)
2274{
2275 // for backward compatibility we need to also ensure that we check
2276 // for basename, indeed filename parameter may be in fact just a
2277 // basename as recorded in an iop.
2279 ? !strcmp(_colorspaces_get_base_name(fullname), filename)
2281}
2282
2283
2286 const char *filename,
2288{
2289 for(GList *iter = self->profiles; iter; iter = g_list_next(iter))
2290 {
2292 if((p->roles & role)
2293 && (p->type == type
2295 {
2296 return p;
2297 }
2298 }
2299
2300 return NULL;
2301}
2302
2309
2310
2311/* ---------------------------------------------------------------------------
2312 * CRUDE: the metadata half of the module interface.
2313 *
2314 * Everything here answers a question ABOUT a profile -- which ones exist for a use,
2315 * what is this one called, where does it sit in a combo box -- and answers it with
2316 * plain values. No cmsHPROFILE crosses this boundary and no caller iterates the list.
2317 *
2318 * These deliberately take no lock. The list is built once by init and never appended
2319 * to again; the ONE datum that mutates at runtime is the DT_COLORSPACE_DISPLAY entry's
2320 * cmsHPROFILE, which _update_display_profile() replaces in place and which nothing
2321 * here reads. Adding a lock around these would put one on 39 call sites that are
2322 * lock-free today, to protect fields nobody writes.
2323 *
2324 * THE ROLE PREDICATE. `role` is mandatory and is not a nicety:
2325 * DT_COLORSPACE_SRGB is registered twice -- a v4 parametric-curve profile valid only
2326 * as input, and a v2 point-TRC profile valid for out/display/category/work -- and the
2327 * two are distinguished by nothing else. A multi-bit mask resolves to the first match
2328 * in registration order, which for sRGB is the v4 input entry. The index-valued calls
2329 * therefore REQUIRE a single bit: an index means nothing outside the enumeration that
2330 * produced it, and an index taken from INPUT|OUTPUT equals neither menu's row number.
2331 * ------------------------------------------------------------------------- */
2332
2333/* Exactly the predicate _get_profile() applies, so enumeration and lookup can never disagree
2334 * about what a role contains. An entry with an empty mask -- the three category entries, and
2335 * every per-image container -- is unreachable by either. */
2338{
2339 return (p->roles & role) != 0;
2340}
2341
2343{
2344 return role != 0 && (role & (role - 1)) == 0;
2345}
2346
2348{
2349 out->type = p->type;
2350 g_strlcpy(out->filename, p->filename, sizeof(out->filename));
2351 g_strlcpy(out->name, p->name, sizeof(out->name));
2352}
2353
2354/* --- LOCK: pin ONE profile's handle for the span of a derivation -----------
2355 *
2356 * Per profile, not per module. Only the DT_COLORSPACE_DISPLAY entry's cmsHPROFILE is
2357 * actually replaced at runtime -- _update_display_profile() closes and swaps it on every
2358 * window move or resize that lands on a different monitor -- but the lock lives on every
2359 * entry rather than on that one, so the next entry that becomes mutable does not
2360 * reintroduce the hazard by default.
2361 *
2362 * Why not one module-wide lock: several pipelines and the GUI derive from profiles
2363 * concurrently. A module-wide reader held across a whole thumbnail conversion queues a
2364 * monitor-profile change behind work that has nothing to do with the display profile, and
2365 * a queued writer in turn blocks every unrelated reader. Per entry, a monitor change
2366 * contends only with users of the display entry.
2367 *
2368 * The entry POINTER is stable for the process: entries are allocated at init and never
2369 * freed or moved, only their ->profile is swapped. So resolving a profile and then locking
2370 * it is sound -- but read ->profile only AFTER taking the lock. */
2372{
2373 if(IS_NULL_PTR(profile)) return;
2374 pthread_rwlock_rdlock((pthread_rwlock_t *)&profile->lock);
2375}
2376
2378{
2379 if(IS_NULL_PTR(profile)) return;
2380 pthread_rwlock_unlock((pthread_rwlock_t *)&profile->lock);
2381}
2382
2385{
2386 if(IS_NULL_PTR(out)) return 0;
2387 *out = NULL;
2388
2390 if(IS_NULL_PTR(self)) return 0;
2391
2392 size_t count = 0;
2393 for(const GList *l = self->profiles; l; l = g_list_next(l))
2394 if(_entry_serves((const dt_colorspaces_color_profile_t *)l->data, role)) count++;
2395
2396 if(count == 0) return 0;
2397
2399 if(IS_NULL_PTR(list)) return 0;
2400
2401 size_t k = 0;
2402 for(const GList *l = self->profiles; l; l = g_list_next(l))
2403 {
2404 const dt_colorspaces_color_profile_t *const p = (const dt_colorspaces_color_profile_t *)l->data;
2405 if(_entry_serves(p, role)) _fill_desc(p, &list[k++]);
2406 }
2407
2408 *out = list;
2409 return count;
2410}
2411
2414 const char *const filename)
2415{
2416 if(!_is_single_role(role)) return -1;
2417
2419 if(IS_NULL_PTR(self)) return -1;
2420
2421 int index = 0;
2422 for(const GList *l = self->profiles; l; l = g_list_next(l))
2423 {
2424 const dt_colorspaces_color_profile_t *const p = (const dt_colorspaces_color_profile_t *)l->data;
2425 if(!_entry_serves(p, role)) continue;
2426
2427 if(p->type == type
2429 return index;
2430
2431 index++;
2432 }
2433
2434 return -1;
2435}
2436
2438 const int index,
2440{
2441 if(!_is_single_role(role) || index < 0 || IS_NULL_PTR(out)) return FALSE;
2442
2444 if(IS_NULL_PTR(self)) return FALSE;
2445
2446 int k = 0;
2447 for(const GList *l = self->profiles; l; l = g_list_next(l))
2448 {
2449 const dt_colorspaces_color_profile_t *const p = (const dt_colorspaces_color_profile_t *)l->data;
2450 if(!_entry_serves(p, role)) continue;
2451 if(k == index)
2452 {
2453 _fill_desc(p, out);
2454 return TRUE;
2455 }
2456 k++;
2457 }
2458
2459 return FALSE;
2460}
2461
2464 const char *const filename)
2465{
2467 if(IS_NULL_PTR(self)) return FALSE;
2468
2469 for(const GList *l = self->profiles; l; l = g_list_next(l))
2470 {
2471 const dt_colorspaces_color_profile_t *const p = (const dt_colorspaces_color_profile_t *)l->data;
2472 if(!_entry_serves(p, role)) continue;
2473 if(p->type == type
2475 return TRUE;
2476 }
2477
2478 return FALSE;
2479}
2480
2481// Copied from dcraw's pseudoinverse()
2483static void dt_colorspaces_pseudoinverse(double (*in)[3], double (*out)[3], int size)
2484{
2485 double work[3][6];
2486
2487 for(int i = 0; i < 3; i++) {
2488 for(int j = 0; j < 6; j++)
2489 work[i][j] = j == i+3;
2490 for(int j = 0; j < 3; j++)
2491 for(int k = 0; k < size; k++)
2492 work[i][j] += in[k][i] * in[k][j];
2493 }
2494 for(int i = 0; i < 3; i++) {
2495 double num = work[i][i];
2496 for(int j = 0; j < 6; j++)
2497 work[i][j] /= num;
2498 for(int k = 0; k < 3; k++) {
2499 if(k==i) continue;
2500 num = work[k][i];
2501 for(int j = 0; j < 6; j++)
2502 work[k][j] -= work[i][j] * num;
2503 }
2504 }
2505 for(int i = 0; i < size; i++)
2506 for(int j = 0; j < 3; j++)
2507 {
2508 out[i][j] = 0.0f;
2509 for(int k = 0; k < 3; k++)
2510 out[i][j] += work[j][k+3] * in[i][k];
2511 }
2512}
2513
2514int 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])
2515{
2516 if(!isnan(in_XYZ_to_CAM[0]))
2517 {
2518 for(int i = 0; i < 9; i++)
2519 XYZ_to_CAM[i/3][i%3] = (double) in_XYZ_to_CAM[i];
2520 for(int i = 0; i < 3; i++)
2521 XYZ_to_CAM[3][i] = 0.0f;
2522 }
2523 else
2524 {
2525 if(isnan(adobe_XYZ_to_CAM[0][0]))
2526 return FALSE;
2527
2528 for(int i = 0; i < 4; i++)
2529 for(int j = 0; j < 3; j++)
2530 XYZ_to_CAM[i][j] = (double)adobe_XYZ_to_CAM[i][j];
2531 }
2532
2533 // Invert the matrix
2534 double inverse[4][3];
2535 dt_colorspaces_pseudoinverse (XYZ_to_CAM, inverse, 4);
2536 for(int i = 0; i < 3; i++)
2537 for(int j = 0; j < 4; j++)
2538 CAM_to_XYZ[i][j] = inverse[j][i];
2539
2540 return TRUE;
2541}
2542
2543// Converted from dcraw's cam_xyz_coeff()
2544// Build the camera RGB to sRGB conversion matrix
2546int dt_colorspaces_conversion_matrices_rgb(const float *adobe_XYZ_to_CAM,
2547 double (*out_RGB_to_CAM)[3], double (*out_CAM_to_RGB)[4],
2548 const float *embedded_matrix,
2549 double *mul)
2550{
2551 double RGB_to_CAM[4][3];
2552
2553 float XYZ_to_CAM[4][3];
2554 XYZ_to_CAM[0][0] = NAN;
2555
2556 if(IS_NULL_PTR(embedded_matrix) || isnan(embedded_matrix[0]))
2557 {
2558 for(int k=0; k<4; k++)
2559 for(int i=0; i<3; i++)
2560 XYZ_to_CAM[k][i] = adobe_XYZ_to_CAM[k * 3 + i];
2561 }
2562 else
2563 {
2564 // keep in sync with reload_defaults from colorin.c
2565 // embedded matrix is used with higher priority than standard one
2566 XYZ_to_CAM[0][0] = embedded_matrix[0];
2567 XYZ_to_CAM[0][1] = embedded_matrix[1];
2568 XYZ_to_CAM[0][2] = embedded_matrix[2];
2569
2570 XYZ_to_CAM[1][0] = embedded_matrix[3];
2571 XYZ_to_CAM[1][1] = embedded_matrix[4];
2572 XYZ_to_CAM[1][2] = embedded_matrix[5];
2573
2574 XYZ_to_CAM[2][0] = embedded_matrix[6];
2575 XYZ_to_CAM[2][1] = embedded_matrix[7];
2576 XYZ_to_CAM[2][2] = embedded_matrix[8];
2577 }
2578
2579 if(isnan(XYZ_to_CAM[0][0]))
2580 return FALSE;
2581
2582 const double RGB_to_XYZ[3][3] = {
2583 // sRGB D65
2584 { 0.412453, 0.357580, 0.180423 },
2585 { 0.212671, 0.715160, 0.072169 },
2586 { 0.019334, 0.119193, 0.950227 },
2587 };
2588
2589 // Multiply RGB matrix
2590 for(int i = 0; i < 4; i++)
2591 for(int j = 0; j < 3; j++)
2592 {
2593 RGB_to_CAM[i][j] = 0.0f;
2594 for(int k = 0; k < 3; k++)
2595 RGB_to_CAM[i][j] += XYZ_to_CAM[i][k] * RGB_to_XYZ[k][j];
2596 }
2597
2598 // Normalize cam_rgb so that cam_rgb * (1,1,1) is (1,1,1,1)
2599 for(int i = 0; i < 4; i++) {
2600 double num = 0.0f;
2601 for(int j = 0; j < 3; j++)
2602 num += RGB_to_CAM[i][j];
2603 for(int j = 0; j < 3; j++)
2604 RGB_to_CAM[i][j] /= num;
2605 if(mul) mul[i] = 1.0f / num;
2606 }
2607
2608 if(out_RGB_to_CAM)
2609 for(int i = 0; i < 4; i++)
2610 for(int j = 0; j < 3; j++)
2611 out_RGB_to_CAM[i][j] = RGB_to_CAM[i][j];
2612
2613 if(out_CAM_to_RGB)
2614 {
2615 // Invert the matrix
2616 double inverse[4][3];
2617 dt_colorspaces_pseudoinverse (RGB_to_CAM, inverse, 4);
2618 for(int i = 0; i < 3; i++)
2619 for(int j = 0; j < 4; j++)
2620 out_CAM_to_RGB[i][j] = inverse[j][i];
2621 }
2622
2623 return TRUE;
2624}
2625
2626void dt_colorspaces_cygm_apply_coeffs_to_rgb(float *out, const float *in, int num, double RGB_to_CAM[4][3],
2627 double CAM_to_RGB[3][4], dt_aligned_pixel_t coeffs)
2628{
2629 // Create the CAM to RGB with applied WB matrix
2630 double CAM_to_RGB_WB[3][4];
2631 for (int a=0; a<3; a++)
2632 for (int b=0; b<4; b++)
2633 CAM_to_RGB_WB[a][b] = CAM_to_RGB[a][b] * coeffs[b];
2634
2635 // Create the RGB->RGB+WB matrix
2636 double RGB_to_RGB_WB[3][3];
2637 for (int a=0; a<3; a++)
2638 for (int b=0; b<3; b++) {
2639 RGB_to_RGB_WB[a][b] = 0.0f;
2640 for (int c=0; c<4; c++)
2641 RGB_to_RGB_WB[a][b] += CAM_to_RGB_WB[a][c] * RGB_to_CAM[c][b];
2642 }
2644 for(int i = 0; i < num; i++)
2645 {
2646 const float *inpos = &in[i*4];
2647 float *outpos = &out[i*4];
2648 outpos[0]=outpos[1]=outpos[2] = 0.0f;
2649 for (int a=0; a<3; a++)
2650 for (int b=0; b<3; b++)
2651 outpos[a] += RGB_to_RGB_WB[a][b] * inpos[b];
2652 }
2653}
2654
2656void dt_colorspaces_cygm_to_rgb(float *out, int num, double CAM_to_RGB[3][4])
2657{
2659 for(int i = 0; i < num; i++)
2660 {
2661 float *in = &out[i*4];
2662 dt_aligned_pixel_t o = {0.0f,0.0f,0.0f};
2663 for(int c = 0; c < 3; c++)
2664 for(int k = 0; k < 4; k++)
2665 o[c] += CAM_to_RGB[c][k] * in[k];
2666 for(int c = 0; c < 3; c++)
2667 in[c] = o[c];
2668 }
2669}
2670
2671void dt_colorspaces_rgb_to_cygm(float *out, int num, double RGB_to_CAM[4][3])
2672{
2674 for(int i = 0; i < num; i++)
2675 {
2676 float *in = &out[i*3];
2677 dt_aligned_pixel_t o = {0.0f,0.0f,0.0f,0.0f};
2678 for(int c = 0; c < 4; c++)
2679 for(int k = 0; k < 3; k++)
2680 o[c] += RGB_to_CAM[c][k] * in[k];
2681 for(int c = 0; c < 4; c++)
2682 in[c] = o[c];
2683 }
2684}
2685
2686// clang-format off
2687// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2688// vim: shiftwidth=2 expandtab tabstop=2 cindent
2689// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2690// clang-format on
static void _update_display_profile(void)
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)
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:487
static void profile_changed(GtkWidget *widget, gpointer user_data)
Definition colorin.c:505
static const dt_profiled_colormatrix_t dt_alternate_colormatrices[]
static const int dt_vendor_colormatrix_cnt
static const int dt_alternate_colormatrix_cnt
static const dt_profiled_colormatrix_t dt_vendor_colormatrices[]
static const dt_profiled_colormatrix_t dt_profiled_colormatrices[]
static const int dt_profiled_colormatrix_cnt
void dt_colorspaces_invalidate_display_profile_memo(void)
Drop the memoised DT_COLORSPACE_DISPLAY entry, whose source profile this module replaces on a monitor...
void dt_colorspaces_flush_profile_memo(void)
Drop every memoised entry. Called by dt_colorprofiles_cleanup().
The colour-profile struct and the maths over it: the derived matrix/LUT engine.
static const float x
const float f
const float const int lutsize
static cmsHPROFILE dt_colorspaces_create_gamma_rec709_rgb_profile(void)
static const cmsCIExyYTRIPLE Rec2020_Primaries
void dt_colorspaces_unlock_profile(const dt_colorspaces_color_profile_t *const profile)
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_role_t role)
static void _colorspaces_destroy(dt_colorspaces_t *self)
static const char * _colorspaces_get_base_name(const char *profile)
cmsHPROFILE dt_colorspaces_create_xyzimatrix_profile(float mat[3][3])
Create a linear-gamma RGB profile from an XYZ->camera matrix.
void dt_colorspaces_free_image_profile(struct dt_colorspaces_color_profile_t *profile)
Release a profile container owned by an image, closing the LCMS2 handle inside it if and only if the ...
static cmsHPROFILE dt_colorspaces_create_linear_rec709_rgb_profile(void)
#define generate_mat3inv_body(c_type, A, B)
#define B(y, x)
static uint64_t _settings_generation
#define A(y, x)
static cmsHPROFILE dt_colorspaces_create_itur_bt1886_rgb_profile(void)
gboolean dt_colorprofiles_bgra8_to_adobergb_rgba8(const uint8_t *const in, uint8_t *const out, const int width, const int height, const dt_colorspaces_color_profile_type_t src_space)
The storage leg: convert an 8-bit plane from src_space (BGRA8) to AdobeRGB (RGBA8),...
void dt_colorspaces_rgb_to_cygm(float *out, int num, double RGB_to_CAM[4][3])
Convert an RGB buffer to 4-channel CYGM, in place.
gboolean dt_colorprofiles_set_display_profile_choice(const dt_colorspaces_color_profile_type_t type, const char *const filename)
Set the monitor profile identity and rebuild the four prepared transforms.
static const cmsCIExyYTRIPLE ProPhoto_Primaries
void rgb2hsl(const dt_aligned_pixel_t rgb, float *h, float *s, float *l)
Convert RGB to HSL. Common helper used by iop modules.
static cmsHPROFILE dt_colorspaces_create_lab_profile()
gboolean dt_colorprofiles_set_display_intent(const dt_iop_color_intent_t intent)
Set the rendering intent used towards the monitor, rebuilding the four prepared transforms.
static void _transform_rgba8_to_bgra8(const cmsHTRANSFORM transform, const uint8_t *image_in, uint8_t *image_out, const int width, const int height)
static dt_colorspaces_profile_changed_handler_t _profile_changed_handler
static cmsHPROFILE dt_colorspaces_create_pq_rec2020_rgb_profile(void)
static const cmsCIExyY D65xyY
void dt_colorprofiles_cleanup(void)
Persist the display/soft-proof settings to conf, flush the derived matrix/LUT memo,...
static cmsHPROFILE dt_colorspaces_create_xyzmatrix_profile(const float mat[3][3])
static const cmsCIExyYTRIPLE Rec709_Primaries
static dt_colorspaces_color_profile_t * _create_profile(dt_colorspaces_color_profile_type_t type, cmsHPROFILE profile, const char *name, dt_colorspaces_profile_role_t roles)
struct dt_colorspaces_color_profile_t * dt_colorspaces_new_image_profile(dt_colorspaces_color_profile_type_t type, cmsHPROFILE profile, gboolean owns_profile)
Build a container for a profile that belongs to ONE image rather than to the application.
void dt_colorprofiles_init(void)
Build the module's single instance: register every built-in profile, load the color/in and color/out ...
__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)
Run a caller-owned LCMS transform over a whole RGBA float image, one OpenMP task per row.
static void _notify_profile_changed(void)
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_role_t role)
Resolve a profile identity to its registered entry.
int dt_colorspaces_get_matrix_from_output_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg, float *lutb, const int lutsize)
Extract the XYZ->profile matrix and the inverse tone curves from an OUTPUT profile.
cmsCIEXYZTRIPLE Rec709_Primaries_Prequantized
void dt_colorspaces_update_display_transforms()
Delete and rebuild the four prepared display transforms from the current display profile and intent.
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 cmsHPROFILE dt_colorspaces_create_srgb_profile()
static cmsHPROFILE dt_colorspaces_create_hlg_rec2020_rgb_profile(void)
static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
static dt_colorspaces_t * _colorspaces_build(void)
static cmsHPROFILE dt_colorspaces_create_display_p3_rgb_profile(void)
cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
Create an ICC virtual profile from the shipped vendor matrices.
size_t dt_colorspaces_enumerate_profiles(const dt_colorspaces_profile_role_t role, dt_colorprofile_desc_t **out)
Ordered snapshot of every profile registered for one role.
static double _PQ_fct(double x)
gboolean dt_colorspaces_profile_exists(const dt_colorspaces_profile_role_t role, const dt_colorspaces_color_profile_type_t type, const char *const filename)
Is this identity registered for this direction?
int dt_colorspaces_get_matrix_from_input_profile(cmsHPROFILE prof, dt_colormatrix_t matrix, float *lutr, float *lutg, float *lutb, const int lutsize)
Extract the profile->XYZ matrix and the per-channel tone curves from an INPUT profile.
__DT_CLONE_TARGETS__ int dt_colorspaces_conversion_matrices_rgb(const float *adobe_XYZ_to_CAM, double(*out_RGB_to_CAM)[3], double(*out_CAM_to_RGB)[4], const float *embedded_matrix, double *mul)
Compute the sRGB->camera and camera->sRGB matrices, and the default white balance multipliers.
void dt_colorspaces_set_profile_changed_handler(dt_colorspaces_profile_changed_handler_t handler)
Register the one callback fired when the display profile changes.
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)
Thin alias of mat3inv_float(), same contract.
static __DT_CLONE_TARGETS__ void dt_colorspaces_pseudoinverse(double(*in)[3], double(*out)[3], int size)
static void _advance_settings_generation(void)
static dt_colorspaces_t * _colorprofiles
cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
Create an ICC virtual profile from the shipped profiled colour matrices.
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])
Compute the XYZ->camera and camera->XYZ matrices for an image.
__DT_CLONE_TARGETS__ void dt_colorspaces_cygm_to_rgb(float *out, int num, double CAM_to_RGB[3][4])
Convert a 4-channel CYGM buffer to RGB, in place.
static pthread_rwlock_t _transforms_lock
static gint _sort_profiles(gconstpointer a, gconstpointer b)
static const cmsCIExyY D50xyY
void dt_colorspaces_cleanup_profile(cmsHPROFILE p)
Close a profile created by any of the dt_colorspaces_create_* / dt_colorspaces_get_rgb_profile_from_m...
static pthread_rwlock_t _settings_lock
gboolean dt_colorprofiles_srgb_to_display_strided(uint8_t *const pixels, const int width, const int height, const int rowstride, const int n_channels, const gboolean has_alpha)
Convert a strided, packed-RGB(A) 8-bit buffer (GdkPixbuf shape) from sRGB to the display profile,...
void dt_colorspaces_set_display_profile(const dt_colorspaces_color_profile_type_t profile_type, GtkWidget *widget)
Refresh the cached display profile from the monitor showing widget (X atom, colord,...
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))
cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
Open an lcms2 RGB profile from an in-memory ICC blob.
static void _fill_desc(const dt_colorspaces_color_profile_t *const p, dt_colorprofile_desc_t *const out)
static cmsHPROFILE dt_colorspaces_create_linear_rec2020_rgb_profile(void)
int dt_colorspaces_profile_index(const dt_colorspaces_profile_role_t role, const dt_colorspaces_color_profile_type_t type, const char *const filename)
Combo position of (type, filename) within direction.
static cmsHPROFILE dt_colorspaces_create_pq_p3_rgb_profile(void)
static gboolean _entry_serves(const dt_colorspaces_color_profile_t *const p, const dt_colorspaces_profile_role_t role)
void dt_colorprofiles_xyz_to_display(const dt_aligned_pixel_t XYZ, dt_aligned_pixel_t RGB)
Convert one D50 XYZ pixel to display RGB.
const char * dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type, const char *filename)
Printable name for a profile identity, without touching the profile list.
static GList * load_profile_from_dir(const char *subdir)
gboolean dt_colorprofiles_set_softproof_profile_choice(const dt_colorspaces_color_profile_type_t type, const char *const filename)
Set the soft-proofing target identity.
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)
Read a profile's description tag into name, handling character encodings.
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)
cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
Create an ICC virtual profile from the shipped alternate matrices.
static gboolean _is_single_role(const dt_colorspaces_profile_role_t role)
gboolean dt_colorprofiles_set_mode(const dt_colorspaces_color_mode_t mode)
Set the proofing mode outright.
static void _srgb_to_display_row(const cmsHTRANSFORM transform, uint8_t *const src, const int width, const int n_channels, const gboolean has_alpha, uint8_t *const row_in, uint8_t *const row_out)
gboolean dt_colorspaces_profile_at(const dt_colorspaces_profile_role_t role, const int index, dt_colorprofile_desc_t *const out)
Identity of the profile at index within direction.
gboolean dt_colorprofiles_set_softproof_intent(const dt_iop_color_intent_t intent)
Set the rendering intent used towards the soft-proofing target.
static const cmsCIExyYTRIPLE Adobe_Primaries
void hsl2rgb(dt_aligned_pixel_t rgb, float h, float s, float l)
Convert HSL back to RGB. Common helper used by iop modules.
static const cmsCIEXYZ d65
gboolean dt_colorspaces_is_profile_equal(const char *fullname, const char *filename)
Do these two names refer to the same profile file?
static cmsHPROFILE _ensure_rgb_profile(cmsHPROFILE profile)
void dt_colorspaces_lock_profile(const dt_colorspaces_color_profile_t *const profile)
dt_colorspaces_color_mode_t dt_colorprofiles_toggle_mode(const dt_colorspaces_color_mode_t mode)
Turn mode on, or back to DT_PROFILE_NORMAL if it is already the current mode, as one locked read-modi...
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)
Apply CYGM white-balance coefficients to an image already converted to RGB by dt_colorspaces_cygm_to_...
int mat3inv_float(float *const dst, const float *const src)
Invert a 3x3 matrix stored row-major as 9 contiguous floats.
static gboolean _profile_choice_differs(const dt_colorspaces_color_profile_type_t cur_type, const char *const cur_filename, const dt_colorspaces_color_profile_type_t type, const char *const filename)
static dt_colorspaces_t * dt_colorspaces_get_global(void)
static double _HLG_fct(double x)
void dt_colorspaces_transform_rgba_float_row(const cmsHTRANSFORM transform, const float *in, float *out, const int width)
Run a caller-owned LCMS transform over one row of RGBA float pixels.
static cmsHPROFILE dt_colorspaces_create_brg_profile()
void dt_colorprofiles_get_settings(dt_colorprofiles_settings_t *const out)
Copy the current settings into caller-provided storage, under one lock.
gboolean dt_colorprofiles_rgba8_to_display_bgra8(const uint8_t *const in, uint8_t *const out, const int width, const int height, const dt_colorspaces_color_profile_type_t src_space)
Convert a whole 8-bit plane from src_space to the display profile: packed RGBA8 in,...
static void _compute_prequantized_primaries(const cmsCIExyY *whitepoint, const cmsCIExyYTRIPLE *primaries, cmsCIEXYZTRIPLE *primaries_prequantized)
The colour-profile module's API: which profiles exist, and how to apply one.
#define TYPE_XYZA_FLT
lcms2 pixel format for float XYZ + one extra channel.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
void(* dt_colorspaces_profile_changed_handler_t)(void)
Callback invoked after the monitor profile actually changed.
static dt_aligned_pixel_t rgb
dt_XYZ_to_sRGB(XYZ, result)
static dt_aligned_pixel_t XYZ
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
static dt_aligned_pixel_t RGB
const dt_colormatrix_t matrix
const float delta
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)
Integer for name, clamped to the bounds declared in the XML.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
int dt_get_num_openmp_threads(void)
Number of OpenMP threads the application decided to use.
Definition darktable.c:518
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
void dt_display_profile_read(GtkWidget *widget, guint8 **buffer, gint *buffer_size, gchar **source)
GtkWidget * window
const int res
Definition dtpthread.h:351
void dt_loc_get_datadir(char *datadir, size_t bufsize)
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
struct dt_iop_tonecurve_params_t preset
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_CONTROL
Definition logging.h:52
@ DT_DEBUG_COLORPROFILE
Definition logging.h:79
@ DT_DEBUG_DEV
Definition logging.h:53
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
#define R
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:37
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
#define __OMP_FOR__(...)
Definition openmp.h:98
static int dt_get_thread_num()
Index of the calling thread within its parallel region, 0 outside one.
Definition openmp.h:129
#define __OMP_PARALLEL__(...)
Definition openmp.h:94
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const char * name
Definition pdf.h:90
dt_iop_color_intent_t
ICC rendering intent, as stored in iop params and in conf.
@ DT_INTENT_ABSOLUTE_COLORIMETRIC
@ DT_INTENT_RELATIVE_COLORIMETRIC
@ DT_INTENT_PERCEPTUAL
@ DT_INTENT_SATURATION
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_ADOBERGB
@ DT_COLORSPACE_PROPHOTO_RGB
@ DT_COLORSPACE_EMBEDDED_MATRIX
@ DT_COLORSPACE_EMBEDDED_ICC
Image-derived profiles: enum 9..14, NOT registered in the list.
@ DT_COLORSPACE_FILE
A user ICC file on disk; the only type whose filename is meaningful.
@ DT_COLORSPACE_DISPLAY
The monitor profile – the one list entry that mutates after init.
@ DT_COLORSPACE_SRGB
sRGB – registered TWICE, and the two entries are not interchangeable.
@ DT_COLORSPACE_INFRARED
@ DT_COLORSPACE_EXPORT
Category placeholders: "whatever the export/softproof/work setting currently says".
@ DT_COLORSPACE_LAB
@ DT_COLORSPACE_REC709
@ DT_COLORSPACE_HLG_P3
@ DT_COLORSPACE_PQ_P3
@ DT_COLORSPACE_PQ_REC2020
@ DT_COLORSPACE_DISPLAY2
Dead value, kept only because old params and conf keys contain it.
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
@ DT_COLORSPACE_HLG_REC2020
@ DT_COLORSPACE_WORK
@ DT_COLORSPACE_LAST
Count, not a colour space. The last usable value is DT_COLORSPACE_LAST - 1.
@ DT_COLORSPACE_ENHANCED_MATRIX
@ DT_COLORSPACE_LIN_REC2020
@ DT_COLORSPACE_DISPLAY_P3
@ DT_COLORSPACE_STANDARD_MATRIX
@ DT_COLORSPACE_XYZ
@ DT_COLORSPACE_BRG
@ DT_COLORSPACE_VENDOR_MATRIX
@ DT_COLORSPACE_SOFTPROOF
@ DT_COLORSPACE_ITUR_BT1886
@ DT_COLORSPACE_LIN_REC709
@ DT_COLORSPACE_ALTERNATE_MATRIX
dt_colorspaces_profile_role_t
Which use a profile is eligible for – the mandatory filter on every lookup and enumeration.
@ DT_PROFILE_ROLE_OUTPUT
Listed in the output/export-profile combo (colorout, export).
@ DT_PROFILE_ROLE_INPUT
Listed in the input-profile combo (colorin).
@ DT_PROFILE_ROLE_WORKING
Listed in the working-profile combo (colorin).
@ DT_PROFILE_ROLE_MONITOR
Eligible for the monitor-profile menu.
dt_colorspaces_color_mode_t
What the output transform is being asked to show: the picture, or a proof of it.
@ DT_PROFILE_GAMUTCHECK
@ DT_PROFILE_NORMAL
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t sign
Definition simd.h:118
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
A profile's public identity: what the GUI displays and stores, and nothing else.
Consistent snapshot of the display and soft-proofing settings.
One registered profile: its identity, its LCMS handle, and where it sits in each combo box.
cmsHPROFILE profile
the actual profile; NULL for the three category entries
char filename[DT_IOP_COLOR_ICC_LEN]
icc file name (absolute; compare with dt_colorspaces_is_profile_equal())
pthread_rwlock_t lock
Guards profile, and nothing else in this struct.
dt_colorspaces_color_profile_type_t type
filename is only used for type DT_COLORSPACE_FILE
char name[512]
product name, displayed in GUI (translated for built-ins)
dt_colorspaces_profile_role_t roles
Which menus this entry appears in, as a dt_colorspaces_profile_role_t mask.
The module's private state. Declared here for the module's own .c files only.
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
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
#define __DT_CLONE_TARGETS__
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:896