1 /*
2 This file is part of darktable,
3 Copyright (C) 2013-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "develop/masks.h"
19 #include "bauhaus/bauhaus.h"
20 #include "common/debug.h"
21 #include "common/mipmap_cache.h"
22 #include "control/conf.h"
23 #include "control/control.h"
24 #include "common/undo.h"
25 #include "develop/blend.h"
26 #include "develop/imageop.h"
27 #include "develop/imageop_gui.h"
28
29 #pragma GCC diagnostic ignored "-Wshadow"
30
dt_masks_dup_masks_form(const dt_masks_form_t * form)31 dt_masks_form_t *dt_masks_dup_masks_form(const dt_masks_form_t *form)
32 {
33 if (!form) return NULL;
34
35 dt_masks_form_t *new_form = malloc(sizeof(struct dt_masks_form_t));
36 memcpy(new_form, form, sizeof(struct dt_masks_form_t));
37
38 // then duplicate the GList *points
39
40 GList* newpoints = NULL;
41
42 if (form->points)
43 {
44 int size_item = (form->functions) ? form->functions->point_struct_size : 0;
45
46 if (size_item != 0)
47 {
48 for (GList *pt = form->points; pt; pt = g_list_next(pt))
49 {
50 void *item = malloc(size_item);
51 memcpy(item, pt->data, size_item);
52 newpoints = g_list_prepend(newpoints, item);
53 }
54 }
55 }
56 new_form->points = g_list_reverse(newpoints); // list was built in reverse order, so un-reverse it
57
58 return new_form;
59 }
60
_dup_masks_form_cb(const void * formdata,gpointer user_data)61 static void *_dup_masks_form_cb(const void *formdata, gpointer user_data)
62 {
63 // duplicate the main form struct
64 dt_masks_form_t *form = (dt_masks_form_t *)formdata;
65 dt_masks_form_t *uform = (dt_masks_form_t *)user_data;
66 const dt_masks_form_t *f = uform == NULL || form->formid != uform->formid ? form : uform;
67 return (void *)dt_masks_dup_masks_form(f);
68 }
69
70 // duplicate the list of forms, replace item in the list with form with the same formid
dt_masks_dup_forms_deep(GList * forms,dt_masks_form_t * form)71 GList *dt_masks_dup_forms_deep(GList *forms, dt_masks_form_t *form)
72 {
73 return (GList *)g_list_copy_deep(forms, _dup_masks_form_cb, (gpointer)form);
74 }
75
_get_opacity(dt_masks_form_gui_t * gui,const dt_masks_form_t * form)76 static int _get_opacity(dt_masks_form_gui_t *gui, const dt_masks_form_t *form)
77 {
78 const dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
79 const dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
80 if(!sel) return 0;
81 const int formid = sel->formid;
82
83 // look for apacity
84 const dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, fpt->parentid);
85 if(!grp || !(grp->type & DT_MASKS_GROUP)) return 0;
86
87 int opacity = 0;
88 for(GList *fpts = grp->points; fpts; fpts = g_list_next(fpts))
89 {
90 const dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
91 if(fpt->formid == formid)
92 {
93 opacity = fpt->opacity * 100;
94 break;
95 }
96 }
97
98 return opacity;
99 }
100
_get_all_types_in_group(dt_masks_form_t * form)101 static dt_masks_type_t _get_all_types_in_group(dt_masks_form_t *form)
102 {
103 if(form->type & DT_MASKS_GROUP)
104 {
105 dt_masks_type_t tp = 0;
106 for(GList *l = form->points; l; l = g_list_next(l))
107 {
108 const dt_masks_point_group_t *pt = (dt_masks_point_group_t *)l->data;
109 dt_masks_form_t *f = dt_masks_get_from_id(darktable.develop, pt->formid);
110 tp |= _get_all_types_in_group(f);
111 }
112 return tp;
113 }
114 else
115 {
116 return form->type;
117 }
118 }
119
dt_masks_mouse_actions(dt_masks_form_t * form)120 GSList *dt_masks_mouse_actions(dt_masks_form_t *form)
121 {
122 dt_masks_type_t formtype = _get_all_types_in_group(form);
123 GSList *lm = NULL;
124
125 if(form->functions && form->functions->setup_mouse_actions)
126 {
127 lm = form->functions->setup_mouse_actions(form);
128 }
129 // add the common action(s) shared by all shapes
130 if(formtype != 0)
131 {
132 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_RIGHT, 0, _("[SHAPE] remove shape"));
133 }
134
135 return lm;
136 }
137
_set_hinter_message(dt_masks_form_gui_t * gui,const dt_masks_form_t * form)138 static void _set_hinter_message(dt_masks_form_gui_t *gui, const dt_masks_form_t *form)
139 {
140 char msg[256] = "";
141
142 int ftype = form->type;
143
144 int opacity = 100;
145
146 const dt_masks_form_t *sel = form;
147 if((ftype & DT_MASKS_GROUP) && (gui->group_edited >= 0))
148 {
149 // we get the selected form
150 const dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
151 sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
152 if(!sel) return;
153
154 opacity = _get_opacity(gui, form);
155 }
156 else
157 {
158 opacity = (int)(dt_conf_get_float("plugins/darkroom/masks/opacity") * 100);
159 }
160
161 if(sel->functions && sel->functions->set_hint_message)
162 {
163 sel->functions->set_hint_message(gui, form, opacity, msg, sizeof(msg));
164 }
165
166 dt_control_hinter_message(darktable.control, msg);
167 }
168
dt_masks_init_form_gui(dt_masks_form_gui_t * gui)169 void dt_masks_init_form_gui(dt_masks_form_gui_t *gui)
170 {
171 memset(gui, 0, sizeof(dt_masks_form_gui_t));
172
173 gui->posx = gui->posy = -1.0f;
174 gui->mouse_leaved_center = TRUE;
175 gui->posx_source = gui->posy_source = -1.0f;
176 gui->source_pos_type = DT_MASKS_SOURCE_POS_RELATIVE_TEMP;
177 }
178
dt_masks_gui_form_create(dt_masks_form_t * form,dt_masks_form_gui_t * gui,int index,dt_iop_module_t * module)179 void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, dt_iop_module_t *module)
180 {
181 const int npoints = g_list_length(gui->points);
182 if(npoints == index)
183 {
184 dt_masks_form_gui_points_t *gpt2
185 = (dt_masks_form_gui_points_t *)calloc(1, sizeof(dt_masks_form_gui_points_t));
186 gui->points = g_list_append(gui->points, gpt2);
187 }
188 else if(npoints < index)
189 return;
190
191 dt_masks_gui_form_remove(form, gui, index);
192
193 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
194 if(dt_masks_get_points_border(darktable.develop, form, &gpt->points, &gpt->points_count, &gpt->border,
195 &gpt->border_count, 0, NULL))
196 {
197 if(form->type & DT_MASKS_CLONE)
198 dt_masks_get_points_border(darktable.develop, form, &gpt->source, &gpt->source_count, NULL, NULL, 1, module);
199 gui->pipe_hash = darktable.develop->preview_pipe->backbuf_hash;
200 gui->formid = form->formid;
201 }
202 }
203
dt_masks_form_gui_points_free(gpointer data)204 void dt_masks_form_gui_points_free(gpointer data)
205 {
206 if(!data) return;
207
208 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)data;
209
210 dt_free_align(gpt->points);
211 dt_free_align(gpt->border);
212 dt_free_align(gpt->source);
213 free(gpt);
214 }
215
dt_masks_gui_form_remove(dt_masks_form_t * form,dt_masks_form_gui_t * gui,int index)216 void dt_masks_gui_form_remove(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index)
217 {
218 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
219 gui->pipe_hash = gui->formid = 0;
220
221 if(gpt)
222 {
223 gpt->points_count = gpt->border_count = gpt->source_count = 0;
224 dt_free_align(gpt->points);
225 gpt->points = NULL;
226 dt_free_align(gpt->border);
227 gpt->border = NULL;
228 dt_free_align(gpt->source);
229 gpt->source = NULL;
230 }
231 }
232
dt_masks_gui_form_test_create(dt_masks_form_t * form,dt_masks_form_gui_t * gui,dt_iop_module_t * module)233 void dt_masks_gui_form_test_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
234 {
235 // we test if the image has changed
236 if(gui->pipe_hash > 0)
237 {
238 if(gui->pipe_hash != darktable.develop->preview_pipe->backbuf_hash)
239 {
240 gui->pipe_hash = gui->formid = 0;
241 g_list_free_full(gui->points, dt_masks_form_gui_points_free);
242 gui->points = NULL;
243 }
244 }
245
246 // we create the spots if needed
247 if(gui->pipe_hash == 0)
248 {
249 if(form->type & DT_MASKS_GROUP)
250 {
251 int pos = 0;
252 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
253 {
254 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
255 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
256 if (!sel) return;
257 dt_masks_gui_form_create(sel, gui, pos, module);
258 pos++;
259 }
260 }
261 else
262 dt_masks_gui_form_create(form, gui, 0, module);
263 }
264 }
265
_check_id(dt_masks_form_t * form)266 static void _check_id(dt_masks_form_t *form)
267 {
268 int nid = 100;
269 for(GList *forms = darktable.develop->forms; forms; )
270 {
271 dt_masks_form_t *ff = (dt_masks_form_t *)forms->data;
272 if(ff->formid == form->formid)
273 {
274 form->formid = nid++;
275 forms = darktable.develop->forms; // jump back to start of list
276 }
277 else
278 forms = g_list_next(forms); // advance to next form
279 }
280 }
281
_set_group_name_from_module(dt_iop_module_t * module,dt_masks_form_t * grp)282 static void _set_group_name_from_module(dt_iop_module_t *module, dt_masks_form_t *grp)
283 {
284 gchar *module_label = dt_history_item_get_name(module);
285 snprintf(grp->name, sizeof(grp->name), "grp %s", module_label);
286 g_free(module_label);
287 }
288
_group_create(dt_develop_t * dev,dt_iop_module_t * module,dt_masks_type_t type)289 static dt_masks_form_t *_group_create(dt_develop_t *dev, dt_iop_module_t *module, dt_masks_type_t type)
290 {
291 dt_masks_form_t* grp = dt_masks_create(type);
292 _set_group_name_from_module(module, grp);
293 _check_id(grp);
294 dev->forms = g_list_append(dev->forms, grp);
295 module->blend_params->mask_id = grp->formid;
296 return grp;
297 }
298
_group_from_module(dt_develop_t * dev,dt_iop_module_t * module)299 static dt_masks_form_t *_group_from_module(dt_develop_t *dev, dt_iop_module_t *module)
300 {
301 return dt_masks_get_from_id(dev, module->blend_params->mask_id);
302 }
303
dt_masks_gui_form_save_creation(dt_develop_t * dev,dt_iop_module_t * module,dt_masks_form_t * form,dt_masks_form_gui_t * gui)304 void dt_masks_gui_form_save_creation(dt_develop_t *dev, dt_iop_module_t *module, dt_masks_form_t *form,
305 dt_masks_form_gui_t *gui)
306 {
307 // we check if the id is already registered
308 _check_id(form);
309
310 if(gui) gui->creation = FALSE;
311
312 // mask nb will be at least the length of the list
313 guint nb = 0;
314
315 // count only the same forms to have a clean numbering
316 for(GList *l = dev->forms; l; l = g_list_next(l))
317 {
318 dt_masks_form_t *f = (dt_masks_form_t *)l->data;
319 if(f->type == form->type) nb++;
320 }
321
322 gboolean exist = FALSE;
323
324 // check that we do not have duplicate, in case some masks have been
325 // removed we can have hole and so nb could already exists.
326 do
327 {
328 exist = FALSE;
329 nb++;
330
331 if(form->functions && form->functions->set_form_name)
332 form->functions->set_form_name(form, nb);
333
334 for(GList *l = dev->forms; l; l = g_list_next(l))
335 {
336 dt_masks_form_t *f = (dt_masks_form_t *)l->data;
337 if(!strcmp(f->name, form->name))
338 {
339 exist = TRUE;
340 break;
341 }
342 }
343 } while(exist);
344
345 dev->forms = g_list_append(dev->forms, form);
346
347 dt_dev_add_masks_history_item(dev, module, TRUE);
348
349 if(module)
350 {
351 // is there already a masks group for this module ?
352 dt_masks_form_t *grp = _group_from_module(dev, module);
353 if(!grp)
354 {
355 // we create a new group
356 if(form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE))
357 grp = _group_create(dev, module, DT_MASKS_GROUP | DT_MASKS_CLONE);
358 else
359 grp = _group_create(dev, module, DT_MASKS_GROUP);
360 }
361 // we add the form in this group
362 dt_masks_point_group_t *grpt = malloc(sizeof(dt_masks_point_group_t));
363 grpt->formid = form->formid;
364 grpt->parentid = grp->formid;
365 grpt->state = DT_MASKS_STATE_SHOW | DT_MASKS_STATE_USE;
366 if(grp->points) grpt->state |= DT_MASKS_STATE_UNION;
367 grpt->opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
368 grp->points = g_list_append(grp->points, grpt);
369 // we save the group
370 dt_dev_add_masks_history_item(dev, module, TRUE);
371 // we update module gui
372 if(gui) dt_masks_iop_update(module);
373 //dt_dev_add_history_item(dev, module, TRUE);
374 }
375 // show the form if needed
376 if(gui) dev->form_gui->formid = form->formid;
377 }
378
dt_masks_form_duplicate(dt_develop_t * dev,int formid)379 int dt_masks_form_duplicate(dt_develop_t *dev, int formid)
380 {
381 // we create a new empty form
382 dt_masks_form_t *fbase = dt_masks_get_from_id(dev, formid);
383 if(!fbase) return -1;
384 dt_masks_form_t *fdest = dt_masks_create(fbase->type);
385 _check_id(fdest);
386
387 // we copy the base values
388 fdest->source[0] = fbase->source[0];
389 fdest->source[1] = fbase->source[1];
390 fdest->version = fbase->version;
391 snprintf(fdest->name, sizeof(fdest->name), _("copy of %s"), fbase->name);
392
393 darktable.develop->forms = g_list_append(dev->forms, fdest);
394
395 // we copy all the points
396 if(fbase->functions)
397 fbase->functions->duplicate_points(dev, fbase, fdest);
398
399 // we save the form
400 dt_dev_add_masks_history_item(dev, NULL, TRUE);
401
402 // and we return its id
403 return fdest->formid;
404 }
405
dt_masks_get_points_border(dt_develop_t * dev,dt_masks_form_t * form,float ** points,int * points_count,float ** border,int * border_count,int source,dt_iop_module_t * module)406 int dt_masks_get_points_border(dt_develop_t *dev, dt_masks_form_t *form, float **points, int *points_count,
407 float **border, int *border_count, int source, dt_iop_module_t *module)
408 {
409 if(form->functions && form->functions->get_points_border)
410 {
411 return form->functions->get_points_border(dev, form, points, points_count, border, border_count, source,
412 module);
413 }
414 return 0;
415 }
416
dt_masks_get_area(dt_iop_module_t * module,dt_dev_pixelpipe_iop_t * piece,dt_masks_form_t * form,int * width,int * height,int * posx,int * posy)417 int dt_masks_get_area(dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form,
418 int *width, int *height, int *posx, int *posy)
419 {
420 if(form->functions)
421 return form->functions->get_area(module, piece, form, width, height, posx, posy);
422
423 return 0;
424 }
425
dt_masks_get_source_area(dt_iop_module_t * module,dt_dev_pixelpipe_iop_t * piece,dt_masks_form_t * form,int * width,int * height,int * posx,int * posy)426 int dt_masks_get_source_area(dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form,
427 int *width, int *height, int *posx, int *posy)
428 {
429 *width = *height = *posx = *posy = 0;
430
431 // must be a clone form
432 if(form->type & DT_MASKS_CLONE)
433 {
434 if(form->functions)
435 return form->functions->get_source_area(module, piece, form, width, height, posx, posy);
436 }
437 return 0;
438 }
439
dt_masks_version(void)440 int dt_masks_version(void)
441 {
442 return DEVELOP_MASKS_VERSION;
443 }
444
dt_masks_legacy_params_v1_to_v2(dt_develop_t * dev,void * params)445 static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *dev, void *params)
446 {
447 /*
448 * difference: before v2 images were originally rotated on load, and then
449 * maybe in flip iop
450 * after v2: images are only rotated in flip iop.
451 */
452
453 dt_masks_form_t *m = (dt_masks_form_t *)params;
454
455 const dt_image_orientation_t ori = dt_image_orientation(&dev->image_storage);
456
457 if(ori == ORIENTATION_NONE)
458 {
459 // image is not rotated, we're fine!
460 m->version = 2;
461 return 0;
462 }
463 else
464 {
465 if(dev->iop == NULL) return 1;
466
467 const char *opname = "flip";
468 dt_iop_module_t *module = NULL;
469
470 for(GList *modules = dev->iop; modules; modules = g_list_next(modules))
471 {
472 dt_iop_module_t *find_op = (dt_iop_module_t *)modules->data;
473 if(!strcmp(find_op->op, opname))
474 {
475 module = find_op;
476 break;
477 }
478 }
479
480 if(module == NULL) return 1;
481
482 dt_dev_pixelpipe_iop_t piece = { 0 };
483
484 module->init_pipe(module, NULL, &piece);
485 module->commit_params(module, module->default_params, NULL, &piece);
486
487 piece.buf_in.width = 1;
488 piece.buf_in.height = 1;
489
490 GList *p = m->points;
491
492 if(!p) return 1;
493
494 if(m->type & DT_MASKS_CIRCLE)
495 {
496 dt_masks_point_circle_t *circle = (dt_masks_point_circle_t *)p->data;
497 module->distort_backtransform(module, &piece, circle->center, 1);
498 }
499 else if(m->type & DT_MASKS_PATH)
500 {
501 for(; p; p = g_list_next(p))
502 {
503 dt_masks_point_path_t *path = (dt_masks_point_path_t *)p->data;
504 module->distort_backtransform(module, &piece, path->corner, 1);
505 module->distort_backtransform(module, &piece, path->ctrl1, 1);
506 module->distort_backtransform(module, &piece, path->ctrl2, 1);
507 }
508 }
509 else if(m->type & DT_MASKS_GRADIENT)
510 { // TODO: new ones have wrong rotation.
511 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)p->data;
512 module->distort_backtransform(module, &piece, gradient->anchor, 1);
513
514 if(ori == ORIENTATION_ROTATE_180_DEG)
515 gradient->rotation -= 180.0f;
516 else if(ori == ORIENTATION_ROTATE_CCW_90_DEG)
517 gradient->rotation -= 90.0f;
518 else if(ori == ORIENTATION_ROTATE_CW_90_DEG)
519 gradient->rotation -= -90.0f;
520 }
521 else if(m->type & DT_MASKS_ELLIPSE)
522 {
523 dt_masks_point_ellipse_t *ellipse = (dt_masks_point_ellipse_t *)p->data;
524 module->distort_backtransform(module, &piece, ellipse->center, 1);
525
526 if(ori & ORIENTATION_SWAP_XY)
527 {
528 const float y = ellipse->radius[0];
529 ellipse->radius[0] = ellipse->radius[1];
530 ellipse->radius[1] = y;
531 }
532 }
533 else if(m->type & DT_MASKS_BRUSH)
534 {
535 for(; p; p = g_list_next(p))
536 {
537 dt_masks_point_brush_t *brush = (dt_masks_point_brush_t *)p->data;
538 module->distort_backtransform(module, &piece, brush->corner, 1);
539 module->distort_backtransform(module, &piece, brush->ctrl1, 1);
540 module->distort_backtransform(module, &piece, brush->ctrl2, 1);
541 }
542 }
543
544 if(m->type & DT_MASKS_CLONE)
545 {
546 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_PATH
547 module->distort_backtransform(module, &piece, m->source, 1);
548 }
549
550 m->version = 2;
551
552 return 0;
553 }
554 }
555
dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t * img,float * points)556 static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *img, float *points)
557 {
558 const float w = (float)img->width, h = (float)img->height;
559
560 const float cx = (float)img->crop_x, cy = (float)img->crop_y;
561
562 const float cw = (float)(img->width - img->crop_x - img->crop_width),
563 ch = (float)(img->height - img->crop_y - img->crop_height);
564
565 /*
566 * masks coordinates are normalized, so we need to:
567 * 1. de-normalize them by image original cropped dimensions
568 * 2. un-crop them by adding top-left crop coordinates
569 * 3. normalize them by the image fully uncropped dimensions
570 */
571 points[0] = ((points[0] * cw) + cx) / w;
572 points[1] = ((points[1] * ch) + cy) / h;
573 }
574
dt_masks_legacy_params_v2_to_v3_transform_only_rescale(const dt_image_t * img,float * points,size_t points_count)575 static void dt_masks_legacy_params_v2_to_v3_transform_only_rescale(const dt_image_t *img, float *points,
576 size_t points_count)
577 {
578 const float w = (float)img->width, h = (float)img->height;
579
580 const float cw = (float)(img->width - img->crop_x - img->crop_width),
581 ch = (float)(img->height - img->crop_y - img->crop_height);
582
583 /*
584 * masks coordinates are normalized, so we need to:
585 * 1. de-normalize them by minimal of image original cropped dimensions
586 * 2. normalize them by the minimal of image fully uncropped dimensions
587 */
588 for(size_t i = 0; i < points_count; i++) points[i] = ((points[i] * MIN(cw, ch))) / MIN(w, h);
589 }
590
dt_masks_legacy_params_v2_to_v3(dt_develop_t * dev,void * params)591 static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *dev, void *params)
592 {
593 /*
594 * difference: before v3 images were originally cropped on load
595 * after v3: images are cropped in rawprepare iop.
596 */
597
598 dt_masks_form_t *m = (dt_masks_form_t *)params;
599
600 const dt_image_t *img = &(dev->image_storage);
601
602 if(img->crop_x == 0 && img->crop_y == 0 && img->crop_width == 0 && img->crop_height == 0)
603 {
604 // image has no "raw cropping", we're fine!
605 m->version = 3;
606 return 0;
607 }
608 else
609 {
610 GList *p = m->points;
611
612 if(!p) return 1;
613
614 if(m->type & DT_MASKS_CIRCLE)
615 {
616 dt_masks_point_circle_t *circle = (dt_masks_point_circle_t *)p->data;
617 dt_masks_legacy_params_v2_to_v3_transform(img, circle->center);
618 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, &circle->radius, 1);
619 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, &circle->border, 1);
620 }
621 else if(m->type & DT_MASKS_PATH)
622 {
623 for(; p; p = g_list_next(p))
624 {
625 dt_masks_point_path_t *path = (dt_masks_point_path_t *)p->data;
626 dt_masks_legacy_params_v2_to_v3_transform(img, path->corner);
627 dt_masks_legacy_params_v2_to_v3_transform(img, path->ctrl1);
628 dt_masks_legacy_params_v2_to_v3_transform(img, path->ctrl2);
629 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, path->border, 2);
630 }
631 }
632 else if(m->type & DT_MASKS_GRADIENT)
633 {
634 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)p->data;
635 dt_masks_legacy_params_v2_to_v3_transform(img, gradient->anchor);
636 }
637 else if(m->type & DT_MASKS_ELLIPSE)
638 {
639 dt_masks_point_ellipse_t *ellipse = (dt_masks_point_ellipse_t *)p->data;
640 dt_masks_legacy_params_v2_to_v3_transform(img, ellipse->center);
641 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, ellipse->radius, 2);
642 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, &ellipse->border, 1);
643 }
644 else if(m->type & DT_MASKS_BRUSH)
645 {
646 for(; p; p = g_list_next(p))
647 {
648 dt_masks_point_brush_t *brush = (dt_masks_point_brush_t *)p->data;
649 dt_masks_legacy_params_v2_to_v3_transform(img, brush->corner);
650 dt_masks_legacy_params_v2_to_v3_transform(img, brush->ctrl1);
651 dt_masks_legacy_params_v2_to_v3_transform(img, brush->ctrl2);
652 dt_masks_legacy_params_v2_to_v3_transform_only_rescale(img, brush->border, 2);
653 }
654 }
655
656 if(m->type & DT_MASKS_CLONE)
657 {
658 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_PATH
659 dt_masks_legacy_params_v2_to_v3_transform(img, m->source);
660 }
661
662 m->version = 3;
663
664 return 0;
665 }
666 }
667
dt_masks_legacy_params_v3_to_v4(dt_develop_t * dev,void * params)668 static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *dev, void *params)
669 {
670 /*
671 * difference affecting ellipse
672 * up to v3: only equidistant feathering
673 * after v4: choice between equidistant and proportional feathering
674 * type of feathering is defined in new flags parameter
675 */
676
677 dt_masks_form_t *m = (dt_masks_form_t *)params;
678
679 GList *p = m->points;
680
681 if(!p) return 1;
682
683 if(m->type & DT_MASKS_ELLIPSE)
684 {
685 dt_masks_point_ellipse_t *ellipse = (dt_masks_point_ellipse_t *)p->data;
686 ellipse->flags = DT_MASKS_ELLIPSE_EQUIDISTANT;
687 }
688
689 m->version = 4;
690
691 return 0;
692 }
693
694
dt_masks_legacy_params_v4_to_v5(dt_develop_t * dev,void * params)695 static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *dev, void *params)
696 {
697 /*
698 * difference affecting gradient
699 * up to v4: only linear gradient (relative to input image)
700 * after v5: curved gradients
701 */
702
703 dt_masks_form_t *m = (dt_masks_form_t *)params;
704
705 GList *p = m->points;
706
707 if(!p) return 1;
708
709 if(m->type & DT_MASKS_GRADIENT)
710 {
711 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)p->data;
712 gradient->curvature = 0.0f;
713 }
714
715 m->version = 5;
716
717 return 0;
718 }
719
dt_masks_legacy_params_v5_to_v6(dt_develop_t * dev,void * params)720 static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *dev, void *params)
721 {
722 /*
723 * difference affecting gradient
724 * up to v5: linear transition
725 * after v5: linear or sigmoidal transition
726 */
727
728 dt_masks_form_t *m = (dt_masks_form_t *)params;
729
730 GList *p = m->points;
731
732 if(!p) return 1;
733
734 if(m->type & DT_MASKS_GRADIENT)
735 {
736 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)p->data;
737 gradient->state = DT_MASKS_GRADIENT_STATE_LINEAR;
738 }
739
740 m->version = 6;
741
742 return 0;
743 }
744
745
dt_masks_legacy_params(dt_develop_t * dev,void * params,const int old_version,const int new_version)746 int dt_masks_legacy_params(dt_develop_t *dev, void *params, const int old_version, const int new_version)
747 {
748 int res = 1;
749 #if 0 // we should not need this any longer
750 if(old_version == 1 && new_version == 2)
751 {
752 res = dt_masks_legacy_params_v1_to_v2(dev, params);
753 }
754 #endif
755
756 if(old_version == 1 && new_version == 6)
757 {
758 res = dt_masks_legacy_params_v1_to_v2(dev, params);
759 if(!res) res = dt_masks_legacy_params_v2_to_v3(dev, params);
760 if(!res) res = dt_masks_legacy_params_v3_to_v4(dev, params);
761 if(!res) res = dt_masks_legacy_params_v4_to_v5(dev, params);
762 if(!res) res = dt_masks_legacy_params_v5_to_v6(dev, params);
763 }
764 else if(old_version == 2 && new_version == 6)
765 {
766 res = dt_masks_legacy_params_v2_to_v3(dev, params);
767 if(!res) res = dt_masks_legacy_params_v3_to_v4(dev, params);
768 if(!res) res = dt_masks_legacy_params_v4_to_v5(dev, params);
769 if(!res) res = dt_masks_legacy_params_v5_to_v6(dev, params);
770 }
771 else if(old_version == 3 && new_version == 6)
772 {
773 res = dt_masks_legacy_params_v3_to_v4(dev, params);
774 if(!res) res = dt_masks_legacy_params_v4_to_v5(dev, params);
775 if(!res) res = dt_masks_legacy_params_v5_to_v6(dev, params);
776 }
777 else if(old_version == 4 && new_version == 6)
778 {
779 res = dt_masks_legacy_params_v4_to_v5(dev, params);
780 if(!res) res = dt_masks_legacy_params_v5_to_v6(dev, params);
781 }
782 else if(old_version == 5 && new_version == 6)
783 {
784 res = dt_masks_legacy_params_v5_to_v6(dev, params);
785 }
786
787 return res;
788 }
789
790 static int form_id = 0;
791
dt_masks_create(dt_masks_type_t type)792 dt_masks_form_t *dt_masks_create(dt_masks_type_t type)
793 {
794 dt_masks_form_t *form = (dt_masks_form_t *)calloc(1, sizeof(dt_masks_form_t));
795 if(!form) return NULL;
796
797 form->type = type;
798 form->version = dt_masks_version();
799 form->formid = time(NULL) + form_id++;
800
801 if (type & DT_MASKS_CIRCLE)
802 form->functions = &dt_masks_functions_circle;
803 else if (type & DT_MASKS_ELLIPSE)
804 form->functions = &dt_masks_functions_ellipse;
805 else if (type & DT_MASKS_BRUSH)
806 form->functions = &dt_masks_functions_brush;
807 else if (type & DT_MASKS_PATH)
808 form->functions = &dt_masks_functions_path;
809 else if (type & DT_MASKS_GRADIENT)
810 form->functions = &dt_masks_functions_gradient;
811 else if (type & DT_MASKS_GROUP)
812 form->functions = &dt_masks_functions_group;
813
814 if (form->functions && form->functions->sanitize_config)
815 form->functions->sanitize_config(type);
816
817 return form;
818 }
819
dt_masks_create_ext(dt_masks_type_t type)820 dt_masks_form_t *dt_masks_create_ext(dt_masks_type_t type)
821 {
822 dt_masks_form_t *form = dt_masks_create(type);
823
824 // all forms created here are registered in darktable.develop->allforms for later cleanup
825 if(form)
826 darktable.develop->allforms = g_list_append(darktable.develop->allforms, form);
827
828 return form;
829 }
830
dt_masks_replace_current_forms(dt_develop_t * dev,GList * forms)831 void dt_masks_replace_current_forms(dt_develop_t *dev, GList *forms)
832 {
833 GList *forms_tmp = dt_masks_dup_forms_deep(forms, NULL);
834
835 while(dev->forms)
836 {
837 darktable.develop->allforms = g_list_append(darktable.develop->allforms, dev->forms->data);
838 dev->forms = g_list_delete_link(dev->forms, dev->forms);
839 }
840
841 dev->forms = forms_tmp;
842 }
843
dt_masks_get_from_id_ext(GList * forms,int id)844 dt_masks_form_t *dt_masks_get_from_id_ext(GList *forms, int id)
845 {
846 for(; forms; forms = g_list_next(forms))
847 {
848 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
849 if(form->formid == id) return form;
850 }
851 return NULL;
852 }
853
dt_masks_get_from_id(dt_develop_t * dev,int id)854 dt_masks_form_t *dt_masks_get_from_id(dt_develop_t *dev, int id)
855 {
856 return dt_masks_get_from_id_ext(dev->forms, id);
857 }
858
dt_masks_read_masks_history(dt_develop_t * dev,const int imgid)859 void dt_masks_read_masks_history(dt_develop_t *dev, const int imgid)
860 {
861 dt_dev_history_item_t *hist_item = NULL;
862 dt_dev_history_item_t *hist_item_last = NULL;
863 int num_prev = -1;
864
865 sqlite3_stmt *stmt;
866 DT_DEBUG_SQLITE3_PREPARE_V2(
867 dt_database_get(darktable.db),
868 "SELECT imgid, formid, form, name, version, points, points_count, source, num "
869 "FROM main.masks_history WHERE imgid = ?1 ORDER BY num",
870 -1, &stmt, NULL);
871 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
872
873 while(sqlite3_step(stmt) == SQLITE_ROW)
874 {
875 // db record:
876 // 0-img, 1-formid, 2-form_type, 3-name, 4-version, 5-points, 6-points_count, 7-source, 8-num
877
878 // we get the values
879
880 const int formid = sqlite3_column_int(stmt, 1);
881 const int num = sqlite3_column_int(stmt, 8);
882 const dt_masks_type_t type = sqlite3_column_int(stmt, 2);
883 dt_masks_form_t *form = dt_masks_create(type);
884 form->formid = formid;
885 const char *name = (const char *)sqlite3_column_text(stmt, 3);
886 g_strlcpy(form->name, name, sizeof(form->name));
887 form->version = sqlite3_column_int(stmt, 4);
888 form->points = NULL;
889 const int nb_points = sqlite3_column_int(stmt, 6);
890 memcpy(form->source, sqlite3_column_blob(stmt, 7), sizeof(float) * 2);
891
892 // and now we "read" the blob
893 if(form->functions)
894 {
895 const char *const ptbuf = (char *)sqlite3_column_blob(stmt, 5);
896 const size_t point_size = form->functions->point_struct_size;
897 for(int i = 0; i < nb_points; i++)
898 {
899 char *point = (char *)malloc(point_size);
900 memcpy(point, ptbuf + i*point_size, point_size);
901 form->points = g_list_append(form->points, point);
902 }
903 }
904
905 if(form->version != dt_masks_version())
906 {
907 if(dt_masks_legacy_params(dev, form, form->version, dt_masks_version()))
908 {
909 const char *fname = dev->image_storage.filename + strlen(dev->image_storage.filename);
910 while(fname > dev->image_storage.filename && *fname != '/') fname--;
911 if(fname > dev->image_storage.filename) fname++;
912
913 fprintf(stderr,
914 "[_dev_read_masks_history] %s (imgid `%i'): mask version mismatch: history is %d, dt %d.\n",
915 fname, imgid, form->version, dt_masks_version());
916 dt_control_log(_("%s: mask version mismatch: %d != %d"), fname, dt_masks_version(), form->version);
917
918 continue;
919 }
920 }
921
922 // if this is a new history entry let's find it
923 if(num_prev != num)
924 {
925 hist_item = NULL;
926 for(GList *history = dev->history; history; history = g_list_next(history))
927 {
928 dt_dev_history_item_t *hitem = (dt_dev_history_item_t *)(history->data);
929 if(hitem->num == num)
930 {
931 hist_item = hitem;
932 break;
933 }
934 }
935 num_prev = num;
936 }
937 // add the form to the history entry
938 if(hist_item)
939 {
940 hist_item->forms = g_list_append(hist_item->forms, form);
941 }
942 else
943 fprintf(stderr,
944 "[_dev_read_masks_history] can't find history entry %i while adding mask %s(%i)\n",
945 num, form->name, formid);
946
947 if(num < dev->history_end) hist_item_last = hist_item;
948 }
949 sqlite3_finalize(stmt);
950
951 // and we update the current forms snapshot
952 dt_masks_replace_current_forms(dev, (hist_item_last)?hist_item_last->forms:NULL);
953 }
954
dt_masks_write_masks_history_item(const int imgid,const int num,dt_masks_form_t * form)955 void dt_masks_write_masks_history_item(const int imgid, const int num, dt_masks_form_t *form)
956 {
957 sqlite3_stmt *stmt;
958
959 // write the form into the database
960 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "INSERT INTO main.masks_history (imgid, num, formid, form, name, "
961 "version, points, points_count,source) VALUES "
962 "(?1, ?9, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
963 -1, &stmt, NULL);
964 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
965 DT_DEBUG_SQLITE3_BIND_INT(stmt, 9, num);
966 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, form->formid);
967 DT_DEBUG_SQLITE3_BIND_INT(stmt, 3, form->type);
968 DT_DEBUG_SQLITE3_BIND_TEXT(stmt, 4, form->name, -1, SQLITE_TRANSIENT);
969 DT_DEBUG_SQLITE3_BIND_BLOB(stmt, 8, form->source, 2 * sizeof(float), SQLITE_TRANSIENT);
970 DT_DEBUG_SQLITE3_BIND_INT(stmt, 5, form->version);
971 if(form->functions)
972 {
973 const size_t point_size = form->functions->point_struct_size;
974 const guint nb = g_list_length(form->points);
975 char *const restrict ptbuf = (char *)malloc(nb * point_size);
976 int pos = 0;
977 for (GList *points = form->points; points; points = g_list_next(points))
978 {
979 memcpy(ptbuf + pos, points->data, point_size);
980 pos += point_size;
981 }
982 DT_DEBUG_SQLITE3_BIND_BLOB(stmt, 6, ptbuf, nb * point_size, SQLITE_TRANSIENT);
983 DT_DEBUG_SQLITE3_BIND_INT(stmt, 7, nb);
984 sqlite3_step(stmt);
985 sqlite3_finalize(stmt);
986 free(ptbuf);
987 }
988 }
989
dt_masks_free_form(dt_masks_form_t * form)990 void dt_masks_free_form(dt_masks_form_t *form)
991 {
992 if(!form) return;
993 g_list_free_full(form->points, free);
994 form->points = NULL;
995 free(form);
996 }
997
dt_masks_events_mouse_leave(struct dt_iop_module_t * module)998 int dt_masks_events_mouse_leave(struct dt_iop_module_t *module)
999 {
1000 if(darktable.develop->form_gui)
1001 {
1002 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1003 gui->mouse_leaved_center = TRUE;
1004 }
1005 return 0;
1006 }
1007
dt_masks_events_mouse_enter(struct dt_iop_module_t * module)1008 int dt_masks_events_mouse_enter(struct dt_iop_module_t *module)
1009 {
1010 if(darktable.develop->form_gui)
1011 {
1012 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1013 gui->mouse_leaved_center = FALSE;
1014 }
1015 return 0;
1016 }
1017
dt_masks_events_mouse_moved(struct dt_iop_module_t * module,double x,double y,double pressure,int which)1018 int dt_masks_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which)
1019 {
1020 // record mouse position even if there are no masks visible
1021 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1022 dt_masks_form_t *form = darktable.develop->form_visible;
1023 float pzx = 0.0f, pzy = 0.0f;
1024
1025 dt_dev_get_pointer_zoom_pos(darktable.develop, x, y, &pzx, &pzy);
1026 pzx += 0.5f;
1027 pzy += 0.5f;
1028
1029 if(gui)
1030 {
1031 // This assume that if this event is generated the mouse is over the center window
1032 gui->mouse_leaved_center = FALSE;
1033 gui->posx = pzx * darktable.develop->preview_pipe->backbuf_width;
1034 gui->posy = pzy * darktable.develop->preview_pipe->backbuf_height;
1035 }
1036
1037 // do not process if no forms visible
1038 if(!form) return 0;
1039
1040 // add an option to allow skip mouse events while editing masks
1041 if(darktable.develop->darkroom_skip_mouse_events) return 0;
1042
1043 int rep = 0;
1044 if(form->functions)
1045 rep = form->functions->mouse_moved(module, pzx, pzy, pressure, which, form, 0, gui, 0);
1046
1047 if(gui) _set_hinter_message(gui, form);
1048
1049 return rep;
1050 }
1051
dt_masks_events_button_released(struct dt_iop_module_t * module,double x,double y,int which,uint32_t state)1052 int dt_masks_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
1053 uint32_t state)
1054 {
1055 // add an option to allow skip mouse events while editing masks
1056 if(darktable.develop->darkroom_skip_mouse_events) return 0;
1057
1058 dt_masks_form_t *form = darktable.develop->form_visible;
1059 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1060 float pzx = 0.0f, pzy = 0.0f;
1061 dt_dev_get_pointer_zoom_pos(darktable.develop, x, y, &pzx, &pzy);
1062 pzx += 0.5f;
1063 pzy += 0.5f;
1064
1065 if(darktable.develop->mask_form_selected_id)
1066 dt_dev_masks_selection_change(darktable.develop, module,
1067 darktable.develop->mask_form_selected_id, FALSE);
1068
1069 if(form->functions)
1070 return form->functions->button_released(module, pzx, pzy, which, state, form, 0, gui, 0);
1071
1072 return 0;
1073 }
1074
dt_masks_events_button_pressed(struct dt_iop_module_t * module,double x,double y,double pressure,int which,int type,uint32_t state)1075 int dt_masks_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure,
1076 int which, int type, uint32_t state)
1077 {
1078 // add an option to allow skip mouse events while editing masks
1079 if(darktable.develop->darkroom_skip_mouse_events) return 0;
1080
1081 dt_masks_form_t *form = darktable.develop->form_visible;
1082 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1083 float pzx = 0.0f, pzy = 0.0f;
1084 dt_dev_get_pointer_zoom_pos(darktable.develop, x, y, &pzx, &pzy);
1085 pzx += 0.5f;
1086 pzy += 0.5f;
1087
1088 // allow to select a shape inside an iop
1089 if(gui && which == 1)
1090 {
1091 dt_masks_form_t *sel = NULL;
1092
1093 if((gui->form_selected || gui->source_selected || gui->point_selected || gui->seg_selected
1094 || gui->feather_selected)
1095 && !gui->creation && gui->group_edited >= 0)
1096 {
1097 // we get the selected form
1098 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
1099 if(fpt)
1100 {
1101 sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
1102 }
1103 }
1104
1105 dt_masks_select_form(module, sel);
1106 }
1107
1108 if(form->functions)
1109 return form->functions->button_pressed(module, pzx, pzy, pressure, which, type, state, form, 0, gui, 0);
1110
1111 return 0;
1112 }
1113
dt_masks_events_mouse_scrolled(struct dt_iop_module_t * module,double x,double y,int up,uint32_t state)1114 int dt_masks_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, uint32_t state)
1115 {
1116 // add an option to allow skip mouse events while editing masks
1117 if(darktable.develop->darkroom_skip_mouse_events) return 0;
1118
1119 dt_masks_form_t *form = darktable.develop->form_visible;
1120 dt_masks_form_gui_t *gui = darktable.develop->form_gui;
1121 float pzx = 0.0f, pzy = 0.0f;
1122 dt_dev_get_pointer_zoom_pos(darktable.develop, x, y, &pzx, &pzy);
1123 pzx += 0.5f;
1124 pzy += 0.5f;
1125
1126 int ret = 0;
1127 const gboolean incr = dt_mask_scroll_increases(up);
1128
1129 if(form->functions)
1130 ret = form->functions->mouse_scrolled(module, pzx, pzy,
1131 incr ? 1 : 0,
1132 state, form, 0, gui, 0);
1133
1134 if(gui)
1135 {
1136 // for brush, the opacity is the density of the masks, do not update opacity here for the brush.
1137 if(gui->creation && dt_modifier_is(state, GDK_CONTROL_MASK))
1138 {
1139 float opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
1140 const float amount = incr ? 0.05f : -0.05f;
1141
1142 opacity = CLAMP(opacity + amount, 0.05f, 1.0f);
1143 dt_conf_set_float("plugins/darkroom/masks/opacity", opacity);
1144 const int opacitypercent = opacity * 100;
1145 dt_toast_log(_("opacity: %d%%"), opacitypercent);
1146 ret = 1;
1147 }
1148
1149 _set_hinter_message(gui, form);
1150 }
1151
1152 return ret;
1153 }
dt_masks_events_post_expose(struct dt_iop_module_t * module,cairo_t * cr,int32_t width,int32_t height,int32_t pointerx,int32_t pointery)1154 void dt_masks_events_post_expose(struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height,
1155 int32_t pointerx, int32_t pointery)
1156 {
1157 dt_develop_t *dev = darktable.develop;
1158 dt_masks_form_t *form = dev->form_visible;
1159 dt_masks_form_gui_t *gui = dev->form_gui;
1160 if(!gui) return;
1161 if(!form) return;
1162
1163 float wd = dev->preview_pipe->backbuf_width;
1164 float ht = dev->preview_pipe->backbuf_height;
1165 if(wd < 1.0 || ht < 1.0) return;
1166 float pzx = 0.0f, pzy = 0.0f;
1167 dt_dev_get_pointer_zoom_pos(dev, pointerx, pointery, &pzx, &pzy);
1168 pzx += 0.5f;
1169 pzy += 0.5f;
1170 float zoom_y = dt_control_get_dev_zoom_y();
1171 float zoom_x = dt_control_get_dev_zoom_x();
1172 dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
1173 int closeup = dt_control_get_dev_closeup();
1174 float zoom_scale = dt_dev_get_zoom_scale(dev, zoom, 1<<closeup, 1);
1175
1176 cairo_save(cr);
1177 cairo_set_source_rgb(cr, .3, .3, .3);
1178
1179 cairo_translate(cr, width / 2.0, height / 2.0f);
1180 cairo_scale(cr, zoom_scale, zoom_scale);
1181 cairo_translate(cr, -.5f * wd - zoom_x * wd, -.5f * ht - zoom_y * ht);
1182
1183 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1184
1185 // we update the form if needed
1186 // add preview when creating a circle, ellipse and gradient
1187 if(!(((form->type & DT_MASKS_CIRCLE) || (form->type & DT_MASKS_ELLIPSE) || (form->type & DT_MASKS_GRADIENT))
1188 && gui->creation))
1189 dt_masks_gui_form_test_create(form, gui, module);
1190
1191 // draw form
1192 if(form->type & DT_MASKS_GROUP)
1193 dt_group_events_post_expose(cr, zoom_scale, form, gui);
1194 else if(form->functions)
1195 form->functions->post_expose(cr, zoom_scale, gui, 0, g_list_length(form->points));
1196
1197 cairo_restore(cr);
1198 }
1199
dt_masks_clear_form_gui(dt_develop_t * dev)1200 void dt_masks_clear_form_gui(dt_develop_t *dev)
1201 {
1202 if(!dev->form_gui) return;
1203 g_list_free_full(dev->form_gui->points, dt_masks_form_gui_points_free);
1204 dev->form_gui->points = NULL;
1205 dt_masks_dynbuf_free(dev->form_gui->guipoints);
1206 dev->form_gui->guipoints = NULL;
1207 dt_masks_dynbuf_free(dev->form_gui->guipoints_payload);
1208 dev->form_gui->guipoints_payload = NULL;
1209 dev->form_gui->guipoints_count = 0;
1210 dev->form_gui->pipe_hash = dev->form_gui->formid = 0;
1211 dev->form_gui->dx = dev->form_gui->dy = 0.0f;
1212 dev->form_gui->scrollx = dev->form_gui->scrolly = 0.0f;
1213 dev->form_gui->form_selected = dev->form_gui->border_selected = dev->form_gui->form_dragging
1214 = dev->form_gui->form_rotating = dev->form_gui->border_toggling = dev->form_gui->gradient_toggling = FALSE;
1215 dev->form_gui->source_selected = dev->form_gui->source_dragging = FALSE;
1216 dev->form_gui->pivot_selected = FALSE;
1217 dev->form_gui->point_border_selected = dev->form_gui->seg_selected = dev->form_gui->point_selected
1218 = dev->form_gui->feather_selected = -1;
1219 dev->form_gui->point_border_dragging = dev->form_gui->seg_dragging = dev->form_gui->feather_dragging
1220 = dev->form_gui->point_dragging = -1;
1221 dev->form_gui->creation_closing_form = dev->form_gui->creation = FALSE;
1222 dev->form_gui->pressure_sensitivity = DT_MASKS_PRESSURE_OFF;
1223 dev->form_gui->creation_module = NULL;
1224 dev->form_gui->point_edited = -1;
1225
1226 dev->form_gui->group_edited = -1;
1227 dev->form_gui->group_selected = -1;
1228 dev->form_gui->edit_mode = DT_MASKS_EDIT_OFF;
1229 // allow to select a shape inside an iop
1230 dt_masks_select_form(NULL, NULL);
1231 }
1232
dt_masks_change_form_gui(dt_masks_form_t * newform)1233 void dt_masks_change_form_gui(dt_masks_form_t *newform)
1234 {
1235 dt_masks_form_t *old = darktable.develop->form_visible;
1236
1237 dt_masks_clear_form_gui(darktable.develop);
1238 darktable.develop->form_visible = newform;
1239
1240 /* update sticky accels window */
1241 if(newform != old && darktable.view_manager->accels_window.window && darktable.view_manager->accels_window.sticky)
1242 dt_view_accels_refresh(darktable.view_manager);
1243 }
1244
dt_masks_reset_form_gui(void)1245 void dt_masks_reset_form_gui(void)
1246 {
1247 dt_masks_change_form_gui(NULL);
1248 dt_iop_module_t *m = darktable.develop->gui_module;
1249 if(m && (m->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(m->flags() & IOP_FLAGS_NO_MASKS)
1250 && m->blend_data)
1251 {
1252 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)m->blend_data;
1253 bd->masks_shown = DT_MASKS_EDIT_OFF;
1254 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), 0);
1255 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
1256 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), 0);
1257 }
1258 }
1259
dt_masks_reset_show_masks_icons(void)1260 void dt_masks_reset_show_masks_icons(void)
1261 {
1262 if(darktable.develop->first_load) return;
1263 for(GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
1264 {
1265 dt_iop_module_t *m = (dt_iop_module_t *)modules->data;
1266 if(m && (m->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(m->flags() & IOP_FLAGS_NO_MASKS))
1267 {
1268 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)m->blend_data;
1269 if(!bd) break; // TODO: this doesn't look right. Why do we break the while look as soon as one module has no blend_data?
1270 bd->masks_shown = DT_MASKS_EDIT_OFF;
1271 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), FALSE);
1272 gtk_widget_queue_draw(bd->masks_edit);
1273 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
1274 {
1275 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), 0);
1276 gtk_widget_queue_draw(bd->masks_shapes[n]);
1277 }
1278 }
1279 }
1280 }
1281
dt_masks_get_edit_mode(struct dt_iop_module_t * module)1282 dt_masks_edit_mode_t dt_masks_get_edit_mode(struct dt_iop_module_t *module)
1283 {
1284 return darktable.develop->form_gui
1285 ? darktable.develop->form_gui->edit_mode
1286 : DT_MASKS_EDIT_OFF;
1287 }
1288
dt_masks_set_edit_mode(struct dt_iop_module_t * module,dt_masks_edit_mode_t value)1289 void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
1290 {
1291 if(!module) return;
1292 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
1293 if(!bd) return;
1294
1295 dt_masks_form_t *grp = NULL;
1296 dt_masks_form_t *form = dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
1297 if(value && form)
1298 {
1299 grp = dt_masks_create_ext(DT_MASKS_GROUP);
1300 grp->formid = 0;
1301 dt_masks_group_ungroup(grp, form);
1302 }
1303
1304 if(bd) bd->masks_shown = value;
1305
1306 dt_masks_change_form_gui(grp);
1307 darktable.develop->form_gui->edit_mode = value;
1308 if(value && form)
1309 dt_dev_masks_selection_change(darktable.develop, NULL, form->formid, FALSE);
1310 else
1311 dt_dev_masks_selection_change(darktable.develop, NULL, 0, FALSE);
1312
1313 if(bd->masks_support)
1314 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit),
1315 value == DT_MASKS_EDIT_OFF ? FALSE : TRUE);
1316
1317 dt_control_queue_redraw_center();
1318 }
1319
dt_masks_set_edit_mode_single_form(struct dt_iop_module_t * module,const int formid,dt_masks_edit_mode_t value)1320 void dt_masks_set_edit_mode_single_form(struct dt_iop_module_t *module, const int formid,
1321 dt_masks_edit_mode_t value)
1322 {
1323 if(!module) return;
1324
1325 dt_masks_form_t *grp = dt_masks_create_ext(DT_MASKS_GROUP);
1326
1327 const int grid = module->blend_params->mask_id;
1328 dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, formid);
1329 if(form)
1330 {
1331 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)malloc(sizeof(dt_masks_point_group_t));
1332 fpt->formid = formid;
1333 fpt->parentid = grid;
1334 fpt->state = DT_MASKS_STATE_USE;
1335 fpt->opacity = 1.0f;
1336 grp->points = g_list_append(grp->points, fpt);
1337 }
1338
1339 dt_masks_form_t *grp2 = dt_masks_create_ext(DT_MASKS_GROUP);
1340 grp2->formid = 0;
1341 dt_masks_group_ungroup(grp2, grp);
1342 dt_masks_change_form_gui(grp2);
1343 darktable.develop->form_gui->edit_mode = value;
1344
1345 if(value && form)
1346 dt_dev_masks_selection_change(darktable.develop, NULL, formid, FALSE);
1347 else
1348 dt_dev_masks_selection_change(darktable.develop, NULL, 0, FALSE);
1349
1350 dt_control_queue_redraw_center();
1351 }
1352
dt_masks_iop_edit_toggle_callback(GtkToggleButton * togglebutton,dt_iop_module_t * module)1353 void dt_masks_iop_edit_toggle_callback(GtkToggleButton *togglebutton, dt_iop_module_t *module)
1354 {
1355 if(!module) return;
1356 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
1357 if(module->blend_params->mask_id == 0)
1358 {
1359 bd->masks_shown = DT_MASKS_EDIT_OFF;
1360 return;
1361 }
1362
1363 // reset the gui
1364 dt_masks_set_edit_mode(module,
1365 (bd->masks_shown == DT_MASKS_EDIT_OFF ? DT_MASKS_EDIT_FULL : DT_MASKS_EDIT_OFF));
1366 }
1367
_menu_no_masks(struct dt_iop_module_t * module)1368 static void _menu_no_masks(struct dt_iop_module_t *module)
1369 {
1370 // we drop all the forms in the iop
1371 dt_masks_form_t *grp = _group_from_module(darktable.develop, module);
1372 if(grp) dt_masks_form_remove(module, NULL, grp);
1373 module->blend_params->mask_id = 0;
1374
1375 // and we update the iop
1376 dt_masks_set_edit_mode(module, DT_MASKS_EDIT_OFF);
1377 dt_masks_iop_update(module);
1378
1379 dt_dev_add_history_item(darktable.develop, module, TRUE);
1380 }
1381
_menu_add_shape(struct dt_iop_module_t * module,dt_masks_type_t type)1382 static void _menu_add_shape(struct dt_iop_module_t *module, dt_masks_type_t type)
1383 {
1384 // we want to be sure that the iop has focus
1385 dt_iop_request_focus(module);
1386 // we create the new form
1387 dt_masks_form_t *form = dt_masks_create(type);
1388 dt_masks_change_form_gui(form);
1389 darktable.develop->form_gui->creation = TRUE;
1390 darktable.develop->form_gui->creation_module = module;
1391 dt_control_queue_redraw_center();
1392 }
1393
_menu_add_exist(dt_iop_module_t * module,int formid)1394 static void _menu_add_exist(dt_iop_module_t *module, int formid)
1395 {
1396 if(!module) return;
1397 dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, formid);
1398 if(!form) return;
1399
1400 // is there already a masks group for this module ?
1401 dt_masks_form_t *grp = _group_from_module(darktable.develop, module);
1402 if(!grp)
1403 {
1404 grp = _group_create(darktable.develop, module, DT_MASKS_GROUP);
1405 }
1406 // we add the form in this group
1407 dt_masks_group_add_form(grp, form);
1408 // we save the group
1409 // and we ensure that we are in edit mode
1410 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1411 dt_masks_iop_update(module);
1412 dt_masks_set_edit_mode(module, DT_MASKS_EDIT_FULL);
1413 }
1414
dt_masks_group_update_name(dt_iop_module_t * module)1415 void dt_masks_group_update_name(dt_iop_module_t *module)
1416 {
1417 dt_masks_form_t *grp = _group_from_module(darktable.develop, module);
1418 if (!grp)
1419 return;
1420
1421 _set_group_name_from_module(module, grp);
1422 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1423 dt_masks_iop_update(module);
1424 }
1425
dt_masks_iop_use_same_as(dt_iop_module_t * module,dt_iop_module_t * src)1426 void dt_masks_iop_use_same_as(dt_iop_module_t *module, dt_iop_module_t *src)
1427 {
1428 if(!module || !src) return;
1429
1430 // we get the source group
1431 int srcid = src->blend_params->mask_id;
1432 dt_masks_form_t *src_grp = dt_masks_get_from_id(darktable.develop, srcid);
1433 if(!src_grp || src_grp->type != DT_MASKS_GROUP) return;
1434
1435 // is there already a masks group for this module ?
1436 dt_masks_form_t *grp = _group_from_module(darktable.develop, module);
1437 if(!grp)
1438 {
1439 grp = _group_create(darktable.develop, module, DT_MASKS_GROUP);
1440 }
1441 // we copy the src group in this group
1442 for(GList *points = src_grp->points; points; points = g_list_next(points))
1443 {
1444 dt_masks_point_group_t *pt = (dt_masks_point_group_t *)points->data;
1445 dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, pt->formid);
1446 if(form)
1447 {
1448 dt_masks_point_group_t *grpt = dt_masks_group_add_form(grp, form);
1449 if(grpt)
1450 {
1451 grpt->state = pt->state;
1452 grpt->opacity = pt->opacity;
1453 }
1454 }
1455 }
1456
1457 // we save the group
1458 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1459 }
1460
dt_masks_iop_combo_populate(GtkWidget * w,struct dt_iop_module_t ** m)1461 void dt_masks_iop_combo_populate(GtkWidget *w, struct dt_iop_module_t **m)
1462 {
1463 // we ensure that the module has focus
1464 dt_iop_module_t *module = *m;
1465 dt_iop_request_focus(module);
1466 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
1467
1468 // we determine a higher approx of the entry number
1469 guint nbe = 5 + g_list_length(darktable.develop->forms) + g_list_length(darktable.develop->iop);
1470 free(bd->masks_combo_ids);
1471 bd->masks_combo_ids = malloc( sizeof(int) *nbe);
1472
1473 int *cids = bd->masks_combo_ids;
1474 GtkWidget *combo = bd->masks_combo;
1475
1476 // we remove all the combo entries except the first one
1477 while(dt_bauhaus_combobox_length(combo) > 1)
1478 {
1479 dt_bauhaus_combobox_remove_at(combo, 1);
1480 }
1481
1482 int pos = 0;
1483 cids[pos++] = 0; // nothing to do for the first entry (already here)
1484
1485
1486 // add existing shapes
1487 int nb = 0;
1488 for(GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1489 {
1490 dt_masks_form_t *form = (dt_masks_form_t *)forms->data;
1491 if((form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) || form->formid == module->blend_params->mask_id)
1492 {
1493 continue;
1494 }
1495
1496 // we search were this form is used in the current module
1497 int used = 0;
1498 dt_masks_form_t *grp = _group_from_module(darktable.develop, module);
1499 if(grp && (grp->type & DT_MASKS_GROUP))
1500 {
1501 for(GList *pts = grp->points; pts; pts = g_list_next(pts))
1502 {
1503 dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
1504 if(pt->formid == form->formid)
1505 {
1506 used = 1;
1507 break;
1508 }
1509 }
1510 }
1511 if(!used)
1512 {
1513 if(nb == 0)
1514 {
1515 dt_bauhaus_combobox_add_section(combo, _("add existing shape"));
1516 cids[pos++] = 0; // nothing to do
1517 }
1518 dt_bauhaus_combobox_add(combo, form->name);
1519 cids[pos++] = form->formid;
1520 nb++;
1521 }
1522 }
1523
1524 // masks from other iops
1525 nb = 0;
1526 int pos2 = 1;
1527 for(GList *modules = darktable.develop->iop; modules; modules = g_list_next(modules))
1528 {
1529 dt_iop_module_t *other_mod = (dt_iop_module_t *)modules->data;
1530 if((other_mod != module) && (other_mod->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(other_mod->flags() & IOP_FLAGS_NO_MASKS))
1531 {
1532 dt_masks_form_t *grp = _group_from_module(darktable.develop, other_mod);
1533 if(grp)
1534 {
1535 if(nb == 0)
1536 {
1537 dt_bauhaus_combobox_add_section(combo, _("use same shapes as"));
1538 cids[pos++] = 0; // nothing to do
1539 }
1540 gchar *module_label = dt_history_item_get_name(other_mod);
1541 dt_bauhaus_combobox_add(combo, module_label);
1542 g_free(module_label);
1543 cids[pos++] = -1 * pos2;
1544 nb++;
1545 }
1546 }
1547 pos2++;
1548 }
1549 }
1550
dt_masks_iop_value_changed_callback(GtkWidget * widget,struct dt_iop_module_t * module)1551 void dt_masks_iop_value_changed_callback(GtkWidget *widget, struct dt_iop_module_t *module)
1552 {
1553 // we get the corresponding value
1554 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)module->blend_data;
1555
1556 int sel = dt_bauhaus_combobox_get(bd->masks_combo);
1557 if(sel == 0) return;
1558 if(sel == 1)
1559 {
1560 ++darktable.gui->reset;
1561 dt_bauhaus_combobox_set(bd->masks_combo, 0);
1562 --darktable.gui->reset;
1563 return;
1564 }
1565 if(sel > 0)
1566 {
1567 int val = bd->masks_combo_ids[sel];
1568 if(val == -1000000)
1569 {
1570 // delete all masks
1571 _menu_no_masks(module);
1572 }
1573 else if(val == -2000001)
1574 {
1575 // add a circle shape
1576 _menu_add_shape(module, DT_MASKS_CIRCLE);
1577 }
1578 else if(val == -2000002)
1579 {
1580 // add a path shape
1581 _menu_add_shape(module, DT_MASKS_PATH);
1582 }
1583 else if(val == -2000016)
1584 {
1585 // add a gradient shape
1586 _menu_add_shape(module, DT_MASKS_GRADIENT);
1587 }
1588 else if(val == -2000032)
1589 {
1590 // add a gradient shape
1591 _menu_add_shape(module, DT_MASKS_ELLIPSE);
1592 }
1593 else if(val == -2000064)
1594 {
1595 // add a brush shape
1596 _menu_add_shape(module, DT_MASKS_BRUSH);
1597 }
1598 else if(val < 0)
1599 {
1600 // use same shapes as another iop
1601 val = -1 * val - 1;
1602 if(val < g_list_length(module->dev->iop))
1603 {
1604 dt_iop_module_t *m = (dt_iop_module_t *)g_list_nth_data(module->dev->iop, val);
1605 dt_masks_iop_use_same_as(module, m);
1606 // and we ensure that we are in edit mode
1607 //dt_dev_add_history_item(darktable.develop, module, TRUE);
1608 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1609 dt_masks_iop_update(module);
1610 dt_masks_set_edit_mode(module, DT_MASKS_EDIT_FULL);
1611 }
1612 }
1613 else if(val > 0)
1614 {
1615 // add an existing shape
1616 _menu_add_exist(module, val);
1617 }
1618 else
1619 return;
1620 }
1621 // we update the combo line
1622 dt_masks_iop_update(module);
1623 }
1624
dt_masks_iop_update(struct dt_iop_module_t * module)1625 void dt_masks_iop_update(struct dt_iop_module_t *module)
1626 {
1627 if(!module) return;
1628
1629 dt_iop_gui_update(module);
1630 dt_iop_gui_update_masks(module);
1631 }
1632
dt_masks_form_remove(struct dt_iop_module_t * module,dt_masks_form_t * grp,dt_masks_form_t * form)1633 void dt_masks_form_remove(struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
1634 {
1635 if(!form) return;
1636 int id = form->formid;
1637 if(grp && !(grp->type & DT_MASKS_GROUP)) return;
1638
1639 if(!(form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE)) && grp)
1640 {
1641 // we try to remove the form from the masks group
1642 int ok = 0;
1643 for(GList *forms = grp->points; forms; forms = g_list_next(forms))
1644 {
1645 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1646 if(grpt->formid == id)
1647 {
1648 ok = 1;
1649 grp->points = g_list_remove(grp->points, grpt);
1650 free(grpt);
1651 break;
1652 }
1653 }
1654 if(ok) dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1655 if(ok && module)
1656 {
1657 dt_masks_iop_update(module);
1658 dt_masks_update_image(darktable.develop);
1659 }
1660 if(ok && grp->points == NULL) dt_masks_form_remove(module, NULL, grp);
1661 return;
1662 }
1663
1664 if(form->type & DT_MASKS_GROUP && form->type & DT_MASKS_CLONE)
1665 {
1666 // when removing a cloning group the children have to be removed, too, as they won't be shown in the mask manager
1667 // and are thus not accessible afterwards.
1668 while(form->points)
1669 {
1670 dt_masks_point_group_t *group_child = (dt_masks_point_group_t *)form->points->data;
1671 dt_masks_form_t *child = dt_masks_get_from_id(darktable.develop, group_child->formid);
1672 dt_masks_form_remove(module, form, child);
1673 // no need to do anything to form->points, the recursive call will have removed child from the list
1674 }
1675 }
1676
1677 // if we are here that mean we have to permanently delete this form
1678 // we drop the form from all modules
1679 int form_removed = 0;
1680 for(GList *iops = darktable.develop->iop; iops; iops = g_list_next(iops))
1681 {
1682 dt_iop_module_t *m = (dt_iop_module_t *)iops->data;
1683 if(m->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1684 {
1685 // is the form the base group of the iop ?
1686 if(id == m->blend_params->mask_id)
1687 {
1688 m->blend_params->mask_id = 0;
1689 dt_masks_iop_update(m);
1690 dt_dev_add_history_item(darktable.develop, m, TRUE);
1691 }
1692 else
1693 {
1694 dt_masks_form_t *iopgrp = _group_from_module(darktable.develop, m);
1695 if(iopgrp && (iopgrp->type & DT_MASKS_GROUP))
1696 {
1697 int ok = 0;
1698 GList *forms = iopgrp->points;
1699 while(forms)
1700 {
1701 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1702 if(grpt->formid == id)
1703 {
1704 ok = 1;
1705 iopgrp->points = g_list_remove(iopgrp->points, grpt);
1706 free(grpt);
1707 forms = iopgrp->points; // jump back to start of list
1708 continue;
1709 }
1710 forms = g_list_next(forms); // advance to next form
1711 }
1712 if(ok)
1713 {
1714 form_removed = 1;
1715 dt_masks_iop_update(m);
1716 dt_masks_update_image(darktable.develop);
1717 if(iopgrp->points == NULL) dt_masks_form_remove(m, NULL, iopgrp);
1718 }
1719 }
1720 }
1721 }
1722 }
1723 // we drop the form from the general list
1724 for(GList *forms = darktable.develop->forms; forms; forms = g_list_next(forms))
1725 {
1726 dt_masks_form_t *f = (dt_masks_form_t *)forms->data;
1727 if(f->formid == id)
1728 {
1729 darktable.develop->forms = g_list_remove(darktable.develop->forms, f);
1730 form_removed = 1;
1731 break;
1732 }
1733 }
1734 if(form_removed) dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
1735 }
1736
dt_masks_form_change_opacity(dt_masks_form_t * form,int parentid,int up)1737 void dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up)
1738 {
1739 if(!form) return;
1740 dt_masks_form_t *grp = dt_masks_get_from_id(darktable.develop, parentid);
1741 if(!grp || !(grp->type & DT_MASKS_GROUP)) return;
1742
1743 // we first need to test if the opacity can be set to the form
1744 if(form->type & DT_MASKS_GROUP) return;
1745 const int id = form->formid;
1746 const float amount = up ? 0.05f : -0.05f;
1747
1748 // so we change the value inside the group
1749 for(GList *fpts = grp->points; fpts; fpts = g_list_next(fpts))
1750 {
1751 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
1752 if(fpt->formid == id)
1753 {
1754 const float opacity = CLAMP(fpt->opacity + amount, 0.05f, 1.0f);
1755 fpt->opacity = opacity;
1756 const int opacitypercent = opacity * 100;
1757 dt_toast_log(_("opacity: %d%%"), opacitypercent);
1758 dt_dev_add_masks_history_item(darktable.develop, NULL, TRUE);
1759 dt_masks_update_image(darktable.develop);
1760 break;
1761 }
1762 }
1763 }
1764
dt_masks_form_move(dt_masks_form_t * grp,int formid,int up)1765 void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
1766 {
1767 if(!grp || !(grp->type & DT_MASKS_GROUP)) return;
1768
1769 // we search the form in the group
1770 dt_masks_point_group_t *grpt = NULL;
1771 guint pos = 0;
1772 for(GList *fpts = grp->points; fpts; fpts = g_list_next(fpts))
1773 {
1774 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
1775 if(fpt->formid == formid)
1776 {
1777 grpt = fpt;
1778 break;
1779 }
1780 pos++;
1781 }
1782
1783 // we remove the form and read it
1784 if(grpt)
1785 {
1786 if(!up && pos == 0) return;
1787 if(up && pos == g_list_length(grp->points) - 1) return;
1788
1789 grp->points = g_list_remove(grp->points, grpt);
1790 if(!up)
1791 pos -= 1;
1792 else
1793 pos += 1;
1794 grp->points = g_list_insert(grp->points, grpt, pos);
1795 dt_dev_add_masks_history_item(darktable.develop, NULL, TRUE);
1796 }
1797 }
1798
_find_in_group(dt_masks_form_t * grp,int formid)1799 static int _find_in_group(dt_masks_form_t *grp, int formid)
1800 {
1801 if(!(grp->type & DT_MASKS_GROUP)) return 0;
1802 if(grp->formid == formid) return 1;
1803 int nb = 0;
1804 for(GList *forms = grp->points; forms; forms = g_list_next(forms))
1805 {
1806 const dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1807 dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, grpt->formid);
1808 if(form)
1809 {
1810 if(form->type & DT_MASKS_GROUP) nb += _find_in_group(form, formid);
1811 }
1812 }
1813 return nb;
1814 }
1815
dt_masks_group_add_form(dt_masks_form_t * grp,dt_masks_form_t * form)1816 dt_masks_point_group_t *dt_masks_group_add_form(dt_masks_form_t *grp, dt_masks_form_t *form)
1817 {
1818 // add a form to group and check for self inclusion
1819
1820 if(!(grp->type & DT_MASKS_GROUP)) return NULL;
1821 // either the form to add is not a group, so no risk
1822 // or we go through all points of form to see if we find a ref to grp->formid
1823 if(!(form->type & DT_MASKS_GROUP) || _find_in_group(form, grp->formid) == 0)
1824 {
1825 dt_masks_point_group_t *grpt = malloc(sizeof(dt_masks_point_group_t));
1826 grpt->formid = form->formid;
1827 grpt->parentid = grp->formid;
1828 grpt->state = DT_MASKS_STATE_SHOW | DT_MASKS_STATE_USE;
1829 if(grp->points) grpt->state |= DT_MASKS_STATE_UNION;
1830 grpt->opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
1831 grp->points = g_list_append(grp->points, grpt);
1832 return grpt;
1833 }
1834
1835 dt_control_log(_("masks can not contain themselves"));
1836 return NULL;
1837 }
1838
dt_masks_group_ungroup(dt_masks_form_t * dest_grp,dt_masks_form_t * grp)1839 void dt_masks_group_ungroup(dt_masks_form_t *dest_grp, dt_masks_form_t *grp)
1840 {
1841 if(!grp || !dest_grp) return;
1842 if(!(grp->type & DT_MASKS_GROUP) || !(dest_grp->type & DT_MASKS_GROUP)) return;
1843
1844 for(GList *forms = grp->points; forms; forms = g_list_next(forms))
1845 {
1846 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1847 dt_masks_form_t *form = dt_masks_get_from_id(darktable.develop, grpt->formid);
1848 if(form)
1849 {
1850 if(form->type & DT_MASKS_GROUP)
1851 {
1852 dt_masks_group_ungroup(dest_grp, form);
1853 }
1854 else
1855 {
1856 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)malloc(sizeof(dt_masks_point_group_t));
1857 fpt->formid = grpt->formid;
1858 fpt->parentid = grpt->parentid;
1859 fpt->state = grpt->state;
1860 fpt->opacity = grpt->opacity;
1861 dest_grp->points = g_list_append(dest_grp->points, fpt);
1862 }
1863 }
1864 }
1865 }
1866
dt_masks_group_get_hash_buffer_length(dt_masks_form_t * form)1867 int dt_masks_group_get_hash_buffer_length(dt_masks_form_t *form)
1868 {
1869 if(!form) return 0;
1870 int pos = 0;
1871 // basic infos
1872 pos += sizeof(dt_masks_type_t);
1873 pos += sizeof(int);
1874 pos += sizeof(int);
1875 pos += 2 * sizeof(float);
1876
1877 for(GList *forms = form->points; forms; forms = g_list_next(forms))
1878 {
1879 if(form->type & DT_MASKS_GROUP)
1880 {
1881 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1882 dt_masks_form_t *f = dt_masks_get_from_id(darktable.develop, grpt->formid);
1883 if(f)
1884 {
1885 // state & opacity
1886 pos += sizeof(int);
1887 pos += sizeof(float);
1888 // the form itself
1889 pos += dt_masks_group_get_hash_buffer_length(f);
1890 }
1891 }
1892 else if(form->functions)
1893 {
1894 pos += form->functions->point_struct_size;
1895 }
1896 }
1897 return pos;
1898 }
1899
dt_masks_group_get_hash_buffer(dt_masks_form_t * form,char * str)1900 char *dt_masks_group_get_hash_buffer(dt_masks_form_t *form, char *str)
1901 {
1902 if(!form) return str;
1903 int pos = 0;
1904 // basic infos
1905 memcpy(str + pos, &form->type, sizeof(dt_masks_type_t));
1906 pos += sizeof(dt_masks_type_t);
1907 memcpy(str + pos, &form->formid, sizeof(int));
1908 pos += sizeof(int);
1909 memcpy(str + pos, &form->version, sizeof(int));
1910 pos += sizeof(int);
1911 memcpy(str + pos, &form->source, sizeof(float) * 2);
1912 pos += 2 * sizeof(float);
1913
1914 for(const GList *forms = form->points; forms; forms = g_list_next(forms))
1915 {
1916 if(form->type & DT_MASKS_GROUP)
1917 {
1918 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
1919 dt_masks_form_t *f = dt_masks_get_from_id(darktable.develop, grpt->formid);
1920 if(f)
1921 {
1922 // state & opacity
1923 memcpy(str + pos, &grpt->state, sizeof(int));
1924 pos += sizeof(int);
1925 memcpy(str + pos, &grpt->opacity, sizeof(float));
1926 pos += sizeof(float);
1927 // the form itself
1928 str = dt_masks_group_get_hash_buffer(f, str + pos) - pos;
1929 }
1930 }
1931 else if(form->functions)
1932 {
1933 memcpy(str + pos,forms->data, form->functions->point_struct_size);
1934 pos += form->functions->point_struct_size;
1935 }
1936 }
1937 return str + pos;
1938 }
1939
dt_masks_update_image(dt_develop_t * dev)1940 void dt_masks_update_image(dt_develop_t *dev)
1941 {
1942 /* invalidate image data*/
1943 // dt_similarity_image_dirty(dev->image_storage.id);
1944
1945 // invalidate buffers and force redraw of darkroom
1946 dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
1947 dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH;
1948 dev->preview2_pipe->changed |= DT_DEV_PIPE_SYNCH;
1949 dt_dev_invalidate_all(dev);
1950 }
1951
1952 // adds formid to used array
1953 // if formid is a group it adds all the forms that belongs to that group
_cleanup_unused_recurs(GList * forms,int formid,int * used,int nb)1954 static void _cleanup_unused_recurs(GList *forms, int formid, int *used, int nb)
1955 {
1956 // first, we search for the formid in used table
1957 for(int i = 0; i < nb; i++)
1958 {
1959 if(used[i] == 0)
1960 {
1961 // we store the formid
1962 used[i] = formid;
1963 break;
1964 }
1965 if(used[i] == formid) break;
1966 }
1967
1968 // if the form is a group, we iterate through the sub-forms
1969 dt_masks_form_t *form = dt_masks_get_from_id_ext(forms, formid);
1970 if(form && (form->type & DT_MASKS_GROUP))
1971 {
1972 for(GList *grpts = form->points; grpts; grpts = g_list_next(grpts))
1973 {
1974 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)grpts->data;
1975 _cleanup_unused_recurs(forms, grpt->formid, used, nb);
1976 }
1977 }
1978 }
1979
1980 // removes from _forms all forms that are not used in history_list up to history_end
_masks_cleanup_unused(GList ** _forms,GList * history_list,const int history_end)1981 static int _masks_cleanup_unused(GList **_forms, GList *history_list, const int history_end)
1982 {
1983 int masks_removed = 0;
1984 GList *forms = *_forms;
1985
1986 // we create a table to store the ids of used forms
1987 guint nbf = g_list_length(forms);
1988 int *used = calloc(nbf, sizeof(int));
1989
1990 // check in history if the module has drawn masks and add it to used array
1991 int num = 0;
1992 for(GList *history = history_list; history && num < history_end; history = g_list_next(history))
1993 {
1994 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1995 dt_develop_blend_params_t *blend_params = hist->blend_params;
1996 if(blend_params)
1997 {
1998 if(blend_params->mask_id > 0) _cleanup_unused_recurs(forms, blend_params->mask_id, used, nbf);
1999 }
2000 num++;
2001 }
2002
2003 // and we delete all unused forms
2004 GList *shapes = forms;
2005 while(shapes)
2006 {
2007 dt_masks_form_t *f = (dt_masks_form_t *)shapes->data;
2008 int u = 0;
2009 for(int i = 0; i < nbf; i++)
2010 {
2011 if(used[i] == f->formid)
2012 {
2013 u = 1;
2014 break;
2015 }
2016 if(used[i] == 0) break;
2017 }
2018
2019 shapes = g_list_next(shapes); // need to get 'next' now, because we may be removing the current node
2020
2021 if(u == 0)
2022 {
2023 forms = g_list_remove(forms, f);
2024 // and add it to allforms for cleanup
2025 darktable.develop->allforms = g_list_append(darktable.develop->allforms, f);
2026 masks_removed = 1;
2027 }
2028 }
2029
2030 free(used);
2031
2032 *_forms = forms;
2033
2034 return masks_removed;
2035 }
2036
2037 // removes all unused form from history
2038 // if there are multiple hist->forms entries in history it may leave some unused forms
2039 // we do it like this so the user can go back in history
2040 // for a more accurate cleanup the user should compress history
dt_masks_cleanup_unused_from_list(GList * history_list)2041 void dt_masks_cleanup_unused_from_list(GList *history_list)
2042 {
2043 // a mask is used in a given hist->forms entry if it is used up to the next hist->forms
2044 // so we are going to remove for each hist->forms from the top
2045 int num = g_list_length(history_list);
2046 int history_end = num;
2047 for(const GList *history = g_list_last(history_list); history; history = g_list_previous(history))
2048 {
2049 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
2050 if(hist->forms && strcmp(hist->op_name, "mask_manager") == 0)
2051 {
2052 _masks_cleanup_unused(&hist->forms, history_list, history_end);
2053 history_end = num - 1;
2054 }
2055 num--;
2056 }
2057 }
2058
dt_masks_cleanup_unused(dt_develop_t * dev)2059 void dt_masks_cleanup_unused(dt_develop_t *dev)
2060 {
2061 dt_masks_change_form_gui(NULL);
2062
2063 // we remove the forms from history
2064 dt_masks_cleanup_unused_from_list(dev->history);
2065
2066 // and we save all that
2067 GList *forms = NULL;
2068 dt_iop_module_t *module = NULL;
2069 int num = 0;
2070 for(const GList *history = dev->history; history && num < dev->history_end; history = g_list_next(history))
2071 {
2072 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
2073
2074 if(hist->forms) forms = hist->forms;
2075 if(hist->module && strcmp(hist->op_name, "mask_manager") != 0) module = hist->module;
2076
2077 num++;
2078 }
2079
2080 dt_masks_replace_current_forms(dev, forms);
2081
2082 if(module)
2083 dt_dev_add_history_item(dev, module, module->enabled);
2084 else
2085 dt_dev_add_masks_history_item(dev, NULL, TRUE);
2086 }
2087
dt_masks_point_in_form_exact(float x,float y,float * points,int points_start,int points_count)2088 int dt_masks_point_in_form_exact(float x, float y, float *points, int points_start, int points_count)
2089 {
2090 // we use ray casting algorithm
2091 // to avoid most problems with horizontal segments, y should be rounded as int
2092 // so that there's very little chance than y==points...
2093
2094 if(points_count > 2 + points_start)
2095 {
2096 int start = isnan(points[points_start * 2]) && !isnan(points[points_start * 2 + 1])
2097 ? points[points_start * 2 + 1]
2098 : points_start;
2099
2100 float yf = (float)y;
2101 int nb = 0;
2102 for(int i = start, next = start + 1; i < points_count;)
2103 {
2104 float y1 = points[i * 2 + 1];
2105 float y2 = points[next * 2 + 1];
2106 //if we need to skip points (in case of deleted point, because of self-intersection)
2107 if(isnan(points[next * 2]))
2108 {
2109 next = isnan(y2) ? start : (int)y2;
2110 continue;
2111 }
2112 if(((yf <= y2 && yf > y1) || (yf >= y2 && yf < y1)) && (points[i * 2] > x)) nb++;
2113
2114 if(next == start) break;
2115 i = next++;
2116 if(next >= points_count) next = start;
2117 }
2118 return (nb & 1);
2119 }
2120 return 0;
2121 }
2122
dt_masks_point_in_form_near(float x,float y,float * points,int points_start,int points_count,float distance,int * near)2123 int dt_masks_point_in_form_near(float x, float y, float *points, int points_start, int points_count, float distance, int *near)
2124 {
2125 // we use ray casting algorithm
2126 // to avoid most problems with horizontal segments, y should be rounded as int
2127 // so that there's very little chance than y==points...
2128
2129 // TODO : distance is only evaluated in x, not y...
2130
2131 if(points_count > 2 + points_start)
2132 {
2133 const int start = isnan(points[points_start * 2]) && !isnan(points[points_start * 2 + 1])
2134 ? points[points_start * 2 + 1]
2135 : points_start;
2136
2137 const float yf = (float)y;
2138 int nb = 0;
2139 for(int i = start, next = start + 1; i < points_count;)
2140 {
2141 const float y1 = points[i * 2 + 1];
2142 const float y2 = points[next * 2 + 1];
2143 //if we need to jump to skip points (in case of deleted point, because of self-intersection)
2144 if(isnan(points[next * 2]))
2145 {
2146 next = isnan(y2) ? start : (int)y2;
2147 continue;
2148 }
2149 if((yf <= y2 && yf > y1) || (yf >= y2 && yf < y1))
2150 {
2151 if(points[i * 2] > x) nb++;
2152 if(points[i * 2] - x < distance && points[i * 2] - x > -distance) *near = 1;
2153 }
2154
2155 if(next == start) break;
2156 i = next++;
2157 if(next >= points_count) next = start;
2158 }
2159 return (nb & 1);
2160 }
2161 return 0;
2162 }
2163
2164 // allow to select a shape inside an iop
dt_masks_select_form(struct dt_iop_module_t * module,dt_masks_form_t * sel)2165 void dt_masks_select_form(struct dt_iop_module_t *module, dt_masks_form_t *sel)
2166 {
2167 gboolean selection_changed = FALSE;
2168
2169 if(sel)
2170 {
2171 if(sel->formid != darktable.develop->mask_form_selected_id)
2172 {
2173 darktable.develop->mask_form_selected_id = sel->formid;
2174 selection_changed = TRUE;
2175 }
2176 }
2177 else
2178 {
2179 if(darktable.develop->mask_form_selected_id != 0)
2180 {
2181 darktable.develop->mask_form_selected_id = 0;
2182 selection_changed = TRUE;
2183 }
2184 }
2185 if(selection_changed)
2186 {
2187 if(!module && darktable.develop->mask_form_selected_id == 0)
2188 module = darktable.develop->gui_module;
2189 if(module)
2190 {
2191 if(module->masks_selection_changed)
2192 module->masks_selection_changed(module, darktable.develop->mask_form_selected_id);
2193 }
2194 }
2195 }
2196
2197 // draw a cross where the source position of a clone mask will be created
dt_masks_draw_clone_source_pos(cairo_t * cr,const float zoom_scale,const float x,const float y)2198 void dt_masks_draw_clone_source_pos(cairo_t *cr, const float zoom_scale, const float x, const float y)
2199 {
2200 const float dx = 3.5f / zoom_scale;
2201 const float dy = 3.5f / zoom_scale;
2202
2203 double dashed[] = { 4.0, 4.0 };
2204 dashed[0] /= zoom_scale;
2205 dashed[1] /= zoom_scale;
2206
2207 cairo_set_dash(cr, dashed, 0, 0);
2208 cairo_set_line_width(cr, 3.0 / zoom_scale);
2209 cairo_set_source_rgba(cr, .3, .3, .3, .8);
2210
2211 cairo_move_to(cr, x + dx, y);
2212 cairo_line_to(cr, x - dx, y);
2213 cairo_move_to(cr, x, y + dy);
2214 cairo_line_to(cr, x, y - dy);
2215 cairo_stroke_preserve(cr);
2216
2217 cairo_set_line_width(cr, 1.0 / zoom_scale);
2218 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2219 cairo_stroke(cr);
2220 }
2221
2222 // sets if the initial source position for a clone mask will be absolute or relative,
2223 // based on mouse position and key state
dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t * gui,const uint32_t state,const float pzx,const float pzy)2224 void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state, const float pzx,
2225 const float pzy)
2226 {
2227 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
2228 gui->source_pos_type = DT_MASKS_SOURCE_POS_ABSOLUTE;
2229 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
2230 gui->source_pos_type = DT_MASKS_SOURCE_POS_RELATIVE_TEMP;
2231 else
2232 fprintf(stderr, "[dt_masks_set_source_pos_initial_state] unknown state for setting masks position type\n");
2233
2234 // both source types record an absolute position,
2235 // for the relative type, the first time is used the position is recorded,
2236 // the second time a relative position is calculated based on that one
2237 gui->posx_source = pzx * darktable.develop->preview_pipe->backbuf_width;
2238 gui->posy_source = pzy * darktable.develop->preview_pipe->backbuf_height;
2239 }
2240
2241 // set the initial source position value for a clone mask
dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t * gui,const int mask_type,dt_masks_form_t * form,const float pzx,const float pzy)2242 void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *gui, const int mask_type, dt_masks_form_t *form,
2243 const float pzx, const float pzy)
2244 {
2245 const float wd = darktable.develop->preview_pipe->backbuf_width;
2246 const float ht = darktable.develop->preview_pipe->backbuf_height;
2247 const float iwd = darktable.develop->preview_pipe->iwidth;
2248 const float iht = darktable.develop->preview_pipe->iheight;
2249
2250 // if this is the first time the relative pos is used
2251 if(gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE_TEMP)
2252 {
2253 // if it has not been defined by the user, set some default
2254 if(gui->posx_source == -1.0f && gui->posy_source == -1.0f)
2255 {
2256 if(form->functions && form->functions->initial_source_pos)
2257 {
2258 form->functions->initial_source_pos(iwd, iht, &gui->posx_source, &gui->posy_source);
2259 }
2260 else
2261 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unsupported masks type when calculating source position initial value\n");
2262
2263 float pts[2] = { pzx * wd + gui->posx_source, pzy * ht + gui->posy_source };
2264 dt_dev_distort_backtransform(darktable.develop, pts, 1);
2265
2266 form->source[0] = pts[0] / iwd;
2267 form->source[1] = pts[1] / iht;
2268 }
2269 else
2270 {
2271 // if a position was defined by the user, use the absolute value the first time
2272 float pts[2] = { gui->posx_source, gui->posy_source };
2273 dt_dev_distort_backtransform(darktable.develop, pts, 1);
2274
2275 form->source[0] = pts[0] / iwd;
2276 form->source[1] = pts[1] / iht;
2277
2278 gui->posx_source = gui->posx_source - pzx * wd;
2279 gui->posy_source = gui->posy_source - pzy * ht;
2280 }
2281
2282 gui->source_pos_type = DT_MASKS_SOURCE_POS_RELATIVE;
2283 }
2284 else if(gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE)
2285 {
2286 // original pos was already defined and relative value calculated, just use it
2287 float pts[2] = { pzx * wd + gui->posx_source, pzy * ht + gui->posy_source };
2288 dt_dev_distort_backtransform(darktable.develop, pts, 1);
2289
2290 form->source[0] = pts[0] / iwd;
2291 form->source[1] = pts[1] / iht;
2292 }
2293 else if(gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
2294 {
2295 // an absolute position was defined by the user
2296 float pts_src[2] = { gui->posx_source, gui->posy_source };
2297 dt_dev_distort_backtransform(darktable.develop, pts_src, 1);
2298
2299 form->source[0] = pts_src[0] / iwd;
2300 form->source[1] = pts_src[1] / iht;
2301 }
2302 else
2303 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unknown source position type\n");
2304 }
2305
2306 // calculates the source position value for preview drawing, on cairo coordinates
dt_masks_calculate_source_pos_value(dt_masks_form_gui_t * gui,const int mask_type,const float initial_xpos,const float initial_ypos,const float xpos,const float ypos,float * px,float * py,const int adding)2307 void dt_masks_calculate_source_pos_value(dt_masks_form_gui_t *gui, const int mask_type, const float initial_xpos,
2308 const float initial_ypos, const float xpos, const float ypos, float *px,
2309 float *py, const int adding)
2310 {
2311 float x = 0.0f, y = 0.0f;
2312 const float pr_d = darktable.develop->preview_downsampling;
2313 const float iwd = pr_d * darktable.develop->preview_pipe->iwidth;
2314 const float iht = pr_d * darktable.develop->preview_pipe->iheight;
2315
2316 if(gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE)
2317 {
2318 x = xpos + gui->posx_source;
2319 y = ypos + gui->posy_source;
2320 }
2321 else if(gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE_TEMP)
2322 {
2323 if(gui->posx_source == -1.0f && gui->posy_source == -1.0f)
2324 {
2325 #if 0 //TODO: replace individual cases with this generic one (will require passing 'form' through multiple layers...)
2326 if(form->functions && form->functions->initial_source_pos)
2327 {
2328 form->functions->initial_source_pos(iwd, iht, &x, &y);
2329 x += xpos;
2330 y += ypos;
2331 }
2332 #else
2333 if(mask_type & DT_MASKS_CIRCLE)
2334 {
2335 dt_masks_functions_circle.initial_source_pos(iwd, iht, &x, &y);
2336 x += xpos;
2337 y += ypos;
2338 }
2339 else if(mask_type & DT_MASKS_ELLIPSE)
2340 {
2341 dt_masks_functions_ellipse.initial_source_pos(iwd, iht, &x, &y);
2342 x += xpos;
2343 y += ypos;
2344 }
2345 else if(mask_type & DT_MASKS_PATH)
2346 {
2347 dt_masks_functions_path.initial_source_pos(iwd, iht, &x, &y);
2348 x += xpos;
2349 y += ypos;
2350 }
2351 else if(mask_type & DT_MASKS_BRUSH)
2352 {
2353 dt_masks_functions_brush.initial_source_pos(iwd, iht, &x, &y);
2354 x += xpos;
2355 y += ypos;
2356 }
2357 #endif
2358 else
2359 fprintf(stderr, "[dt_masks_calculate_source_pos_value] unsupported masks type when calculating source position value\n");
2360 }
2361 else
2362 {
2363 x = gui->posx_source;
2364 y = gui->posy_source;
2365 }
2366 }
2367 else if(gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
2368 {
2369 // if the user is actually adding the mask follow the cursor
2370 if(adding)
2371 {
2372 x = xpos + gui->posx_source - initial_xpos;
2373 y = ypos + gui->posy_source - initial_ypos;
2374 }
2375 else
2376 {
2377 // if not added yet set the start position
2378 x = gui->posx_source;
2379 y = gui->posy_source;
2380 }
2381 }
2382 else
2383 fprintf(stderr, "[dt_masks_calculate_source_pos_value] unknown source position type for setting source position value\n");
2384
2385 *px = x;
2386 *py = y;
2387 }
2388
2389 #include "detail.c"
2390
2391 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
2392 // vim: shiftwidth=2 expandtab tabstop=2 cindent
2393 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2394