Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
spots.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011 Henrik Andersson.
4 Copyright (C) 2011-2013, 2016 johannes hanika.
5 Copyright (C) 2011 Robert Bieber.
6 Copyright (C) 2011 Rostyslav Pidgornyi.
7 Copyright (C) 2011-2014, 2016-2019 Tobias Ellinghaus.
8 Copyright (C) 2012-2013, 2015, 2017-2022 Pascal Obry.
9 Copyright (C) 2012 Richard Wonka.
10 Copyright (C) 2012-2014, 2016, 2020 Ulrich Pegelow.
11 Copyright (C) 2013-2014, 2016, 2019-2021 Aldric Renaudin.
12 Copyright (C) 2013-2016 Roman Lebedev.
13 Copyright (C) 2013 Simon Spannagel.
14 Copyright (C) 2015 Pedro Côrte-Real.
15 Copyright (C) 2016-2017 Peter Budai.
16 Copyright (C) 2017 Heiko Bauke.
17 Copyright (C) 2018-2020, 2023, 2025-2026 Aurélien PIERRE.
18 Copyright (C) 2018 Edgardo Hoszowski.
19 Copyright (C) 2018 Maurizio Paglia.
20 Copyright (C) 2018 rawfiner.
21 Copyright (C) 2019 Andreas Schneider.
22 Copyright (C) 2020 Chris Elston.
23 Copyright (C) 2020 Diederik Ter Rahe.
24 Copyright (C) 2020 Hubert Kowalski.
25 Copyright (C) 2020 Marco.
26 Copyright (C) 2020-2021 Ralf Brown.
27 Copyright (C) 2022 Martin Bařinka.
28 Copyright (C) 2022 Philipp Lutz.
29 Copyright (C) 2025-2026 Guillaume Stutin.
30
31 darktable is free software: you can redistribute it and/or modify
32 it under the terms of the GNU General Public License as published by
33 the Free Software Foundation, either version 3 of the License, or
34 (at your option) any later version.
35
36 darktable is distributed in the hope that it will be useful,
37 but WITHOUT ANY WARRANTY; without even the implied warranty of
38 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 GNU General Public License for more details.
40
41 You should have received a copy of the GNU General Public License
42 along with darktable. If not, see <http://www.gnu.org/licenses/>.
43*/
44#ifdef HAVE_CONFIG_H
45#include "common/darktable.h"
46#include "config.h"
47#endif
48#include "control/conf.h"
49#include "control/control.h"
50#include "develop/blend.h"
51#include "develop/imageop.h"
52#include "develop/imageop_gui.h"
53#include "develop/masks.h"
54
55#include "gui/gtk.h"
56#include "iop/iop_api.h"
57#include <gtk/gtk.h>
58#include <stdlib.h>
59
60
61// this is the version of the modules parameters,
62// and includes version information about compile-time dt
64
65typedef struct dt_iop_spots_params_t
66{
67 int clone_id[64];
68 int clone_algo[64];
70
76
78
79
80// this returns a translatable name
81const char *name()
82{
83 return _("spot removal");
84}
85
86const char *deprecated_msg()
87{
88 return _("this module is deprecated. please use the retouch module instead.");
89}
90
91const char **description(struct dt_iop_module_t *self)
92{
93 return dt_iop_set_description(self, _("remove sensor dust spots"),
94 _("corrective"),
95 _("linear, RGB, scene-referred"),
96 _("geometric, raw"),
97 _("linear, RGB, scene-referred"));
98}
99
101{
102 return IOP_GROUP_EFFECTS;
103}
104
109
111{
112 return IOP_CS_RGB;
113}
114
115int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
116 void *new_params, const int new_version)
117{
118 if(old_version == 1 && new_version == 2)
119 {
120 typedef struct dt_iop_spots_v1_t
121 {
122 float x, y;
123 float xc, yc;
124 float radius;
125 } dt_iop_spots_v1_t;
126 typedef struct dt_iop_spots_params_v1_t
127 {
128 int num_spots;
129 dt_iop_spots_v1_t spot[32];
130 } dt_iop_spots_params_v1_t;
131
132 dt_iop_spots_params_v1_t *o = (dt_iop_spots_params_v1_t *)old_params;
135
136 *n = *d; // start with a fresh copy of default parameters
137 for(int i = 0; i < o->num_spots; i++)
138 {
139 // we have to register a new circle mask
141
142 // spots v1 was before raw orientation changes
143 form->version = 1;
144
146 circle->center[0] = o->spot[i].x;
147 circle->center[1] = o->spot[i].y;
148 circle->radius = o->spot[i].radius;
149 circle->border = 0.0f;
150 form->points = g_list_append(form->points, circle);
151 form->source[0] = o->spot[i].xc;
152 form->source[1] = o->spot[i].yc;
153
154 // adapt for raw orientation changes
155 dt_masks_legacy_params(self->dev, form, form->version, dt_masks_version());
156
157 dt_masks_gui_form_save_creation(self->dev, self, form, NULL);
158
159 // and add it to the module params
160 n->clone_id[i] = form->formid;
161 n->clone_algo[i] = 2;
162 }
163
164 // look for spot history num, if not found then it will be added
165 // at the end of the list.
166 int last_spot_num = 0;
167 int count = 0;
168 for(GList *l = self->dev->history; l; l = g_list_next(l))
169 {
171 count++;
172 if(!strcmp(item->op_name, "spots")) last_spot_num = item->num;
173 }
174
175 if(last_spot_num == 0) last_spot_num = count;
176
177 // record all forms for this module & history num
178 // also record the group in the blend params.
179
181
182 for(GList *l = self->dev->forms; l; l = g_list_next(l))
183 {
184 dt_masks_form_t *form = (dt_masks_form_t *)l->data;
185 if(form && (form->type & DT_MASKS_GROUP))
186 {
187 bp->mask_id = form->formid;
188 }
189 dt_masks_write_masks_history_item(self->dev->image_storage.id, last_spot_num, form);
190 }
191
192 return 0;
193 }
194 return 1;
195}
196
197static void _resynch_params(struct dt_iop_module_t *self)
198{
201
202 // we create 2 new buffers
203 int nid[64] = { 0 };
204 int nalgo[64] = { 2 };
205
206 // we go through all forms in blend params
208 if(grp && (grp->type & DT_MASKS_GROUP))
209 {
210 int i = 0;
211 for(GList *forms = grp->points; (i < 64) && forms; forms = g_list_next(forms))
212 {
213 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
214 nid[i] = grpt->formid;
215 for(int j = 0; j < 64; j++)
216 {
217 if(p->clone_id[j] == nid[i])
218 {
219 nalgo[i] = p->clone_algo[j];
220 break;
221 }
222 }
223 i++;
224 }
225 }
226
227 // we reaffect params
228 for(int i = 0; i < 64; i++)
229 {
230 p->clone_algo[i] = nalgo[i];
231 p->clone_id[i] = nid[i];
232 }
233}
234
235
236static gboolean _reset_form_creation(GtkWidget *widget, dt_iop_module_t *self)
237{
239
240 // we check the nb of shapes limit
242 guint nb = 0;
243 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
244
245 if(nb >= 64)
246 {
247 dt_control_log(_("spot module is limited to 64 shapes. please add a new instance !"));
248 }
249
250 if(nb < 64
251 && (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_path))
252 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle))
253 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse))))
254 {
255 // we unset the creation mode
257 }
258 if(widget != g->bt_path || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
259 if(widget != g->bt_circle || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
260 if(widget != g->bt_ellipse || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
261
262 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
263
264 return (nb < 64);
265}
266
267static int _shape_is_being_added(dt_iop_module_t *self, const int shape_type)
268{
269 int being_added = 0;
270
271 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)
272 && (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self))
273 {
275 {
276 GList *forms = dt_masks_get_visible_form(self->dev)->points;
277 if(!IS_NULL_PTR(forms))
278 {
279 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
280 if(grpt)
281 {
283 if(!IS_NULL_PTR(form)) being_added = (form->type & shape_type);
284 }
285 }
286 }
287 else
288 being_added = (dt_masks_get_visible_form(self->dev)->type & shape_type);
289 }
290 return being_added;
291}
292
293static gboolean _add_shape(GtkWidget *widget, dt_iop_module_t *self)
294{
295 //turn module on (else shape creation won't work)
296 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
297
298 //switch mask edit mode off
300 if(bd) bd->masks_shown = DT_MASKS_EDIT_OFF;
301
302 if(!_reset_form_creation(widget, self)) return TRUE;
303 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return FALSE;
304
306 // we want to be sure that the iop has focus
308 // we create the new form
310 if(widget == g->bt_path)
312 else if(widget == g->bt_circle)
314 else if(widget == g->bt_ellipse)
316
317
318 const dt_masks_type_t masks_type = (type | DT_MASKS_CLONE);
319 dt_masks_creation_mode_enter(self, masks_type);
320
321
323 return FALSE;
324}
325
326static gboolean _add_shape_callback(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
327{
328 if(darktable.gui->reset) return FALSE;
329
331
332 _add_shape(widget, self);
333
334 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE));
335 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE));
336 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_POLYGON));
337
338 return TRUE;
339}
340
341static gboolean _edit_masks(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
342{
343 if(darktable.gui->reset) return FALSE;
344
345 // if we don't have the focus, request for it and quit, gui_focus() do the rest
346 if(self->dev->gui_module != self)
347 {
349 return FALSE;
350 }
351
354
355 //hide all shapes and free if some are in creation
356 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
358
359 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
360 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
361 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
362
364
366
368
369 // update edit shapes status
372 //only toggle shape show button if shapes exist
373 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
374 {
375 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
376 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
377 }
378 else
379 {
380 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
381 }
382
384
386
387 return TRUE;
388}
389
390static inline __attribute__((always_inline)) gboolean masks_form_is_in_roi(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
391 const dt_dev_pixelpipe_iop_t *piece,
392 dt_masks_form_t *form, const dt_iop_roi_t *roi_in,
393 const dt_iop_roi_t *roi_out)
394{
395 // we get the area for the form
396 int fl, ft, fw, fh;
397 dt_dev_pixelpipe_iop_t piece_copy = *piece;
398
399 if(dt_masks_get_area(self, (dt_dev_pixelpipe_t *)pipe, &piece_copy, form, &fw, &fh, &fl, &ft) != 0) return FALSE;
400
401 // is the form outside of the roi?
402 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
403 if(ft >= roi_out->y + roi_out->height || ft + fh <= roi_out->y || fl >= roi_out->x + roi_out->width
404 || fl + fw <= roi_out->x)
405 return FALSE;
406
407 return TRUE;
408}
409
410void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
411 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
412 const dt_iop_roi_t *roi_in)
413{
414 *roi_out = *roi_in;
415}
416
417// needed if mask dest is in roi and mask src is not
418void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
419 struct dt_dev_pixelpipe_iop_t *piece,
420 const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
421{
422 dt_dev_pixelpipe_t *const processing_pipe = (dt_dev_pixelpipe_t *)pipe;
423 *roi_in = *roi_out;
424
425 int roir = roi_in->width + roi_in->x;
426 int roib = roi_in->height + roi_in->y;
427 int roix = roi_in->x;
428 int roiy = roi_in->y;
429
431
432 // We iterate through all spots or polygons
434 if(grp && (grp->type & DT_MASKS_GROUP))
435 {
436 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
437 {
438 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
439 // we get the spot
440 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
441 if(!IS_NULL_PTR(form))
442 {
443 // if the form is outside the roi, we just skip it
444 if(!masks_form_is_in_roi(self, processing_pipe, piece, form, roi_in, roi_out))
445 {
446 continue;
447 }
448
449 // we get the area for the source
450 int fl, ft, fw, fh;
451
452 if(dt_masks_get_source_area(self, processing_pipe, piece, form, &fw, &fh, &fl, &ft) != 0)
453 {
454 continue;
455 }
456 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
457
458 // we enlarge the roi if needed
459 roiy = fminf(ft, roiy);
460 roix = fminf(fl, roix);
461 roir = fmaxf(fl + fw, roir);
462 roib = fmaxf(ft + fh, roib);
463 }
464 }
465 }
466
467 // now we set the values
468 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
469 roi_in->x = CLAMP(roix, 0, scwidth - 1);
470 roi_in->y = CLAMP(roiy, 0, scheight - 1);
471 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
472 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
473}
474
476static void masks_point_denormalize(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi,
477 const float *points, size_t points_count, float *new)
478{
479 const float scalex = pipe->iwidth * roi->scale, scaley = pipe->iheight * roi->scale;
480
481 for(size_t i = 0; i < points_count * 2; i += 2)
482 {
483 new[i] = points[i] * scalex;
484 new[i + 1] = points[i + 1] * scaley;
485 }
486}
487
488static inline __attribute__((always_inline)) int masks_point_calc_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
489 const dt_iop_roi_t *roi, const float *target, const float *source, int *dx,
490 int *dy)
491{
492 dt_boundingbox_t points;
493 masks_point_denormalize(pipe, roi, target, 1, points);
494 masks_point_denormalize(pipe, roi, source, 1, points + 2);
495
496 const int res = dt_dev_distort_transform_plus(pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, 2);
497 if(!res) return res;
498
499 *dx = points[0] - points[2];
500 *dy = points[1] - points[3];
501
502 return res;
503}
504
505static inline __attribute__((always_inline)) int masks_get_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
506 const dt_iop_roi_t *roi,
507 dt_masks_form_t *form, int *dx, int *dy)
508{
509 int res = 0;
510
511 if(form->type & DT_MASKS_POLYGON)
512 {
514
515 res = masks_point_calc_delta(self, pipe, roi, pt->node, form->source, dx, dy);
516 }
517 else if(form->type & DT_MASKS_CIRCLE)
518 {
520
521 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
522 }
523 else if(form->type & DT_MASKS_ELLIPSE)
524 {
526
527 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
528 }
529
530 return res;
531}
532
534static int _process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
535 const float *const in,
536 float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int ch)
537{
540
541// we don't modify most of the image:
543 for(int k = 0; k < roi_out->height; k++)
544 {
545 float *outb = out + (size_t)ch * k * roi_out->width;
546 const float *inb = in + (size_t)ch * roi_in->width * (k + roi_out->y - roi_in->y)
547 + ch * (roi_out->x - roi_in->x);
548 memcpy(outb, inb, sizeof(float) * roi_out->width * ch);
549 }
550
551 // iterate through all forms
553 int pos = 0;
554 if(grp && (grp->type & DT_MASKS_GROUP))
555 {
556 for(const GList *forms = grp->points; (pos < 64) && forms; pos++, forms = g_list_next(forms))
557 {
558 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
559 // we get the spot
560 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
561 if(IS_NULL_PTR(form))
562 {
563 continue;
564 }
565
566 // if the form is outside the roi, we just skip it
567 if(!masks_form_is_in_roi(self, pipe, piece, form, roi_in, roi_out))
568 {
569 continue;
570 }
571
572 if(d->clone_algo[pos] == 1 && (form->type & DT_MASKS_CIRCLE))
573 {
575
576 dt_boundingbox_t points;
577 masks_point_denormalize(pipe, roi_in, circle->center, 1, points);
578 masks_point_denormalize(pipe, roi_in, form->source, 1, points + 2);
579
581 {
582 continue;
583 }
584
585 // convert from world space:
586 float radius10[2] = { circle->radius, circle->radius };
587 float radf[2];
588 masks_point_denormalize(pipe, roi_in, radius10, 1, radf);
589
590 const int rad = MIN(radf[0], radf[1]);
591 const int posx = points[0] - rad;
592 const int posy = points[1] - rad;
593 const int posx_source = points[2] - rad;
594 const int posy_source = points[3] - rad;
595 const int dx = posx - posx_source;
596 const int dy = posy - posy_source;
597 const int fw = 2 * rad, fh = 2 * rad;
598
599 float *filter = malloc(sizeof(float) * (2 * rad + 1));
600 if(IS_NULL_PTR(filter)) return 1;
601
602 if(rad > 0)
603 {
604 for(int k = -rad; k <= rad; k++)
605 {
606 const float kk = 1.0f - fabsf(k / (float)rad);
607 filter[rad + k] = kk * kk * (3.0f - 2.0f * kk);
608 }
609 }
610 else
611 {
612 filter[0] = 1.0f;
613 }
614
615 for(int yy = posy; yy < posy + fh; yy++)
616 {
617 // we test if we are inside roi_out
618 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
619 // we test if the source point is inside roi_in
620 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
621 for(int xx = posx; xx < posx + fw; xx++)
622 {
623 // we test if we are inside roi_out
624 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
625 // we test if the source point is inside roi_in
626 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
627
628 const float f = filter[xx - posx + 1] * filter[yy - posy + 1];
629 for(int c = 0; c < ch; c++)
630 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
631 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
632 + in[ch * ((size_t)roi_in->width * (yy - posy + posy_source - roi_in->y) + xx - posx
633 + posx_source - roi_in->x) + c] * f;
634 }
635 }
636
637 dt_free(filter);
638 }
639 else
640 {
641 // we get the mask
642 float *mask = NULL;
643 int posx, posy, width, height;
644 if(dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &width, &height, &posx, &posy) != 0)
645 {
646 return 1;
647 }
648 if(width < 1 || height < 1)
649 {
651 continue;
652 }
653 const int fts = posy * roi_in->scale;
654 const int fhs = height * roi_in->scale;
655 const int fls = posx * roi_in->scale;
656 const int fws = width * roi_in->scale;
657 int dx = 0, dy = 0;
658
659 // now we search the delta with the source
660 if(!masks_get_delta(self, pipe, roi_in, form, &dx, &dy))
661 {
663 continue;
664 }
665
666 if(dx != 0 || dy != 0)
667 {
668 // now we do the pixel clone
669 for(int yy = fts + 1; yy < fts + fhs - 1; yy++)
670 {
671 // we test if we are inside roi_out
672 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
673 // we test if the source point is inside roi_in
674 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
675 for(int xx = fls + 1; xx < fls + fws - 1; xx++)
676 {
677 // we test if we are inside roi_out
678 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
679 // we test if the source point is inside roi_in
680 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
681
682 const float f = mask[((int)((yy - fts) / roi_in->scale)) * width
683 + (int)((xx - fls) / roi_in->scale)] * grpt->opacity;
684
685 for(int c = 0; c < ch; c++)
686 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
687 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
688 + in[ch * ((size_t)roi_in->width * (yy - dy - roi_in->y) + xx - dx - roi_in->x) + c] * f;
689 }
690 }
691 }
693 }
694 }
695 }
696 return 0;
697}
698
699int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
700{
701 const dt_iop_roi_t *const roi_in = &piece->roi_in;
702 const dt_iop_roi_t *const roi_out = &piece->roi_out;
703 const float *in = (float *)i;
704 float *out = (float *)o;
705 return _process(self, pipe, piece, in, out, roi_in, roi_out, piece->dsc_in.channels);
706}
707
709 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
710 const dt_iop_roi_t *const roi_out)
711{
712 (void)pipe;
713 if(_process(self, pipe, piece, in, out, roi_in, roi_out, 1)) return;
714}
715
718{
719 // we don't need global data:
720 module->global_data = NULL; // malloc(sizeof(dt_iop_spots_global_data_t));
721 module->params = calloc(1, sizeof(dt_iop_spots_params_t));
722 module->default_params = calloc(1, sizeof(dt_iop_spots_params_t));
723 // our module is disabled by default
724 // by default:
725 module->default_enabled = 0;
726 module->params_size = sizeof(dt_iop_spots_params_t);
727 module->gui_data = NULL;
728 // init defaults:
729 dt_iop_spots_params_t tmp = (dt_iop_spots_params_t){ { 0 }, { 2 } };
730
731 memcpy(module->default_params, &tmp, sizeof(dt_iop_spots_params_t));
732}
733
734void gui_focus(struct dt_iop_module_t *self, gboolean in)
735{
736 if(self->enabled)
737 {
739
740 if(in)
741 {
743
744 // update edit shapes status
747 //only toggle shape show button if shapes exist
748 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
749 {
751
752 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
754 && (self->dev->gui_module == self));
755 }
756 else
757 {
758 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
759 }
760 }
761 else
762 {
763 // lost focus, hide all shapes
764 if (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
766 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
767 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
768 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
769 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
771 }
772 }
773}
774
778{
779 memcpy(piece->data, params, sizeof(dt_iop_spots_params_t));
780}
781
783{
784 piece->data = dt_calloc_align(sizeof(dt_iop_spots_data_t));
785 piece->data_size = sizeof(dt_iop_spots_data_t);
786}
787
789{
790 dt_free_align(piece->data);
791 piece->data = NULL;
792}
793
796{
797 _resynch_params(self);
799 // update clones count
801 guint nb = 0;
802 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
803 gchar *str = g_strdup_printf("%d", nb);
804 gtk_label_set_text(g->label, str);
805 dt_free(str);
806
807 // enable/disable shapes toolbar
808 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE));
809 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_POLYGON));
810 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE));
811
812 // update edit shapes status
814
815 //only toggle shape show button if shapes exist
816 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
817 {
818 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
819 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
820 }
821 else
822 {
823 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
824 }
826}
827
829{
831
832 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
833
834 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
835 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("number of strokes:")), FALSE, TRUE, 0);
836 g->label = GTK_LABEL(dt_ui_label_new("-1"));
837 gtk_widget_set_tooltip_text(hbox, _("click on a shape and drag on canvas.\nuse the mouse wheel "
838 "to adjust size.\nright click to remove a shape."));
839
840 g->bt_edit_masks = dt_iop_togglebutton_new(self, NULL, N_("show and edit shapes"), NULL,
841 G_CALLBACK(_edit_masks), TRUE, 0, 0,
843
844 g->bt_path = dt_iop_togglebutton_new(self, N_("shapes"), N_("add path"), N_("add multiple paths"),
845 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
847
848 g->bt_ellipse = dt_iop_togglebutton_new(self, N_("shapes"), N_("add ellipse"), N_("add multiple ellipses"),
849 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
851
852 g->bt_circle = dt_iop_togglebutton_new(self, N_("shapes"), N_("add circle"), N_("add multiple circles"),
853 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
855
856 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->label), FALSE, TRUE, 0);
857 gtk_box_pack_start(GTK_BOX(self->widget), hbox, TRUE, TRUE, 0);
858}
859
860void gui_reset(struct dt_iop_module_t *self)
861{
862 // hide the previous masks
864}
865
866// clang-format off
867// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
868// vim: shiftwidth=2 expandtab tabstop=2 cindent
869// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
870// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
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_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
const dt_aligned_pixel_t f
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
int type
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:861
darktable_t darktable
Definition darktable.c:181
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
float dt_boundingbox_t[4]
Definition darktable.h:709
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#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
void dt_iop_params_t
Definition dev_history.h:41
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1557
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
void dtgtk_cairo_paint_masks_circle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_ellipse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Paint a 45 deg-rotated ellipse that touches the unit square boundaries.
void dtgtk_cairo_paint_masks_polygon(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_edit(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
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
@ IOP_FLAGS_INTERNAL_MASKS
Definition imageop.h:179
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:175
@ IOP_GROUP_EFFECTS
Definition imageop.h:142
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
static const float x
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
int dt_masks_legacy_params(dt_develop_t *dev, void *params, const int old_version, const int new_version)
void dt_masks_change_form_gui(dt_masks_form_t *newform)
@ DT_MASKS_EDIT_OFF
Definition masks.h:201
@ DT_MASKS_EDIT_FULL
Definition masks.h:202
void dt_masks_reset_form_gui(void)
void dt_masks_write_masks_history_item(const int32_t imgid, const int num, dt_masks_form_t *form)
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_POLYGON
Definition masks.h:132
@ DT_MASKS_ELLIPSE
Definition masks.h:136
@ DT_MASKS_CLONE
Definition masks.h:134
@ DT_MASKS_CIRCLE
Definition masks.h:131
@ DT_MASKS_GROUP
Definition masks.h:133
int dt_masks_version(void)
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
gboolean dt_masks_creation_mode_enter(dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
static int dt_masks_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition masks.h:853
int dt_masks_get_source_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
int dt_masks_get_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
struct _GtkWidget GtkWidget
Definition splash.h:29
void init(dt_iop_module_t *module)
Definition spots.c:717
const char ** description(struct dt_iop_module_t *self)
Definition spots.c:91
int default_group()
Definition spots.c:100
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o)
Definition spots.c:699
static gboolean _add_shape(GtkWidget *widget, dt_iop_module_t *self)
Definition spots.c:293
static void _resynch_params(struct dt_iop_module_t *self)
Definition spots.c:197
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:776
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition spots.c:795
void distort_mask(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition spots.c:708
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition spots.c:734
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:782
const char * name()
Definition spots.c:81
void gui_reset(struct dt_iop_module_t *self)
Definition spots.c:860
static gboolean _add_shape_callback(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
Definition spots.c:326
void gui_init(dt_iop_module_t *self)
Definition spots.c:828
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:110
int flags()
Definition spots.c:105
static int _shape_is_being_added(dt_iop_module_t *self, const int shape_type)
Definition spots.c:267
static gboolean _reset_form_creation(GtkWidget *widget, dt_iop_module_t *self)
Definition spots.c:236
static __DT_CLONE_TARGETS__ void masks_point_denormalize(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi, const float *points, size_t points_count, float *new)
Definition spots.c:476
static gboolean _edit_masks(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
Definition spots.c:341
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:788
struct dt_iop_spots_params_t dt_iop_spots_data_t
Definition spots.c:77
static __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 float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int ch)
Definition spots.c:534
const char * deprecated_msg()
Definition spots.c:86
void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition spots.c:418
void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in)
Definition spots.c:410
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 spots.c:115
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_develop_t * develop
Definition darktable.h:770
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
dt_image_t image_storage
Definition develop.h:259
struct dt_iop_module_t * gui_module
Definition develop.h:165
GList * history
Definition develop.h:275
struct dt_masks_form_gui_t * form_gui
Definition develop.h:327
GList * forms
Definition develop.h:321
int32_t reset
Definition gtk.h:172
int32_t id
Definition image.h:319
unsigned int channels
Definition format.h:54
GtkDarktableToggleButton * off
Definition imageop.h:339
gpointer blend_data
Definition imageop.h:318
dt_iop_params_t * default_params
Definition imageop.h:307
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:316
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
gboolean enabled
Definition imageop.h:298
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
GtkLabel * label
Definition spots.c:73
GtkWidget * bt_circle
Definition spots.c:74
GtkWidget * bt_ellipse
Definition spots.c:74
GtkWidget * bt_edit_masks
Definition spots.c:74
GtkWidget * bt_path
Definition spots.c:74
int clone_algo[64]
Definition spots.c:68
dt_masks_edit_mode_t edit_mode
Definition masks.h:463
dt_iop_module_t * creation_module
Definition masks.h:505
gboolean creation
Definition masks.h:503
dt_masks_type_t type
Definition masks.h:378
float source[2]
Definition masks.h:385
GList * points
Definition masks.h:377
#define MIN(a, b)
Definition thinplate.c:32