Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
zonesystem.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Bruce Guenter.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010-2013, 2016 johannes hanika.
6 Copyright (C) 2010 Pascal de Bruijn.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Brian Teague.
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, 2019 Tobias Ellinghaus.
13 Copyright (C) 2012 José Carlos García Sogo.
14 Copyright (C) 2012 Loic Guibert.
15 Copyright (C) 2012 Richard Wonka.
16 Copyright (C) 2012-2014, 2016-2017 Ulrich Pegelow.
17 Copyright (C) 2013-2016 Roman Lebedev.
18 Copyright (C) 2014 parafin.
19 Copyright (C) 2015 Pedro Côrte-Real.
20 Copyright (C) 2017-2018, 2021 Dan Torop.
21 Copyright (C) 2017 Heiko Bauke.
22 Copyright (C) 2018, 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
23 Copyright (C) 2018 Edgardo Hoszowski.
24 Copyright (C) 2018 Maurizio Paglia.
25 Copyright (C) 2018-2021 Pascal Obry.
26 Copyright (C) 2018 rawfiner.
27 Copyright (C) 2019 Andreas Schneider.
28 Copyright (C) 2019 emeikei.
29 Copyright (C) 2019 luzpaz.
30 Copyright (C) 2020 Aldric Renaudin.
31 Copyright (C) 2020 Diederik Ter Rahe.
32 Copyright (C) 2020-2021 Hubert Kowalski.
33 Copyright (C) 2020-2021 Ralf Brown.
34 Copyright (C) 2021 Chris Elston.
35 Copyright (C) 2022 Hanno Schwalm.
36 Copyright (C) 2022 Martin Bařinka.
37 Copyright (C) 2022 Philipp Lutz.
38
39 darktable is free software: you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation, either version 3 of the License, or
42 (at your option) any later version.
43
44 darktable is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with darktable. If not, see <http://www.gnu.org/licenses/>.
51*/
52#ifdef HAVE_CONFIG_H
53#include "config.h"
55#endif
56#include "develop/imageop_gui.h"
57#include <assert.h>
58#include <stdlib.h>
59#include <string.h>
60
61#include "system/macros.h"
62#include "system/openmp.h"
64#include "system/mem_alloc.h"
65#include "system/simd.h"
67#include "common/utility.h"
68#include "pixel/gaussian.h"
69#include "math/math.h"
70#include "control/control.h"
71#include "control/signal.h"
72#include "develop/develop.h"
73#include "common/logging.h"
74#include "develop/imageop.h"
76#include "widgets/drawingarea.h"
77#include "iop/iop_api.h"
78
79#include <librsvg/rsvg.h>
80#include "gui/screen_metrics.h"
81// ugh, ugly hack. why do people break stuff all the time?
82#ifndef RSVG_CAIRO_H
83#include <librsvg/rsvg-cairo.h>
84#endif
85
86
88#define MAX_ZONE_SYSTEM_SIZE 24
89
92{
93 int size; // $DEFAULT: 10
94 float zone[MAX_ZONE_SYSTEM_SIZE + 1]; // $DEFAULT: -1.0
96
105
106
107/*
108void init_presets (dt_iop_module_so_t *self)
109{
110 dt_gui_presets_add_generic(_("Fill-light 0.25EV with 4 zones"), self->op, self->version(),
111&(dt_iop_zonesystem_params_t){0.25,0.25,4.0} , sizeof(dt_iop_zonesystem_params_t), 1);
112 dt_gui_presets_add_generic(_("Fill-shadow -0.25EV with 4 zones"), self->op, self->version(),
113&(dt_iop_zonesystem_params_t){-0.25,0.25,4.0} , sizeof(dt_iop_zonesystem_params_t), 1);
114}
115*/
116
136
137
138const char *name()
139{
140 return _("zone system");
141}
142
148
149const char *deprecated_msg()
150{
151 return _("this module is deprecated. please use the tone equalizer module instead.");
152}
153
155{
156 return IOP_GROUP_TONES;
157}
158
160{
161 return IOP_CS_LAB;
162}
163
164/* get the zone index of pixel lightness from zonemap */
165static inline int _iop_zonesystem_zone_index_from_lightness(float lightness, float *zonemap, int size)
166{
167 for(int k = 0; k < size - 1; k++)
168 if(zonemap[k + 1] >= lightness) return k;
169 return size - 1;
170}
171
172/* calculate a zonemap with scale values for each zone based on controlpoints from param */
173static inline void _iop_zonesystem_calculate_zonemap(struct dt_iop_zonesystem_params_t *p, float *zonemap)
174{
175 int steps = 0;
176 int pk = 0;
177
178 for(int k = 0; k < p->size; k++)
179 {
180 if((k > 0 && k < p->size - 1) && p->zone[k] == -1)
181 steps++;
182 else
183 {
184 /* set 0 and 1.0 for first and last element in zonesystem size, or the actually parameter value */
185 zonemap[k] = k == 0 ? 0.0 : k == (p->size - 1) ? 1.0 : p->zone[k];
186
187 /* for each step from pk to k, calculate values
188 for now this is linear distributed
189 */
190 for(int l = 1; l <= steps; l++)
191 zonemap[pk + l] = zonemap[pk] + (((zonemap[k] - zonemap[pk]) / (steps + 1)) * l);
192
193 /* store k into pk and reset zone steps for next range*/
194 pk = k;
195 steps = 0;
196 }
197 }
198}
199
200static inline __attribute__((always_inline)) void process_common_setup(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
201 const dt_dev_pixelpipe_iop_t *piece,
202 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
203 const dt_iop_roi_t *const roi_out)
204{
205 const int width = roi_out->width;
206 const int height = roi_out->height;
207
208 if(self->dev->gui_attached && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out))
209 {
211 if(IS_NULL_PTR(g)) return;
212
214 if(IS_NULL_PTR(g->in_preview_buffer) || IS_NULL_PTR(g->out_preview_buffer) || g->preview_width != width
215 || g->preview_height != height)
216 {
217 dt_free(g->in_preview_buffer);
218 dt_free(g->out_preview_buffer);
219 g->in_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
220 g->out_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
221 g->preview_width = width;
222 g->preview_height = height;
223 }
225 }
226}
227
229static void process_common_cleanup(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
230 const dt_dev_pixelpipe_iop_t *piece,
231 const void *const ivoid, void *const ovoid,
232 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
233{
236
237 const int width = roi_out->width;
238 const int height = roi_out->height;
239 const size_t ch = piece->dsc_in.channels;
240 const int size = d->params.size;
241
242 if(pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, width, height);
243
244 /* if gui and have buffer lets gaussblur and fill buffer with zone indexes */
245 if(self->dev->gui_attached
246 && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out)
247 && !IS_NULL_PTR(g) && g->in_preview_buffer
248 && g->out_preview_buffer)
249 {
250 float Lmax[] = { 100.0f };
251 float Lmin[] = { 0.0f };
252
253 /* setup gaussian kernel */
254 const int radius = 8;
255 const float sigma = 2.5 * (radius * roi_in->scale);
256
258
259 float *tmp = g_malloc_n((size_t)width * height, sizeof(float));
260
261 if(gauss && !IS_NULL_PTR(tmp))
262 {
264 for(size_t k = 0; k < (size_t)width * height; k++) tmp[k] = ((float *)ivoid)[ch * k];
265
266 dt_gaussian_blur(gauss, tmp, tmp);
267
268 /* create zonemap preview for input */
271 for(size_t k = 0; k < (size_t)width * height; k++)
272 {
273 g->in_preview_buffer[k] = CLAMPS(tmp[k] * (size - 1) / 100.0f, 0, size - 2);
274 }
277 for(size_t k = 0; k < (size_t)width * height; k++) tmp[k] = ((float *)ovoid)[ch * k];
278
279 dt_gaussian_blur(gauss, tmp, tmp);
280
281
282 /* create zonemap preview for output */
285 for(size_t k = 0; k < (size_t)width * height; k++)
286 {
287 g->out_preview_buffer[k] = CLAMPS(tmp[k] * (size - 1) / 100.0f, 0, size - 2);
288 }
290 }
291
292 dt_free(tmp);
293 if(gauss) dt_gaussian_free(gauss);
294 }
295}
296
298int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
299 void *const ovoid)
300{
301 const dt_iop_roi_t *const roi_in = &piece->roi_in;
302 const dt_iop_roi_t *const roi_out = &piece->roi_out;
303
304 const dt_iop_zonesystem_data_t *const d = (const dt_iop_zonesystem_data_t *const)piece->data;
305 process_common_setup(self, pipe, piece, ivoid, ovoid, roi_in, roi_out);
306
307 const int size = d->params.size;
308
309 const float *const restrict in = (const float *const)ivoid;
310 float *const restrict out = (float *const)ovoid;
311 const size_t npixels = (size_t)roi_out->width * roi_out->height;
313 for(size_t k = 0; k < (size_t)4 * npixels; k += 4)
314 {
315 /* remap lightness into zonemap and apply lightness */
316 const int rz = CLAMPS(in[k] * d->rzscale, 0, size - 2); // zone index
317 const float zs = ((rz > 0) ? (d->zonemap_offset[rz] / in[k]) : 0) + d->zonemap_scale[rz];
318 for_each_channel(c,aligned(in,out))
319 {
320 out[k+c] = in[k+c] * zs;
321 }
322 }
323
324 process_common_cleanup(self, pipe, piece, ivoid, ovoid, roi_in, roi_out);
325 return 0;
326}
327
330{
332
334
335 d->params = *p;
336 d->rzscale = (d->params.size - 1) / 100.0f;
337
338 /* calculate zonemap */
339 float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
340 _iop_zonesystem_calculate_zonemap(&(d->params), zonemap);
341
342 const int size = d->params.size;
343
344 // precompute scale and offset
345 for(int k = 0; k < size - 1; k++) d->zonemap_scale[k] = (zonemap[k + 1] - zonemap[k]) * (size - 1);
346 for(int k = 0; k < size - 1; k++)
347 d->zonemap_offset[k] = 100.0f * ((k + 1) * zonemap[k] - k * zonemap[k + 1]);
348}
349
351{
353 piece->data_size = sizeof(dt_iop_zonesystem_data_t);
354}
355
357{
358 dt_free_align(piece->data);
359 piece->data = NULL;
360}
361
362void gui_update(struct dt_iop_module_t *self)
363{
364 // dt_iop_module_t *module = (dt_iop_module_t *)self;
366 // dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)module->params;
367 gtk_widget_queue_draw(GTK_WIDGET(g->zones));
368}
369
370static void _iop_zonesystem_redraw_preview_callback(gpointer instance, gpointer user_data);
371
372static gboolean dt_iop_zonesystem_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self);
373
374static gboolean dt_iop_zonesystem_bar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self);
375static gboolean dt_iop_zonesystem_bar_motion_notify(GtkWidget *widget, GdkEventMotion *event,
376 dt_iop_module_t *self);
377static gboolean dt_iop_zonesystem_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
378 dt_iop_module_t *self);
379static gboolean dt_iop_zonesystem_bar_button_press(GtkWidget *widget, GdkEventButton *event,
380 dt_iop_module_t *self);
381static gboolean dt_iop_zonesystem_bar_button_release(GtkWidget *widget, GdkEventButton *event,
382 dt_iop_module_t *self);
383static gboolean dt_iop_zonesystem_bar_scrolled(GtkWidget *widget, GdkEventScroll *event,
384 dt_iop_module_t *self);
385
386
387static void size_allocate_callback(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
388{
389 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
391
392 if(g->image) cairo_surface_destroy(g->image);
393 dt_free(g->image_buffer);
394
395 /* load the dt logo as a background */
396 g->image = dt_util_get_logo(MIN(allocation->width, allocation->height) * 0.75);
397 if(g->image)
398 {
399 g->image_buffer = cairo_image_surface_get_data(g->image);
400 g->image_width = cairo_image_surface_get_width(g->image);
401 g->image_height = cairo_image_surface_get_height(g->image);
402 }
403 else
404 {
405 g->image_buffer = NULL;
406 g->image_width = 0;
407 g->image_height = 0;
408 }
409}
410
411void gui_init(struct dt_iop_module_t *self)
412{
414 g->in_preview_buffer = g->out_preview_buffer = NULL;
415 g->is_dragging = FALSE;
416 g->hilite_zone = FALSE;
417 g->preview_width = g->preview_height = 0;
418 g->mouse_over_output_zones = FALSE;
419
420 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
421
423 g_signal_connect(G_OBJECT(g->preview), "size-allocate", G_CALLBACK(size_allocate_callback), self);
424 g_signal_connect(G_OBJECT(g->preview), "draw", G_CALLBACK(dt_iop_zonesystem_preview_draw), self);
425 gtk_widget_add_events(GTK_WIDGET(g->preview), GDK_POINTER_MOTION_MASK
426 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
427 | GDK_LEAVE_NOTIFY_MASK);
428
429 /* create the zonesystem bar widget */
430 g->zones = gtk_drawing_area_new();
431 gtk_widget_set_tooltip_text(g->zones, _("lightness zones\nuse mouse scrollwheel to change the number of zones\n"
432 "left-click on a border to create a marker\n"
433 "right-click on a marker to delete it"));
434 g_signal_connect(G_OBJECT(g->zones), "draw", G_CALLBACK(dt_iop_zonesystem_bar_draw), self);
435 g_signal_connect(G_OBJECT(g->zones), "motion-notify-event", G_CALLBACK(dt_iop_zonesystem_bar_motion_notify),
436 self);
437 g_signal_connect(G_OBJECT(g->zones), "leave-notify-event", G_CALLBACK(dt_iop_zonesystem_bar_leave_notify),
438 self);
439 g_signal_connect(G_OBJECT(g->zones), "button-press-event", G_CALLBACK(dt_iop_zonesystem_bar_button_press),
440 self);
441 g_signal_connect(G_OBJECT(g->zones), "button-release-event",
442 G_CALLBACK(dt_iop_zonesystem_bar_button_release), self);
443 g_signal_connect(G_OBJECT(g->zones), "scroll-event", G_CALLBACK(dt_iop_zonesystem_bar_scrolled), self);
444 gtk_widget_add_events(GTK_WIDGET(g->zones), GDK_POINTER_MOTION_MASK
445 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
446 | GDK_LEAVE_NOTIFY_MASK | dt_widget_scroll_mask());
447 gtk_widget_set_size_request(g->zones, -1, DT_PIXEL_APPLY_DPI(40));
448
449 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->preview, TRUE, TRUE, 0);
450 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->zones, TRUE, TRUE, 0);
451
452 /* add signal handler for preview pipe finish to redraw the preview */
455
456
457 g->image = NULL;
458 g->image_buffer = NULL;
459 g->image_width = 0;
460 g->image_height = 0;
461}
462
463void gui_cleanup(struct dt_iop_module_t *self)
464{
466
468 dt_free(g->in_preview_buffer);
469 dt_free(g->out_preview_buffer);
470 if(g->image) cairo_surface_destroy(g->image);
471 dt_free(g->image_buffer);
472
474}
475
476#define DT_ZONESYSTEM_INSET DT_PIXEL_APPLY_DPI(5)
477#define DT_ZONESYSTEM_BAR_SPLIT_WIDTH 0.0
478#define DT_ZONESYSTEM_REFERENCE_SPLIT 0.30
479static gboolean dt_iop_zonesystem_bar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
480{
483
484 const int inset = DT_ZONESYSTEM_INSET;
485 GtkAllocation allocation;
486 gtk_widget_get_allocation(widget, &allocation);
487 int width = allocation.width, height = allocation.height;
488 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
489 cairo_t *cr = cairo_create(cst);
490
491 /* clear background */
492 cairo_set_source_rgb(cr, .15, .15, .15);
493 cairo_paint(cr);
494
495
496 /* translate and scale */
497 width -= 2 * inset;
498 height -= 2 * inset;
499 cairo_save(cr);
500 cairo_translate(cr, inset, inset);
501 cairo_scale(cr, width, height);
502
503 /* render the bars */
504 float zonemap[MAX_ZONE_SYSTEM_SIZE] = { 0 };
506 float s = (1. / (p->size - 2));
507 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
508 for(int i = 0; i < p->size - 1; i++)
509 {
510 /* draw the reference zone */
511 float z = s * i;
512 cairo_rectangle(cr, (1. / (p->size - 1)) * i, 0, (1. / (p->size - 1)),
514 cairo_set_source_rgb(cr, z, z, z);
515 cairo_fill(cr);
516
517 /* draw zone mappings */
518 cairo_rectangle(cr, zonemap[i], DT_ZONESYSTEM_REFERENCE_SPLIT + DT_ZONESYSTEM_BAR_SPLIT_WIDTH,
519 (zonemap[i + 1] - zonemap[i]), 1.0 - DT_ZONESYSTEM_REFERENCE_SPLIT);
520 cairo_set_source_rgb(cr, z, z, z);
521 cairo_fill(cr);
522 }
523 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
524 cairo_restore(cr);
525
526 /* render zonebar control lines */
527 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
528 cairo_set_line_width(cr, 1.);
529 cairo_rectangle(cr, inset, inset, width, height);
530 cairo_set_source_rgb(cr, .1, .1, .1);
531 cairo_stroke(cr);
532 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
533
534 /* render control points handles */
535 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
536 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
537 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
538 for(int k = 1; k < p->size - 1; k++)
539 {
540 float nzw = zonemap[k + 1] - zonemap[k];
541 float pzw = zonemap[k] - zonemap[k - 1];
542 if((((g->mouse_x / width) > (zonemap[k] - (pzw / 2.0)))
543 && ((g->mouse_x / width) < (zonemap[k] + (nzw / 2.0)))) || p->zone[k] != -1)
544 {
545 gboolean is_under_mouse = ((width * zonemap[k]) - arrw * .5f < g->mouse_x
546 && (width * zonemap[k]) + arrw * .5f > g->mouse_x);
547
548 cairo_move_to(cr, inset + (width * zonemap[k]), height + (2 * inset) - 1);
549 cairo_rel_line_to(cr, -arrw * .5f, 0);
550 cairo_rel_line_to(cr, arrw * .5f, -arrw);
551 cairo_rel_line_to(cr, arrw * .5f, arrw);
552 cairo_close_path(cr);
553
554 if(is_under_mouse)
555 cairo_fill(cr);
556 else
557 cairo_stroke(cr);
558 }
559 }
560
561
562 /* push mem surface into widget */
563 cairo_destroy(cr);
564 cairo_set_source_surface(crf, cst, 0, 0);
565 cairo_paint(crf);
566 cairo_surface_destroy(cst);
567
568 return TRUE;
569}
570
571static gboolean dt_iop_zonesystem_bar_button_press(GtkWidget *widget, GdkEventButton *event,
572 dt_iop_module_t *self)
573{
576 const int inset = DT_ZONESYSTEM_INSET;
577 GtkAllocation allocation;
578 gtk_widget_get_allocation(widget, &allocation);
579 int width = allocation.width - 2 * inset; /*, height = allocation.height - 2*inset;*/
580
581 /* calculate zonemap */
582 float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
584
585 /* translate mouse into zone index */
586 int k = _iop_zonesystem_zone_index_from_lightness(g->mouse_x / width, zonemap, p->size);
587 float zw = zonemap[k + 1] - zonemap[k];
588 if((g->mouse_x / width) > zonemap[k] + (zw / 2)) k++;
589
590
591 if(event->button == 1)
592 {
593 if(p->zone[k] == -1)
594 {
595 p->zone[k] = zonemap[k];
596 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
597 }
598 g->is_dragging = TRUE;
599 g->current_zone = k;
600 }
601 else if(event->button == 3)
602 {
603 /* clear the controlpoint */
604 p->zone[k] = -1;
605 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
606 }
607
608 return TRUE;
609}
610
611static gboolean dt_iop_zonesystem_bar_button_release(GtkWidget *widget, GdkEventButton *event,
612 dt_iop_module_t *self)
613{
615 if(event->button == 1)
616 {
617 g->is_dragging = FALSE;
618 }
619 return TRUE;
620}
621
622static gboolean dt_iop_zonesystem_bar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
623{
625 int cs = CLAMP(p->size, 4, MAX_ZONE_SYSTEM_SIZE);
626
627 int delta_y;
628 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
629 {
630 p->size = CLAMP(p->size - delta_y, 4, MAX_ZONE_SYSTEM_SIZE);
631 p->zone[cs] = -1;
632 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
633 gtk_widget_queue_draw(widget);
634 }
635
636 return TRUE;
637}
638
639static gboolean dt_iop_zonesystem_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
640 dt_iop_module_t *self)
641{
643 g->hilite_zone = FALSE;
644 gtk_widget_queue_draw(g->preview);
645 return TRUE;
646}
647
648static gboolean dt_iop_zonesystem_bar_motion_notify(GtkWidget *widget, GdkEventMotion *event,
649 dt_iop_module_t *self)
650{
653 const int inset = DT_ZONESYSTEM_INSET;
654 GtkAllocation allocation;
655 gtk_widget_get_allocation(widget, &allocation);
656 int width = allocation.width - 2 * inset, height = allocation.height - 2 * inset;
657
658 /* calculate zonemap */
659 float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
661
662 /* record mouse position within control */
663 g->mouse_x = CLAMP(event->x - inset, 0, width);
664 g->mouse_y = CLAMP(height - 1 - event->y + inset, 0, height);
665
666 if(g->is_dragging)
667 {
668 if((g->mouse_x / width) > zonemap[g->current_zone - 1]
669 && (g->mouse_x / width) < zonemap[g->current_zone + 1])
670 {
671 p->zone[g->current_zone] = (g->mouse_x / width);
672 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
673 }
674 }
675 else
676 {
677 /* decide which zone the mouse is over */
678 if(g->mouse_y >= height * (1.0 - DT_ZONESYSTEM_REFERENCE_SPLIT))
679 {
680 g->zone_under_mouse = (g->mouse_x / width) / (1.0 / (p->size - 1));
681 g->mouse_over_output_zones = TRUE;
682 }
683 else
684 {
685 float xpos = g->mouse_x / width;
686 for(int z = 0; z < p->size - 1; z++)
687 {
688 if(xpos >= zonemap[z] && xpos < zonemap[z + 1])
689 {
690 g->zone_under_mouse = z;
691 break;
692 }
693 }
694 g->mouse_over_output_zones = FALSE;
695 }
696 g->hilite_zone = (g->mouse_y < height) ? TRUE : FALSE;
697 }
698
699 gtk_widget_queue_draw(self->gui->widget);
700 gtk_widget_queue_draw(g->preview);
701 return TRUE;
702}
703
704
705static gboolean dt_iop_zonesystem_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
706{
707 const int inset = DT_PIXEL_APPLY_DPI(2);
708 GtkAllocation allocation;
709 gtk_widget_get_allocation(widget, &allocation);
710 int width = allocation.width, height = allocation.height;
711
714
715 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
716 cairo_t *cr = cairo_create(cst);
717
718 /* clear background */
719 GtkStyleContext *context = gtk_widget_get_style_context(self->gui->expander);
720 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
721
722 width -= 2 * inset;
723 height -= 2 * inset;
724 cairo_translate(cr, inset, inset);
725
727 if(g->in_preview_buffer && g->out_preview_buffer && self->enabled)
728 {
729 /* calculate the zonemap */
730 float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
732
733 /* let's generate a pixbuf from pixel zone buffer */
734 guchar *image = g_malloc_n((size_t)4 * g->preview_width * g->preview_height, sizeof(guchar));
735 guchar *buffer = g->mouse_over_output_zones ? g->out_preview_buffer : g->in_preview_buffer;
736 for(int k = 0; k < g->preview_width * g->preview_height; k++)
737 {
738 int zone = 255 * CLIP(((1.0 / (p->size - 1)) * buffer[k]));
739 image[4 * k + 2] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 255 : zone;
740 image[4 * k + 1] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 255 : zone;
741 image[4 * k + 0] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 0 : zone;
742 }
744
745 const int wd = g->preview_width, ht = g->preview_height;
746 const float scale = fminf(width / (float)wd, height / (float)ht);
747 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
748 cairo_surface_t *surface = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_RGB24, wd, ht, stride);
749 cairo_translate(cr, width / 2.0, height / 2.0f);
750 cairo_scale(cr, scale, scale);
751 cairo_translate(cr, -.5f * wd, -.5f * ht);
752
753 cairo_rectangle(cr, DT_PIXEL_APPLY_DPI(1), DT_PIXEL_APPLY_DPI(1), wd - DT_PIXEL_APPLY_DPI(2),
754 ht - DT_PIXEL_APPLY_DPI(2));
755 cairo_set_source_surface(cr, surface, 0, 0);
756 cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
757 cairo_fill_preserve(cr);
758 cairo_surface_destroy(surface);
759
760 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
761 cairo_set_source_rgb(cr, .1, .1, .1);
762 cairo_stroke(cr);
763
764 dt_free(image);
765 }
766 else
767 {
769 // draw a big, subdued dt logo
770 if(g->image)
771 {
772 GdkRGBA *color;
773 gtk_style_context_get(context, gtk_widget_get_state_flags(self->gui->expander), "background-color", &color,
774 NULL);
775
776 cairo_set_source_surface(cr, g->image, (width - g->image_width) * 0.5, (height - g->image_height) * 0.5);
777 cairo_rectangle(cr, 0, 0, width, height);
778 cairo_set_operator(cr, CAIRO_OPERATOR_HSL_LUMINOSITY);
779 cairo_fill_preserve(cr);
780 cairo_set_operator(cr, CAIRO_OPERATOR_DARKEN);
781 cairo_set_source_rgb(cr, color->red + 0.02, color->green + 0.02, color->blue + 0.02);
782 cairo_fill_preserve(cr);
783 cairo_set_operator(cr, CAIRO_OPERATOR_LIGHTEN);
784 cairo_set_source_rgb(cr, color->red - 0.02, color->green - 0.02, color->blue - 0.02);
785 cairo_fill(cr);
786
787 gdk_rgba_free(color);
788 }
789 }
790
791 cairo_destroy(cr);
792 cairo_set_source_surface(crf, cst, 0, 0);
793 cairo_paint(crf);
794 cairo_surface_destroy(cst);
795
796 return TRUE;
797}
798
799void _iop_zonesystem_redraw_preview_callback(gpointer instance, gpointer user_data)
800{
801 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
803
805}
806
807// clang-format off
808// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
809// vim: shiftwidth=2 expandtab tabstop=2 cindent
810// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
811// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ IOP_CS_LAB
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:969
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
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
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:54
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:335
__DT_CLONE_TARGETS__ void dt_gaussian_blur(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:176
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:127
@ DT_IOP_GAUSSIAN_ZERO
Definition gaussian.h:34
GdkRGBA color[]
Definition geotagging.c:541
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.
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
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
#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
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 CLAMPS(A, L, H)
Definition math.h:78
#define CLIP(x)
Definition math.h:83
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
static __DT_CLONE_TARGETS__ void process_common_setup(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece)
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
#define for_each_channel(_var,...)
Definition simd.h:87
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float sigma
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:167
unsigned int channels
Definition format.h:83
GtkWidget * expander
Definition imageop_gui.h:57
GtkWidget * widget
Definition imageop_gui.h:47
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
gboolean enabled
Definition imageop.h:313
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_iop_zonesystem_params_t params
Definition zonesystem.c:100
cairo_surface_t * image
Definition zonesystem.c:131
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
cairo_surface_t * dt_util_get_logo(const float size)
Definition utility.c:524
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition zonesystem.c:328
static gboolean dt_iop_zonesystem_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
Definition zonesystem.c:639
int default_group()
Definition zonesystem.c:154
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition zonesystem.c:298
#define DT_ZONESYSTEM_BAR_SPLIT_WIDTH
Definition zonesystem.c:477
static gboolean dt_iop_zonesystem_bar_button_press(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition zonesystem.c:571
static gboolean dt_iop_zonesystem_bar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition zonesystem.c:479
#define DT_ZONESYSTEM_REFERENCE_SPLIT
Definition zonesystem.c:478
static gboolean dt_iop_zonesystem_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition zonesystem.c:705
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition zonesystem.c:350
const char * name()
Definition zonesystem.c:138
void gui_update(struct dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition zonesystem.c:362
static gboolean dt_iop_zonesystem_bar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
Definition zonesystem.c:622
static void _iop_zonesystem_calculate_zonemap(struct dt_iop_zonesystem_params_t *p, float *zonemap)
Definition zonesystem.c:173
static int _iop_zonesystem_zone_index_from_lightness(float lightness, float *zonemap, int size)
Definition zonesystem.c:165
void gui_init(struct dt_iop_module_t *self)
Definition zonesystem.c:411
static void _iop_zonesystem_redraw_preview_callback(gpointer instance, gpointer user_data)
Definition zonesystem.c:799
#define DT_ZONESYSTEM_INSET
Definition zonesystem.c:476
static void size_allocate_callback(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
Definition zonesystem.c:387
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition zonesystem.c:159
int flags()
Definition zonesystem.c:143
void gui_cleanup(struct dt_iop_module_t *self)
Definition zonesystem.c:463
static gboolean dt_iop_zonesystem_bar_button_release(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition zonesystem.c:611
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition zonesystem.c:356
static __DT_CLONE_TARGETS__ void process_common_cleanup(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition zonesystem.c:229
const char * deprecated_msg()
Definition zonesystem.c:149
static gboolean dt_iop_zonesystem_bar_motion_notify(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
Definition zonesystem.c:648
#define MAX_ZONE_SYSTEM_SIZE
Definition zonesystem.c:88