Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorprofiles/iop_profile.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2018-2019 Edgardo Hoszowski.
4 Copyright (C) 2019-2021, 2025 Aurélien PIERRE.
5 Copyright (C) 2019 Hanno Schwalm.
6 Copyright (C) 2019 luzpaz.
7 Copyright (C) 2019 Marcus Rückert.
8 Copyright (C) 2019-2020 Pascal Obry.
9 Copyright (C) 2020-2021 Dan Torop.
10 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
11 Copyright (C) 2021 Heiko Bauke.
12 Copyright (C) 2021 Hubert Kowalski.
13 Copyright (C) 2021 Ralf Brown.
14 Copyright (C) 2021 Sakari Kapanen.
15 Copyright (C) 2022 Martin Bařinka.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU Lesser General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU Lesser General Public License for more details.
26
27 You should have received a copy of the GNU Lesser General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30#ifndef DT_COLORPROFILES_IOP_PROFILE_H
31#define DT_COLORPROFILES_IOP_PROFILE_H
32
72#include "pixel/format.h" // dt_iop_colorspace_type_t
75/* develop/imageop.h is deliberately NOT included. Including it made common/ depend on
76 * develop/ (a layering inversion) and closed a 6-node include cycle with common/opencl.h.
77 * It used to be needed for four iop/pixelpipe types used as POINTERS, which were then
78 * tag-declared here instead; those declarations have since moved out with the
79 * pipeline-facing half, and no declaration below names an iop or pipe type at all. The
80 * `dt_iop_*` names that remain (dt_iop_colorspace_type_t, dt_iop_color_intent_t) are pixel
81 * and profile vocabulary, from pixel/format.h and colorprofiles/profile_types.h. */
82
83#ifdef HAVE_CONFIG_H
84#include "config.h"
85#endif
86
87#ifdef HAVE_OPENCL
88#include <CL/cl.h> // for cl_mem
89#endif
90
91
116{
129 dt_colormatrix_t matrix_in; // don't align on more than 16 bits or OpenCL will fail
141 float *lut_in[3];
143 float *lut_out[3];
148 float unbounded_coeffs_in[3][3] DT_ALIGNED_PIXEL;
150 float unbounded_coeffs_out[3][3] DT_ALIGNED_PIXEL;
162 float grey;
163 dt_colormatrix_t matrix_in_transposed; // same as matrix_in, but stored such as to permit vectorization
164 dt_colormatrix_t matrix_out_transposed; // same as matrix_out, but stored such as to permit vectorization
166
238 const char *profile_filename,
239 const int intent);
240
249
264
265/* --- APPLY: the pixel loop ------------------------------------------------
266 *
267 * Convert a buffer between colour spaces. Branches internally on what the profile is:
268 * a matrix-shaper with tone curves takes our own vectorised matrix + LUT path, anything
269 * else falls back to lcms2. Callers neither choose nor see which.
270 *
271 * `op_name`/`instance_name` label the -d perf trace only, and are plain strings rather
272 * than the dt_iop_module_t they come from: this module sits below develop/ and cannot
273 * name an iop. */
274
313void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name,
314 const float *const image_in, float *const image_out,
315 const int width, const int height,
316 const int cst_from, const int cst_to, int *converted_cst,
318
319#ifdef HAVE_OPENCL
339int dt_colorspaces_apply_profile_cl(const char *const op_name, const char *const instance_name,
340 const int devid, cl_mem dev_img_in, cl_mem dev_img_out,
341 const int width, const int height,
342 const int cst_from, const int cst_to, int *converted_cst,
344#endif
345
369void dt_ioppr_transform_image_colorspace_rgb(const float *const image_in, float *const image_out, const int width,
370 const int height,
373 const char *message);
374
375#ifdef HAVE_OPENCL
389
402// must be in synch with colorspaces.cl dt_colorspaces_iccprofile_info_cl_t
404{
405 cl_float matrix_in[9];
406 cl_float matrix_out[9];
407 cl_int lutsize;
408 cl_float unbounded_coeffs_in[3][3];
409 cl_float unbounded_coeffs_out[3][3];
411 cl_float grey;
413
421
442
469 cl_float **_profile_lut_cl, cl_mem *_dev_profile_info,
470 cl_mem *_dev_profile_lut);
481 cl_float **_profile_lut_cl, cl_mem *_dev_profile_info,
482 cl_mem *_dev_profile_lut);
498int dt_ioppr_transform_image_colorspace_rgb_cl(const int devid, cl_mem dev_img_in, cl_mem dev_img_out,
499 const int width, const int height,
502 const char *message);
503#endif
504
505/* --- The per-pixel primitives -------------------------------------------------------
506 *
507 * the following must have the matrix_in and matrix_out generated
508 *
509 * Everything below is `static inline` and published as inline code on purpose: these run
510 * once per pixel per module, so a call would dominate the work. That is the one legitimate
511 * reason a header carries an implementation, and it is why this header includes
512 * common/colorspaces_inline_conversions.h -- a deliberate performance trade, not a
513 * convenience.
514 *
515 * They all take the profile's fields as SEPARATE arguments rather than the struct, so the
516 * OpenMP `uniform`/`aligned` clauses can promise the vectoriser that the matrix and the LUTs
517 * are loop-invariant and aligned. Passing `profile_info` instead would forfeit that.
518 *
519 * NONE of them checks the "is this a matrix profile" sentinel: they assume the matrices are
520 * real, which is what the leading comment means. Call them only on a profile that came back
521 * from dt_colorspaces_add_profile() with `!isnan(matrix_in[0][0])`.
522 */
523
537{
538 // TODO: check if optimization is worthwhile!
539 const float ft = CLAMPS(v * (lutsize - 1), 0, lutsize - 1);
540 const int t = (ft < lutsize - 2) ? ft : lutsize - 2;
541 const float f = ft - t;
542 const float l1 = lut[t];
543 const float l2 = lut[t + 1];
544 return l1 * (1.0f - f) + l2 * f;
545}
546
547
560{
561 return coeff[1] * powf(x * coeff[0], coeff[2]);
562}
563
578{
579 return (x < 1.0f) ? extrapolate_lut(lut, x, lutsize) : eval_exp(coeff, x);
580}
593#ifdef _OPENMP
594#pragma omp declare simd \
595 aligned(rgb_in, rgb_out, unbounded_coeffs:16) \
596 aligned(lut:64) \
597 uniform(lut, unbounded_coeffs)
598#endif
599static inline __attribute__((always_inline)) void
601 const float unbounded_coeffs[3][3], const int lutsize)
602{
603 for(int c = 0; c < 3; c++)
604 {
605 rgb_out[c] = (!IS_NULL_PTR(lut[c]) && lut[c][0] >= 0.0f) ? dt_ioppr_eval_trc(rgb_in[c], lut[c], unbounded_coeffs[c], lutsize)
606 : rgb_in[c];
607 }
608}
609
629#ifdef _OPENMP
630#pragma omp declare simd \
631 aligned(rgb:16) \
632 aligned(matrix_in:64) \
633 aligned(unbounded_coeffs_in:16) \
634 aligned(lut_in:64) \
635 uniform(matrix_in, lut_in, unbounded_coeffs_in)
636#endif
638 const dt_colormatrix_t matrix_in, float *const lut_in[3],
639 const float unbounded_coeffs_in[3][3],
640 const int lutsize, const int nonlinearlut)
641{
642 float luminance = 0.f;
643
644 if(nonlinearlut)
645 {
647 _apply_trc(rgb, linear_rgb, lut_in, unbounded_coeffs_in, lutsize);
648 luminance = matrix_in[1][0] * linear_rgb[0] + matrix_in[1][1] * linear_rgb[1] + matrix_in[1][2] * linear_rgb[2];
649 }
650 else
651 luminance = matrix_in[1][0] * rgb[0] + matrix_in[1][1] * rgb[1] + matrix_in[1][2] * rgb[2];
652
653 return luminance;
654}
655
656
672#ifdef _OPENMP
673#pragma omp declare simd \
674 aligned(rgb, xyz:16) \
675 aligned(matrix_in_transposed:64) \
676 aligned(unbounded_coeffs_in:16) \
677 aligned(lut_in:64) \
678 uniform(matrix_in_transposed, lut_in, unbounded_coeffs_in)
679#endif
681 const dt_colormatrix_t matrix_in_transposed, float *const lut_in[3],
682 const float unbounded_coeffs_in[3][3],
683 const int lutsize, const int nonlinearlut)
684{
685 if(nonlinearlut)
686 {
688 _apply_trc(rgb, linear_rgb, lut_in, unbounded_coeffs_in, lutsize);
689 dt_apply_transposed_color_matrix(linear_rgb, matrix_in_transposed, xyz);
690 }
691 else
692 dt_apply_transposed_color_matrix(rgb, matrix_in_transposed, xyz);
693}
694
709#ifdef _OPENMP
710#pragma omp declare simd \
711 aligned(lab, rgb:16) \
712 aligned(matrix_out_transposed:64) \
713 aligned(unbounded_coeffs_out:16) \
714 aligned(lut_out:64) \
715 uniform(matrix_out_transposed, lut_out, unbounded_coeffs_out)
716#endif
718 const dt_colormatrix_t matrix_out_transposed, float *const lut_out[3],
719 const float unbounded_coeffs_out[3][3],
720 const int lutsize, const int nonlinearlut)
721{
723 dt_Lab_to_XYZ(lab, xyz);
724
725 if(nonlinearlut)
726 {
728 dt_apply_transposed_color_matrix(xyz, matrix_out_transposed, linear_rgb);
729 _apply_trc(linear_rgb, rgb, lut_out, unbounded_coeffs_out, lutsize);
730 }
731 else
732 {
733 dt_apply_transposed_color_matrix(xyz, matrix_out_transposed, rgb);
734 }
735}
736
748#ifdef _OPENMP
749#pragma omp declare simd \
750 aligned(rgb, lab:16) \
751 aligned(matrix_in_transposed:64) \
752 aligned(unbounded_coeffs_in:16) \
753 aligned(lut_in:64) \
754 uniform(matrix_in_transposed, lut_in, unbounded_coeffs_in)
755#endif
757 const dt_colormatrix_t matrix_in_transposed, float *const lut_in[3],
758 const float unbounded_coeffs_in[3][3],
759 const int lutsize, const int nonlinearlut)
760{
761 dt_aligned_pixel_t xyz = { 0.f };
762 dt_ioppr_rgb_matrix_to_xyz(rgb, xyz, matrix_in_transposed, lut_in, unbounded_coeffs_in, lutsize, nonlinearlut);
763 dt_XYZ_to_Lab(xyz, lab);
764}
765
773{
774 return profile_info->grey;
775}
776
791static inline float dt_ioppr_compensate_middle_grey(const float x, const dt_iop_order_iccprofile_info_t *const profile_info)
792{
793 // we transform the curve nodes from the image colorspace to lab
794 dt_aligned_pixel_t lab = { 0.0f };
795 const dt_aligned_pixel_t rgb = { x, x, x };
796 dt_ioppr_rgb_matrix_to_lab(rgb, lab, profile_info->matrix_in_transposed, profile_info->lut_in,
797 profile_info->unbounded_coeffs_in, profile_info->lutsize, profile_info->nonlinearlut);
798 return lab[0] * .01f;
799}
800
810static inline float dt_ioppr_uncompensate_middle_grey(const float x, const dt_iop_order_iccprofile_info_t *const profile_info)
811{
812 // we transform the curve nodes from lab to the image colorspace
813 const dt_aligned_pixel_t lab = { x * 100.f, 0.0f, 0.0f };
814 dt_aligned_pixel_t rgb = { 0.0f };
815
816 dt_ioppr_lab_to_rgb_matrix(lab, rgb, profile_info->matrix_out_transposed, profile_info->lut_out,
817 profile_info->unbounded_coeffs_out, profile_info->lutsize, profile_info->nonlinearlut);
818 return rgb[0];
819}
820
821
822/* --- The transform core, unwrapped ---------------------------------------------------
823 *
824 * Shared with the pipeline-facing half in develop/iop_profile.c. These were static; they are
825 * the transform core both sides drive, not either side's private business. The two transform workers
826 * take the module's name strings, not the module: naming it in a log line was the only use,
827 * and a pointer would have kept this header reaching into develop/.
828 *
829 * These are the two branches dt_colorspaces_apply_profile() picks between, plus the three
830 * builders that decide which branch a profile lands on, exposed for code that must build or
831 * fix up a ::dt_iop_order_iccprofile_info_t by hand -- an image-derived input profile, say,
832 * whose matrices come from iop/colorin.c rather than from a file. Prefer
833 * dt_colorspaces_apply_profile(): calling a worker directly means choosing the branch
834 * yourself, and choosing it wrong is silently wrong output rather than an error.
835 *
836 * As of this writing the tree has no caller for any of these five outside
837 * colorprofiles/iop_profile.c itself.
838 */
839
871int dt_ioppr_init_unbounded_coeffs(float *lutr, float *lutg, float *lutb,
873 float *unbounded_coeffsb, const int lutsize);
896void dt_ioppr_transform_lcms2(const char *op, const char *multi_name, const float *const image_in,
897 float *const image_out, const int width, const int height,
921void dt_ioppr_transform_matrix(const char *op, const char *multi_name, const float *const image_in,
922 float *const image_out, const int width, const int height,
927
928#endif
929// clang-format off
930// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
931// vim: shiftwidth=2 expandtab tabstop=2 cindent
932// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
933// clang-format on
dt_iop_colorspace_type_t
static const float x
const float l2
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.
static dt_aligned_pixel_t rgb_out
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.
const float f
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...
const float *const lut
void dt_colorspaces_init_cl_global(void)
Compile the colorspace kernels. Called once by common/opencl.c at device init.
void dt_ioppr_transform_image_colorspace_rgb(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_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
Convert a 4-channel float buffer between two RGB profiles.
void dt_ioppr_transform_matrix(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 vectorised branch of dt_colorspaces_apply_profile(): RGB <-> Lab by matrix and tone-curve LUT.
int dt_ioppr_init_unbounded_coeffs(float *lutr, float *lutg, float *lutb, float *unbounded_coeffsr, float *unbounded_coeffsg, float *unbounded_coeffsb, const int lutsize)
Fit the power-law continuation of three tone curves past white.
const float *const const float coeff[3]
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.
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.
const int t
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.
static dt_aligned_pixel_t float *const const float unbounded_coeffs[3][3]
void dt_colorspaces_invalidate_display_profile_memo(void)
Drop the memoised DT_COLORSPACE_DISPLAY entry, whose source profile this module replaces on a monitor...
const float l1
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.
const float const int lutsize
const float v
void dt_ioppr_clear_lut_curves(dt_iop_order_iccprofile_info_t *profile_info)
Mark all six tone curves linear, by writing -1.0 into entry 0 of each.
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.
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 dt_aligned_pixel_t rgb
dt_Lab_to_XYZ(Lab, XYZ)
dt_apply_transposed_color_matrix(XYZ, xyz_to_srgb_matrix_transposed, sRGB)
dt_XYZ_to_Lab(XYZ, Lab)
float *const restrict luminance
#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
#define CLAMPS(A, L, H)
Definition math.h:78
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:65
The colour-profile vocabulary, and nothing else.
dt_iop_color_intent_t
ICC rendering intent, as stored in iop params and in conf.
#define DT_IOP_COLOR_ICC_LEN
Size of every ICC filename buffer that crosses a module boundary.
dt_colorspaces_color_profile_type_t
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
The three colorspace kernels, compiled once and held by common/opencl.c.
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,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
dt_iop_color_intent_t intent
Rendering intent, passed to cmsCreateTransform on the lcms2 fallback only. The matrix path ignores it...
float grey
Luminance of 18.42% grey through this profile, used by the tone-mapping modules to place middle grey....
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
float * lut_out[3]
Per-channel linear -> encoded tone curve, same convention as lut_in.
dt_colormatrix_t matrix_out
XYZ (D50) -> RGB, row-major; the inverse of matrix_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
char filename[DT_IOP_COLOR_ICC_LEN]
ICC file name, the other half of the memo key; "" for every built-in. Compared with strcmp,...
float unbounded_coeffs_in[3][3] DT_ALIGNED_PIXEL
Power-law fit {a, b, c} of lut_in past 1.0, evaluated by eval_exp() as b * (a*x)^c....
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
float unbounded_coeffs_out[3][3] DT_ALIGNED_PIXEL
Same fit for lut_out.