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 }
404 }
405 else
406 {
408 for(size_t y = 0; y < stride; y += ch)
409 {
410 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
411 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
412
414 const dt_aligned_pixel_simd_t vin = dt_load_simd_aligned(in);
416 dt_XYZ_to_Lab(xyz, out);
417 }
418 }
419}
420
421
423static inline void _transform_lab_to_rgb_matrix(const float *const image_in, float *const image_out, const int width,
424 const int height,
426{
427 const int ch = 4;
428 const size_t stride = (size_t)width * height * ch;
429 const int use_nontemporal = !profile_info->nonlinearlut;
430 const dt_colormatrix_t *matrix_ptr = &profile_info->matrix_out_transposed;
431 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(*matrix_ptr, 0);
432 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(*matrix_ptr, 1);
433 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(*matrix_ptr, 2);
435 for(size_t y = 0; y < stride; y += ch)
436 {
437 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
438 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
439
441 const float alpha = in[3]; // some code does in-place conversions and relies on alpha being preserved
442 dt_Lab_to_XYZ(in, xyz);
443 const dt_aligned_pixel_simd_t vxyz = dt_load_simd_aligned(xyz);
444 dt_aligned_pixel_simd_t rgb = dt_mat3x4_mul_vec4(vxyz, m0, m1, m2);
445 rgb[3] = alpha;
448 else
450 }
451
453 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
454
455 if(profile_info->nonlinearlut)
456 {
457 // TODO : maybe optimize that path like _transform_matrix_rgb
459 profile_info->lut_out[2], profile_info->unbounded_coeffs_out[0],
460 profile_info->unbounded_coeffs_out[1], profile_info->unbounded_coeffs_out[2],
461 profile_info->lutsize);
462 }
463}
464
465
467static inline void _transform_matrix_rgb(const float *const restrict image_in,
468 float *const restrict image_out,
469 const int width, const int height,
472{
473 const int ch = 4;
474 const size_t stride = (size_t)width * height * ch;
475
476 // RGB -> XYZ -> RGB are 2 matrices products, they can be premultiplied globally ahead
477 // and put in a new matrix. then we spare one matrix product per pixel.
482 const dt_aligned_pixel_simd_t m0 = dt_colormatrix_row_to_simd(matrix, 0);
483 const dt_aligned_pixel_simd_t m1 = dt_colormatrix_row_to_simd(matrix, 1);
484 const dt_aligned_pixel_simd_t m2 = dt_colormatrix_row_to_simd(matrix, 2);
485
486 if(profile_info_from->nonlinearlut || profile_info_to->nonlinearlut)
487 {
488 const int use_nontemporal = !profile_info_to->nonlinearlut;
489 const int run_lut_in[3] DT_ALIGNED_PIXEL= { (profile_info_from->lut_in[0][0] >= 0.0f),
490 (profile_info_from->lut_in[1][0] >= 0.0f),
491 (profile_info_from->lut_in[2][0] >= 0.0f) };
492
493 const int run_lut_out[3] DT_ALIGNED_PIXEL = { (profile_info_to->lut_out[0][0] >= 0.0f),
494 (profile_info_to->lut_out[1][0] >= 0.0f),
495 (profile_info_to->lut_out[2][0] >= 0.0f) };
497 for(size_t y = 0; y < stride; y += 4)
498 {
499 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
500 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
502
503 // linearize if non-linear input
504 if(profile_info_from->nonlinearlut)
505 {
506 for(size_t c = 0; c < 3; c++)
507 {
508 rgb[c] = (run_lut_in[c]
509 ? dt_ioppr_eval_trc(in[c], profile_info_from->lut_in[c],
510 profile_info_from->unbounded_coeffs_in[c], profile_info_from->lutsize)
511 : in[c]);
512 }
513 }
514 else
515 {
517 rgb[c] = in[c];
518 }
519
520 if(profile_info_to->nonlinearlut)
521 {
522 // convert color space
524 const dt_aligned_pixel_simd_t vrgb = dt_load_simd_aligned(rgb);
526
527 // de-linearize non-linear output
528 for(size_t c = 0; c < 3; c++)
529 {
530 out[c] = (run_lut_out[c]
531 ? dt_ioppr_eval_trc(temp[c], profile_info_to->lut_out[c],
532 profile_info_to->unbounded_coeffs_out[c], profile_info_to->lutsize)
533 : temp[c]);
534 }
535 }
536 else
537 {
538 // convert color space
539 const dt_aligned_pixel_simd_t vrgb = dt_load_simd_aligned(rgb);
542 else
544 }
545 }
546
548 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
549 }
550 else
551 {
553 for(size_t y = 0; y < stride; y += 4)
554 {
555 const float *const restrict in = __builtin_assume_aligned(image_in + y, 16);
556 float *const restrict out = __builtin_assume_aligned(image_out + y, 16);
557
558 const dt_aligned_pixel_simd_t vin = dt_load_simd_aligned(in);
560 }
561 dt_omploop_sfence(); // ensure that nontemporal writes complete before we attempt to read output
562 }
563}
564
565
566inline void dt_ioppr_transform_matrix(const char *op, const char *multi_name,
567 const float *const restrict image_in,
568 float *const restrict image_out,
569 const int width, const int height,
574{
575 if(cst_from == cst_to)
576 {
578 return;
579 }
580
582
584 {
586 }
588 {
590 }
591 else
592 {
594 fprintf(stderr, "[dt_ioppr_transform_matrix] invalid conversion from %i to %i\n", cst_from, cst_to);
595 }
596}
597
598
599#define DT_IOPPR_LUT_SAMPLES 0x10000
600
603{
605 profile_info->filename[0] = '\0';
608 profile_info->unbounded_coeffs_in[0][0] = profile_info->unbounded_coeffs_in[1][0] = profile_info->unbounded_coeffs_in[2][0] = -1.0f;
609 profile_info->unbounded_coeffs_out[0][0] = profile_info->unbounded_coeffs_out[1][0] = profile_info->unbounded_coeffs_out[2][0] = -1.0f;
610 profile_info->nonlinearlut = 0;
611 profile_info->grey = 0.f;
613 for(int i = 0; i < 3; i++)
614 {
615 profile_info->lut_in[i] = dt_alloc_align_float(profile_info->lutsize);
616 profile_info->lut_in[i][0] = -1.0f;
617 profile_info->lut_out[i] = dt_alloc_align_float(profile_info->lutsize);
618 profile_info->lut_out[i][0] = -1.0f;
619 }
620}
621
622#undef DT_IOPPR_LUT_SAMPLES
623
625{
626 /* The whole teardown, not just the LUTs. A dt_iop_order_iccprofile_info_t owns six
627 * aligned float arrays, so releasing one is "free the curves, free the struct, drop the
628 * pointer" -- three steps every caller was open-coding, and three chances to free the
629 * struct while leaving 1.5 MB of curves behind. Takes the pointer by address so the
630 * caller's variable cannot be left dangling. */
632
633 for(int i = 0; i < 3; i++)
634 {
635 dt_free_align((*profile_info)->lut_in[i]);
636 (*profile_info)->lut_in[i] = NULL;
637 dt_free_align((*profile_info)->lut_out[i]);
638 (*profile_info)->lut_out[i] = NULL;
639 }
640
643}
644
645void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width,
646 const int height,
649 const char *message)
650{
652 {
653 return;
654 }
655 if(profile_info_from->type == profile_info_to->type
656 && strcmp(profile_info_from->filename, profile_info_to->filename) == 0)
657 {
658 if(image_in != image_out)
659 memcpy(image_out, image_in, sizeof(float) * 4 * width * height);
660
661 return;
662 }
663
664 dt_times_t start_time = { 0 }, end_time = { 0 };
666
667 if(!isnan(profile_info_from->matrix_in[0][0]) && !isnan(profile_info_from->matrix_out[0][0])
668 && !isnan(profile_info_to->matrix_in[0][0]) && !isnan(profile_info_to->matrix_out[0][0]))
669 {
671
673 {
675 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f CPU) [%s]\n",
676 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
677 }
678 }
679 else
680 {
682
684 {
686 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f lcms2) [%s]\n",
687 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
688 }
689 }
690}
691
692#ifdef HAVE_OPENCL
693/* The kernels this subsystem compiles, owned HERE. They used to be handed to
694 * common/opencl.c, parked on the application-wide dt_opencl_t, and read back from it --
695 * a round trip through a god-struct that added nothing but an ordering. opencl.c still
696 * calls init/free, because the kernels must be built after the devices exist, but the
697 * pointer never leaves this file. */
699
701{
703
704 const int program = 23; // colorspaces.cl, from programs.conf
705 g->kernel_colorspaces_transform_lab_to_rgb_matrix = dt_opencl_create_kernel(program, "colorspaces_transform_lab_to_rgb_matrix");
706 g->kernel_colorspaces_transform_rgb_matrix_to_lab = dt_opencl_create_kernel(program, "colorspaces_transform_rgb_matrix_to_lab");
707 g->kernel_colorspaces_transform_rgb_matrix_to_rgb
708 = dt_opencl_create_kernel(program, "colorspaces_transform_rgb_matrix_to_rgb");
710}
711
713{
716 if(IS_NULL_PTR(g)) return;
717
718 // destroy kernels
719 dt_opencl_free_kernel(g->kernel_colorspaces_transform_lab_to_rgb_matrix);
720 dt_opencl_free_kernel(g->kernel_colorspaces_transform_rgb_matrix_to_lab);
721 dt_opencl_free_kernel(g->kernel_colorspaces_transform_rgb_matrix_to_rgb);
722
723 dt_free(g);
724}
725
727{
728 for(int i = 0; i < 9; i++)
729 {
730 profile_info_cl->matrix_in[i] = profile_info->matrix_in[i/3][i%3];
731 profile_info_cl->matrix_out[i] = profile_info->matrix_out[i/3][i%3];
732 }
733 profile_info_cl->lutsize = profile_info->lutsize;
734 for(int i = 0; i < 3; i++)
735 {
736 for(int j = 0; j < 3; j++)
737 {
738 profile_info_cl->unbounded_coeffs_in[i][j] = profile_info->unbounded_coeffs_in[i][j];
739 profile_info_cl->unbounded_coeffs_out[i][j] = profile_info->unbounded_coeffs_out[i][j];
740 }
741 }
742 profile_info_cl->nonlinearlut = profile_info->nonlinearlut;
743 profile_info_cl->grey = profile_info->grey;
744}
745
747{
748 cl_float *trc = malloc(sizeof(cl_float) * 6 * profile_info->lutsize);
749 if(trc)
750 {
751 int x = 0;
752 for(int c = 0; c < 3; c++)
753 for(int y = 0; y < profile_info->lutsize; y++, x++)
754 trc[x] = profile_info->lut_in[c][y];
755 for(int c = 0; c < 3; c++)
756 for(int y = 0; y < profile_info->lutsize; y++, x++)
757 trc[x] = profile_info->lut_out[c][y];
758 }
759 return trc;
760}
761
764 cl_float **_profile_lut_cl, cl_mem *_dev_profile_info,
765 cl_mem *_dev_profile_lut)
766{
767 cl_int err = CL_SUCCESS;
768
770 cl_float *profile_lut_cl = NULL;
771 cl_mem dev_profile_info = NULL;
772 cl_mem dev_profile_lut = NULL;
773
774 if(profile_info)
775 {
778
781 {
782 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 5\n");
784 goto cleanup;
785 }
786
787 dev_profile_lut = dt_opencl_copy_host_to_device(devid, profile_lut_cl, 256, 256 * 6, sizeof(float));
789 {
790 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 6\n");
792 goto cleanup;
793 }
794 }
795 else
796 {
797 profile_lut_cl = malloc(sizeof(cl_float) * 1 * 6);
798
799 dev_profile_lut = dt_opencl_copy_host_to_device(devid, profile_lut_cl, 1, 1 * 6, sizeof(float));
801 {
802 fprintf(stderr, "[dt_ioppr_build_iccprofile_params_cl] error allocating memory 7\n");
804 goto cleanup;
805 }
806 }
807
808cleanup:
813
814 return err;
815}
816
839
841 const int width, const int height,
844 const char *message)
845{
846 cl_int err = CL_SUCCESS;
847
849 {
850 return FALSE;
851 }
852 if(profile_info_from->type == profile_info_to->type
853 && strcmp(profile_info_from->filename, profile_info_to->filename) == 0)
854 {
856 {
857 size_t origin[] = { 0, 0, 0 };
858 size_t region[] = { width, height, 1 };
859
861 if(err != CL_SUCCESS)
862 {
864 "[dt_ioppr_transform_image_colorspace_rgb_cl] error on copy image for color transformation\n");
865 return FALSE;
866 }
867 }
868
869 return TRUE;
870 }
871
872 const size_t ch = 4;
873 float *src_buffer_in = NULL;
874 float *src_buffer_out = NULL;
876
877 int kernel_transform = 0;
878 cl_mem dev_tmp = NULL;
879
881 cl_mem dev_lut_from = NULL;
883 cl_float *lut_from_cl = NULL;
884
885 cl_mem dev_profile_info_to = NULL;
886 cl_mem dev_lut_to = NULL;
888 cl_float *lut_to_cl = NULL;
889
890 cl_mem matrix_cl = NULL;
891
892 // if we have a matrix use opencl
893 if(!isnan(profile_info_from->matrix_in[0][0]) && !isnan(profile_info_from->matrix_out[0][0])
894 && !isnan(profile_info_to->matrix_in[0][0]) && !isnan(profile_info_to->matrix_out[0][0]))
895 {
896 dt_times_t start_time = { 0 }, end_time = { 0 };
898
899 size_t origin[] = { 0, 0, 0 };
900 size_t region[] = { width, height, 1 };
901
903
906
909
912
913 if(in_place)
914 {
915 dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
917 {
918 fprintf(
919 stderr,
920 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 4\n");
922 goto cleanup;
923 }
924
926 if(err != CL_SUCCESS)
927 {
929 "[dt_ioppr_transform_image_colorspace_rgb_cl] error on copy image for color transformation\n");
930 goto cleanup;
931 }
932 }
933 else
934 {
936 }
937
941 {
943 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 5\n");
945 goto cleanup;
946 }
947 dev_lut_from = dt_opencl_copy_host_to_device(devid, lut_from_cl, 256, 256 * 6, sizeof(float));
949 {
951 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 6\n");
953 goto cleanup;
954 }
955
959 {
961 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 7\n");
963 goto cleanup;
964 }
965 dev_lut_to = dt_opencl_copy_host_to_device(devid, lut_to_cl, 256, 256 * 6, sizeof(float));
967 {
969 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 8\n");
971 goto cleanup;
972 }
973 float matrix3x4[12];
977 {
979 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 7\n");
981 goto cleanup;
982 }
983
984 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
985
986 dt_opencl_set_kernel_arg(devid, kernel_transform, 0, sizeof(cl_mem), (void *)&dev_tmp);
987 dt_opencl_set_kernel_arg(devid, kernel_transform, 1, sizeof(cl_mem), (void *)&dev_img_out);
988 dt_opencl_set_kernel_arg(devid, kernel_transform, 2, sizeof(int), (void *)&width);
989 dt_opencl_set_kernel_arg(devid, kernel_transform, 3, sizeof(int), (void *)&height);
990 dt_opencl_set_kernel_arg(devid, kernel_transform, 4, sizeof(cl_mem), (void *)&dev_profile_info_from);
991 dt_opencl_set_kernel_arg(devid, kernel_transform, 5, sizeof(cl_mem), (void *)&dev_lut_from);
992 dt_opencl_set_kernel_arg(devid, kernel_transform, 6, sizeof(cl_mem), (void *)&dev_profile_info_to);
993 dt_opencl_set_kernel_arg(devid, kernel_transform, 7, sizeof(cl_mem), (void *)&dev_lut_to);
994 dt_opencl_set_kernel_arg(devid, kernel_transform, 8, sizeof(cl_mem), (void *)&matrix_cl);
996 if(err != CL_SUCCESS)
997 {
999 "[dt_ioppr_transform_image_colorspace_rgb_cl] error %i enqueue kernel for color transformation\n",
1000 err);
1001 goto cleanup;
1002 }
1003
1005 {
1007 fprintf(stderr, "image colorspace transform RGB-->RGB took %.3f secs (%.3f GPU) [%s]\n",
1008 end_time.clock - start_time.clock, end_time.user - start_time.user, (message) ? message : "");
1009 }
1010 }
1011 else
1012 {
1013 // no matrix, call lcms2
1017 {
1019 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 1\n");
1021 goto cleanup;
1022 }
1023
1025 if(err != CL_SUCCESS)
1026 {
1028 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 2\n");
1029 goto cleanup;
1030 }
1031
1032 // just call the CPU version for now
1034 profile_info_to, message);
1035
1037 if(err != CL_SUCCESS)
1038 {
1040 "[dt_ioppr_transform_image_colorspace_rgb_cl] error allocating memory for color transformation 3\n");
1041 goto cleanup;
1042 }
1043 }
1044
1045cleanup:
1049
1053
1057
1059
1060 return (err == CL_SUCCESS) ? TRUE : FALSE;
1061}
1062#endif
1063
1064#undef DT_IOP_ORDER_PROFILE
1065// clang-format off
1066// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1067// vim: shiftwidth=2 expandtab tabstop=2 cindent
1068// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1069// clang-format on
1070
1071
1072/* ---------------------------------------------------------------------------
1073 * The derived-profile memo.
1074 *
1075 * Matrix + tone-curve LUTs extracted from a profile: expensive to build (two
1076 * 65536-entry extractions) and a pure function of (type, filename), so it is
1077 * memoised. This lived on dt_develop_t, which made it per-image, unsynchronised,
1078 * and duplicated between concurrent exports; and it made develop/ take this
1079 * module's rwlock by hand to build an entry.
1080 *
1081 * It is the module's now, and flushed by dt_colorprofiles_cleanup().
1082 *
1083 * What deliberately does NOT live here: profiles derived from one image --
1084 * DT_COLORSPACE_EMBEDDED_ICC through DT_COLORSPACE_ALTERNATE_MATRIX. They are not
1085 * registered in the profile list and cannot be resolved by identity at all, so
1086 * they are not a function of their key and would stomp each other here. They live
1087 * on the pipe that built them (dt_dev_pixelpipe_t.owned_input_profile_info).
1088 * ------------------------------------------------------------------------- */
1089
1091/* Raw pthread type, like this module's other locks: dt_pthread_mutex_t is a struct
1092 * wrapper in Debug builds, so a static PTHREAD_MUTEX_INITIALIZER would be initialising
1093 * a subobject -- which clang rejects under -Werror and gcc silently accepts. */
1095
1107
1109{
1110 /* The DISPLAY entry is derived from a cmsHPROFILE this module replaces whenever the
1111 * monitor profile changes. Nothing invalidated it before, so a session kept the previous
1112 * monitor's matrices and tone curves for as long as the memo lived. That was already
1113 * wrong per-image; now that the memo is process-wide it would persist for the whole run.
1114 *
1115 * Dropped rather than rebuilt: the next caller that wants it will build it, and doing it
1116 * here would mean building a profile under this lock from the profile-changed handler. */
1118 for(GList *l = _profile_info_memo; l; )
1119 {
1120 GList *next = g_list_next(l);
1122 if(entry && entry->type == DT_COLORSPACE_DISPLAY)
1123 {
1126 }
1127 l = next;
1128 }
1130}
1131
1132static int _generate_profile_info(dt_iop_order_iccprofile_info_t *profile_info, const int type, const char *filename, const int intent)
1133{
1134 int err_code = 0;
1135 cmsHPROFILE *rgb_profile = NULL;
1136
1139
1140 profile_info->nonlinearlut = 0;
1141 profile_info->grey = 0.1842f;
1142
1143 profile_info->type = type;
1144 g_strlcpy(profile_info->filename, filename, sizeof(profile_info->filename));
1145 profile_info->intent = intent;
1146
1147 /* The DISPLAY entry's cmsHPROFILE is the one thing in the list that is replaced at
1148 * runtime, so resolving it and DERIVING FROM IT have to happen under the same lock.
1149 * develop/ used to take this lock by hand -- and released it immediately after the
1150 * lookup, before cmsGetColorSpace() and the two 65536-entry extractions below, which
1151 * are the parts that actually touch the handle. Inside the module the whole span is
1152 * covered, which is what the lock was for. */
1153 const dt_colorspaces_color_profile_t *profile
1155
1157 if(profile) rgb_profile = profile->profile;
1158
1159 // we only allow rgb profiles
1160 if(rgb_profile)
1161 {
1164 {
1165 fprintf(stderr, "working profile color space `%c%c%c%c' not supported\n",
1166 (char)(rgb_color_space>>24),
1167 (char)(rgb_color_space>>16),
1168 (char)(rgb_color_space>>8),
1169 (char)(rgb_color_space));
1170 rgb_profile = NULL;
1171 }
1172 }
1173
1174 // get the matrix
1175 if(rgb_profile)
1176 {
1178 profile_info->lut_in[1], profile_info->lut_in[2],
1179 profile_info->lutsize)
1181 profile_info->lut_out[0], profile_info->lut_out[1],
1182 profile_info->lut_out[2], profile_info->lutsize))
1183 {
1186 }
1187 else if(isnan(profile_info->matrix_in[0][0]) || isnan(profile_info->matrix_out[0][0]))
1188 {
1191 }
1192 else
1193 {
1194 transpose_3xSSE(profile_info->matrix_in, profile_info->matrix_in_transposed);
1195 transpose_3xSSE(profile_info->matrix_out, profile_info->matrix_out_transposed);
1196 }
1197 }
1198
1199 // now try to initialize unbounded mode:
1200 // we do extrapolation for input values above 1.0f.
1201 // unfortunately we can only do this if we got the computation
1202 // in our hands, i.e. for the fast builtin-dt-matrix-profile path.
1203 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1204 {
1205 profile_info->nonlinearlut = dt_ioppr_init_unbounded_coeffs(profile_info->lut_in[0], profile_info->lut_in[1], profile_info->lut_in[2],
1206 profile_info->unbounded_coeffs_in[0], profile_info->unbounded_coeffs_in[1], profile_info->unbounded_coeffs_in[2], profile_info->lutsize);
1207 dt_ioppr_init_unbounded_coeffs(profile_info->lut_out[0], profile_info->lut_out[1], profile_info->lut_out[2],
1208 profile_info->unbounded_coeffs_out[0], profile_info->unbounded_coeffs_out[1], profile_info->unbounded_coeffs_out[2], profile_info->lutsize);
1209 }
1210
1211 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]) && profile_info->nonlinearlut)
1212 {
1213 const dt_aligned_pixel_t rgb = { 0.1842f, 0.1842f, 0.1842f };
1214 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);
1215 }
1216
1218
1219 return err_code;
1220}
1221
1222/* Caller holds _profile_info_lock. */
1226 const char *profile_filename,
1227 const int intent)
1228{
1230
1231 /* Caller holds _profile_info_lock: this walks a list the pipeline worker and the
1232 * GUI thread both append to. */
1233 for(GList *profiles = _profile_info_memo; profiles; profiles = g_list_next(profiles))
1234 {
1236 if(prof->type == profile_type && prof->intent == intent
1237 && strcmp(prof->filename, profile_filename) == 0)
1238 {
1240 break;
1241 }
1242 }
1243
1244 return profile_info;
1245}
1246
1249 const char *profile_filename,
1250 const int intent)
1251{
1252 /* Find-or-create as ONE critical section. Reached from the pipeline worker -- iop/lut3d.c
1253 * and iop/tonecurve.c call it from process()/process_cl(), once per tile -- and from the
1254 * GUI thread via iop/colorin.c. The lock has to span the lookup as well as the append:
1255 * two threads missing the same key concurrently would otherwise each build an entry
1256 * (1.5 MB of tone-curve LUTs apiece) and append both. */
1258
1261 {
1265 if(err == 0)
1266 {
1268 }
1269 else
1270 {
1271 /* dt_ioppr_init_profile_info() has already allocated six DT_IOPPR_LUT_SAMPLES float
1272 * arrays -- 1.5 MB -- so freeing the struct alone leaked all of them on this path. */
1274 }
1275 }
1276
1278
1279 return profile_info;
1280}
1281
1282/* ---------------------------------------------------------------------------
1283 * APPLY: the pixel loop.
1284 *
1285 * One entry point for converting a buffer between colour spaces, branching
1286 * internally on what the profile actually is. A matrix-shaper profile with tone
1287 * curves goes through our own vectorised matrix + LUT path; anything else -- a
1288 * CLUT profile, a v4 parametric curve lcms2 will not reduce -- falls back to
1289 * cmsDoTransform. Callers do not choose, and do not see either.
1290 *
1291 * These used to live in develop/, which is why every consumer had to know the
1292 * distinction existed. The two implementations they dispatch to were already
1293 * here; only the branch was upstairs.
1294 *
1295 * The op/instance names are for the -d perf trace only. They are plain strings
1296 * rather than the dt_iop_module_t they were read from, because this module sits
1297 * below develop/ and cannot name an iop.
1298 * ------------------------------------------------------------------------- */
1299
1300void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, const float *const image_in,
1301 float *const image_out, const int width, const int height,
1302 const int cst_from, const int cst_to, int *converted_cst,
1304{
1305 if(cst_from == cst_to)
1306 {
1308 return;
1309 }
1311 {
1313 return;
1314 }
1316 {
1318 return;
1319 }
1320 if(profile_info->type == DT_COLORSPACE_NONE)
1321 {
1323 return;
1324 }
1325
1326 dt_times_t start_time = { 0 }, end_time = { 0 };
1328
1329 // matrix should be never NAN, this is only to test it against lcms2!
1330 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1331 {
1333
1335 {
1337 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f CPU) [%s %s]\n",
1338 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1339 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1340 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1341 }
1342 }
1343 else
1344 {
1346
1348 {
1350 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f lcms2) [%s %s]\n",
1351 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1352 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1353 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1354 }
1355 }
1356
1357 if(*converted_cst == cst_from)
1358 fprintf(stderr, "[dt_colorspaces_apply_profile] invalid conversion from %i to %i\n", cst_from, cst_to);
1359}
1360
1361#ifdef HAVE_OPENCL
1362int dt_colorspaces_apply_profile_cl(const char *const op_name, const char *const instance_name, const int devid, cl_mem dev_img_in,
1363 cl_mem dev_img_out, const int width, const int height,
1364 const int cst_from, const int cst_to, int *converted_cst,
1366{
1367 cl_int err = CL_SUCCESS;
1368
1372
1373 if(cst_from == cst_to)
1374 {
1376 return TRUE;
1377 }
1379 {
1381 return TRUE;
1382 }
1384 {
1386 return FALSE;
1387 }
1388 if(profile_info->type == DT_COLORSPACE_NONE)
1389 {
1391 return FALSE;
1392 }
1393
1394 const size_t ch = 4;
1395 float *src_buffer = NULL;
1396
1397 int kernel_transform = 0;
1398 cl_mem dev_profile_info = NULL;
1399 cl_mem dev_lut = NULL;
1401 cl_float *lut_cl = NULL;
1402
1404
1405 // if we have a matrix use opencl
1406 if(!isnan(profile_info->matrix_in[0][0]) && !isnan(profile_info->matrix_out[0][0]))
1407 {
1408 dt_times_t start_time = { 0 }, end_time = { 0 };
1410
1412 {
1414 }
1416 {
1418 }
1419 else
1420 {
1423 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] invalid conversion from %i to %i\n", cst_from, cst_to);
1424 goto cleanup;
1425 }
1426
1429
1432 {
1433 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 5\n");
1435 goto cleanup;
1436 }
1437 dev_lut = dt_opencl_copy_host_to_device(devid, lut_cl, 256, 256 * 6, sizeof(float));
1438 if(IS_NULL_PTR(dev_lut))
1439 {
1440 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 6\n");
1442 goto cleanup;
1443 }
1444
1445 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
1446
1447 dt_opencl_set_kernel_arg(devid, kernel_transform, 0, sizeof(cl_mem), (void *)&dev_img_in);
1448 dt_opencl_set_kernel_arg(devid, kernel_transform, 1, sizeof(cl_mem), (void *)&dev_img_out);
1449 dt_opencl_set_kernel_arg(devid, kernel_transform, 2, sizeof(int), (void *)&width);
1450 dt_opencl_set_kernel_arg(devid, kernel_transform, 3, sizeof(int), (void *)&height);
1451 dt_opencl_set_kernel_arg(devid, kernel_transform, 4, sizeof(cl_mem), (void *)&dev_profile_info);
1452 dt_opencl_set_kernel_arg(devid, kernel_transform, 5, sizeof(cl_mem), (void *)&dev_lut);
1454 if(err != CL_SUCCESS)
1455 {
1456 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error %i enqueue kernel for color transformation\n", err);
1457 goto cleanup;
1458 }
1459
1461
1463 {
1465 fprintf(stderr, "image colorspace transform %s-->%s took %.3f secs (%.3f GPU) [%s %s]\n",
1466 dt_iop_colorspace_is_rgb(cst_from) ? "RGB" : "Lab",
1467 dt_iop_colorspace_is_rgb(cst_to) ? "RGB" : "Lab",
1468 end_time.clock - start_time.clock, end_time.user - start_time.user, op_name, instance_name);
1469 }
1470 }
1471 else
1472 {
1473 // no matrix, call lcms2
1476 {
1477 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 1\n");
1479 goto cleanup;
1480 }
1481
1483 if(err != CL_SUCCESS)
1484 {
1485 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 2\n");
1486 goto cleanup;
1487 }
1488
1489 // just call the CPU version for now
1492
1494 if(err != CL_SUCCESS)
1495 {
1496 fprintf(stderr, "[dt_colorspaces_apply_profile_cl] error allocating memory for color transformation 3\n");
1497 goto cleanup;
1498 }
1499 }
1500
1501cleanup:
1505 dt_free(lut_cl);
1506
1507 return (err == CL_SUCCESS) ? TRUE : FALSE;
1508}
1509#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:78
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:85
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 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)
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:40
@ DT_DEBUG_DEV
Definition logging.h:38
int32_t dt_get_debug_flags(void)
Definition darktable.c:2075
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 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
Definition mem_alloc.h:61
#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
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2276
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:2303
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2616
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2170
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2472
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:2401
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2213
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:2267
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2487
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2527
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:2356
#define ROUNDUPDHT(a, b)
Definition opencl.h:83
#define ROUNDUPDWD(a, b)
Definition opencl.h:82
#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
#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