Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
overexposed.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Bruce Guenter.
4 Copyright (C) 2010-2014, 2016, 2019 Tobias Ellinghaus.
5 Copyright (C) 2011 Antony Dovgal.
6 Copyright (C) 2011 Ger Siemerink.
7 Copyright (C) 2011-2012 Henrik Andersson.
8 Copyright (C) 2011-2013, 2016 johannes hanika.
9 Copyright (C) 2011 Jérémy Rosen.
10 Copyright (C) 2011 Olivier Tribout.
11 Copyright (C) 2011 Robert Bieber.
12 Copyright (C) 2012 Pascal de Bruijn.
13 Copyright (C) 2012 Richard Wonka.
14 Copyright (C) 2012 Simon Spannagel.
15 Copyright (C) 2012-2014, 2017 Ulrich Pegelow.
16 Copyright (C) 2013, 2020 Aldric Renaudin.
17 Copyright (C) 2014 Edouard Gomez.
18 Copyright (C) 2014-2016 Roman Lebedev.
19 Copyright (C) 2015 Pedro Côrte-Real.
20 Copyright (C) 2017 Heiko Bauke.
21 Copyright (C) 2018, 2020, 2023, 2025-2026 Aurélien PIERRE.
22 Copyright (C) 2018-2019 Edgardo Hoszowski.
23 Copyright (C) 2019 Andreas Schneider.
24 Copyright (C) 2020 Dan Torop.
25 Copyright (C) 2020 Diederik Ter Rahe.
26 Copyright (C) 2020 Hubert Kowalski.
27 Copyright (C) 2020 Pascal Obry.
28 Copyright (C) 2020-2022 Ralf Brown.
29 Copyright (C) 2021 Sakari Kapanen.
30 Copyright (C) 2022 Hanno Schwalm.
31 Copyright (C) 2022 Martin Bařinka.
32 Copyright (C) 2022 Philipp Lutz.
33
34 darktable is free software: you can redistribute it and/or modify
35 it under the terms of the GNU General Public License as published by
36 the Free Software Foundation, either version 3 of the License, or
37 (at your option) any later version.
38
39 darktable is distributed in the hope that it will be useful,
40 but WITHOUT ANY WARRANTY; without even the implied warranty of
41 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
42 GNU General Public License for more details.
43
44 You should have received a copy of the GNU General Public License
45 along with darktable. If not, see <http://www.gnu.org/licenses/>.
46*/
47#ifdef HAVE_CONFIG_H
48#include "system/macros.h"
49#include "system/mem_alloc.h"
51#include "common/logging.h"
52#include "system/openmp.h"
53#include "system/simd.h"
55#include "config.h"
56#endif
57#include "develop/imageop_gui.h"
58#include <stdlib.h>
59
60#include <cairo.h>
61
62#include "common/opencl.h"
63#include "develop/iop_profile.h"
64#include "develop/develop.h"
65#include "develop/imageop.h"
67
68#include "develop/tiling.h"
69#include "iop/iop_api.h"
70
71DT_MODULE(3)
72
79
81 = { {
82 { 0.0f, 0.0f, 0.0f, 1.0f }, // black
83 { 1.0f, 1.0f, 1.0f, 1.0f } // white
84 },
85 {
86 { 1.0f, 0.0f, 0.0f, 1.0f }, // red
87 { 0.0f, 0.0f, 1.0f, 1.0f } // blue
88 },
89 {
90 { 0.371f, 0.434f, 0.934f, 1.0f }, // purple (#5f6fef)
91 { 0.512f, 0.934f, 0.371f, 1.0f } // green (#83ef5f)
92 } };
93
98
103
104/* The indicator settings, sealed by commit_params() from the darkroom's GUI state. They are not
105 * module parameters, so nothing else carries them into the pipeline cache key: runtime_data_hash()
106 * folds them in, and process()/process_cl() render from them rather than re-reading
107 * dev->overexposed, so the pixels are the ones the hash describes. */
115
116const char *name()
117{
118 return _("overexposed");
119}
120
122{
123 return IOP_GROUP_TECHNICAL;
124}
125
130
135
136
137int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
138 void *new_params, const int new_version)
139{
140 // we do no longer have module params in here and just ignore any legacy entries
141 return 0;
142}
143
144
146int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
147 void *const ovoid)
148{
149 const dt_iop_roi_t *const roi_out = &piece->roi_out;
150 const dt_iop_overexposed_data_t *const d = (const dt_iop_overexposed_data_t *)piece->data;
151
152 const int ch = 4;
153
154 const float lower = exp2f(fminf(d->lower, -4.f)); // in EV
155 const float upper = d->upper / 100.0f; // in %
156
157 const int colorscheme = d->colorscheme;
158 const float *const upper_color = dt_iop_overexposed_colors[colorscheme][0];
159 const float *const lower_color = dt_iop_overexposed_colors[colorscheme][1];
160
161 const float *const restrict in = __builtin_assume_aligned((const float *const restrict)ivoid, 64);
162 float *const restrict out = __builtin_assume_aligned((float *const restrict)ovoid, 64);
163
165
166 if(d->mode == DT_CLIPPING_PREVIEW_ANYRGB)
167 {
168 // Any of the RGB channels is out of bounds
170 for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k += ch)
171 {
172 if(in[k + 0] >= upper || in[k + 1] >= upper || in[k + 2] >= upper)
173 {
174 copy_pixel(out + k, upper_color);
175 }
176 else if(in[k + 0] <= lower && in[k + 1] <= lower && in[k + 2] <= lower)
177 {
178 copy_pixel(out + k, lower_color);
179 }
180 else
181 {
182 copy_pixel(out + k, in + k);
183 }
184 }
185 }
186
187 else if(d->mode == DT_CLIPPING_PREVIEW_GAMUT && !IS_NULL_PTR(current_profile))
188 {
189 // Gamut is out of bounds
191 for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k += ch)
192 {
193 const float luminance = dt_ioppr_get_rgb_matrix_luminance(in + k,
194 current_profile->matrix_in, current_profile->lut_in,
195 current_profile->unbounded_coeffs_in,
196 current_profile->lutsize, current_profile->nonlinearlut);
197
198 // luminance is out of bounds
199 if(luminance >= upper)
200 {
201 copy_pixel(out + k, upper_color);
202 }
203 else if(luminance <= lower)
204 {
205 copy_pixel(out + k, lower_color);
206 }
207 // luminance is ok, so check for saturation
208 else
209 {
210 dt_aligned_pixel_t saturation = { 0.f };
211
212 for_each_channel(c,aligned(saturation, in : 64))
213 {
214 saturation[c] = (in[k + c] - luminance);
215 saturation[c] = sqrtf(saturation[c] * saturation[c] / (luminance * luminance + in[k + c] * in[k + c]));
216 }
217
218 // we got over-saturation, relatively to luminance or absolutely over RGB
219 if(saturation[0] > upper || saturation[1] > upper || saturation[2] > upper ||
220 in[k + 0] >= upper || in[k + 1] >= upper || in[k + 2] >= upper)
221 {
222 copy_pixel(out + k, upper_color);
223 }
224
225 // saturation is fine but we got out-of-bounds RGB
226 else if(in[k + 0] <= lower && in[k + 1] <= lower && in[k + 2] <= lower)
227 {
228 copy_pixel(out + k, lower_color);
229 }
230
231 // evererything is fine
232 else
233 {
234 copy_pixel(out + k, in + k);
235 }
236 }
237 }
238 }
239
240 else if(d->mode == DT_CLIPPING_PREVIEW_LUMINANCE && !IS_NULL_PTR(current_profile))
241 {
242 // Luminance channel is out of bounds
244 for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k += ch)
245 {
246 const float luminance = dt_ioppr_get_rgb_matrix_luminance(in + k,
247 current_profile->matrix_in, current_profile->lut_in,
248 current_profile->unbounded_coeffs_in,
249 current_profile->lutsize, current_profile->nonlinearlut);
250
251 if(luminance >= upper)
252 {
253 copy_pixel(out + k, upper_color);
254 }
255
256 else if(luminance <= lower)
257 {
258 copy_pixel(out + k, lower_color);
259 }
260 else
261 {
262 copy_pixel(out + k, in + k);
263 }
264 }
265 }
266
267 else if(d->mode == DT_CLIPPING_PREVIEW_SATURATION && !IS_NULL_PTR(current_profile))
268 {
269 // Show saturation out of bounds where luminance is valid
271 for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k += ch)
272 {
273 const float luminance = dt_ioppr_get_rgb_matrix_luminance(in + k,
274 current_profile->matrix_in, current_profile->lut_in,
275 current_profile->unbounded_coeffs_in,
276 current_profile->lutsize, current_profile->nonlinearlut);
277 if(luminance < upper && luminance > lower)
278 {
279 dt_aligned_pixel_t saturation = { 0.f };
280
281 for_each_channel(c,aligned(saturation, in : 64))
282 {
283 saturation[c] = (in[k + c] - luminance);
284 saturation[c] = sqrtf(saturation[c] * saturation[c] / (luminance * luminance + in[k + c] * in[k + c]));
285 }
286
287 // we got over-saturation, relatively to luminance or absolutely over RGB
288 if(saturation[0] > upper || saturation[1] > upper || saturation[2] > upper ||
289 in[k + 0] >= upper || in[k + 1] >= upper || in[k + 2] >= upper)
290 {
291 copy_pixel(out + k, upper_color);
292 }
293 else if(in[k + 0] <= lower && in[k + 1] <= lower && in[k + 2] <= lower)
294 {
295 copy_pixel(out + k, lower_color);
296 }
297 else
298 {
299 copy_pixel(out + k, in + k);
300 }
301 }
302
303 else
304 {
305 copy_pixel(out + k, in + k);
306 }
307 }
308 }
309
311 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
312
313 return 0;
314}
315
316#ifdef HAVE_OPENCL
317int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
318{
319 const dt_iop_roi_t *const roi_out = &piece->roi_out;
320 const dt_iop_overexposed_data_t *const d = (const dt_iop_overexposed_data_t *)piece->data;
322
323 cl_int err = -999;
324 const int devid = pipe->devid;
325 const int width = roi_out->width;
326 const int height = roi_out->height;
327
329
330 const int use_work_profile = (IS_NULL_PTR(current_profile)) ? 0 : 1;
331 cl_mem dev_profile_info = NULL;
332 cl_mem dev_profile_lut = NULL;
334 cl_float *profile_lut_cl = NULL;
335
336 err = dt_ioppr_build_iccprofile_params_cl(current_profile, devid, &profile_info_cl, &profile_lut_cl,
337 &dev_profile_info, &dev_profile_lut);
338 if(err != CL_SUCCESS) goto error;
339
340 const float lower = exp2f(fminf(d->lower, -4.f)); // in EV
341 const float upper = d->upper / 100.0f; // in %
342 const int colorscheme = d->colorscheme;
343
344 const float *upper_color = dt_iop_overexposed_colors[colorscheme][0];
345 const float *lower_color = dt_iop_overexposed_colors[colorscheme][1];
346 const int mode = d->mode;
347
348 size_t sizes[2] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid) };
349 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 0, sizeof(cl_mem), &dev_in);
350 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 1, sizeof(cl_mem), &dev_out);
351 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 2, sizeof(int), &width);
352 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 3, sizeof(int), &height);
353 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 4, sizeof(float), &lower);
354 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 5, sizeof(float), &upper);
355 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 6, 4 * sizeof(float), lower_color);
356 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 7, 4 * sizeof(float), upper_color);
357 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 8, sizeof(cl_mem), (void *)&dev_profile_info);
358 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 9, sizeof(cl_mem), (void *)&dev_profile_lut);
359 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 10, sizeof(int), (void *)&use_work_profile);
360 dt_opencl_set_kernel_arg(devid, gd->kernel_overexposed, 11, sizeof(int), (void *)&mode);
361 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_overexposed, sizes);
362 if(err != CL_SUCCESS) goto error;
363 return TRUE;
364
365error:
366 dt_print(DT_DEBUG_OPENCL, "[opencl_overexposed] couldn't enqueue kernel! %i\n", err);
367 return FALSE;
368}
369#endif
370
372{
373 tiling->factor = 3.0f; // in + out + temp
374 tiling->factor_cl = 3.0f;
375 tiling->maxbuf = 1.0f;
376 tiling->maxbuf_cl = 1.0f;
377 tiling->overhead = 0;
378 tiling->overlap = 0;
379 tiling->xalign = 1;
380 tiling->yalign = 1;
381}
382
383
385{
386 const int program = 2; // basic.cl from programs.conf
389 module->data = gd;
390 gd->kernel_overexposed = dt_opencl_create_kernel(program, "overexposed");
391}
392
393
400
403{
404 dt_develop_t *dev = self->dev;
406 d->mode = dev->overexposed.mode;
407 d->colorscheme = dev->overexposed.colorscheme;
408 d->lower = dev->overexposed.lower;
409 d->upper = dev->overexposed.upper;
410
412 piece->enabled = 0;
413}
414
415gboolean runtime_data_hash(struct dt_iop_module_t *self __attribute__((unused)),
418{
419 return TRUE;
420}
421
427
429{
430 dt_free_align(piece->data);
431 piece->data = NULL;
432}
433
435{
436 module->params = calloc(1, sizeof(dt_iop_overexposed_t));
437 module->default_params = calloc(1, sizeof(dt_iop_overexposed_t));
438 module->hide_enable_button = 1;
439 module->default_enabled = 1;
440 module->params_size = sizeof(dt_iop_overexposed_t);
441
442 // Bypasses the cache from here down while the indicator is shown. What keeps a changed setting
443 // from being served the previous frame is runtime_data_hash(), not this: a bypass leaves the
444 // hash unchanged, so the last module found the displayed cacheline under it and reused it.
446}
447
448// clang-format off
449// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
450// vim: shiftwidth=2 expandtab tabstop=2 cindent
451// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
452// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ IOP_CS_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.
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_iop_params_t
Definition dev_history.h:43
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
dt_dev_overexposed_colorscheme_t
Definition develop.h:76
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
dt_clipping_preview_mode_t
Definition develop.h:154
@ DT_CLIPPING_PREVIEW_ANYRGB
Definition develop.h:156
@ DT_CLIPPING_PREVIEW_GAMUT
Definition develop.h:155
@ DT_CLIPPING_PREVIEW_SATURATION
Definition develop.h:158
@ DT_CLIPPING_PREVIEW_LUMINANCE
Definition develop.h:157
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1685
@ IOP_FLAGS_HIDDEN
Definition imageop.h:189
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
@ IOP_FLAGS_NO_HISTORY_STACK
Definition imageop.h:193
@ IOP_GROUP_TECHNICAL
Definition imageop.h:162
void *const ovoid
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
@ DT_DEBUG_OPENCL
Definition logging.h:57
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 luminance
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
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
#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
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
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:2603
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void init(dt_iop_module_t *module)
int default_group()
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
const char * name()
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
void cleanup_global(dt_iop_module_so_t *module)
dt_iop_overexposed_colorscheme_t
Definition overexposed.c:74
@ DT_IOP_OVEREXPOSED_BLACKWHITE
Definition overexposed.c:75
@ DT_IOP_OVEREXPOSED_REDBLUE
Definition overexposed.c:76
@ DT_IOP_OVEREXPOSED_PURPLEGREEN
Definition overexposed.c:77
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
gboolean runtime_data_hash(struct dt_iop_module_t *self __attribute__((unused)), dt_dev_pixelpipe_t *pipe __attribute__((unused)), const dt_dev_pixelpipe_iop_t *piece __attribute__((unused)))
static const float DT_ALIGNED_ARRAY dt_iop_overexposed_colors[][2][4]
Definition overexposed.c:81
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void init_global(dt_iop_module_so_t *module)
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
static void copy_pixel(float *const __restrict__ out, const float *const __restrict__ in)
Definition simd.h:217
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 device-side view of a dt_iop_order_iccprofile_info_t: the scalar fields only.
dt_dev_pixelpipe_type_t type
int32_t gui_attached
Definition develop.h:167
dt_clipping_preview_mode_t mode
Definition develop.h:441
float lower
Definition develop.h:439
struct dt_develop_t::@15 overexposed
dt_dev_overexposed_colorscheme_t colorscheme
Definition develop.h:438
gboolean enabled
Definition develop.h:370
float upper
Definition develop.h:440
dt_iop_global_data_t * data
Definition imageop.h:238
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
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_...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
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...
dt_dev_overexposed_colorscheme_t colorscheme
dt_clipping_preview_mode_t mode
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__