Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
circle.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2014, 2016, 2021 Aldric Renaudin.
4 Copyright (C) 2013, 2018, 2020-2022 Pascal Obry.
5 Copyright (C) 2013 Simon Spannagel.
6 Copyright (C) 2013-2017 Tobias Ellinghaus.
7 Copyright (C) 2013-2016, 2019 Ulrich Pegelow.
8 Copyright (C) 2014-2016 Roman Lebedev.
9 Copyright (C) 2017-2019 Edgardo Hoszowski.
10 Copyright (C) 2018 johannes hanika.
11 Copyright (C) 2019 Andreas Schneider.
12 Copyright (C) 2019, 2023, 2025-2026 Aurélien PIERRE.
13 Copyright (C) 2019 Matthieu Moy.
14 Copyright (C) 2020, 2022 Chris Elston.
15 Copyright (C) 2020 GrahamByrnes.
16 Copyright (C) 2020 Heiko Bauke.
17 Copyright (C) 2020-2021 Hubert Kowalski.
18 Copyright (C) 2020-2021 Ralf Brown.
19 Copyright (C) 2021 Diederik Ter Rahe.
20 Copyright (C) 2022 Martin Bařinka.
21 Copyright (C) 2025-2026 Guillaume Stutin.
22
23 darktable is free software: you can redistribute it and/or modify
24 it under the terms of the GNU General Public License as published by
25 the Free Software Foundation, either version 3 of the License, or
26 (at your option) any later version.
27
28 darktable is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
32
33 You should have received a copy of the GNU General Public License
34 along with darktable. If not, see <http://www.gnu.org/licenses/>.
35*/
36#include "bauhaus/bauhaus.h"
37#include "common/debug.h"
38#include "common/undo.h"
39#include "control/conf.h"
40#include "develop/blend.h"
41#include "develop/imageop.h"
42#include "develop/masks.h"
44
45#define HARDNESS_MIN 0.0005f
46#define HARDNESS_MAX 1.0f
47
48#define BORDER_MIN 0.00005f
49#define BORDER_MAX 0.5f
50
51static void _circle_get_distance(float x, float y, float as, dt_masks_form_gui_t *gui, int index,
52 int num_points, int *inside, int *inside_border, int *near,
53 int *inside_source, float *dist)
54{
55 // initialise returned values
56 *inside_source = 0;
57 *inside = 0;
58 *inside_border = 0;
59 *near = -1;
60 *dist = FLT_MAX;
61
62 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
63 if(IS_NULL_PTR(gpt)) return;
64
65 // we first check if we are inside the source form
66 const float pt[2] = { x, y };
67
68 if(gpt->source && gpt->source_count > 0
69 && dt_masks_point_in_form_exact(pt, 1, gpt->source, 1, gpt->source_count) >= 0)
70 {
71 *inside_source = 1;
72 *inside = 1;
73
74 // distance from source center
75 const float center_dx = x - gpt->source[0];
76 const float center_dy = y - gpt->source[1];
77 *dist = sqf(center_dx) + sqf(center_dy);
78
79 return;
80 }
81
82 if(IS_NULL_PTR(gpt->points) || gpt->points_count <= 0 || !gpt->border || gpt->border_count <= 0) return;
83
84 // distance from center
85 const float center_dx = x - gpt->points[0];
86 const float center_dy = y - gpt->points[1];
87 *dist = sqf(center_dx) + sqf(center_dy);
88
89 // we check if it's inside borders
90 if(dt_masks_point_in_form_exact(pt, 1, gpt->border, 1, gpt->border_count) < 0) return;
91 *inside = 1;
92
93 // and we check if it's inside form
94 if(dt_masks_point_in_form_exact(pt, 1, gpt->points, 1, gpt->points_count) < 0)
95 *inside_border = 1;
96}
97
101static void _circle_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
102 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
103 int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
104{
105 _circle_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
106 inside_border, near, inside_source, dist);
107}
108
109static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
110{
111 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, 0,
112 NULL, NULL, NULL, _circle_distance_cb, NULL, NULL);
113}
114
115static void _circle_get_creation_values(const dt_masks_form_t *form, float *radius, float *border)
116{
117 const gboolean use_spot_defaults = dt_masks_form_uses_spot_defaults(form);
118 *radius = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/circle/size"
119 : "plugins/darkroom/masks/circle/size");
120 *border = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/circle/border"
121 : "plugins/darkroom/masks/circle/border");
122}
123
134
135static int _circle_get_points(dt_develop_t *dev, float x, float y, float radius, float radius2, float rotation,
136 float **points, int *points_count);
137
138// Build the temporary preview geometry for circle creation so the expose path only
139// handles drawing and buffer lifetime.
142{
143 float radius_shape = 0.0f;
144 float radius_border = 0.0f;
145 _circle_get_creation_values(form, &radius_shape, &radius_border);
146 radius_border += radius_shape;
147
148 float center[2];
150
151 *preview = (dt_masks_preview_buffers_t){ 0 };
152 int err = _circle_get_points(darktable.develop, center[0], center[1], radius_shape, 0.0f, 0.0f,
153 &preview->points, &preview->points_count);
154 if(!err && radius_shape != radius_border)
155 err = _circle_get_points(darktable.develop, center[0], center[1], radius_border, 0.0f, 0.0f,
156 &preview->border, &preview->border_count);
157
158 if(!err && dt_masks_form_is_clone(form))
159 {
160 float source_pos[2] = { 0.0f, 0.0f };
161 dt_masks_calculate_source_pos_origin(gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1],
162 &source_pos[0], &source_pos[1], FALSE);
163 const float center_source[2] = { source_pos[0] - gui->pos[0], source_pos[1] - gui->pos[1] };
164
166 if(IS_NULL_PTR(preview->source_points))
167 err = 1;
168
169 for(int i = 0; !err && i < preview->points_count; i++)
170 {
171 preview->source_points[i * 2] = preview->points[i * 2] + center_source[0];
172 preview->source_points[i * 2 + 1] = preview->points[i * 2 + 1] + center_source[1];
173 }
174 }
175
176 if(err)
178
179 return err;
180}
181
182static int _init_hardness(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
183{
185 increment, flow, _("Hardness: %3.2f%%"), 100.0f);
186 return 1;
187}
188
189static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
190{
191
193 increment, flow, _("Size: %3.2f%%"), 2.f * 100.f);
194 return 1;
195}
196
197static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
198{
199 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
200 increment, flow, _("Opacity: %3.2f%%"), 100.f);
201 return 1;
202}
203
205{
206 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
207 const dt_masks_node_circle_t *circle = (const dt_masks_node_circle_t *)(form->points)->data;
208 if(IS_NULL_PTR(circle)) return NAN;
209
210 switch(interaction)
211 {
213 return circle->radius;
215 return circle->border;
216 default:
217 return NAN;
218 }
219}
220
221static gboolean _circle_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
222{
223 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
224 const dt_masks_node_circle_t *circle = (const dt_masks_node_circle_t *)(form->points)->data;
225 if(IS_NULL_PTR(circle)) return FALSE;
226 center[0] = circle->center[0];
227 center[1] = circle->center[1];
228 *area = M_PI_F * sqf(circle->radius);
229 return TRUE;
230}
231
232static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
233 int index, const float amount, const dt_masks_increment_t increment, const int flow);
234static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
235 int index, const float amount, const dt_masks_increment_t increment, const int flow);
236
238 dt_masks_increment_t increment, int flow,
239 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
240{
241 if(IS_NULL_PTR(form)) return NAN;
242 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
243 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
244 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
245 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
246
247 switch(interaction)
248 {
250 if(!_change_size(form, gui, module, index, value, increment, flow)) return NAN;
251 return _circle_get_interaction_value(form, interaction);
253 if(!_change_hardness(form, gui, module, index, value, increment, flow)) return NAN;
254 return _circle_get_interaction_value(form, interaction);
255 default:
256 return NAN;
257 }
258}
259
260static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
261{
262 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
263 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)(form->points)->data;
264 if(IS_NULL_PTR(circle)) return 0;
265
266 circle->border = CLAMPF(dt_masks_apply_increment(circle->border, amount, increment, flow),
268
269 _init_hardness(form, amount, increment, flow);
270
271 // we recreate the form points
272 dt_masks_gui_form_create(form, gui, index, module);
273
274 return 1;
275}
276
277static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
278{
279 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
280 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)(form->points)->data;
281 if(IS_NULL_PTR(circle)) return 0;
282
283 // Sanitize
284 // do not exceed upper limit of 1.0 and lower limit of 0.004
285 if(amount > 1.0f && (circle->border > 1.0f ))
286 return 1;
287
288 const int node_hovered = gui->node_hovered;
289
290 // Growing/shrinking
291 if(node_hovered == -1 || node_hovered == 0)
292 {
293 circle->radius = dt_masks_apply_increment(circle->radius, amount, increment, flow);
294 }
295
296 _init_size(form, amount, increment, flow);
297
298 // we recreate the form points
299 dt_masks_gui_form_create(form, gui, index, module);
300
301 return 1;
302}
303
304/* Shape handlers receive widget-space coordinates, while normalized output-image
305 * coordinates come from `gui->rel_pos` and absolute output-image
306 * coordinates come from `gui->pos`. */
307static int _circle_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
308 uint32_t state, dt_masks_form_t *form, int parentid,
309 dt_masks_form_gui_t *gui, int index,
310 dt_masks_interaction_t interaction)
311{
312
313
314
315 if(gui->creation)
316 {
317 if(dt_modifier_is(state, GDK_CONTROL_MASK))
318 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
319 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
320 return _init_hardness(form, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
321 else
322 return _init_size(form, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
323 }
324 else if(gui->form_selected)
325 {
326 if(dt_modifier_is(state, GDK_CONTROL_MASK))
327 return dt_masks_form_change_opacity(form, parentid, up, flow);
328 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
329 return _change_hardness(form, gui, module, index, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
330 else
331 return _change_size(form, gui, module, index, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
332 }
333 return 0;
334}
335
336static int _circle_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
337 double pressure, int which, int type, uint32_t state,
338 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
339{
340 if(which == 1)
341 {
342 if(gui->creation)
343 {
344 if((dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK)) || dt_modifier_is(state, GDK_SHIFT_MASK))
345 {
346 // set some absolute or relative position for the source of the clone mask
348 return 1;
349 }
350
351 dt_iop_module_t *crea_module = gui->creation_module;
352 // we create the circle
354 if(IS_NULL_PTR(circle)) return 0;
355
356 _circle_init_new(form, gui, circle);
357 form->points = g_list_append(form->points, circle);
358 dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui);
359
360 return 1;
361 }
362 else // creation is FALSE
363 {
364 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
365 if(IS_NULL_PTR(gpt)) return 0;
366
368 {
369 // we start the source dragging
370 gui->delta[0] = gpt->source[0] - gui->pos[0];
371 gui->delta[1] = gpt->source[1] - gui->pos[1];
372 return 1;
373 }
374 else if(gui->form_selected && gui->edit_mode == DT_MASKS_EDIT_FULL)
375 {
376 // we start the form dragging
377 gui->delta[0] = gpt->points[0] - gui->pos[0];
378 gui->delta[1] = gpt->points[1] - gui->pos[1];
379 return 1;
380 }
381 else if(gui->handle_hovered >= 0 && gui->edit_mode == DT_MASKS_EDIT_FULL)
382 {
383 return 1;
384 }
385 }
386 }
387
388 return 0;
389}
390
391static int _circle_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
392 uint32_t state, dt_masks_form_t *form, int parentid,
393 dt_masks_form_gui_t *gui, int index)
394{
395
396
397
398
399
400
401
402 if(gui->form_dragging)
403 {
404 // we end the form dragging
405 return 1;
406 }
407 else if(gui->source_dragging)
408 {
409 return 1;
410 }
411 return 0;
412}
413
414static int _circle_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
415 int parentid, dt_masks_form_gui_t *gui, int index)
416{
417 return 0;
418}
419
420static int _circle_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure,
421 int which, dt_masks_form_t *form, int parentid,
422 dt_masks_form_gui_t *gui, int index)
423{
424 if(gui->creation)
425 {
426 // Let the cursor motion be redrawn as it moves in GUI
427 return 1;
428 }
429
430 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
431
432 if(gui->form_dragging || gui->source_dragging)
433 {
435 // apply delta to the current mouse position
436 float pts[2];
437 dt_masks_gui_delta_to_raw_norm(dev, gui, pts);
438
439 // we move all points in normalized input space
440 if(gui->form_dragging)
441 {
442 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
443 if(IS_NULL_PTR(circle)) return 0;
444 circle->center[0] = pts[0];
445 circle->center[1] = pts[1];
446 }
447 else if(gui->source_dragging)
448 {
449 form->source[0] = pts[0];
450 form->source[1] = pts[1];
451 }
452
453 // we recreate the form points
454 dt_masks_gui_form_create(form, gui, index, module);
455
456 return 1;
457 }
458 return 0;
459}
460
461static void _circle_draw_shape(cairo_t *cr, const float *points, const int points_count, const int coord_nb, const gboolean border, const gboolean source)
462{
463 cairo_move_to(cr, points[coord_nb * 2 + 2], points[coord_nb * 2 + 3]);
464 for(int i = 2; i < points_count; i++)
465 cairo_line_to(cr, points[i * 2], points[i * 2 + 1]);
466 cairo_close_path(cr);
467}
468
469static float *_points_to_transform(float x, float y, float radius, float wd, float ht, int *points_count)
470{
471 // how many points do we need?
472 const float r = radius * MIN(wd, ht);
473 const size_t l = (size_t)(2.0f * M_PI * r);
474 // allocate buffer
475 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((l + 1) * 2, 0);
476 if(IS_NULL_PTR(points))
477 {
478 *points_count = 0;
479 return NULL;
480 }
481 *points_count = l + 1;
482
483 // now we set the points, first the center, then the circumference
484 float center[2] = { x, y };
486 const float center_x = center[0];
487 const float center_y = center[1];
488 points[0] = center_x;
489 points[1] = center_y;
490 __OMP_PARALLEL_FOR_SIMD__(if(l > 100) aligned(points:64))
491 for(int i = 1; i < l + 1; i++)
492 {
493 const float alpha = (i - 1) * 2.0f * M_PI / (float)l;
494 points[i * 2] = center_x + r * cosf(alpha);
495 points[i * 2 + 1] = center_y + r * sinf(alpha);
496 }
497 return points;
498}
499
500static int _circle_get_points_source(dt_develop_t *dev, float x, float y, float xs, float ys, float radius,
501 float radius2, float rotation, float **points, int *points_count,
502 const dt_iop_module_t *module)
503{
504 // global callback signature
505
506 const float wd = dev->roi.raw_width;
507 const float ht = dev->roi.raw_height;
508
509 // compute the points of the target (center and circumference of circle)
510 // we get the point in RAW image reference
511 *points = _points_to_transform(x, y, radius, wd, ht, points_count);
512 if(IS_NULL_PTR(*points)) return 1;
513
514 // we transform with all distortion that happen *before* the module
515 // so we have now the TARGET points in module input reference
517 *points, *points_count))
518 goto error;
519
520 // now we move all the points by the shift
521 // so we have now the SOURCE points in module input reference
522 float pts[2] = { xs, ys };
525 pts, 1))
526 goto error;
527
528 {
529 const float dx = pts[0] - (*points)[0];
530 const float dy = pts[1] - (*points)[1];
531 __OMP_PARALLEL_FOR_SIMD__(if(*points_count > 100) aligned(points:64))
532 for(int i = 0; i < *points_count; i++)
533 {
534 (*points)[i * 2] += dx;
535 (*points)[i * 2 + 1] += dy;
536 }
537 }
538
539 // we apply the rest of the distortions (those after the module)
540 // so we have now the SOURCE points in final image reference
542 *points, *points_count))
543 goto error;
544
545 return 0;
546
547 // if we failed, then free all and return
548error:
550 *points = NULL;
551 *points_count = 0;
552 return 1;
553}
554
555static int _circle_get_points(dt_develop_t *dev, float x, float y, float radius, float radius2, float rotation,
556 float **points, int *points_count)
557{
558 // global callback signature
559
560 const float wd = dev->roi.raw_width;
561 const float ht = dev->roi.raw_height;
562
563 // compute the points we need to transform (center and circumference of circle)
564 *points = _points_to_transform(x, y, radius, wd, ht, points_count);
565 if(IS_NULL_PTR(*points)) return 1;
566
567 // and transform them with all distorted modules
568 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
569 {
571 *points = NULL;
572 *points_count = 0;
573 return 1;
574 }
575
576 // if we failed, then free all and return
577 return 0;
578}
579
580static void _circle_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int num_points)
581{
582 // add a preview when creating a circle
583 // in creation mode
584 if(gui->creation)
585 {
588 if(_circle_get_creation_preview(form, gui, &preview)) return;
589
590 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.points, preview.points_count,
591 preview.border, preview.border_count,
592 &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT,
593 CAIRO_LINE_CAP_ROUND, FALSE, FALSE);
594
595 // draw a cross where the source will be created
596 if(dt_masks_form_is_clone(form))
597 {
598 dt_masks_draw_source_preview(cr, zoom_scale, gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1], FALSE);
599 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.source_points, preview.points_count,
600 NULL, 0, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT,
601 CAIRO_LINE_CAP_ROUND, FALSE, TRUE);
602 }
604
605 return;
606 } // creation
607
608 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
609 if(IS_NULL_PTR(gpt)) return;
610
611 // we draw the main shape
612 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
613 if(gpt->points && gpt->points_count > 1)
614 dt_draw_shape_lines(DT_MASKS_NO_DASH, FALSE, cr, num_points, selected, zoom_scale, gpt->points,
615 gpt->points_count, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT);
616 // we draw the borders
617 if(gui->group_selected == index)
618 {
619 if(gpt->border && gpt->border_count > 1)
620 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, num_points, (gui->border_selected), zoom_scale, gpt->border,
621 gpt->border_count, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_ROUND);
622 }
623
624 // draw the source if any
625 if(gpt->source && gpt->source_count > 6 && gpt->points && gpt->points_count > 0)
626 {
627 dt_masks_gui_center_point_t center_pt = { .main = { gpt->points[0], gpt->points[1] },
628 .source = { gpt->source[0], gpt->source[1] }};
629
630 dt_masks_draw_source(cr, gui, index, num_points, zoom_scale, &center_pt, &dt_masks_functions_circle.draw_shape);
631 }
632}
633
634static void _bounding_box(const float *const points, int num_points, int *width, int *height, int *posx, int *posy)
635{
636 // search for min/max X and Y coordinates
637 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
638 for(int i = 1; i < num_points; i++) // skip point[0], which is circle's center
639 {
640 xmin = fminf(points[i * 2], xmin);
641 xmax = fmaxf(points[i * 2], xmax);
642 ymin = fminf(points[i * 2 + 1], ymin);
643 ymax = fmaxf(points[i * 2 + 1], ymax);
644 }
645 // set the min/max values we found
646 *posx = xmin;
647 *posy = ymin;
648 *width = (xmax - xmin);
649 *height = (ymax - ymin);
650}
651
652static int _circle_get_points_border(dt_develop_t *dev, struct dt_masks_form_t *form, float **points,
653 int *points_count, float **border, int *border_count, int source,
654 const dt_iop_module_t *module)
655{
656 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
657 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
658 if(IS_NULL_PTR(circle)) return 0;
659 float x = circle->center[0];
660 float y = circle->center[1];
661 if(source)
662 {
663 float xs = form->source[0];
664 float ys = form->source[1];
665 return _circle_get_points_source(dev, x, y, xs, ys, circle->radius, circle->radius, 0.0f, points, points_count, module);
666 }
667 else
668 {
669 if(form->functions->get_points(dev, x, y, circle->radius, circle->radius, 0, points, points_count) != 0)
670 return 1;
671 if(!IS_NULL_PTR(border))
672 {
673 float outer_radius = circle->radius + circle->border;
674 return form->functions->get_points(dev, x, y, outer_radius, outer_radius, 0, border, border_count);
675 }
676 return 0;
677 }
678 return 1;
679}
680
683 dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
684{
685 // we get the circle values
686 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
687 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
688 if(IS_NULL_PTR(circle)) return 0;
689 float wd = pipe->iwidth, ht = pipe->iheight;
690
691 // compute the points we need to transform (center and circumference of circle)
692 const float outer_radius = circle->radius + circle->border;
693 int num_points;
694 float *const restrict points =
695 _points_to_transform(form->source[0], form->source[1], outer_radius, wd, ht, &num_points);
696 if(IS_NULL_PTR(points))
697 return 1;
698
699 // and transform them with all distorted modules
700 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, num_points))
701 {
703 return 1;
704 }
705
706 _bounding_box(points, num_points, width, height, posx, posy);
708 return 0;
709}
710
711static int _circle_get_area(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe,
712 const dt_dev_pixelpipe_iop_t *const restrict piece,
713 dt_masks_form_t *const restrict form,
714 int *width, int *height, int *posx, int *posy)
715{
716 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
717 // we get the circle values
718 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
719 if(IS_NULL_PTR(circle)) return 0;
720 float wd = pipe->iwidth, ht = pipe->iheight;
721
722 // compute the points we need to transform (center and circumference of circle)
723 const float outer_radius = circle->radius + circle->border;
724 int num_points;
725 float *const restrict points =
726 _points_to_transform(circle->center[0], circle->center[1], outer_radius, wd, ht, &num_points);
727 if(IS_NULL_PTR(points))
728 return 1;
729
730 // and transform them with all distorted modules
731 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, num_points))
732 {
734 return 1;
735 }
736
737 _bounding_box(points, num_points, width, height, posx, posy);
739 return 0;
740}
741
742static int _circle_get_mask(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe,
743 const dt_dev_pixelpipe_iop_t *const restrict piece,
744 dt_masks_form_t *const restrict form,
745 float **buffer, int *width, int *height, int *posx, int *posy)
746{
747 double start2 = 0.0;
749
750 // we get the area
751 if(_circle_get_area(module, pipe, piece, form, width, height, posx, posy) != 0) return 1;
752
754 {
755 dt_print(DT_DEBUG_MASKS, "[masks %s] circle area took %0.04f sec\n", form->name, dt_get_wtime() - start2);
756 start2 = dt_get_wtime();
757 }
758
759 // we get the circle values
760 dt_masks_node_circle_t *const restrict circle = (dt_masks_node_circle_t *)((form->points)->data);
761
762 // we create a buffer of points with all points in the area
763 const int w = *width, h = *height;
764 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h * 2, 0);
765 if(IS_NULL_PTR(points))
766 return 1;
767
768 const float pos_x = *posx;
769 const float pos_y = *posy;
770 __OMP_PARALLEL_FOR__(if(h*w > 50000) num_threads(MIN(darktable.num_openmp_threads,(h*w)/20000)))
771 for(int i = 0; i < h; i++)
772 {
773 float *const restrict p = points + 2 * i * w;
774 const float y = i + pos_y;
775 __OMP_SIMD__(aligned(points : 64))
776 for(int j = 0; j < w; j++)
777 {
778 p[2*j] = pos_x + j;
779 p[2*j + 1] = y;
780 }
781 }
783 {
784 dt_print(DT_DEBUG_MASKS, "[masks %s] circle draw took %0.04f sec\n", form->name, dt_get_wtime() - start2);
785
786 start2 = dt_get_wtime();
787 }
788 // we back transform all this points
789 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)w * h))
790 {
792 return 1;
793 }
794
796 {
797 dt_print(DT_DEBUG_MASKS, "[masks %s] circle transform took %0.04f sec\n", form->name,
798 dt_get_wtime() - start2);
799 start2 = dt_get_wtime();
800 }
801
802 // we allocate the buffer
803 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
804 if(IS_NULL_PTR(*buffer))
805 {
807 return 1;
808 }
809
810 // we populate the buffer
811 float *const restrict ptbuffer = *buffer;
812 const int wi = pipe->iwidth, hi = pipe->iheight;
813 const int mindim = MIN(wi, hi);
814 const float centerx = circle->center[0] * wi;
815 const float centery = circle->center[1] * hi;
816 const float radius2 = circle->radius * mindim * circle->radius * mindim;
817 const float total2 = (circle->radius + circle->border) * mindim * (circle->radius + circle->border) * mindim;
818 const float border2 = total2 - radius2;
819 __OMP_PARALLEL_FOR_SIMD__(if(h*w > 50000) num_threads(MIN(darktable.num_openmp_threads,(h*w)/20000)) aligned(points, ptbuffer : 64))
820 for(int i = 0 ; i < h*w; i++)
821 {
822 // find the square of the distance from the center
823 const float l2 = sqf(points[2 * i] - centerx) + sqf(points[2 * i + 1] - centery);
824 // quadratic falloff between the circle's radius and the radius of the outside of the feathering
825 const float ratio = (total2 - l2) / border2;
826 // enforce 1.0 inside the circle and 0.0 outside the feathering
827 const float f = CLIP(ratio);
828 ptbuffer[i] = sqf(f);
829 }
830
832
834 dt_print(DT_DEBUG_MASKS, "[masks %s] circle fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
835
836 return 0;
837}
838
839
840static int _circle_get_mask_roi(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe,
841 const dt_dev_pixelpipe_iop_t *const restrict piece,
842 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
843 float *const restrict buffer)
844{
845 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 1;
846 if(IS_NULL_PTR(module)) return 1;
847 double start1 = 0.0;
848 double start2 = start1;
849
850 if(darktable.unmuted & DT_DEBUG_PERF) start2 = start1 = dt_get_wtime();
851
852 // we get the circle parameters
853 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
854 if(IS_NULL_PTR(circle)) return 1;
855 const int wi = pipe->iwidth, hi = pipe->iheight;
856 const float centerx = circle->center[0] * wi;
857 const float centery = circle->center[1] * hi;
858 const int min_dimention = MIN(wi, hi);
859 const float total_radius = (circle->radius + circle->border) * min_dimention;
860 const float sqr_radius = circle->radius * min_dimention * circle->radius * min_dimention;
861 const float sqr_total = total_radius * total_radius;
862 const float sqr_border = sqr_total - sqr_radius;
863
864 // we create a buffer of grid points for later interpolation: higher speed and reduced memory footprint;
865 // we match size of buffer to bounding box around the shape
866 const int width = roi->width;
867 const int height = roi->height;
868 const int px = roi->x;
869 const int py = roi->y;
870 const float iscale = 1.0f / roi->scale;
871 // scale dependent resolution: when zoomed in (scale > 1), use finer grid to avoid interpolation holes
872 const float grid_scale = 1.0f / MAX(roi->scale, 1e-6f);
873 const int grid = CLAMP((10.0f * grid_scale + 2.0f) / 3.0f, 1, 4);
874 const int grid_width = (width + grid - 1) / grid + 1; // grid dimension of total roi
875 const int grid_height = (height + grid - 1) / grid + 1; // grid dimension of total roi
876
877 // initialize output buffer with zero
878 memset(buffer, 0, sizeof(float) * width * height);
879
881 {
882 dt_print(DT_DEBUG_MASKS, "[masks %s] circle init took %0.04f sec\n", form->name, dt_get_wtime() - start2);
883 start2 = dt_get_wtime();
884 }
885
886 // we look at the outer circle of the shape - no effects outside of this circle;
887 // we need many points as we do not know how the circle might get distorted in the pixelpipe
888 const size_t circpts = dt_masks_roundup(MIN(360, 2 * M_PI * sqr_total), 8);
889 float *const restrict circ = dt_pixelpipe_cache_alloc_align_float_cache(circpts * 2, 0);
890 if(IS_NULL_PTR(circ)) return 1;
891 __OMP_PARALLEL_FOR__(if(circpts/8 > 1000))
892 for(int n = 0; n < circpts / 8; n++)
893 {
894 const float phi = (2.0f * M_PI * n) / circpts;
895 const float x = total_radius * cosf(phi);
896 const float y = total_radius * sinf(phi);
897 const float cx = centerx;
898 const float cy = centery;
899 const int index_x = 2 * n * 8;
900 const int index_y = 2 * n * 8 + 1;
901 // take advantage of symmetry
902 circ[index_x] = cx + x;
903 circ[index_y] = cy + y;
904 circ[index_x + 2] = cx + x;
905 circ[index_y + 2] = cy - y;
906 circ[index_x + 4] = cx - x;
907 circ[index_y + 4] = cy + y;
908 circ[index_x + 6] = cx - x;
909 circ[index_y + 6] = cy - y;
910 circ[index_x + 8] = cx + y;
911 circ[index_y + 8] = cy + x;
912 circ[index_x + 10] = cx + y;
913 circ[index_y + 10] = cy - x;
914 circ[index_x + 12] = cx - y;
915 circ[index_y + 12] = cy + x;
916 circ[index_x + 14] = cx - y;
917 circ[index_y + 14] = cy - x;
918 }
919
920 // we transform the outer circle from input image coordinates to current point in pixelpipe
921 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, circ,
922 circpts))
923 {
925 return 1;
926 }
927
929 {
930 dt_print(DT_DEBUG_MASKS, "[masks %s] circle outline took %0.04f sec\n", form->name, dt_get_wtime() - start2);
931 start2 = dt_get_wtime();
932 }
933
934 // we get the min/max values ...
935 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
936 for(int n = 0; n < circpts; n++)
937 {
938 // just in case that transform throws surprising values
939 if(!(isnormal(circ[2 * n]) && isnormal(circ[2 * n + 1]))) continue;
940
941 xmin = MIN(xmin, circ[2 * n]);
942 xmax = MAX(xmax, circ[2 * n]);
943 ymin = MIN(ymin, circ[2 * n + 1]);
944 ymax = MAX(ymax, circ[2 * n + 1]);
945 }
946
947#if 0
948 printf("xmin %f, xmax %f, ymin %f, ymax %f\n", xmin, xmax, ymin, ymax);
949 printf("wi %d, hi %d, iscale %f\n", wi, hi, iscale);
950 printf("w %d, h %d, px %d, py %d\n", w, h, px, py);
951#endif
952
953 // ... and calculate the bounding box with a bit of reserve
954 const int bbxm = CLAMP((int)floorf(xmin / iscale - px) / grid - 1, 0, grid_width - 1);
955 const int bbXM = CLAMP((int)ceilf(xmax / iscale - px) / grid + 2, 0, grid_width - 1);
956 const int bbym = CLAMP((int)floorf(ymin / iscale - py) / grid - 1, 0, grid_height - 1);
957 const int bbYM = CLAMP((int)ceilf(ymax / iscale - py) / grid + 2, 0, grid_height - 1);
958 const int bbw = bbXM - bbxm + 1;
959 const int bbh = bbYM - bbym + 1;
960
961#if 0
962 printf("bbxm %d, bbXM %d, bbym %d, bbYM %d\n", bbxm, bbXM, bbym, bbYM);
963 printf("gw %d, gh %d, bbw %d, bbh %d\n", gw, gh, bbw, bbh);
964#endif
965
967
969 {
970 dt_print(DT_DEBUG_MASKS, "[masks %s] circle bounding box took %0.04f sec\n", form->name, dt_get_wtime() - start2);
971 start2 = dt_get_wtime();
972 }
973
974 // check if there is anything to do at all;
975 // only if width and height of bounding box is 2 or greater the shape lies inside of roi and requires action
976 if(bbw <= 1 || bbh <= 1)
977 return 0;
978
979 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)bbw * bbh * 2, 0);
980 if(IS_NULL_PTR(points)) return 1;
981
982 // we populate the grid points in module coordinates
983 __OMP_PARALLEL_FOR__(collapse(2) if(bbw*bbh > 50000))
984 for(int j = bbym; j <= bbYM; j++)
985 for(int i = bbxm; i <= bbXM; i++)
986 {
987 const size_t index = (size_t)(j - bbym) * bbw + i - bbxm;
988 points[index * 2] = (grid * i + px) * iscale;
989 points[index * 2 + 1] = (grid * j + py) * iscale;
990 }
991
993 {
994 dt_print(DT_DEBUG_MASKS, "[masks %s] circle grid took %0.04f sec\n", form->name, dt_get_wtime() - start2);
995 start2 = dt_get_wtime();
996 }
997
998 // we back transform all these points to the input image coordinates
999 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points,
1000 (size_t)bbw * bbh))
1001 {
1003 return 1;
1004 }
1005
1007 {
1008 dt_print(DT_DEBUG_MASKS, "[masks %s] circle transform took %0.04f sec\n", form->name,
1009 dt_get_wtime() - start2);
1010 start2 = dt_get_wtime();
1011 }
1012
1013 // we calculate the mask values at the transformed points;
1014 // for results: re-use the points array
1015 __OMP_PARALLEL_FOR__(collapse(2) if(bbh*bbw > 50000) num_threads(MIN(darktable.num_openmp_threads,(height*width)/20000)))
1016 for(int j = 0; j < bbh; j++)
1017 for(int i = 0; i < bbw; i++)
1018 {
1019 const size_t index = (size_t)j * bbw + i;
1020 // find the square of the distance from the center
1021 const float l2 = sqf(points[2 * index] - centerx) + sqf(points[2 * index + 1] - centery);
1022 // quadratic falloff between the circle's radius and the radius of the outside of the feathering
1023 const float ratio = (sqr_total - l2) / sqr_border;
1024 // enforce 1.0 inside the circle and 0.0 outside the feathering
1025 const float f = CLAMP(ratio, 0.0f, 1.0f);
1026 points[2*index] = f * f;
1027 }
1028
1030 {
1031 dt_print(DT_DEBUG_MASKS, "[masks %s] circle draw took %0.04f sec\n", form->name,
1032 dt_get_wtime() - start2);
1033 start2 = dt_get_wtime();
1034 }
1035
1036 // we fill the pre-initialized output buffer by interpolation;
1037 // we only need to take the contents of our bounding box into account
1038 const int endx = MIN(width, bbXM * grid);
1039 const int endy = MIN(height, bbYM * grid);
1040 const float inv_grid2 = 1.0f / (grid * grid);
1041 float w0[4], w1[4];
1042 for(int i = 0; i < grid; i++)
1043 {
1044 w0[i] = (float)(grid - i);
1045 w1[i] = (float)i;
1046 }
1047 __OMP_PARALLEL_FOR__(if((size_t)(endy - bbym * grid) * (size_t)(endx - bbxm * grid) > 50000))
1048 for(int j = bbym * grid; j < endy; j++)
1049 {
1050 const int jj = j % grid;
1051 const int mj = j / grid - bbym;
1052 const float wj0 = w0[jj];
1053 const float wj1 = w1[jj];
1054 const size_t row_base = (size_t)mj * bbw;
1055 float *const row = buffer + (size_t)j * width;
1056 int ii = 0;
1057 int mi = 0;
1058 for(int i = bbxm * grid; i < endx; i++)
1059 {
1060 const size_t mindex = row_base + mi;
1061 const float wii0 = w0[ii];
1062 const float wii1 = w1[ii];
1063 row[i] = (points[mindex * 2] * wii0 * wj0
1064 + points[(mindex + 1) * 2] * wii1 * wj0
1065 + points[(mindex + bbw) * 2] * wii0 * wj1
1066 + points[(mindex + bbw + 1) * 2] * wii1 * wj1) * inv_grid2;
1067 ii++;
1068 if(ii == grid)
1069 {
1070 ii = 0;
1071 mi++;
1072 }
1073 }
1074 }
1075
1077
1079 {
1080 dt_print(DT_DEBUG_MASKS, "[masks %s] circle fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1081 dt_print(DT_DEBUG_MASKS, "[masks %s] circle total render took %0.04f sec\n", form->name,
1082 dt_get_wtime() - start1);
1083 }
1084
1085 return 0;
1086}
1087
1089{
1091 {
1092 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/circle/size", 0.001f, 0.5f);
1093 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/circle/border", 0.0005f, 0.5f);
1094 }
1095 else
1096 {
1097 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/circle/size", 0.001f, 0.5f);
1098 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/circle/border", 0.0005f, 0.5f);
1099 }
1100}
1101
1102static void _circle_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1103{
1104 snprintf(form->name, sizeof(form->name), _("circle #%d"), (int)nb);
1105}
1106
1107static void _circle_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1108 const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
1109{
1110 // circle has same controls on creation and on edit
1111 g_snprintf(msgbuf, msgbuf_len,
1112 _("<b>Size</b>: scroll, <b>Hardness</b>: shift+scroll\n"
1113 "<b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1114}
1115
1117{
1118 // unused arg, keep compiler from complaining
1120}
1121
1122static void _circle_initial_source_pos(const float iwd, const float iht, float *x, float *y)
1123{
1124 const float radius = MIN(0.5f, dt_conf_get_float("plugins/darkroom/spots/circle/size"));
1125 float offset[2] = { radius, -radius };
1127 *x = offset[0];
1128 *y = offset[1];
1129}
1130
1131// The function table for circles. This must be public, i.e. no "static" keyword.
1133 .point_struct_size = sizeof(struct dt_masks_node_circle_t),
1134 .sanitize_config = _circle_sanitize_config,
1135 .set_form_name = _circle_set_form_name,
1136 .set_hint_message = _circle_set_hint_message,
1137 .duplicate_points = _circle_duplicate_points,
1138 .initial_source_pos = _circle_initial_source_pos,
1139 .get_distance = _circle_get_distance,
1140 .get_points = _circle_get_points,
1141 .get_points_border = _circle_get_points_border,
1142 .get_mask = _circle_get_mask,
1143 .get_mask_roi = _circle_get_mask_roi,
1144 .get_area = _circle_get_area,
1145 .get_source_area = _circle_get_source_area,
1146 .get_gravity_center = _circle_get_gravity_center,
1147 .get_interaction_value = _circle_get_interaction_value,
1148 .set_interaction_value = _circle_set_interaction_value,
1149 .update_hover = _find_closest_handle,
1150 .mouse_moved = _circle_events_mouse_moved,
1151 .mouse_scrolled = _circle_events_mouse_scrolled,
1152 .button_pressed = _circle_events_button_pressed,
1153 .button_released = _circle_events_button_released,
1154 .key_pressed = _circle_events_key_pressed,
1155 .post_expose = _circle_events_post_expose,
1156 .draw_shape = _circle_draw_shape
1157};
1158
1159
1160
1161// clang-format off
1162// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1163// vim: shiftwidth=2 expandtab tabstop=2 cindent
1164// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1165// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static float _circle_set_interaction_value(dt_masks_form_t *form, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
Definition circle.c:237
static void _circle_get_distance(float x, float y, float as, dt_masks_form_gui_t *gui, int index, int num_points, int *inside, int *inside_border, int *near, int *inside_source, float *dist)
Definition circle.c:51
static void _circle_sanitize_config(dt_masks_type_t type)
Definition circle.c:1088
static float * _points_to_transform(float x, float y, float radius, float wd, float ht, int *points_count)
Definition circle.c:469
static int _circle_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index, dt_masks_interaction_t interaction)
Definition circle.c:307
static void _circle_duplicate_points(dt_develop_t *dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition circle.c:1116
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
Definition circle.c:109
#define HARDNESS_MIN
Definition circle.c:45
static int _circle_get_mask(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const restrict piece, dt_masks_form_t *const restrict form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition circle.c:742
static void _circle_init_new(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_masks_node_circle_t *circle)
Definition circle.c:124
static int _init_hardness(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:182
static int _circle_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition circle.c:420
static void _circle_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int num_points)
Definition circle.c:580
static float _circle_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition circle.c:204
static int _circle_get_points(dt_develop_t *dev, float x, float y, float radius, float radius2, float rotation, float **points, int *points_count)
Definition circle.c:555
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:197
static void _circle_distance_cb(float pointer_x, float pointer_y, float cursor_radius, dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside, int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
Circle-specific inside/border hit testing adapter.
Definition circle.c:101
static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:277
#define HARDNESS_MAX
Definition circle.c:46
static int _circle_get_points_border(dt_develop_t *dev, struct dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, int source, const dt_iop_module_t *module)
Definition circle.c:652
static int _circle_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 circle.c:681
static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:260
static int _circle_events_button_released(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition circle.c:391
static int _circle_get_mask_roi(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const restrict piece, dt_masks_form_t *const form, const dt_iop_roi_t *const roi, float *const restrict buffer)
Definition circle.c:840
static gboolean _circle_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
Definition circle.c:221
static void _circle_initial_source_pos(const float iwd, const float iht, float *x, float *y)
Definition circle.c:1122
static void _circle_get_creation_values(const dt_masks_form_t *form, float *radius, float *border)
Definition circle.c:115
static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:189
static void _circle_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition circle.c:1102
const dt_masks_functions_t dt_masks_functions_circle
Definition circle.c:1132
static int _circle_get_area(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const restrict piece, dt_masks_form_t *const restrict form, int *width, int *height, int *posx, int *posy)
Definition circle.c:711
static int _circle_get_creation_preview(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
Definition circle.c:140
static void _circle_draw_shape(cairo_t *cr, const float *points, const int points_count, const int coord_nb, const gboolean border, const gboolean source)
Definition circle.c:461
static int _circle_get_points_source(dt_develop_t *dev, float x, float y, float xs, float ys, float radius, float radius2, float rotation, float **points, int *points_count, const dt_iop_module_t *module)
Definition circle.c:500
static int _circle_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition circle.c:414
static void _bounding_box(const float *const points, int num_points, int *width, int *height, int *posx, int *posy)
Definition circle.c:634
static int _circle_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure, int which, int type, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition circle.c:336
static void _circle_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form, const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
Definition circle.c:1107
const dt_aligned_pixel_t f
static const int row
int type
float dt_conf_get_float(const char *name)
float dt_conf_get_and_sanitize_float(const char *name, float min, float max)
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define __OMP_SIMD__(...)
Definition darktable.h:274
@ DT_DEBUG_PERF
Definition darktable.h:741
@ DT_DEBUG_MASKS
Definition darktable.h:749
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#define M_PI_F
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1589
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1162
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:1621
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1650
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:106
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_MASKS_DASH_STICK
Definition draw.h:94
@ DT_MASKS_NO_DASH
Definition draw.h:93
static void dt_draw_shape_lines(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap)
Draw the lines of a mask shape.
Definition draw.h:734
static const float x
const float l2
#define w1
Definition lmmse.c:59
void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, float *px, float *py, const int adding)
Compute preview-space source position for drawing the clone indicator.
void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module)
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
static void dt_masks_draw_source_preview(cairo_t *cr, const float zoom_scale, dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, const int adding)
Definition masks.h:1141
int dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up, const int flow)
int dt_masks_point_in_form_exact(const float *pts, int num_pts, const float *points, int points_start, int points_count)
Check whether any 2D point in pts[] lies inside the form points[].
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *gui, const int index, const int nb, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
dt_masks_type_t
Definition masks.h:130
@ DT_MASKS_NON_CLONE
Definition masks.h:139
@ DT_MASKS_CLONE
Definition masks.h:135
dt_masks_interaction_t
Definition masks.h:303
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:306
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:305
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:617
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks.h:821
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
static gboolean dt_masks_form_is_clone(const dt_masks_form_t *form)
Definition masks.h:655
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *form, const char *feature, float amount, float v_min, float v_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
dt_masks_increment_t
Definition masks.h:194
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:196
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:197
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *gui, dt_masks_form_t *form)
Initialize the clone source position based on current GUI state.
static int dt_masks_roundup(int num, int mult)
Definition masks.h:1475
static void dt_masks_draw_preview_shape(cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, void(*const *draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source), const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks.h:787
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Shared selection logic for node/handle/segment hit testing.
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:625
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state)
Decide initial source positioning mode for clone masks.
static gboolean dt_masks_form_uses_spot_defaults(const dt_masks_form_t *form)
Definition masks.h:650
static void dt_masks_reset_source(dt_masks_form_t *form)
Definition masks.h:660
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define CLIP(x)
Definition math.h:81
#define M_PI
Definition math.h:45
float iscale
Definition mipmap_cache.c:2
static float gh(const float f)
const float uint32_t state[4]
const float r
int32_t num_openmp_threads
Definition darktable.h:786
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
int32_t raw_height
Definition develop.h:228
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
struct dt_develop_t::@17 roi
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
gboolean source_selected
Definition masks.h:492
gboolean source_dragging
Definition masks.h:500
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
dt_iop_module_t * creation_module
Definition masks.h:519
gboolean form_dragging
Definition masks.h:499
gboolean creation
Definition masks.h:517
gboolean form_selected
Definition masks.h:490
gboolean border_selected
Definition masks.h:491
float delta[2]
Definition masks.h:469
const dt_masks_functions_t * functions
Definition masks.h:381
dt_masks_type_t type
Definition masks.h:380
float source[2]
Definition masks.h:387
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
int(* get_points)(struct dt_develop_t *dev, float x, float y, float radius_a, float radius_b, float rotation, float **points, int *points_count)
Definition masks.h:326
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:370
struct dt_masks_gui_center_point_t::@34 main
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29