Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
conversion.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2025 Aurélien PIERRE.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
22
24#include "common/hash.h"
25#include "common/logging.h"
26#include "system/macros.h"
27#include "system/mem_alloc.h"
28#include "system/openmp.h"
29#include "system/simd.h"
31
32#include <lcms2.h>
33#include <math.h>
34#include <stdlib.h>
35#include <string.h>
36
50/* First field, deliberately. Issues #1212/#1216: two field crashes handed accessors a
51 * conversion pointer whose FIRST bytes were readable but which was not (or no longer) a
52 * conversion -- the fault only fired on a deeper member. A tag at offset zero is checkable
53 * on exactly the memory those pointers could read, so the accessors can refuse and name the
54 * pointer instead of dereferencing into the unmapped part. */
55#define DT_CONVERSION_MAGIC_LIVE 0xC0117E51u
56#define DT_CONVERSION_MAGIC_DEAD 0xDEADC017u
57
59{
60 uint32_t magic;
61
64
65 gboolean is_matrix;
66 gboolean has_clipping;
67
70
71 /* The source profile's own RGB -> XYZ, kept uncomposed so a caller can describe the space
72 * the buffer arrives in. Derived on both branches -- falling back to lcms2 says nothing
73 * about whether the SOURCE reduced to a matrix. */
76
77 float *lut_source[3];
78 float coeffs_source[3][3];
80
81 float *lut_target[3];
82 float coeffs_target[3][3];
84
85 cmsHTRANSFORM xform;
86 cmsHTRANSFORM clip_xform;
87 gboolean gamutcheck;
88
89 /* Handles this conversion created and must close. An endpoint resolved from the profile
90 * list is BORROWED -- the list owns it -- and never lands here. The only owned handle in
91 * practice is the quantised soft-proof copy. */
92 cmsHPROFILE owned[1];
94
95 /* What this conversion IS, as a number: see dt_colorspaces_conversion_identity(). */
97};
98
99/* Some built-in profiles carry a parametric TRC, which lcms2 reproduces exactly: a round trip
100 * through such a profile is the identity, and soft-proofing it shows nothing. Serialising and
101 * reopening quantises the curve into a sampled table, which is what makes the proof visible.
102 * Moved here verbatim from iop/colorout.c -- it is the only reason that module still named a
103 * cmsHPROFILE. */
104static cmsHPROFILE _quantise_profile(cmsHPROFILE profile)
105{
107 cmsHPROFILE quantised = NULL;
108
109 if(profile && cmsSaveProfileToMem(profile, NULL, &size))
110 {
111 char *data = malloc(size);
112 if(!IS_NULL_PTR(data))
113 {
114 if(cmsSaveProfileToMem(profile, data, &size)) quantised = cmsOpenProfileFromMem(data, size);
115 dt_free(data);
116 }
117 }
118
119 return quantised;
120}
121
122/* Resolve one endpoint to a handle. Returns the container when the identity came from the
123 * profile list, so the caller can hold that entry's lock across the derivation, or NULL when
124 * the endpoint carried an already-resolved image-owned profile (which the caller pins by
125 * outliving us) or could not be resolved at all. */
127 const dt_colorspaces_color_profile_t **entry)
128{
129 *entry = NULL;
130 if(IS_NULL_PTR(endpoint)) return NULL;
131
132 if(!IS_NULL_PTR(endpoint->resolved)) return endpoint->resolved->profile;
133
134 const dt_colorspaces_color_profile_t *const found
135 = dt_colorspaces_get_profile(endpoint->type, endpoint->filename ? endpoint->filename : "",
136 endpoint->role);
137 if(IS_NULL_PTR(found)) return NULL;
138
139 *entry = found;
140 return found->profile;
141}
142
143/* Fold one endpoint's identity into a running hash.
144 *
145 * `entry' is the registered profile the endpoint resolved to, or NULL when the caller supplied
146 * an already-resolved, image-owned profile (dt_colorspaces_endpoint_t::resolved) -- in that case
147 * the endpoint's own type/filename is the only name that profile has. Such a profile belongs to
148 * ONE image, so two images' embedded profiles share a name here; that is harmless because the
149 * rest of a pipeline's cache key already separates two images, and it is the caller's business
150 * either way. */
152 const dt_colorspaces_color_profile_t *const entry)
153{
155 const char *filename = "";
156
157 if(!IS_NULL_PTR(entry))
158 {
159 type = entry->type;
160 filename = entry->filename;
161 }
162 else if(!IS_NULL_PTR(endpoint))
163 {
164 type = endpoint->type;
165 if(!IS_NULL_PTR(endpoint->filename)) filename = endpoint->filename;
166 }
167
168 hash = dt_hash(hash, (const char *)&type, sizeof(type));
169 return dt_hash(hash, filename, strlen(filename));
170}
171
172/* lcms2 pixel format for a handle. colorin used to derive this from cmsGetColorSpace() and
173 * colorout from the type enum; the profile itself is the authority for both, and it answers
174 * the export case (an ICC embedded in the source file) that no type enum describes. */
175static cmsUInt32Number _format_for(cmsHPROFILE profile, gboolean *supported)
176{
177 *supported = TRUE;
178 if(IS_NULL_PTR(profile)) return TYPE_RGBA_FLT;
179
181 switch(space)
182 {
183 case cmsSigRgbData:
184 return TYPE_RGBA_FLT;
185 case cmsSigXYZData:
186 return TYPE_XYZA_FLT;
187 default:
188 /* The signature is four packed characters, most significant first. */
189 dt_print(DT_DEBUG_COLORPROFILE, "[colorspaces] profile color space `%c%c%c%c' not supported\n",
190 (char)(space >> 24), (char)(space >> 16), (char)(space >> 8), (char)(space));
191 *supported = FALSE;
192 return TYPE_RGBA_FLT;
193 }
194}
195
196static gboolean _allocate_curves(float *lut[3])
197{
198 for(int c = 0; c < 3; c++)
199 {
201 if(IS_NULL_PTR(lut[c])) return FALSE;
202 lut[c][0] = -1.0f; // linear until proven otherwise
203 }
204 return TRUE;
205}
206
207static void _free_curves(float *lut[3])
208{
209 for(int c = 0; c < 3; c++)
210 {
211 dt_free_align(lut[c]);
212 lut[c] = NULL;
213 }
214}
215
217 const dt_colorspaces_endpoint_t *const to,
218 const dt_colorspaces_endpoint_t *const clip,
219 const dt_colorspaces_endpoint_t *const proof,
220 const dt_iop_color_intent_t intent,
222{
223 if(IS_NULL_PTR(from) || IS_NULL_PTR(to)) return NULL;
224
225 /* dt_calloc_align, NOT calloc: this struct embeds dt_colormatrix_t members, whose declared
226 * 64-byte alignment the compiler is entitled to rely on with aligned vector loads. A plain
227 * calloc returns 16-aligned storage, and on any block that lands 16-mod-32 the first wide
228 * access to a matrix member faults -- issues #1212/#1216: all three field backtraces show a
229 * VALID conversion at an address = 16 (mod 32), crashing in conversion_source_matrix(). */
231 if(IS_NULL_PTR(conversion)) return NULL;
232 conversion->magic = DT_CONVERSION_MAGIC_LIVE;
233
234 /* Read before any profile entry lock is taken, so this never nests the settings lock inside
235 * one. It is folded into the identity below: it is the only thing that distinguishes two
236 * monitor profiles, which share the name DT_COLORSPACE_DISPLAY and differ only in content. */
239
240 conversion->from_type = from->type;
241 conversion->to_type = to->type;
242 conversion->matrix[0][0] = NAN;
243 conversion->clip_matrix[0][0] = NAN;
244
245 /* Resolve, THEN pin, and hold until the last derivation is done. The display profile's
246 * handle is replaced whenever the window lands on another monitor, so every read of it --
247 * the matrix extraction as much as cmsCreateTransform() -- has to sit inside the lock. The
248 * lock is per profile, so pinning the monitor profile here does not stand between an
249 * unrelated thumbnail conversion and the profile it uses. */
253 cmsHPROFILE to_profile = _resolve_endpoint(to, &to_entry);
254 cmsHPROFILE clip_profile = _resolve_endpoint(clip, &clip_entry);
256
261
262 /* Everything the failure path needs is declared before the first goto: jumping forward past
263 * an initialisation is legal C but leaves the object indeterminate, and -Wjump-misses-init
264 * is right to complain about it. */
267 gboolean proofing = FALSE;
268 gboolean matrix_allowed = FALSE;
269
271 if(!IS_NULL_PTR(clip) && IS_NULL_PTR(clip_profile)) goto give_up;
272
275
276 /* A quantised copy of the soft-proof profile is the one handle this object owns. If
277 * quantising fails we drop proofing rather than proof against something that would show
278 * nothing -- which is what this code has always done. */
280 {
282 if(!IS_NULL_PTR(proof_profile)) conversion->owned[conversion->n_owned++] = proof_profile;
283 }
284
287 conversion->has_clipping = !IS_NULL_PTR(clip_profile);
288
289 /* --- the matrix branch ---
290 *
291 * Both factors are the plain colorant matrices lcms2 reports, composed as
292 * `target_out x source_in`. That is the same expression, on the same handles, that
293 * iop/colorin.c and iop/colorout.c each built by hand, so the result is bit-identical to
294 * what they produced -- which matters, because these two modules decide the colour of every
295 * exported pixel. */
297
298 conversion->have_source_matrix
300 == 0;
301 if(!conversion->have_source_matrix) conversion->source_matrix[0][0] = NAN;
302
303 if(matrix_allowed && _allocate_curves(conversion->lut_source) && _allocate_curves(conversion->lut_target))
304 {
306 const gboolean have_source
308 conversion->lut_source[1], conversion->lut_source[2],
310 const gboolean have_target
312 conversion->lut_target[1], conversion->lut_target[2],
314
316 {
317 conversion->nonlinear_source
318 = dt_ioppr_init_unbounded_coeffs(conversion->lut_source[0], conversion->lut_source[1],
319 conversion->lut_source[2], conversion->coeffs_source[0],
320 conversion->coeffs_source[1], conversion->coeffs_source[2],
322 conversion->nonlinear_target
323 = dt_ioppr_init_unbounded_coeffs(conversion->lut_target[0], conversion->lut_target[1],
324 conversion->lut_target[2], conversion->coeffs_target[0],
325 conversion->coeffs_target[1], conversion->coeffs_target[2],
327
328 /* A curve stage the caller cannot execute is not a curve stage we may silently skip:
329 * dropping it would render the image through the wrong transfer function. Fall back to
330 * lcms2, which applies both sides itself. */
331 const gboolean source_ok = (conversion->nonlinear_source == 0) || (flags & DT_CONVERSION_SOURCE_CURVES);
332 const gboolean target_ok = (conversion->nonlinear_target == 0) || (flags & DT_CONVERSION_TARGET_CURVES);
333
334 if(source_ok && target_ok)
335 {
336 if(conversion->has_clipping)
337 {
338 /* The clipping space contributes its PRIMARIES only: the clamp bounds the colour to
339 * that gamut, it is not a round trip through its transfer function. Hence the two
340 * matrices and no curves. */
344 {
347 conversion->is_matrix = TRUE;
348 }
349 }
350 else
351 {
353 conversion->is_matrix = TRUE;
354 }
355 }
356 }
357 }
358
359 if(conversion->is_matrix)
360 {
361 /* Keep exactly the sides the caller declared it consumes, whether or not they turned out
362 * to be linear. A device kernel reads `curve[0] < 0` as "this channel is linear" and
363 * needs the buffer present to read it, so handing back NULL for a linear-but-requested
364 * side would make every caller invent a ramp to upload instead. The side nobody asked
365 * for is 768 KB of table nothing will read. */
368 }
369 else
370 {
371 _free_curves(conversion->lut_source);
372 _free_curves(conversion->lut_target);
373 conversion->nonlinear_source = conversion->nonlinear_target = 0;
374 conversion->matrix[0][0] = NAN;
375 conversion->clip_matrix[0][0] = NAN;
376
377 /* --- the lcms2 branch ---
378 *
379 * cmsCreateProofingTransform() with a NULL proof and no proofing flags IS an ordinary
380 * transform, so one call covers both cases. NOCACHE because these transforms are driven
381 * from OpenMP loops and lcms2's 1-pixel memo is per-transform mutable state. */
383 if(proofing)
384 {
387 }
388
389 if(conversion->has_clipping)
390 {
392 intent, transform_flags);
394 intent, transform_flags);
395 if(IS_NULL_PTR(conversion->xform) || IS_NULL_PTR(conversion->clip_xform))
396 {
397 if(!IS_NULL_PTR(conversion->xform)) cmsDeleteTransform(conversion->xform);
398 if(!IS_NULL_PTR(conversion->clip_xform)) cmsDeleteTransform(conversion->clip_xform);
399 conversion->xform = conversion->clip_xform = NULL;
400 conversion->has_clipping = FALSE;
401 }
402 }
403
404 if(IS_NULL_PTR(conversion->xform))
405 {
406 conversion->has_clipping = FALSE;
410 }
411
412 if(IS_NULL_PTR(conversion->xform)) goto give_up;
413 }
414
415 /* The identity of what was actually built -- see dt_colorspaces_conversion_identity(). It is
416 * computed HERE, from the finished object, rather than from the arguments: the branches above
417 * drop proofing when quantising fails and fall back from the matrix path to lcms2, so what the
418 * caller asked for and what it gets are not always the same thing, and it is what it GETS that
419 * decides the pixels.
420 *
421 * The curve LUTs are deliberately not hashed. They run to DT_CONVERSION_LUT_SAMPLES entries
422 * per channel -- 1.5 MB across both stages, on a path that runs once per pipeline resync --
423 * and they are a pure function of the profiles named just above, whose every mutation is
424 * either a new name or a new generation. The matrices are hashed because they are small and
425 * because doing so costs nothing. */
426 {
427 uint64_t identity = 5381;
428 identity = _hash_endpoint(identity, from, from_entry);
429 identity = _hash_endpoint(identity, to, to_entry);
430 identity = _hash_endpoint(identity, clip, clip_entry);
431 identity = _hash_endpoint(identity, proof, proof_entry);
432 identity = dt_hash(identity, (const char *)&intent, sizeof(intent));
433 identity = dt_hash(identity, (const char *)&flags, sizeof(flags));
434 identity = dt_hash(identity, (const char *)&settings.generation, sizeof(settings.generation));
435
436 identity = dt_hash(identity, (const char *)&conversion->is_matrix, sizeof(conversion->is_matrix));
437 identity = dt_hash(identity, (const char *)&conversion->has_clipping, sizeof(conversion->has_clipping));
438 identity = dt_hash(identity, (const char *)&conversion->gamutcheck, sizeof(conversion->gamutcheck));
439 identity = dt_hash(identity, (const char *)&conversion->have_source_matrix,
440 sizeof(conversion->have_source_matrix));
441 identity = dt_hash(identity, (const char *)conversion->matrix, sizeof(conversion->matrix));
442 identity = dt_hash(identity, (const char *)conversion->clip_matrix, sizeof(conversion->clip_matrix));
443 identity = dt_hash(identity, (const char *)conversion->source_matrix, sizeof(conversion->source_matrix));
444 identity = dt_hash(identity, (const char *)&conversion->nonlinear_source,
445 sizeof(conversion->nonlinear_source));
446 identity = dt_hash(identity, (const char *)&conversion->nonlinear_target,
447 sizeof(conversion->nonlinear_target));
448 identity = dt_hash(identity, (const char *)conversion->coeffs_source, sizeof(conversion->coeffs_source));
449 identity = dt_hash(identity, (const char *)conversion->coeffs_target, sizeof(conversion->coeffs_target));
450
451 /* Whether a curve stage exists at all is structure, not content. */
452 for(int c = 0; c < 3; c++)
453 {
454 const gboolean has_source = !IS_NULL_PTR(conversion->lut_source[c]);
455 const gboolean has_target = !IS_NULL_PTR(conversion->lut_target[c]);
456 identity = dt_hash(identity, (const char *)&has_source, sizeof(has_source));
457 identity = dt_hash(identity, (const char *)&has_target, sizeof(has_target));
458 }
459
460 conversion->identity = identity;
461 }
462
467 return conversion;
468
469give_up:
470 /* Unlocked against the entries the locks were taken on, never against a re-test of the
471 * conditions that produced them: those conditions have been rewritten by now. */
477 return NULL;
478}
479
489static gboolean _conversion_valid(const dt_colorspaces_conversion_t *const conversion, const char *caller)
490{
491 if(IS_NULL_PTR(conversion)) return FALSE;
492 if(conversion->magic == DT_CONVERSION_MAGIC_LIVE) return TRUE;
493
494 if(conversion->magic == DT_CONVERSION_MAGIC_DEAD)
495 dt_print(DT_DEBUG_ALWAYS, "[colorprofiles] %s was handed a FREED conversion %p -- refusing\n",
496 caller, (void *)conversion);
497 else
498 dt_print(DT_DEBUG_ALWAYS, "[colorprofiles] %s was handed %p which is NOT a conversion"
499 " (tag 0x%08x) -- refusing\n", caller, (void *)conversion, conversion->magic);
500 return FALSE;
501}
502
504{
505 return _conversion_valid(conversion, "conversion_identity") ? conversion->identity : 0;
506}
507
509{
510 if(IS_NULL_PTR(conversion) || IS_NULL_PTR(*conversion)) return;
511 dt_colorspaces_conversion_t *c = *conversion;
512
513 if(!_conversion_valid(c, "free_conversion"))
514 {
515 /* Freeing a pointer that is not a live conversion CORRUPTS THE HEAP -- it is how a
516 * clobbered field turns into a crash three modules away. Drop the reference instead. */
517 *conversion = NULL;
518 return;
519 }
520 c->magic = DT_CONVERSION_MAGIC_DEAD;
521
522 _free_curves(c->lut_source);
523 _free_curves(c->lut_target);
524
525 if(!IS_NULL_PTR(c->xform)) cmsDeleteTransform(c->xform);
526 if(!IS_NULL_PTR(c->clip_xform)) cmsDeleteTransform(c->clip_xform);
527
528 for(int k = 0; k < c->n_owned; k++) dt_colorspaces_cleanup_profile(c->owned[k]);
529
530 dt_free_align(c); // paired with dt_calloc_align: on Windows this memory is _aligned_malloc'd
531 *conversion = NULL;
532}
533
534/* --- apply ---------------------------------------------------------------- */
535
536static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t _clamp_unit(dt_aligned_pixel_simd_t v)
537{
538 v[0] = CLAMP(v[0], 0.0f, 1.0f);
539 v[1] = CLAMP(v[1], 0.0f, 1.0f);
540 v[2] = CLAMP(v[2], 0.0f, 1.0f);
541 v[3] = 0.0f;
542 return v;
543}
544
546static void _apply_target_curves(const dt_colorspaces_conversion_t *const c, float *const restrict out,
547 const size_t npixels)
548{
549 const float *const restrict lut0 = c->lut_target[0];
550 const float *const restrict lut1 = c->lut_target[1];
551 const float *const restrict lut2 = c->lut_target[2];
552 const int run_lut0 = lut0[0] >= 0.0f;
553 const int run_lut1 = lut1[0] >= 0.0f;
554 const int run_lut2 = lut2[0] >= 0.0f;
555 if(!(run_lut0 || run_lut1 || run_lut2)) return;
556
557 const float *const coeff0 = c->coeffs_target[0];
558 const float *const coeff1 = c->coeffs_target[1];
559 const float *const coeff2 = c->coeffs_target[2];
560
561 if(run_lut0 && run_lut1 && run_lut2)
562 {
564 for(size_t k = 0; k < npixels; k++)
565 {
566 const size_t idx = 4 * k;
570 }
571 }
572 else
573 {
575 for(size_t k = 0; k < npixels; k++)
576 {
577 const size_t idx = 4 * k;
581 }
582 }
583}
584
585/* The target curves are a SEPARATE pass over the output buffer, not a stage fused into the
586 * matrix loop. Fusing them is the obvious simplification and it is wrong: the matrix loop is
587 * `__OMP_PARALLEL_FOR_SIMD__`, so the compiler vectorises it across pixels and contracts the
588 * multiply-adds into FMAs, and folding a table lookup into the loop body changes what it can
589 * contract. Measured: the fused form moved 747159 of 2549760 exported pixels by one LSB on a
590 * raw. One LSB is small; a colour-management change that moves pixels for no stated reason is
591 * not, so the structure stays as the two modules had it. */
593static void _apply_matrix(const dt_colorspaces_conversion_t *const c, const float *const restrict in,
594 float *const restrict out, const size_t npixels,
596{
598 transpose_3xSSE(c->matrix, m);
599 transpose_3xSSE(c->clip_matrix, cm);
600 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(m, 0);
601 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(m, 1);
602 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(m, 2);
603 const dt_aligned_pixel_simd_t c0 = dt_colormatrix_row_to_simd(cm, 0);
604 const dt_aligned_pixel_simd_t c1 = dt_colormatrix_row_to_simd(cm, 1);
605 const dt_aligned_pixel_simd_t c2 = dt_colormatrix_row_to_simd(cm, 2);
606
607 /* Gate on the buffer, not on the count: the side the caller did not ask for is released
608 * even when the profile turned out to have curves, so `nonlinear_target > 0` can be true
609 * with nothing to read. */
610 const gboolean decode = !IS_NULL_PTR(c->lut_source[0]) && c->nonlinear_source > 0;
611 const gboolean encode = !IS_NULL_PTR(c->lut_target[0]) && c->nonlinear_target > 0;
612 const gboolean clipping = c->has_clipping;
613
614 if(!decode && IS_NULL_PTR(hook))
615 {
616 /* Nothing to do per pixel but the matrix. Non-temporal stores unless a second pass is
617 * about to read this buffer straight back, which is what they are bad at. */
618 if(encode)
619 {
621 for(size_t k = 0; k < npixels; k++)
622 {
623 const size_t idx = 4 * k;
624 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(in + idx), m0, m1, m2);
627 }
628 }
629 else
630 {
632 for(size_t k = 0; k < npixels; k++)
633 {
634 const size_t idx = 4 * k;
635 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(in + idx), m0, m1, m2);
638 }
640 }
641 }
642 else
643 {
644 const float *const lut_r = decode ? c->lut_source[0] : NULL;
645 const float *const lut_g = decode ? c->lut_source[1] : NULL;
646 const float *const lut_b = decode ? c->lut_source[2] : NULL;
647
649 for(size_t k = 0; k < npixels; k++)
650 {
651 const float *const in_pixel = in + 4 * k;
652 float *const out_pixel = out + 4 * k;
653
655 /* A channel marked linear is passed through rather than sampled: that is what keeps
656 * values above white unbounded instead of clipped at the top of the table. */
657 staged[0] = (decode && lut_r[0] >= 0.0f)
658 ? dt_ioppr_eval_trc(in_pixel[0], lut_r, c->coeffs_source[0], DT_CONVERSION_LUT_SAMPLES)
659 : in_pixel[0];
660 staged[1] = (decode && lut_g[0] >= 0.0f)
661 ? dt_ioppr_eval_trc(in_pixel[1], lut_g, c->coeffs_source[1], DT_CONVERSION_LUT_SAMPLES)
662 : in_pixel[1];
663 staged[2] = (decode && lut_b[0] >= 0.0f)
664 ? dt_ioppr_eval_trc(in_pixel[2], lut_b, c->coeffs_source[2], DT_CONVERSION_LUT_SAMPLES)
665 : in_pixel[2];
666 staged[3] = 0.0f;
667
669
670 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(staged), m0, m1, m2);
672
673 if(encode)
675 else
677 }
679 }
680
682}
683
685static void _apply_lcms2(const dt_colorspaces_conversion_t *const c, const float *const in, float *const out,
686 const size_t width, const size_t height, const dt_colorspaces_conversion_hook_t hook)
687{
688 /* Alias the transforms outside the parallel region and share the aliases explicitly,
689 * rather than reaching through the struct from inside the loop. */
690 const cmsHTRANSFORM xform = c->xform;
691 const cmsHTRANSFORM clip_xform = c->clip_xform;
692 const gboolean clipping = c->has_clipping;
693 const gboolean gamutcheck = c->gamutcheck;
694
696 for(size_t row = 0; row < height; row++)
697 {
698 const float *const restrict source = in + 4 * row * width;
699 float *const restrict target = out + 4 * row * width;
700
701 if(!IS_NULL_PTR(hook))
702 {
703 /* The hook runs on the values just before the colour conversion proper. lcms2 decodes
704 * internally, so here that means before cmsDoTransform -- which is where colorin has
705 * always applied it. Staged through the output row, as it was. */
706 for(size_t j = 0; j < width; j++)
707 {
708 hook(source + 4 * j, target + 4 * j);
709 target[4 * j + 3] = 0.0f;
710 }
711 dt_colorspaces_transform_rgba_float_row(xform, target, target, width);
712 }
713 else
714 {
715 dt_colorspaces_transform_rgba_float_row(xform, source, target, width);
716 }
717
718 if(clipping)
719 {
720 float *const restrict clipped = target;
722 for(size_t j = 0; j < width; j++)
723 {
724 for(int ch = 0; ch < 3; ch++) clipped[4 * j + ch] = CLAMP(clipped[4 * j + ch], 0.0f, 1.0f);
725 }
726 dt_colorspaces_transform_rgba_float_row(clip_xform, target, target, width);
727 }
728
729 if(gamutcheck)
730 {
731 for(size_t j = 0; j < width; j++)
732 {
733 if(target[4 * j + 0] < 0.0f || target[4 * j + 1] < 0.0f || target[4 * j + 2] < 0.0f)
734 {
735 target[4 * j + 0] = 0.0f;
736 target[4 * j + 1] = 1.0f;
737 target[4 * j + 2] = 1.0f;
738 }
739 }
740 }
741 }
742}
743
745 const float *const in, float *const out, const size_t width,
746 const size_t height, const dt_colorspaces_conversion_hook_t hook)
747{
748 if(IS_NULL_PTR(in) || IS_NULL_PTR(out)) return;
749 if(!_conversion_valid(conversion, "apply_conversion"))
750 {
751 /* Passthrough, not garbage: the caller owns a destination buffer that WILL be consumed. */
752 if(in != out) memcpy(out, in, width * height * 4 * sizeof(float));
753 return;
754 }
755
756 if(conversion->is_matrix)
757 _apply_matrix(conversion, in, out, width * height, hook);
758 else
759 _apply_lcms2(conversion, in, out, width, height, hook);
760}
761
762void dt_colorspaces_apply_conversion(const dt_colorspaces_conversion_t *const conversion, const float *const in,
763 float *const out, const size_t width, const size_t height)
764{
766}
767
768/* --- what a device kernel needs ------------------------------------------- */
769
771{
772 return _conversion_valid(conversion, "conversion_is_matrix") && conversion->is_matrix;
773}
774
776{
777 return _conversion_valid(conversion, "conversion_has_clipping") && conversion->has_clipping;
778}
779
782{
783 if(!_conversion_valid(conversion, "conversion_matrix") || !conversion->is_matrix) return FALSE;
784 memcpy(matrix, conversion->matrix, sizeof(dt_colormatrix_t));
785 return TRUE;
786}
787
790{
791 if(!_conversion_valid(conversion, "conversion_source_matrix") || !conversion->have_source_matrix) return FALSE;
792 memcpy(matrix, conversion->source_matrix, sizeof(dt_colormatrix_t));
793 return TRUE;
794}
795
798{
799 if(!_conversion_valid(conversion, "conversion_clip_matrix") || !conversion->is_matrix || !conversion->has_clipping) return FALSE;
800 memcpy(matrix, conversion->clip_matrix, sizeof(dt_colormatrix_t));
801 return TRUE;
802}
803
805 const int channel)
806{
807 if(!_conversion_valid(conversion, "conversion_source_curve") || channel < 0 || channel > 2) return NULL;
808 return conversion->lut_source[channel];
809}
810
812 const int channel)
813{
814 if(!_conversion_valid(conversion, "conversion_target_curve") || channel < 0 || channel > 2) return NULL;
815 return conversion->lut_target[channel];
816}
817
819{
820 if(!_conversion_valid(conversion, "conversion_source_coeffs") || IS_NULL_PTR(conversion->lut_source[0])) return NULL;
821 return &conversion->coeffs_source[0][0];
822}
823
825{
826 if(!_conversion_valid(conversion, "conversion_target_coeffs") || IS_NULL_PTR(conversion->lut_target[0])) return NULL;
827 return &conversion->coeffs_target[0][0];
828}
829
830// clang-format off
831// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
832// vim: shiftwidth=2 expandtab tabstop=2 cindent
833// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
834// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
int dt_ioppr_init_unbounded_coeffs(float *const lutr, float *const lutg, float *const lutb, float *const unbounded_coeffsr, float *const unbounded_coeffsg, float *const unbounded_coeffsb, const int lutsize)
Fit the power-law continuation of three tone curves past white.
The colour-profile struct and the maths over it: the derived matrix/LUT engine.
const float *const lut
const float v
void dt_colorspaces_unlock_profile(const dt_colorspaces_color_profile_t *const profile)
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.
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.
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...
void dt_colorspaces_lock_profile(const dt_colorspaces_color_profile_t *const profile)
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.
void dt_colorprofiles_get_settings(dt_colorprofiles_settings_t *const out)
Copy the current settings into caller-provided storage, under one lock.
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.
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
static const int row
const dt_colormatrix_t matrix
static cmsHPROFILE _resolve_endpoint(const dt_colorspaces_endpoint_t *const endpoint, const dt_colorspaces_color_profile_t **entry)
Definition conversion.c:126
const float * dt_colorspaces_conversion_source_coeffs(const dt_colorspaces_conversion_t *const conversion)
The 3x3 power-law fits extrapolating the source curves past white, as one flat array of 9 floats in c...
Definition conversion.c:818
static gboolean _allocate_curves(float *lut[3])
Definition conversion.c:196
void dt_colorspaces_apply_conversion_hooked(const dt_colorspaces_conversion_t *const conversion, const float *const in, float *const out, const size_t width, const size_t height, const dt_colorspaces_conversion_hook_t hook)
dt_colorspaces_apply_conversion() with a per-pixel hook. See dt_colorspaces_conversion_hook_t for why...
Definition conversion.c:744
gboolean dt_colorspaces_conversion_has_clipping(const dt_colorspaces_conversion_t *const conversion)
Whether the conversion has a gamut-clipping stage, i.e. whether a clip endpoint was given AND survive...
Definition conversion.c:775
gboolean dt_colorspaces_conversion_matrix(const dt_colorspaces_conversion_t *const conversion, dt_colormatrix_t matrix)
The composed source-to-target matrix, row-major.
Definition conversion.c:780
static uint64_t _hash_endpoint(uint64_t hash, const dt_colorspaces_endpoint_t *const endpoint, const dt_colorspaces_color_profile_t *const entry)
Definition conversion.c:151
const float * dt_colorspaces_conversion_target_curve(const dt_colorspaces_conversion_t *const conversion, const int channel)
One channel of the target encoding curves. Same contract as dt_colorspaces_conversion_source_curve().
Definition conversion.c:811
static __DT_CLONE_TARGETS__ void _apply_target_curves(const dt_colorspaces_conversion_t *const c, float *const restrict out, const size_t npixels)
Definition conversion.c:546
gboolean dt_colorspaces_conversion_is_matrix(const dt_colorspaces_conversion_t *const conversion)
Whether the conversion reduced to matrices and curves, and can therefore be run by a device kernel at...
Definition conversion.c:770
const float * dt_colorspaces_conversion_target_coeffs(const dt_colorspaces_conversion_t *const conversion)
The same fits for the target curves. Same contract.
Definition conversion.c:824
static __DT_CLONE_TARGETS__ void _apply_matrix(const dt_colorspaces_conversion_t *const c, const float *const restrict in, float *const restrict out, const size_t npixels, const dt_colorspaces_conversion_hook_t hook)
Definition conversion.c:593
static __DT_CLONE_TARGETS__ void _apply_lcms2(const dt_colorspaces_conversion_t *const c, const float *const in, float *const out, const size_t width, const size_t height, const dt_colorspaces_conversion_hook_t hook)
Definition conversion.c:685
static cmsUInt32Number _format_for(cmsHPROFILE profile, gboolean *supported)
Definition conversion.c:175
#define DT_CONVERSION_MAGIC_LIVE
Definition conversion.c:55
uint64_t dt_colorspaces_conversion_identity(const dt_colorspaces_conversion_t *const conversion)
What this conversion IS, as a number: equal for two conversions that render the same pixels,...
Definition conversion.c:503
gboolean dt_colorspaces_conversion_source_matrix(const dt_colorspaces_conversion_t *const conversion, dt_colormatrix_t matrix)
The SOURCE profile's own RGB -> XYZ (D50) matrix, before composition.
Definition conversion.c:788
static void _free_curves(float *lut[3])
Definition conversion.c:207
static gboolean _conversion_valid(const dt_colorspaces_conversion_t *const conversion, const char *caller)
Is this pointer a live conversion?
Definition conversion.c:489
const float * dt_colorspaces_conversion_source_curve(const dt_colorspaces_conversion_t *const conversion, const int channel)
One channel of the source decoding curves, DT_CONVERSION_LUT_SAMPLES entries.
Definition conversion.c:804
static cmsHPROFILE _quantise_profile(cmsHPROFILE profile)
Definition conversion.c:104
void dt_colorspaces_free_conversion(dt_colorspaces_conversion_t **conversion)
Release a conversion and NULL the caller's pointer.
Definition conversion.c:508
gboolean dt_colorspaces_conversion_clip_matrix(const dt_colorspaces_conversion_t *const conversion, dt_colormatrix_t matrix)
The clip-to-target matrix, the second leg of a clipping conversion.
Definition conversion.c:796
dt_colorspaces_conversion_t * dt_colorspaces_prepare_conversion(const dt_colorspaces_endpoint_t *const from, const dt_colorspaces_endpoint_t *const to, const dt_colorspaces_endpoint_t *const clip, const dt_colorspaces_endpoint_t *const proof, const dt_iop_color_intent_t intent, const dt_colorspaces_conversion_flags_t flags)
Build a conversion from from to to. The expensive call; do it once.
Definition conversion.c:216
void dt_colorspaces_apply_conversion(const dt_colorspaces_conversion_t *const conversion, const float *const in, float *const out, const size_t width, const size_t height)
Convert a 4-channel float image through a prepared conversion.
Definition conversion.c:762
#define DT_CONVERSION_MAGIC_DEAD
Definition conversion.c:56
A PREPARED CONVERSION: everything needed to turn pixels in one colour space into pixels in another,...
dt_colorspaces_conversion_flags_t
What the caller wants, and what the caller can consume.
Definition conversion.h:118
@ DT_CONVERSION_FORCE_LCMS2
Never take the matrix path, even when both profiles reduce to matrices. Backs the plugins/lighttable/...
Definition conversion.h:122
@ DT_CONVERSION_SOURCE_CURVES
The caller can apply the SOURCE profile's decoding curves before the matrix. Without this,...
Definition conversion.h:125
@ DT_CONVERSION_GAMUTCHECK
Mark out-of-gamut pixels rather than merely proofing them. Requires a soft-proof endpoint,...
Definition conversion.h:131
@ DT_CONVERSION_TARGET_CURVES
The caller can apply the TARGET profile's encoding curves after the matrix. Without this,...
Definition conversion.h:128
void(* dt_colorspaces_conversion_hook_t)(const float *const in, float *const out)
A per-pixel hook run between the source curves and the colour conversion proper.
Definition conversion.h:191
#define DT_CONVERSION_LUT_SAMPLES
Entries per tone curve. Callers that upload the curves to a device need this to size the buffer; the ...
Definition conversion.h:67
int supported(struct dt_imageio_module_storage_t *storage, struct dt_imageio_module_format_t *format)
Definition example.c:273
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
hash
Definition layers.h:90
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_COLORPROFILE
Definition logging.h:79
@ DT_DEBUG_ALWAYS
Definition logging.h:49
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
float *const restrict const size_t const size_t ch
#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
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:69
static void dt_colormatrix_mul(dt_colormatrix_t dst, const dt_colormatrix_t m1, const dt_colormatrix_t m2)
Definition matrices.h:167
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#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_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define dt_omploop_sfence()
Definition openmp.h:164
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
dt_iop_color_intent_t
ICC rendering intent, as stored in iop params and in conf.
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
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
unsigned __int64 uint64_t
Definition strptime.c:75
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_color_profile_type_t type
filename is only used for type DT_COLORSPACE_FILE
dt_colormatrix_t clip_matrix
clip -> target; meaningless unless has_clipping
Definition conversion.c:69
dt_colormatrix_t source_matrix
Definition conversion.c:74
dt_colorspaces_color_profile_type_t from_type
Definition conversion.c:62
dt_colormatrix_t matrix
source -> target, or source -> clip when clipping
Definition conversion.c:68
cmsHTRANSFORM clip_xform
clip -> target; NULL unless has_clipping
Definition conversion.c:86
float coeffs_source[3][3]
9 contiguous floats: what the kernels upload verbatim
Definition conversion.c:78
cmsHTRANSFORM xform
source -> target, or source -> clip when clipping
Definition conversion.c:85
float * lut_source[3]
NULL when this conversion has no source curve stage.
Definition conversion.c:77
dt_colorspaces_color_profile_type_t to_type
Definition conversion.c:63
One end of a conversion: a profile, named either by identity or by handing over one this module alrea...
Definition conversion.h:93
#define __DT_CLONE_TARGETS__