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:
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:
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:
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:
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:
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:
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
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.
259
264 return 1;
265
269
273
275
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 {
294 lutr[0] = -1.0f;
295 else
296 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(red_curve, k / (lutsize - 1.0f));
298 lutg[0] = -1.0f;
299 else
300 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(green_curve, k / (lutsize - 1.0f));
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
313 return 3;
314 // also need to reverse gamma, to apply reverse before matrix multiplication:
319 {
323 return 4;
324 }
325
326 if(lutr && lutg && lutb)
327 {
328 // pass on tonecurves, in case lutsize > 0:
330 lutr[0] = -1.0f;
331 else
332 for(int k = 0; k < lutsize; k++) lutr[k] = cmsEvalToneCurveFloat(rev_red, k / (lutsize - 1.0f));
334 lutg[0] = -1.0f;
335 else
336 for(int k = 0; k < lutsize; k++) lutg[k] = cmsEvalToneCurveFloat(rev_green, k / (lutsize - 1.0f));
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
346 }
347
348 if(matrix)
350
351 return 0;
352}
353
359
365
367{
369}
370
371static void _compute_prequantized_primaries(const cmsCIExyY* whitepoint,
372 const cmsCIExyYTRIPLE* primaries,
374{
375 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, NULL);
376
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
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{
404
405 cmsToneCurve *out_curves[3] = { trc, trc, trc };
406 cmsHPROFILE profile = cmsCreateRGBProfile(whitepoint, primaries, out_curves);
407
409
411
412 cmsMLUsetASCII(mlu1, "en", "US", "Public Domain");
414
415 cmsMLUsetASCII(mlu2, "en", "US", desc);
417
418 cmsMLUsetASCII(mlu3, "en", "US", dmdd);
420
421 cmsMLUsetASCII(mlu4, "en", "US", "darktable");
423
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
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 };
497
498 cmsHPROFILE profile = _create_lcms_profile("sRGB", "sRGB",
500
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 };
520
522
523 cmsHPROFILE profile = _create_lcms_profile("BRG", "BRG",
525
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 };
535
536 cmsHPROFILE profile = _create_lcms_profile("Gamma Rec709 RGB", "Gamma Rec709 RGB",
538
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
548
549 cmsHPROFILE profile = _create_lcms_profile("ITU-R BT.1886 (gamma 2.4 Rec709)", "ITU-R BT.1886 (gamma 2.4 Rec709)",
551
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
563
564 cmsHPROFILE profile = _create_lcms_profile("Adobe RGB (compatible)", "Adobe RGB",
566
568
569 return profile;
570}
571
572
573cmsHPROFILE dt_colorspaces_create_alternate_profile(const char *makermodel)
574{
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 } };
595 cmsHPROFILE hp;
596
597 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
598
601 if(IS_NULL_PTR(hp)) return NULL;
602
603 char name[512];
604 snprintf(name, sizeof(name), "darktable alternate %s", makermodel);
607 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
609 cmsMLUsetASCII(mlu1, "en", "US", name);
611 cmsMLUsetASCII(mlu2, "en", "US", name);
614 // this will only be displayed when the embedded profile is read by for example GIMP
619
620 return hp;
621}
622
623cmsHPROFILE dt_colorspaces_create_vendor_profile(const char *makermodel)
624{
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 } };
645 cmsHPROFILE hp;
646
647 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
648
651 if(IS_NULL_PTR(hp)) return NULL;
652
653 char name[512];
654 snprintf(name, sizeof(name), "darktable vendor %s", makermodel);
657 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
659 cmsMLUsetASCII(mlu1, "en", "US", name);
661 cmsMLUsetASCII(mlu2, "en", "US", name);
664 // this will only be displayed when the embedded profile is read by for example GIMP
669
670 return hp;
671}
672
673cmsHPROFILE dt_colorspaces_create_darktable_profile(const char *makermodel)
674{
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 } };
695 cmsHPROFILE hp;
696
697 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
698
701 if(IS_NULL_PTR(hp)) return NULL;
702
703 char name[512];
704 snprintf(name, sizeof(name), "darktable profiled %s", makermodel);
707 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
709 cmsMLUsetASCII(mlu1, "en", "US", name);
711 cmsMLUsetASCII(mlu2, "en", "US", name);
714 // this will only be displayed when the embedded profile is read by for example GIMP
719
720 return hp;
721}
722
723static cmsHPROFILE dt_colorspaces_create_xyz_profile(void)
724{
725 cmsHPROFILE hXYZ = cmsCreateXYZProfile();
728
729 if(IS_NULL_PTR(hXYZ)) return NULL;
730
733 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
735 cmsMLUsetASCII(mlu1, "en", "US", "linear XYZ");
737 cmsMLUsetASCII(mlu2, "en", "US", "darktable linear XYZ");
740 // this will only be displayed when the embedded profile is read by for example GIMP
745
746 return hXYZ;
747}
748
750{
752
753 cmsHPROFILE profile = _create_lcms_profile("Linear Rec709 RGB", "Linear Rec709 RGB",
755
757
758 return profile;
759}
760
762{
764
765 cmsHPROFILE profile = _create_lcms_profile("Linear Rec2020 RGB", "Linear Rec2020 RGB",
767
769
770 return profile;
771}
772
774{
776
777 cmsHPROFILE profile = _create_lcms_profile("PQ Rec2020 RGB", "PQ Rec2020 RGB",
779
781
782 return profile;
783}
784
786{
788
789 cmsHPROFILE profile = _create_lcms_profile("HLG Rec2020 RGB", "HLG Rec2020 RGB",
791
793
794 return profile;
795}
796
798{
800
801 cmsHPROFILE profile = _create_lcms_profile("PQ P3 RGB", "PQ P3 RGB",
803
805
806 return profile;
807}
808
810{
812
813 cmsHPROFILE profile = _create_lcms_profile("HLG P3 RGB", "HLG P3 RGB",
815
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 };
825
826 cmsHPROFILE profile = _create_lcms_profile("Display P3 RGB", "Display P3 RGB",
828
830
831 return profile;
832}
833
835{
837
838 cmsHPROFILE profile = _create_lcms_profile("Linear ProPhoto RGB", "Linear ProPhoto RGB",
840
842
843 return profile;
844}
845
847{
849
850 // linear rgb with r and b swapped:
852
853 cmsHPROFILE profile = _create_lcms_profile("Linear Infrared BGR", "darktable Linear Infrared BGR",
855
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
922 cmsXYZ2xyY(&D65, &d65);
923
925 Gamma[0] = Gamma[1] = Gamma[2] = cmsBuildGamma(NULL, 1.0);
928 if(IS_NULL_PTR(profile)) return NULL;
929
932 cmsMLUsetASCII(mlu0, "en", "US", "(dt internal)");
934 cmsMLUsetASCII(mlu1, "en", "US", "color matrix built-in");
936 cmsMLUsetASCII(mlu2, "en", "US", "color matrix built-in");
939 // this will only be displayed when the embedded profile is read by for example GIMP
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{
959 {
964
969
971
975
980
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().
992
996
999 }
1000
1001 return profile;
1002}
1003
1004cmsHPROFILE dt_colorspaces_get_rgb_profile_from_mem(uint8_t *data, uint32_t size)
1005{
1007
1008 return profile;
1009}
1010
1012{
1013 if(IS_NULL_PTR(p)) return;
1015}
1016
1017void dt_colorspaces_get_profile_name(cmsHPROFILE p, const char *language, const char *country, char *name,
1018 size_t len)
1019{
1021 gchar *buf = NULL;
1022 wchar_t *wbuf = NULL;
1023 gchar *utf8 = NULL;
1024
1026 if(size == 0) goto error;
1027
1028 buf = (char *)calloc(size + 1, sizeof(char));
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));
1039 if(size == 0) goto error;
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{
1150
1153
1156
1159
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
1170 display_profile,
1172 self->display_intent,
1174
1178 display_profile,
1180 self->display_intent,
1182
1186 display_profile,
1188 self->display_intent,
1190
1191 self->transform_display_to_adobe_rgb = cmsCreateTransform(display_profile,
1196 self->display_intent,
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. */
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
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
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. */
1288
1290{
1291 if(IS_NULL_PTR(out)) return;
1292
1293 const dt_colorspaces_t *const self = dt_colorspaces_get_global();
1294
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;
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
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;
1333 }
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);
1339
1340 return changed;
1341}
1342
1344{
1346
1349
1350 const gboolean changed = (self->display_intent != intent);
1351 if(changed)
1352 {
1353 self->display_intent = intent;
1355 }
1357
1358 if(changed) _update_display_transforms(self);
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
1372 const gboolean changed = _profile_choice_differs(self->softproof_type, self->softproof_filename, type, filename);
1373 if(changed)
1374 {
1375 self->softproof_type = type;
1378 }
1380
1381 return changed;
1382}
1383
1385{
1387
1389 const gboolean changed = (self->softproof_intent != intent);
1390 if(changed)
1391 {
1392 self->softproof_intent = intent;
1394 }
1396
1397 return changed;
1398}
1399
1401{
1403
1405 const gboolean changed = (self->mode != mode);
1406 if(changed)
1407 {
1408 self->mode = mode;
1410 }
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. */
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 }
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{
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.
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
1523 const cmsHTRANSFORM transform = self->transform_xyz_to_display;
1524 if(transform)
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
1544
1546 {
1548 }
1550 {
1552 }
1554 {
1555 // already in display space: pass through, swapping R <-> B (transform stays NULL)
1556 }
1557 else
1558 {
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 {
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. */
1578
1580
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
1596
1598 {
1600 }
1601 else
1602 {
1607 if(!IS_NULL_PTR(from) && !IS_NULL_PTR(to))
1608 {
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
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
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
1665 const cmsHTRANSFORM transform = self->transform_srgb_to_display;
1667 {
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 {
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++)
1693 row_in, row_out);
1694 }
1695
1696 g_free(scratch);
1698
1699 return TRUE;
1700}
1701
1702// caller holds _transforms_lock for writing
1704{
1706
1707 dt_free(color_profiles->xprofile_data);
1708 color_profiles->xprofile_data = tmp_data;
1709 color_profiles->xprofile_size = size;
1710
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)
1735
1736 // update cached transforms for color management of thumbnails
1738
1739 break;
1740 }
1741 }
1742 }
1743}
1744
1745
1747{
1748 dt_print(DT_DEBUG_COLORPROFILE, "[lcms2] error %d: %s\n", ErrorCode, text);
1749}
1750
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
1768{
1770 const gchar *d_name;
1771 char datadir[DT_PATH_MAX] = { 0 };
1772 char confdir[DT_PATH_MAX] = { 0 };
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);
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;
1814 }
1815
1818 }
1820 }
1821 g_dir_close(dir);
1823 }
1824 dt_free(dirname);
1825 return temp_profiles;
1826}
1827
1829{
1831
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
1945
1946 // read {userconfig,datadir}/color/in/*.icc, in this order.
1949 {
1951 prof->roles = DT_PROFILE_ROLE_INPUT;
1952 }
1953 res->profiles = g_list_concat(res->profiles, temp_profiles);
1954
1955 // read {conf,data}dir/color/out/*.icc
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
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
1973 {
1974 prof->roles |= DT_PROFILE_ROLE_WORKING;
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
2007
2010
2013
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 }
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{
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. */
2114
2115 int profile_changed = 0;
2116 CdWindow *window = CD_WINDOW(source);
2117 GError *error = NULL;
2120 {
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;
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 {
2143 "[color profile] colord gave us a new screen profile: '%s' (size: %" G_GSIZE_FORMAT ")\n", filename, size);
2144 }
2145 else
2146 {
2148 }
2149 }
2150 }
2151 }
2154
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
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. */
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
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 {
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 }
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.
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
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())
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:919