Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
levels.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2013, 2016, 2018 johannes hanika.
4 Copyright (C) 2010 Alexandre Prokoudine.
5 Copyright (C) 2010-2011 Bruce Guenter.
6 Copyright (C) 2010-2012 Henrik Andersson.
7 Copyright (C) 2010, 2012-2016, 2018-2019 Tobias Ellinghaus.
8 Copyright (C) 2011 Jochen Schroeder.
9 Copyright (C) 2011 Olivier Tribout.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011 Rostyslav Pidgornyi.
12 Copyright (C) 2011-2014, 2016-2017 Ulrich Pegelow.
13 Copyright (C) 2012 Christian Tellefsen.
14 Copyright (C) 2012 José Carlos García Sogo.
15 Copyright (C) 2012, 2014 parafin.
16 Copyright (C) 2012 Richard Wonka.
17 Copyright (C) 2013 Dennis Gnad.
18 Copyright (C) 2013-2016 Roman Lebedev.
19 Copyright (C) 2014, 2018-2022 Pascal Obry.
20 Copyright (C) 2015 Pedro Côrte-Real.
21 Copyright (C) 2017-2018, 2020-2021 Dan Torop.
22 Copyright (C) 2017 Heiko Bauke.
23 Copyright (C) 2018-2019, 2022-2026 Aurélien PIERRE.
24 Copyright (C) 2018-2019 Edgardo Hoszowski.
25 Copyright (C) 2018 Maurizio Paglia.
26 Copyright (C) 2018 rawfiner.
27 Copyright (C) 2019 Andreas Schneider.
28 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
29 Copyright (C) 2019 Diederik ter Rahe.
30 Copyright (C) 2019 emeikei.
31 Copyright (C) 2020 Aldric Renaudin.
32 Copyright (C) 2020-2021 Chris Elston.
33 Copyright (C) 2020-2021 Ralf Brown.
34 Copyright (C) 2021 Hubert Kowalski.
35 Copyright (C) 2021 lhietal.
36 Copyright (C) 2022 Hanno Schwalm.
37 Copyright (C) 2022 Martin Bařinka.
38 Copyright (C) 2022 Philipp Lutz.
39
40 darktable is free software: you can redistribute it and/or modify
41 it under the terms of the GNU General Public License as published by
42 the Free Software Foundation, either version 3 of the License, or
43 (at your option) any later version.
44
45 darktable is distributed in the hope that it will be useful,
46 but WITHOUT ANY WARRANTY; without even the implied warranty of
47 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 GNU General Public License for more details.
49
50 You should have received a copy of the GNU General Public License
51 along with darktable. If not, see <http://www.gnu.org/licenses/>.
52*/
53#ifdef HAVE_CONFIG_H
54#include "config.h"
55#endif
56#include <assert.h>
57#include <math.h>
58#include <stdint.h>
59#include <stdlib.h>
60#include <string.h>
61
62#include "bauhaus/bauhaus.h"
63#include "common/colorspaces.h"
64#include "common/opencl.h"
65#include "control/control.h"
66#include "develop/develop.h"
67#include "develop/imageop.h"
69#include "develop/imageop_gui.h"
71#include "dtgtk/drawingarea.h"
72#include "gui/draw.h"
73#include "gui/gtk.h"
74
75#include "gui/presets.h"
76#include "iop/iop_api.h"
77#include "libs/colorpicker.h"
78
79#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(5)
80#define DT_GUI_CURVE_INFL .3f
81
83
84static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data);
85static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
86static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
87static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
88static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
89static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data);
90static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self);
91//static void dt_iop_levels_mode_callback(GtkWidget *combo, gpointer user_data);
92//static void dt_iop_levels_percentiles_callback(GtkWidget *slider, gpointer user_data);
93
95{
96 LEVELS_MODE_MANUAL, // $DESCRIPTION: "manual"
97 LEVELS_MODE_AUTOMATIC // $DESCRIPTION: "automatic"
99
101{
102 dt_iop_levels_mode_t mode; // $DEFAULT: LEVELS_MODE_MANUAL
103 float black; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 0.0
104 float gray; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0
105 float white; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0
106 float levels[3];
108
127
136
137const char *name()
138{
139 return _("levels (legacy)");
140}
141
143{
144 return IOP_GROUP_TONES;
145}
146
151
153{
154 return IOP_CS_LAB;
155}
156
159{
160 default_input_format(self, pipe, piece, dsc);
161 dsc->channels = 4;
162 dsc->datatype = TYPE_FLOAT;
163}
164
165const char **description(struct dt_iop_module_t *self)
166{
167 return dt_iop_set_description(self, _("adjust black, white and mid-gray points"),
168 _("creative"),
169 _("linear or non-linear, Lab, display-referred"),
170 _("non-linear, Lab"),
171 _("non-linear, Lab, display-referred"));
172}
173
174int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
175 void *new_params, const int new_version)
176{
177 if(old_version == 1 && new_version == 2)
178 {
179 typedef struct dt_iop_levels_params_v1_t
180 {
181 float levels[3];
182 int levels_preset;
183 } dt_iop_levels_params_v1_t;
184
185 dt_iop_levels_params_v1_t *o = (dt_iop_levels_params_v1_t *)old_params;
188
189 *n = *d; // start with a fresh copy of default parameters
190
191 n->levels[0] = o->levels[0];
192 n->levels[1] = o->levels[1];
193 n->levels[2] = o->levels[2];
194 return 0;
195 }
196 return 1;
197}
198
199static void dt_iop_levels_compute_levels_manual(const uint32_t *histogram, float *levels)
200{
201 if(IS_NULL_PTR(histogram)) return;
202
203 // search histogram for min (search from bottom)
204 for(int k = 0; k <= 4 * 255; k += 4)
205 {
206 if(histogram[k] > 1)
207 {
208 levels[0] = ((float)(k) / (4 * 256));
209 break;
210 }
211 }
212 // then for max (search from top)
213 for(int k = 4 * 255; k >= 0; k -= 4)
214 {
215 if(histogram[k] > 1)
216 {
217 levels[2] = ((float)(k) / (4 * 256));
218 break;
219 }
220 }
221 levels[1] = levels[0] / 2 + levels[2] / 2;
222}
223
226 const dt_dev_pixelpipe_iop_t *piece)
227{
229 dt_dev_histogram_stats_t histogram_stats = { 0 };
230 uint32_t *histogram = NULL;
231
233 histogram_stats = self->histogram_stats;
234 if(self->histogram && histogram_stats.bins_count > 0)
235 {
236 const size_t histogram_size = 4 * histogram_stats.bins_count * sizeof(uint32_t);
237 histogram = dt_alloc_align(histogram_size);
238 if(!IS_NULL_PTR(histogram)) memcpy(histogram, self->histogram, histogram_size);
239 }
241
242 uint32_t total = histogram_stats.pixels;
243
245 for(int k = 0; k < 3; k++)
246 {
247 thr[k] = (float)total * d->percentiles[k] / 100.0f;
248 d->levels[k] = NAN;
249 }
250
251 if(IS_NULL_PTR(histogram)) return;
252
253 // find min and max levels
254 size_t n = 0;
255 for(uint32_t i = 0; i < histogram_stats.bins_count; i++)
256 {
257 n += histogram[4 * i];
258
259 for(int k = 0; k < 3; k++)
260 {
261 if(isnan(d->levels[k]) && (n >= thr[k]))
262 {
263 d->levels[k] = (float)i / (float)(histogram_stats.bins_count - 1);
264 }
265 }
266 }
267
268 dt_free_align(histogram);
269
270 // for numerical reasons sometimes the threshold is sharp but in float and n is size_t.
271 // in this case we want to make sure we don't keep nan:
272 if(isnan(d->levels[2])) d->levels[2] = 1.0f;
273
274 // compute middle level from min and max levels
275 float center = d->percentiles[1] / 100.0f;
276 if(!isnan(d->levels[0]) && !isnan(d->levels[2]))
277 d->levels[1] = (1.0f - center) * d->levels[0] + center * d->levels[2];
278}
279
281static void compute_lut(const dt_dev_pixelpipe_iop_t *piece)
282{
284
285 // Building the lut for values in the [0,1] range
286 float delta = (d->levels[2] - d->levels[0]) / 2.0f;
287 float mid = d->levels[0] + delta;
288 float tmp = (d->levels[1] - mid) / delta;
289 d->in_inv_gamma = pow(10, tmp);
290
291 for(unsigned int i = 0; i < 0x10000; i++)
292 {
293 float percentage = (float)i / (float)0x10000ul;
294 d->lut[i] = 100.0f * powf(percentage, d->in_inv_gamma);
295 }
296}
297
299{
300 (void)pipe;
301 (void)piece;
304
305 /* we need to save the last picked color to prevent flickering when
306 * changing from one picker to another, as the picked_color value does not
307 * update as rapidly */
308
309 float mean_picked_color = *self->picked_color / 100.0;
310
311 if(mean_picked_color != c->last_picked_color)
312 {
313 dt_aligned_pixel_t previous_color;
314 previous_color[0] = p->levels[0];
315 previous_color[1] = p->levels[1];
316 previous_color[2] = p->levels[2];
317
318 c->last_picked_color = mean_picked_color;
319
320 if(picker == c->blackpick)
321 {
322 if(mean_picked_color > p->levels[1])
323 {
324 p->levels[0] = p->levels[1] - FLT_EPSILON;
325 }
326 else
327 {
328 p->levels[0] = mean_picked_color;
329 }
330 }
331 else if(picker == c->greypick)
332 {
333 if(mean_picked_color < p->levels[0] || mean_picked_color > p->levels[2])
334 {
335 p->levels[1] = p->levels[1];
336 }
337 else
338 {
339 p->levels[1] = mean_picked_color;
340 }
341 }
342 else if(picker == c->whitepick)
343 {
344 if(mean_picked_color < p->levels[1])
345 {
346 p->levels[2] = p->levels[1] + FLT_EPSILON;
347 }
348 else
349 {
350 p->levels[2] = mean_picked_color;
351 }
352 }
353
354 if(previous_color[0] != p->levels[0]
355 || previous_color[1] != p->levels[1]
356 || previous_color[2] != p->levels[2])
357 {
359 }
360 }
361}
362
363/*
364 * WARNING: unlike commit_params, which is thread safe wrt gui thread and
365 * pipes, this function lives in the pipeline thread, and NOT thread safe!
366 */
367static inline __attribute__((always_inline)) void commit_params_late(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
368 const dt_dev_pixelpipe_iop_t *piece)
369{
372
373 if(d->mode == LEVELS_MODE_AUTOMATIC)
374 {
375 if(!IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out))
376 {
378 const uint64_t hash = g->hash;
380
381 // note that the case 'hash == 0' on first invocation in a session implies that d->levels[]
382 // contains NANs which initiates special handling below to avoid inconsistent results. in all
383 // other cases we make sure that the preview pipe has left us with proper readings for
384 // g->auto_levels[]. if data are not yet there we need to wait (with timeout).
385 if(hash != piece->global_hash)
386 dt_control_log(_("inconsistent output"));
387
389 d->levels[0] = g->auto_levels[0];
390 d->levels[1] = g->auto_levels[1];
391 d->levels[2] = g->auto_levels[2];
393
394 compute_lut(piece);
395 }
396
397 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out)
398 || isnan(d->levels[0]) || isnan(d->levels[1])
399 || isnan(d->levels[2]))
400 {
402 compute_lut(piece);
403 }
404
405 if(!IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out)
406 && d->mode == LEVELS_MODE_AUTOMATIC)
407 {
408 uint64_t hash = piece->global_hash;
410 g->auto_levels[0] = d->levels[0];
411 g->auto_levels[1] = d->levels[1];
412 g->auto_levels[2] = d->levels[2];
413 g->hash = hash;
415 }
416 }
417}
418
421 const void *const ivoid, void *const ovoid)
422{
423 const dt_iop_roi_t *const roi_out = &piece->roi_out;
424 const int ch = 4;
425 const dt_iop_levels_data_t *const d = (dt_iop_levels_data_t *)piece->data;
426 (void)pipe;
427
428 if(d->mode == LEVELS_MODE_AUTOMATIC)
429 {
430 commit_params_late(self, pipe, piece);
431 }
432
433 const float *const restrict in = (float*)ivoid;
434 float *const restrict out = (float*)ovoid;
435 const size_t npixels = (size_t)roi_out->width * roi_out->height;
437 for(int i = 0; i < ch * npixels; i += ch)
438 {
439 const float L_in = in[i] / 100.0f;
440 float L_out;
441 if(L_in <= d->levels[0])
442 {
443 // Anything below the lower threshold just clips to zero
444 L_out = 0.0f;
445 }
446 else
447 {
448 const float percentage = (L_in - d->levels[0]) / (d->levels[2] - d->levels[0]);
449 // Within the expected input range we can use the lookup table, else we need to compute from scratch
450 L_out = percentage < 1.0f ? d->lut[(int)(percentage * 0x10000ul)] : 100.0f * powf(percentage, d->in_inv_gamma);
451 }
452
453 // Preserving contrast
454 const float denom = (in[i] > 0.01f) ? in[i] : 0.01f;
455 out[i] = L_out;
456 out[i+1] = in[i+1] * L_out / denom;
457 out[i+2] = in[i+2] * L_out / denom;
458 }
459
461 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
462 return 0;
463}
464
465// void init_presets (dt_iop_module_so_t *self)
466//{
467// dt_iop_levels_params_t p;
468// p.levels_preset = 0;
469//
470// p.levels[0] = 0;
471// p.levels[1] = 0.5;
472// p.levels[2] = 1;
473// dt_gui_presets_add_generic(_("unmodified"), self->op, self->version(), &p, sizeof(p), 1);
474//}
475
478{
481
482 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
484 else
485 piece->request_histogram &= ~(DT_REQUEST_ON);
486
488
489 piece->histogram_params.bins_count = 256;
490
491 if(p->mode == LEVELS_MODE_AUTOMATIC)
492 {
493 d->mode = LEVELS_MODE_AUTOMATIC;
494
497
499
500 piece->histogram_params.bins_count = 16384;
501
502 /*
503 * in principle, we do not need/want histogram in FULL pipe
504 * because we will use histogram from preview pipe there,
505 * but it might happen that for some reasons we do not have
506 * histogram of preview pipe yet - e.g. on first pipe run
507 * (just after setting mode to automatic)
508 */
509
510 d->percentiles[0] = p->black;
511 d->percentiles[1] = p->gray;
512 d->percentiles[2] = p->white;
513
514 d->levels[0] = NAN;
515 d->levels[1] = NAN;
516 d->levels[2] = NAN;
517
518 // commit_params_late() will compute LUT later
519 }
520 else
521 {
522 d->mode = LEVELS_MODE_MANUAL;
523
525
526 d->levels[0] = p->levels[0];
527 d->levels[1] = p->levels[1];
528 d->levels[2] = p->levels[2];
529 compute_lut(piece);
530 }
531}
532
534{
535 piece->data = dt_calloc_align(sizeof(dt_iop_levels_data_t));
536 piece->data_size = sizeof(dt_iop_levels_data_t);
537}
538
540{
541 // clean up everything again.
542 dt_free_align(piece->data);
543 piece->data = NULL;
544}
545
546void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
547{
550
551 if(w == g->mode)
552 {
553 /* blackpick/greypick/whitepick live on the "manual" stack page. Switching mode
554 can hide that page while a picker stays active, sampling/drawing on the image
555 for a control the user can no longer see or turn off from this widget. */
557
558 if(p->mode == LEVELS_MODE_AUTOMATIC)
559 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "automatic");
560 else
561 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "manual");
562 }
563}
564
566{
569
570 dt_bauhaus_combobox_set(g->mode, p->mode);
571
572 gui_changed(self, g->mode, 0);
573
575 g->auto_levels[0] = NAN;
576 g->auto_levels[1] = NAN;
577 g->auto_levels[2] = NAN;
578 g->hash = 0;
580
581 gtk_widget_queue_draw(self->widget);
582}
583
585{
586 dt_iop_default_init(module);
587
588 module->request_histogram |= (DT_REQUEST_ON);
589
590 dt_iop_levels_params_t *d = module->default_params;
591
592 d->levels[0] = 0.0f;
593 d->levels[1] = 0.5f;
594 d->levels[2] = 1.0f;
595}
596
598{
600
602 c->auto_levels[0] = NAN;
603 c->auto_levels[1] = NAN;
604 c->auto_levels[2] = NAN;
605 c->hash = 0;
607
608 c->modes = NULL;
609
610 c->mouse_x = c->mouse_y = -1.0;
611 c->dragging = 0;
612 c->activeToggleButton = NULL;
613 c->last_picked_color = -1;
614
615 c->mode_stack = gtk_stack_new();
616 gtk_stack_set_homogeneous(GTK_STACK(c->mode_stack),FALSE);
617
618 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
619 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
620 GtkWidget *vbox_manual = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
621 gtk_box_pack_start(GTK_BOX(vbox_manual),
622 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
623 "plugins/darkroom/levels/graphheight", 280, 100),
624 FALSE, FALSE, 0);
625
626 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area),_("drag handles to set black, gray, and white points. "
627 "operates on L channel."));
628
629 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
630 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
631 | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
632 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_levels_area_draw), self);
633 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_levels_button_press), self);
634 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(dt_iop_levels_button_release), self);
635 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_levels_motion_notify), self);
636 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_levels_leave_notify), self);
637 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(dt_iop_levels_scroll), self);
638
639 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
640
641 GtkWidget *autobutton = gtk_button_new_with_label(_("auto"));
642 gtk_widget_set_tooltip_text(autobutton, _("apply auto levels"));
643 g_signal_connect(G_OBJECT(autobutton), "clicked", G_CALLBACK(dt_iop_levels_autoadjust_callback), self);
644
645 c->blackpick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
646 gtk_widget_set_tooltip_text(c->blackpick, _("pick black point from image"));
647 gtk_widget_set_name(GTK_WIDGET(c->blackpick), "picker-black");
648
649 c->greypick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
650 gtk_widget_set_tooltip_text(c->greypick, _("pick medium gray point from image"));
651 gtk_widget_set_name(GTK_WIDGET(c->greypick), "picker-grey");
652
653 c->whitepick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
654 gtk_widget_set_tooltip_text(c->whitepick, _("pick white point from image"));
655 gtk_widget_set_name(GTK_WIDGET(c->whitepick), "picker-white");
656
657 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(autobutton ), TRUE, TRUE, 0);
658 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->blackpick), TRUE, TRUE, 0);
659 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->greypick ), TRUE, TRUE, 0);
660 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->whitepick), TRUE, TRUE, 0);
661 gtk_box_pack_start(GTK_BOX(vbox_manual), box, TRUE, TRUE, 0);
662
663 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_manual, "manual");
664
665 GtkWidget *vbox_automatic = self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
666
667 c->percentile_black = dt_bauhaus_slider_from_params(self, N_("black"));
668 gtk_widget_set_tooltip_text(c->percentile_black, _("black percentile"));
669 dt_bauhaus_slider_set_format(c->percentile_black, "%");
670
671 c->percentile_grey = dt_bauhaus_slider_from_params(self, N_("gray"));
672 gtk_widget_set_tooltip_text(c->percentile_grey, _("gray percentile"));
673 dt_bauhaus_slider_set_format(c->percentile_grey, "%");
674
675 c->percentile_white = dt_bauhaus_slider_from_params(self, N_("white"));
676 gtk_widget_set_tooltip_text(c->percentile_white, _("white percentile"));
677 dt_bauhaus_slider_set_format(c->percentile_white, "%");
678
679 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_automatic, "automatic");
680
681 // start building top level widget
682 self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
683
684 c->mode = dt_bauhaus_combobox_from_params(self, N_("mode"));
685
686 gtk_box_pack_start(GTK_BOX(self->widget), c->mode_stack, TRUE, TRUE, 0);
687}
688
690{
692 g_list_free(g->modes);
693 g->modes = NULL;
694
696}
697
698static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
699{
700 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
702 c->mouse_x = c->mouse_y = -1.0;
703 gtk_widget_queue_draw(widget);
704 return TRUE;
705}
706
707static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
708{
709 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
712
713 const int inset = DT_GUI_CURVE_EDITOR_INSET;
714 GtkAllocation allocation;
715 gtk_widget_get_allocation(GTK_WIDGET(c->area), &allocation);
716 int width = allocation.width, height = allocation.height;
717 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
718 cairo_t *cr = cairo_create(cst);
719
720 // clear bg
721 cairo_set_source_rgb(cr, .2, .2, .2);
722 cairo_paint(cr);
723
724 cairo_translate(cr, inset, inset);
725 width -= 2 * inset;
726 height -= 2 * inset;
727
728 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
729 cairo_set_source_rgb(cr, .1, .1, .1);
730 cairo_rectangle(cr, 0, 0, width, height);
731 cairo_stroke(cr);
732
733 cairo_set_source_rgb(cr, .3, .3, .3);
734 cairo_rectangle(cr, 0, 0, width, height);
735 cairo_fill(cr);
736
737 // draw grid
738 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
739 cairo_set_source_rgb(cr, .1, .1, .1);
740 dt_draw_vertical_lines(cr, 4, 0, 0, width, height);
741
742 // Drawing the vertical line indicators
743 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
744
745 for(int k = 0; k < 3; k++)
746 {
747 if(k == c->handle_move && c->mouse_x > 0)
748 cairo_set_source_rgb(cr, 1, 1, 1);
749 else
750 cairo_set_source_rgb(cr, .7, .7, .7);
751
752 cairo_move_to(cr, width * p->levels[k], height);
753 cairo_rel_line_to(cr, 0, -height);
754 cairo_stroke(cr);
755 }
756
757 // draw x positions
758 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
759 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
760 for(int k = 0; k < 3; k++)
761 {
762 switch(k)
763 {
764 case 0:
765 cairo_set_source_rgb(cr, 0, 0, 0);
766 break;
767
768 case 1:
769 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
770 break;
771
772 default:
773 cairo_set_source_rgb(cr, 1, 1, 1);
774 break;
775 }
776
777 cairo_move_to(cr, width * p->levels[k], height + inset - 1);
778 cairo_rel_line_to(cr, -arrw * .5f, 0);
779 cairo_rel_line_to(cr, arrw * .5f, -arrw);
780 cairo_rel_line_to(cr, arrw * .5f, arrw);
781 cairo_close_path(cr);
782 if(c->handle_move == k && c->mouse_x > 0)
783 cairo_fill(cr);
784 else
785 cairo_stroke(cr);
786 }
787
788 cairo_translate(cr, 0, height);
789
790 // draw lum histogram in background
791 // only if the module is enabled
792 if(self->enabled)
793 {
794 uint32_t *hist = self->histogram;
795 const gboolean is_linear = FALSE;
796 float hist_max = is_linear ? self->histogram_max[0] : logf(1.0 + self->histogram_max[0]);
797 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
798 {
799 cairo_save(cr);
800 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
801 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
802 dt_draw_histogram_8(cr, hist, 4, 0, is_linear);
803 cairo_restore(cr);
804 }
805 }
806
807 // Cleaning up
808 cairo_destroy(cr);
809 cairo_set_source_surface(crf, cst, 0, 0);
810 cairo_paint(crf);
811 cairo_surface_destroy(cst);
812 return TRUE;
813}
814
828static void dt_iop_levels_move_handle(dt_iop_module_t *self, int handle_move, float new_pos, float *levels,
829 float drag_start_percentage)
830{
832 float min_x = 0;
833 float max_x = 1;
834
835 if((handle_move < 0) || handle_move > 2) return;
836
837 if(IS_NULL_PTR(levels)) return;
838
839 // Determining the minimum and maximum bounds for the drag handles
840 switch(handle_move)
841 {
842 case 0:
843 max_x = fminf(levels[2] - (0.05 / drag_start_percentage), 1);
844 max_x = fminf((levels[2] * (1 - drag_start_percentage) - 0.05) / (1 - drag_start_percentage), max_x);
845 break;
846
847 case 1:
848 min_x = levels[0] + 0.05;
849 max_x = levels[2] - 0.05;
850 break;
851
852 case 2:
853 min_x = fmaxf((0.05 / drag_start_percentage) + levels[0], 0);
854 min_x = fmaxf((levels[0] * (1 - drag_start_percentage) + 0.05) / (1 - drag_start_percentage), min_x);
855 break;
856 }
857
858 levels[handle_move] = fminf(max_x, fmaxf(min_x, new_pos));
859
860 if(handle_move != 1) levels[1] = levels[0] + (drag_start_percentage * (levels[2] - levels[0]));
861
862 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
863 c->last_picked_color = -1;
864}
865
866static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
867{
868 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
871 const int inset = DT_GUI_CURVE_EDITOR_INSET;
872 GtkAllocation allocation;
873 gtk_widget_get_allocation(widget, &allocation);
874 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
875 if(!c->dragging)
876 {
877 c->mouse_x = CLAMP(event->x - inset, 0, width);
878 c->drag_start_percentage = (p->levels[1] - p->levels[0]) / (p->levels[2] - p->levels[0]);
879 }
880 c->mouse_y = CLAMP(event->y - inset, 0, height);
881
882 if(c->dragging)
883 {
884 if(c->handle_move >= 0 && c->handle_move < 3)
885 {
886 const float mx = (CLAMP(event->x - inset, 0, width)) / (float)width;
887
888 dt_iop_levels_move_handle(self, c->handle_move, mx, p->levels, c->drag_start_percentage);
889 }
891 }
892 else
893 {
894 c->handle_move = 0;
895 const float mx = CLAMP(event->x - inset, 0, width) / (float)width;
896 float dist = fabsf(p->levels[0] - mx);
897 for(int k = 1; k < 3; k++)
898 {
899 float d2 = fabsf(p->levels[k] - mx);
900 if(d2 < dist)
901 {
902 c->handle_move = k;
903 dist = d2;
904 }
905 }
906 }
907 gtk_widget_queue_draw(widget);
908
909 return TRUE;
910}
911
912static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
913{
914 // set active point
915 if(event->button == 1)
916 {
917 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
918
919 if(self->dev->gui_module != self) dt_iop_request_focus(self);
920
921 if(event->type == GDK_2BUTTON_PRESS)
922 {
923 // Reset
925 memcpy(self->params, self->default_params, self->params_size);
926
927 // Needed in case the user scrolls or drags immediately after a reset,
928 // as drag_start_percentage is only updated when the mouse is moved.
929 c->drag_start_percentage = 0.5;
931 gtk_widget_queue_draw(self->widget);
932 }
933 else
934 {
936 c->dragging = 1;
937 }
938 return TRUE;
939 }
940 return FALSE;
941}
942
943static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
944{
945 if(event->button == 1)
946 {
947 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
949 c->dragging = 0;
950 return TRUE;
951 }
952 return FALSE;
953}
954
955static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
956{
957 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
960
961 int delta_y;
962
964
965 if(c->dragging)
966 {
967 return FALSE;
968 }
969
970 if(self->dev->gui_module != self) dt_iop_request_focus(self);
971
972 const float interval = 0.002; // Distance moved for each scroll event
973 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
974 {
975 float new_position = p->levels[c->handle_move] - interval * delta_y;
976 dt_iop_levels_move_handle(self, c->handle_move, new_position, p->levels, c->drag_start_percentage);
978 return TRUE;
979 }
980
981 return TRUE; // Ensure that scrolling the widget cannot move side panel
982}
983
984static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
985{
986 if(dt_gui_widgets_suppressed()) return;
989
991
993
994 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
995 c->last_picked_color = -1;
996
998}
999
1000// clang-format off
1001// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1002// vim: shiftwidth=2 expandtab tabstop=2 cindent
1003// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1004// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int levels(struct dt_imageio_module_data_t *data)
Definition avif.c:635
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3613
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ IOP_CS_LAB
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)
@ DT_COLOR_PICKER_POINT
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_control_log(const char *msg,...)
Definition control.c:777
darktable_t darktable
Definition darktable.c:183
void * dt_alloc_align(size_t size)
Definition darktable.c:484
#define dt_free_align(ptr)
Definition darktable.h:503
static void * dt_calloc_align(size_t size)
Definition darktable.h:510
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:546
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
#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:293
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:42
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:414
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
static void dt_draw_vertical_lines(cairo_t *cr, const int num, const int left, const int top, const int right, const int bottom)
Definition draw.h:240
static void dt_draw_histogram_8(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const gboolean linear)
Definition draw.h:454
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
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
Definition gtk.c:300
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:3068
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:321
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
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:3220
#define IOP_GUI_FREE
Definition imageop.h:641
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:450
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:198
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:456
@ IOP_GROUP_TONES
Definition imageop.h:167
#define IOP_GUI_ALLOC(module)
Definition imageop.h:638
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
void init(dt_iop_module_t *module)
Definition levels.c:584
const char ** description(struct dt_iop_module_t *self)
Definition levels.c:165
static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition levels.c:912
int default_group()
Definition levels.c:142
static __DT_CLONE_TARGETS__ void dt_iop_levels_compute_levels_automatic(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:225
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition levels.c:565
__DT_CLONE_TARGETS__ int process(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition levels.c:420
#define DT_GUI_CURVE_EDITOR_INSET
Definition levels.c:79
dt_iop_levels_mode_t
Definition levels.c:95
@ LEVELS_MODE_MANUAL
Definition levels.c:96
@ LEVELS_MODE_AUTOMATIC
Definition levels.c:97
const char * name()
Definition levels.c:137
void gui_init(dt_iop_module_t *self)
Definition levels.c:597
static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
Definition levels.c:984
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition levels.c:546
void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:476
void gui_cleanup(dt_iop_module_t *self)
Definition levels.c:689
static void dt_iop_levels_compute_levels_manual(const uint32_t *histogram, float *levels)
Definition levels.c:199
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:539
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:152
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 levels.c:157
int flags()
Definition levels.c:147
static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition levels.c:955
static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition levels.c:698
static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition levels.c:707
static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition levels.c:943
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:533
static void dt_iop_levels_move_handle(dt_iop_module_t *self, int handle_move, float new_pos, float *levels, float drag_start_percentage)
Definition levels.c:828
static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition levels.c:866
static __DT_CLONE_TARGETS__ void compute_lut(const dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:281
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:298
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 levels.c:174
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
float dt_aligned_pixel_t[4]
@ DT_REQUEST_ON
Definition pixelpipe.h:48
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:49
struct _GtkWidget GtkWidget
Definition splash.h:29
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_develop_t * develop
Definition darktable.h:798
dt_dev_request_flags_t request_histogram
struct dt_iop_module_t *void * data
dt_dev_histogram_collection_params_t histogram_params
int32_t gui_attached
Definition develop.h:162
struct dt_iop_module_t * gui_module
Definition develop.h:165
gint scroll_mask
Definition gtk.h:230
unsigned int channels
Definition format.h:54
dt_iop_buffer_type_t datatype
Definition format.h:56
float lut[0x10000]
Definition levels.c:134
float percentiles[3]
Definition levels.c:131
dt_iop_levels_mode_t mode
Definition levels.c:130
GtkWidget * greypick
Definition levels.c:125
GtkWidget * mode_stack
Definition levels.c:113
GtkWidget * percentile_black
Definition levels.c:120
GtkToggleButton * activeToggleButton
Definition levels.c:118
GtkWidget * percentile_white
Definition levels.c:122
GtkDrawingArea * area
Definition levels.c:114
GtkWidget * whitepick
Definition levels.c:125
GtkWidget * percentile_grey
Definition levels.c:121
GtkWidget * blackpick
Definition levels.c:125
dt_iop_levels_mode_t mode
Definition levels.c:102
dt_iop_params_t * default_params
Definition imageop.h:344
GtkWidget * widget
Definition imageop.h:374
struct dt_develop_t * dev
Definition imageop.h:333
dt_iop_gui_data_t * gui_data
Definition imageop.h:348
dt_dev_request_flags_t request_histogram
Definition imageop.h:296
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:315
uint32_t histogram_max[4]
Definition imageop.h:317
gboolean enabled
Definition imageop.h:335
int32_t params_size
Definition imageop.h:346
uint32_t * histogram
Definition imageop.h:313
dt_aligned_pixel_t picked_color
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:344
Region of interest passed through the pixelpipe.
Definition imageop.h:72