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
46#include "config.h"
47#endif
48#include "develop/masks_gui.h"
49#include "system/macros.h"
50#include "system/openmp.h"
52#include "system/mem_alloc.h"
54#include "control/control.h"
55#include "develop/blend.h"
56#include "develop/blend_gui.h"
57#include "develop/imageop.h"
58#include "develop/imageop_gui.h"
59#include "develop/masks.h"
60
61#include "iop/iop_api.h"
62#include <gtk/gtk.h>
63#include <stdlib.h>
64#include "widgets/label.h"
65
66
67// this is the version of the modules parameters,
68// and includes version information about compile-time dt
70
71typedef struct dt_iop_spots_params_t
72{
73 int clone_id[64];
74 int clone_algo[64];
76
82
84
85
86// this returns a translatable name
87const char *name()
88{
89 return _("spot removal");
90}
91
92const char *deprecated_msg()
93{
94 return _("this module is deprecated. please use the retouch module instead.");
95}
96
97const char **description(struct dt_iop_module_t *self)
98{
99 return dt_iop_set_description(self, _("remove sensor dust spots"),
100 _("corrective"),
101 _("linear, RGB, scene-referred"),
102 _("geometric, raw"),
103 _("linear, RGB, scene-referred"));
104}
105
107{
108 return IOP_GROUP_EFFECTS;
109}
110
115
120
121int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version,
122 void *new_params, const int new_version)
123{
124 if(old_version == 1 && new_version == 2)
125 {
126 typedef struct dt_iop_spots_v1_t
127 {
128 float x, y;
129 float xc, yc;
130 float radius;
131 } dt_iop_spots_v1_t;
132 typedef struct dt_iop_spots_params_v1_t
133 {
134 int num_spots;
135 dt_iop_spots_v1_t spot[32];
136 } dt_iop_spots_params_v1_t;
137
138 dt_iop_spots_params_v1_t *o = (dt_iop_spots_params_v1_t *)old_params;
141
142 *n = *d; // start with a fresh copy of default parameters
143 for(int i = 0; i < o->num_spots; i++)
144 {
145 // we have to register a new circle mask
147
148 // spots v1 was before raw orientation changes
149 form->version = 1;
150
152 circle->center[0] = o->spot[i].x;
153 circle->center[1] = o->spot[i].y;
154 circle->radius = o->spot[i].radius;
155 circle->border = 0.0f;
156 form->points = g_list_append(form->points, circle);
157 form->source[0] = o->spot[i].xc;
158 form->source[1] = o->spot[i].yc;
159
160 // adapt for raw orientation changes
161 dt_masks_legacy_params(self->dev, form, form->version, dt_masks_version());
162
163 dt_masks_gui_form_save_creation(self->dev, self, form, NULL);
164
165 // and add it to the module params
166 n->clone_id[i] = form->formid;
167 n->clone_algo[i] = 2;
168 }
169
170 // look for spot history num, if not found then it will be added
171 // at the end of the list.
172 int last_spot_num = 0;
173 int count = 0;
174 for(GList *l = self->dev->history; l; l = g_list_next(l))
175 {
177 count++;
178 if(!strcmp(item->op_name, "spots")) last_spot_num = item->num;
179 }
180
181 if(last_spot_num == 0) last_spot_num = count;
182
183 // record all forms for this module & history num
184 // also record the group in the blend params.
185
187
188 for(GList *l = self->dev->forms; l; l = g_list_next(l))
189 {
190 dt_masks_form_t *form = (dt_masks_form_t *)l->data;
191 if(form && (form->type & DT_MASKS_GROUP))
192 {
193 bp->mask_id = form->formid;
194 }
195 dt_masks_write_masks_history_item(self->dev->image_storage.id, last_spot_num, form);
196 }
197
198 return 0;
199 }
200 return 1;
201}
202
203static void _resynch_params(struct dt_iop_module_t *self)
204{
207
208 // we create 2 new buffers
209 int nid[64] = { 0 };
210 int nalgo[64] = { 2 };
211
212 // we go through all forms in blend params
214 if(grp && (grp->type & DT_MASKS_GROUP))
215 {
216 int i = 0;
217 for(GList *forms = grp->points; (i < 64) && forms; forms = g_list_next(forms))
218 {
219 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
220 nid[i] = grpt->formid;
221 for(int j = 0; j < 64; j++)
222 {
223 if(p->clone_id[j] == nid[i])
224 {
225 nalgo[i] = p->clone_algo[j];
226 break;
227 }
228 }
229 i++;
230 }
231 }
232
233 // we reaffect params
234 for(int i = 0; i < 64; i++)
235 {
236 p->clone_algo[i] = nalgo[i];
237 p->clone_id[i] = nid[i];
238 }
239}
240
241
242static gboolean _reset_form_creation(GtkWidget *widget, dt_iop_module_t *self)
243{
245
246 // we check the nb of shapes limit
248 guint nb = 0;
249 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
250
251 if(nb >= 64)
252 {
253 dt_control_log(_("spot module is limited to 64 shapes. please add a new instance !"));
254 }
255
256 if(nb < 64
257 && (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_path))
258 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle))
259 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse))))
260 {
261 // we unset the creation mode
262 dt_masks_change_form_gui(self->dev, NULL);
263 }
264 if(widget != g->bt_path || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
265 if(widget != g->bt_circle || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
266 if(widget != g->bt_ellipse || nb >= 64) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
267
268 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
269
270 return (nb < 64);
271}
272
273static int _shape_is_being_added(dt_iop_module_t *self, const int shape_type)
274{
275 int being_added = 0;
276
277 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)
278 && (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self))
279 {
281 {
282 GList *forms = dt_masks_get_visible_form(self->dev)->points;
283 if(!IS_NULL_PTR(forms))
284 {
285 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
286 if(grpt)
287 {
288 const dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
289 if(!IS_NULL_PTR(form)) being_added = (form->type & shape_type);
290 }
291 }
292 }
293 else
294 being_added = (dt_masks_get_visible_form(self->dev)->type & shape_type);
295 }
296 return being_added;
297}
298
299static gboolean _add_shape(GtkWidget *widget, dt_iop_module_t *self)
300{
301 //turn module on (else shape creation won't work)
302 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), TRUE);
303
304 //switch mask edit mode off
306 if(bd) bd->masks_shown = DT_MASKS_EDIT_OFF;
307
308 if(!_reset_form_creation(widget, self)) return TRUE;
309 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return FALSE;
310
312 // we want to be sure that the iop has focus
314 // we create the new form
316 if(widget == g->bt_path)
318 else if(widget == g->bt_circle)
320 else if(widget == g->bt_ellipse)
322
323
324 const dt_masks_type_t masks_type = (type | DT_MASKS_CLONE);
325 dt_masks_creation_mode_enter(self->dev, self, masks_type);
326
327
329 return FALSE;
330}
331
332static gboolean _add_shape_callback(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
333{
334 if(dt_gui_widgets_suppressed()) return FALSE;
335
337
338 _add_shape(widget, self);
339
340 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE));
341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE));
342 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_POLYGON));
343
344 return TRUE;
345}
346
347static gboolean _edit_masks(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
348{
349 if(dt_gui_widgets_suppressed()) return FALSE;
350
351 // if we don't have the focus, request for it and quit, gui_focus() do the rest
352 if(self->dev->gui_module != self)
353 {
355 return FALSE;
356 }
357
360
361 //hide all shapes and free if some are in creation
362 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
363 dt_masks_change_form_gui(self->dev, NULL);
364
365 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
366 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
367 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
368
370
372
374
375 // update edit shapes status
377 dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
378 //only toggle shape show button if shapes exist
379 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
380 {
381 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
382 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
383 }
384 else
385 {
386 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
387 }
388
390
392
393 return TRUE;
394}
395
396void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
397 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
398 const dt_iop_roi_t *roi_in)
399{
400 *roi_out = *roi_in;
401}
402
403// needed if mask dest is in roi and mask src is not
404void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
406 const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
407{
408 *roi_in = *roi_out;
409
410 int roir = roi_in->width + roi_in->x;
411 int roib = roi_in->height + roi_in->y;
412 int roix = roi_in->x;
413 int roiy = roi_in->y;
414
415 // This runs on the pipeline thread: the group id comes from the piece and the shapes from the
416 // run's snapshot, never from self->blend_params and the GUI-owned dev->forms.
417 const dt_develop_blend_params_t *bp = (const dt_develop_blend_params_t *)piece->blendop_data;
418
419 // We iterate through all spots or polygons
421 if(grp && (grp->type & DT_MASKS_GROUP))
422 {
423 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
424 {
425 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
426 // we get the spot, and skip it when it is outside the roi
428 if(IS_NULL_PTR(form) || !dt_masks_form_is_in_roi(self, pipe, piece, form, roi_in, roi_out)) continue;
429
430 // we get the area for the source
431 dt_masks_area_t area;
432 if(dt_masks_get_source_area(self, pipe, piece, form, &area) != DT_MASKS_RASTER_OK) continue;
433 dt_masks_area_scale(&area, roi_in->scale);
434
435 // we enlarge the roi if needed
436 roiy = fminf(area.y, roiy);
437 roix = fminf(area.x, roix);
438 roir = fmaxf(area.x + area.width, roir);
439 roib = fmaxf(area.y + area.height, roib);
440 }
441 }
442
443 // now we set the values
444 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
445 roi_in->x = CLAMP(roix, 0, scwidth - 1);
446 roi_in->y = CLAMP(roiy, 0, scheight - 1);
447 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
448 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
449}
450
453 const float *points, size_t points_count, float *new)
454{
455 const float scalex = pipe->iwidth * roi->scale, scaley = pipe->iheight * roi->scale;
456
457 for(size_t i = 0; i < points_count * 2; i += 2)
458 {
459 new[i] = points[i] * scalex;
460 new[i + 1] = points[i + 1] * scaley;
461 }
462}
463
464static inline __attribute__((always_inline)) int masks_point_calc_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
465 const dt_iop_roi_t *roi, const float *target, const float *source, int *dx,
466 int *dy)
467{
468 dt_boundingbox_t points;
469 masks_point_denormalize(pipe, roi, target, 1, points);
470 masks_point_denormalize(pipe, roi, source, 1, points + 2);
471
473 if(!res) return res;
474
475 *dx = points[0] - points[2];
476 *dy = points[1] - points[3];
477
478 return res;
479}
480
481static inline __attribute__((always_inline)) int masks_get_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
482 const dt_iop_roi_t *roi,
483 dt_masks_form_t *form, int *dx, int *dy)
484{
485 int res = 0;
486
487 if(form->type & DT_MASKS_POLYGON)
488 {
490
491 res = masks_point_calc_delta(self, pipe, roi, pt->node, form->source, dx, dy);
492 }
493 else if(form->type & DT_MASKS_CIRCLE)
494 {
496
497 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
498 }
499 else if(form->type & DT_MASKS_ELLIPSE)
500 {
502
503 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
504 }
505
506 return res;
507}
508
510// The buffers a spot is cloned between.
511typedef struct _spots_buffers_t
512{
513 const float *in;
514 float *out;
517 int ch;
519
520// Clone a circle spot with the historical smoothstep falloff (clone_algo 1). Returns non-zero on
521// allocation failure.
522// Blend the disc of radius `rad` centred on (posx + rad, posy + rad) with the same disc shifted
523// by (-dx, -dy), weighted by the separable falloff `filter`. Pixels outside either ROI are left
524// alone.
525static void _spots_blend_disc(const _spots_buffers_t *buffers, const float *const filter, const int rad,
526 const int posx, const int posy, const int dx, const int dy)
527{
528 const float *in = buffers->in;
529 float *out = buffers->out;
530 const dt_iop_roi_t *const roi_in = buffers->roi_in;
531 const dt_iop_roi_t *const roi_out = buffers->roi_out;
532 const int ch = buffers->ch;
533 const int fw = 2 * rad;
534 const int fh = 2 * rad;
535
536 for(int yy = posy; yy < posy + fh; yy++)
537 {
538 // we test if we are inside roi_out
539 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
540 // we test if the source point is inside roi_in
541 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
542 for(int xx = posx; xx < posx + fw; xx++)
543 {
544 // we test if we are inside roi_out
545 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
546 // we test if the source point is inside roi_in
547 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
548
549 const float f = filter[xx - posx + 1] * filter[yy - posy + 1];
550 for(int c = 0; c < ch; c++)
551 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
552 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
553 + in[ch * ((size_t)roi_in->width * (yy - dy - roi_in->y) + xx - dx - roi_in->x) + c] * f;
554 }
555 }
556}
557
558static int _spots_clone_circle(const struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
559 const dt_masks_form_t *form, const _spots_buffers_t *buffers)
560{
561 const dt_iop_roi_t *const roi_in = buffers->roi_in;
562
564
565 dt_boundingbox_t points;
566 masks_point_denormalize(pipe, roi_in, circle->center, 1, points);
567 masks_point_denormalize(pipe, roi_in, form->source, 1, points + 2);
568
570 {
571 return 0;
572 }
573
574 // convert from world space:
575 float radius10[2] = { circle->radius, circle->radius };
576 float radf[2];
577 masks_point_denormalize(pipe, roi_in, radius10, 1, radf);
578
579 const int rad = MIN(radf[0], radf[1]);
580 const int posx = points[0] - rad;
581 const int posy = points[1] - rad;
582 const int posx_source = points[2] - rad;
583 const int posy_source = points[3] - rad;
584 const int dx = posx - posx_source;
585 const int dy = posy - posy_source;
586
587 float *filter = malloc(sizeof(float) * (2 * rad + 1));
588 if(IS_NULL_PTR(filter)) return 1;
589
590 if(rad > 0)
591 {
592 for(int k = -rad; k <= rad; k++)
593 {
594 const float kk = 1.0f - fabsf(k / (float)rad);
595 filter[rad + k] = kk * kk * (3.0f - 2.0f * kk);
596 }
597 }
598 else
599 {
600 filter[0] = 1.0f;
601 }
602
603 _spots_blend_disc(buffers, filter, rad, posx, posy, dx, dy);
604
605 dt_free(filter);
606 return 0;
607}
608
609// Clone any spot through its rasterised mask. Returns non-zero when the mask cannot be built.
611 const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const float opacity,
612 const _spots_buffers_t *buffers)
613{
614 const float *in = buffers->in;
615 float *out = buffers->out;
616 const dt_iop_roi_t *const roi_in = buffers->roi_in;
617 const dt_iop_roi_t *const roi_out = buffers->roi_out;
618 const int ch = buffers->ch;
619
620 // we get the mask
621 float *mask = NULL;
622 dt_masks_area_t area;
623 /* Only a real failure aborts the module. A shape with nothing to draw (EMPTY) falls
624 * through to the zero-size check below and is skipped like any other empty shape --
625 * aborting here would silently drop every REMAINING shape in the group. The out
626 * parameters are guaranteed written on every outcome, so that check is safe to reach. */
627 if(dt_masks_get_mask(self, pipe, piece, form, &mask, &area) == DT_MASKS_RASTER_ERROR) return 1;
628
629 // now we search the delta with the source; nothing to clone without one
630 int dx = 0;
631 int dy = 0;
632 if(area.width < 1 || area.height < 1 || !masks_get_delta(self, pipe, roi_in, form, &dx, &dy)
633 || (dx == 0 && dy == 0))
634 {
636 return 0;
637 }
638
639 const int fts = area.y * roi_in->scale;
640 const int fhs = area.height * roi_in->scale;
641 const int fls = area.x * roi_in->scale;
642 const int fws = area.width * roi_in->scale;
643
644 // now we do the pixel clone
645 for(int yy = fts + 1; yy < fts + fhs - 1; yy++)
646 {
647 // we test if we are inside roi_out
648 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
649 // we test if the source point is inside roi_in
650 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
651 for(int xx = fls + 1; xx < fls + fws - 1; xx++)
652 {
653 // we test if we are inside roi_out
654 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
655 // we test if the source point is inside roi_in
656 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
657
658 const float f = mask[((int)((yy - fts) / roi_in->scale)) * area.width
659 + (int)((xx - fls) / roi_in->scale)] * opacity;
660
661 for(int c = 0; c < ch; c++)
662 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
663 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
664 + in[ch * ((size_t)roi_in->width * (yy - dy - roi_in->y) + xx - dx - roi_in->x) + c] * f;
665 }
666 }
668 return 0;
669}
670
672 const float *const in,
673 float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int ch)
674{
676 // Pipeline thread: see modify_roi_in() for why the piece and the run's snapshot, not the module.
677 const dt_develop_blend_params_t *bp = (const dt_develop_blend_params_t *)piece->blendop_data;
678
679// we don't modify most of the image:
681 for(int k = 0; k < roi_out->height; k++)
682 {
683 float *outb = out + (size_t)ch * k * roi_out->width;
684 const float *inb = in + (size_t)ch * roi_in->width * (k + roi_out->y - roi_in->y)
685 + ch * (roi_out->x - roi_in->x);
686 memcpy(outb, inb, sizeof(float) * roi_out->width * ch);
687 }
688
689 const _spots_buffers_t buffers = { .in = in, .out = out, .roi_in = roi_in, .roi_out = roi_out, .ch = ch };
690
691 // iterate through all forms
693 int pos = 0;
694 if(grp && (grp->type & DT_MASKS_GROUP))
695 {
696 for(const GList *forms = grp->points; (pos < 64) && forms; pos++, forms = g_list_next(forms))
697 {
698 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
699 // we get the spot
701 if(IS_NULL_PTR(form))
702 {
703 continue;
704 }
705
706 // if the form is outside the roi, we just skip it
707 if(!dt_masks_form_is_in_roi(self, pipe, piece, form, roi_in, roi_out))
708 {
709 continue;
710 }
711
712 const int err = (d->clone_algo[pos] == 1 && (form->type & DT_MASKS_CIRCLE))
713 ? _spots_clone_circle(self, pipe, form, &buffers)
714 : _spots_clone_mask(self, pipe, piece, form, grpt->opacity, &buffers);
715 if(err) return err;
716 }
717 }
718 return 0;
719}
720
721int 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)
722{
723 const dt_iop_roi_t *const roi_in = &piece->roi_in;
724 const dt_iop_roi_t *const roi_out = &piece->roi_out;
725 const float *in = (float *)i;
726 float *out = (float *)o;
727 return _process(self, pipe, piece, in, out, roi_in, roi_out, piece->dsc_in.channels);
728}
729
731 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
732 const dt_iop_roi_t *const roi_out)
733{
734 (void)pipe;
735 if(_process(self, pipe, piece, in, out, roi_in, roi_out, 1)) return;
736}
737
740{
741 // we don't need global data:
742 module->global_data = NULL; // malloc(sizeof(dt_iop_spots_global_data_t));
743 module->params = calloc(1, sizeof(dt_iop_spots_params_t));
744 module->default_params = calloc(1, sizeof(dt_iop_spots_params_t));
745 // our module is disabled by default
746 // by default:
747 module->default_enabled = 0;
748 module->params_size = sizeof(dt_iop_spots_params_t);
749 // init defaults:
750 dt_iop_spots_params_t tmp = (dt_iop_spots_params_t){ { 0 }, { 2 } };
751
752 memcpy(module->default_params, &tmp, sizeof(dt_iop_spots_params_t));
753}
754
755void gui_focus(struct dt_iop_module_t *self, gboolean in)
756{
757 if(self->enabled)
758 {
760
761 if(in)
762 {
764
765 // update edit shapes status
768 //only toggle shape show button if shapes exist
769 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
770 {
772
773 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
775 && (self->dev->gui_module == self));
776 }
777 else
778 {
779 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
780 }
781 }
782 else
783 {
784 // lost focus, hide all shapes
785 if (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
786 dt_masks_change_form_gui(self->dev, NULL);
787 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
788 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
789 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
790 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
792 }
793 }
794}
795
802
808
810{
811 dt_free_align(piece->data);
812 piece->data = NULL;
813}
814
817{
818 _resynch_params(self);
820 // update clones count
822 guint nb = 0;
823 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
824 gchar *str = g_strdup_printf("%d", nb);
825 gtk_label_set_text(g->label, str);
826 dt_free(str);
827
828 // enable/disable shapes toolbar
829 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE));
830 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_POLYGON));
831 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE));
832
833 // update edit shapes status
835
836 //only toggle shape show button if shapes exist
837 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
838 {
839 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
840 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
841 }
842 else
843 {
844 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
845 }
847}
848
850{
852
853 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
854
855 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
856 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("number of strokes:")), FALSE, TRUE, 0);
857 g->label = GTK_LABEL(dt_ui_label_new("-1"));
858 gtk_widget_set_tooltip_text(hbox, _("click on a shape and drag on canvas.\nuse the mouse wheel "
859 "to adjust size.\nright click to remove a shape."));
860
861 g->bt_edit_masks = dt_iop_togglebutton_new(self, NULL, N_("show and edit shapes"), NULL,
862 G_CALLBACK(_edit_masks), TRUE, 0, 0,
864
865 g->bt_path = dt_iop_togglebutton_new(self, N_("shapes"), N_("add path"), N_("add multiple paths"),
866 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
868
869 g->bt_ellipse = dt_iop_togglebutton_new(self, N_("shapes"), N_("add ellipse"), N_("add multiple ellipses"),
870 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
872
873 g->bt_circle = dt_iop_togglebutton_new(self, N_("shapes"), N_("add circle"), N_("add multiple circles"),
874 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
876
877 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->label), FALSE, TRUE, 0);
878 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox, TRUE, TRUE, 0);
879}
880
881void gui_reset(struct dt_iop_module_t *self)
882{
883 // hide the previous masks
885}
886
887// clang-format off
888// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
889// vim: shiftwidth=2 expandtab tabstop=2 cindent
890// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
891// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
The blending panel's GUI state and API, cut out of develop/blend.h.
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
static const float x
const float f
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_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
void dt_iop_params_t
Definition dev_history.h:43
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:1864
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
const int res
Definition dtpthread.h:351
float dt_boundingbox_t[4]
Definition image.h:83
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:1909
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_INTERNAL_MASKS
Definition imageop.h:198
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:194
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
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 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
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
_lib_location_type_t type
Definition location.c:1
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
int dt_masks_legacy_params(dt_develop_t *dev, void *params, const int old_version, const int new_version)
Definition masks.c:821
dt_masks_raster_result_t dt_masks_get_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, dt_masks_area_t *area)
Definition masks.c:1647
dt_masks_form_t * dt_masks_get_from_id_in_pipe(const dt_dev_pixelpipe_t *pipe, int id)
Definition masks.c:933
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_OK
Definition masks.h:314
void dt_masks_write_masks_history_item(const int32_t imgid, const int num, dt_masks_form_t *form)
Definition masks.c:1131
int dt_masks_version(void)
Definition masks.c:502
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:867
void dt_masks_area_scale(dt_masks_area_t *area, const double scale)
Definition masks.c:450
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
dt_masks_raster_result_t dt_masks_get_source_area(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, dt_masks_area_t *area)
Definition masks.c:486
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
gboolean dt_masks_form_is_in_roi(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out)
Definition masks.c:475
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4894
gboolean dt_masks_creation_mode_enter(dt_develop_t *dev, dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
Definition masks_gui.c:5762
void dt_masks_gui_form_save_creation(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui)
Save the form creation right after a shape has been finished drawing.
Definition masks_gui.c:2525
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
#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
#define o
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define dt_pixelpipe_cache_free_align(mem)
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
void init(dt_iop_module_t *module)
Definition spots.c:739
const char ** description(struct dt_iop_module_t *self)
Definition spots.c:97
int default_group()
Definition spots.c:106
static 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:671
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:721
static gboolean _add_shape(GtkWidget *widget, dt_iop_module_t *self)
Definition spots.c:299
static void _resynch_params(struct dt_iop_module_t *self)
Definition spots.c:203
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:797
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition spots.c:816
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:730
static int _spots_clone_circle(const struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_masks_form_t *form, const _spots_buffers_t *buffers)
Definition spots.c:558
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition spots.c:755
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:803
const char * name()
Definition spots.c:87
void gui_reset(struct dt_iop_module_t *self)
Definition spots.c:881
static gboolean _add_shape_callback(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
Definition spots.c:332
void gui_init(dt_iop_module_t *self)
Definition spots.c:849
static int _spots_clone_mask(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const float opacity, const _spots_buffers_t *buffers)
Definition spots.c:610
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:116
int flags()
Definition spots.c:111
static int _shape_is_being_added(dt_iop_module_t *self, const int shape_type)
Definition spots.c:273
static gboolean _reset_form_creation(GtkWidget *widget, dt_iop_module_t *self)
Definition spots.c:242
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:452
static gboolean _edit_masks(GtkWidget *widget, GdkEventButton *e, dt_iop_module_t *self)
Definition spots.c:347
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:809
struct dt_iop_spots_params_t dt_iop_spots_data_t
Definition spots.c:83
const char * deprecated_msg()
Definition spots.c:92
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:404
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:396
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:121
static void _spots_blend_disc(const _spots_buffers_t *buffers, const float *const filter, const int rad, const int posx, const int posy, const int dx, const int dy)
Definition spots.c:525
float * out
Definition spots.c:514
const dt_iop_roi_t * roi_in
Definition spots.c:515
const float * in
Definition spots.c:513
const dt_iop_roi_t * roi_out
Definition spots.c:516
dt_image_t image_storage
Definition develop.h:225
struct dt_iop_module_t * gui_module
Definition develop.h:170
GList * history
Definition develop.h:259
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
GList * forms
Definition develop.h:304
int32_t id
Definition image.h:410
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
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
GtkLabel * label
Definition spots.c:79
GtkWidget * bt_circle
Definition spots.c:80
GtkWidget * bt_ellipse
Definition spots.c:80
GtkWidget * bt_edit_masks
Definition spots.c:80
GtkWidget * bt_path
Definition spots.c:80
int clone_algo[64]
Definition spots.c:74
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
dt_masks_type_t type
Definition masks.h:254
float source[2]
Definition masks.h:261
GList * points
Definition masks.h:253
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
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)