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 if(p->mode == LEVELS_MODE_AUTOMATIC)
554 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "automatic");
555 else
556 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "manual");
557 }
558}
559
561{
564
565 dt_bauhaus_combobox_set(g->mode, p->mode);
566
567 gui_changed(self, g->mode, 0);
568
570 g->auto_levels[0] = NAN;
571 g->auto_levels[1] = NAN;
572 g->auto_levels[2] = NAN;
573 g->hash = 0;
575
576 gtk_widget_queue_draw(self->widget);
577}
578
580{
581 dt_iop_default_init(module);
582
583 module->request_histogram |= (DT_REQUEST_ON);
584
585 dt_iop_levels_params_t *d = module->default_params;
586
587 d->levels[0] = 0.0f;
588 d->levels[1] = 0.5f;
589 d->levels[2] = 1.0f;
590}
591
593{
595
597 c->auto_levels[0] = NAN;
598 c->auto_levels[1] = NAN;
599 c->auto_levels[2] = NAN;
600 c->hash = 0;
602
603 c->modes = NULL;
604
605 c->mouse_x = c->mouse_y = -1.0;
606 c->dragging = 0;
607 c->activeToggleButton = NULL;
608 c->last_picked_color = -1;
609
610 c->mode_stack = gtk_stack_new();
611 gtk_stack_set_homogeneous(GTK_STACK(c->mode_stack),FALSE);
612
613 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
614 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
615 GtkWidget *vbox_manual = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
616 gtk_box_pack_start(GTK_BOX(vbox_manual),
617 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
618 "plugins/darkroom/levels/graphheight", 280, 100),
619 FALSE, FALSE, 0);
620
621 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area),_("drag handles to set black, gray, and white points. "
622 "operates on L channel."));
623
624 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
625 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
626 | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
627 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_levels_area_draw), self);
628 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_levels_button_press), self);
629 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(dt_iop_levels_button_release), self);
630 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_levels_motion_notify), self);
631 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_levels_leave_notify), self);
632 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(dt_iop_levels_scroll), self);
633
634 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
635
636 GtkWidget *autobutton = gtk_button_new_with_label(_("auto"));
637 gtk_widget_set_tooltip_text(autobutton, _("apply auto levels"));
638 g_signal_connect(G_OBJECT(autobutton), "clicked", G_CALLBACK(dt_iop_levels_autoadjust_callback), self);
639
640 c->blackpick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
641 gtk_widget_set_tooltip_text(c->blackpick, _("pick black point from image"));
642 gtk_widget_set_name(GTK_WIDGET(c->blackpick), "picker-black");
643
644 c->greypick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
645 gtk_widget_set_tooltip_text(c->greypick, _("pick medium gray point from image"));
646 gtk_widget_set_name(GTK_WIDGET(c->greypick), "picker-grey");
647
648 c->whitepick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
649 gtk_widget_set_tooltip_text(c->whitepick, _("pick white point from image"));
650 gtk_widget_set_name(GTK_WIDGET(c->whitepick), "picker-white");
651
652 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(autobutton ), TRUE, TRUE, 0);
653 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->blackpick), TRUE, TRUE, 0);
654 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->greypick ), TRUE, TRUE, 0);
655 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->whitepick), TRUE, TRUE, 0);
656 gtk_box_pack_start(GTK_BOX(vbox_manual), box, TRUE, TRUE, 0);
657
658 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_manual, "manual");
659
660 GtkWidget *vbox_automatic = self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
661
662 c->percentile_black = dt_bauhaus_slider_from_params(self, N_("black"));
663 gtk_widget_set_tooltip_text(c->percentile_black, _("black percentile"));
664 dt_bauhaus_slider_set_format(c->percentile_black, "%");
665
666 c->percentile_grey = dt_bauhaus_slider_from_params(self, N_("gray"));
667 gtk_widget_set_tooltip_text(c->percentile_grey, _("gray percentile"));
668 dt_bauhaus_slider_set_format(c->percentile_grey, "%");
669
670 c->percentile_white = dt_bauhaus_slider_from_params(self, N_("white"));
671 gtk_widget_set_tooltip_text(c->percentile_white, _("white percentile"));
672 dt_bauhaus_slider_set_format(c->percentile_white, "%");
673
674 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_automatic, "automatic");
675
676 // start building top level widget
677 self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
678
679 c->mode = dt_bauhaus_combobox_from_params(self, N_("mode"));
680
681 gtk_box_pack_start(GTK_BOX(self->widget), c->mode_stack, TRUE, TRUE, 0);
682}
683
685{
687 g_list_free(g->modes);
688 g->modes = NULL;
689
691}
692
693static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
694{
695 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
697 c->mouse_x = c->mouse_y = -1.0;
698 gtk_widget_queue_draw(widget);
699 return TRUE;
700}
701
702static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
703{
704 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
707
708 const int inset = DT_GUI_CURVE_EDITOR_INSET;
709 GtkAllocation allocation;
710 gtk_widget_get_allocation(GTK_WIDGET(c->area), &allocation);
711 int width = allocation.width, height = allocation.height;
712 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
713 cairo_t *cr = cairo_create(cst);
714
715 // clear bg
716 cairo_set_source_rgb(cr, .2, .2, .2);
717 cairo_paint(cr);
718
719 cairo_translate(cr, inset, inset);
720 width -= 2 * inset;
721 height -= 2 * inset;
722
723 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
724 cairo_set_source_rgb(cr, .1, .1, .1);
725 cairo_rectangle(cr, 0, 0, width, height);
726 cairo_stroke(cr);
727
728 cairo_set_source_rgb(cr, .3, .3, .3);
729 cairo_rectangle(cr, 0, 0, width, height);
730 cairo_fill(cr);
731
732 // draw grid
733 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
734 cairo_set_source_rgb(cr, .1, .1, .1);
735 dt_draw_vertical_lines(cr, 4, 0, 0, width, height);
736
737 // Drawing the vertical line indicators
738 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
739
740 for(int k = 0; k < 3; k++)
741 {
742 if(k == c->handle_move && c->mouse_x > 0)
743 cairo_set_source_rgb(cr, 1, 1, 1);
744 else
745 cairo_set_source_rgb(cr, .7, .7, .7);
746
747 cairo_move_to(cr, width * p->levels[k], height);
748 cairo_rel_line_to(cr, 0, -height);
749 cairo_stroke(cr);
750 }
751
752 // draw x positions
753 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
754 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
755 for(int k = 0; k < 3; k++)
756 {
757 switch(k)
758 {
759 case 0:
760 cairo_set_source_rgb(cr, 0, 0, 0);
761 break;
762
763 case 1:
764 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
765 break;
766
767 default:
768 cairo_set_source_rgb(cr, 1, 1, 1);
769 break;
770 }
771
772 cairo_move_to(cr, width * p->levels[k], height + inset - 1);
773 cairo_rel_line_to(cr, -arrw * .5f, 0);
774 cairo_rel_line_to(cr, arrw * .5f, -arrw);
775 cairo_rel_line_to(cr, arrw * .5f, arrw);
776 cairo_close_path(cr);
777 if(c->handle_move == k && c->mouse_x > 0)
778 cairo_fill(cr);
779 else
780 cairo_stroke(cr);
781 }
782
783 cairo_translate(cr, 0, height);
784
785 // draw lum histogram in background
786 // only if the module is enabled
787 if(self->enabled)
788 {
789 uint32_t *hist = self->histogram;
790 const gboolean is_linear = FALSE;
791 float hist_max = is_linear ? self->histogram_max[0] : logf(1.0 + self->histogram_max[0]);
792 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
793 {
794 cairo_save(cr);
795 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
796 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
797 dt_draw_histogram_8(cr, hist, 4, 0, is_linear);
798 cairo_restore(cr);
799 }
800 }
801
802 // Cleaning up
803 cairo_destroy(cr);
804 cairo_set_source_surface(crf, cst, 0, 0);
805 cairo_paint(crf);
806 cairo_surface_destroy(cst);
807 return TRUE;
808}
809
823static void dt_iop_levels_move_handle(dt_iop_module_t *self, int handle_move, float new_pos, float *levels,
824 float drag_start_percentage)
825{
827 float min_x = 0;
828 float max_x = 1;
829
830 if((handle_move < 0) || handle_move > 2) return;
831
832 if(IS_NULL_PTR(levels)) return;
833
834 // Determining the minimum and maximum bounds for the drag handles
835 switch(handle_move)
836 {
837 case 0:
838 max_x = fminf(levels[2] - (0.05 / drag_start_percentage), 1);
839 max_x = fminf((levels[2] * (1 - drag_start_percentage) - 0.05) / (1 - drag_start_percentage), max_x);
840 break;
841
842 case 1:
843 min_x = levels[0] + 0.05;
844 max_x = levels[2] - 0.05;
845 break;
846
847 case 2:
848 min_x = fmaxf((0.05 / drag_start_percentage) + levels[0], 0);
849 min_x = fmaxf((levels[0] * (1 - drag_start_percentage) + 0.05) / (1 - drag_start_percentage), min_x);
850 break;
851 }
852
853 levels[handle_move] = fminf(max_x, fmaxf(min_x, new_pos));
854
855 if(handle_move != 1) levels[1] = levels[0] + (drag_start_percentage * (levels[2] - levels[0]));
856
857 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
858 c->last_picked_color = -1;
859}
860
861static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
862{
863 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
866 const int inset = DT_GUI_CURVE_EDITOR_INSET;
867 GtkAllocation allocation;
868 gtk_widget_get_allocation(widget, &allocation);
869 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
870 if(!c->dragging)
871 {
872 c->mouse_x = CLAMP(event->x - inset, 0, width);
873 c->drag_start_percentage = (p->levels[1] - p->levels[0]) / (p->levels[2] - p->levels[0]);
874 }
875 c->mouse_y = CLAMP(event->y - inset, 0, height);
876
877 if(c->dragging)
878 {
879 if(c->handle_move >= 0 && c->handle_move < 3)
880 {
881 const float mx = (CLAMP(event->x - inset, 0, width)) / (float)width;
882
883 dt_iop_levels_move_handle(self, c->handle_move, mx, p->levels, c->drag_start_percentage);
884 }
886 }
887 else
888 {
889 c->handle_move = 0;
890 const float mx = CLAMP(event->x - inset, 0, width) / (float)width;
891 float dist = fabsf(p->levels[0] - mx);
892 for(int k = 1; k < 3; k++)
893 {
894 float d2 = fabsf(p->levels[k] - mx);
895 if(d2 < dist)
896 {
897 c->handle_move = k;
898 dist = d2;
899 }
900 }
901 }
902 gtk_widget_queue_draw(widget);
903
904 return TRUE;
905}
906
907static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
908{
909 // set active point
910 if(event->button == 1)
911 {
912 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
913
914 if(self->dev->gui_module != self) dt_iop_request_focus(self);
915
916 if(event->type == GDK_2BUTTON_PRESS)
917 {
918 // Reset
920 memcpy(self->params, self->default_params, self->params_size);
921
922 // Needed in case the user scrolls or drags immediately after a reset,
923 // as drag_start_percentage is only updated when the mouse is moved.
924 c->drag_start_percentage = 0.5;
926 gtk_widget_queue_draw(self->widget);
927 }
928 else
929 {
931 c->dragging = 1;
932 }
933 return TRUE;
934 }
935 return FALSE;
936}
937
938static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
939{
940 if(event->button == 1)
941 {
942 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
944 c->dragging = 0;
945 return TRUE;
946 }
947 return FALSE;
948}
949
950static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
951{
952 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
955
956 int delta_y;
957
959
960 if(c->dragging)
961 {
962 return FALSE;
963 }
964
965 if(self->dev->gui_module != self) dt_iop_request_focus(self);
966
967 const float interval = 0.002; // Distance moved for each scroll event
968 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
969 {
970 float new_position = p->levels[c->handle_move] - interval * delta_y;
971 dt_iop_levels_move_handle(self, c->handle_move, new_position, p->levels, c->drag_start_percentage);
973 return TRUE;
974 }
975
976 return TRUE; // Ensure that scrolling the widget cannot move side panel
977}
978
979static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
980{
981 if(darktable.gui->reset) return;
984
986
988
989 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
990 c->last_picked_color = -1;
991
993}
994
995// clang-format off
996// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
997// vim: shiftwidth=2 expandtab tabstop=2 cindent
998// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
999// 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:2301
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
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:761
darktable_t darktable
Definition darktable.c:181
void * dt_alloc_align(size_t size)
Definition darktable.c:446
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
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_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#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
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
@ 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:219
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
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
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:316
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
#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_DEPRECATED
Definition imageop.h:168
@ 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_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:579
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:907
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:560
__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:592
static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
Definition levels.c:979
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:684
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:950
static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition levels.c:693
static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition levels.c:702
static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition levels.c:938
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:823
static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition levels.c:861
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:775
struct dt_develop_t * develop
Definition darktable.h:770
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: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
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: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
dt_dev_request_flags_t request_histogram
Definition imageop.h:266
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:278
uint32_t histogram_max[4]
Definition imageop.h:280
gboolean enabled
Definition imageop.h:298
int32_t params_size
Definition imageop.h:309
uint32_t * histogram
Definition imageop.h:276
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72