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"
56#endif
57#include <assert.h>
58#include <math.h>
59#include <stdint.h>
60#include <stdlib.h>
61#include <string.h>
62
63#include "widgets/bauhaus.h"
65#include "develop/develop.h"
66#include "system/macros.h"
67#include "system/openmp.h"
69#include "system/mem_alloc.h"
70#include "system/simd.h"
72#include "develop/imageop.h"
76#include "develop/imageop_gui.h"
77#include "math/openmp_maths.h"
78#include "widgets/draw.h"
79
80#include "gui/presets.h"
81#include "iop/iop_api.h"
82#include "widgets/scroll_wrap.h"
83#include "gui/screen_metrics.h"
84
85#define DT_GUI_CURVE_EDITOR_INSET DT_PIXEL_APPLY_DPI(5)
86#define DT_GUI_CURVE_INFL .3f
87
89
90static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data);
91static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data);
92static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
93static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data);
94static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data);
95static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data);
96static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self);
97//static void dt_iop_levels_mode_callback(GtkWidget *combo, gpointer user_data);
98//static void dt_iop_levels_percentiles_callback(GtkWidget *slider, gpointer user_data);
99
101{
102 LEVELS_MODE_MANUAL, // $DESCRIPTION: "manual"
103 LEVELS_MODE_AUTOMATIC // $DESCRIPTION: "automatic"
105
107{
108 dt_iop_levels_mode_t mode; // $DEFAULT: LEVELS_MODE_MANUAL
109 float black; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 0.0
110 float gray; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0
111 float white; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0
112 float levels[3];
114
133
142
143const char *name()
144{
145 return _("levels (legacy)");
146}
147
149{
150 return IOP_GROUP_TONES;
151}
152
157
159{
160 return IOP_CS_LAB;
161}
162
165{
166 default_input_format(self, pipe, piece, dsc);
167 dsc->channels = 4;
168 dsc->datatype = TYPE_FLOAT;
169}
170
171const char **description(struct dt_iop_module_t *self)
172{
173 return dt_iop_set_description(self, _("adjust black, white and mid-gray points"),
174 _("creative"),
175 _("linear or non-linear, Lab, display-referred"),
176 _("non-linear, Lab"),
177 _("non-linear, Lab, display-referred"));
178}
179
180int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
181 void *new_params, const int new_version)
182{
183 if(old_version == 1 && new_version == 2)
184 {
185 typedef struct dt_iop_levels_params_v1_t
186 {
187 float levels[3];
188 int levels_preset;
189 } dt_iop_levels_params_v1_t;
190
191 dt_iop_levels_params_v1_t *o = (dt_iop_levels_params_v1_t *)old_params;
194
195 *n = *d; // start with a fresh copy of default parameters
196
197 n->levels[0] = o->levels[0];
198 n->levels[1] = o->levels[1];
199 n->levels[2] = o->levels[2];
200 return 0;
201 }
202 return 1;
203}
204
205static void dt_iop_levels_compute_levels_manual(const uint32_t *histogram, float *levels)
206{
207 if(IS_NULL_PTR(histogram)) return;
208
209 // search histogram for min (search from bottom)
210 for(int k = 0; k <= 4 * 255; k += 4)
211 {
212 if(histogram[k] > 1)
213 {
214 levels[0] = ((float)(k) / (4 * 256));
215 break;
216 }
217 }
218 // then for max (search from top)
219 for(int k = 4 * 255; k >= 0; k -= 4)
220 {
221 if(histogram[k] > 1)
222 {
223 levels[2] = ((float)(k) / (4 * 256));
224 break;
225 }
226 }
227 levels[1] = levels[0] / 2 + levels[2] / 2;
228}
229
232 const dt_dev_pixelpipe_iop_t *piece)
233{
235 dt_dev_histogram_stats_t histogram_stats = { 0 };
236 uint32_t *histogram = NULL;
237
239 histogram_stats = self->histogram_stats;
240 if(self->histogram && histogram_stats.bins_count > 0)
241 {
242 const size_t histogram_size = 4 * histogram_stats.bins_count * sizeof(uint32_t);
243 histogram = dt_alloc_align(histogram_size);
244 if(!IS_NULL_PTR(histogram)) memcpy(histogram, self->histogram, histogram_size);
245 }
247
248 uint32_t total = histogram_stats.pixels;
249
251 for(int k = 0; k < 3; k++)
252 {
253 thr[k] = (float)total * d->percentiles[k] / 100.0f;
254 d->levels[k] = NAN;
255 }
256
257 if(IS_NULL_PTR(histogram)) return;
258
259 // find min and max levels
260 size_t n = 0;
261 for(uint32_t i = 0; i < histogram_stats.bins_count; i++)
262 {
263 n += histogram[4 * i];
264
265 for(int k = 0; k < 3; k++)
266 {
267 if(isnan(d->levels[k]) && (n >= thr[k]))
268 {
269 d->levels[k] = (float)i / (float)(histogram_stats.bins_count - 1);
270 }
271 }
272 }
273
274 dt_free_align(histogram);
275
276 // for numerical reasons sometimes the threshold is sharp but in float and n is size_t.
277 // in this case we want to make sure we don't keep nan:
278 if(isnan(d->levels[2])) d->levels[2] = 1.0f;
279
280 // compute middle level from min and max levels
281 float center = d->percentiles[1] / 100.0f;
282 if(!isnan(d->levels[0]) && !isnan(d->levels[2]))
283 d->levels[1] = (1.0f - center) * d->levels[0] + center * d->levels[2];
284}
285
287static void compute_lut(const dt_dev_pixelpipe_iop_t *piece)
288{
290
291 // Building the lut for values in the [0,1] range
292 float delta = (d->levels[2] - d->levels[0]) / 2.0f;
293 float mid = d->levels[0] + delta;
294 float tmp = (d->levels[1] - mid) / delta;
295 d->in_inv_gamma = pow(10, tmp);
296
297 for(unsigned int i = 0; i < 0x10000; i++)
298 {
299 float percentage = (float)i / (float)0x10000ul;
300 d->lut[i] = 100.0f * powf(percentage, d->in_inv_gamma);
301 }
302}
303
305{
306 (void)pipe;
307 (void)piece;
310
311 /* we need to save the last picked color to prevent flickering when
312 * changing from one picker to another, as the picked_color value does not
313 * update as rapidly */
314
315 float mean_picked_color = *self->picked_color / 100.0;
316
317 if(mean_picked_color != c->last_picked_color)
318 {
319 dt_aligned_pixel_t previous_color;
320 previous_color[0] = p->levels[0];
321 previous_color[1] = p->levels[1];
322 previous_color[2] = p->levels[2];
323
324 c->last_picked_color = mean_picked_color;
325
326 if(picker == c->blackpick)
327 {
328 if(mean_picked_color > p->levels[1])
329 {
330 p->levels[0] = p->levels[1] - FLT_EPSILON;
331 }
332 else
333 {
334 p->levels[0] = mean_picked_color;
335 }
336 }
337 else if(picker == c->greypick)
338 {
339 if(mean_picked_color < p->levels[0] || mean_picked_color > p->levels[2])
340 {
341 p->levels[1] = p->levels[1];
342 }
343 else
344 {
345 p->levels[1] = mean_picked_color;
346 }
347 }
348 else if(picker == c->whitepick)
349 {
350 if(mean_picked_color < p->levels[1])
351 {
352 p->levels[2] = p->levels[1] + FLT_EPSILON;
353 }
354 else
355 {
356 p->levels[2] = mean_picked_color;
357 }
358 }
359
360 if(previous_color[0] != p->levels[0]
361 || previous_color[1] != p->levels[1]
362 || previous_color[2] != p->levels[2])
363 {
364 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
365 }
366 }
367}
368
369/*
370 * WARNING: unlike commit_params, which is thread safe wrt gui thread and
371 * pipes, this function lives in the pipeline thread, and NOT thread safe!
372 */
373static inline __attribute__((always_inline)) void commit_params_late(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
374 const dt_dev_pixelpipe_iop_t *piece)
375{
378
379 if(d->mode == LEVELS_MODE_AUTOMATIC)
380 {
381 if(!IS_NULL_PTR(g) && !dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out))
382 {
384 const uint64_t hash = g->hash;
386
387 // note that the case 'hash == 0' on first invocation in a session implies that d->levels[]
388 // contains NANs which initiates special handling below to avoid inconsistent results. in all
389 // other cases we make sure that the preview pipe has left us with proper readings for
390 // g->auto_levels[]. if data are not yet there we need to wait (with timeout).
391 if(hash != piece->global_hash)
392 dt_control_log(_("inconsistent output"));
393
395 d->levels[0] = g->auto_levels[0];
396 d->levels[1] = g->auto_levels[1];
397 d->levels[2] = g->auto_levels[2];
399
400 compute_lut(piece);
401 }
402
403 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out)
404 || isnan(d->levels[0]) || isnan(d->levels[1])
405 || isnan(d->levels[2]))
406 {
408 compute_lut(piece);
409 }
410
411 if(!IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, &piece->roi_out)
412 && d->mode == LEVELS_MODE_AUTOMATIC)
413 {
414 uint64_t hash = piece->global_hash;
416 g->auto_levels[0] = d->levels[0];
417 g->auto_levels[1] = d->levels[1];
418 g->auto_levels[2] = d->levels[2];
419 g->hash = hash;
421 }
422 }
423}
424
427 const void *const ivoid, void *const ovoid)
428{
429 const dt_iop_roi_t *const roi_out = &piece->roi_out;
430 const int ch = 4;
431 const dt_iop_levels_data_t *const d = (dt_iop_levels_data_t *)piece->data;
432 (void)pipe;
433
434 if(d->mode == LEVELS_MODE_AUTOMATIC)
435 {
436 commit_params_late(self, pipe, piece);
437 }
438
439 const float *const restrict in = (float*)ivoid;
440 float *const restrict out = (float*)ovoid;
441 const size_t npixels = (size_t)roi_out->width * roi_out->height;
443 for(int i = 0; i < ch * npixels; i += ch)
444 {
445 const float L_in = in[i] / 100.0f;
446 float L_out;
447 if(L_in <= d->levels[0])
448 {
449 // Anything below the lower threshold just clips to zero
450 L_out = 0.0f;
451 }
452 else
453 {
454 const float percentage = (L_in - d->levels[0]) / (d->levels[2] - d->levels[0]);
455 // Within the expected input range we can use the lookup table, else we need to compute from scratch
456 L_out = percentage < 1.0f ? d->lut[(int)(percentage * 0x10000ul)] : 100.0f * powf(percentage, d->in_inv_gamma);
457 }
458
459 // Preserving contrast
460 const float denom = (in[i] > 0.01f) ? in[i] : 0.01f;
461 out[i] = L_out;
462 out[i+1] = in[i+1] * L_out / denom;
463 out[i+2] = in[i+2] * L_out / denom;
464 }
465
467 dt_iop_alpha_copy(ivoid, ovoid, roi_out->width, roi_out->height);
468 return 0;
469}
470
471// void init_presets (dt_iop_module_so_t *self)
472//{
473// dt_iop_levels_params_t p;
474// p.levels_preset = 0;
475//
476// p.levels[0] = 0;
477// p.levels[1] = 0.5;
478// p.levels[2] = 1;
479// dt_gui_presets_add_generic(_("unmodified"), self->op, self->version(), &p, sizeof(p), 1);
480//}
481
484{
487
488 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
490 else
491 piece->request_histogram &= ~(DT_REQUEST_ON);
492
494
495 piece->histogram_params.bins_count = 256;
496
497 if(p->mode == LEVELS_MODE_AUTOMATIC)
498 {
499 d->mode = LEVELS_MODE_AUTOMATIC;
500
503
505
506 piece->histogram_params.bins_count = 16384;
507
508 /*
509 * in principle, we do not need/want histogram in FULL pipe
510 * because we will use histogram from preview pipe there,
511 * but it might happen that for some reasons we do not have
512 * histogram of preview pipe yet - e.g. on first pipe run
513 * (just after setting mode to automatic)
514 */
515
516 d->percentiles[0] = p->black;
517 d->percentiles[1] = p->gray;
518 d->percentiles[2] = p->white;
519
520 d->levels[0] = NAN;
521 d->levels[1] = NAN;
522 d->levels[2] = NAN;
523
524 // commit_params_late() will compute LUT later
525 }
526 else
527 {
528 d->mode = LEVELS_MODE_MANUAL;
529
531
532 d->levels[0] = p->levels[0];
533 d->levels[1] = p->levels[1];
534 d->levels[2] = p->levels[2];
535 compute_lut(piece);
536 }
537}
538
540{
541 piece->data = dt_calloc_align(sizeof(dt_iop_levels_data_t));
542 piece->data_size = sizeof(dt_iop_levels_data_t);
543}
544
546{
547 // clean up everything again.
548 dt_free_align(piece->data);
549 piece->data = NULL;
550}
551
552void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
553{
556
557 if(w == g->mode)
558 {
559 /* blackpick/greypick/whitepick live on the "manual" stack page. Switching mode
560 can hide that page while a picker stays active, sampling/drawing on the image
561 for a control the user can no longer see or turn off from this widget. */
563
564 if(p->mode == LEVELS_MODE_AUTOMATIC)
565 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "automatic");
566 else
567 gtk_stack_set_visible_child_name(GTK_STACK(g->mode_stack), "manual");
568 }
569}
570
572{
575
576 dt_bauhaus_combobox_set(g->mode, p->mode);
577
578 gui_changed(self, g->mode, 0);
579
581 g->auto_levels[0] = NAN;
582 g->auto_levels[1] = NAN;
583 g->auto_levels[2] = NAN;
584 g->hash = 0;
586
587 gtk_widget_queue_draw(self->gui->widget);
588}
589
591{
592 dt_iop_default_init(module);
593
594 module->request_histogram |= (DT_REQUEST_ON);
595
596 dt_iop_levels_params_t *d = module->default_params;
597
598 d->levels[0] = 0.0f;
599 d->levels[1] = 0.5f;
600 d->levels[2] = 1.0f;
601}
602
604{
606
608 c->auto_levels[0] = NAN;
609 c->auto_levels[1] = NAN;
610 c->auto_levels[2] = NAN;
611 c->hash = 0;
613
614 c->modes = NULL;
615
616 c->mouse_x = c->mouse_y = -1.0;
617 c->dragging = 0;
618 c->activeToggleButton = NULL;
619 c->last_picked_color = -1;
620
621 c->mode_stack = gtk_stack_new();
622 gtk_stack_set_homogeneous(GTK_STACK(c->mode_stack),FALSE);
623
624 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
625 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
626 GtkWidget *vbox_manual = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
627 gtk_box_pack_start(GTK_BOX(vbox_manual),
628 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
629 "plugins/darkroom/levels/graphheight", 280, 100),
630 FALSE, FALSE, 0);
631
632 gtk_widget_set_tooltip_text(GTK_WIDGET(c->area),_("drag handles to set black, gray, and white points. "
633 "operates on L channel."));
634
635 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
636 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
637 | GDK_LEAVE_NOTIFY_MASK | dt_widget_scroll_mask());
638 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(dt_iop_levels_area_draw), self);
639 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(dt_iop_levels_button_press), self);
640 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(dt_iop_levels_button_release), self);
641 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(dt_iop_levels_motion_notify), self);
642 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(dt_iop_levels_leave_notify), self);
643 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(dt_iop_levels_scroll), self);
644
645 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
646
647 GtkWidget *autobutton = gtk_button_new_with_label(_("auto"));
648 gtk_widget_set_tooltip_text(autobutton, _("apply auto levels"));
649 g_signal_connect(G_OBJECT(autobutton), "clicked", G_CALLBACK(dt_iop_levels_autoadjust_callback), self);
650
651 c->blackpick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
652 gtk_widget_set_tooltip_text(c->blackpick, _("pick black point from image"));
653 gtk_widget_set_name(GTK_WIDGET(c->blackpick), "picker-black");
654
655 c->greypick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
656 gtk_widget_set_tooltip_text(c->greypick, _("pick medium gray point from image"));
657 gtk_widget_set_name(GTK_WIDGET(c->greypick), "picker-grey");
658
659 c->whitepick = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, NULL);
660 gtk_widget_set_tooltip_text(c->whitepick, _("pick white point from image"));
661 gtk_widget_set_name(GTK_WIDGET(c->whitepick), "picker-white");
662
663 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(autobutton ), TRUE, TRUE, 0);
664 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->blackpick), TRUE, TRUE, 0);
665 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->greypick ), TRUE, TRUE, 0);
666 gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->whitepick), TRUE, TRUE, 0);
667 gtk_box_pack_start(GTK_BOX(vbox_manual), box, TRUE, TRUE, 0);
668
669 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_manual, "manual");
670
671 GtkWidget *vbox_automatic = self->gui->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
672
673 c->percentile_black = dt_bauhaus_slider_from_params(self, N_("black"));
674 gtk_widget_set_tooltip_text(c->percentile_black, _("black percentile"));
675 dt_bauhaus_slider_set_format(c->percentile_black, "%");
676
677 c->percentile_grey = dt_bauhaus_slider_from_params(self, N_("gray"));
678 gtk_widget_set_tooltip_text(c->percentile_grey, _("gray percentile"));
679 dt_bauhaus_slider_set_format(c->percentile_grey, "%");
680
681 c->percentile_white = dt_bauhaus_slider_from_params(self, N_("white"));
682 gtk_widget_set_tooltip_text(c->percentile_white, _("white percentile"));
683 dt_bauhaus_slider_set_format(c->percentile_white, "%");
684
685 gtk_stack_add_named(GTK_STACK(c->mode_stack), vbox_automatic, "automatic");
686
687 // start building top level widget
688 self->gui->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
689
690 c->mode = dt_bauhaus_combobox_from_params(self, N_("mode"));
691
692 gtk_box_pack_start(GTK_BOX(self->gui->widget), c->mode_stack, TRUE, TRUE, 0);
693}
694
696{
698 g_list_free(g->modes);
699 g->modes = NULL;
700
702}
703
704static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
705{
706 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
708 c->mouse_x = c->mouse_y = -1.0;
709 gtk_widget_queue_draw(widget);
710 return TRUE;
711}
712
713static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
714{
715 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
718
719 const int inset = DT_GUI_CURVE_EDITOR_INSET;
720 GtkAllocation allocation;
721 gtk_widget_get_allocation(GTK_WIDGET(c->area), &allocation);
722 int width = allocation.width, height = allocation.height;
723 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
724 cairo_t *cr = cairo_create(cst);
725
726 // clear bg
727 cairo_set_source_rgb(cr, .2, .2, .2);
728 cairo_paint(cr);
729
730 cairo_translate(cr, inset, inset);
731 width -= 2 * inset;
732 height -= 2 * inset;
733
734 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
735 cairo_set_source_rgb(cr, .1, .1, .1);
736 cairo_rectangle(cr, 0, 0, width, height);
737 cairo_stroke(cr);
738
739 cairo_set_source_rgb(cr, .3, .3, .3);
740 cairo_rectangle(cr, 0, 0, width, height);
741 cairo_fill(cr);
742
743 // draw grid
744 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
745 cairo_set_source_rgb(cr, .1, .1, .1);
746 dt_draw_vertical_lines(cr, 4, 0, 0, width, height);
747
748 // Drawing the vertical line indicators
749 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
750
751 for(int k = 0; k < 3; k++)
752 {
753 if(k == c->handle_move && c->mouse_x > 0)
754 cairo_set_source_rgb(cr, 1, 1, 1);
755 else
756 cairo_set_source_rgb(cr, .7, .7, .7);
757
758 cairo_move_to(cr, width * p->levels[k], height);
759 cairo_rel_line_to(cr, 0, -height);
760 cairo_stroke(cr);
761 }
762
763 // draw x positions
764 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
765 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
766 for(int k = 0; k < 3; k++)
767 {
768 switch(k)
769 {
770 case 0:
771 cairo_set_source_rgb(cr, 0, 0, 0);
772 break;
773
774 case 1:
775 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
776 break;
777
778 default:
779 cairo_set_source_rgb(cr, 1, 1, 1);
780 break;
781 }
782
783 cairo_move_to(cr, width * p->levels[k], height + inset - 1);
784 cairo_rel_line_to(cr, -arrw * .5f, 0);
785 cairo_rel_line_to(cr, arrw * .5f, -arrw);
786 cairo_rel_line_to(cr, arrw * .5f, arrw);
787 cairo_close_path(cr);
788 if(c->handle_move == k && c->mouse_x > 0)
789 cairo_fill(cr);
790 else
791 cairo_stroke(cr);
792 }
793
794 cairo_translate(cr, 0, height);
795
796 // draw lum histogram in background
797 // only if the module is enabled
798 if(self->enabled)
799 {
800 uint32_t *hist = self->histogram;
801 const gboolean is_linear = FALSE;
802 float hist_max = is_linear ? self->histogram_max[0] : logf(1.0 + self->histogram_max[0]);
803 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
804 {
805 cairo_save(cr);
806 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
807 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
808 dt_draw_histogram_8(cr, hist, 4, 0, is_linear);
809 cairo_restore(cr);
810 }
811 }
812
813 // Cleaning up
814 cairo_destroy(cr);
815 cairo_set_source_surface(crf, cst, 0, 0);
816 cairo_paint(crf);
817 cairo_surface_destroy(cst);
818 return TRUE;
819}
820
834static void dt_iop_levels_move_handle(dt_iop_module_t *self, int handle_move, float new_pos, float *levels,
835 float drag_start_percentage)
836{
838 float min_x = 0;
839 float max_x = 1;
840
841 if((handle_move < 0) || handle_move > 2) return;
842
843 if(IS_NULL_PTR(levels)) return;
844
845 // Determining the minimum and maximum bounds for the drag handles
846 switch(handle_move)
847 {
848 case 0:
849 max_x = fminf(levels[2] - (0.05 / drag_start_percentage), 1);
850 max_x = fminf((levels[2] * (1 - drag_start_percentage) - 0.05) / (1 - drag_start_percentage), max_x);
851 break;
852
853 case 1:
854 min_x = levels[0] + 0.05;
855 max_x = levels[2] - 0.05;
856 break;
857
858 case 2:
859 min_x = fmaxf((0.05 / drag_start_percentage) + levels[0], 0);
860 min_x = fmaxf((levels[0] * (1 - drag_start_percentage) + 0.05) / (1 - drag_start_percentage), min_x);
861 break;
862 }
863
864 levels[handle_move] = fminf(max_x, fmaxf(min_x, new_pos));
865
866 if(handle_move != 1) levels[1] = levels[0] + (drag_start_percentage * (levels[2] - levels[0]));
867
868 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
869 c->last_picked_color = -1;
870}
871
872static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
873{
874 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
877 const int inset = DT_GUI_CURVE_EDITOR_INSET;
878 GtkAllocation allocation;
879 gtk_widget_get_allocation(widget, &allocation);
880 int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
881 if(!c->dragging)
882 {
883 c->mouse_x = CLAMP(event->x - inset, 0, width);
884 c->drag_start_percentage = (p->levels[1] - p->levels[0]) / (p->levels[2] - p->levels[0]);
885 }
886 c->mouse_y = CLAMP(event->y - inset, 0, height);
887
888 if(c->dragging)
889 {
890 if(c->handle_move >= 0 && c->handle_move < 3)
891 {
892 const float mx = (CLAMP(event->x - inset, 0, width)) / (float)width;
893
894 dt_iop_levels_move_handle(self, c->handle_move, mx, p->levels, c->drag_start_percentage);
895 }
896 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
897 }
898 else
899 {
900 c->handle_move = 0;
901 const float mx = CLAMP(event->x - inset, 0, width) / (float)width;
902 float dist = fabsf(p->levels[0] - mx);
903 for(int k = 1; k < 3; k++)
904 {
905 float d2 = fabsf(p->levels[k] - mx);
906 if(d2 < dist)
907 {
908 c->handle_move = k;
909 dist = d2;
910 }
911 }
912 }
913 gtk_widget_queue_draw(widget);
914
915 return TRUE;
916}
917
918static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
919{
920 // set active point
921 if(event->button == 1)
922 {
923 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
924
925 if(self->dev->gui_module != self) dt_iop_request_focus(self);
926
927 if(event->type == GDK_2BUTTON_PRESS)
928 {
929 // Reset
931 memcpy(self->params, self->default_params, self->params_size);
932
933 // Needed in case the user scrolls or drags immediately after a reset,
934 // as drag_start_percentage is only updated when the mouse is moved.
935 c->drag_start_percentage = 0.5;
936 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
937 gtk_widget_queue_draw(self->gui->widget);
938 }
939 else
940 {
942 c->dragging = 1;
943 }
944 return TRUE;
945 }
946 return FALSE;
947}
948
949static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
950{
951 if(event->button == 1)
952 {
953 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
955 c->dragging = 0;
956 return TRUE;
957 }
958 return FALSE;
959}
960
961static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
962{
963 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
966
967 int delta_y;
968
970
971 if(c->dragging)
972 {
973 return FALSE;
974 }
975
976 if(self->dev->gui_module != self) dt_iop_request_focus(self);
977
978 const float interval = 0.002; // Distance moved for each scroll event
979 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
980 {
981 float new_position = p->levels[c->handle_move] - interval * delta_y;
982 dt_iop_levels_move_handle(self, c->handle_move, new_position, p->levels, c->drag_start_percentage);
983 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
984 return TRUE;
985 }
986
987 return TRUE; // Ensure that scrolling the widget cannot move side panel
988}
989
990static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
991{
992 if(dt_gui_widgets_suppressed()) return;
995
997
999
1000 if(!IS_NULL_PTR(c->activeToggleButton)) gtk_toggle_button_set_active(c->activeToggleButton, FALSE);
1001 c->last_picked_color = -1;
1002
1003 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1004}
1005
1006// clang-format off
1007// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1008// vim: shiftwidth=2 expandtab tabstop=2 cindent
1009// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1010// 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:641
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ 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
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
void dt_control_log(const char *msg,...)
Definition control.c:824
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
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)
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:402
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
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:255
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:469
@ TYPE_FLOAT
Definition format.h:56
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:308
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:1893
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_TONES
Definition imageop.h:156
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
void init(dt_iop_module_t *module)
Definition levels.c:590
const char ** description(struct dt_iop_module_t *self)
Definition levels.c:171
static gboolean dt_iop_levels_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition levels.c:918
int default_group()
Definition levels.c:148
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:231
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition levels.c:571
__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:426
#define DT_GUI_CURVE_EDITOR_INSET
Definition levels.c:85
dt_iop_levels_mode_t
Definition levels.c:101
@ LEVELS_MODE_MANUAL
Definition levels.c:102
@ LEVELS_MODE_AUTOMATIC
Definition levels.c:103
const char * name()
Definition levels.c:143
void gui_init(dt_iop_module_t *self)
Definition levels.c:603
static void dt_iop_levels_autoadjust_callback(GtkRange *range, dt_iop_module_t *self)
Definition levels.c:990
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition levels.c:552
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:482
void gui_cleanup(dt_iop_module_t *self)
Definition levels.c:695
static void dt_iop_levels_compute_levels_manual(const uint32_t *histogram, float *levels)
Definition levels.c:205
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:545
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:158
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:163
int flags()
Definition levels.c:153
static gboolean dt_iop_levels_scroll(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
Definition levels.c:961
static gboolean dt_iop_levels_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
Definition levels.c:704
static gboolean dt_iop_levels_area_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
Definition levels.c:713
static gboolean dt_iop_levels_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition levels.c:949
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:539
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:834
static gboolean dt_iop_levels_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition levels.c:872
static __DT_CLONE_TARGETS__ void compute_lut(const dt_dev_pixelpipe_iop_t *piece)
Definition levels.c:287
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:304
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:180
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:53
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
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.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
unsigned __int64 uint64_t
Definition strptime.c:75
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:167
struct dt_iop_module_t * gui_module
Definition develop.h:170
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
float lut[0x10000]
Definition levels.c:140
float percentiles[3]
Definition levels.c:137
dt_iop_levels_mode_t mode
Definition levels.c:136
GtkWidget * greypick
Definition levels.c:131
GtkWidget * mode_stack
Definition levels.c:119
GtkWidget * percentile_black
Definition levels.c:126
GtkToggleButton * activeToggleButton
Definition levels.c:124
GtkWidget * percentile_white
Definition levels.c:128
GtkDrawingArea * area
Definition levels.c:120
GtkWidget * whitepick
Definition levels.c:131
GtkWidget * percentile_grey
Definition levels.c:127
GtkWidget * blackpick
Definition levels.c:131
dt_iop_levels_mode_t mode
Definition levels.c:108
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_dev_request_flags_t request_histogram
Definition imageop.h:274
dt_dev_histogram_stats_t histogram_stats
Definition imageop.h:293
uint32_t histogram_max[4]
Definition imageop.h:295
gboolean enabled
Definition imageop.h:313
int32_t params_size
Definition imageop.h:335
uint32_t * histogram
Definition imageop.h:291
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
Telling the user something happened.
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)