Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
toneequal.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2018-2020, 2022-2026 Aurélien PIERRE.
4 Copyright (C) 2019 Andreas Schneider.
5 Copyright (C) 2019, 2021 luzpaz.
6 Copyright (C) 2019-2022 Pascal Obry.
7 Copyright (C) 2019-2020 rawfiner.
8 Copyright (C) 2019 Tobias Ellinghaus.
9 Copyright (C) 2020, 2022 Aldric Renaudin.
10 Copyright (C) 2020-2021 Chris Elston.
11 Copyright (C) 2020-2022 Diederik Ter Rahe.
12 Copyright (C) 2020 hatsunearu.
13 Copyright (C) 2020-2021 Hubert Kowalski.
14 Copyright (C) 2020 Matthieu Moy.
15 Copyright (C) 2020-2021 Ralf Brown.
16 Copyright (C) 2020 U-DESKTOP-TRPCBD3\Matthijs.
17 Copyright (C) 2021 Dan Torop.
18 Copyright (C) 2021 Heiko Bauke.
19 Copyright (C) 2021 lhietal.
20 Copyright (C) 2021 Marco Carrarini.
21 Copyright (C) 2021 Mark-64.
22 Copyright (C) 2021 Paolo DePetrillo.
23 Copyright (C) 2022 Hanno Schwalm.
24 Copyright (C) 2022 Martin Bařinka.
25 Copyright (C) 2022 Nicolas Auffray.
26 Copyright (C) 2022 Philipp Lutz.
27 Copyright (C) 2022 Sakari Kapanen.
28 Copyright (C) 2022 Victor Forsiuk.
29 Copyright (C) 2023-2024 Alynx Zhou.
30 Copyright (C) 2023 Luca Zulberti.
31 Copyright (C) 2025-2026 Guillaume Stutin.
32
33 Ansel is free software: you can redistribute it and/or modify
34 it under the terms of the GNU General Public License as published by
35 the Free Software Foundation, either version 3 of the License, or
36 (at your option) any later version.
37
38 Ansel is distributed in the hope that it will be useful,
39 but WITHOUT ANY WARRANTY; without even the implied warranty of
40 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 GNU General Public License for more details.
42
43 You should have received a copy of the GNU General Public License
44 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
45*/
46
47/*** DOCUMENTATION
48 *
49 * This module aims at relighting the scene by performing an exposure compensation
50 * selectively on specified exposures octaves, the same way HiFi audio equalizers allow to set
51 * a gain for each octave.
52 *
53 * It is intended to work in scene-linear camera RGB, to behave as if light was physically added
54 * or removed from the scene. As such, it should be put before input profile in the pipe, but preferably
55 * after exposure. It also need to be placed after the rotation, perspective and cropping modules
56 * for the interactive editing to work properly (so the image buffer overlap perfectly with the
57 * image preview).
58 *
59 * Because it works before camera RGB -> XYZ conversion, the exposure cannot be computed from
60 * any human-based perceptual colour model (Y channel), hence why several RGB norms are provided as estimators of
61 * the pixel energy to compute a luminance map. None of them is perfect, and I'm still
62 * looking forward to a real spectral energy estimator. The best physically-accurate norm should be the euclidean
63 * norm, but the best looking is often the power norm, which has no theoretical background.
64 * The geometric mean also display interesting properties as it interprets saturated colours
65 * as low-lights, allowing to lighten and desaturate them in a realistic way.
66 *
67 * The exposure correction is computed as a series of each octave's gain weighted by the
68 * gaussian of the radial distance between the current pixel exposure and each octave's center.
69 * This allows for a smooth and continuous infinite-order interpolation, preserving exposure gradients
70 * as best as possible. The radius of the kernel is user-defined and can be tweaked to get
71 * a smoother interpolation (possibly generating oscillations), or a more monotonous one
72 * (possibly less smooth). The actual factors of the gaussian series are computed by
73 * solving the linear system taking the user-input parameters as target exposures compensations.
74 *
75 * Notice that every pixel operation is performed in linear space. The exposures in log2 (EV)
76 * are only used for user-input parameters and for the gaussian weights of the radial distance
77 * between pixel exposure and octave's centers.
78 *
79 * The details preservation modes make use of a fast guided filter optimized to perform
80 * an edge-aware surface blur on the luminance mask, in the same spirit as the bilateral
81 * filter, but without its classic issues of gradient reversal around sharp edges. This
82 * surface blur will allow to perform piece-wise smooth exposure compensation, so local contrast
83 * will be preserved inside contiguous regions. Various mask refinements are provided to help
84 * the edge-taping of the filter (feathering parameter) while keeping smooth contiguous region
85 * (quantization parameter), but also to translate (exposure boost) and dilate (contrast boost)
86 * the exposure histogram through the control octaves, to center it on the control view
87 * and make maximum use of the available channels.
88 *
89 * Users should be aware that not all the available octaves will be useful on every pictures.
90 * Some automatic options will help them to optimize the luminance mask, performing histogram
91 * analyse, mapping the average exposure to -4EV, and mapping the first and last deciles of
92 * the histogram on its average ± 4EV. These automatic helpers usually fail on X-Trans sensors,
93 * maybe because of bad demosaicing, possibly resulting in outliers\negative RGB values.
94 * Since they fail the same way on filmic's auto-tuner, we might need to investigate X-Trans
95 * algos at some point.
96 *
97***/
98
99#ifdef HAVE_CONFIG_H
100#include "config.h"
101#endif
102#include <assert.h>
103#include <math.h>
104#include <stdlib.h>
105#include <stdio.h>
106#include <string.h>
107#include <time.h>
108
109#include "bauhaus/bauhaus.h"
110#include "common/darktable.h"
111#include "develop/masks.h"
113#include "common/eigf.h"
114#include "common/interpolation.h"
116#include "common/opencl.h"
117#include "common/collection.h"
118#include "control/conf.h"
119#include "control/control.h"
120#include "develop/blend.h"
121#include "develop/develop.h"
122#include "develop/imageop.h"
123#include "develop/imageop_math.h"
124#include "develop/imageop_gui.h"
126#include "dtgtk/drawingarea.h"
127#include "dtgtk/expander.h"
128
130#include "gui/draw.h"
131#include "gui/gtk.h"
132#include "gui/presets.h"
134#include "iop/iop_api.h"
135#include "iop/choleski.h"
136#include "libs/colorpicker.h"
137
138#ifdef _OPENMP
139#include <omp.h>
140#endif
141
142
144
145
146#define UI_SAMPLES 256 // 128 is a bit small for 4K resolution
147#define CONTRAST_FULCRUM exp2f(-4.0f)
148#define MIN_FLOAT exp2f(-16.0f)
149
155#define CHANNELS 9
156#define PIXEL_CHAN 8
157#define LUT_RESOLUTION 10000
158
159// radial distances used for pixel ops
160static const float centers_ops[PIXEL_CHAN] DT_ALIGNED_ARRAY = {-56.0f / 7.0f, // = -8.0f
161 -48.0f / 7.0f,
162 -40.0f / 7.0f,
163 -32.0f / 7.0f,
164 -24.0f / 7.0f,
165 -16.0f / 7.0f,
166 -8.0f / 7.0f,
167 0.0f / 7.0f}; // split 8 EV into 7 evenly-spaced channels
168
169static const float centers_params[CHANNELS] DT_ALIGNED_ARRAY = { -8.0f, -7.0f, -6.0f, -5.0f,
170 -4.0f, -3.0f, -2.0f, -1.0f, 0.0f};
171
172
174{
175 DT_TONEEQ_NONE = 0, // $DESCRIPTION: "no"
176 DT_TONEEQ_AVG_GUIDED, // $DESCRIPTION: "averaged guided filter"
177 DT_TONEEQ_GUIDED, // $DESCRIPTION: "guided filter"
178 DT_TONEEQ_AVG_EIGF, // $DESCRIPTION: "averaged eigf"
179 DT_TONEEQ_EIGF // $DESCRIPTION: "eigf"
181
182
184{
185 float noise; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "blacks"
186 float ultra_deep_blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "deep shadows"
187 float deep_blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
188 float blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "light shadows"
189 float shadows; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
190 float midtones; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "dark highlights"
191 float highlights; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
192 float whites; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "whites"
193 float speculars; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "speculars"
194 float blending; // $MIN: 0.01 $MAX: 100.0 $DEFAULT: 5.0 $DESCRIPTION: "smoothing diameter"
195 float smoothing; // $DEFAULT: 1.414213562 sqrtf(2.0f)
196 float feathering; // $MIN: 0.01 $MAX: 10000.0 $DEFAULT: 1.0 $DESCRIPTION: "edges refinement (feathering)"
197 float quantization; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mask quantization"
198 float contrast_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast compensation"
199 float exposure_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask exposure compensation"
200 dt_iop_toneequalizer_filter_t details; // $DEFAULT: DT_TONEEQ_EIGF
201 dt_iop_luminance_mask_method_t method; // $DEFAULT: DT_TONEEQ_NORM_2 $DESCRIPTION: "luminance estimator"
202 int iterations; // $MIN: 1 $MAX: 20 $DEFAULT: 1 $DESCRIPTION: "filter diffusion"
204
205
217
218
220{
221 // TODO: put OpenCL kernels here at some point
223
224
226{
227 // Mem arrays 64-bytes aligned - contiguous memory
229 float gui_lut[UI_SAMPLES] DT_ALIGNED_ARRAY; // LUT for the UI graph
230 float interpolation_matrix[CHANNELS * PIXEL_CHAN] DT_ALIGNED_ARRAY;
231 int histogram[UI_SAMPLES] DT_ALIGNED_ARRAY; // histogram for the UI graph
232 float temp_user_params[CHANNELS] DT_ALIGNED_ARRAY;
233 float cursor_exposure; // store the exposure value at current cursor position
234 float step; // scrolling step
235
236 // 14 int to pack - contiguous memory
244
245 // Preview luminance cache state shared with the GUI.
246 // The GUI never owns raw luminance buffers directly anymore: it only keeps the
247 // cache hash, dimensions and one retained cache entry reference so every reader
248 // goes through the pixelpipe cache locking API before sampling.
252
253 // Misc stuff, contiguity, length and alignment unknown
254 float scale;
255 float sigma;
259
260 // Preview luminance cache entry retained across pipe runs for GUI sampling.
261 // Lifetime is explicit: process() transfers one ref to the GUI when the current
262 // preview-sized output matches darkroom preview, and invalidation/cleanup drop it.
264
265 // GTK garbage, nobody cares, no SIMD here
267 GtkDrawingArea *area;
271 GtkNotebook *notebook;
273
274 // Cache Pango and Cairo stuff for the equalizer drawing
277 float graph_left_space; // used to center the circle on the mouse.
285 float x_label;
286 int inset;
288
289 GtkAllocation allocation;
290 cairo_surface_t *cst;
291 cairo_t *cr;
292 PangoLayout *layout;
293 PangoRectangle ink;
294 PangoFontDescription *desc;
295 GtkStyleContext *context;
296
297 // Event for equalizer drawing
300 float area_x; // x coordinate of cursor over graph/drawing area
301 float area_y; // y coordinate
303
304 // Flags for UI events
305 int valid_nodes_x; // TRUE if x coordinates of graph nodes have been inited
306 int valid_nodes_y; // TRUE if y coordinates of graph nodes have been inited
307 int area_cursor_valid; // TRUE if mouse cursor is over the graph area
308 int area_dragging; // TRUE if left-button has been pushed but not released and cursor motion is recorded
309 int cursor_valid; // TRUE if mouse cursor is over the preview image
310 int has_focus; // TRUE if the widget has the focus from GTK
311
312 // Flags for buffer caches invalidation
313 int interpolation_valid; // TRUE if the interpolation_matrix is ready
314 int luminance_valid; // TRUE if the luminance cache is ready
315 int histogram_valid; // TRUE if the histogram cache and stats are ready
316 int lut_valid; // TRUE if the gui_lut is ready
317 int graph_valid; // TRUE if the UI graph view is ready
318 int user_param_valid; // TRUE if users params set in interactive view are in bounds
319 int factors_valid; // TRUE if radial-basis coeffs are ready
320
322
323
324const char *name()
325{
326 return _("tone e_qualizer");
327}
328
329const char *aliases()
330{
331 return _("tone curve|tone mapping|relight|background light|shadows highlights");
332}
333
334
335const char **description(struct dt_iop_module_t *self)
336{
337 return dt_iop_set_description(self, _("relight the scene as if the lighting was done directly on the scene"),
338 _("corrective and creative"),
339 _("linear, RGB, scene-referred"),
340 _("quasi-linear, RGB"),
341 _("quasi-linear, RGB, scene-referred"));
342}
343
345{
346 return IOP_GROUP_TONES;
347}
348
353
355{
356 return IOP_CS_RGB;
357}
358
361{
362 default_input_format(self, pipe, piece, dsc);
363 dsc->channels = 4;
364 dsc->datatype = TYPE_FLOAT;
365}
366
367int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
368 const int new_version)
369{
370 if(old_version == 1 && new_version == 2)
371 {
372 typedef struct dt_iop_toneequalizer_params_v1_t
373 {
374 float noise, ultra_deep_blacks, deep_blacks, blacks, shadows, midtones, highlights, whites, speculars;
375 float blending, feathering, contrast_boost, exposure_boost;
377 int iterations;
379 } dt_iop_toneequalizer_params_v1_t;
380
381 dt_iop_toneequalizer_params_v1_t *o = (dt_iop_toneequalizer_params_v1_t *)old_params;
384
385 *n = *d; // start with a fresh copy of default parameters
386
387 // Olds params
388 n->noise = o->noise;
389 n->ultra_deep_blacks = o->ultra_deep_blacks;
390 n->deep_blacks = o->deep_blacks;
391 n->blacks = o->blacks;
392 n->shadows = o->shadows;
393 n->midtones = o->midtones;
394 n->highlights = o->highlights;
395 n->whites = o->whites;
396 n->speculars = o->speculars;
397
398 n->blending = o->blending;
399 n->feathering = o->feathering;
400 n->contrast_boost = o->contrast_boost;
401 n->exposure_boost = o->exposure_boost;
402
403 n->details = o->details;
404 n->iterations = o->iterations;
405 n->method = o->method;
406
407 // New params
408 n->quantization = 0.01f;
409 n->smoothing = sqrtf(2.0f);
410 return 0;
411 }
412 return 1;
413}
414
416{
417 // this function is used to set the exposure params for the 4 "compress shadows
418 // highlights" presets, which use basically the same curve, centered around
419 // -4EV with an exposure compensation that puts middle-grey at -4EV.
420 p->noise = step;
421 p->ultra_deep_blacks = 5.f / 3.f * step;
422 p->deep_blacks = 5.f / 3.f * step;
423 p->blacks = step;
424 p->shadows = 0.0f;
425 p->midtones = -step;
426 p->highlights = -5.f / 3.f * step;
427 p->whites = -5.f / 3.f * step;
428 p->speculars = -step;
429}
430
431
433{
434 // create a tone curve meant to be used without filter (as a flat, non-local, 1D tone curve) that reverts
435 // the local settings above.
436 p->noise = -15.f / 9.f * step;
437 p->ultra_deep_blacks = -14.f / 9.f * step;
438 p->deep_blacks = -12.f / 9.f * step;
439 p->blacks = -8.f / 9.f * step;
440 p->shadows = 0.f;
441 p->midtones = 8.f / 9.f * step;
442 p->highlights = 12.f / 9.f * step;
443 p->whites = 14.f / 9.f * step;
444 p->speculars = 15.f / 9.f * step;
445}
446
448{
450 memset(&p, 0, sizeof(p));
451
452 p.method = DT_TONEEQ_NORM_POWER;
453 p.contrast_boost = 0.0f;
454 p.details = DT_TONEEQ_NONE;
455 p.exposure_boost = -0.5f;
456 p.feathering = 1.0f;
457 p.iterations = 1;
458 p.smoothing = sqrtf(2.0f);
459 p.quantization = 0.0f;
460
461 // Init exposure settings
462 p.noise = p.ultra_deep_blacks = p.deep_blacks = p.blacks = p.shadows = p.midtones = p.highlights = p.whites = p. speculars = 0.0f;
463
464 // No blending
465 dt_gui_presets_add_generic(_("simple tone curve"), self->op,
466 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
467
468 // Simple utils blendings
469 p.details = DT_TONEEQ_EIGF;
470 p.method = DT_TONEEQ_NORM_2;
471
472 p.blending = 5.0f;
473 p.feathering = 1.0f;
474 p.iterations = 1;
475 p.quantization = 0.0f;
476 p.exposure_boost = 0.0f;
477 p.contrast_boost = 0.0f;
478 dt_gui_presets_add_generic(_("mask blending: all purposes"), self->op,
479 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
480
481 p.blending = 1.0f;
482 p.feathering = 10.0f;
483 p.iterations = 3;
484 dt_gui_presets_add_generic(_("mask blending: people with backlight"), self->op,
485 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
486
487 // Shadows/highlights presets
488 // move middle-grey to the center of the range
489 p.exposure_boost = -1.57f;
490 p.contrast_boost = 0.0f;
491 p.blending = 2.0f;
492 p.feathering = 50.0f;
493 p.iterations = 5;
494 p.quantization = 0.0f;
495
496 // slight modification to give higher compression
497 p.details = DT_TONEEQ_EIGF;
498 p.feathering = 20.0f;
500 dt_gui_presets_add_generic(_("compress shadows/highlights (eigf): strong"), self->op,
501 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
502 p.details = DT_TONEEQ_GUIDED;
503 p.feathering = 500.0f;
504 dt_gui_presets_add_generic(_("compress shadows/highlights (gf): strong"), self->op,
505 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
506
507 p.details = DT_TONEEQ_EIGF;
508 p.blending = 3.0f;
509 p.feathering = 7.0f;
510 p.iterations = 3;
512 dt_gui_presets_add_generic(_("compress shadows/highlights (eigf): medium"), self->op,
513 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
514 p.details = DT_TONEEQ_GUIDED;
515 p.feathering = 500.0f;
516 dt_gui_presets_add_generic(_("compress shadows/highlights (gf): medium"), self->op,
517 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
518
519 p.details = DT_TONEEQ_EIGF;
520 p.blending = 5.0f;
521 p.feathering = 1.0f;
522 p.iterations = 1;
524 dt_gui_presets_add_generic(_("compress shadows/highlights (eigf): soft"), self->op,
525 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
526 p.details = DT_TONEEQ_GUIDED;
527 p.feathering = 500.0f;
528 dt_gui_presets_add_generic(_("compress shadows/highlights (gf): soft"), self->op,
529 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
530
531 // build the 1D contrast curves that revert the local compression of contrast above
532 p.details = DT_TONEEQ_NONE;
534 dt_gui_presets_add_generic(_("contrast tone curve: soft"), self->op,
535 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
536
538 dt_gui_presets_add_generic(_("contrast tone curve: medium"), self->op,
539 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
540
542 dt_gui_presets_add_generic(_("contrast tone curve: strong"), self->op,
543 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
544
545 // relight
546 p.details = DT_TONEEQ_EIGF;
547 p.blending = 5.0f;
548 p.feathering = 1.0f;
549 p.iterations = 1;
550 p.quantization = 0.0f;
551 p.exposure_boost = -0.5f;
552 p.contrast_boost = 0.0f;
553
554 p.noise = 0.0f;
555 p.ultra_deep_blacks = 0.15f;
556 p.deep_blacks = 0.6f;
557 p.blacks = 1.15f;
558 p.shadows = 1.33f;
559 p.midtones = 1.15f;
560 p.highlights = 0.6f;
561 p.whites = 0.15f;
562 p.speculars = 0.0f;
563
564 dt_gui_presets_add_generic(_("relight: fill-in"), self->op,
565 self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
566}
567
568
573static gboolean in_mask_editing(dt_iop_module_t *self)
574{
575 const dt_develop_t *dev = self->dev;
576 return dev->form_gui && dt_masks_get_visible_form(dev);
577}
578
580{
581 // Invalidate the preview luminance cache and histogram when
582 // the luminance mask extraction parameters have changed.
583 // Keep the ref hand-off visible here: we detach the GUI from the shared cache
584 // entry under the GUI lock, then release the retained cache ref afterwards.
585 // This is one of the cases that used to go wrong when tone equalizer stored
586 // ad-hoc GUI buffers outside the pixelpipe cache.
588 if(IS_NULL_PTR(g)) return;
589
590 dt_pixel_cache_entry_t *preview_entry = NULL;
592 g->max_histogram = 1;
593 g->luminance_valid = FALSE;
594 g->histogram_valid = 0;
595 g->thumb_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
596 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
597 g->thumb_preview_buf_width = 0;
598 g->thumb_preview_buf_height = 0;
599 preview_entry = g->thumb_preview_entry;
600 g->thumb_preview_entry = NULL;
602
603 if(!IS_NULL_PTR(preview_entry))
605}
606
607
608static inline __attribute__((always_inline)) int sanity_check(dt_iop_module_t *self)
609{
610 // If tone equalizer is put after flip/orientation module,
611 // the pixel buffer will be in landscape orientation even for pictures displayed in portrait orientation
612 // so the interactive editing will fail. Disable the module and issue a warning then.
613
614 const double position_self = self->iop_order;
615 const double position_min = dt_ioppr_get_iop_order(self->dev->iop_order_list, "flip", 0);
616
617 if(position_self < position_min && self->enabled)
618 {
619 dt_control_log(_("tone equalizer needs to be after distortion modules in the pipeline - disabled"));
620 fprintf(stdout, "tone equalizer needs to be after distortion modules in the pipeline - disabled\n");
621 self->enabled = 0;
623
624 if(self->dev->gui_attached)
625 {
626 // Repaint the on/off icon
627 if(self->off)
628 {
630 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), self->enabled);
632 }
633 }
634 return 0;
635 }
636
637 return 1;
638}
639
640// gaussian-ish kernel - sum is == 1.0f so we don't care much about actual coeffs
642 { { 0.076555024f, 0.124401914f, 0.076555024f },
643 { 0.124401914f, 0.196172249f, 0.124401914f },
644 { 0.076555024f, 0.124401914f, 0.076555024f } };
645
646static float get_luminance_from_buffer(const float *const buffer,
647 const size_t width, const size_t height,
648 const size_t x, const size_t y)
649{
650 // Get the weighted average luminance of the 3x3 pixels region centered in (x, y)
651 // x and y are ratios in [0, 1] of the width and height
652
653 if(y >= height || x >= width) return NAN;
654
655 const size_t y_abs[4] DT_ALIGNED_PIXEL =
656 { MAX(y, 1) - 1, // previous line
657 y, // center line
658 MIN(y + 1, height - 1), // next line
659 y }; // padding for vectorization
660
661 float luminance = 0.0f;
662 if (x > 0 && x < width - 2)
663 {
664 // no clamping needed on x, which allows us to vectorize
665 // apply the convolution
666 for(int i = 0; i < 3; ++i)
667 {
668 const size_t y_i = y_abs[i];
670 luminance += buffer[width * y_i + x-1 + j] * gauss_kernel[i][j];
671 }
672 return luminance;
673 }
674
675 const size_t x_abs[4] DT_ALIGNED_PIXEL =
676 { MAX(x, 1) - 1, // previous column
677 x, // center column
678 MIN(x + 1, width - 1), // next column
679 x }; // padding for vectorization
680
681 // convolution
682 for(int i = 0; i < 3; ++i)
683 {
684 const size_t y_i = y_abs[i];
686 luminance += buffer[width * y_i + x_abs[j]] * gauss_kernel[i][j];
687 }
688 return luminance;
689}
690
691
692/***
693 * Exposure compensation computation
694 *
695 * Construct the final correction factor by summing the octaves channels gains weighted by
696 * the gaussian of the radial distance (pixel exposure - octave center)
697 *
698 ***/
699
701static inline __attribute__((always_inline)) float gaussian_denom(const float sigma)
702{
703 // Gaussian function denominator such that y = exp(- radius^2 / denominator)
704 // this is the constant factor of the exponential, so we don't need to recompute it
705 // for every single pixel
706 return 2.0f * sigma * sigma;
707}
708
709
711static inline __attribute__((always_inline)) float gaussian_func(const float radius, const float denominator)
712{
713 // Gaussian function without normalization
714 // this is the variable part of the exponential
715 // the denominator should be evaluated with `gaussian_denom`
716 // ahead of the array loop for optimal performance
717 return expf(- radius * radius / denominator);
718}
719
720#define DT_TONEEQ_USE_LUT TRUE
721#if DT_TONEEQ_USE_LUT
722
723// this is the version currently used, as using a lut gives a
724// big performance speedup on some cpus
726static inline void apply_toneequalizer(const float *const restrict in,
727 const float *const restrict luminance,
728 float *const restrict out,
729 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
730 const size_t ch,
731 const dt_iop_toneequalizer_data_t *const d)
732{
733 const size_t num_elem = (size_t)roi_in->width * roi_in->height;
734 const int min_ev = -8;
735 const int max_ev = 0;
736 const float* restrict lut = d->correction_lut;
738 for(size_t k = 0; k < num_elem; ++k)
739 {
740 // The radial-basis interpolation is valid in [-8; 0] EV and can quickely diverge outside
741 const float exposure = fast_clamp(log2f(luminance[k]), min_ev, max_ev);
742 const float correction = lut[(unsigned)roundf((exposure - min_ev) * LUT_RESOLUTION)];
743 const size_t idx = k * ch;
744 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + idx);
745 const dt_aligned_pixel_simd_t correction_v = { correction, correction, correction, 1.0f };
746 dt_store_simd_nontemporal(out + idx, pix_in * correction_v);
747 }
748 dt_omploop_sfence(); // ensure that nontemporal writes complete before the caller reads output
749}
750
751#else
752
753// we keep this version for further reference (e.g. for implementing
754// a gpu version)
756static inline void apply_toneequalizer(const float *const restrict in,
757 const float *const restrict luminance,
758 float *const restrict out,
759 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
760 const size_t ch,
761 const dt_iop_toneequalizer_data_t *const d)
762{
763 const size_t num_elem = roi_in->width * roi_in->height;
764 const float *const restrict factors = d->factors;
765 const float sigma = d->smoothing;
766 const float gauss_denom = gaussian_denom(sigma);
768 for(size_t k = 0; k < num_elem; ++k)
769 {
770 // build the correction for the current pixel
771 // as the sum of the contribution of each luminance channelcorrection
772 float result = 0.0f;
773
774 // The radial-basis interpolation is valid in [-8; 0] EV and can quickely diverge outside
775 const float exposure = fast_clamp(log2f(luminance[k]), -8.0f, 0.0f);
776 __OMP_SIMD__(aligned(luminance, centers_ops, factors:64) safelen(PIXEL_CHAN) reduction(+:result))
777 for(int i = 0; i < PIXEL_CHAN; ++i)
778 result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
779
780 // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
781 const float correction = fast_clamp(result, 0.25f, 4.0f);
782 const size_t idx = k * ch;
783 const dt_aligned_pixel_simd_t pix_in = dt_load_simd_aligned(in + idx);
784 const dt_aligned_pixel_simd_t correction_v = { correction, correction, correction, 1.0f };
785 dt_store_simd_nontemporal(out + idx, pix_in * correction_v);
786 }
787 dt_omploop_sfence(); // ensure that nontemporal writes complete before the caller reads output
788}
789#endif // USE_LUT
790
791static inline float pixel_correction(const float exposure,
792 const float *const restrict factors,
793 const float sigma)
794{
795 // build the correction for the current pixel
796 // as the sum of the contribution of each luminance channel
797 float result = 0.0f;
798 const float gauss_denom = gaussian_denom(sigma);
799 const float expo = fast_clamp(exposure, -8.0f, 0.0f);
800 __OMP_SIMD__(aligned(centers_ops, factors:64) safelen(PIXEL_CHAN) reduction(+:result))
801 for(int i = 0; i < PIXEL_CHAN; ++i)
802 result += gaussian_func(expo - centers_ops[i], gauss_denom) * factors[i];
803
804 return fast_clamp(result, 0.25f, 4.0f);
805}
806
807
808static inline int compute_luminance_mask(const float *const restrict in, float *const restrict luminance,
809 const size_t width, const size_t height, const size_t ch,
810 const dt_iop_toneequalizer_data_t *const d)
811{
812 switch(d->details)
813 {
814 case(DT_TONEEQ_NONE):
815 {
816 // No contrast boost here
817 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost, 0.0f, 1.0f);
818 break;
819 }
820
822 {
823 // Still no contrast boost
824 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost, 0.0f, 1.0f);
825 if(fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
826 DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization, exp2f(-14.0f), 4.0f) != 0)
827 return 1;
828 break;
829 }
830
831 case(DT_TONEEQ_GUIDED):
832 {
833 // Contrast boosting is done around the average luminance of the mask.
834 // This is to make exposure corrections easier to control for users, by spreading
835 // the dynamic range along all exposure channels, because guided filters
836 // tend to flatten the luminance mask a lot around an average ± 2 EV
837 // which makes only 2-3 channels usable.
838 // we assume the distribution is centered around -4EV, e.g. the center of the nodes
839 // the exposure boost should be used to make this assumption true
840 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost,
841 CONTRAST_FULCRUM, d->contrast_boost);
842 if(fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
843 DT_GF_BLENDING_LINEAR, d->scale, d->quantization, exp2f(-14.0f), 4.0f) != 0)
844 return 1;
845 break;
846 }
847
848 case(DT_TONEEQ_AVG_EIGF):
849 {
850 // Still no contrast boost
851 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost, 0.0f, 1.0f);
852 if(fast_eigf_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
853 DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization, exp2f(-14.0f), 4.0f) != 0)
854 return 1;
855 break;
856 }
857
858 case(DT_TONEEQ_EIGF):
859 {
860 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost,
861 CONTRAST_FULCRUM, d->contrast_boost);
862 if(fast_eigf_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
863 DT_GF_BLENDING_LINEAR, d->scale, d->quantization, exp2f(-14.0f), 4.0f) != 0)
864 return 1;
865 break;
866 }
867
868 default:
869 {
870 luminance_mask(in, luminance, width, height, ch, d->method, d->exposure_boost, 0.0f, 1.0f);
871 break;
872 }
873 }
874 return 0;
875}
876
877
878/***
879 * Actual transfer functions
880 **/
881
883static inline void display_luminance_mask(const float *const restrict in,
884 const float *const restrict luminance,
885 float *const restrict out,
886 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
887 const dt_dev_pixelpipe_t *pipe,
888 const size_t ch)
889{
890 const size_t offset_x = (roi_in->x < roi_out->x) ? -roi_in->x + roi_out->x : 0;
891 const size_t offset_y = (roi_in->y < roi_out->y) ? -roi_in->y + roi_out->y : 0;
892
893 // The output dimensions need to be smaller or equal to the input ones
894 // there is no logical reason they shouldn't, except some weird bug in the pipe
895 // in this case, ensure we don't segfault
896 const size_t in_width = roi_in->width;
897 const size_t out_width = (roi_in->width > roi_out->width) ? roi_out->width : roi_in->width;
898 const size_t out_height = (roi_in->height > roi_out->height) ? roi_out->height : roi_in->height;
899 __OMP_PARALLEL_FOR__(collapse(2))
900 for(size_t i = 0 ; i < out_height; ++i)
901 for(size_t j = 0; j < out_width; ++j)
902 {
903 // normalize the mask intensity between -8 EV and 0 EV for clarity,
904 // and add a "gamma" 2.0 for better legibility in shadows
905 const float intensity = sqrtf(fminf(fmaxf(luminance[(i + offset_y) * in_width + (j + offset_x)] - 0.00390625f, 0.f) / 0.99609375f, 1.f));
906 const size_t index = (i * out_width + j) * ch;
907 dt_aligned_pixel_simd_t intensity_v = dt_simd_set1(intensity);
908
909 // Keep mask-display alpha consistent with the input while showing a grayscale mask.
911 {
912 const size_t in_index = ((i + offset_y) * in_width + (j + offset_x)) * ch;
913 intensity_v[3] = in[in_index + 3];
914 }
915
916 dt_store_simd_nontemporal(out + index, intensity_v);
917 }
918 dt_omploop_sfence(); // ensure that nontemporal writes complete before the caller reads output
919}
920
921
922static inline __attribute__((always_inline)) int toneeq_process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
923 const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
924 void *const restrict ovoid, const dt_iop_roi_t *const roi_in,
925 const dt_iop_roi_t *const roi_out)
926{
927 const dt_iop_toneequalizer_data_t *const d = (const dt_iop_toneequalizer_data_t *const)piece->data;
929
930 const float *const restrict in = dt_check_sse_aligned((float *const)ivoid);
931 float *const restrict out = dt_check_sse_aligned((float *const)ovoid);
932 float *restrict luminance = NULL;
933 dt_pixel_cache_entry_t *luminance_entry = NULL;
934 gboolean created_luminance_entry = FALSE;
936
937 if(IS_NULL_PTR(in) || IS_NULL_PTR(out))
938 {
939 // Pointers are not 64-bits aligned, and SSE code will segfault
940 dt_control_log(_("tone equalizer in/out buffer are ill-aligned, please report the bug to the developers"));
941 fprintf(stdout, "tone equalizer in/out buffer are ill-aligned, please report the bug to the developers\n");
942 return 1;
943 }
944
945 const size_t width = roi_in->width;
946 const size_t height = roi_in->height;
947 const size_t num_elem = width * height;
948 const size_t ch = 4;
949
950 // Get the hash of the upstream pipe to track changes
951 const int position = self->iop_order;
952 const gboolean preview_output = dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out);
953
954 // Sanity checks
955 if(width < 1 || height < 1) return 1;
956 if(roi_in->width < roi_out->width || roi_in->height < roi_out->height) return 0; // input should be at least as large as output
957 if(!sanity_check(self))
958 {
959 // if module just got disabled by sanity checks, due to pipe position, just pass input through
960 dt_simd_memcpy(in, out, num_elem * ch);
961 return 0;
962 }
963
964 if(self->dev->gui_attached)
965 {
966 // If the module instance has changed order in the pipe, invalidate the caches
967 if(!IS_NULL_PTR(g) && g->pipe_order != position)
968 {
969 dt_pixel_cache_entry_t *preview_entry = NULL;
971 g->pipe_order = position;
972 g->thumb_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
973 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
974 g->thumb_preview_buf_width = 0;
975 g->thumb_preview_buf_height = 0;
976 g->luminance_valid = FALSE;
977 g->histogram_valid = FALSE;
978 preview_entry = g->thumb_preview_entry;
979 g->thumb_preview_entry = NULL;
981
982 if(preview_entry)
984 }
985 }
986
987 if(self->dev->gui_attached)
988 {
989 // Cache the luminance mask in the shared pixelpipe cache so the key follows
990 // the exact module state and GUI readers can reuse the same lifetime/locking model.
991 // `piece->global_hash` already includes the upstream image state and the module
992 // params committed to that run, so the luminance cacheline stays coherent with
993 // both preview and main pipelines without adding toneequal-specific validity rules.
994 //
995 // The fixes we rely on here were exercised with:
996 // - history edits and parameter edits invalidating/rebuilding the mask,
997 // - opening the module on an already computed preview and reattaching to the
998 // existing cacheline without waiting for a fresh process(),
999 // - GUI histogram/cursor sampling while the worker threads are running,
1000 // - mask display staying restricted to the full pipe while the luminance data
1001 // itself comes from whichever pipe produced the preview-sized output.
1002 void *cache_data = NULL;
1003 static const char cache_tag[] = "toneequal:luminance";
1004 luminance_hash = dt_hash(piece->global_hash, cache_tag, sizeof(cache_tag));
1005
1006 created_luminance_entry = dt_dev_pixelpipe_cache_get(darktable.pixelpipe_cache, luminance_hash,
1007 num_elem * sizeof(float), "toneequal luminance",
1008 pipe->type, TRUE, &cache_data,
1009 &luminance_entry);
1010 luminance = (float *)cache_data;
1011 if(IS_NULL_PTR(luminance) || IS_NULL_PTR(luminance_entry))
1012 {
1013 if(luminance_entry)
1014 {
1015 if(created_luminance_entry)
1018 }
1019 return 1;
1020 }
1021
1022 if(created_luminance_entry)
1023 {
1025 {
1029 return 1;
1030 }
1031
1033 }
1034 }
1035 else
1036 {
1037 // Export/thumbnail pipes don't need persistent GUI sampling, so a local temp buffer is enough.
1039 if(IS_NULL_PTR(luminance)) return 1;
1040
1042 {
1044 return 1;
1045 }
1046 }
1047
1048 // Display output
1049 if(self->dev->gui_attached && pipe->type == DT_DEV_PIXELPIPE_FULL)
1050 {
1051 if(g->mask_display)
1052 {
1053 display_luminance_mask(in, luminance, out, roi_in, roi_out, pipe, ch);
1054 ((dt_dev_pixelpipe_t *)pipe)->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
1055 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
1056 }
1057 else
1058 apply_toneequalizer(in, luminance, out, roi_in, roi_out, ch, d);
1059 }
1060 else
1061 {
1062 apply_toneequalizer(in, luminance, out, roi_in, roi_out, ch, d);
1063 }
1064
1065 if(preview_output && self->dev->gui_attached && !IS_NULL_PTR(g) && luminance_entry)
1066 {
1067 dt_pixel_cache_entry_t *old_entry = NULL;
1068 gboolean keep_process_ref = FALSE;
1069
1070 // Transfer one cache ref from this process run to the GUI if this run produced
1071 // the preview-sized image darkroom is sampling from. Keeping this explicit avoids
1072 // guessing whether FULL/PREVIEW pipe type owns the GUI state when both pipes can
1073 // share the same output size.
1075 if(g->thumb_preview_entry != luminance_entry || g->thumb_preview_hash != luminance_hash)
1076 {
1077 old_entry = g->thumb_preview_entry;
1078 g->thumb_preview_entry = luminance_entry;
1079 g->thumb_preview_hash = luminance_hash;
1080 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1081 g->thumb_preview_buf_width = width;
1082 g->thumb_preview_buf_height = height;
1083 g->luminance_valid = TRUE;
1084 g->histogram_valid = FALSE;
1085 keep_process_ref = TRUE;
1086 }
1088
1089 if(old_entry)
1091
1092 if(!keep_process_ref)
1094 }
1095 else if(luminance_entry)
1096 {
1098 }
1099 else
1100 {
1102 }
1103
1104 return 0;
1105}
1106
1107int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
1108 const void *const restrict ivoid, void *const restrict ovoid)
1109{
1110 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1111 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1112 return toneeq_process(self, pipe, piece, ivoid, ovoid, roi_in, roi_out);
1113}
1114
1115
1116void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
1117 struct dt_dev_pixelpipe_iop_t *piece,
1118 const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
1119{
1120 // Pad the zoomed-in view to avoid weird stuff with local averages at the borders of
1121 // the preview
1122
1124
1125 // Get the scaled window radius for the box average
1126 const int max_size = (piece->iwidth > piece->iheight) ? piece->iwidth : piece->iheight;
1127 const float diameter = d->blending * max_size * roi_in->scale;
1128 const int radius = (int)((diameter - 1.0f) / ( 2.0f));
1129 d->radius = radius;
1130
1131 /*
1132 // Enlarge the preview roi with padding if needed
1133 if(self->dev->gui_attached && sanity_check(self))
1134 {
1135 int roiy = fmaxf(roi_in->y - radius, 0.0f);
1136 int roix = fmaxf(roi_in->x - radius, 0.0f);
1137 int roir = fminf(roix + roi_in->width + 2 * radius, piece->buf_in.width * roi_in->scale);
1138 int roib = fminf(roiy + roi_in->height + 2 * radius, piece->buf_in.height * roi_in->scale);
1139
1140 // Set the values and check
1141 roi_in->x = roix;
1142 roi_in->y = roiy;
1143 roi_in->width = roir - roi_in->x;
1144 roi_in->height = roib - roi_in->y;
1145 }
1146 */
1147}
1148
1149
1150/***
1151 * Setters and Getters for parameters
1152 *
1153 * Remember the user params split the [-8; 0] EV range in 9 channels and define a set of (x, y)
1154 * coordinates, where x are the exposure channels (evenly-spaced by 1 EV in [-8; 0] EV)
1155 * and y are the desired exposure compensation for each channel.
1156 *
1157 * This (x, y) set is interpolated by radial-basis function using a series of 8 gaussians.
1158 * Losing 1 degree of freedom makes it an approximation rather than an interpolation but
1159 * helps reducing a bit the oscillations and fills a full AVX vector.
1160 *
1161 * The coefficients/factors used in the interpolation/approximation are linear, but keep in
1162 * mind that users params are expressed as log2 gains, so we always need to do the log2/exp2
1163 * flip/flop between both.
1164 *
1165 * User params of exposure compensation are expected between [-2 ; +2] EV for practical UI reasons
1166 * and probably numerical stability reasons, but there is no theoretical obstacle to enlarge
1167 * this range. The main reason for not allowing it is tone equalizer is mostly intended
1168 * to do local changes, and these don't look so well if you are too harsh on the changes.
1169 * For heavier tonemapping, it should be used in combination with a tone curve or filmic.
1170 *
1171 ***/
1172
1173static void compute_correction_lut(float* restrict lut, const float sigma, const float *const restrict factors)
1174{
1175 const float gauss_denom = gaussian_denom(sigma);
1176 const int min_ev = -8;
1177 assert(PIXEL_CHAN == -min_ev);
1178 for(int j = 0; j <= LUT_RESOLUTION * PIXEL_CHAN; j++)
1179 {
1180 // build the correction for each pixel
1181 // as the sum of the contribution of each luminance channelcorrection
1182 float exposure = (float)j / (float)LUT_RESOLUTION + min_ev;
1183 float result = 0.0f;
1184 for(int i = 0; i < PIXEL_CHAN; ++i)
1185 result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
1186 // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
1187 lut[j] = fast_clamp(result, 0.25f, 4.0f);
1188 }
1189}
1190
1192{
1193 assert(CHANNELS == 9);
1194
1195 // Get user-set channels gains in EV (log2)
1196 factors[0] = p->noise; // -8 EV
1197 factors[1] = p->ultra_deep_blacks; // -7 EV
1198 factors[2] = p->deep_blacks; // -6 EV
1199 factors[3] = p->blacks; // -5 EV
1200 factors[4] = p->shadows; // -4 EV
1201 factors[5] = p->midtones; // -3 EV
1202 factors[6] = p->highlights; // -2 EV
1203 factors[7] = p->whites; // -1 EV
1204 factors[8] = p->speculars; // +0 EV
1205}
1206
1207
1209{
1210 assert(CHANNELS == 9);
1211
1212 // Get user-set channels gains in EV (log2)
1213 get_channels_gains(factors, p);
1214
1215 // Convert from EV offsets to linear factors
1216 __OMP_SIMD__(aligned(factors:64))
1217 for(int c = 0; c < CHANNELS; ++c)
1218 factors[c] = exp2f(factors[c]);
1219}
1220
1221
1222static int compute_channels_factors(const float factors[PIXEL_CHAN], float out[CHANNELS], const float sigma)
1223{
1224 // Input factors are the weights for the radial-basis curve approximation of user params
1225 // Output factors are the gains of the user parameters channels
1226 // aka the y coordinates of the approximation for x = { CHANNELS }
1227 assert(PIXEL_CHAN == 8);
1228
1229 int valid = 1;
1230 __OMP_PARALLEL_FOR_SIMD__(aligned(factors, out, centers_params:64) shared(valid) firstprivate(centers_params))
1231 for(int i = 0; i < CHANNELS; ++i)
1232 {
1233 // Compute the new channels factors
1234 out[i] = pixel_correction(centers_params[i], factors, sigma);
1235
1236 // check they are in [-2, 2] EV and not NAN
1237 if(isnan(out[i]) || out[i] < 0.25f || out[i] > 4.0f) valid = 0;
1238 }
1239
1240 return valid;
1241}
1242
1243
1244static int compute_channels_gains(const float in[CHANNELS], float out[CHANNELS])
1245{
1246 // Helper function to compute the new channels gains (log) from the factors (linear)
1247 assert(PIXEL_CHAN == 8);
1248
1249 const int valid = 1;
1250
1251 for(int i = 0; i < CHANNELS; ++i)
1252 out[i] = log2f(in[i]);
1253
1254 return valid;
1255}
1256
1257
1259{
1260 p->noise = factors[0];
1261 p->ultra_deep_blacks = factors[1];
1262 p->deep_blacks = factors[2];
1263 p->blacks = factors[3];
1264 p->shadows = factors[4];
1265 p->midtones = factors[5];
1266 p->highlights = factors[6];
1267 p->whites = factors[7];
1268 p->speculars = factors[8];
1269
1270 return 1;
1271}
1272
1273
1274/***
1275 * Cache invalidation and initializatiom
1276 ***/
1277
1278
1279static void gui_cache_init(struct dt_iop_module_t *self)
1280{
1282 if(IS_NULL_PTR(g)) return;
1283
1285 g->thumb_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1286 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1287 g->max_histogram = 1;
1288 g->scale = 1.0f;
1289 g->sigma = sqrtf(2.0f);
1290 g->mask_display = FALSE;
1291
1292 g->interpolation_valid = FALSE; // TRUE if the interpolation_matrix is ready
1293 g->luminance_valid = FALSE; // TRUE if the luminance cache is ready
1294 g->histogram_valid = FALSE; // TRUE if the histogram cache and stats are ready
1295 g->lut_valid = FALSE; // TRUE if the gui_lut is ready
1296 g->graph_valid = FALSE; // TRUE if the UI graph view is ready
1297 g->user_param_valid = FALSE; // TRUE if users params set in interactive view are in bounds
1298 g->factors_valid = TRUE; // TRUE if radial-basis coeffs are ready
1299
1300 g->valid_nodes_x = FALSE; // TRUE if x coordinates of graph nodes have been inited
1301 g->valid_nodes_y = FALSE; // TRUE if y coordinates of graph nodes have been inited
1302 g->area_cursor_valid = FALSE; // TRUE if mouse cursor is over the graph area
1303 g->area_dragging = FALSE; // TRUE if left-button has been pushed but not released and cursor motion is recorded
1304 g->cursor_valid = FALSE; // TRUE if mouse cursor is over the preview image
1305
1306 g->thumb_preview_entry = NULL;
1307 g->thumb_preview_buf_width = 0;
1308 g->thumb_preview_buf_height = 0;
1309
1310 g->desc = NULL;
1311 g->layout = NULL;
1312 g->cr = NULL;
1313 g->cst = NULL;
1314 g->context = NULL;
1315
1316 g->pipe_order = 0;
1318}
1319
1321{
1322 if(width) *width = 0;
1323 if(height) *height = 0;
1325
1327 if(IS_NULL_PTR(piece) || !piece->enabled || piece->roi_in.width <= 0 || piece->roi_in.height <= 0)
1329
1330 if(width) *width = piece->roi_in.width;
1331 if(height) *height = piece->roi_in.height;
1332
1333 static const char cache_tag[] = "toneequal:luminance";
1334 return dt_hash(piece->global_hash, cache_tag, sizeof(cache_tag));
1335}
1336
1337
1339 const float sigma)
1340{
1341 // Build the symmetrical definite positive part of the augmented matrix
1342 // of the radial-basis interpolation weights
1343
1344 const float gauss_denom = gaussian_denom(sigma);
1345 __OMP_SIMD__(aligned(A, centers_ops, centers_params:64) collapse(2))
1346 for(int i = 0; i < CHANNELS; ++i)
1347 for(int j = 0; j < PIXEL_CHAN; ++j)
1348 A[i * PIXEL_CHAN + j] = gaussian_func(centers_params[i] - centers_ops[j], gauss_denom);
1349}
1350
1351
1353static inline void compute_log_histogram_and_stats(const float *const restrict luminance,
1354 int histogram[UI_SAMPLES],
1355 const size_t num_elem,
1356 int *max_histogram,
1357 float *first_decile, float *last_decile)
1358{
1359 // (Re)init the histogram
1360 memset(histogram, 0, sizeof(int) * UI_SAMPLES);
1361
1362 // we first calculate an extended histogram for better accuracy
1363 #define TEMP_SAMPLES 2 * UI_SAMPLES
1364 int temp_hist[TEMP_SAMPLES];
1365 memset(temp_hist, 0, sizeof(int) * TEMP_SAMPLES);
1366
1367 // Split exposure in bins
1368 __OMP_PARALLEL_FOR__(reduction(+:temp_hist[:TEMP_SAMPLES]))
1369 for(size_t k = 0; k < num_elem; k++)
1370 {
1371 // extended histogram bins between [-10; +6] EV remapped between [0 ; 2 * UI_SAMPLES]
1372 const int index = CLAMP((int)(((log2f(luminance[k]) + 10.0f) / 16.0f) * (float)TEMP_SAMPLES), 0, TEMP_SAMPLES - 1);
1373 temp_hist[index] += 1;
1374 }
1375
1376 const int first = (int)((float)num_elem * 0.05f);
1377 const int last = (int)((float)num_elem * (1.0f - 0.95f));
1378 int population = 0;
1379 int first_pos = 0;
1380 int last_pos = 0;
1381
1382 // scout the extended histogram bins looking for deciles
1383 // these would not be accurate with the regular histogram
1384 for(int k = 0; k < TEMP_SAMPLES; ++k)
1385 {
1386 const size_t prev_population = population;
1387 population += temp_hist[k];
1388 if(prev_population < first && first <= population)
1389 {
1390 first_pos = k;
1391 break;
1392 }
1393 }
1394 population = 0;
1395 for(int k = TEMP_SAMPLES - 1; k >= 0; --k)
1396 {
1397 const size_t prev_population = population;
1398 population += temp_hist[k];
1399 if(prev_population < last && last <= population)
1400 {
1401 last_pos = k;
1402 break;
1403 }
1404 }
1405
1406 // Convert decile positions to exposures
1407 *first_decile = 16.0 * (float)first_pos / (float)(TEMP_SAMPLES - 1) - 10.0;
1408 *last_decile = 16.0 * (float)last_pos / (float)(TEMP_SAMPLES - 1) - 10.0;
1409
1410 // remap the extended histogram into the normal one
1411 // bins between [-8; 0] EV remapped between [0 ; UI_SAMPLES]
1412 for(size_t k = 0; k < TEMP_SAMPLES; ++k)
1413 {
1414 float EV = 16.0 * (float)k / (float)(TEMP_SAMPLES - 1) - 10.0;
1415 const int i = CLAMP((int)(((EV + 8.0f) / 8.0f) * (float)UI_SAMPLES), 0, UI_SAMPLES - 1);
1416 histogram[i] += temp_hist[k];
1417
1418 // store the max numbers of elements in bins for later normalization
1419 *max_histogram = histogram[i] > *max_histogram ? histogram[i] : *max_histogram;
1420 }
1421}
1422
1423static inline void update_histogram(struct dt_iop_module_t *const self)
1424{
1426 if(IS_NULL_PTR(g)) return;
1427
1428 dt_pixel_cache_entry_t *preview_entry = NULL;
1429 size_t width = 0;
1430 size_t height = 0;
1432 gboolean needs_histogram = FALSE;
1433
1434 // Readers take a temporary cache ref while copying the GUI-visible entry pointer,
1435 // then read-lock the cacheline only around the actual sampling. This keeps both
1436 // ownership transfer and lock lifetime visible at the call site.
1438 if(!g->histogram_valid && g->luminance_valid && g->thumb_preview_entry)
1439 {
1440 preview_entry = g->thumb_preview_entry;
1441 width = g->thumb_preview_buf_width;
1442 height = g->thumb_preview_buf_height;
1443 preview_hash = g->thumb_preview_hash;
1445 needs_histogram = TRUE;
1446 }
1448
1449 if(!needs_histogram || width == 0 || height == 0)
1450 {
1451 if(!IS_NULL_PTR(preview_entry))
1453 return;
1454 }
1455
1456 int histogram[UI_SAMPLES];
1457 int max_histogram = 1;
1458 float first_decile = 0.0f;
1459 float last_decile = 0.0f;
1460
1462 const float *const preview_buf = (const float *const)dt_pixel_cache_entry_get_data(preview_entry);
1463 if(preview_buf)
1464 compute_log_histogram_and_stats(preview_buf, histogram, width * height, &max_histogram, &first_decile,
1465 &last_decile);
1467
1468 if(IS_NULL_PTR(preview_buf))
1469 {
1471 return;
1472 }
1473
1475 if(g->thumb_preview_entry == preview_entry && g->thumb_preview_hash == preview_hash && !g->histogram_valid)
1476 {
1477 memcpy(g->histogram, histogram, sizeof(histogram));
1478 g->max_histogram = max_histogram;
1479 g->histogram_first_decile = first_decile;
1480 g->histogram_last_decile = last_decile;
1481 g->histogram_average = (first_decile + last_decile) / 2.0f;
1482 g->histogram_valid = TRUE;
1483 }
1485
1487}
1488
1489
1492 const float offset,
1493 const float scaling)
1494{
1495 // Compute the LUT of the exposure corrections in EV,
1496 // offset and scale it for display in GUI widget graph
1497
1498 float *const restrict LUT = g->gui_lut;
1499 const float *const restrict factors = g->factors;
1500 const float sigma = g->sigma;
1501 __OMP_FOR_SIMD__(aligned(LUT, factors:64))
1502 for(int k = 0; k < UI_SAMPLES; k++)
1503 {
1504 // build the inset graph curve LUT
1505 // the x range is [-14;+2] EV
1506 const float x = (8.0f * (((float)k) / ((float)(UI_SAMPLES - 1)))) - 8.0f;
1507 LUT[k] = offset - log2f(pixel_correction(x, factors, sigma)) / scaling;
1508 }
1509}
1510
1511
1512
1513static inline gboolean update_curve_lut(struct dt_iop_module_t *self)
1514{
1517
1518 if(IS_NULL_PTR(g)) return FALSE;
1519
1520 gboolean valid = TRUE;
1521
1522 if(!g->interpolation_valid)
1523 {
1524 build_interpolation_matrix(g->interpolation_matrix, g->sigma);
1525 g->interpolation_valid = TRUE;
1526 g->factors_valid = FALSE;
1527 }
1528
1529 if(!g->user_param_valid)
1530 {
1531 float factors[CHANNELS] DT_ALIGNED_ARRAY;
1532 get_channels_factors(factors, p);
1533 dt_simd_memcpy(factors, g->temp_user_params, CHANNELS);
1534 g->user_param_valid = TRUE;
1535 g->factors_valid = FALSE;
1536 }
1537
1538 if(!g->factors_valid && g->user_param_valid)
1539 {
1540 float factors[CHANNELS] DT_ALIGNED_ARRAY;
1541 dt_simd_memcpy(g->temp_user_params, factors, CHANNELS);
1542 if(pseudo_solve(g->interpolation_matrix, factors, CHANNELS, PIXEL_CHAN, 1) != 0)
1543 {
1544 valid = FALSE;
1545 }
1546 else
1547 {
1548 dt_simd_memcpy(factors, g->factors, PIXEL_CHAN);
1549 g->factors_valid = TRUE;
1550 g->lut_valid = FALSE;
1551 }
1552 }
1553
1554 if(!g->lut_valid && g->factors_valid)
1555 {
1556 compute_lut_correction(g, 0.5f, 4.0f);
1557 g->lut_valid = TRUE;
1558 }
1559
1560 return valid;
1561}
1562
1563
1565{
1568
1569 module->data = gd;
1570}
1571
1572
1574{
1575 dt_free(module->data);
1576}
1577
1578
1581{
1585
1586 // Trivial params passing
1587 d->method = p->method;
1588 d->details = p->details;
1589 d->iterations = p->iterations;
1590 d->smoothing = p->smoothing;
1591 d->quantization = p->quantization;
1592
1593 // UI blending param is set in % of the largest image dimension
1594 d->blending = p->blending / 100.0f;
1595
1596 // UI guided filter feathering param increases the edges taping
1597 // but the actual regularization params applied in guided filter behaves the other way
1598 d->feathering = 1.f / (p->feathering);
1599
1600 // UI params are in log2 offsets (EV) : convert to linear factors
1601 d->contrast_boost = exp2f(p->contrast_boost);
1602 d->exposure_boost = exp2f(p->exposure_boost);
1603
1604 /*
1605 * Perform a radial-based interpolation using a series gaussian functions
1606 */
1607 // FIXME: trying to spare some CPU cycles by mixing GUI params update
1608 // with pipeline code is not worth the thread-safety issues (solved only by deadlocks).
1609 // Move that to GUI code.
1610 if(self->dev->gui_attached && !IS_NULL_PTR(g))
1611 {
1612 if(g->sigma != p->smoothing) g->interpolation_valid = FALSE;
1613 g->sigma = p->smoothing;
1614 g->user_param_valid = FALSE; // force updating channels factors
1615
1616 update_curve_lut(self);
1617 dt_simd_memcpy(g->factors, d->factors, PIXEL_CHAN);
1618 }
1619 else
1620 {
1621 // No cache : Build / Solve interpolation matrix
1622 float factors[CHANNELS] DT_ALIGNED_ARRAY;
1623 get_channels_factors(factors, p);
1624
1626 build_interpolation_matrix(A, p->smoothing);
1627 if(pseudo_solve(A, factors, CHANNELS, PIXEL_CHAN, 0) != 0) return;
1628
1629 dt_simd_memcpy(factors, d->factors, PIXEL_CHAN);
1630 }
1631
1632 // compute the correction LUT here to spare some time in process
1633 // when computing several times toneequalizer with same parameters
1634 compute_correction_lut(d->correction_lut, d->smoothing, d->factors);
1635}
1636
1637
1639{
1641 piece->data_size = sizeof(dt_iop_toneequalizer_data_t);
1642}
1643
1644
1646{
1647 dt_free_align(piece->data);
1648 piece->data = NULL;
1649}
1650
1652{
1653 dt_iop_module_t *module = (dt_iop_module_t *)self;
1655 const dt_iop_toneequalizer_params_t *p = (const dt_iop_toneequalizer_params_t *)module->params;
1656
1657 switch(p->details)
1658 {
1659 case(DT_TONEEQ_NONE):
1660 {
1661 gtk_widget_set_visible(g->blending, FALSE);
1662 gtk_widget_set_visible(g->feathering, FALSE);
1663 gtk_widget_set_visible(g->iterations, FALSE);
1664 gtk_widget_set_visible(g->contrast_boost, FALSE);
1665 gtk_widget_set_visible(g->quantization, FALSE);
1666 break;
1667 }
1668
1670 case(DT_TONEEQ_AVG_EIGF):
1671 {
1672 gtk_widget_set_visible(g->blending, TRUE);
1673 gtk_widget_set_visible(g->feathering, TRUE);
1674 gtk_widget_set_visible(g->iterations, TRUE);
1675 gtk_widget_set_visible(g->contrast_boost, FALSE);
1676 gtk_widget_set_visible(g->quantization, TRUE);
1677 break;
1678 }
1679
1680 case(DT_TONEEQ_GUIDED):
1681 case(DT_TONEEQ_EIGF):
1682 {
1683 gtk_widget_set_visible(g->blending, TRUE);
1684 gtk_widget_set_visible(g->feathering, TRUE);
1685 gtk_widget_set_visible(g->iterations, TRUE);
1686 gtk_widget_set_visible(g->contrast_boost, TRUE);
1687 gtk_widget_set_visible(g->quantization, TRUE);
1688 break;
1689 }
1690 }
1691}
1692
1694{
1695 ++darktable.gui->reset;
1696 dt_bauhaus_slider_set(g->noise, p->noise);
1697 dt_bauhaus_slider_set(g->ultra_deep_blacks, p->ultra_deep_blacks);
1698 dt_bauhaus_slider_set(g->deep_blacks, p->deep_blacks);
1699 dt_bauhaus_slider_set(g->blacks, p->blacks);
1700 dt_bauhaus_slider_set(g->shadows, p->shadows);
1701 dt_bauhaus_slider_set(g->midtones, p->midtones);
1702 dt_bauhaus_slider_set(g->highlights, p->highlights);
1703 dt_bauhaus_slider_set(g->whites, p->whites);
1704 dt_bauhaus_slider_set(g->speculars, p->speculars);
1705 --darktable.gui->reset;
1706}
1707
1708
1709void gui_update(struct dt_iop_module_t *self)
1710{
1713
1714 dt_bauhaus_slider_set(g->smoothing, logf(p->smoothing) / logf(sqrtf(2.0f)) - 1.0f);
1715
1718
1719 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
1720}
1721
1722void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1723{
1725 if(w == g->method ||
1726 w == g->blending ||
1727 w == g->feathering ||
1728 w == g->iterations ||
1729 w == g->quantization)
1730 {
1732 }
1733 else if (w == g->details)
1734 {
1737 }
1738 else if (w == g->contrast_boost || w == g->exposure_boost)
1739 {
1742 }
1743}
1744
1745static void smoothing_callback(GtkWidget *slider, gpointer user_data)
1746{
1747 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1748 if(darktable.gui->reset) return;
1751
1752 p->smoothing= powf(sqrtf(2.0f), 1.0f + dt_bauhaus_slider_get(slider));
1753
1754 float factors[CHANNELS] DT_ALIGNED_ARRAY;
1755 get_channels_factors(factors, p);
1756
1757 // Solve the interpolation by least-squares to check the validity of the smoothing param
1758 const int valid = update_curve_lut(self);
1759 if(!valid) dt_control_log(_("the interpolation is unstable, decrease the curve smoothing"));
1760
1761 // Redraw graph before launching computation
1762 update_curve_lut(self);
1763 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1765
1766 // Unlock the colour picker so we can display our own custom cursor
1768}
1769
1770static void show_luminance_mask_callback(GtkWidget *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1771{
1772 if(darktable.gui->reset) return;
1774
1775 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
1776
1778
1779 // if blend module is displaying mask do not display it here
1782
1783 g->mask_display = !g->mask_display;
1784
1785 if(g->mask_display)
1787
1788 dt_iop_set_cache_bypass(self, g->mask_display);
1789 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
1791
1792 // Unlock the colour picker so we can display our own custom cursor
1794}
1795
1796
1797/***
1798 * GUI Interactivity
1799 **/
1800
1801static void _switch_cursors(struct dt_iop_module_t *self)
1802{
1804 if(IS_NULL_PTR(g) || !self->dev->gui_attached) return;
1805
1806 if(!self->expanded)
1807 {
1808 // If the module lost focus, do nothing and let the app decide.
1809 return;
1810 }
1811 else if(!self->enabled)
1812 {
1813 // A disabled module does not own the cursor state: leave whatever the view
1814 // or another tool has currently selected, and most importantly do not hide it.
1815 return;
1816 }
1817 else if(!sanity_check(self) || in_mask_editing(self) || dt_iop_color_picker_is_visible(self->dev))
1818 {
1819 // if we are editing masks or using colour-pickers, do not display controls
1820
1821 // display default cursor
1824 return;
1825 }
1826 else if((self->dev->pipe->processing || self->dev->preview_pipe->processing) && g->cursor_valid)
1827 {
1828 // if pipe is busy or dirty but cursor is on preview,
1829 // display waiting cursor while pipe reprocesses
1832
1834 }
1835 else if(self->enabled && g->cursor_valid && !self->dev->pipe->processing)
1836 {
1837 // if pipe is clean and idle and cursor is on preview,
1838 // hide GTK cursor because we display our custom one
1841 _("scroll over image to change tone exposure\n"
1842 "shift+scroll for large steps; "
1843 "ctrl+scroll for small steps"));
1844
1846 }
1847 else if(!g->cursor_valid)
1848 {
1849 // if module is active and opened but cursor is out of the preview,
1850 // display default cursor
1853
1855 }
1856 else
1857 {
1858 // in any other situation where module has focus,
1859 // reset the cursor but don't launch a redraw
1862 }
1863}
1864
1865
1866int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
1867{
1868 // Whenever the mouse moves over the picture preview, store its coordinates in the GUI struct
1869 // for later use. This works only if dev->preview_pipe perfectly overlaps with the UI preview
1870 // meaning all distortions, cropping, rotations etc. are applied before this module in the pipe.
1871
1872 dt_develop_t *dev = self->dev;
1874
1875 const int fail = !sanity_check(self);
1876 if(fail) return 0;
1877
1879 {
1880 g->cursor_valid = FALSE;
1881 g->area_active_node = -1;
1882 _switch_cursors(self);
1883 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1884 return 0;
1885 }
1886
1887 const int wd = dev->roi.preview_width;
1888 const int ht = dev->roi.preview_height;
1889
1890 if(IS_NULL_PTR(g)) return 0;
1891 if(wd < 1 || ht < 1) return 0;
1892
1893 float pzxpy[2] = { (float)x, (float)y };
1896
1897 const int x_pointer = pzxpy[0];
1898 const int y_pointer = pzxpy[1];
1899
1900 // Cursor is valid if it's inside the picture frame
1901 if(x_pointer >= 0 && x_pointer < wd && y_pointer >= 0 && y_pointer < ht)
1902 {
1903 g->cursor_valid = TRUE;
1904 g->cursor_pos_x = x_pointer;
1905 g->cursor_pos_y = y_pointer;
1906 }
1907 else
1908 {
1909 g->cursor_valid = FALSE;
1910 g->cursor_pos_x = 0;
1911 g->cursor_pos_y = 0;
1912 }
1913
1914 // Store the current preview exposure too, to spare recomputing it in the UI callbacks.
1915 if(g->cursor_valid && !dev->pipe->processing)
1916 {
1917 dt_pixel_cache_entry_t *preview_entry = NULL;
1918 size_t preview_width = 0;
1919 size_t preview_height = 0;
1920
1921 // Keep the GUI cache entry alive before releasing the GUI state lock.
1922 // Pipe workers can replace the retained entry while mouse motion keeps sampling it.
1924 if(g->luminance_valid && !IS_NULL_PTR(g->thumb_preview_entry))
1925 {
1926 preview_entry = g->thumb_preview_entry;
1927 preview_width = g->thumb_preview_buf_width;
1928 preview_height = g->thumb_preview_buf_height;
1930 }
1932
1933 if(!IS_NULL_PTR(preview_entry) && preview_width > 0 && preview_height > 0)
1934 {
1936 const float *const preview_buf = (const float *const)dt_pixel_cache_entry_get_data(preview_entry);
1937 const float cursor_exposure
1938 = preview_buf ? log2f(get_luminance_from_buffer(preview_buf, preview_width, preview_height,
1939 (size_t)x_pointer, (size_t)y_pointer))
1940 : NAN;
1942
1943 if(!isnan(cursor_exposure))
1944 {
1945 g->cursor_exposure = cursor_exposure;
1946 }
1947
1948 }
1949
1950 if(preview_entry)
1952 }
1953
1954 _switch_cursors(self);
1955 return 1;
1956}
1957
1958
1960{
1962
1963 if(IS_NULL_PTR(g)) return 0;
1964
1965 g->cursor_valid = FALSE;
1966 g->area_active_node = -1;
1967
1968 // display default cursor
1971 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1972
1973 return 1;
1974}
1975
1976
1977static inline int set_new_params_interactive(const float control_exposure, const float exposure_offset, const float blending_sigma,
1979{
1980 // Apply an exposure offset optimized smoothly over all the exposure channels,
1981 // taking user instruction to apply exposure_offset EV at control_exposure EV,
1982 // and commit the new params is the solution is valid.
1983
1984 // Raise the user params accordingly to control correction and distance from cursor exposure
1985 // to blend smoothly the desired correction
1986 const float std = gaussian_denom(blending_sigma);
1987 if(g->user_param_valid)
1988 {
1989 for(int i = 0; i < CHANNELS; ++i)
1990 g->temp_user_params[i] *= exp2f(gaussian_func(centers_params[i] - control_exposure, std) * exposure_offset);
1991 }
1992
1993 // Get the new weights for the radial-basis approximation
1994 float factors[CHANNELS] DT_ALIGNED_ARRAY;
1995 dt_simd_memcpy(g->temp_user_params, factors, CHANNELS);
1996 if(g->user_param_valid)
1997 g->user_param_valid = (pseudo_solve(g->interpolation_matrix, factors, CHANNELS, PIXEL_CHAN, 1) == 0);
1998 if(!g->user_param_valid) dt_control_log(_("the interpolation is unstable, decrease the curve smoothing"));
1999
2000 // Compute new user params for channels and store them locally
2001 if(g->user_param_valid)
2002 g->user_param_valid = compute_channels_factors(factors, g->temp_user_params, g->sigma);
2003 if(!g->user_param_valid) dt_control_log(_("some parameters are out-of-bounds"));
2004
2005 const int commit = g->user_param_valid;
2006
2007 if(commit)
2008 {
2009 // Accept the solution
2010 dt_simd_memcpy(factors, g->factors, PIXEL_CHAN);
2011 g->lut_valid = 0;
2012
2013 // Convert the linear temp parameters to log gains and commit
2014 float gains[CHANNELS] DT_ALIGNED_ARRAY;
2015 compute_channels_gains(g->temp_user_params, gains);
2016 commit_channels_gains(gains, p);
2017 }
2018 else
2019 {
2020 // Reset the GUI copy of user params
2021 get_channels_factors(factors, p);
2022 dt_simd_memcpy(factors, g->temp_user_params, CHANNELS);
2023 g->user_param_valid = 1;
2024 }
2025
2026 return commit;
2027}
2028
2029
2030int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
2031{
2032 dt_develop_t *dev = self->dev;
2035
2036 if(!sanity_check(self)) return 0;
2037 if(darktable.gui->reset) return 1;
2038 if(IS_NULL_PTR(g)) return 0;
2039 if(!self->expanded) return 0;
2040 if(dt_iop_color_picker_is_visible(dev)) return 0;
2041
2042 // turn-on the module if off
2043 if(!self->enabled)
2044 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
2045
2046 // if GUI buffers not ready, exit but still handle the cursor
2047 const int fail = (!g->cursor_valid || !g->interpolation_valid || !g->user_param_valid || dev->pipe->processing || !self->expanded);
2048 if(fail) return 1;
2049
2050 // Re-read the exposure in case the preview changed after the mouse moved.
2051 dt_pixel_cache_entry_t *preview_entry = NULL;
2052 size_t preview_width = 0;
2053 size_t preview_height = 0;
2054 int cursor_x = 0;
2055 int cursor_y = 0;
2056
2057 // Copy the cursor sample source while holding the GUI state lock, then keep the
2058 // cacheline alive with an explicit ref until the sampling read lock is released.
2060 if(g->luminance_valid && !IS_NULL_PTR(g->thumb_preview_entry))
2061 {
2062 preview_entry = g->thumb_preview_entry;
2063 preview_width = g->thumb_preview_buf_width;
2064 preview_height = g->thumb_preview_buf_height;
2065 cursor_x = g->cursor_pos_x;
2066 cursor_y = g->cursor_pos_y;
2068 }
2070
2071 if(IS_NULL_PTR(preview_entry) || preview_width == 0 || preview_height == 0)
2072 {
2073 if(!IS_NULL_PTR(preview_entry))
2075 return 1;
2076 }
2077
2078 if(!IS_NULL_PTR(preview_entry) && preview_width > 0 && preview_height > 0)
2079 {
2081 const float *const preview_buf = (const float *const)dt_pixel_cache_entry_get_data(preview_entry);
2082 const float cursor_exposure
2083 = preview_buf ? log2f(get_luminance_from_buffer(preview_buf, preview_width, preview_height,
2084 (size_t)cursor_x, (size_t)cursor_y))
2085 : NAN;
2087
2088 if(!isnan(cursor_exposure))
2089 {
2090 g->cursor_exposure = cursor_exposure;
2091 }
2092 }
2093
2094 if(!IS_NULL_PTR(preview_entry))
2096
2097 // Set the correction from mouse scroll input
2098 const float increment = (up) ? +1.0f : -1.0f;
2099
2100 float step;
2101 if(dt_modifier_is(state, GDK_SHIFT_MASK))
2102 step = 1.0f; // coarse
2103 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
2104 step = 0.1f; // fine
2105 else
2106 step = 0.25f; // standard
2107
2108 const float offset = step * ((float)increment);
2109
2110 // Get the desired correction on exposure channels
2111 const int commit = set_new_params_interactive(g->cursor_exposure, offset, g->sigma * g->sigma / 2.0f, g, p);
2112
2113 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2114
2115 if(commit)
2116 {
2117 // Update GUI with new params
2119
2121 }
2122
2123 return 1;
2124}
2125
2126/***
2127 * GTK/Cairo drawings and custom widgets
2128 **/
2129
2130static inline gboolean _init_drawing(dt_iop_module_t *const restrict self, GtkWidget *widget,
2131 dt_iop_toneequalizer_gui_data_t *const restrict g);
2132
2133
2134void cairo_draw_hatches(cairo_t *cr, double center[2], double span[2], int instances, double line_width, double shade)
2135{
2136 // center is the (x, y) coordinates of the region to draw
2137 // span is the distance of the region's bounds to the center, over (x, y) axes
2138
2139 // Get the coordinates of the corners of the bounding box of the region
2140 double C0[2] = { center[0] - span[0], center[1] - span[1] };
2141 double C2[2] = { center[0] + span[0], center[1] + span[1] };
2142
2143 double delta[2] = { 2.0 * span[0] / (double)instances,
2144 2.0 * span[1] / (double)instances };
2145
2146 cairo_set_line_width(cr, line_width);
2147 cairo_set_source_rgb(cr, shade, shade, shade);
2148
2149 for(int i = -instances / 2 - 1; i <= instances / 2 + 1; i++)
2150 {
2151 cairo_move_to(cr, C0[0] + (double)i * delta[0], C0[1]);
2152 cairo_line_to(cr, C2[0] + (double)i * delta[0], C2[1]);
2153 cairo_stroke(cr);
2154 }
2155}
2156
2157static void get_shade_from_luminance(cairo_t *cr, const float luminance, const float alpha)
2158{
2159 // TODO: fetch screen gamma from ICC display profile
2160 const float gamma = 1.0f / 2.2f;
2161 const float shade = powf(luminance, gamma);
2162 cairo_set_source_rgba(cr, shade, shade, shade, alpha);
2163}
2164
2165
2166static void draw_exposure_cursor(cairo_t *cr, const double pointerx, const double pointery, const double radius, const float luminance, const float zoom_scale, const int instances, const float alpha)
2167{
2168 // Draw a circle cursor filled with a grey shade corresponding to a luminance value
2169 // or hatches if the value is above the overexposed threshold
2170
2171 const double radius_z = radius / zoom_scale;
2172
2174 cairo_arc(cr, pointerx, pointery, radius_z, 0, 2 * M_PI);
2175 cairo_fill_preserve(cr);
2176 cairo_save(cr);
2177 cairo_clip(cr);
2178
2179 if(log2f(luminance) > 0.0f)
2180 {
2181 // if overexposed, draw hatches
2182 double pointer_coord[2] = { pointerx, pointery };
2183 double span[2] = { radius_z, radius_z };
2184 cairo_draw_hatches(cr, pointer_coord, span, instances, DT_PIXEL_APPLY_DPI(1. / zoom_scale), 0.3);
2185 }
2186 cairo_restore(cr);
2187}
2188
2189
2190static void match_color_to_background(cairo_t *cr, const float exposure, const float alpha)
2191{
2192 float shade = 0.0f;
2193 // TODO: put that as a preference in anselrc
2194 const float contrast = 1.0f;
2195
2196 if(exposure > -2.5f)
2197 shade = (fminf(exposure * contrast, 0.0f) - 2.5f);
2198 else
2199 shade = (fmaxf(exposure / contrast, -5.0f) + 2.5f);
2200
2201 get_shade_from_luminance(cr, exp2f(shade), alpha);
2202}
2203
2204
2205void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height,
2206 int32_t pointerx, int32_t pointery)
2207{
2208 // Draw the custom exposure cursor over the image preview
2209
2210 dt_develop_t *dev = self->dev;
2212 if(IS_NULL_PTR(g)) return;
2213
2214 // If the darkroom picker owns the center view, keep tone equalizer overlays out of the way.
2215 if(in_mask_editing(self) || dt_iop_color_picker_is_visible(dev)) return;
2216
2217 const int fail = (!g->cursor_valid || !g->interpolation_valid || dev->pipe->processing
2218 || !sanity_check(self) || !self->expanded);
2219 if(fail) return;
2220
2221 if(!g->graph_valid)
2222 if(!_init_drawing(self, self->widget, g)) return;
2223
2224 // Get coordinates
2225 const float x_pointer = g->cursor_pos_x;
2226 const float y_pointer = g->cursor_pos_y;
2227 dt_pixel_cache_entry_t *preview_entry = NULL;
2228 size_t preview_width = 0;
2229 size_t preview_height = 0;
2230 float factors[PIXEL_CHAN] DT_ALIGNED_ARRAY;
2231 float sigma = 0.0f;
2232
2233 float exposure_in = 0.0f;
2234 float luminance_in = 0.0f;
2235 float correction = 0.0f;
2236 float exposure_out = 0.0f;
2237 float luminance_out = 0.0f;
2238 if(self->enabled)
2239 {
2240 // The drawing pass samples the same retained luminance cacheline as the event
2241 // handlers, so the ref has to be taken before another pipe callback can detach it.
2243 if(g->luminance_valid && !IS_NULL_PTR(g->thumb_preview_entry))
2244 {
2245 preview_entry = g->thumb_preview_entry;
2246 preview_width = g->thumb_preview_buf_width;
2247 preview_height = g->thumb_preview_buf_height;
2249 dt_simd_memcpy(g->factors, factors, PIXEL_CHAN);
2250 sigma = g->sigma;
2251 }
2253 }
2254
2255 if(!IS_NULL_PTR(preview_entry) && preview_width > 0 && preview_height > 0)
2256 {
2258 const float *const preview_buf = (const float *const)dt_pixel_cache_entry_get_data(preview_entry);
2259 if(!IS_NULL_PTR(preview_buf))
2260 {
2261 exposure_in = log2f(get_luminance_from_buffer(preview_buf, preview_width, preview_height,
2262 (size_t)x_pointer, (size_t)y_pointer));
2263 luminance_in = exp2f(exposure_in);
2264 correction = log2f(pixel_correction(exposure_in, factors, sigma));
2265 exposure_out = exposure_in + correction;
2266 luminance_out = exp2f(exposure_out);
2267 }
2268 else
2269 {
2270 exposure_in = NAN;
2271 correction = NAN;
2272 }
2274
2275 if(!isnan(exposure_in))
2276 {
2277 g->cursor_exposure = exposure_in;
2278 }
2279 }
2280
2281 if(preview_entry)
2283
2284 if(isnan(correction) || isnan(exposure_in)) return; // something went wrong
2285
2286 // Rescale and shift Cairo drawing coordinates
2287 const float zoom_scale = dt_dev_get_overlay_scale(dev);
2288 dt_dev_rescale_roi(dev, cr, width, height);
2289
2290 // set custom cursor dimensions
2291 const double outer_radius = 16.;
2292 const double inner_radius = outer_radius / 2.0;
2293 const double setting_offset_x = (outer_radius + 4. * g->inner_padding) / zoom_scale;
2294 const double fill_width = DT_PIXEL_APPLY_DPI(4) / zoom_scale;
2295
2296 // setting fill bars
2297 match_color_to_background(cr, exposure_out, 1.0);
2298 cairo_set_line_width(cr, 2.0 * fill_width);
2299 cairo_move_to(cr, x_pointer - setting_offset_x, y_pointer);
2300
2301 if(correction > 0.0f)
2302 cairo_arc(cr, x_pointer, y_pointer, setting_offset_x, M_PI, M_PI + correction * M_PI / 4.0);
2303 else
2304 cairo_arc_negative(cr, x_pointer, y_pointer, setting_offset_x, M_PI, M_PI + correction * M_PI / 4.0);
2305
2306 cairo_stroke(cr);
2307
2308 // setting ground level
2309 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5) / zoom_scale);
2310 cairo_move_to(cr, x_pointer + (outer_radius + 2. * g->inner_padding) / zoom_scale, y_pointer);
2311 cairo_line_to(cr, x_pointer + outer_radius / zoom_scale, y_pointer);
2312 cairo_move_to(cr, x_pointer - outer_radius / zoom_scale, y_pointer);
2313 cairo_line_to(cr, x_pointer - setting_offset_x - 4.0 * g->inner_padding / zoom_scale, y_pointer);
2314 cairo_stroke(cr);
2315
2316 // setting cursor cross hair
2317 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.5) / zoom_scale);
2318 cairo_move_to(cr, x_pointer, y_pointer + setting_offset_x + fill_width);
2319 cairo_line_to(cr, x_pointer, y_pointer + outer_radius / zoom_scale);
2320 cairo_move_to(cr, x_pointer, y_pointer - outer_radius / zoom_scale);
2321 cairo_line_to(cr, x_pointer, y_pointer - setting_offset_x - fill_width);
2322 cairo_stroke(cr);
2323
2324 // draw exposure cursor
2325 draw_exposure_cursor(cr, x_pointer, y_pointer, outer_radius, luminance_in, zoom_scale, 6, .9);
2326 draw_exposure_cursor(cr, x_pointer, y_pointer, inner_radius, luminance_out, zoom_scale, 3, .9);
2327
2328 // Create Pango objects : texts
2329 char text[256];
2330 PangoLayout *layout;
2331 PangoRectangle ink;
2332 PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
2333
2334 // Avoid text resizing based on zoom level
2335 const int old_size = pango_font_description_get_size(desc);
2336 pango_font_description_set_size (desc, (int)(old_size / zoom_scale));
2337 layout = pango_cairo_create_layout(cr);
2338 pango_layout_set_font_description(layout, desc);
2340
2341 // Build text object
2342 if(preview_entry && self->enabled)
2343 snprintf(text, sizeof(text), _("%+.1f EV"), exposure_in);
2344 else
2345 snprintf(text, sizeof(text), "? EV");
2346 pango_layout_set_text(layout, text, -1);
2347 pango_layout_get_pixel_extents(layout, &ink, NULL);
2348
2349 // Draw the text plain blackground
2350 get_shade_from_luminance(cr, luminance_out, 0.75);
2351 cairo_rectangle(cr, x_pointer + (outer_radius + 2. * g->inner_padding) / zoom_scale,
2352 y_pointer - ink.y - ink.height / 2.0 - g->inner_padding / zoom_scale,
2353 ink.width + 2.0 * ink.x + 4. * g->inner_padding / zoom_scale,
2354 ink.height + 2.0 * ink.y + 2. * g->inner_padding / zoom_scale);
2355 cairo_fill(cr);
2356
2357 // Display the EV reading
2358 match_color_to_background(cr, exposure_out, 1.0);
2359 cairo_move_to(cr, x_pointer + (outer_radius + 4. * g->inner_padding) / zoom_scale,
2360 y_pointer - ink.y - ink.height / 2.);
2361 pango_cairo_show_layout(cr, layout);
2362
2363 cairo_stroke(cr);
2364
2365 pango_font_description_free(desc);
2366 g_object_unref(layout);
2367
2368 if(preview_entry && self->enabled)
2369 {
2370 // Search for nearest node in graph and highlight it
2371 const float radius_threshold = 0.45f;
2372 g->area_active_node = -1;
2373 if(g->cursor_valid)
2374 for(int i = 0; i < CHANNELS; ++i)
2375 {
2376 const float delta_x = fabsf(g->cursor_exposure - centers_params[i]);
2377 if(delta_x < radius_threshold)
2378 g->area_active_node = i;
2379 }
2380
2381 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2382 }
2383}
2384
2385
2386void gui_focus(struct dt_iop_module_t *self, gboolean in)
2387{
2389 _switch_cursors(self);
2390 if(!in)
2391 {
2392 //lost focus - stop showing mask
2393 const gboolean was_mask = g->mask_display;
2394 g->mask_display = FALSE;
2396 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2398 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), FALSE);
2399 if(was_mask) dt_dev_pixelpipe_update_history_main(self->dev);
2401 }
2402 else
2403 {
2404 gboolean needs_preview_update = FALSE;
2405
2406 if(self->enabled && self->dev && self->dev->preview_pipe && !self->dev->preview_pipe->processing)
2407 {
2409 if(!IS_NULL_PTR(piece) && piece->enabled && piece->roi_in.width > 0 && piece->roi_in.height > 0)
2410 {
2411 // Opening the module can happen after preview processing already finished.
2412 // In that case the preview pipe may stay idle because darkroom can reuse an
2413 // existing backbuffer, so reattach to the existing luminance cacheline here
2414 // instead of waiting for process() to run again. This was tested by opening
2415 // tone equalizer on a fresh darkroom image with no pending recompute.
2416 static const char cache_tag[] = "toneequal:luminance";
2417 const uint64_t preview_hash = dt_hash(piece->global_hash, cache_tag, sizeof(cache_tag));
2418 void *preview_buf = NULL;
2419 dt_pixel_cache_entry_t *preview_entry = NULL;
2420
2421 gboolean preview_ready = dt_dev_pixelpipe_cache_ref_entry_by_hash(darktable.pixelpipe_cache, preview_hash,
2422 &preview_buf, &preview_entry);
2423 if(preview_ready && (IS_NULL_PTR(preview_buf) || IS_NULL_PTR(preview_entry)))
2424 {
2425 if(!IS_NULL_PTR(preview_entry))
2427 preview_ready = FALSE;
2428 }
2429
2430 if(preview_ready)
2431 {
2432 dt_pixel_cache_entry_t *old_entry = NULL;
2433 gboolean keep_new_entry = FALSE;
2435 if(g->thumb_preview_entry != preview_entry || g->thumb_preview_hash != preview_hash
2436 || g->thumb_preview_buf_width != piece->roi_in.width
2437 || g->thumb_preview_buf_height != piece->roi_in.height || !g->luminance_valid)
2438 {
2439 old_entry = g->thumb_preview_entry;
2440 g->thumb_preview_entry = preview_entry;
2441 g->thumb_preview_hash = preview_hash;
2442 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
2443 g->thumb_preview_buf_width = piece->roi_in.width;
2444 g->thumb_preview_buf_height = piece->roi_in.height;
2445 g->luminance_valid = TRUE;
2446 g->histogram_valid = FALSE;
2447 keep_new_entry = TRUE;
2448 }
2450
2451 if(old_entry)
2453 if(!keep_new_entry)
2455 }
2456 else
2457 {
2459 g->pending_preview_hash = preview_hash;
2461 needs_preview_update = TRUE;
2462 }
2463 }
2464 else
2465 needs_preview_update = TRUE;
2466 }
2467
2468 if(needs_preview_update)
2470
2472 _("scroll over image to change tone exposure\n"
2473 "shift+scroll for large steps; "
2474 "ctrl+scroll for small steps"));
2475 }
2476}
2477
2478
2479static inline gboolean _init_drawing(dt_iop_module_t *const restrict self, GtkWidget *widget,
2480 dt_iop_toneequalizer_gui_data_t *const restrict g)
2481{
2482 // Cache the equalizer graph objects to avoid recomputing all the view at each redraw
2483 gtk_widget_get_allocation(widget, &g->allocation);
2484
2485 if(g->cst) cairo_surface_destroy(g->cst);
2486 g->cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, g->allocation.width, g->allocation.height);
2487
2488 if(g->cr) cairo_destroy(g->cr);
2489 g->cr = cairo_create(g->cst);
2490
2491 if(g->layout) g_object_unref(g->layout);
2492 g->layout = pango_cairo_create_layout(g->cr);
2493
2494 if(g->desc) pango_font_description_free(g->desc);
2495 g->desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
2496
2497 pango_layout_set_font_description(g->layout, g->desc);
2499 g->context = gtk_widget_get_style_context(widget);
2500
2501 char text[256];
2502
2503 // Get the text line height for spacing
2504 snprintf(text, sizeof(text), "X");
2505 pango_layout_set_text(g->layout, text, -1);
2506 pango_layout_get_pixel_extents(g->layout, &g->ink, NULL);
2507 g->line_height = g->ink.height;
2508
2509 // Get the width of a minus sign for legend labels spacing
2510 snprintf(text, sizeof(text), "-");
2511 pango_layout_set_text(g->layout, text, -1);
2512 pango_layout_get_pixel_extents(g->layout, &g->ink, NULL);
2513 g->sign_width = g->ink.width / 2.0;
2514
2515 // Set the sizes, margins and paddings
2516 g->inner_padding = INNER_PADDING;
2517 g->inset = g->inner_padding + darktable.bauhaus->quad_width;
2518 g->graph_left_space = g->line_height + g->inner_padding;
2519 g->graph_width = g->allocation.width - g->inset - 2.0 * g->line_height; // align the right border on sliders
2520 g->graph_height = g->allocation.height - g->inset - 2.0 * g->line_height; // give room to nodes
2521 g->gradient_left_limit = 0.0;
2522 g->gradient_right_limit = g->graph_width;
2523 g->gradient_top_limit = g->graph_height + 2 * g->inner_padding;
2524 g->gradient_width = g->gradient_right_limit - g->gradient_left_limit;
2525 g->legend_top_limit = -0.5 * g->line_height - 2.0 * g->inner_padding;
2526 g->x_label = g->graph_width + g->sign_width + 3.0 * g->inner_padding;
2527
2528 gtk_render_background(g->context, g->cr, 0, 0, g->allocation.width, g->allocation.height);
2529
2530 // set the graph as the origin of the coordinates
2531 cairo_translate(g->cr, g->line_height + 2 * g->inner_padding, g->line_height + 3 * g->inner_padding);
2532
2533 // display x-axis and y-axis legends (EV)
2535
2536 float value = -8.0f;
2537
2538 for(int k = 0; k < CHANNELS; k++)
2539 {
2540 const float xn = (((float)k) / ((float)(CHANNELS - 1))) * g->graph_width - g->sign_width;
2541 snprintf(text, sizeof(text), "%+.0f", value);
2542 pango_layout_set_text(g->layout, text, -1);
2543 pango_layout_get_pixel_extents(g->layout, &g->ink, NULL);
2544 cairo_move_to(g->cr, xn - 0.5 * g->ink.width - g->ink.x,
2545 g->legend_top_limit - 0.5 * g->ink.height - g->ink.y);
2546 pango_cairo_show_layout(g->cr, g->layout);
2547 cairo_stroke(g->cr);
2548
2549 value += 1.0;
2550 }
2551
2552 value = 2.0f;
2553
2554 for(int k = 0; k < 5; k++)
2555 {
2556 const float yn = (k / 4.0f) * g->graph_height;
2557 snprintf(text, sizeof(text), "%+.0f", value);
2558 pango_layout_set_text(g->layout, text, -1);
2559 pango_layout_get_pixel_extents(g->layout, &g->ink, NULL);
2560 cairo_move_to(g->cr, g->x_label - 0.5 * g->ink.width - g->ink.x,
2561 yn - 0.5 * g->ink.height - g->ink.y);
2562 pango_cairo_show_layout(g->cr, g->layout);
2563 cairo_stroke(g->cr);
2564
2565 value -= 1.0;
2566 }
2567
2569 // Draw the perceptually even gradient
2570 cairo_pattern_t *grad;
2571 grad = cairo_pattern_create_linear(g->gradient_left_limit, 0.0, g->gradient_right_limit, 0.0);
2573 cairo_set_line_width(g->cr, 0.0);
2574 cairo_rectangle(g->cr, g->gradient_left_limit, g->gradient_top_limit, g->gradient_width, g->line_height);
2575 cairo_set_source(g->cr, grad);
2576 cairo_fill(g->cr);
2577 cairo_pattern_destroy(grad);
2578
2580 // Draw the perceptually even gradient
2581 grad = cairo_pattern_create_linear(0.0, g->graph_height, 0.0, 0.0);
2583 cairo_set_line_width(g->cr, 0.0);
2584 cairo_rectangle(g->cr, -g->line_height - 2 * g->inner_padding, 0.0, g->line_height, g->graph_height);
2585 cairo_set_source(g->cr, grad);
2586 cairo_fill(g->cr);
2587
2588 cairo_pattern_destroy(grad);
2589
2590 // Draw frame borders
2591 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(0.5));
2593 cairo_rectangle(g->cr, 0, 0, g->graph_width, g->graph_height);
2594 cairo_stroke_preserve(g->cr);
2595
2596 // end of caching section, this will not be drawn again
2597
2598 g->graph_valid = 1;
2599
2600 return TRUE;
2601}
2602
2603
2604// must be called while holding self->gui_lock
2606{
2607 if(IS_NULL_PTR(g)) return;
2608
2609 if(!g->valid_nodes_x && g->graph_width > 0)
2610 {
2611 for(int i = 0; i < CHANNELS; ++i)
2612 g->nodes_x[i] = (((float)i) / ((float)(CHANNELS - 1))) * g->graph_width;
2613 g->valid_nodes_x = TRUE;
2614 }
2615}
2616
2617
2618// must be called while holding self->gui_lock
2620{
2621 if(IS_NULL_PTR(g)) return;
2622
2623 if(g->user_param_valid && g->graph_height > 0)
2624 {
2625 for(int i = 0; i < CHANNELS; ++i)
2626 g->nodes_y[i] = (0.5 - log2f(g->temp_user_params[i]) / 4.0) * g->graph_height; // assumes factors in [-2 ; 2] EV
2627 g->valid_nodes_y = TRUE;
2628 }
2629}
2630
2631
2632static gboolean area_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
2633{
2634 // Draw the widget equalizer view
2635 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2637 if(IS_NULL_PTR(g)) return FALSE;
2638
2639 // Init or refresh the drawing cache
2640 //if(!g->graph_valid)
2641 if(!_init_drawing(self, widget, g)) return FALSE; // this can be cached and drawn just once, but too lazy to debug a cache invalidation for Cairo objects
2642
2643 // since the widget sizes are not cached and invalidated properly above (yet...)
2644 // force the invalidation of the nodes coordinates to account for possible widget resizing
2645 g->valid_nodes_x = FALSE;
2646 g->valid_nodes_y = FALSE;
2647
2648 // Refresh cached UI elements
2649 update_histogram(self);
2650 update_curve_lut(self);
2651
2652 // Draw graph background
2653 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(0.5));
2654 cairo_rectangle(g->cr, 0, 0, g->graph_width, g->graph_height);
2656 cairo_fill(g->cr);
2657
2658 // draw grid
2659 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(0.5));
2661 dt_draw_grid(g->cr, 8, 0, 0, g->graph_width, g->graph_height);
2662
2663 // draw ground level
2665 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(1));
2666 cairo_move_to(g->cr, 0, 0.5 * g->graph_height);
2667 cairo_line_to(g->cr, g->graph_width, 0.5 * g->graph_height);
2668 cairo_stroke(g->cr);
2669
2670 if(g->histogram_valid && self->enabled)
2671 {
2672 // draw the inset histogram
2674 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(4.0));
2675 cairo_move_to(g->cr, 0, g->graph_height);
2676
2677 for(int k = 0; k < UI_SAMPLES; k++)
2678 {
2679 // the x range is [-8;+0] EV
2680 const float x_temp = (8.0 * (float)k / (float)(UI_SAMPLES - 1)) - 8.0;
2681 const float y_temp = (float)(g->histogram[k]) / (float)(g->max_histogram) * 0.96;
2682 cairo_line_to(g->cr, (x_temp + 8.0) * g->graph_width / 8.0,
2683 (1.0 - y_temp) * g->graph_height );
2684 }
2685 cairo_line_to(g->cr, g->graph_width, g->graph_height);
2686 cairo_close_path(g->cr);
2687 cairo_fill(g->cr);
2688
2689 if(g->histogram_last_decile > -0.1f)
2690 {
2691 // histogram overflows controls in highlights : display warning
2692 cairo_save(g->cr);
2693 cairo_set_source_rgb(g->cr, 0.75, 0.50, 0.);
2694 dtgtk_cairo_paint_gamut_check(g->cr, g->graph_width - 2.5 * g->line_height, 0.5 * g->line_height,
2695 2.0 * g->line_height, 2.0 * g->line_height, 0, NULL);
2696 cairo_restore(g->cr);
2697 }
2698
2699 if(g->histogram_first_decile < -7.9f)
2700 {
2701 // histogram overflows controls in lowlights : display warning
2702 cairo_save(g->cr);
2703 cairo_set_source_rgb(g->cr, 0.75, 0.50, 0.);
2704 dtgtk_cairo_paint_gamut_check(g->cr, 0.5 * g->line_height, 0.5 * g->line_height,
2705 2.0 * g->line_height, 2.0 * g->line_height, 0, NULL);
2706 cairo_restore(g->cr);
2707 }
2708 }
2709
2710 if(g->lut_valid)
2711 {
2712 // draw the interpolation curve
2714 cairo_move_to(g->cr, 0, g->gui_lut[0] * g->graph_height);
2715 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(3));
2716
2717 for(int k = 1; k < UI_SAMPLES; k++)
2718 {
2719 // the x range is [-8;+0] EV
2720 const float x_temp = (8.0f * (((float)k) / ((float)(UI_SAMPLES - 1)))) - 8.0f;
2721 const float y_temp = g->gui_lut[k];
2722
2723 cairo_line_to(g->cr, (x_temp + 8.0f) * g->graph_width / 8.0f,
2724 y_temp * g->graph_height );
2725 }
2726 cairo_stroke(g->cr);
2727 }
2728
2729 init_nodes_x(g);
2730 init_nodes_y(g);
2731
2732 if(g->user_param_valid)
2733 {
2734 // draw nodes positions
2735 for(int k = 0; k < CHANNELS; k++)
2736 {
2737 const float xn = g->nodes_x[k];
2738 const float yn = g->nodes_y[k];
2739
2740 // fill bars
2741 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(6));
2743 cairo_move_to(g->cr, xn, 0.5 * g->graph_height);
2744 cairo_line_to(g->cr, xn, yn);
2745 cairo_stroke(g->cr);
2746
2747 // bullets
2748 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(3));
2749 cairo_arc(g->cr, xn, yn, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
2751 cairo_stroke_preserve(g->cr);
2752
2753 if(g->area_active_node == k)
2755 else
2757
2758 cairo_fill(g->cr);
2759 }
2760 }
2761
2762 if(self->enabled)
2763 {
2764 if(g->area_cursor_valid)
2765 {
2766 const float radius = g->sigma * g->graph_width / 8.0f / sqrtf(2.0f);
2767 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(1.5));
2768 const float y =g->gui_lut[(int)CLAMP(((UI_SAMPLES - 1) * g->area_x / g->graph_width), 0, UI_SAMPLES - 1)];
2769 cairo_arc(g->cr, g->area_x, y * g->graph_height, radius, 0, 2. * M_PI);
2771 cairo_stroke(g->cr);
2772 }
2773
2774 if(g->cursor_valid)
2775 {
2776
2777 float x_pos = (g->cursor_exposure + 8.0f) / 8.0f * g->graph_width;
2778
2779 if(x_pos > g->graph_width || x_pos < 0.0f)
2780 {
2781 // exposure at current position is outside [-8; 0] EV :
2782 // bound it in the graph limits and show it in orange
2783 cairo_set_source_rgb(g->cr, 0.75, 0.50, 0.);
2784 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(3));
2785 x_pos = (x_pos < 0.0f) ? 0.0f : g->graph_width;
2786 }
2787 else
2788 {
2790 cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(1.5));
2791 }
2792
2793 cairo_move_to(g->cr, x_pos, 0.0);
2794 cairo_line_to(g->cr, x_pos, g->graph_height);
2795 cairo_stroke(g->cr);
2796 }
2797 }
2798
2799 // clean and exit
2800 cairo_set_source_surface(cr, g->cst, 0, 0);
2801 cairo_paint(cr);
2802
2803 return TRUE;
2804}
2805
2806static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
2807{
2808 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2809 if(darktable.gui->reset) return 1;
2810 if(!self->enabled) return 0;
2811
2813 g->area_x = (event->x - g->inset);
2814 g->area_y = (event->y - g->inset);
2815 g->area_dragging = FALSE;
2816 g->area_active_node = -1;
2817 g->area_cursor_valid = (g->area_x > 0.0f && g->area_x < g->graph_width && g->area_y > 0.0f && g->area_y < g->graph_height);
2818
2819 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2820 return TRUE;
2821}
2822
2823
2824static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
2825{
2826 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2827 if(darktable.gui->reset) return 1;
2828 if(!self->enabled) return 0;
2829
2832
2833 if(g->area_dragging)
2834 {
2835 // cursor left area : force commit to avoid glitches
2837
2839 }
2840 g->area_x = (event->x - g->inset);
2841 g->area_y = (event->y - g->inset);
2842 g->area_dragging = FALSE;
2843 g->area_active_node = -1;
2844 g->area_cursor_valid = (g->area_x > 0.0f && g->area_x < g->graph_width && g->area_y > 0.0f && g->area_y < g->graph_height);
2845
2846 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2847 return TRUE;
2848}
2849
2850
2851static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2852{
2853 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2854 if(darktable.gui->reset) return 1;
2855
2857
2859
2860 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
2861 {
2864
2865 // reset nodes params
2866 p->noise = d->noise;
2867 p->ultra_deep_blacks = d->ultra_deep_blacks;
2868 p->deep_blacks = d->deep_blacks;
2869 p->blacks = d->blacks;
2870 p->shadows = d->shadows;
2871 p->midtones = d->midtones;
2872 p->highlights = d->highlights;
2873 p->whites = d->whites;
2874 p->speculars = d->speculars;
2875
2876 // update UI sliders
2878
2879 // Redraw graph
2880 gtk_widget_queue_draw(self->widget);
2882 return TRUE;
2883 }
2884 else if(event->button == 1)
2885 {
2886 if(self->enabled)
2887 {
2888 g->area_dragging = 1;
2889 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2890 }
2891 else
2892 {
2894 }
2895 return TRUE;
2896 }
2897
2898 // Unlock the colour picker so we can display our own custom cursor
2900
2901 return FALSE;
2902}
2903
2904
2905static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
2906{
2907 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2908 if(darktable.gui->reset) return 1;
2909 if(!self->enabled) return 0;
2910
2913
2914 if(g->area_dragging)
2915 {
2916 // vertical distance travelled since button_pressed event
2917 const float offset = (-event->y + g->area_y) / g->graph_height * 4.0f; // graph spans over 4 EV
2918 const float cursor_exposure = g->area_x / g->graph_width * 8.0f - 8.0f;
2919
2920 // Get the desired correction on exposure channels
2921 g->area_dragging = set_new_params_interactive(cursor_exposure, offset, g->sigma * g->sigma / 2.0f, g, p);
2922 }
2923
2924 g->area_x = (event->x - g->graph_left_space);
2925 g->area_y = event->y;
2926 g->area_cursor_valid = (g->area_x > 0.0f && g->area_x < g->graph_width && g->area_y > 0.0f && g->area_y < g->graph_height);
2927 g->area_active_node = -1;
2928
2929 // Search if cursor is close to a node
2930 if(g->valid_nodes_x)
2931 {
2932 const float radius_threshold = fabsf(g->nodes_x[1] - g->nodes_x[0]) * 0.45f;
2933 for(int i = 0; i < CHANNELS; ++i)
2934 {
2935 const float delta_x = fabsf(g->area_x - g->nodes_x[i]);
2936 if(delta_x < radius_threshold)
2937 {
2938 g->area_active_node = i;
2939 g->area_cursor_valid = 1;
2940 }
2941 }
2942 }
2943
2944 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2945 return TRUE;
2946}
2947
2948
2949static gboolean area_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2950{
2951 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2952 if(darktable.gui->reset) return 1;
2953 if(!self->enabled) return 0;
2954
2956
2957 // Give focus to module
2959
2960 if(event->button == 1)
2961 {
2963
2964 if(g->area_dragging)
2965 {
2966 // Update GUI with new params
2969 g->area_dragging= 0;
2970 return TRUE;
2971 }
2972 }
2973 return FALSE;
2974}
2975
2976
2977static gboolean notebook_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2978{
2979 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2980 if(darktable.gui->reset) return 1;
2981
2982 // Give focus to module
2984
2985 // Unlock the colour picker so we can display our own custom cursor
2987
2988 return 0;
2989}
2990
2996static void _develop_ui_pipe_started_callback(gpointer instance, gpointer user_data)
2997{
2998 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3000 if(IS_NULL_PTR(g)) return;
3001 _switch_cursors(self);
3002
3003 // if module is not active, disable mask preview
3004 if(!self->expanded || !self->enabled)
3005 {
3006 g->mask_display = 0;
3007 }
3008
3009 ++darktable.gui->reset;
3010 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
3011 --darktable.gui->reset;
3012}
3013
3014
3015static void _develop_history_resync_callback(gpointer instance, gpointer user_data)
3016{
3017 (void)instance;
3018 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3020 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->preview_pipe)) return;
3021
3022 const uint64_t preview_hash = _current_preview_luminance_hash(self, NULL, NULL);
3023 if(preview_hash == DT_PIXELPIPE_CACHE_HASH_INVALID)
3024 {
3026 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3028 _switch_cursors(self);
3029 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3030 return;
3031 }
3032
3033 gboolean already_attached = FALSE;
3035 if(!IS_NULL_PTR(g->thumb_preview_entry) && g->thumb_preview_hash == preview_hash && g->luminance_valid)
3036 {
3037 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3038 already_attached = TRUE;
3039 }
3041
3042 if(!already_attached)
3043 {
3044 void *preview_buf = NULL;
3045 dt_pixel_cache_entry_t *preview_entry = NULL;
3046 gboolean preview_ready = dt_dev_pixelpipe_cache_ref_entry_by_hash(darktable.pixelpipe_cache, preview_hash,
3047 &preview_buf, &preview_entry);
3048 if(preview_ready && (IS_NULL_PTR(preview_buf) || IS_NULL_PTR(preview_entry)))
3049 {
3050 if(!IS_NULL_PTR(preview_entry))
3052 preview_ready = FALSE;
3053 }
3054
3055 if(preview_ready)
3056 {
3057 size_t preview_width = 0;
3058 size_t preview_height = 0;
3059 (void)_current_preview_luminance_hash(self, &preview_width, &preview_height);
3060
3061 dt_pixel_cache_entry_t *old_entry = NULL;
3062 gboolean keep_new_entry = FALSE;
3064 if(g->thumb_preview_entry != preview_entry || g->thumb_preview_hash != preview_hash
3065 || g->thumb_preview_buf_width != preview_width || g->thumb_preview_buf_height != preview_height
3066 || !g->luminance_valid)
3067 {
3068 old_entry = g->thumb_preview_entry;
3069 g->thumb_preview_entry = preview_entry;
3070 g->thumb_preview_hash = preview_hash;
3071 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3072 g->thumb_preview_buf_width = preview_width;
3073 g->thumb_preview_buf_height = preview_height;
3074 g->luminance_valid = TRUE;
3075 g->histogram_valid = FALSE;
3076 keep_new_entry = TRUE;
3077 }
3078 else
3079 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3081
3082 if(old_entry)
3084 if(!keep_new_entry)
3086 }
3087 else
3088 {
3090 g->pending_preview_hash = preview_hash;
3092 }
3093 }
3094
3095 _switch_cursors(self);
3096 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3097}
3098
3099static void _develop_cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
3100{
3101 (void)instance;
3102 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3104 if(IS_NULL_PTR(g) || IS_NULL_PTR(self->dev) || IS_NULL_PTR(self->dev->preview_pipe)) return;
3105
3107 const gboolean matched = (g->pending_preview_hash == hash);
3109 if(!matched) return;
3110
3111 size_t preview_width = 0;
3112 size_t preview_height = 0;
3113 const uint64_t preview_hash = _current_preview_luminance_hash(self, &preview_width, &preview_height);
3114 if(preview_hash != hash) return;
3115
3116 void *preview_buf = NULL;
3117 dt_pixel_cache_entry_t *preview_entry = NULL;
3118 const gboolean preview_ready = dt_dev_pixelpipe_cache_ref_entry_by_hash(darktable.pixelpipe_cache, preview_hash,
3119 &preview_buf, &preview_entry);
3120 if(!preview_ready || IS_NULL_PTR(preview_buf) || IS_NULL_PTR(preview_entry))
3121 {
3122 if(!IS_NULL_PTR(preview_entry))
3124 return;
3125 }
3126
3127 dt_pixel_cache_entry_t *old_entry = NULL;
3128 gboolean keep_new_entry = FALSE;
3130 if(g->thumb_preview_entry != preview_entry || g->thumb_preview_hash != preview_hash
3131 || g->thumb_preview_buf_width != preview_width || g->thumb_preview_buf_height != preview_height
3132 || !g->luminance_valid)
3133 {
3134 old_entry = g->thumb_preview_entry;
3135 g->thumb_preview_entry = preview_entry;
3136 g->thumb_preview_hash = preview_hash;
3137 g->thumb_preview_buf_width = preview_width;
3138 g->thumb_preview_buf_height = preview_height;
3139 g->luminance_valid = TRUE;
3140 g->histogram_valid = FALSE;
3141 keep_new_entry = TRUE;
3142 }
3143 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3145
3146 if(old_entry)
3148 if(!keep_new_entry)
3150
3151 _switch_cursors(self);
3152 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3153}
3154
3155
3156static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
3157{
3158 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
3160 if(IS_NULL_PTR(g)) return;
3161 _switch_cursors(self);
3162}
3163
3164
3165void gui_reset(struct dt_iop_module_t *self)
3166{
3168 if(IS_NULL_PTR(g)) return;
3170 dt_bauhaus_widget_set_quad_active(g->exposure_boost, FALSE);
3171 dt_bauhaus_widget_set_quad_active(g->contrast_boost, FALSE);
3173
3174 // Redraw graph
3175 gtk_widget_queue_draw(self->widget);
3176}
3177
3178static gboolean _sample_picker_luminance_mask(const float *const buffer, const size_t width, const size_t height,
3179 float *const picked, float *const picked_min, float *const picked_max)
3180{
3181 const dt_develop_t *const dev = darktable.develop;
3182 const dt_colorpicker_sample_t *const sample = dev ? dev->color_picker.primary_sample : NULL;
3183 if(IS_NULL_PTR(buffer) || IS_NULL_PTR(sample) || width < 1 || height < 1 || IS_NULL_PTR(picked) || IS_NULL_PTR(picked_min) || IS_NULL_PTR(picked_max)) return FALSE;
3184
3185 if(sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
3186 {
3187 const size_t box[4] = {
3188 CLAMP((size_t)roundf(sample->box[0] * width), 0, width),
3189 CLAMP((size_t)roundf(sample->box[1] * height), 0, height),
3190 CLAMP((size_t)roundf(sample->box[2] * width), 0, width),
3191 CLAMP((size_t)roundf(sample->box[3] * height), 0, height)
3192 };
3193 const size_t x0 = MIN(box[0], width - 1);
3194 const size_t y0 = MIN(box[1], height - 1);
3195 const size_t x1 = CLAMP(MAX(box[2], x0 + 1), 1, width);
3196 const size_t y1 = CLAMP(MAX(box[3], y0 + 1), 1, height);
3197
3198 float mean = 0.0f;
3199 float minimum = INFINITY;
3200 float maximum = -INFINITY;
3201 size_t count = 0;
3202
3203 // Browse the exact picker box on the preview luminance mask so picker feedback
3204 // reflects the same scalar field tone equalizer actually edits.
3205 for(size_t y = y0; y < y1; ++y)
3206 {
3207 const size_t row = y * width;
3208 for(size_t x = x0; x < x1; ++x)
3209 {
3210 const float value = buffer[row + x];
3211 mean += value;
3212 minimum = fminf(minimum, value);
3213 maximum = fmaxf(maximum, value);
3214 ++count;
3215 }
3216 }
3217
3218 if(count == 0) return FALSE;
3219 *picked = mean / (float)count;
3220 *picked_min = minimum;
3221 *picked_max = maximum;
3222 return isfinite(*picked) && isfinite(*picked_min) && isfinite(*picked_max);
3223 }
3224
3225 const size_t x = CLAMP((size_t)roundf(sample->point[0] * width), 0, width - 1);
3226 const size_t y = CLAMP((size_t)roundf(sample->point[1] * height), 0, height - 1);
3227 const float value = get_luminance_from_buffer(buffer, width, height, x, y);
3228 *picked = value;
3229 *picked_min = value;
3230 *picked_max = value;
3231 return isfinite(value);
3232}
3233
3256{
3259 dt_pixel_cache_entry_t *preview_entry = NULL;
3260 size_t preview_width = 0;
3261 size_t preview_height = 0;
3262
3263 if(IS_NULL_PTR(g) || (picker != g->exposure_boost && picker != g->contrast_boost))
3264 {
3265 dt_print(DT_DEBUG_DEV, "[picker/toneequal] passthrough picker=%p pipe=%p hash=%" PRIu64 "\n",
3266 (void *)picker, (void *)pipe, piece ? piece->global_hash : 0);
3267 _switch_cursors(self);
3268 return;
3269 }
3270
3271 g->area_active_node = -1;
3272
3273 // Picker callbacks can run while the preview pipe publishes a newer luminance
3274 // cacheline, so take the cache ref under the same GUI state lock as other readers.
3276 preview_entry = g->thumb_preview_entry;
3277 preview_width = g->thumb_preview_buf_width;
3278 preview_height = g->thumb_preview_buf_height;
3279 if(!IS_NULL_PTR(preview_entry))
3282
3283 if(IS_NULL_PTR(preview_entry) || preview_width < 1 || preview_height < 1)
3284 {
3285 if(!IS_NULL_PTR(preview_entry))
3287 dt_print(DT_DEBUG_DEV, "[picker/toneequal] no preview mask picker=%p pipe=%p hash=%" PRIu64 "\n",
3288 (void *)picker, (void *)pipe, piece ? piece->global_hash : 0);
3289 _switch_cursors(self);
3290 return;
3291 }
3292
3294 const float *const preview_buf = (const float *const)dt_pixel_cache_entry_get_data(preview_entry);
3295 float picked = NAN;
3296 float picked_min = NAN;
3297 float picked_max = NAN;
3298 const gboolean sampled = _sample_picker_luminance_mask(preview_buf, preview_width, preview_height,
3299 &picked, &picked_min, &picked_max);
3302
3303 if(!sampled)
3304 {
3305 dt_print(DT_DEBUG_DEV, "[picker/toneequal] mask sample failed picker=%p pipe=%p hash=%" PRIu64 "\n",
3306 (void *)picker, (void *)pipe, piece ? piece->global_hash : 0);
3307 _switch_cursors(self);
3308 return;
3309 }
3310
3311 g->cursor_valid = isfinite(picked) && picked > 0.0f;
3312 g->cursor_exposure = g->cursor_valid ? log2f(picked) : 0.0f;
3313
3314 if(picker == g->exposure_boost)
3315 {
3316 if(isfinite(picked) && picked > 0.0f)
3317 {
3318 p->exposure_boost = log2f(CONTRAST_FULCRUM / picked);
3319 ++darktable.gui->reset;
3320 dt_bauhaus_slider_set(g->exposure_boost, p->exposure_boost);
3321 --darktable.gui->reset;
3325 "[picker/toneequal] exposure picker=%p luminance=%g set=%g pipe=%p hash=%" PRIu64 "\n",
3326 (void *)picker, picked, p->exposure_boost, (void *)pipe, piece ? piece->global_hash : 0);
3327 }
3328 else
3329 {
3331 "[picker/toneequal] exposure picker=%p invalid luminance=%g pipe=%p hash=%" PRIu64 "\n",
3332 (void *)picker, picked, (void *)pipe, piece ? piece->global_hash : 0);
3333 }
3334 }
3335 else
3336 {
3337 const float fd_old = fminf(picked_min, picked_max);
3338 const float ld_old = fmaxf(picked_min, picked_max);
3339
3340 if(isfinite(fd_old) && isfinite(ld_old) && fd_old > 0.0f && ld_old > fd_old)
3341 {
3342 const float s1 = CONTRAST_FULCRUM - exp2f(-7.0f);
3343 const float s2 = exp2f(-1.0f) - CONTRAST_FULCRUM;
3344 const float mix = fd_old * s2 + ld_old * s1;
3345 float contrast = log2f(mix / (CONTRAST_FULCRUM * (ld_old - fd_old)));
3346
3347 // Blur-assisted detail modes need the same positive-contrast correction as
3348 // the legacy auto button because the sampled spread is measured upstream of
3349 // the guided filter blur and would otherwise undershoot in the final mask.
3350 if(p->details == DT_TONEEQ_EIGF && contrast > 0.0f)
3351 {
3352 const float correction = -0.0276f + 0.01823f * p->feathering + (0.7566f - 1.0f) * contrast;
3353 if(p->feathering < 5.0f)
3354 contrast += correction;
3355 else if(p->feathering < 10.0f)
3356 contrast += correction * (2.0f - p->feathering / 5.0f);
3357 }
3358 else if(p->details == DT_TONEEQ_GUIDED && contrast > 0.0f)
3359 {
3360 contrast = 0.0235f + 1.1225f * contrast;
3361 }
3362
3363 p->contrast_boost = contrast;
3364 ++darktable.gui->reset;
3365 dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
3366 --darktable.gui->reset;
3370 "[picker/toneequal] contrast picker=%p min=%g max=%g set=%g pipe=%p hash=%" PRIu64 "\n",
3371 (void *)picker, fd_old, ld_old, p->contrast_boost, (void *)pipe,
3372 piece ? piece->global_hash : 0);
3373 }
3374 else
3375 {
3377 "[picker/toneequal] contrast picker=%p invalid min=%g max=%g pipe=%p hash=%" PRIu64 "\n",
3378 (void *)picker, fd_old, ld_old, (void *)pipe, piece ? piece->global_hash : 0);
3379 }
3380 }
3381
3383 gtk_widget_queue_draw(GTK_WIDGET(g->area));
3384 _switch_cursors(self);
3385}
3386
3387
3388void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
3389 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
3390{
3391 if(piece->dsc_in.channels != 4) return;
3392
3394 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3395 const size_t width = roi_out->width;
3396 const size_t height = roi_out->height;
3397 const size_t num_elem = width * height;
3398 float *luminance = dt_pixelpipe_cache_alloc_align_float(num_elem, pipe);
3399 if(IS_NULL_PTR(luminance)) return;
3400
3401 // Build the same luminance mask scalar field the picker edits, but with neutral
3402 // boost/contrast because autoset can only solve the exposure translation.
3403 luminance_mask((const float *const)i, luminance, width, height, piece->dsc_in.channels, p->method, 1.0f, 0.0f, 1.0f);
3404
3405 float mean = 0.0f;
3406 size_t count = 0;
3407 __OMP_PARALLEL_FOR__(reduction(+:mean, count))
3408 for(size_t k = 0; k < num_elem; ++k)
3409 {
3410 const float value = luminance[k];
3411 if(!isfinite(value) || value <= 0.0f) continue;
3412 mean += value;
3413 count++;
3414 }
3415
3417 if(count == 0) return;
3418
3419 const float picked = mean / (float)count;
3420 p->exposure_boost = log2f(CONTRAST_FULCRUM / picked);
3421}
3422
3423void gui_init(struct dt_iop_module_t *self)
3424{
3426
3427 gui_cache_init(self);
3428
3429 g->notebook = dt_ui_notebook_new();
3430
3431 // Advanced view
3432
3433 self->widget = dt_ui_notebook_page(g->notebook, N_("graph"), NULL);
3434
3435 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
3436 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
3437 gtk_box_pack_start(GTK_BOX(self->widget),
3438 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
3439 "plugins/darkroom/toneequal/graphheight", 280, 120),
3440 FALSE, FALSE, 0);
3441 gtk_widget_add_events(GTK_WIDGET(g->area), GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
3442 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
3443 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
3444 gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
3445 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(area_draw), self);
3446 g_signal_connect(G_OBJECT(g->area), "button-press-event", G_CALLBACK(area_button_press), self);
3447 g_signal_connect(G_OBJECT(g->area), "button-release-event", G_CALLBACK(area_button_release), self);
3448 g_signal_connect(G_OBJECT(g->area), "leave-notify-event", G_CALLBACK(area_leave_notify), self);
3449 g_signal_connect(G_OBJECT(g->area), "enter-notify-event", G_CALLBACK(area_enter_notify), self);
3450 g_signal_connect(G_OBJECT(g->area), "motion-notify-event", G_CALLBACK(area_motion_notify), self);
3451 gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double-click to reset the curve"));
3452
3453 g->smoothing = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), -2.33f, +1.67f, 0, 0.0f, 2);
3454 dt_bauhaus_slider_set_soft_range(g->smoothing, -1.0f, 1.0f);
3455 dt_bauhaus_widget_set_label(g->smoothing, N_("curve smoothing"));
3456 gtk_widget_set_tooltip_text(g->smoothing, _("positive values will produce more progressive tone transitions\n"
3457 "but the curve might become oscillatory in some settings.\n"
3458 "negative values will avoid oscillations and behave more robustly\n"
3459 "but may produce brutal tone transitions and damage local contrast."));
3460 gtk_box_pack_start(GTK_BOX(self->widget), g->smoothing, FALSE, FALSE, 0);
3461 g_signal_connect(G_OBJECT(g->smoothing), "value-changed", G_CALLBACK(smoothing_callback), self);
3462
3463 g->exposure_boost = dt_color_picker_new(self, DT_COLOR_PICKER_AREA,
3464 dt_bauhaus_slider_from_params(self, "exposure_boost"));
3465 dt_bauhaus_slider_set_soft_range(g->exposure_boost, -4.0, 4.0);
3466 dt_bauhaus_slider_set_format(g->exposure_boost, _(" EV"));
3467 gtk_widget_set_tooltip_text(g->exposure_boost, _("use this to slide the mask average exposure along channels\n"
3468 "for a better control of the exposure correction with the available nodes.\n"
3469 "the color picker will map the sampled tone to -4 EV."));
3470
3471 g->contrast_boost = dt_color_picker_new(self, DT_COLOR_PICKER_AREA,
3472 dt_bauhaus_slider_from_params(self, "contrast_boost"));
3473 dt_bauhaus_slider_set_soft_range(g->contrast_boost, -2.0, 2.0);
3474 dt_bauhaus_slider_set_format(g->contrast_boost, _(" EV"));
3475 gtk_widget_set_tooltip_text(g->contrast_boost, _("use this to counter the averaging effect of the guided filter\n"
3476 "and dilate the mask contrast around -4EV\n"
3477 "this allows to spread the exposure histogram over more channels\n"
3478 "for a better control of the exposure correction.\n"
3479 "the color picker will fit the sampled spread inside the control range."));
3480
3481 // Simple view
3482
3483 self->widget = dt_ui_notebook_page(g->notebook, N_("sliders"), NULL);
3484
3485 g->noise = dt_bauhaus_slider_from_params(self, "noise");
3486 dt_bauhaus_slider_set_format(g->noise, _(" EV"));
3487
3488 g->ultra_deep_blacks = dt_bauhaus_slider_from_params(self, "ultra_deep_blacks");
3489 dt_bauhaus_slider_set_format(g->ultra_deep_blacks, _(" EV"));
3490
3491 g->deep_blacks = dt_bauhaus_slider_from_params(self, "deep_blacks");
3492 dt_bauhaus_slider_set_format(g->deep_blacks, _(" EV"));
3493
3494 g->blacks = dt_bauhaus_slider_from_params(self, "blacks");
3495 dt_bauhaus_slider_set_format(g->blacks, _(" EV"));
3496
3497 g->shadows = dt_bauhaus_slider_from_params(self, "shadows");
3498 dt_bauhaus_slider_set_format(g->shadows, _(" EV"));
3499
3500 g->midtones = dt_bauhaus_slider_from_params(self, "midtones");
3501 dt_bauhaus_slider_set_format(g->midtones, _(" EV"));
3502
3503 g->highlights = dt_bauhaus_slider_from_params(self, "highlights");
3504 dt_bauhaus_slider_set_format(g->highlights, _(" EV"));
3505
3506 g->whites = dt_bauhaus_slider_from_params(self, "whites");
3507 dt_bauhaus_slider_set_format(g->whites, _(" EV"));
3508
3509 g->speculars = dt_bauhaus_slider_from_params(self, "speculars");
3510 dt_bauhaus_slider_set_format(g->speculars, _(" EV"));
3511
3512 dt_bauhaus_widget_set_label(g->noise, N_("-8 EV"));
3513 dt_bauhaus_widget_set_label(g->ultra_deep_blacks, N_("-7 EV"));
3514 dt_bauhaus_widget_set_label(g->deep_blacks, N_("-6 EV"));
3515 dt_bauhaus_widget_set_label(g->blacks, N_("-5 EV"));
3516 dt_bauhaus_widget_set_label(g->shadows, N_("-4 EV"));
3517 dt_bauhaus_widget_set_label(g->midtones, N_("-3 EV"));
3518 dt_bauhaus_widget_set_label(g->highlights, N_("-2 EV"));
3519 dt_bauhaus_widget_set_label(g->whites, N_("-1 EV"));
3520 dt_bauhaus_widget_set_label(g->speculars, N_("+0 EV"));
3521
3522 // Masking options
3523
3524 self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
3525
3526 g->method = dt_bauhaus_combobox_from_params(self, "method");
3528 gtk_widget_set_tooltip_text(g->method, _("preview the mask and chose the estimator that gives you the\n"
3529 "higher contrast between areas to dodge and areas to burn"));
3530
3531 g->details = dt_bauhaus_combobox_from_params(self, N_("details"));
3532 dt_bauhaus_widget_set_label(g->details, N_("preserve details"));
3533 gtk_widget_set_tooltip_text(g->details, _("'no' affects global and local contrast (safe if you only add contrast)\n"
3534 "'guided filter' only affects global contrast and tries to preserve local contrast\n"
3535 "'averaged guided filter' is a geometric mean of 'no' and 'guided filter' methods\n"
3536 "'eigf' (exposure-independent guided filter) is a guided filter that is exposure-independent, it smooths shadows and highlights the same way (contrary to guided filter which smooths less the highlights)\n"
3537 "'averaged eigf' is a geometric mean of 'no' and 'exposure-independent guided filter' methods"));
3538
3539 g->iterations = dt_bauhaus_slider_from_params(self, "iterations");
3540 dt_bauhaus_slider_set_soft_max(g->iterations, 5);
3541 gtk_widget_set_tooltip_text(g->iterations, _("number of passes of guided filter to apply\n"
3542 "helps diffusing the edges of the filter at the expense of speed"));
3543
3544 g->blending = dt_bauhaus_slider_from_params(self, "blending");
3545 dt_bauhaus_slider_set_soft_range(g->blending, 1.0, 45.0);
3546 dt_bauhaus_slider_set_format(g->blending, "%");
3547 gtk_widget_set_tooltip_text(g->blending, _("diameter of the blur in percent of the largest image size\n"
3548 "warning: big values of this parameter can make the darkroom\n"
3549 "preview much slower if denoise profiled is used."));
3550
3551 g->feathering = dt_bauhaus_slider_from_params(self, "feathering");
3552 dt_bauhaus_slider_set_soft_range(g->feathering, 0.1, 50.0);
3553 gtk_widget_set_tooltip_text(g->feathering, _("precision of the feathering:\n"
3554 "higher values force the mask to follow edges more closely\n"
3555 "but may void the effect of the smoothing\n"
3556 "lower values give smoother gradients and better smoothing\n"
3557 "but may lead to inaccurate edges taping and halos"));
3558
3559 g->quantization = dt_bauhaus_slider_from_params(self, "quantization");
3560 dt_bauhaus_slider_set_format(g->quantization, _(" EV"));
3561 gtk_widget_set_tooltip_text(g->quantization, _("0 disables the quantization.\n"
3562 "higher values posterize the luminance mask to help the guiding\n"
3563 "produce piece-wise smooth areas when using high feathering values"));
3564
3565 // start building top level widget
3566 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3567
3568 const int active_page = dt_conf_get_int("plugins/darkroom/toneequal/gui_page");
3569 gtk_widget_show(gtk_notebook_get_nth_page(g->notebook, active_page));
3570 gtk_notebook_set_current_page(g->notebook, active_page);
3571
3572 g_signal_connect(G_OBJECT(g->notebook), "button-press-event", G_CALLBACK(notebook_button_press), self);
3573 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
3574
3575 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3576 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("display exposure mask")), TRUE, TRUE, 0);
3577 g->show_luminance_mask = dt_iop_togglebutton_new(self, NULL, N_("display exposure mask"), NULL, G_CALLBACK(show_luminance_mask_callback),
3578 FALSE, 0, 0, dtgtk_cairo_paint_showmask, hbox);
3579
3581 dt_gui_add_class(g->show_luminance_mask, "dt_bauhaus_alignment");
3582 gtk_box_pack_start(GTK_BOX(self->widget), hbox, FALSE, FALSE, 0);
3583
3584 // Force UI redraws when pipe starts/finishes computing and switch cursors
3586 G_CALLBACK(_develop_history_resync_callback), self);
3588 G_CALLBACK(_develop_cacheline_ready_callback), self);
3590 G_CALLBACK(_develop_ui_pipe_finished_callback), self);
3591
3593 G_CALLBACK(_develop_ui_pipe_started_callback), self);
3594}
3595
3596
3598{
3601
3602 dt_conf_set_int("plugins/darkroom/toneequal/gui_page", gtk_notebook_get_current_page (g->notebook));
3603
3608
3609 dt_pixel_cache_entry_t *preview_entry = NULL;
3611 preview_entry = g->thumb_preview_entry;
3612 g->thumb_preview_entry = NULL;
3613 g->thumb_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3614 g->pending_preview_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
3615 g->thumb_preview_buf_width = 0;
3616 g->thumb_preview_buf_height = 0;
3617 g->luminance_valid = FALSE;
3619 if(!IS_NULL_PTR(preview_entry))
3621 if(g->desc) pango_font_description_free(g->desc);
3622 if(g->layout) g_object_unref(g->layout);
3623 if(g->cr) cairo_destroy(g->cr);
3624 if(g->cst) cairo_surface_destroy(g->cst);
3625
3627}
3628
3629// clang-format off
3630// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3631// vim: shiftwidth=2 expandtab tabstop=2 cindent
3632// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3633// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1647
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
void dt_bauhaus_slider_set_soft_max(GtkWidget *widget, float val)
Definition bauhaus.c:1624
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1726
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2101
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
#define INNER_PADDING
Definition bauhaus.h:79
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_SCENE
Definition blend.h:60
static int pseudo_solve(float *const restrict A, float *const restrict y, const size_t m, const size_t n, const int checks)
Definition choleski.h:366
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static const float scaling
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
void dt_collection_hint_message(const dt_collection_t *collection)
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
gboolean dt_iop_color_picker_is_visible(const dt_develop_t *dev)
@ DT_COLOR_PICKER_AREA
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:38
#define A(y, x)
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
const float delta
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:918
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:398
#define dt_control_set_cursor_visible(visible)
Definition control.h:148
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define DT_ALIGNED_PIXEL
Definition darktable.h:389
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define __OMP_SIMD__(...)
Definition darktable.h:262
@ DT_DEBUG_DEV
Definition darktable.h:717
#define for_each_channel(_var,...)
Definition darktable.h:662
static void * dt_check_sse_aligned(void *pointer)
Definition darktable.h:504
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:263
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition darktable.h:1043
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:442
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define __OMP_FOR_SIMD__(...)
Definition darktable.h:260
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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 darktable.h:281
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
#define dt_dev_pixelpipe_update_history_main(dev)
#define dt_dev_pixelpipe_update_history_preview(dev)
dt_dev_pixelpipe_iop_t * dt_dev_distort_get_iop_pipe(struct dt_dev_pixelpipe_t *pipe, struct dt_iop_module_t *module)
Definition develop.c:1593
float dt_dev_get_overlay_scale(dt_develop_t *dev)
Get the overlay scale factor in GUI logical coordinates.
Definition develop.c:1712
void dt_dev_coordinates_image_norm_to_preview_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1144
gboolean dt_dev_rescale_roi(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit within given width/height, centered.
Definition develop.c:1824
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:367
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1003
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:136
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:117
static void dt_draw_grid(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:143
static void dt_cairo_perceptual_gradient(cairo_pattern_t *grad, double alpha)
Definition draw.h:491
void dtgtk_cairo_paint_gamut_check(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
static __DT_CLONE_TARGETS__ int fast_eigf_surface_blur(float *const restrict image, const size_t width, const size_t height, const float sigma, float feathering, const int iterations, const dt_iop_guided_filter_blending_t filter, const float scale, const float quantization, const float quantize_min, const float quantize_max)
Definition eigf.h:260
static __DT_CLONE_TARGETS__ int fast_surface_blur(float *const restrict image, const size_t width, const size_t height, const int radius, float feathering, const int iterations, const dt_iop_guided_filter_blending_t filter, const float scale, const float quantization, const float quantize_min, const float quantize_max)
@ DT_GF_BLENDING_LINEAR
@ DT_GF_BLENDING_GEOMEAN
static float fast_clamp(const float value, const float bottom, const float top)
void default_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 format.c:57
@ TYPE_FLOAT
Definition format.h:46
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
Definition gtk.c:2836
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2259
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2254
void dt_gui_set_pango_resolution(PangoLayout *layout)
Definition gtk.c:1467
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled, const dt_develop_blend_colorspace_t blend_cst)
#define DT_GUI_MODULE(x)
static __DT_CLONE_TARGETS__ void dt_simd_memcpy(const float *const __restrict__ in, float *const __restrict__ out, const size_t num_elem)
Definition imagebuf.h:68
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:2915
@ DT_REQUEST_COLORPICK_OFF
Definition imageop.h:196
#define dt_omploop_sfence()
Definition imageop.h:702
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_TONES
Definition imageop.h:137
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
Definition imageop_gui.c:77
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
void *const ovoid
int dt_ioppr_get_iop_order(GList *iop_order_list, const char *op_name, const int multi_priority)
Return the iop_order for a given operation/instance pair.
Definition iop_order.c:868
static const float x
const float *const lut
static float mix(const float a, const float b, const float t)
Definition liquify.c:705
float *const restrict const size_t const size_t const float exposure_boost
dt_iop_luminance_mask_method_t
@ DT_TONEEQ_NORM_2
@ DT_TONEEQ_LAST
@ DT_TONEEQ_NORM_POWER
float *const restrict luminance
float *const restrict const size_t const size_t const float const float const float contrast_boost
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
static const int max_size
Definition map.c:130
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
#define M_PI
Definition math.h:45
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
void * dt_pixel_cache_entry_get_data(dt_pixel_cache_entry_t *entry)
void dt_dev_pixelpipe_cache_ref_count_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
int dt_dev_pixelpipe_cache_get(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, const size_t size, const char *name, const int id, const gboolean alloc, void **data, dt_pixel_cache_entry_t **entry)
Get a cache line from the cache.
int dt_dev_pixelpipe_cache_remove(dt_dev_pixelpipe_cache_t *cache, const gboolean force, dt_pixel_cache_entry_t *cache_entry)
Arbitrarily remove the cache entry matching hash. Entries having a reference count > 0 (inter-thread ...
gboolean dt_dev_pixelpipe_cache_ref_entry_by_hash(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cache entry by hash.
void dt_dev_pixelpipe_cache_wrlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the write lock on the entry.
void dt_dev_pixelpipe_cache_rdlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ DT_SIGNAL_DEVELOP_HISTORY_CHANGE
This signal is raised when develop history is changed no param, no returned value.
Definition signal.h:204
@ DT_SIGNAL_HISTORY_RESYNC
This signal is raised once darkroom history has been resynchronized into all live pipelines....
Definition signal.h:209
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:185
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:179
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float sigma
const float noise
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:790
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_collection_t * collection
Definition darktable.h:781
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_control_t * control
Definition darktable.h:773
GdkRGBA graph_bg
Definition bauhaus.h:281
GdkRGBA graph_border
Definition bauhaus.h:281
PangoFontDescription * pango_font_desc
Definition bauhaus.h:274
GdkRGBA color_fill
Definition bauhaus.h:277
GdkRGBA inset_histogram
Definition bauhaus.h:282
float quad_width
Definition bauhaus.h:273
GdkRGBA graph_fg
Definition bauhaus.h:281
dt_lib_colorpicker_size_t size
Definition colorpicker.h:63
dt_boundingbox_t box
Definition colorpicker.h:62
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:162
GList * iop_order_list
Definition develop.h:285
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
struct dt_colorpicker_sample_t * primary_sample
Definition develop.h:391
int32_t preview_height
Definition develop.h:213
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
struct dt_masks_form_gui_t * form_gui
Definition develop.h:327
struct dt_develop_t::@17 roi
int32_t preview_width
Definition develop.h:213
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
gint scroll_mask
Definition gtk.h:224
int32_t reset
Definition gtk.h:172
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
GModule *dt_dev_operation_t op
Definition imageop.h:230
dt_iop_global_data_t * data
Definition imageop.h:233
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:264
GtkDarktableToggleButton * off
Definition imageop.h:339
dt_iop_params_t * default_params
Definition imageop.h:307
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
gboolean expanded
Definition imageop.h:346
gboolean enabled
Definition imageop.h:298
int request_mask_display
Definition imageop.h:268
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
float factors[8] DT_ALIGNED_ARRAY
Definition toneequal.c:208
dt_iop_toneequalizer_filter_t details
Definition toneequal.c:215
float correction_lut[8 *10000+1] DT_ALIGNED_ARRAY
Definition toneequal.c:209
dt_iop_luminance_mask_method_t method
Definition toneequal.c:214
int histogram[256] DT_ALIGNED_ARRAY
Definition toneequal.c:231
dt_pixel_cache_entry_t * thumb_preview_entry
Definition toneequal.c:263
float interpolation_matrix[9 *8] DT_ALIGNED_ARRAY
Definition toneequal.c:230
float factors[8] DT_ALIGNED_ARRAY
Definition toneequal.c:228
float temp_user_params[9] DT_ALIGNED_ARRAY
Definition toneequal.c:232
float gui_lut[256] DT_ALIGNED_ARRAY
Definition toneequal.c:229
PangoFontDescription * desc
Definition toneequal.c:294
dt_iop_toneequalizer_filter_t details
Definition toneequal.c:200
dt_iop_luminance_mask_method_t method
Definition toneequal.c:201
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
static void get_shade_from_luminance(cairo_t *cr, const float luminance, const float alpha)
Definition toneequal.c:2157
static void draw_exposure_cursor(cairo_t *cr, const double pointerx, const double pointery, const double radius, const float luminance, const float zoom_scale, const int instances, const float alpha)
Definition toneequal.c:2166
static void smoothing_callback(GtkWidget *slider, gpointer user_data)
Definition toneequal.c:1745
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)
Definition toneequal.c:1579
static gboolean area_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition toneequal.c:2632
static gboolean area_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition toneequal.c:2851
static int compute_luminance_mask(const float *const restrict in, float *const restrict luminance, const size_t width, const size_t height, const size_t ch, const dt_iop_toneequalizer_data_t *const d)
Definition toneequal.c:808
const char ** description(struct dt_iop_module_t *self)
Definition toneequal.c:335
int default_group()
Definition toneequal.c:344
void cairo_draw_hatches(cairo_t *cr, double center[2], double span[2], int instances, double line_width, double shade)
Definition toneequal.c:2134
static gboolean area_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition toneequal.c:2824
static float pixel_correction(const float exposure, const float *const restrict factors, const float sigma)
Definition toneequal.c:791
int scrolled(struct dt_iop_module_t *self, double x, double y, int up, uint32_t state)
Definition toneequal.c:2030
#define CHANNELS
Definition toneequal.c:155
static __DT_CLONE_TARGETS__ void compute_log_histogram_and_stats(const float *const restrict luminance, int histogram[256], const size_t num_elem, int *max_histogram, float *first_decile, float *last_decile)
Definition toneequal.c:1353
static void get_channels_gains(float factors[9], const dt_iop_toneequalizer_params_t *p)
Definition toneequal.c:1191
static void _develop_history_resync_callback(gpointer instance, gpointer user_data)
Definition toneequal.c:3015
static void match_color_to_background(cairo_t *cr, const float exposure, const float alpha)
Definition toneequal.c:2190
#define TEMP_SAMPLES
static void build_interpolation_matrix(float A[9 *8], const float sigma)
Definition toneequal.c:1338
#define PIXEL_CHAN
Definition toneequal.c:156
static gboolean in_mask_editing(dt_iop_module_t *self)
Definition toneequal.c:573
void show_guiding_controls(struct dt_iop_module_t *self)
Definition toneequal.c:1651
const char * aliases()
Definition toneequal.c:329
static void init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
Definition toneequal.c:2619
static gboolean update_curve_lut(struct dt_iop_module_t *self)
Definition toneequal.c:1513
static void _switch_cursors(struct dt_iop_module_t *self)
Definition toneequal.c:1801
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition toneequal.c:2386
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition toneequal.c:1638
static const float centers_ops[8] DT_ALIGNED_ARRAY
Definition toneequal.c:160
static __DT_CLONE_TARGETS__ void compute_lut_correction(struct dt_iop_toneequalizer_gui_data_t *g, const float offset, const float scaling)
Definition toneequal.c:1491
static int set_new_params_interactive(const float control_exposure, const float exposure_offset, const float blending_sigma, dt_iop_toneequalizer_gui_data_t *g, dt_iop_toneequalizer_params_t *p)
Definition toneequal.c:1977
const char * name()
Definition toneequal.c:324
void gui_reset(struct dt_iop_module_t *self)
Definition toneequal.c:3165
#define CONTRAST_FULCRUM
Definition toneequal.c:147
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition toneequal.c:1709
static void compress_shadows_highlight_preset_set_exposure_params(dt_iop_toneequalizer_params_t *p, const float step)
Definition toneequal.c:415
void gui_init(struct dt_iop_module_t *self)
Definition toneequal.c:3423
#define UI_SAMPLES
Definition toneequal.c:146
static void dilate_shadows_highlight_preset_set_exposure_params(dt_iop_toneequalizer_params_t *p, const float step)
Definition toneequal.c:432
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition toneequal.c:1722
static void init_nodes_x(dt_iop_toneequalizer_gui_data_t *g)
Definition toneequal.c:2605
static void _develop_cacheline_ready_callback(gpointer instance, const guint64 hash, gpointer user_data)
Definition toneequal.c:3099
static int compute_channels_gains(const float in[9], float out[9])
Definition toneequal.c:1244
static void compute_correction_lut(float *restrict lut, const float sigma, const float *const restrict factors)
Definition toneequal.c:1173
static const dt_colormatrix_t gauss_kernel
Definition toneequal.c:641
void cleanup_global(dt_iop_module_so_t *module)
Definition toneequal.c:1573
static void gui_cache_init(struct dt_iop_module_t *self)
Definition toneequal.c:1279
static void update_histogram(struct dt_iop_module_t *const self)
Definition toneequal.c:1423
static uint64_t _current_preview_luminance_hash(dt_iop_module_t *self, size_t *width, size_t *height)
Definition toneequal.c:1320
static void _develop_ui_pipe_started_callback(gpointer instance, gpointer user_data)
Definition toneequal.c:2996
static gboolean area_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition toneequal.c:2806
void update_exposure_sliders(dt_iop_toneequalizer_gui_data_t *g, dt_iop_toneequalizer_params_t *p)
Definition toneequal.c:1693
static gboolean area_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition toneequal.c:2949
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition toneequal.c:354
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 toneequal.c:359
static gboolean area_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition toneequal.c:2905
int flags()
Definition toneequal.c:349
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition toneequal.c:2205
void gui_cleanup(struct dt_iop_module_t *self)
Definition toneequal.c:3597
dt_iop_toneequalizer_filter_t
Definition toneequal.c:174
@ DT_TONEEQ_AVG_GUIDED
Definition toneequal.c:176
@ DT_TONEEQ_NONE
Definition toneequal.c:175
@ DT_TONEEQ_EIGF
Definition toneequal.c:179
@ DT_TONEEQ_GUIDED
Definition toneequal.c:177
@ DT_TONEEQ_AVG_EIGF
Definition toneequal.c:178
void init_presets(dt_iop_module_so_t *self)
Definition toneequal.c:447
int mouse_leave(struct dt_iop_module_t *self)
Definition toneequal.c:1959
static gboolean notebook_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition toneequal.c:2977
static gboolean _sample_picker_luminance_mask(const float *const buffer, const size_t width, const size_t height, float *const picked, float *const picked_min, float *const picked_max)
Definition toneequal.c:3178
static void get_channels_factors(float factors[9], const dt_iop_toneequalizer_params_t *p)
Definition toneequal.c:1208
static void _develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition toneequal.c:3156
static __DT_CLONE_TARGETS__ void display_luminance_mask(const float *const restrict in, const float *const restrict luminance, float *const restrict out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_dev_pixelpipe_t *pipe, const size_t ch)
Definition toneequal.c:883
static gboolean _init_drawing(dt_iop_module_t *const restrict self, GtkWidget *widget, dt_iop_toneequalizer_gui_data_t *const restrict g)
Definition toneequal.c:2479
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition toneequal.c:1645
static float get_luminance_from_buffer(const float *const buffer, const size_t width, const size_t height, const size_t x, const size_t y)
Definition toneequal.c:646
void init_global(dt_iop_module_so_t *module)
Definition toneequal.c:1564
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
Definition toneequal.c:3388
static int commit_channels_gains(const float factors[9], dt_iop_toneequalizer_params_t *p)
Definition toneequal.c:1258
int mouse_moved(struct dt_iop_module_t *self, double x, double y, double pressure, int which)
Definition toneequal.c:1866
static int compute_channels_factors(const float factors[8], float out[9], const float sigma)
Definition toneequal.c:1222
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Update tone equalizer sliders from one picker sample.
Definition toneequal.c:3254
static void show_luminance_mask_callback(GtkWidget *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition toneequal.c:1770
void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition toneequal.c:1116
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition toneequal.c:367
#define LUT_RESOLUTION
Definition toneequal.c:157
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid)
Definition toneequal.c:1107
static void invalidate_luminance_cache(dt_iop_module_t *const self)
Definition toneequal.c:579
static __DT_CLONE_TARGETS__ void apply_toneequalizer(const float *const restrict in, const float *const restrict luminance, float *const restrict out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const size_t ch, const dt_iop_toneequalizer_data_t *const d)
Definition toneequal.c:756