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 "system/macros.h"
37#include "system/openmp.h"
38#include "common/logging.h"
39#include "common/times.h"
41#include "common/conf.h"
42#include "develop/imageop.h"
43#include "develop/masks.h"
44#include "develop/masks_gui.h"
47#include "math/openmp_maths.h"
49
50#define FADING_MIN 0.0005f
51#define FADING_MAX 1.0f
52
53#define BORDER_MIN 0.00005f
54#define BORDER_MAX 0.5f
55
56static void _circle_get_distance(float x, float y, float as, dt_masks_form_gui_t *gui, int index,
57 int num_points, int *inside, int *inside_border, int *near_handle,
58 int *inside_source, float *dist)
59{
60 // initialise returned values
61 *inside_source = 0;
62 *inside = 0;
63 *inside_border = 0;
64 *near_handle = -1;
65 *dist = FLT_MAX;
66
67 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
68 if(IS_NULL_PTR(gpt)) return;
69 /* Nothing to walk when the cursor cannot reach a sample: the box every sample spans, grown by
70 * the cursor's reach, answers for the whole shape in four comparisons, and every answer
71 * initialised above is what the walk would have given -- nothing inside, nothing near. */
72 if(!dt_masks_gui_points_reach(gpt, x, y, 2.0f * as)) return;
73
74 // we first check if we are inside the source form
75 const float pt[2] = { x, y };
76
77 if(gpt->source && gpt->source_count > 0
78 && dt_masks_point_in_form_exact(pt, 1, gpt->source, 1, gpt->source_count, NULL, 0) >= 0)
79 {
80 *inside_source = 1;
81 *inside = 1;
82
83 // distance from source center
84 const float center_dx = x - gpt->source[0];
85 const float center_dy = y - gpt->source[1];
86 *dist = sqf(center_dx) + sqf(center_dy);
87
88 return;
89 }
90
91 if(IS_NULL_PTR(gpt->points) || gpt->points_count <= 0 || !gpt->border || gpt->border_count <= 0) return;
92
93 // distance from center
94 const float center_dx = x - gpt->points[0];
95 const float center_dy = y - gpt->points[1];
96 *dist = sqf(center_dx) + sqf(center_dy);
97
98 // we check if it's inside borders
99 if(dt_masks_point_in_form_exact(pt, 1, gpt->border, 1, gpt->border_count, NULL, 0) < 0) return;
100 *inside = 1;
101
102 // and we check if it's inside form
103 if(dt_masks_point_in_form_exact(pt, 1, gpt->points, 1, gpt->points_count, NULL, 0) < 0)
104 *inside_border = 1;
105}
106
110static void _circle_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
111 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
112 int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
113{
114 _circle_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
115 inside_border, near_handle, inside_source, dist);
116}
117
118static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
119{
120 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, 0,
121 NULL, NULL, NULL, _circle_distance_cb, NULL, NULL);
122}
123
124static void _circle_get_creation_values(const dt_masks_form_t *form, float *radius, float *border)
125{
126 const gboolean use_spot_defaults = dt_masks_form_uses_spot_defaults(form);
127 *radius = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/circle/size"
128 : "plugins/darkroom/masks/circle/size");
129 *border = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/circle/border"
130 : "plugins/darkroom/masks/circle/border");
131}
132
134{
135 dt_masks_gui_cursor_to_raw_norm(gui->dev, gui, circle->center);
136 _circle_get_creation_values(form, &circle->radius, &circle->border);
137
138 if(dt_masks_form_is_clone(form))
140 else
142}
143
144static int _circle_get_points(dt_develop_t *dev, float x, float y, float radius, float radius2, float rotation,
145 float **points, int *points_count);
146
147// Build the temporary preview geometry for circle creation so the expose path only
148// handles drawing and buffer lifetime.
151{
152 float radius_shape = 0.0f;
153 float radius_border = 0.0f;
154 _circle_get_creation_values(form, &radius_shape, &radius_border);
155 radius_border += radius_shape;
156
157 float center[2];
159
161 int err = _circle_get_points(gui->dev, center[0], center[1], radius_shape, 0.0f, 0.0f,
162 &preview->points, &preview->points_count);
163 if(!err && radius_shape != radius_border)
164 err = _circle_get_points(gui->dev, center[0], center[1], radius_border, 0.0f, 0.0f,
165 &preview->border, &preview->border_count);
166
167 if(!err && dt_masks_form_is_clone(form))
169
170 if(err)
172
173 return err;
174}
175
176static int _init_fading(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
177{
179 increment, flow, _("Fading: %3.2f%%"), 100.0f);
180 return 1;
181}
182
183static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
184{
185
187 increment, flow, _("Size: %3.2f%%"), 2.f * 100.f);
188 return 1;
189}
190
191static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
192{
193 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
194 increment, flow, _("Opacity: %3.2f%%"), 100.f);
195 return 1;
196}
197
199{
200 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
201 const dt_masks_node_circle_t *circle = (const dt_masks_node_circle_t *)(form->points)->data;
202 if(IS_NULL_PTR(circle)) return NAN;
203
204 switch(interaction)
205 {
207 return circle->radius;
209 return circle->border;
210 default:
211 return NAN;
212 }
213}
214
215static gboolean _circle_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
216{
217 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
218 const dt_masks_node_circle_t *circle = (const dt_masks_node_circle_t *)(form->points)->data;
219 if(IS_NULL_PTR(circle)) return FALSE;
220 center[0] = circle->center[0];
221 center[1] = circle->center[1];
222 *area = M_PI_F * sqf(circle->radius);
223 return TRUE;
224}
225
226static int _change_fading(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
227 int index, const float amount, const dt_masks_increment_t increment, const int flow);
228static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
229 int index, const float amount, const dt_masks_increment_t increment, const int flow);
230
232 dt_masks_increment_t increment, int flow,
233 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
234{
235 if(IS_NULL_PTR(form)) return NAN;
236 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
237 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
238 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
239 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
240
241 switch(interaction)
242 {
244 if(!_change_size(form, gui, module, index, value, increment, flow)) return NAN;
245 return _circle_get_interaction_value(form, interaction);
247 if(!_change_fading(form, gui, module, index, value, increment, flow)) return NAN;
248 return _circle_get_interaction_value(form, interaction);
249 default:
250 return NAN;
251 }
252}
253
254static int _change_fading(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)
255{
256 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
257 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)(form->points)->data;
258 if(IS_NULL_PTR(circle)) return 0;
259
260 circle->border = CLAMPF(dt_masks_apply_increment(circle->border, amount, increment, flow),
262
263 _init_fading(form, amount, increment, flow);
264
265 // we recreate the form points
266 dt_masks_gui_form_create(form, gui, index, module);
267
268 return 1;
269}
270
271static 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)
272{
273 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
274 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)(form->points)->data;
275 if(IS_NULL_PTR(circle)) return 0;
276
277 // Sanitize
278 // do not exceed upper limit of 1.0 and lower limit of 0.004
279 if(amount > 1.0f && (circle->border > 1.0f ))
280 return 1;
281
282 const int node_hovered = gui->node_hovered;
283
284 // Growing/shrinking
285 if(node_hovered == -1 || node_hovered == 0)
286 {
287 circle->radius = dt_masks_apply_increment(circle->radius, amount, increment, flow);
288 }
289
290 _init_size(form, amount, increment, flow);
291
292 // we recreate the form points
293 dt_masks_gui_form_create(form, gui, index, module);
294
295 return 1;
296}
297
298/* Shape handlers receive widget-space coordinates, while normalized output-image
299 * coordinates come from `gui->rel_pos` and absolute output-image
300 * coordinates come from `gui->pos`. */
301static int _circle_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
302 uint32_t state, dt_masks_form_t *form, int parentid,
303 dt_masks_form_gui_t *gui, int index,
304 dt_masks_interaction_t interaction)
305{
306
307
308
309 // `state` is the caller's raw key state, kept for the callback signature: the property to
310 // act on was already resolved from it by dt_masks_scroll_get_interaction(). A circle owns no
311 // rotation, so that mapping falls through and the wheel does nothing here.
312 if(gui->creation)
313 {
314 switch(interaction)
315 {
317 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
319 return _init_fading(form, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
321 return _init_size(form, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
322 default:
323 return 0;
324 }
325 }
326 else if(gui->form_selected)
327 {
328 switch(interaction)
329 {
331 return dt_masks_form_change_opacity(gui->dev, form, parentid, up, flow);
333 return _change_fading(form, gui, module, index, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
335 return _change_size(form, gui, module, index, up ? +1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
336 default:
337 return 0;
338 }
339 }
340 return 0;
341}
342
343static int _circle_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
344 double pressure, int which, int type, uint32_t state,
345 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
346{
347 if(which == 1)
348 {
349 if(gui->creation)
350 {
351 if((dt_modifier_is(state, DT_PRIMARY_MASK | GDK_SHIFT_MASK)) || dt_modifier_is(state, GDK_SHIFT_MASK))
352 {
353 // set some absolute or relative position for the source of the clone mask
355 return 1;
356 }
357
358 dt_iop_module_t *crea_module = gui->creation_module;
359 // we create the circle
361 if(IS_NULL_PTR(circle)) return 0;
362
363 _circle_init_new(form, gui, circle);
364 form->points = g_list_append(form->points, circle);
365 dt_masks_gui_form_save_creation(gui->dev, crea_module, form, gui);
366
367 return 1;
368 }
369 else // creation is FALSE
370 {
371 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
372 if(IS_NULL_PTR(gpt)) return 0;
373
375 {
376 // we start the source dragging
377 gui->delta[0] = gpt->source[0] - gui->pos[0];
378 gui->delta[1] = gpt->source[1] - gui->pos[1];
379 return 1;
380 }
381 else if(gui->form_selected && gui->edit_mode == DT_MASKS_EDIT_FULL)
382 {
383 // we start the form dragging
384 gui->delta[0] = gpt->points[0] - gui->pos[0];
385 gui->delta[1] = gpt->points[1] - gui->pos[1];
386 return 1;
387 }
388 else if(gui->handle_hovered >= 0 && gui->edit_mode == DT_MASKS_EDIT_FULL)
389 {
390 return 1;
391 }
392 }
393 }
394
395 return 0;
396}
397
398static int _circle_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
399 uint32_t state, dt_masks_form_t *form, int parentid,
400 dt_masks_form_gui_t *gui, int index)
401{
402
403
404
405
406
407
408
409 if(gui->form_dragging)
410 {
411 // we end the form dragging
412 return 1;
413 }
414 else if(gui->source_dragging)
415 {
416 return 1;
417 }
418 return 0;
419}
420
421static int _circle_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
422 int parentid, dt_masks_form_gui_t *gui, int index)
423{
424 return 0;
425}
426
427static int _circle_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure,
428 int which, dt_masks_form_t *form, int parentid,
429 dt_masks_form_gui_t *gui, int index)
430{
431 if(gui->creation)
432 {
433 // Let the cursor motion be redrawn as it moves in GUI
434 return 1;
435 }
436
437 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
438
439 if(gui->form_dragging || gui->source_dragging)
440 {
441 dt_develop_t *dev = gui->dev;
442 // apply delta to the current mouse position
443 float pts[2];
444 dt_masks_gui_delta_to_raw_norm(dev, gui, pts);
445
446 // we move all points in normalized input space
447 if(gui->form_dragging)
448 {
449 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
450 if(IS_NULL_PTR(circle)) return 0;
451 circle->center[0] = pts[0];
452 circle->center[1] = pts[1];
453 }
454 else if(gui->source_dragging)
455 {
456 form->source[0] = pts[0];
457 form->source[1] = pts[1];
458 }
459
460 // we recreate the form points
461 dt_masks_gui_form_create(form, gui, index, module);
462
463 return 1;
464 }
465 return 0;
466}
467
468static void _circle_draw_shape(dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int coord_nb, const gboolean border, const gboolean source,
469 const dt_masks_skip_range_t *skips, const int skip_count)
470{
471 /* A circle has no self-intersections, so it never carries an exclusion list -- these are the
472 * shared shape_draw_function_t signature, not something to honour here. */
473 (void)dev; (void)border; (void)source; (void)skips; (void)skip_count;
474
475 /* One line_to per point sampled at RAW resolution is several per device pixel; emit at the
476 * resolution the context can actually show. See dt_draw_min_emit_step() (mirrors the brush). */
477 const double min_step = dt_draw_min_emit_step(cr);
478 const double min_step2 = min_step * min_step;
479 double last_x = points[coord_nb * 2 + 2], last_y = points[coord_nb * 2 + 3];
480 cairo_move_to(cr, last_x, last_y);
481 for(int i = 2; i < points_count; i++)
482 {
483 const double x = points[i * 2], y = points[i * 2 + 1];
484 const double dx = x - last_x, dy = y - last_y;
485 if((dx * dx + dy * dy) < min_step2) continue;
486 cairo_line_to(cr, x, y);
487 last_x = x; last_y = y;
488 }
489 cairo_close_path(cr);
490}
491
492static float *_points_to_transform(dt_develop_t *dev, float x, float y, float radius, float wd, float ht, int *points_count)
493{
494 // how many points do we need?
495 const float r = radius * MIN(wd, ht);
496 const size_t l = (size_t)(2.0f * M_PI * r);
497 // allocate buffer
498 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((l + 1) * 2, 0);
499 if(IS_NULL_PTR(points))
500 {
501 *points_count = 0;
502 return NULL;
503 }
504 *points_count = l + 1;
505
506 // now we set the points, first the center, then the circumference
507 float center[2] = { x, y };
509 const float center_x = center[0];
510 const float center_y = center[1];
511 points[0] = center_x;
512 points[1] = center_y;
513 __OMP_PARALLEL_FOR_SIMD__(if(l > 100) aligned(points:64))
514 for(int i = 1; i < l + 1; i++)
515 {
516 const float alpha = (i - 1) * 2.0f * M_PI / (float)l;
517 points[i * 2] = center_x + r * cosf(alpha);
518 points[i * 2 + 1] = center_y + r * sinf(alpha);
519 }
520 return points;
521}
522
523static int _circle_get_points_source(dt_develop_t *dev, float x, float y, float xs, float ys, float radius,
524 float radius2, float rotation, float **points, int *points_count,
525 const dt_iop_module_t *module)
526{
527 // global callback signature
528
530 const float wd = geometry.raw_width;
531 const float ht = geometry.raw_height;
532
533 // compute the points of the target (center and circumference of circle)
534 // we get the point in RAW image reference
535 *points = _points_to_transform(dev, x, y, radius, wd, ht, points_count);
536 if(IS_NULL_PTR(*points)) return 1;
537
538 return dt_masks_points_shift_to_source(dev, module, points, points_count, xs, ys, 0);
539}
540
541static int _circle_get_points(dt_develop_t *dev, float x, float y, float radius, float radius2, float rotation,
542 float **points, int *points_count)
543{
544 // global callback signature
545
547 const float wd = geometry.raw_width;
548 const float ht = geometry.raw_height;
549
550 // compute the points we need to transform (center and circumference of circle)
551 *points = _points_to_transform(dev, x, y, radius, wd, ht, points_count);
552 if(IS_NULL_PTR(*points)) return 1;
553
554 // and transform them with all distorted modules
555 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
556 {
558 *points = NULL;
559 *points_count = 0;
560 return 1;
561 }
562
563 // if we failed, then free all and return
564 return 0;
565}
566
567static void _circle_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int num_points)
568{
569 // add a preview when creating a circle
570 // in creation mode
571 if(gui->creation)
572 {
575 if(_circle_get_creation_preview(form, gui, &preview)) return;
576
577 dt_masks_draw_preview_shape(gui->dev, cr, zoom_scale, num_points, preview.points, preview.points_count,
578 preview.border, preview.border_count,
579 &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT,
580 CAIRO_LINE_CAP_ROUND, FALSE, FALSE);
581
582 // draw a cross where the source will be created
583 if(dt_masks_form_is_clone(form))
584 {
585 dt_masks_draw_source_preview(cr, zoom_scale, gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1], FALSE);
586 dt_masks_draw_preview_shape(gui->dev, cr, zoom_scale, num_points, preview.source_points, preview.points_count,
587 NULL, 0, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT,
588 CAIRO_LINE_CAP_ROUND, FALSE, TRUE);
589 }
591
592 return;
593 } // creation
594
595 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
596 if(IS_NULL_PTR(gpt)) return;
597
598 // we draw the main shape
599 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
600 if(gpt->points && gpt->points_count > 1)
601 dt_draw_shape_lines(gui->dev, DT_MASKS_NO_DASH, FALSE, cr, num_points, selected, zoom_scale, gpt->points,
602 gpt->points_count, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_BUTT, NULL, 0);
603 // we draw the borders
604 if(gui->group_selected == index)
605 {
606 if(gpt->border && gpt->border_count > 1)
607 dt_draw_shape_lines(gui->dev, DT_MASKS_DASH_STICK, FALSE, cr, num_points, (gui->border_selected), zoom_scale, gpt->border,
608 gpt->border_count, &dt_masks_functions_circle.draw_shape, CAIRO_LINE_CAP_ROUND, NULL, 0);
609 }
610
611 // draw the source if any
612 if(gpt->source && gpt->source_count > 6 && gpt->points && gpt->points_count > 0)
613 {
614 dt_masks_gui_center_point_t center_pt = { .main = { gpt->points[0], gpt->points[1] },
615 .source = { gpt->source[0], gpt->source[1] }};
616
617 dt_masks_draw_source(cr, gui, index, num_points, zoom_scale, &center_pt, &dt_masks_functions_circle.draw_shape);
618 }
619}
620
621
623 float **points,
624 int *points_count, float **border, int *border_count,
625 dt_masks_skip_range_t **border_skips, int *border_skip_count,
626 int source,
627 const dt_iop_module_t *module)
628{
629 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
630 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
631
632 /* No geometry to build an outline from: the outline is empty, which is a result, not a
633 * failure. Reporting success here (as this did) told the caller to cache a NULL outline as
634 * if it had been built. */
635 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
636 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
637 if(IS_NULL_PTR(circle)) return DT_MASKS_RASTER_EMPTY;
638 float x = circle->center[0];
639 float y = circle->center[1];
640 if(source)
641 {
642 float xs = form->source[0];
643 float ys = form->source[1];
645 _circle_get_points_source(dev, x, y, xs, ys, circle->radius, circle->radius, 0.0f, points, points_count, module));
646 }
647 else
648 {
649 if(form->functions->get_points(dev, x, y, circle->radius, circle->radius, 0, points, points_count) != 0)
651 if(!IS_NULL_PTR(border))
652 {
653 float outer_radius = circle->radius + circle->border;
655 form->functions->get_points(dev, x, y, outer_radius, outer_radius, 0, border, border_count));
656 }
657 return DT_MASKS_RASTER_OK;
658 }
659}
660
663 dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
664{
665 /* Every early exit below must leave the out-parameters defined: callers read them
666 * whenever this reports anything but ERROR, and three of them used to report success
667 * for a shape with no geometry while writing none of these. */
668 *width = 0;
669 *height = 0;
670 *posx = 0;
671 *posy = 0;
672 // we get the circle values
673 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
674 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
675 if(IS_NULL_PTR(circle)) return DT_MASKS_RASTER_EMPTY;
676 float wd = pipe->iwidth, ht = pipe->iheight;
677
678 // compute the points we need to transform (center and circumference of circle)
679 const float outer_radius = circle->radius + circle->border;
680 int num_points;
681 float *const restrict points =
682 _points_to_transform(pipe->dev, form->source[0], form->source[1], outer_radius, wd, ht, &num_points);
683 if(IS_NULL_PTR(points))
685
686 // and transform them with all distorted modules
687 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, num_points))
688 {
691 }
692
693 dt_masks_points_bounding_box(points, num_points, width, height, posx, posy);
695 return DT_MASKS_RASTER_OK;
696}
697
699 const dt_dev_pixelpipe_iop_t *const restrict piece,
700 dt_masks_form_t *const restrict form,
701 int *width, int *height, int *posx, int *posy)
702{
703 *width = 0;
704 *height = 0;
705 *posx = 0;
706 *posy = 0;
707 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
708 // we get the circle values
709 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
710 if(IS_NULL_PTR(circle)) return DT_MASKS_RASTER_EMPTY;
711 float wd = pipe->iwidth, ht = pipe->iheight;
712
713 // compute the points we need to transform (center and circumference of circle)
714 const float outer_radius = circle->radius + circle->border;
715 int num_points;
716 float *const restrict points =
717 _points_to_transform(pipe->dev, circle->center[0], circle->center[1], outer_radius, wd, ht, &num_points);
718 if(IS_NULL_PTR(points))
720
721 // and transform them with all distorted modules
722 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, num_points))
723 {
726 }
727
728 dt_masks_points_bounding_box(points, num_points, width, height, posx, posy);
730 return DT_MASKS_RASTER_OK;
731}
732
734 const dt_dev_pixelpipe_iop_t *const restrict piece,
735 dt_masks_form_t *const restrict form,
736 float **buffer, int *width, int *height, int *posx, int *posy)
737{
738 *buffer = NULL;
739 *width = 0;
740 *height = 0;
741 *posx = 0;
742 *posy = 0;
743 double start2 = 0.0;
745
746 // we get the area
747 const dt_masks_raster_result_t area = _circle_get_area(module, pipe, piece, form, width, height, posx, posy);
748 if(area != DT_MASKS_RASTER_OK) return area;
749
751 {
752 dt_print(DT_DEBUG_MASKS, "[masks %s] circle area took %0.04f sec\n", form->name, dt_get_wtime() - start2);
753 start2 = dt_get_wtime();
754 }
755
756 // we get the circle values
757 dt_masks_node_circle_t *const restrict circle = (dt_masks_node_circle_t *)((form->points)->data);
758
759 // we create a buffer of points with all points in the area
760 const int w = *width, h = *height;
761 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h * 2, 0);
762 if(IS_NULL_PTR(points))
764
765 const float pos_x = *posx;
766 const float pos_y = *posy;
767 __OMP_PARALLEL_FOR__(if(h*w > 50000) num_threads(MIN(dt_get_num_openmp_threads(),(h*w)/20000)))
768 for(int i = 0; i < h; i++)
769 {
770 float *const restrict p = points + 2 * i * w;
771 const float y = i + pos_y;
772 __OMP_SIMD__(aligned(points : 64))
773 for(int j = 0; j < w; j++)
774 {
775 p[2*j] = pos_x + j;
776 p[2*j + 1] = y;
777 }
778 }
780 {
781 dt_print(DT_DEBUG_MASKS, "[masks %s] circle draw took %0.04f sec\n", form->name, dt_get_wtime() - start2);
782
783 start2 = dt_get_wtime();
784 }
785 // we back transform all this points
786 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)w * h))
787 {
790 }
791
793 {
794 dt_print(DT_DEBUG_MASKS, "[masks %s] circle transform took %0.04f sec\n", form->name,
795 dt_get_wtime() - start2);
796 start2 = dt_get_wtime();
797 }
798
799 // we allocate the buffer
800 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
801 if(IS_NULL_PTR(*buffer))
802 {
805 }
806
807 // we populate the buffer
808 float *const restrict ptbuffer = *buffer;
809 const int wi = pipe->iwidth, hi = pipe->iheight;
810 const int mindim = MIN(wi, hi);
811 const float centerx = circle->center[0] * wi;
812 const float centery = circle->center[1] * hi;
813 const float radius2 = circle->radius * mindim * circle->radius * mindim;
814 const float total2 = (circle->radius + circle->border) * mindim * (circle->radius + circle->border) * mindim;
815 const float border2 = total2 - radius2;
816 __OMP_PARALLEL_FOR_SIMD__(if(h*w > 50000) num_threads(MIN(dt_get_num_openmp_threads(),(h*w)/20000)) aligned(points, ptbuffer : 64))
817 for(int i = 0 ; i < h*w; i++)
818 {
819 // find the square of the distance from the center
820 const float l2 = sqf(points[2 * i] - centerx) + sqf(points[2 * i + 1] - centery);
821 // quadratic falloff between the circle's radius and the radius of the outside of the feathering
822 const float ratio = (total2 - l2) / border2;
823 // enforce 1.0 inside the circle and 0.0 outside the feathering
824 const float f = CLIP(ratio);
825 ptbuffer[i] = sqf(f);
826 }
827
829
831 dt_print(DT_DEBUG_MASKS, "[masks %s] circle fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
832
833 return DT_MASKS_RASTER_OK;
834}
835
836
838 const dt_dev_pixelpipe_iop_t *const restrict piece,
839 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
840 float *const restrict buffer, dt_iop_roi_t *touched)
841{
842 dt_masks_touched_none(touched);
843 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
844 if(IS_NULL_PTR(module)) return DT_MASKS_RASTER_ERROR;
845 double start1 = 0.0;
846 double start2 = start1;
847
848 if(dt_get_debug_flags() & DT_DEBUG_PERF) start2 = start1 = dt_get_wtime();
849
850 // we get the circle parameters
851 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)((form->points)->data);
852 if(IS_NULL_PTR(circle)) return DT_MASKS_RASTER_EMPTY;
853 const int wi = pipe->iwidth, hi = pipe->iheight;
854 const float centerx = circle->center[0] * wi;
855 const float centery = circle->center[1] * hi;
856 const int min_dimention = MIN(wi, hi);
857 const float total_radius = (circle->radius + circle->border) * min_dimention;
858 const float sqr_radius = circle->radius * min_dimention * circle->radius * min_dimention;
859 const float sqr_total = total_radius * total_radius;
860 const float sqr_border = sqr_total - sqr_radius;
861
862 // we create a buffer of grid points for later interpolation: higher speed and reduced memory footprint;
863 // we match size of buffer to bounding box around the shape
864 const int width = roi->width;
865 const int height = roi->height;
866 const int px = roi->x;
867 const int py = roi->y;
868 const float iscale = 1.0f / roi->scale;
869 // scale dependent resolution: when zoomed in (scale > 1), use finer grid to avoid interpolation holes
870 const float grid_scale = 1.0f / MAX(roi->scale, 1e-6f);
871 const int grid = CLAMP((10.0f * grid_scale + 2.0f) / 3.0f, 1, 4);
872 const int grid_width = (width + grid - 1) / grid + 1; // grid dimension of total roi
873 const int grid_height = (height + grid - 1) / grid + 1; // grid dimension of total roi
874
875 // initialize output buffer with zero
876 memset(buffer, 0, sizeof(float) * width * height);
877
879 {
880 dt_print(DT_DEBUG_MASKS, "[masks %s] circle init took %0.04f sec\n", form->name, dt_get_wtime() - start2);
881 start2 = dt_get_wtime();
882 }
883
884 // we look at the outer circle of the shape - no effects outside of this circle;
885 // we need many points as we do not know how the circle might get distorted in the pixelpipe
886 const size_t circpts = dt_masks_roundup(MIN(360, 2 * M_PI * sqr_total), 8);
887 float *const restrict circ = dt_pixelpipe_cache_alloc_align_float_cache(circpts * 2, 0);
888 if(IS_NULL_PTR(circ)) return DT_MASKS_RASTER_ERROR;
889 __OMP_PARALLEL_FOR__(if(circpts/8 > 1000))
890 for(int n = 0; n < circpts / 8; n++)
891 {
892 const float phi = (2.0f * M_PI * n) / circpts;
893 const float x = total_radius * cosf(phi);
894 const float y = total_radius * sinf(phi);
895 const float cx = centerx;
896 const float cy = centery;
897 const int index_x = 2 * n * 8;
898 const int index_y = 2 * n * 8 + 1;
899 // take advantage of symmetry
900 circ[index_x] = cx + x;
901 circ[index_y] = cy + y;
902 circ[index_x + 2] = cx + x;
903 circ[index_y + 2] = cy - y;
904 circ[index_x + 4] = cx - x;
905 circ[index_y + 4] = cy + y;
906 circ[index_x + 6] = cx - x;
907 circ[index_y + 6] = cy - y;
908 circ[index_x + 8] = cx + y;
909 circ[index_y + 8] = cy + x;
910 circ[index_x + 10] = cx + y;
911 circ[index_y + 10] = cy - x;
912 circ[index_x + 12] = cx - y;
913 circ[index_y + 12] = cy + x;
914 circ[index_x + 14] = cx - y;
915 circ[index_y + 14] = cy - x;
916 }
917
918 // we transform the outer circle from input image coordinates to current point in pixelpipe
919 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, circ,
920 circpts))
921 {
924 }
925
927 {
928 dt_print(DT_DEBUG_MASKS, "[masks %s] circle outline took %0.04f sec\n", form->name, dt_get_wtime() - start2);
929 start2 = dt_get_wtime();
930 }
931
932 // we get the min/max values ...
933 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
934 for(int n = 0; n < circpts; n++)
935 {
936 // just in case that transform throws surprising values
937 if(!(isnormal(circ[2 * n]) && isnormal(circ[2 * n + 1]))) continue;
938
939 xmin = MIN(xmin, circ[2 * n]);
940 xmax = MAX(xmax, circ[2 * n]);
941 ymin = MIN(ymin, circ[2 * n + 1]);
942 ymax = MAX(ymax, circ[2 * n + 1]);
943 }
944
945#if 0
946 printf("xmin %f, xmax %f, ymin %f, ymax %f\n", xmin, xmax, ymin, ymax);
947 printf("wi %d, hi %d, iscale %f\n", wi, hi, iscale);
948 printf("w %d, h %d, px %d, py %d\n", w, h, px, py);
949#endif
950
951 // ... and calculate the bounding box with a bit of reserve
952 const int bbxm = CLAMP((int)floorf(xmin / iscale - px) / grid - 1, 0, grid_width - 1);
953 const int bbXM = CLAMP((int)ceilf(xmax / iscale - px) / grid + 2, 0, grid_width - 1);
954 const int bbym = CLAMP((int)floorf(ymin / iscale - py) / grid - 1, 0, grid_height - 1);
955 const int bbYM = CLAMP((int)ceilf(ymax / iscale - py) / grid + 2, 0, grid_height - 1);
956 const int bbw = bbXM - bbxm + 1;
957 const int bbh = bbYM - bbym + 1;
958
959#if 0
960 printf("bbxm %d, bbXM %d, bbym %d, bbYM %d\n", bbxm, bbXM, bbym, bbYM);
961 printf("gw %d, gh %d, bbw %d, bbh %d\n", gw, gh, bbw, bbh);
962#endif
963
965
967 {
968 dt_print(DT_DEBUG_MASKS, "[masks %s] circle bounding box took %0.04f sec\n", form->name, dt_get_wtime() - start2);
969 start2 = dt_get_wtime();
970 }
971
972 // check if there is anything to do at all;
973 // only if width and height of bounding box is 2 or greater the shape lies inside of roi and requires action
974 if(bbw <= 1 || bbh <= 1)
976
977 const dt_masks_sample_grid_t sample_grid
978 = { .x0 = bbxm, .y0 = bbym, .width = bbw, .height = bbh,
979 .step = grid, .px = px, .py = py, .iscale = iscale };
980 float *const restrict points
981 = dt_masks_sample_grid_backtransform(pipe, module->iop_order, &sample_grid, "circle", form->name);
982 if(IS_NULL_PTR(points)) return DT_MASKS_RASTER_ERROR;
983 start2 = dt_get_wtime();
984
985 // we calculate the mask values at the transformed points;
986 // for results: re-use the points array
987 __OMP_PARALLEL_FOR__(collapse(2) if(bbh*bbw > 50000) num_threads(MIN(dt_get_num_openmp_threads(),(height*width)/20000)))
988 for(int j = 0; j < bbh; j++)
989 for(int i = 0; i < bbw; i++)
990 {
991 const size_t index = (size_t)j * bbw + i;
992 // find the square of the distance from the center
993 const float l2 = sqf(points[2 * index] - centerx) + sqf(points[2 * index + 1] - centery);
994 // quadratic falloff between the circle's radius and the radius of the outside of the feathering
995 const float ratio = (sqr_total - l2) / sqr_border;
996 // enforce 1.0 inside the circle and 0.0 outside the feathering
997 const float f = CLAMP(ratio, 0.0f, 1.0f);
998 points[2*index] = f * f;
999 }
1000
1002 {
1003 dt_print(DT_DEBUG_MASKS, "[masks %s] circle draw took %0.04f sec\n", form->name,
1004 dt_get_wtime() - start2);
1005 start2 = dt_get_wtime();
1006 }
1007
1008 // we fill the pre-initialized output buffer by interpolation;
1009 // we only need to take the contents of our bounding box into account
1010 int endx = 0, endy = 0;
1011 dt_masks_sample_grid_interpolate(points, &sample_grid, buffer, width, height, &endx, &endy);
1012
1014
1015 dt_masks_touched_set(touched, bbxm * grid, bbym * grid, endx - 1, endy - 1, width, height);
1016
1018 {
1019 dt_print(DT_DEBUG_MASKS, "[masks %s] circle fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1020 dt_print(DT_DEBUG_MASKS, "[masks %s] circle total render took %0.04f sec\n", form->name,
1021 dt_get_wtime() - start1);
1022 }
1023
1024 return DT_MASKS_RASTER_OK;
1025}
1026
1028{
1030 {
1031 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/circle/size", 0.001f, 0.5f);
1032 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/circle/border", 0.0005f, 0.5f);
1033 }
1034 else
1035 {
1036 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/circle/size", 0.001f, 0.5f);
1037 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/circle/border", 0.0005f, 0.5f);
1038 }
1039}
1040
1041static void _circle_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1042{
1043 snprintf(form->name, sizeof(form->name), _("circle #%d"), (int)nb);
1044}
1045
1046static void _circle_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1047 char *const restrict msgbuf, const size_t msgbuf_len)
1048{
1049 // Only gestures that cannot be discovered any other way. What the wheel does is the user's
1050 // own mapping now (masks_gui.h), shown in the Drawn tab, so it is not repeated here.
1051 if(gui->creation)
1052 {
1053 if(dt_masks_form_is_clone(form))
1054 g_strlcat(msgbuf, _("<b>Set source</b>: Shift+Click"), msgbuf_len);
1055 }
1056 else if(gui->source_selected)
1057 g_strlcat(msgbuf, _("<b>Move source</b>: Drag"), msgbuf_len);
1058 else if(gui->form_selected || gui->border_selected)
1059 g_strlcat(msgbuf, _("<b>Move</b>: Drag"), msgbuf_len);
1060}
1061
1063{
1064 // unused arg, keep compiler from complaining
1066}
1067
1068static void _circle_initial_source_pos(dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
1069{
1070 const float radius = MIN(0.5f, dt_conf_get_float("plugins/darkroom/spots/circle/size"));
1071 float offset[2] = { radius, -radius };
1073 *x = offset[0];
1074 *y = offset[1];
1075}
1076
1077// The function table for circles. This must be public, i.e. no "static" keyword.
1079 .point_struct_size = sizeof(struct dt_masks_node_circle_t),
1080 .sanitize_config = _circle_sanitize_config,
1081 .set_form_name = _circle_set_form_name,
1082 .set_hint_message = _circle_set_hint_message,
1083 .duplicate_points = _circle_duplicate_points,
1084 .initial_source_pos = _circle_initial_source_pos,
1085 .get_distance = _circle_get_distance,
1086 .get_points = _circle_get_points,
1087 .get_points_border = _circle_get_points_border,
1088 .get_mask = _circle_get_mask,
1089 .get_mask_roi = _circle_get_mask_roi,
1090 .get_area = _circle_get_area,
1091 .get_source_area = _circle_get_source_area,
1092 .get_gravity_center = _circle_get_gravity_center,
1093 .get_interaction_value = _circle_get_interaction_value,
1094 .set_interaction_value = _circle_set_interaction_value,
1095 .update_hover = _find_closest_handle,
1096 .mouse_moved = _circle_events_mouse_moved,
1097 .mouse_scrolled = _circle_events_mouse_scrolled,
1098 .button_pressed = _circle_events_button_pressed,
1099 .button_released = _circle_events_button_released,
1100 .key_pressed = _circle_events_key_pressed,
1101 .post_expose = _circle_events_post_expose,
1102 .draw_shape = _circle_draw_shape
1103};
1104
1105
1106
1107// clang-format off
1108// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1109// vim: shiftwidth=2 expandtab tabstop=2 cindent
1110// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1111// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
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:231
static void _circle_sanitize_config(dt_masks_type_t type)
Definition circle.c:1027
static void _circle_initial_source_pos(dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
Definition circle.c:1068
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:301
static void _circle_duplicate_points(dt_develop_t *dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition circle.c:1062
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
Definition circle.c:118
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:133
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:427
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:567
static void _circle_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form, char *const restrict msgbuf, const size_t msgbuf_len)
Definition circle.c:1046
static float _circle_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition circle.c:198
static dt_masks_raster_result_t _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:698
static gboolean _circle_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
Definition circle.c:215
#define FADING_MIN
Definition circle.c:50
static float * _points_to_transform(dt_develop_t *dev, float x, float y, float radius, float wd, float ht, int *points_count)
Definition circle.c:492
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:541
static int _change_fading(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:254
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:191
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_handle, int *inside_source, float *dist, void *user_data)
Circle-specific inside/border hit testing adapter.
Definition circle.c:110
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:271
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_handle, int *inside_source, float *dist)
Definition circle.c:56
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:398
static void _circle_get_creation_values(const dt_masks_form_t *form, float *radius, float *border)
Definition circle.c:124
static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:183
#define FADING_MAX
Definition circle.c:51
static void _circle_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition circle.c:1041
const dt_masks_functions_t dt_masks_functions_circle
Definition circle.c:1078
static dt_masks_raster_result_t _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, dt_iop_roi_t *touched)
Definition circle.c:837
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:149
static dt_masks_raster_result_t _circle_get_points_border(dt_develop_t *dev, struct dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int source, const dt_iop_module_t *module)
Definition circle.c:622
static int _init_fading(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition circle.c:176
static void _circle_draw_shape(dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int coord_nb, const gboolean border, const gboolean source, const dt_masks_skip_range_t *skips, const int skip_count)
Definition circle.c:468
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:523
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:421
static dt_masks_raster_result_t _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:661
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:343
static dt_masks_raster_result_t _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:733
static const float x
const float l2
const float f
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
float dt_conf_get_and_sanitize_float(const char *name, float min, float max)
int dt_get_num_openmp_threads(void)
Number of OpenMP threads the application decided to use.
Definition darktable.c:518
#define M_PI_F
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1765
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1255
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1797
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:1826
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
GtkWidget * geometry
its size, under the preview
GtkWidget * preview
what the selected row actually captures
GHashTable * selected
set of checked row labels, mirrored to conf on every change
static double dt_draw_min_emit_step(cairo_t *cr)
Stroke a line with style.
Definition draw.h:931
@ DT_MASKS_DASH_STICK
Definition draw.h:135
@ DT_MASKS_NO_DASH
Definition draw.h:134
static void dt_draw_shape_lines(struct dt_develop_t *dev, 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, const struct dt_masks_skip_range_t *skips, const int skip_count)
Draw the lines of a mask shape.
Definition draw.h:831
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
void dt_masks_points_bounding_box(const float *const points, const int num_points, int *width, int *height, int *posx, int *posy)
Definition masks.c:322
void dt_masks_sample_grid_interpolate(const float *const points, const dt_masks_sample_grid_t *const grid, float *const buffer, const int buf_width, const int buf_height, int *const endx, int *const endy)
Definition masks.c:393
float * dt_masks_sample_grid_backtransform(struct dt_dev_pixelpipe_t *pipe, const double iop_order, const dt_masks_sample_grid_t *const grid, const char *const shape, const char *const form_name)
Definition masks.c:345
static dt_masks_raster_result_t dt_masks_raster_from_status(const int status)
Definition masks.h:322
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_EMPTY
Definition masks.h:315
@ DT_MASKS_RASTER_OK
Definition masks.h:314
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.
Definition masks.c:1332
int dt_masks_form_change_opacity(dt_develop_t *dev, dt_masks_form_t *form, int parentid, int up, const int flow)
Definition masks_gui.c:5840
static gboolean dt_masks_form_is_clone(const dt_masks_form_t *form)
Definition masks.h:356
static int dt_masks_roundup(int num, int mult)
Definition masks.h:763
static gboolean dt_masks_form_uses_spot_defaults(const dt_masks_form_t *form)
Definition masks.h:351
static void dt_masks_reset_source(dt_masks_form_t *form)
Definition masks.h:361
The per-shape function table, private to the masks implementation.
int dt_masks_point_in_form_exact(const float *pts, int num_pts, const float *points, int points_start, int points_count, const dt_masks_skip_range_t *skips, int skip_count)
Ray-cast point-in-polygon over a form point stream, honouring skip ranges.
Definition masks_gui.c:5403
int dt_masks_points_shift_to_source(struct dt_develop_t *dev, const struct dt_iop_module_t *module, float **points, int *points_count, const float xs, const float ys, const int first_shifted)
int dt_masks_preview_add_clone_source(struct dt_masks_form_gui_t *gui, struct dt_masks_preview_buffers_t *preview)
Definition masks_gui.c:6083
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index, const int node_count, 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.
Definition masks_gui.c:3368
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
Definition masks_gui.c:5246
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *mask_gui, dt_masks_form_t *mask_form)
Initialize the clone source position based on current GUI state.
Definition masks_gui.c:5570
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)
Centralized hit-testing for node/handle/segment selection across shapes.
Definition masks_gui.c:1183
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *mask_gui, const uint32_t key_state)
Decide initial source positioning mode for clone masks.
Definition masks_gui.c:5545
void dt_masks_gui_form_create(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module)
Definition masks_gui.c:1914
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount, float value_min, float value_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.
Definition masks_gui.c:5305
void dt_masks_gui_form_save_creation(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui)
Save the form creation right after a shape has been finished drawing.
Definition masks_gui.c:2525
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
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_gui.h:719
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_gui.h:304
static void dt_masks_draw_preview_shape(struct dt_develop_t *dev, cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, const shape_draw_function_t *draw_shape, const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks_gui.h:410
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks_gui.h:443
static gboolean dt_masks_gui_points_reach(const dt_masks_form_gui_points_t *gp, const float x, const float y, const float reach)
Definition masks_gui.h:83
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_gui.h:312
static void dt_masks_touched_set(dt_iop_roi_t *touched, int x0, int y0, int x1, int y1, const int width, const int height)
static void dt_masks_touched_none(dt_iop_roi_t *touched)
@ DT_MASKS_EDIT_FULL
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_CLONE
Definition masks_types.h:67
dt_masks_interaction_t
@ DT_MASKS_INTERACTION_OPACITY
@ DT_MASKS_INTERACTION_SIZE
@ DT_MASKS_INTERACTION_FADING
dt_masks_increment_t
@ DT_MASKS_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define CLIP(x)
Definition math.h:83
#define M_PI
Definition math.h:47
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
float iscale
Definition mipmap_cache.c:2
static float gh(const float f)
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
const float r
Objective facts about the image a dev is working on.
struct dt_develop_t * dev
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
gboolean source_selected
Definition masks_gui.h:153
gboolean source_dragging
Definition masks_gui.h:161
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
gboolean form_dragging
Definition masks_gui.h:160
gboolean form_selected
Definition masks_gui.h:151
gboolean border_selected
Definition masks_gui.h:152
struct dt_develop_t * dev
Definition masks_gui.h:101
const dt_masks_functions_t * functions
Definition masks.h:255
dt_masks_type_t type
Definition masks.h:254
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
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)
shape_draw_function_t draw_shape
struct dt_masks_gui_center_point_t::@27 main
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)