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
117{
118 return IOP_CS_RGB;
119}
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
396static inline __attribute__((always_inline)) gboolean masks_form_is_in_roi(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
397 const dt_dev_pixelpipe_iop_t *piece,
398 dt_masks_form_t *form, const dt_iop_roi_t *roi_in,
399 const dt_iop_roi_t *roi_out)
400{
401 // we get the area for the form
402 int fl, ft, fw, fh;
403 dt_dev_pixelpipe_iop_t piece_copy = *piece;
404
405 if(dt_masks_get_area(self, (dt_dev_pixelpipe_t *)pipe, &piece_copy, form, &fw, &fh, &fl, &ft)
407 return FALSE;
408
409 // is the form outside of the roi?
410 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
411 if(ft >= roi_out->y + roi_out->height || ft + fh <= roi_out->y || fl >= roi_out->x + roi_out->width
412 || fl + fw <= roi_out->x)
413 return FALSE;
414
415 return TRUE;
416}
417
418void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
419 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
420 const dt_iop_roi_t *roi_in)
421{
422 *roi_out = *roi_in;
423}
424
425// needed if mask dest is in roi and mask src is not
426void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
427 struct dt_dev_pixelpipe_iop_t *piece,
428 const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
429{
430 dt_dev_pixelpipe_t *const processing_pipe = (dt_dev_pixelpipe_t *)pipe;
431 *roi_in = *roi_out;
432
433 int roir = roi_in->width + roi_in->x;
434 int roib = roi_in->height + roi_in->y;
435 int roix = roi_in->x;
436 int roiy = roi_in->y;
437
439
440 // We iterate through all spots or polygons
442 if(grp && (grp->type & DT_MASKS_GROUP))
443 {
444 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
445 {
446 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
447 // we get the spot
448 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
449 if(!IS_NULL_PTR(form))
450 {
451 // if the form is outside the roi, we just skip it
452 if(!masks_form_is_in_roi(self, processing_pipe, piece, form, roi_in, roi_out))
453 {
454 continue;
455 }
456
457 // we get the area for the source
458 int fl, ft, fw, fh;
459
460 if(dt_masks_get_source_area(self, processing_pipe, piece, form, &fw, &fh, &fl, &ft)
462 {
463 continue;
464 }
465 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
466
467 // we enlarge the roi if needed
468 roiy = fminf(ft, roiy);
469 roix = fminf(fl, roix);
470 roir = fmaxf(fl + fw, roir);
471 roib = fmaxf(ft + fh, roib);
472 }
473 }
474 }
475
476 // now we set the values
477 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
478 roi_in->x = CLAMP(roix, 0, scwidth - 1);
479 roi_in->y = CLAMP(roiy, 0, scheight - 1);
480 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
481 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
482}
483
485static void masks_point_denormalize(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi,
486 const float *points, size_t points_count, float *new)
487{
488 const float scalex = pipe->iwidth * roi->scale, scaley = pipe->iheight * roi->scale;
489
490 for(size_t i = 0; i < points_count * 2; i += 2)
491 {
492 new[i] = points[i] * scalex;
493 new[i + 1] = points[i + 1] * scaley;
494 }
495}
496
497static inline __attribute__((always_inline)) int masks_point_calc_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
498 const dt_iop_roi_t *roi, const float *target, const float *source, int *dx,
499 int *dy)
500{
501 dt_boundingbox_t points;
502 masks_point_denormalize(pipe, roi, target, 1, points);
503 masks_point_denormalize(pipe, roi, source, 1, points + 2);
504
506 if(!res) return res;
507
508 *dx = points[0] - points[2];
509 *dy = points[1] - points[3];
510
511 return res;
512}
513
514static inline __attribute__((always_inline)) int masks_get_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
515 const dt_iop_roi_t *roi,
516 dt_masks_form_t *form, int *dx, int *dy)
517{
518 int res = 0;
519
520 if(form->type & DT_MASKS_POLYGON)
521 {
523
524 res = masks_point_calc_delta(self, pipe, roi, pt->node, form->source, dx, dy);
525 }
526 else if(form->type & DT_MASKS_CIRCLE)
527 {
529
530 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
531 }
532 else if(form->type & DT_MASKS_ELLIPSE)
533 {
535
536 res = masks_point_calc_delta(self, pipe, roi, pt->center, form->source, dx, dy);
537 }
538
539 return res;
540}
541
543static int _process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
544 const float *const in,
545 float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int ch)
546{
549
550// we don't modify most of the image:
552 for(int k = 0; k < roi_out->height; k++)
553 {
554 float *outb = out + (size_t)ch * k * roi_out->width;
555 const float *inb = in + (size_t)ch * roi_in->width * (k + roi_out->y - roi_in->y)
556 + ch * (roi_out->x - roi_in->x);
557 memcpy(outb, inb, sizeof(float) * roi_out->width * ch);
558 }
559
560 // iterate through all forms
562 int pos = 0;
563 if(grp && (grp->type & DT_MASKS_GROUP))
564 {
565 for(const GList *forms = grp->points; (pos < 64) && forms; pos++, forms = g_list_next(forms))
566 {
567 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
568 // we get the spot
569 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
570 if(IS_NULL_PTR(form))
571 {
572 continue;
573 }
574
575 // if the form is outside the roi, we just skip it
576 if(!masks_form_is_in_roi(self, pipe, piece, form, roi_in, roi_out))
577 {
578 continue;
579 }
580
581 if(d->clone_algo[pos] == 1 && (form->type & DT_MASKS_CIRCLE))
582 {
584
585 dt_boundingbox_t points;
586 masks_point_denormalize(pipe, roi_in, circle->center, 1, points);
587 masks_point_denormalize(pipe, roi_in, form->source, 1, points + 2);
588
590 {
591 continue;
592 }
593
594 // convert from world space:
595 float radius10[2] = { circle->radius, circle->radius };
596 float radf[2];
597 masks_point_denormalize(pipe, roi_in, radius10, 1, radf);
598
599 const int rad = MIN(radf[0], radf[1]);
600 const int posx = points[0] - rad;
601 const int posy = points[1] - rad;
602 const int posx_source = points[2] - rad;
603 const int posy_source = points[3] - rad;
604 const int dx = posx - posx_source;
605 const int dy = posy - posy_source;
606 const int fw = 2 * rad, fh = 2 * rad;
607
608 float *filter = malloc(sizeof(float) * (2 * rad + 1));
609 if(IS_NULL_PTR(filter)) return 1;
610
611 if(rad > 0)
612 {
613 for(int k = -rad; k <= rad; k++)
614 {
615 const float kk = 1.0f - fabsf(k / (float)rad);
616 filter[rad + k] = kk * kk * (3.0f - 2.0f * kk);
617 }
618 }
619 else
620 {
621 filter[0] = 1.0f;
622 }
623
624 for(int yy = posy; yy < posy + fh; yy++)
625 {
626 // we test if we are inside roi_out
627 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
628 // we test if the source point is inside roi_in
629 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
630 for(int xx = posx; xx < posx + fw; xx++)
631 {
632 // we test if we are inside roi_out
633 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
634 // we test if the source point is inside roi_in
635 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
636
637 const float f = filter[xx - posx + 1] * filter[yy - posy + 1];
638 for(int c = 0; c < ch; c++)
639 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
640 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
641 + in[ch * ((size_t)roi_in->width * (yy - posy + posy_source - roi_in->y) + xx - posx
642 + posx_source - roi_in->x) + c] * f;
643 }
644 }
645
646 dt_free(filter);
647 }
648 else
649 {
650 // we get the mask
651 float *mask = NULL;
652 int posx, posy, width, height;
653 /* Only a real failure aborts the module. A shape with nothing to draw (EMPTY) falls
654 * through to the zero-size check below and is skipped like any other empty shape --
655 * aborting here would silently drop every REMAINING shape in the group. The out
656 * parameters are guaranteed written on every outcome, so that check is safe to reach. */
657 if(dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &width, &height, &posx,
658 &posy)
660 {
661 return 1;
662 }
663 if(width < 1 || height < 1)
664 {
666 continue;
667 }
668 const int fts = posy * roi_in->scale;
669 const int fhs = height * roi_in->scale;
670 const int fls = posx * roi_in->scale;
671 const int fws = width * roi_in->scale;
672 int dx = 0, dy = 0;
673
674 // now we search the delta with the source
675 if(!masks_get_delta(self, pipe, roi_in, form, &dx, &dy))
676 {
678 continue;
679 }
680
681 if(dx != 0 || dy != 0)
682 {
683 // now we do the pixel clone
684 for(int yy = fts + 1; yy < fts + fhs - 1; yy++)
685 {
686 // we test if we are inside roi_out
687 if(yy < roi_out->y || yy >= roi_out->y + roi_out->height) continue;
688 // we test if the source point is inside roi_in
689 if(yy - dy < roi_in->y || yy - dy >= roi_in->y + roi_in->height) continue;
690 for(int xx = fls + 1; xx < fls + fws - 1; xx++)
691 {
692 // we test if we are inside roi_out
693 if(xx < roi_out->x || xx >= roi_out->x + roi_out->width) continue;
694 // we test if the source point is inside roi_in
695 if(xx - dx < roi_in->x || xx - dx >= roi_in->x + roi_in->width) continue;
696
697 const float f = mask[((int)((yy - fts) / roi_in->scale)) * width
698 + (int)((xx - fls) / roi_in->scale)] * grpt->opacity;
699
700 for(int c = 0; c < ch; c++)
701 out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c]
702 = out[ch * ((size_t)roi_out->width * (yy - roi_out->y) + xx - roi_out->x) + c] * (1.0f - f)
703 + in[ch * ((size_t)roi_in->width * (yy - dy - roi_in->y) + xx - dx - roi_in->x) + c] * f;
704 }
705 }
706 }
708 }
709 }
710 }
711 return 0;
712}
713
714int 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)
715{
716 const dt_iop_roi_t *const roi_in = &piece->roi_in;
717 const dt_iop_roi_t *const roi_out = &piece->roi_out;
718 const float *in = (float *)i;
719 float *out = (float *)o;
720 return _process(self, pipe, piece, in, out, roi_in, roi_out, piece->dsc_in.channels);
721}
722
724 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
725 const dt_iop_roi_t *const roi_out)
726{
727 (void)pipe;
728 if(_process(self, pipe, piece, in, out, roi_in, roi_out, 1)) return;
729}
730
733{
734 // we don't need global data:
735 module->global_data = NULL; // malloc(sizeof(dt_iop_spots_global_data_t));
736 module->params = calloc(1, sizeof(dt_iop_spots_params_t));
737 module->default_params = calloc(1, sizeof(dt_iop_spots_params_t));
738 // our module is disabled by default
739 // by default:
740 module->default_enabled = 0;
741 module->params_size = sizeof(dt_iop_spots_params_t);
742 // init defaults:
743 dt_iop_spots_params_t tmp = (dt_iop_spots_params_t){ { 0 }, { 2 } };
744
745 memcpy(module->default_params, &tmp, sizeof(dt_iop_spots_params_t));
746}
747
748void gui_focus(struct dt_iop_module_t *self, gboolean in)
749{
750 if(self->enabled)
751 {
753
754 if(in)
755 {
757
758 // update edit shapes status
761 //only toggle shape show button if shapes exist
762 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
763 {
765
766 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
768 && (self->dev->gui_module == self));
769 }
770 else
771 {
772 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
773 }
774 }
775 else
776 {
777 // lost focus, hide all shapes
778 if (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
779 dt_masks_change_form_gui(self->dev, NULL);
780 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), FALSE);
781 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), FALSE);
782 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), FALSE);
783 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
785 }
786 }
787}
788
792{
793 memcpy(piece->data, params, sizeof(dt_iop_spots_params_t));
794}
795
797{
798 piece->data = dt_calloc_align(sizeof(dt_iop_spots_data_t));
799 piece->data_size = sizeof(dt_iop_spots_data_t);
800}
801
803{
804 dt_free_align(piece->data);
805 piece->data = NULL;
806}
807
810{
811 _resynch_params(self);
813 // update clones count
815 guint nb = 0;
816 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
817 gchar *str = g_strdup_printf("%d", nb);
818 gtk_label_set_text(g->label, str);
819 dt_free(str);
820
821 // enable/disable shapes toolbar
822 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), _shape_is_being_added(self, DT_MASKS_CIRCLE));
823 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_path), _shape_is_being_added(self, DT_MASKS_POLYGON));
824 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), _shape_is_being_added(self, DT_MASKS_ELLIPSE));
825
826 // update edit shapes status
828
829 //only toggle shape show button if shapes exist
830 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
831 {
832 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
833 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
834 }
835 else
836 {
837 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
838 }
840}
841
843{
845
846 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
847
848 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
849 gtk_box_pack_start(GTK_BOX(hbox), dt_ui_label_new(_("number of strokes:")), FALSE, TRUE, 0);
850 g->label = GTK_LABEL(dt_ui_label_new("-1"));
851 gtk_widget_set_tooltip_text(hbox, _("click on a shape and drag on canvas.\nuse the mouse wheel "
852 "to adjust size.\nright click to remove a shape."));
853
854 g->bt_edit_masks = dt_iop_togglebutton_new(self, NULL, N_("show and edit shapes"), NULL,
855 G_CALLBACK(_edit_masks), TRUE, 0, 0,
857
858 g->bt_path = dt_iop_togglebutton_new(self, N_("shapes"), N_("add path"), N_("add multiple paths"),
859 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
861
862 g->bt_ellipse = dt_iop_togglebutton_new(self, N_("shapes"), N_("add ellipse"), N_("add multiple ellipses"),
863 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
865
866 g->bt_circle = dt_iop_togglebutton_new(self, N_("shapes"), N_("add circle"), N_("add multiple circles"),
867 G_CALLBACK(_add_shape_callback), TRUE, 0, 0,
869
870 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(g->label), FALSE, TRUE, 0);
871 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox, TRUE, TRUE, 0);
872}
873
874void gui_reset(struct dt_iop_module_t *self)
875{
876 // hide the previous masks
878}
879
880// clang-format off
881// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
882// vim: shiftwidth=2 expandtab tabstop=2 cindent
883// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
884// 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:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
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:1797
@ 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:1893
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
_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:805
dt_masks_raster_result_t 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)
Definition masks.c:464
dt_masks_raster_result_t 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.c:1626
@ 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:1110
int dt_masks_version(void)
Definition masks.c:486
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:851
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
dt_masks_raster_result_t 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)
Definition masks.c:450
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
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
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#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:732
const char ** description(struct dt_iop_module_t *self)
Definition spots.c:97
int default_group()
Definition spots.c:106
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:714
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:790
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition spots.c:809
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:723
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition spots.c:748
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition spots.c:796
const char * name()
Definition spots.c:87
void gui_reset(struct dt_iop_module_t *self)
Definition spots.c:874
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:842
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:485
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:802
struct dt_iop_spots_params_t dt_iop_spots_data_t
Definition spots.c:83
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:543
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:426
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:418
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
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
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:401
unsigned int channels
Definition format.h:83
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)