Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorprofiles/iop_profile.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2018-2021, 2023, 2026 Aurélien PIERRE.
4 Copyright (C) 2018-2019 Edgardo Hoszowski.
5 Copyright (C) 2019 Aldric Renaudin.
6 Copyright (C) 2019 Andreas Schneider.
7 Copyright (C) 2019, 2022 Hanno Schwalm.
8 Copyright (C) 2019 Heiko Bauke.
9 Copyright (C) 2019 Jacques Le Clerc.
10 Copyright (C) 2019 jakubfi.
11 Copyright (C) 2019 luzpaz.
12 Copyright (C) 2019-2021 Pascal Obry.
13 Copyright (C) 2019, 2021 Philippe Weyland.
14 Copyright (C) 2019, 2021 Sakari Kapanen.
15 Copyright (C) 2019 Tobias Ellinghaus.
16 Copyright (C) 2020-2021 Dan Torop.
17 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
18 Copyright (C) 2020 Hubert Kowalski.
19 Copyright (C) 2020-2021 Ralf Brown.
20 Copyright (C) 2021 paolodepetrillo.
21 Copyright (C) 2022 Martin Bařinka.
22 Copyright (C) 2022 Philipp Lutz.
23 Copyright (C) 2022 Victor Forsiuk.
24 Copyright (C) 2024 Alynx Zhou.
25
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39#ifdef HAVE_CONFIG_H
40/* The colour-profile maths: the profile struct's lifecycle, the LCMS2 and matrix transform
41 * workers, and the OpenCL profile-parameter plumbing. Nothing here knows about dt_develop_t,
42 * dt_dev_pixelpipe_t or dt_iop_module_t.
43 *
44 * Split out of develop/iop_profile.c. That file declared its API in common/, forward-declaring
45 * three develop/ types to do it, and pixel/eaw.c, pixel/rgb_norms.h and
46 * pixel/colorequal_shared.h all needed the low-level half -- so moving the whole thing up to
47 * develop/ traded one layering problem for another. This half belongs at layer 2 with its
48 * consumers; the pipeline-facing half stayed in develop/iop_profile.c.
49 *
50 * Five helpers that were static are now dt_ioppr_* and declared in the header: the develop/
51 * half calls them, and they are the shared core rather than either side's private business.
52 */
53
54#include "develop/imageop_math.h" // dt_iop_estimate_exp: pure curve fitting, misfiled at layer 5
55#include "common/logging.h"
56#include "common/opencl.h"
57
58#include <glib/gi18n.h>
59#include "config.h"
60#endif
61
65#include "math/matrices.h"
66
67#include <assert.h>
68#include <stdio.h>
69#include <stdlib.h>
70#include <string.h>
72#include "common/times.h"
73
74
76{
77 profile_info->matrix_in[0][0] = NAN;
78 profile_info->matrix_in_transposed[0][0] = NAN;
79 profile_info->matrix_out[0][0] = NAN;
80 profile_info->matrix_out_transposed[0][0] = NAN;
81}
82
85{
86 for(int i = 0; i < 3; i++)
87 {
88 profile_info->lut_in[i][0] = -1.0f;
89 profile_info->lut_out[i][0] = -1.0f;
90 }
91}
92
93static void _transform_from_to_rgb_lab_lcms2(const float *const image_in, float *const image_out, const int width,
95 const char *filename, const int intent, const int direction)
96{
97 cmsHTRANSFORM *xform = NULL;
98 cmsHPROFILE *rgb_profile = NULL;
99 cmsHPROFILE *lab_profile = NULL;
100
101 /* Resolve first, then lock the entry we resolved: the entry pointer is stable for the
102 * process, its ->profile is not, so the handle is read only under the lock. */
103 const dt_colorspaces_color_profile_t *profile
107
109 if(profile) rgb_profile = profile->profile;
110 if(rgb_profile)
111 {
114 {
115 fprintf(stderr, "working profile color space `%c%c%c%c' not supported\n",
116 (char)(rgb_color_space>>24),
117 (char)(rgb_color_space>>16),
118 (char)(rgb_color_space>>8),
119 (char)(rgb_color_space));
121 }
122 }
124 {
126 fprintf(stderr, _("unsupported working profile %s has been replaced by Rec2020 RGB!\n"), filename);
127 }
128
130
131 cmsHPROFILE *input_profile = NULL;
132 cmsHPROFILE *output_profile = NULL;
135
136 if(direction == 1) // rgb --> lab
137 {
142 }
143 else // lab -->rgb
144 {
149 }
150
152
154
155 if(xform)
156 {
158 }
159 else
160 fprintf(stderr, "[_transform_from_to_rgb_lab_lcms2] cannot create transform\n");
161
162 if(xform) cmsDeleteTransform(xform);
163}
164
165static inline __attribute__((always_inline)) void _transform_rgb_to_rgb_lcms2(const float *const image_in, float *const image_out, const int width,
167 const char *filename_from,
169 const int intent)
170{
171 cmsHTRANSFORM *xform = NULL;
172 cmsHPROFILE *from_rgb_profile = NULL;
173 cmsHPROFILE *to_rgb_profile = NULL;
174
175 /* Resolve both, then lock both, then read the handles. Two read locks in a fixed order;
176 * readers do not exclude readers, so the pair cannot deadlock against a caller taking
177 * them the other way round. */
181 : NULL;
185 : NULL;
186
189
191 {
193 }
194 else
195 {
196 fprintf(stderr, "[_transform_rgb_to_rgb_lcms2] invalid from profile\n");
197 }
198
200 {
201 if(profile_to) to_rgb_profile = profile_to->profile;
202 }
203 else
204 {
205 fprintf(stderr, "[_transform_rgb_to_rgb_lcms2] invalid to profile\n");
206 }
207
209 {
212 {
213 fprintf(stderr, "[_transform_rgb_to_rgb_lcms2] profile color space `%c%c%c%c' not supported\n",
214 (char)(rgb_color_space >> 24), (char)(rgb_color_space >> 16), (char)(rgb_color_space >> 8),
215 (char)(rgb_color_space));
217 }
218 }
220 {
223 {
224 fprintf(stderr, "[_transform_rgb_to_rgb_lcms2] profile color space `%c%c%c%c' not supported\n",
225 (char)(rgb_color_space >> 24), (char)(rgb_color_space >> 16), (char)(rgb_color_space >> 8),
226 (char)(rgb_color_space));
228 }
229 }
230
231 cmsHPROFILE *input_profile = NULL;
232 cmsHPROFILE *output_profile = NULL;
235
240
243
246
247 if(xform)
248 {
250 }
251 else
252 fprintf(stderr, "[_transform_rgb_to_rgb_lcms2] cannot create transform\n");
253
254 if(xform) cmsDeleteTransform(xform);
255}
256
257void dt_ioppr_transform_lcms2(const char *op, const char *multi_name, const float *const image_in, float *const image_out,
258 const int width, const int height,
262{
263 if(cst_from == cst_to)
264 {
266 return;
267 }
268
270
272 {
274 "[dt_ioppr_transform_lcms2] transfoming from RGB to Lab (%s %s)\n", op, multi_name);
276 profile_info->filename, profile_info->intent, 1);
277 }
279 {
281 "[dt_ioppr_transform_lcms2] transfoming from Lab to RGB (%s %s)\n", op, multi_name);
283 profile_info->filename, profile_info->intent, -1);
284 }
285 else
286 {
288 fprintf(stderr, "[dt_ioppr_transform_lcms2] invalid conversion from %i to %i\n", cst_from, cst_to);
289 }
290}
291
292static inline __attribute__((always_inline)) void _transform_lcms2_rgb(const float *const image_in, float *const image_out, const int width,
293 const int height,
296{
298 profile_info_from->filename, profile_info_to->type, profile_info_to->filename,
299 profile_info_to->intent);
300}
301
302
303inline int dt_ioppr_init_unbounded_coeffs(float *const lutr, float *const lutg, float *const lutb,
304 float *const unbounded_coeffsr, float *const unbounded_coeffsg, float *const unbounded_coeffsb, const int lutsize)
305{
306 int nonlinearlut = 0;
307 float *lut[3] = { lutr, lutg, lutb };
309
310 for(int k = 0; k < 3; k++)
311 {
312 // omit luts marked as linear (negative as marker)
313 if(lut[k][0] >= 0.0f)
314 {
315 const dt_aligned_pixel_t x = { 0.7f, 0.8f, 0.9f, 1.0f };
316 const dt_aligned_pixel_t y = { extrapolate_lut(lut[k], x[0], lutsize),
319 extrapolate_lut(lut[k], x[3], lutsize) };
321
322 nonlinearlut++;
323 }
324 else
325 unbounded_coeffs[k][0] = -1.0f;
326 }
327
328 return nonlinearlut;
329}
330
331
332static inline void _apply_tonecurves(const float *const image_in, float *const image_out,
333 const int width, const int height,
334 const float *const restrict lutr,
335 const float *const restrict lutg,
336 const float *const restrict lutb,
337 const float *const restrict unbounded_coeffsr,
338 const float *const restrict unbounded_coeffsg,
339 const float *const restrict unbounded_coeffsb,
340 const int lutsize)
341{
342 const int ch = 4;
343 const float *const lut[3] = { lutr, lutg, lutb };
345 const size_t stride = (size_t)ch * width * height;
346
347 // do we have any lut to apply, or is this a linear profile?
348 if((lut[0][0] >= 0.0f) && (lut[1][0] >= 0.0f) && (lut[2][0] >= 0.0f))
349 {
351 for(size_t k = 0; k < stride; k += ch)
352 {
353 for(int c = 0; c < 3; c++) // for_each_channel doesn't vectorize, and some code needs image_out[3] preserved
354 {
356 }
357 }
358 }
359 else if((lut[0][0] >= 0.0f) || (lut[1][0] >= 0.0f) || (lut[2][0] >= 0.0f))
360 {
362 for(size_t k = 0; k < stride; k += ch)
363 {
364 for(int c = 0; c < 3; c++) // for_each_channel doesn't vectorize, and some code needs image_out[3] preserved
365 {
366 if(lut[c][0] >= 0.0f)
367 {
369 }
370 }
371 }
372 }
373}
374
375
377static inline void _transform_rgb_to_lab_matrix(const float *const restrict image_in, float *const restrict image_out,
378 const int width, const int height,
380{
381 const int ch = 4;
382 const size_t stride = (size_t)width * height * ch;
383 const dt_colormatrix_t *matrix_ptr = &profile_info->matrix_in_transposed;
384 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(*matrix_ptr, 0);
385 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(*matrix_ptr, 1);
386 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(*matrix_ptr, 2);
387
388 if(profile_info->nonlinearlut)
389 {
390 // TODO : maybe optimize that path like _transform_matrix_rgb
392 profile_info->lut_in[2], profile_info->unbounded_coeffs_in[0],
393 profile_info->unbounded_coeffs_in[1], profile_info->unbounded_coeffs_in[2],
394 profile_info->lutsize);
396 for(size_t y = 0; y < stride; y += ch)
397 {
398 float *const restrict in = __builtin_assume_aligned(image_out + y, 16);
400 const dt_aligned_pixel_simd_t vin = dt_load_simd_aligned(in);
402 dt_XYZ_to_Lab(xyz, in);
403 // the tone curves wrote RGB only: alpha comes from the source, as in _transform_lab_to_rgb_matrix()
404 in[3] = image_in[y + 3];
405 }
406 }
407 else
408 {
410 for(size_t y = 0; y < stride; y += ch)
411 {
412 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
413 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
414
416 const float alpha = in[3]; // a mask preview carries its mask there, and may convert in place
417 const dt_aligned_pixel_simd_t vin = dt_load_simd_aligned(in);
419 dt_XYZ_to_Lab(xyz, out);
420 out[3] = alpha;
421 }
422 }
423}
424
425
427static inline void _transform_lab_to_rgb_matrix(const float *const image_in, float *const image_out, const int width,
428 const int height,
430{
431 const int ch = 4;
432 const size_t stride = (size_t)width * height * ch;
433 const int use_nontemporal = !profile_info->nonlinearlut;
434 const dt_colormatrix_t *matrix_ptr = &profile_info->matrix_out_transposed;
435 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(*matrix_ptr, 0);
436 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(*matrix_ptr, 1);
437 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(*matrix_ptr, 2);
439 for(size_t y = 0; y < stride; y += ch)
440 {
441 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
442 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
443
445 const float alpha = in[3]; // some code does in-place conversions and relies on alpha being preserved
446 dt_Lab_to_XYZ(in, xyz);
447 const dt_aligned_pixel_simd_t vxyz = dt_load_simd_aligned(xyz);
448 dt_aligned_pixel_simd_t rgb = dt_mat3x4_mul_vec4(vxyz, m0, m1, m2);
449 rgb[3] = alpha;
452 else
454 }
455
457 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
458
459 if(profile_info->nonlinearlut)
460 {
461 // TODO : maybe optimize that path like _transform_matrix_rgb
463 profile_info->lut_out[2], profile_info->unbounded_coeffs_out[0],
464 profile_info->unbounded_coeffs_out[1], profile_info->unbounded_coeffs_out[2],
465 profile_info->lutsize);
466 }
467}
468
469
471static inline void _transform_matrix_rgb(const float *const restrict image_in,
472 float *const restrict image_out,
473 const int width, const int height,
476{
477 const int ch = 4;
478 const size_t stride = (size_t)width * height * ch;
479
480 // RGB -> XYZ -> RGB are 2 matrices products, they can be premultiplied globally ahead
481 // and put in a new matrix. then we spare one matrix product per pixel.
486 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(matrix, 0);
487 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(matrix, 1);
488 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(matrix, 2);
489
490 if(profile_info_from->nonlinearlut || profile_info_to->nonlinearlut)
491 {
492 const int use_nontemporal = !profile_info_to->nonlinearlut;
493 const int run_lut_in[3] DT_ALIGNED_PIXEL= { (profile_info_from->lut_in[0][0] >= 0.0f),
494 (profile_info_from->lut_in[1][0] >= 0.0f),
495 (profile_info_from->lut_in[2][0] >= 0.0f) };
496
497 const int run_lut_out[3] DT_ALIGNED_PIXEL = { (profile_info_to->lut_out[0][0] >= 0.0f),
498 (profile_info_to->lut_out[1][0] >= 0.0f),
499 (profile_info_to->lut_out[2][0] >= 0.0f) };
501 for(size_t y = 0; y < stride; y += 4)
502 {
503 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
504 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
506
507 // linearize if non-linear input
508 if(profile_info_from->nonlinearlut)
509 {
510 for(size_t c = 0; c < 3; c++)
511 {
512 rgb[c] = (run_lut_in[c]
513 ? dt_ioppr_eval_trc(in[c], profile_info_from->lut_in[c],
514 profile_info_from->unbounded_coeffs_in[c], profile_info_from->lutsize)
515 : in[c]);
516 }
517 }
518 else
519 {
521 rgb[c] = in[c];
522 }
523
524 if(profile_info_to->nonlinearlut)
525 {
526 // convert color space
528 const dt_aligned_pixel_simd_t vrgb = dt_load_simd_aligned(rgb);
530
531 // de-linearize non-linear output
532 for(size_t c = 0; c < 3; c++)
533 {
534 out[c] = (run_lut_out[c]
535 ? dt_ioppr_eval_trc(temp[c], profile_info_to->lut_out[c],
536 profile_info_to->unbounded_coeffs_out[c], profile_info_to->lutsize)
537 : temp[c]);
538 }
539 }
540 else
541 {
542 // convert color space
543 const dt_aligned_pixel_simd_t vrgb = dt_load_simd_aligned(rgb);
546 else
548 }
549 }
550
552 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
553 }
554 else
555 {
557 for(size_t y = 0; y < stride; y += 4)
558 {
559 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
560 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
561
562 const dt_aligned_pixel_simd_t vin = dt_load_simd_aligned(in);
564 }
565 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
566 }
567}
568
569
570inline void dt_ioppr_transform_matrix(const char *op, const char *multi_name,
571 const float *const restrict image_in,
572 float *const restrict image_out,
573 const int width, const int height,
578{
579 if(cst_from == cst_to)
580 {
582 return;
583 }
584
586
588 {
590 }
592 {
594 }
595 else
596 {
598 fprintf(stderr, "[dt_ioppr_transform_matrix] invalid conversion from %i to %i\n", cst_from, cst_to);
599 }
600}
601
602
603#define DT_IOPPR_LUT_SAMPLES 0x10000
604
607{
609 profile_info->filename[0] = '\0';
612 profile_info->unbounded_coeffs_in[0][0] = profile_info->unbounded_coeffs_in[1][0] = profile_info->unbounded_coeffs_in[2][0] = -1.0f;
613 profile_info->unbounded_coeffs_out[0][0] = profile_info->unbounded_coeffs_out[1][0] = profile_info->unbounded_coeffs_out[2][0] = -1.0f;
614 profile_info->nonlinearlut = 0;
615 profile_info->grey = 0.f;
617 for(int i = 0; i < 3; i++)
618 {
619 profile_info->lut_in[i] = dt_alloc_align_float(profile_info->lutsize);
620 profile_info->lut_in[i][0] = -1.0f;
621 profile_info->lut_out[i] = dt_alloc_align_float(profile_info->lutsize);
622 profile_info->lut_out[i][0] = -1.0f;
623 }
624}
625
626#undef DT_IOPPR_LUT_SAMPLES
627
629{
630 /* The whole teardown, not just the LUTs. A dt_iop_order_iccprofile_info_t owns six
631 * aligned float arrays, so releasing one is "free the curves, free the struct, drop the
632 * pointer" -- three steps every caller was open-coding, and three chances to free the
633 * struct while leaving 1.5 MB of curves behind. Takes the pointer by address so the
634 * caller's variable cannot be left dangling. */
636
637 for(int i = 0; i < 3; i++)
638 {
639 dt_free_align((*profile_info)->lut_in[i]);
640 (*profile_info)->lut_in[i] = NULL;
641 dt_free_align((*profile_info)->lut_out[i]);
642 (*profile_info)->lut_out[i] = NULL;
643 }
644
647}
648
649void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width,
650 const int height,
653 const char *message)
654{
656 {
657 return;
658 }
659 if(profile_info_from->type == profile_info_to->type
660 && strcmp(profile_info_from->filename, profile_info_to->filename) == 0)
661 {
662 if(image_in != image_out)
663 memcpy(image_out, image_in, sizeof(float) * 4 * width * height);
664
665 return;
666 }
667
668 dt_times_t start_time = { 0 }, end_time = { 0 };
670
671 if(!isnan(profile_info_from->matrix_in[0][0]) && !isnan(profile_info_from->matrix_out[0][0])
672 && !isnan(profile_info_to->matrix_in[0][0]) && !isnan(profile_info_to->matrix_out[0][0]))
673 {
675
677 {
679 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f CPU) [%s]\n",
680 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
681 }
682 }
683 else
684 {
686
688 {
690 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f lcms2) [%s]\n",
691 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
692 }
693 }
694}
695
696#ifdef HAVE_OPENCL
697/* The kernels this subsystem compiles, owned HERE. They used to be handed to
698 * common/opencl.c, parked on the application-wide dt_opencl_t, and read back from it --
699 * a round trip through a god-struct that added nothing but an ordering. opencl.c still
700 * calls init/free, because the kernels must be built after the devices exist, but the
701 * pointer never leaves this file. */
703
705{
707
708 const int program = 23; // colorspaces.cl, from programs.conf
709 g->kernel_colorspaces_transform_lab_to_rgb_matrix = dt_opencl_create_kernel(program, "colorspaces_transform_lab_to_rgb_matrix");
710 g->kernel_colorspaces_transform_rgb_matrix_to_lab = dt_opencl_create_kernel(program, "colorspaces_transform_rgb_matrix_to_lab");
711 g->kernel_colorspaces_transform_rgb_matrix_to_rgb
712 = dt_opencl_create_kernel(program, "colorspaces_transform_rgb_matrix_to_rgb");
714}
715
717{
720 if(IS_NULL_PTR(g)) return;
721
722 // destroy kernels
723 dt_opencl_free_kernel(g->kernel_colorspaces_transform_lab_to_rgb_matrix);
724 dt_opencl_free_kernel(g->kernel_colorspaces_transform_rgb_matrix_to_lab);
725 dt_opencl_free_kernel(g->kernel_colorspaces_transform_rgb_matrix_to_rgb);
726
727 dt_free(g);
728}
729
731{
732 for(int i = 0; i < 9; i++)
733 {
734 profile_info_cl->matrix_in[i] = profile_info->matrix_in[i/3][i%3];
735 profile_info_cl->matrix_out[i] = profile_info->matrix_out[i/3][i%3];
736 }
737 profile_info_cl->lutsize = profile_info->lutsize;
738 for(int i = 0; i < 3; i++)
739 {
740 for(int j = 0; j < 3; j++)
741 {
742 profile_info_cl->unbounded_coeffs_in[i][j] = profile_info->unbounded_coeffs_in[i][j];
743 profile_info_cl->unbounded_coeffs_out[i][j] = profile_info->unbounded_coeffs_out[i][j];
744 }
745 }
746 profile_info_cl->nonlinearlut = profile_info->nonlinearlut;
747 profile_info_cl->grey = profile_info->grey;
748}
749
751{
752 cl_float *trc = malloc(sizeof(cl_float) * 6 * profile_info->lutsize);
753 if(trc)
754 {
755 int x = 0;
756 for(int c = 0; c < 3; c++)
757 for(int y = 0; y < profile_info->lutsize; y++, x++)
758 trc[x] = profile_info->lut_in[c][y];
759 for(int c = 0; c < 3; c++)
760 for(int y = 0; y < profile_info->lutsize; y++, x++)
761 trc[x] = profile_info->lut_out[c][y];
762 }
763 return trc;
764}
765
768 cl_float **_profile_lut_cl, cl_mem *_dev_profile_info,
769 cl_mem *_dev_profile_lut)
770{
771 cl_int err = CL_SUCCESS;
772
774 cl_float *profile_lut_cl = NULL;
775 cl_mem dev_profile_info = NULL;
776 cl_mem dev_profile_lut = NULL;
777
778 if(profile_info)
779 {
782
785 {
786 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 5\n");
788 goto cleanup;
789 }
790
791 dev_profile_lut = dt_opencl_copy_host_to_device(devid, profile_lut_cl, 256, 256 * 6, sizeof(float));
793 {
794 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 6\n");
796 goto cleanup;
797 }
798 }
799 else
800 {
801 profile_lut_cl = malloc(sizeof(cl_float) * 1 * 6);
802
803 dev_profile_lut = dt_opencl_copy_host_to_device(devid, profile_lut_cl, 1, 1 * 6, sizeof(float));
805 {
806 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 7\n");
808 goto cleanup;
809 }
810 }
811
812cleanup:
817
818 return err;
819}
820
843
845 const int width, const int height,
848 const char *message)
849{
850 cl_int err = CL_SUCCESS;
851
853 {
854 return FALSE;
855 }
856 if(profile_info_from->type == profile_info_to->type
857 && strcmp(profile_info_from->filename, profile_info_to->filename) == 0)
858 {
860 {
861 size_t origin[] = { 0, 0, 0 };
862 size_t region[] = { width, height, 1 };
863
865 if(err != CL_SUCCESS)
866 {
868 "[dt_ioppr_transform_image_colorspace_rgb_cl] error on copy image for color transformation\n");
869 return FALSE;
870 }
871 }
872
873 return TRUE;
874 }
875
876 const size_t ch = 4;
877 float *src_buffer_in = NULL;
878 float *src_buffer_out = NULL;
880
881 int kernel_transform = 0;
882 cl_mem dev_tmp = NULL;
883
885 cl_mem dev_lut_from = NULL;
887 cl_float *lut_from_cl = NULL;
888
889 cl_mem dev_profile_info_to = NULL;
890 cl_mem dev_lut_to = NULL;
892 cl_float *lut_to_cl = NULL;
893
894 cl_mem matrix_cl = NULL;
895
896 // if we have a matrix use opencl
897 if(!isnan(profile_info_from->matrix_in[0][0]) && !isnan(profile_info_from->matrix_out[0][0])
898 && !isnan(profile_info_to->matrix_in[0][0]) && !isnan(profile_info_to->matrix_out[0][0]))
899 {
900 dt_times_t start_time = { 0 }, end_time = { 0 };
902
903 size_t origin[] = { 0, 0, 0 };
904 size_t region[] = { width, height, 1 };
905
907
910
913
916
917 if(in_place)
918 {
919 dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
921 {
922 fprintf(
923 stderr,
924 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 4\n");
926 goto cleanup;
927 }
928
930 if(err != CL_SUCCESS)
931 {
933 "[dt_ioppr_transform_image_colorspace_rgb_cl] error on copy image for color transformation\n");
934 goto cleanup;
935 }
936 }
937 else
938 {
940 }
941
945 {
947 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 5\n");
949 goto cleanup;
950 }
951 dev_lut_from = dt_opencl_copy_host_to_device(devid, lut_from_cl, 256, 256 * 6, sizeof(float));
953 {
955 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 6\n");
957 goto cleanup;
958 }
959
963 {
965 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 7\n");
967 goto cleanup;
968 }
969 dev_lut_to = dt_opencl_copy_host_to_device(devid, lut_to_cl, 256, 256 * 6, sizeof(float));
971 {
973 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 8\n");
975 goto cleanup;
976 }
977 float matrix3x4[12];
981 {
983 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 7\n");
985 goto cleanup;
986 }
987
988 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
989
990 dt_opencl_set_kernel_arg(devid, kernel_transform, 0, sizeof(cl_mem), (void *)&dev_tmp);
991 dt_opencl_set_kernel_arg(devid, kernel_transform, 1, sizeof(cl_mem), (void *)&dev_img_out);
992 dt_opencl_set_kernel_arg(devid, kernel_transform, 2, sizeof(int), (void *)&width);
993 dt_opencl_set_kernel_arg(devid, kernel_transform, 3, sizeof(int), (void *)&height);
994 dt_opencl_set_kernel_arg(devid, kernel_transform, 4, sizeof(cl_mem), (void *)&dev_profile_info_from);
995 dt_opencl_set_kernel_arg(devid, kernel_transform, 5, sizeof(cl_mem), (void *)&dev_lut_from);
996 dt_opencl_set_kernel_arg(devid, kernel_transform, 6, sizeof(cl_mem), (void *)&dev_profile_info_to);
997 dt_opencl_set_kernel_arg(devid, kernel_transform, 7, sizeof(cl_mem), (void *)&dev_lut_to);
998 dt_opencl_set_kernel_arg(devid, kernel_transform, 8, sizeof(cl_mem), (void *)&matrix_cl);
1000 if(err != CL_SUCCESS)
1001 {
1003 "[dt_ioppr_transform_image_colorspace_rgb_cl] error %i enqueue kernel for color transformation\n",
1004 err);
1005 goto cleanup;
1006 }
1007
1009 {
1011 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f GPU) [%s]\n",
1012 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
1013 }
1014 }
1015 else
1016 {
1017 // no matrix, call lcms2
1021 {
1023 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 1\n");
1025 goto cleanup;
1026 }
1027
1029 if(err != CL_SUCCESS)
1030 {
1032 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 2\n");
1033 goto cleanup;
1034 }
1035
1036 // just call the CPU version for now
1038 profile_info_to, message);
1039
1041 if(err != CL_SUCCESS)
1042 {
1044 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 3\n");
1045 goto cleanup;
1046 }
1047 }
1048
1049cleanup:
1053
1057
1061
1063
1064 return (err == CL_SUCCESS) ? TRUE : FALSE;
1065}
1066#endif
1067
1068#undef DT_IOP_ORDER_PROFILE
1069// clang-format off
1070// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1071// vim: shiftwidth=2 expandtab tabstop=2 cindent
1072// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1073// clang-format on
1074
1075
1076/* ---------------------------------------------------------------------------
1077 * The derived-profile memo.
1078 *
1079 * Matrix + tone-curve LUTs extracted from a profile: expensive to build (two
1080 * 65536-entry extractions) and a pure function of (type, filename), so it is
1081 * memoised. This lived on dt_develop_t, which made it per-image, unsynchronised,
1082 * and duplicated between concurrent exports; and it made develop/ take this
1083 * module's rwlock by hand to build an entry.
1084 *
1085 * It is the module's now, and flushed by dt_colorprofiles_cleanup().
1086 *
1087 * What deliberately does NOT live here: profiles derived from one image --
1088 * DT_COLORSPACE_EMBEDDED_ICC through DT_COLORSPACE_ALTERNATE_MATRIX. They are not
1089 * registered in the profile list and cannot be resolved by identity at all, so
1090 * they are not a function of their key and would stomp each other here. They live
1091 * on the pipe that built them (dt_dev_pixelpipe_t.owned_input_profile_info).
1092 * ------------------------------------------------------------------------- */
1093
1095
1096/* Entries evicted from the memo but NOT yet freed. dt_colorspaces_add_profile() hands out a
1097 * pointer the caller keeps -- dt_dev_pixelpipe_t::output_profile_info caches one for the
1098 * lifetime of a pipe, and GUI code reads it from callbacks that run long after the pipe did.
1099 * Freeing an entry on eviction leaves every one of those pointers dangling, and the eviction
1100 * is not a rare event: it happens whenever the monitor profile changes, i.e. on a window move
1101 * between monitors, which is also exactly when things repaint.
1102 *
1103 * So an evicted entry is retired rather than freed, and released at shutdown. The cost is
1104 * bounded by the number of profile changes in a session -- a handful of ~1.5 MB entries in a
1105 * pathological one -- against a use-after-free in the pixel path (Sentry 141501108: SIGSEGV in
1106 * _apply_trc() reading a freed profile's LUTs from a pipe-finished callback).
1107 *
1108 * This mirrors what dt_conf_get_string_const() does with replaced values, and for the same
1109 * reason: the API hands out a borrowed pointer with no way to tell the borrower it expired. */
1111/* Raw pthread type, like this module's other locks: dt_pthread_mutex_t is a struct
1112 * wrapper in Debug builds, so a static PTHREAD_MUTEX_INITIALIZER would be initialising
1113 * a subobject -- which clang rejects under -Werror and gcc silently accepts. */
1115
1117{
1119 while(_profile_info_memo)
1120 {
1124 }
1125 // Retired entries go the same way. This runs at teardown, when no consumer is left to hold
1126 // one; releasing them earlier is what the retirement exists to avoid.
1128 {
1132 }
1134}
1135
1137{
1138 /* The DISPLAY entry is derived from a cmsHPROFILE this module replaces whenever the
1139 * monitor profile changes. Nothing invalidated it before, so a session kept the previous
1140 * monitor's matrices and tone curves for as long as the memo lived. That was already
1141 * wrong per-image; now that the memo is process-wide it would persist for the whole run.
1142 *
1143 * Dropped rather than rebuilt: the next caller that wants it will build it, and doing it
1144 * here would mean building a profile under this lock from the profile-changed handler. */
1146 for(GList *l = _profile_info_memo; l; )
1147 {
1148 GList *next = g_list_next(l);
1150 if(entry && entry->type == DT_COLORSPACE_DISPLAY)
1151 {
1152 // Retired, not freed: consumers hold this pointer (see _profile_info_retired).
1155 }
1156 l = next;
1157 }
1159}
1160
1161static int _generate_profile_info(dt_iop_order_iccprofile_info_t *profile_info, const int type, const char *filename, const int intent)
1162{
1163 int err_code = 0;
1164 cmsHPROFILE *rgb_profile = NULL;
1165
1168
1169 profile_info->nonlinearlut = 0;
1170 profile_info->grey = 0.1842f;
1171
1172 profile_info->type = type;
1173 g_strlcpy(profile_info->filename, filename, sizeof(profile_info->filename));
1174 profile_info->intent = intent;
1175
1176 /* The DISPLAY entry's cmsHPROFILE is the one thing in the list that is replaced at
1177 * runtime, so resolving it and DERIVING FROM IT have to happen under the same lock.
1178 * develop/ used to take this lock by hand -- and released it immediately after the
1179 * lookup, before cmsGetColorSpace() and the two 65536-entry extractions below, which
1180 * are the parts that actually touch the handle. Inside the module the whole span is
1181 * covered, which is what the lock was for. */
1182 const dt_colorspaces_color_profile_t *profile
1184
1186 if(profile) rgb_profile = profile->profile;
1187
1188 // we only allow rgb profiles
1189 if(rgb_profile)
1190 {
1193 {
1194 fprintf(stderr, "working profile color space `%c%c%c%c' not supported\n",
1195 (char)(rgb_color_space>>24),
1196 (char)(rgb_color_space>>16),
1197 (char)(rgb_color_space>>8),
1198 (char)(rgb_color_space));
1199 rgb_profile = NULL;
1200 }
1201 }
1202
1203 // get the matrix
1204 if(rgb_profile)
1205 {
1207 profile_info->lut_in[1], profile_info->lut_in[2],
1208 profile_info->lutsize)
1210 profile_info->lut_out[0], profile_info->lut_out[1],
1211 profile_info->lut_out[2], profile_info->lutsize))
1212 {
1215 }
1216 else if(isnan(profile_info->matrix_in[0][0]) || isnan(profile_info->matrix_out[0][0]))
1217 {
1220 }
1221 else
1222 {
1223 transpose_3xSSE(profile_info->matrix_in, profile_info->matrix_in_transposed);
1224 transpose_3xSSE(profile_info->matrix_out, profile_info->matrix_out_transposed);
1225 }
1226 }
1227
1228 // now try to initialize unbounded mode:
1229 // we do extrapolation for input values above 1.0f.
1230 // unfortunately we can only do this if we got the computation
1231 // in our hands, i.e. for the fast builtin-dt-matrix-profile path.
1232 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1233 {
1234 profile_info->nonlinearlut = dt_ioppr_init_unbounded_coeffs(profile_info->lut_in[0], profile_info->lut_in[1], profile_info->lut_in[2],
1235 profile_info->unbounded_coeffs_in[0], profile_info->unbounded_coeffs_in[1], profile_info->unbounded_coeffs_in[2], profile_info->lutsize);
1236 dt_ioppr_init_unbounded_coeffs(profile_info->lut_out[0], profile_info->lut_out[1], profile_info->lut_out[2],
1237 profile_info->unbounded_coeffs_out[0], profile_info->unbounded_coeffs_out[1], profile_info->unbounded_coeffs_out[2], profile_info->lutsize);
1238 }
1239
1240 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]) && profile_info->nonlinearlut)
1241 {
1242 const dt_aligned_pixel_t rgb = { 0.1842f, 0.1842f, 0.1842f };
1243 profile_info->grey = dt_ioppr_get_rgb_matrix_luminance(rgb, profile_info->matrix_in, profile_info->lut_in, profile_info->unbounded_coeffs_in, profile_info->lutsize, profile_info->nonlinearlut);
1244 }
1245
1247
1248 return err_code;
1249}
1250
1251/* Caller holds _profile_info_lock. */
1255 const char *profile_filename,
1256 const int intent)
1257{
1259
1260 /* Caller holds _profile_info_lock: this walks a list the pipeline worker and the
1261 * GUI thread both append to. */
1262 for(GList *profiles = _profile_info_memo; profiles; profiles = g_list_next(profiles))
1263 {
1265 if(prof->type == profile_type && prof->intent == intent
1266 && strcmp(prof->filename, profile_filename) == 0)
1267 {
1269 break;
1270 }
1271 }
1272
1273 return profile_info;
1274}
1275
1278 const char *profile_filename,
1279 const int intent)
1280{
1281 /* Find-or-create as ONE critical section. Reached from the pipeline worker -- iop/lut3d.c
1282 * and iop/tonecurve.c call it from process()/process_cl(), once per tile -- and from the
1283 * GUI thread via iop/colorin.c. The lock has to span the lookup as well as the append:
1284 * two threads missing the same key concurrently would otherwise each build an entry
1285 * (1.5 MB of tone-curve LUTs apiece) and append both. */
1287
1290 {
1294 if(err == 0)
1295 {
1297 }
1298 else
1299 {
1300 /* dt_ioppr_init_profile_info() has already allocated six DT_IOPPR_LUT_SAMPLES float
1301 * arrays -- 1.5 MB -- so freeing the struct alone leaked all of them on this path. */
1303 }
1304 }
1305
1307
1308 return profile_info;
1309}
1310
1311/* ---------------------------------------------------------------------------
1312 * APPLY: the pixel loop.
1313 *
1314 * One entry point for converting a buffer between colour spaces, branching
1315 * internally on what the profile actually is. A matrix-shaper profile with tone
1316 * curves goes through our own vectorised matrix + LUT path; anything else -- a
1317 * CLUT profile, a v4 parametric curve lcms2 will not reduce -- falls back to
1318 * cmsDoTransform. Callers do not choose, and do not see either.
1319 *
1320 * These used to live in develop/, which is why every consumer had to know the
1321 * distinction existed. The two implementations they dispatch to were already
1322 * here; only the branch was upstairs.
1323 *
1324 * The op/instance names are for the -d perf trace only. They are plain strings
1325 * rather than the dt_iop_module_t they were read from, because this module sits
1326 * below develop/ and cannot name an iop.
1327 * ------------------------------------------------------------------------- */
1328
1329void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, const float *const image_in,
1330 float *const image_out, const int width, const int height,
1331 const int cst_from, const int cst_to, int *converted_cst,
1333{
1334 if(cst_from == cst_to)
1335 {
1337 return;
1338 }
1340 {
1342 return;
1343 }
1345 {
1347 return;
1348 }
1349 if(profile_info->type == DT_COLORSPACE_NONE)
1350 {
1352 return;
1353 }
1354
1355 dt_times_t start_time = { 0 }, end_time = { 0 };
1357
1358 // matrix should be never NAN, this is only to test it against lcms2!
1359 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1360 {
1362
1364 {
1366 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f CPU) [%s %s]\n",
1367 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1368 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1369 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1370 }
1371 }
1372 else
1373 {
1375
1377 {
1379 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f lcms2) [%s %s]\n",
1380 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1381 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1382 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1383 }
1384 }
1385
1386 if(*converted_cst == cst_from)
1387 fprintf(stderr, "[dt_colorspaces_apply_profile] invalid conversion from %i to %i\n", cst_from, cst_to);
1388}
1389
1390#ifdef HAVE_OPENCL
1391int dt_colorspaces_apply_profile_cl(const char *const op_name, const char *const instance_name, const int devid, cl_mem dev_img_in,
1392 cl_mem dev_img_out, const int width, const int height,
1393 const int cst_from, const int cst_to, int *converted_cst,
1395{
1396 cl_int err = CL_SUCCESS;
1397
1401
1402 if(cst_from == cst_to)
1403 {
1405 return TRUE;
1406 }
1408 {
1410 return TRUE;
1411 }
1413 {
1415 return FALSE;
1416 }
1417 if(profile_info->type == DT_COLORSPACE_NONE)
1418 {
1420 return FALSE;
1421 }
1422
1423 const size_t ch = 4;
1424 float *src_buffer = NULL;
1425
1426 int kernel_transform = 0;
1427 cl_mem dev_profile_info = NULL;
1428 cl_mem dev_lut = NULL;
1430 cl_float *lut_cl = NULL;
1431
1433
1434 // if we have a matrix use opencl
1435 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1436 {
1437 dt_times_t start_time = { 0 }, end_time = { 0 };
1439
1441 {
1443 }
1445 {
1447 }
1448 else
1449 {
1452 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] invalid conversion from %i to %i\n", cst_from, cst_to);
1453 goto cleanup;
1454 }
1455
1458
1461 {
1462 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 5\n");
1464 goto cleanup;
1465 }
1466 dev_lut = dt_opencl_copy_host_to_device(devid, lut_cl, 256, 256 * 6, sizeof(float));
1467 if(IS_NULL_PTR(dev_lut))
1468 {
1469 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 6\n");
1471 goto cleanup;
1472 }
1473
1474 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1475
1476 dt_opencl_set_kernel_arg(devid, kernel_transform, 0, sizeof(cl_mem), (void *)&dev_img_in);
1477 dt_opencl_set_kernel_arg(devid, kernel_transform, 1, sizeof(cl_mem), (void *)&dev_img_out);
1478 dt_opencl_set_kernel_arg(devid, kernel_transform, 2, sizeof(int), (void *)&width);
1479 dt_opencl_set_kernel_arg(devid, kernel_transform, 3, sizeof(int), (void *)&height);
1480 dt_opencl_set_kernel_arg(devid, kernel_transform, 4, sizeof(cl_mem), (void *)&dev_profile_info);
1481 dt_opencl_set_kernel_arg(devid, kernel_transform, 5, sizeof(cl_mem), (void *)&dev_lut);
1483 if(err != CL_SUCCESS)
1484 {
1485 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error %i enqueue kernel for color transformation\n", err);
1486 goto cleanup;
1487 }
1488
1490
1492 {
1494 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f GPU) [%s %s]\n",
1495 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1496 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1497 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1498 }
1499 }
1500 else
1501 {
1502 // no matrix, call lcms2
1505 {
1506 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 1\n");
1508 goto cleanup;
1509 }
1510
1512 if(err != CL_SUCCESS)
1513 {
1514 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 2\n");
1515 goto cleanup;
1516 }
1517
1518 // just call the CPU version for now
1521
1523 if(err != CL_SUCCESS)
1524 {
1525 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 3\n");
1526 goto cleanup;
1527 }
1528 }
1529
1530cleanup:
1534 dt_free(lut_cl);
1535
1536 return (err == CL_SUCCESS) ? TRUE : FALSE;
1537}
1538#endif
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
void output_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition basebuffer.c:104
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
Definition basebuffer.c:111
dt_iop_colorspace_type_t
@ IOP_CS_LAB
static __DT_CLONE_TARGETS__ void _transform_lab_to_rgb_matrix(const float *const image_in, float *const image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info)
static __DT_CLONE_TARGETS__ void _transform_matrix_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to)
static dt_colorspaces_cl_global_t * _colorspaces_cl_global
int dt_colorspaces_apply_profile_cl(const char *const op_name, const char *const instance_name, const int devid, cl_mem dev_img_in, cl_mem dev_img_out, const int width, const int height, const int cst_from, const int cst_to, int *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
OpenCL counterpart of dt_colorspaces_apply_profile(), same contract.
dt_iop_order_iccprofile_info_t * dt_colorspaces_add_profile(const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
Find-or-build the derived matrix/LUT data for a profile identity, memoised.
void dt_ioppr_cleanup_profile_info(dt_iop_order_iccprofile_info_t **profile_info)
Release a profile info: its tone-curve LUTs, the struct itself, and the caller's pointer – must be ca...
static dt_iop_order_iccprofile_info_t * _get_profile_info_from_list(const dt_colorspaces_color_profile_type_t profile_type, const char *profile_filename, const int intent)
#define DT_IOPPR_LUT_SAMPLES
void dt_colorspaces_init_cl_global(void)
Compile the colorspace kernels. Called once by common/opencl.c at device init.
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.
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static int _generate_profile_info(dt_iop_order_iccprofile_info_t *profile_info, const int type, const char *filename, const int intent)
void dt_ioppr_get_profile_info_cl(const dt_iop_order_iccprofile_info_t *const profile_info, dt_colorspaces_iccprofile_info_cl_t *profile_info_cl)
sets profile_info_cl using profile_info, to be used as a parameter when calling opencl.
__DT_CLONE_TARGETS__ void dt_ioppr_init_profile_info(dt_iop_order_iccprofile_info_t *profile_info, const int lutsize)
Put a freshly allocated dt_iop_order_iccprofile_info_t into its empty state – must be called before u...
static pthread_mutex_t _profile_info_lock
void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, const float *const image_in, float *const image_out, const int width, const int height, const int cst_from, const int cst_to, int *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
Convert a 4-channel float buffer between RGB and Lab through one profile.
static __DT_CLONE_TARGETS__ void _transform_rgb_to_lab_matrix(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info)
static GList * _profile_info_memo
static void _transform_from_to_rgb_lab_lcms2(const float *const image_in, float *const image_out, const int width, const int height, const dt_colorspaces_color_profile_type_t type, const char *filename, const int intent, const int direction)
void dt_colorspaces_free_cl_global(void)
Release the kernels and the struct from dt_colorspaces_init_cl_global(). NULL-safe.
void dt_ioppr_free_iccprofile_params_cl(dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
free parameters build with the previous function.
__DT_CLONE_TARGETS__ void dt_ioppr_clear_lut_curves(dt_iop_order_iccprofile_info_t *const profile_info)
Mark all six tone curves linear, by writing -1.0 into entry 0 of each.
static GList * _profile_info_retired
static void _apply_tonecurves(const float *const image_in, float *const image_out, const int width, const int height, const float *const restrict lutr, const float *const restrict lutg, const float *const restrict lutb, const float *const restrict unbounded_coeffsr, const float *const restrict unbounded_coeffsg, const float *const restrict unbounded_coeffsb, const int lutsize)
void dt_colorspaces_invalidate_display_profile_memo(void)
Drop the memoised DT_COLORSPACE_DISPLAY entry, whose source profile this module replaces on a monitor...
cl_float * dt_ioppr_get_trc_cl(const dt_iop_order_iccprofile_info_t *const profile_info)
returns the profile_info trc, to be used as a parameter when calling opencl.
void dt_ioppr_transform_matrix(const char *op, const char *multi_name, const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_colorspace_type_t cst_from, const dt_iop_colorspace_type_t cst_to, dt_iop_colorspace_type_t *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
int dt_ioppr_transform_image_colorspace_rgb_cl(const int devid, cl_mem dev_img_in, cl_mem dev_img_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
OpenCL counterpart of dt_ioppr_transform_image_colorspace_rgb().
cl_int dt_ioppr_build_iccprofile_params_cl(const dt_iop_order_iccprofile_info_t *const profile_info, const int devid, dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
build the required parameters for a kernel that uses a profile info.
void dt_colorspaces_flush_profile_memo(void)
Drop every memoised entry. Called by dt_colorprofiles_cleanup().
void dt_ioppr_transform_lcms2(const char *op, const char *multi_name, const float *const image_in, float *const image_out, const int width, const int height, const dt_iop_colorspace_type_t cst_from, const dt_iop_colorspace_type_t cst_to, dt_iop_colorspace_type_t *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
The lcms2 branch of dt_colorspaces_apply_profile(): RGB <-> Lab via cmsDoTransform.
The colour-profile struct and the maths over it: the derived matrix/LUT engine.
static const float x
const float *const lut
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
const float const int lutsize
void dt_colorspaces_unlock_profile(const dt_colorspaces_color_profile_t *const profile)
__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.
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_lock_profile(const dt_colorspaces_color_profile_t *const profile)
The colour-profile module's API: which profiles exist, and how to apply one.
static dt_aligned_pixel_t rgb
dt_Lab_to_XYZ(Lab, XYZ)
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)))
dt_XYZ_to_Lab(XYZ, Lab)
const dt_colormatrix_t matrix
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
static gboolean dt_iop_colorspace_is_rgb(const dt_iop_colorspace_type_t cst)
Definition format.h:75
static void dt_iop_estimate_exp(const float *const x, const float *const y, const int num, float *coeff)
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_DEV
Definition logging.h:53
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
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 pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:150
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_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
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
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2595
int dt_opencl_copy_device_to_host(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2622
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2935
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2489
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2791
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2720
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2532
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2586
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2806
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2846
int dt_opencl_write_host_to_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2675
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#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
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
@ DT_INTENT_PERCEPTUAL
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_DISPLAY
The monitor profile – the one list entry that mutates after init.
@ DT_COLORSPACE_LAB
@ DT_COLORSPACE_NONE
No profile / "take it from the image settings". Never matches a list entry.
@ DT_COLORSPACE_LIN_REC2020
@ DT_PROFILE_ROLE_WORKING
Listed in the working-profile combo (colorin).
@ DT_PROFILE_ROLE_ANY
All four roles, in registration order.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
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
The three colorspace kernels, compiled once and held by common/opencl.c.
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
The device-side view of a dt_iop_order_iccprofile_info_t: the scalar fields only.
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
#define __DT_CLONE_TARGETS__
static void dt_get_times(dt_times_t *t)
Definition times.h:50