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/logging.h"
25#include "system/macros.h"
26#include "system/mem_alloc.h"
27#include "system/openmp.h"
28#include "system/simd.h"
30
31#include <lcms2.h>
32#include <math.h>
33#include <stdlib.h>
34#include <string.h>
35
50{
53
54 gboolean is_matrix;
55 gboolean has_clipping;
56
59
60 /* The source profile's own RGB -> XYZ, kept uncomposed so a caller can describe the space
61 * the buffer arrives in. Derived on both branches -- falling back to lcms2 says nothing
62 * about whether the SOURCE reduced to a matrix. */
65
66 float *lut_source[3];
67 float coeffs_source[3][3];
69
70 float *lut_target[3];
71 float coeffs_target[3][3];
73
74 cmsHTRANSFORM xform;
75 cmsHTRANSFORM clip_xform;
76 gboolean gamutcheck;
77
78 /* Handles this conversion created and must close. An endpoint resolved from the profile
79 * list is BORROWED -- the list owns it -- and never lands here. The only owned handle in
80 * practice is the quantised soft-proof copy. */
81 cmsHPROFILE owned[1];
83};
84
85/* Some built-in profiles carry a parametric TRC, which lcms2 reproduces exactly: a round trip
86 * through such a profile is the identity, and soft-proofing it shows nothing. Serialising and
87 * reopening quantises the curve into a sampled table, which is what makes the proof visible.
88 * Moved here verbatim from iop/colorout.c -- it is the only reason that module still named a
89 * cmsHPROFILE. */
90static cmsHPROFILE _quantise_profile(cmsHPROFILE profile)
91{
93 cmsHPROFILE quantised = NULL;
94
95 if(profile && cmsSaveProfileToMem(profile, NULL, &size))
96 {
97 char *data = malloc(size);
98 if(!IS_NULL_PTR(data))
99 {
100 if(cmsSaveProfileToMem(profile, data, &size)) quantised = cmsOpenProfileFromMem(data, size);
101 dt_free(data);
102 }
103 }
104
105 return quantised;
106}
107
108/* Resolve one endpoint to a handle. Returns the container when the identity came from the
109 * profile list, so the caller can hold that entry's lock across the derivation, or NULL when
110 * the endpoint carried an already-resolved image-owned profile (which the caller pins by
111 * outliving us) or could not be resolved at all. */
113 const dt_colorspaces_color_profile_t **entry)
114{
115 *entry = NULL;
116 if(IS_NULL_PTR(endpoint)) return NULL;
117
118 if(!IS_NULL_PTR(endpoint->resolved)) return endpoint->resolved->profile;
119
120 const dt_colorspaces_color_profile_t *const found
121 = dt_colorspaces_get_profile(endpoint->type, endpoint->filename ? endpoint->filename : "",
122 endpoint->role);
123 if(IS_NULL_PTR(found)) return NULL;
124
125 *entry = found;
126 return found->profile;
127}
128
129/* lcms2 pixel format for a handle. colorin used to derive this from cmsGetColorSpace() and
130 * colorout from the type enum; the profile itself is the authority for both, and it answers
131 * the export case (an ICC embedded in the source file) that no type enum describes. */
132static cmsUInt32Number _format_for(cmsHPROFILE profile, gboolean *supported)
133{
134 *supported = TRUE;
135 if(IS_NULL_PTR(profile)) return TYPE_RGBA_FLT;
136
138 switch(space)
139 {
140 case cmsSigRgbData:
141 return TYPE_RGBA_FLT;
142 case cmsSigXYZData:
143 return TYPE_XYZA_FLT;
144 default:
145 /* The signature is four packed characters, most significant first. */
146 dt_print(DT_DEBUG_COLORPROFILE, "[colorspaces] profile color space `%c%c%c%c' not supported\n",
147 (char)(space >> 24), (char)(space >> 16), (char)(space >> 8), (char)(space));
148 *supported = FALSE;
149 return TYPE_RGBA_FLT;
150 }
151}
152
153static gboolean _allocate_curves(float *lut[3])
154{
155 for(int c = 0; c < 3; c++)
156 {
158 if(IS_NULL_PTR(lut[c])) return FALSE;
159 lut[c][0] = -1.0f; // linear until proven otherwise
160 }
161 return TRUE;
162}
163
164static void _free_curves(float *lut[3])
165{
166 for(int c = 0; c < 3; c++)
167 {
168 dt_free_align(lut[c]);
169 lut[c] = NULL;
170 }
171}
172
174 const dt_colorspaces_endpoint_t *const to,
175 const dt_colorspaces_endpoint_t *const clip,
176 const dt_colorspaces_endpoint_t *const proof,
177 const dt_iop_color_intent_t intent,
179{
180 if(IS_NULL_PTR(from) || IS_NULL_PTR(to)) return NULL;
181
183 if(IS_NULL_PTR(conversion)) return NULL;
184
185 conversion->from_type = from->type;
186 conversion->to_type = to->type;
187 conversion->matrix[0][0] = NAN;
188 conversion->clip_matrix[0][0] = NAN;
189
190 /* Resolve, THEN pin, and hold until the last derivation is done. The display profile's
191 * handle is replaced whenever the window lands on another monitor, so every read of it --
192 * the matrix extraction as much as cmsCreateTransform() -- has to sit inside the lock. The
193 * lock is per profile, so pinning the monitor profile here does not stand between an
194 * unrelated thumbnail conversion and the profile it uses. */
198 cmsHPROFILE to_profile = _resolve_endpoint(to, &to_entry);
199 cmsHPROFILE clip_profile = _resolve_endpoint(clip, &clip_entry);
201
206
207 /* Everything the failure path needs is declared before the first goto: jumping forward past
208 * an initialisation is legal C but leaves the object indeterminate, and -Wjump-misses-init
209 * is right to complain about it. */
212 gboolean proofing = FALSE;
213 gboolean matrix_allowed = FALSE;
214
216 if(!IS_NULL_PTR(clip) && IS_NULL_PTR(clip_profile)) goto give_up;
217
220
221 /* A quantised copy of the soft-proof profile is the one handle this object owns. If
222 * quantising fails we drop proofing rather than proof against something that would show
223 * nothing -- which is what this code has always done. */
225 {
227 if(!IS_NULL_PTR(proof_profile)) conversion->owned[conversion->n_owned++] = proof_profile;
228 }
229
232 conversion->has_clipping = !IS_NULL_PTR(clip_profile);
233
234 /* --- the matrix branch ---
235 *
236 * Both factors are the plain colorant matrices lcms2 reports, composed as
237 * `target_out x source_in`. That is the same expression, on the same handles, that
238 * iop/colorin.c and iop/colorout.c each built by hand, so the result is bit-identical to
239 * what they produced -- which matters, because these two modules decide the colour of every
240 * exported pixel. */
242
243 conversion->have_source_matrix
245 == 0;
246 if(!conversion->have_source_matrix) conversion->source_matrix[0][0] = NAN;
247
248 if(matrix_allowed && _allocate_curves(conversion->lut_source) && _allocate_curves(conversion->lut_target))
249 {
251 const gboolean have_source
253 conversion->lut_source[1], conversion->lut_source[2],
255 const gboolean have_target
257 conversion->lut_target[1], conversion->lut_target[2],
259
261 {
262 conversion->nonlinear_source
263 = dt_ioppr_init_unbounded_coeffs(conversion->lut_source[0], conversion->lut_source[1],
264 conversion->lut_source[2], conversion->coeffs_source[0],
265 conversion->coeffs_source[1], conversion->coeffs_source[2],
267 conversion->nonlinear_target
268 = dt_ioppr_init_unbounded_coeffs(conversion->lut_target[0], conversion->lut_target[1],
269 conversion->lut_target[2], conversion->coeffs_target[0],
270 conversion->coeffs_target[1], conversion->coeffs_target[2],
272
273 /* A curve stage the caller cannot execute is not a curve stage we may silently skip:
274 * dropping it would render the image through the wrong transfer function. Fall back to
275 * lcms2, which applies both sides itself. */
276 const gboolean source_ok = (conversion->nonlinear_source == 0) || (flags & DT_CONVERSION_SOURCE_CURVES);
277 const gboolean target_ok = (conversion->nonlinear_target == 0) || (flags & DT_CONVERSION_TARGET_CURVES);
278
279 if(source_ok && target_ok)
280 {
281 if(conversion->has_clipping)
282 {
283 /* The clipping space contributes its PRIMARIES only: the clamp bounds the colour to
284 * that gamut, it is not a round trip through its transfer function. Hence the two
285 * matrices and no curves. */
289 {
292 conversion->is_matrix = TRUE;
293 }
294 }
295 else
296 {
298 conversion->is_matrix = TRUE;
299 }
300 }
301 }
302 }
303
304 if(conversion->is_matrix)
305 {
306 /* Keep exactly the sides the caller declared it consumes, whether or not they turned out
307 * to be linear. A device kernel reads `curve[0] < 0` as "this channel is linear" and
308 * needs the buffer present to read it, so handing back NULL for a linear-but-requested
309 * side would make every caller invent a ramp to upload instead. The side nobody asked
310 * for is 768 KB of table nothing will read. */
313 }
314 else
315 {
316 _free_curves(conversion->lut_source);
317 _free_curves(conversion->lut_target);
318 conversion->nonlinear_source = conversion->nonlinear_target = 0;
319 conversion->matrix[0][0] = NAN;
320 conversion->clip_matrix[0][0] = NAN;
321
322 /* --- the lcms2 branch ---
323 *
324 * cmsCreateProofingTransform() with a NULL proof and no proofing flags IS an ordinary
325 * transform, so one call covers both cases. NOCACHE because these transforms are driven
326 * from OpenMP loops and lcms2's 1-pixel memo is per-transform mutable state. */
328 if(proofing)
329 {
332 }
333
334 if(conversion->has_clipping)
335 {
337 intent, transform_flags);
339 intent, transform_flags);
340 if(IS_NULL_PTR(conversion->xform) || IS_NULL_PTR(conversion->clip_xform))
341 {
342 if(!IS_NULL_PTR(conversion->xform)) cmsDeleteTransform(conversion->xform);
343 if(!IS_NULL_PTR(conversion->clip_xform)) cmsDeleteTransform(conversion->clip_xform);
344 conversion->xform = conversion->clip_xform = NULL;
345 conversion->has_clipping = FALSE;
346 }
347 }
348
349 if(IS_NULL_PTR(conversion->xform))
350 {
351 conversion->has_clipping = FALSE;
355 }
356
357 if(IS_NULL_PTR(conversion->xform)) goto give_up;
358 }
359
364 return conversion;
365
366give_up:
367 /* Unlocked against the entries the locks were taken on, never against a re-test of the
368 * conditions that produced them: those conditions have been rewritten by now. */
374 return NULL;
375}
376
378{
379 if(IS_NULL_PTR(conversion) || IS_NULL_PTR(*conversion)) return;
380 dt_colorspaces_conversion_t *c = *conversion;
381
382 _free_curves(c->lut_source);
383 _free_curves(c->lut_target);
384
385 if(!IS_NULL_PTR(c->xform)) cmsDeleteTransform(c->xform);
386 if(!IS_NULL_PTR(c->clip_xform)) cmsDeleteTransform(c->clip_xform);
387
388 for(int k = 0; k < c->n_owned; k++) dt_colorspaces_cleanup_profile(c->owned[k]);
389
390 free(c);
391 *conversion = NULL;
392}
393
394/* --- apply ---------------------------------------------------------------- */
395
396static inline __attribute__((always_inline)) dt_aligned_pixel_simd_t _clamp_unit(dt_aligned_pixel_simd_t v)
397{
398 v[0] = CLAMP(v[0], 0.0f, 1.0f);
399 v[1] = CLAMP(v[1], 0.0f, 1.0f);
400 v[2] = CLAMP(v[2], 0.0f, 1.0f);
401 v[3] = 0.0f;
402 return v;
403}
404
406static void _apply_target_curves(const dt_colorspaces_conversion_t *const c, float *const restrict out,
407 const size_t npixels)
408{
409 const float *const restrict lut0 = c->lut_target[0];
410 const float *const restrict lut1 = c->lut_target[1];
411 const float *const restrict lut2 = c->lut_target[2];
412 const int run_lut0 = lut0[0] >= 0.0f;
413 const int run_lut1 = lut1[0] >= 0.0f;
414 const int run_lut2 = lut2[0] >= 0.0f;
415 if(!(run_lut0 || run_lut1 || run_lut2)) return;
416
417 const float *const coeff0 = c->coeffs_target[0];
418 const float *const coeff1 = c->coeffs_target[1];
419 const float *const coeff2 = c->coeffs_target[2];
420
421 if(run_lut0 && run_lut1 && run_lut2)
422 {
424 for(size_t k = 0; k < npixels; k++)
425 {
426 const size_t idx = 4 * k;
430 }
431 }
432 else
433 {
435 for(size_t k = 0; k < npixels; k++)
436 {
437 const size_t idx = 4 * k;
441 }
442 }
443}
444
445/* The target curves are a SEPARATE pass over the output buffer, not a stage fused into the
446 * matrix loop. Fusing them is the obvious simplification and it is wrong: the matrix loop is
447 * `__OMP_PARALLEL_FOR_SIMD__`, so the compiler vectorises it across pixels and contracts the
448 * multiply-adds into FMAs, and folding a table lookup into the loop body changes what it can
449 * contract. Measured: the fused form moved 747159 of 2549760 exported pixels by one LSB on a
450 * raw. One LSB is small; a colour-management change that moves pixels for no stated reason is
451 * not, so the structure stays as the two modules had it. */
453static void _apply_matrix(const dt_colorspaces_conversion_t *const c, const float *const restrict in,
454 float *const restrict out, const size_t npixels,
456{
458 transpose_3xSSE(c->matrix, m);
459 transpose_3xSSE(c->clip_matrix, cm);
460 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(m, 0);
461 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(m, 1);
462 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(m, 2);
463 const dt_aligned_pixel_simd_t c0 = dt_colormatrix_row_to_simd(cm, 0);
464 const dt_aligned_pixel_simd_t c1 = dt_colormatrix_row_to_simd(cm, 1);
465 const dt_aligned_pixel_simd_t c2 = dt_colormatrix_row_to_simd(cm, 2);
466
467 /* Gate on the buffer, not on the count: the side the caller did not ask for is released
468 * even when the profile turned out to have curves, so `nonlinear_target > 0` can be true
469 * with nothing to read. */
470 const gboolean decode = !IS_NULL_PTR(c->lut_source[0]) && c->nonlinear_source > 0;
471 const gboolean encode = !IS_NULL_PTR(c->lut_target[0]) && c->nonlinear_target > 0;
472 const gboolean clipping = c->has_clipping;
473
474 if(!decode && IS_NULL_PTR(hook))
475 {
476 /* Nothing to do per pixel but the matrix. Non-temporal stores unless a second pass is
477 * about to read this buffer straight back, which is what they are bad at. */
478 if(encode)
479 {
481 for(size_t k = 0; k < npixels; k++)
482 {
483 const size_t idx = 4 * k;
484 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(in + idx), m0, m1, m2);
487 }
488 }
489 else
490 {
492 for(size_t k = 0; k < npixels; k++)
493 {
494 const size_t idx = 4 * k;
495 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(in + idx), m0, m1, m2);
498 }
500 }
501 }
502 else
503 {
504 const float *const lut_r = decode ? c->lut_source[0] : NULL;
505 const float *const lut_g = decode ? c->lut_source[1] : NULL;
506 const float *const lut_b = decode ? c->lut_source[2] : NULL;
507
509 for(size_t k = 0; k < npixels; k++)
510 {
511 const float *const in_pixel = in + 4 * k;
512 float *const out_pixel = out + 4 * k;
513
515 /* A channel marked linear is passed through rather than sampled: that is what keeps
516 * values above white unbounded instead of clipped at the top of the table. */
517 staged[0] = (decode && lut_r[0] >= 0.0f)
518 ? dt_ioppr_eval_trc(in_pixel[0], lut_r, c->coeffs_source[0], DT_CONVERSION_LUT_SAMPLES)
519 : in_pixel[0];
520 staged[1] = (decode && lut_g[0] >= 0.0f)
521 ? dt_ioppr_eval_trc(in_pixel[1], lut_g, c->coeffs_source[1], DT_CONVERSION_LUT_SAMPLES)
522 : in_pixel[1];
523 staged[2] = (decode && lut_b[0] >= 0.0f)
524 ? dt_ioppr_eval_trc(in_pixel[2], lut_b, c->coeffs_source[2], DT_CONVERSION_LUT_SAMPLES)
525 : in_pixel[2];
526 staged[3] = 0.0f;
527
529
530 dt_aligned_pixel_simd_t v = dt_mat3x4_mul_vec4(dt_load_simd_aligned(staged), m0, m1, m2);
532
533 if(encode)
535 else
537 }
539 }
540
542}
543
545static void _apply_lcms2(const dt_colorspaces_conversion_t *const c, const float *const in, float *const out,
546 const size_t width, const size_t height, const dt_colorspaces_conversion_hook_t hook)
547{
548 /* Alias the transforms outside the parallel region and share the aliases explicitly,
549 * rather than reaching through the struct from inside the loop. */
550 const cmsHTRANSFORM xform = c->xform;
551 const cmsHTRANSFORM clip_xform = c->clip_xform;
552 const gboolean clipping = c->has_clipping;
553 const gboolean gamutcheck = c->gamutcheck;
554
556 for(size_t row = 0; row < height; row++)
557 {
558 const float *const restrict source = in + 4 * row * width;
559 float *const restrict target = out + 4 * row * width;
560
561 if(!IS_NULL_PTR(hook))
562 {
563 /* The hook runs on the values just before the colour conversion proper. lcms2 decodes
564 * internally, so here that means before cmsDoTransform -- which is where colorin has
565 * always applied it. Staged through the output row, as it was. */
566 for(size_t j = 0; j < width; j++)
567 {
568 hook(source + 4 * j, target + 4 * j);
569 target[4 * j + 3] = 0.0f;
570 }
571 dt_colorspaces_transform_rgba_float_row(xform, target, target, width);
572 }
573 else
574 {
575 dt_colorspaces_transform_rgba_float_row(xform, source, target, width);
576 }
577
578 if(clipping)
579 {
580 float *const restrict clipped = target;
582 for(size_t j = 0; j < width; j++)
583 {
584 for(int ch = 0; ch < 3; ch++) clipped[4 * j + ch] = CLAMP(clipped[4 * j + ch], 0.0f, 1.0f);
585 }
586 dt_colorspaces_transform_rgba_float_row(clip_xform, target, target, width);
587 }
588
589 if(gamutcheck)
590 {
591 for(size_t j = 0; j < width; j++)
592 {
593 if(target[4 * j + 0] < 0.0f || target[4 * j + 1] < 0.0f || target[4 * j + 2] < 0.0f)
594 {
595 target[4 * j + 0] = 0.0f;
596 target[4 * j + 1] = 1.0f;
597 target[4 * j + 2] = 1.0f;
598 }
599 }
600 }
601 }
602}
603
605 const float *const in, float *const out, const size_t width,
606 const size_t height, const dt_colorspaces_conversion_hook_t hook)
607{
608 if(IS_NULL_PTR(conversion) || IS_NULL_PTR(in) || IS_NULL_PTR(out)) return;
609
610 if(conversion->is_matrix)
611 _apply_matrix(conversion, in, out, width * height, hook);
612 else
613 _apply_lcms2(conversion, in, out, width, height, hook);
614}
615
616void dt_colorspaces_apply_conversion(const dt_colorspaces_conversion_t *const conversion, const float *const in,
617 float *const out, const size_t width, const size_t height)
618{
620}
621
622/* --- what a device kernel needs ------------------------------------------- */
623
625{
626 return !IS_NULL_PTR(conversion) && conversion->is_matrix;
627}
628
630{
631 return !IS_NULL_PTR(conversion) && conversion->has_clipping;
632}
633
636{
637 if(IS_NULL_PTR(conversion) || !conversion->is_matrix) return FALSE;
638 memcpy(matrix, conversion->matrix, sizeof(dt_colormatrix_t));
639 return TRUE;
640}
641
644{
645 if(IS_NULL_PTR(conversion) || !conversion->have_source_matrix) return FALSE;
646 memcpy(matrix, conversion->source_matrix, sizeof(dt_colormatrix_t));
647 return TRUE;
648}
649
652{
653 if(IS_NULL_PTR(conversion) || !conversion->is_matrix || !conversion->has_clipping) return FALSE;
654 memcpy(matrix, conversion->clip_matrix, sizeof(dt_colormatrix_t));
655 return TRUE;
656}
657
659 const int channel)
660{
661 if(IS_NULL_PTR(conversion) || channel < 0 || channel > 2) return NULL;
662 return conversion->lut_source[channel];
663}
664
666 const int channel)
667{
668 if(IS_NULL_PTR(conversion) || channel < 0 || channel > 2) return NULL;
669 return conversion->lut_target[channel];
670}
671
673{
674 if(IS_NULL_PTR(conversion) || IS_NULL_PTR(conversion->lut_source[0])) return NULL;
675 return &conversion->coeffs_source[0][0];
676}
677
679{
680 if(IS_NULL_PTR(conversion) || IS_NULL_PTR(conversion->lut_target[0])) return NULL;
681 return &conversion->coeffs_target[0][0];
682}
683
684// clang-format off
685// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
686// vim: shiftwidth=2 expandtab tabstop=2 cindent
687// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
688// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:282
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.
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:112
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:672
static gboolean _allocate_curves(float *lut[3])
Definition conversion.c:153
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:604
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:629
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:634
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:665
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:406
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:624
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:678
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:453
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:545
static cmsUInt32Number _format_for(cmsHPROFILE profile, gboolean *supported)
Definition conversion.c:132
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:642
static void _free_curves(float *lut[3])
Definition conversion.c:164
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:658
static cmsHPROFILE _quantise_profile(cmsHPROFILE profile)
Definition conversion.c:90
void dt_colorspaces_free_conversion(dt_colorspaces_conversion_t **conversion)
Release a conversion and NULL the caller's pointer.
Definition conversion.c:377
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:650
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:173
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:616
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:117
@ DT_CONVERSION_FORCE_LCMS2
Never take the matrix path, even when both profiles reduce to matrices. Backs the plugins/lighttable/...
Definition conversion.h:121
@ DT_CONVERSION_SOURCE_CURVES
The caller can apply the SOURCE profile's decoding curves before the matrix. Without this,...
Definition conversion.h:124
@ DT_CONVERSION_GAMUTCHECK
Mark out-of-gamut pixels rather than merely proofing them. Requires a soft-proof endpoint,...
Definition conversion.h:130
@ DT_CONVERSION_TARGET_CURVES
The caller can apply the TARGET profile's encoding curves after the matrix. Without this,...
Definition conversion.h:127
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:190
#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:66
int supported(struct dt_imageio_module_storage_t *storage, struct dt_imageio_module_format_t *format)
Definition example.c:273
@ DT_DEBUG_COLORPROFILE
Definition logging.h:65
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
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:65
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)
Definition mem_alloc.h:122
static float * dt_alloc_align_float(size_t pixels)
Definition mem_alloc.h:135
#define dt_free(ptr)
Definition mem_alloc.h:97
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:64
#define dt_omploop_sfence()
Definition openmp.h:119
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:61
dt_iop_color_intent_t
ICC rendering intent, as stored in iop params and in conf.
dt_colorspaces_color_profile_type_t
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
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
dt_colormatrix_t clip_matrix
clip -> target; meaningless unless has_clipping
Definition conversion.c:58
dt_colormatrix_t source_matrix
Definition conversion.c:63
dt_colorspaces_color_profile_type_t from_type
Definition conversion.c:51
dt_colormatrix_t matrix
source -> target, or source -> clip when clipping
Definition conversion.c:57
cmsHTRANSFORM clip_xform
clip -> target; NULL unless has_clipping
Definition conversion.c:75
float coeffs_source[3][3]
9 contiguous floats: what the kernels upload verbatim
Definition conversion.c:67
cmsHTRANSFORM xform
source -> target, or source -> clip when clipping
Definition conversion.c:74
float * lut_source[3]
NULL when this conversion has no source curve stage.
Definition conversion.c:66
dt_colorspaces_color_profile_type_t to_type
Definition conversion.c:52
One end of a conversion: a profile, named either by identity or by handing over one this module alrea...
Definition conversion.h:92
#define __DT_CLONE_TARGETS__