Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
ellipse.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013, 2018-2022 Pascal Obry.
4 Copyright (C) 2013-2017 Tobias Ellinghaus.
5 Copyright (C) 2013-2014, 2016, 2019 Ulrich Pegelow.
6 Copyright (C) 2014, 2016, 2021 Aldric Renaudin.
7 Copyright (C) 2014-2017 Roman Lebedev.
8 Copyright (C) 2017-2019 Edgardo Hoszowski.
9 Copyright (C) 2018 johannes hanika.
10 Copyright (C) 2019 Andreas Schneider.
11 Copyright (C) 2019, 2023, 2025-2026 Aurélien PIERRE.
12 Copyright (C) 2020-2022 Chris Elston.
13 Copyright (C) 2020 GrahamByrnes.
14 Copyright (C) 2020 Heiko Bauke.
15 Copyright (C) 2020-2021 Hubert Kowalski.
16 Copyright (C) 2020-2021 Ralf Brown.
17 Copyright (C) 2021 Diederik Ter Rahe.
18 Copyright (C) 2022 luzpaz.
19 Copyright (C) 2022 Martin Bařinka.
20 Copyright (C) 2023 Luca Zulberti.
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*/
37#include "math/math.h"
38#include "system/macros.h"
39#include "system/openmp.h"
40#include "common/logging.h"
41#include "common/times.h"
43#include "common/conf.h"
44#include "develop/imageop.h"
45#include "develop/masks.h"
46#include "develop/masks_gui.h"
49#include "math/openmp_maths.h"
51
52
53#define FADING_MIN 0.0005f
54#define FADING_MAX 1.0f
55
56#define BORDER_MIN 0.00005f
57#define BORDER_MAX 0.5f
58
59#define RADIUS_CLONE_MIN 0.00005f
60#define RADIUS_CLONE_MAX 0.5f
61
62#define RADIUS_NON_CLONE_MIN 0.0005f
63#define RADIUS_NON_CLONE_MAX 1.0f
64
65static inline void _ellipse_point_transform(const float xref, const float yref, const float x, const float y,
66 const float sinr, const float cosr, float *xnew, float *ynew)
67{
68 const float xtmp = (sinr * sinr + cosr * cosr) * (x - xref) + (cosr * sinr - cosr * sinr) * (y - yref);
69 const float ytmp = (cosr * sinr - cosr * sinr) * (x - xref) + (sinr * sinr + cosr * cosr) * (y - yref);
70
71 *xnew = xref + xtmp;
72 *ynew = yref + ytmp;
73}
74
75/*
76// Jordan's point in polygon test
77static int _ellipse_cross_test(float x, float y, float *point_1, float *point_2)
78{
79 float x_b = point_1[0];
80 float y_b = point_1[1];
81 float x_c = point_2[0];
82 float y_c = point_2[1];
83
84 // Early exit : bounding box test
85 float min_y = fminf(y_b, y_c);
86 float max_y = fmaxf(y_b, y_c);
87 if (y <= min_y || y > max_y) return 1;
88
89 const float x_a = x;
90 const float y_a = y;
91
92 // special case : horizontal line
93 if(y_a == y_b && y_b == y_c)
94 {
95 if((x_b <= x_a && x_a <= x_c) || (x_c <= x_a && x_a <= x_b))
96 return 0;
97 else
98 return 1;
99 }
100
101 // order points b and c by y
102 if(y_b > y_c)
103 {
104 float tmp;
105 tmp = x_b, x_b = x_c, x_c = tmp;
106 tmp = y_b, y_b = y_c, y_c = tmp;
107 }
108
109 if(y_a == y_b && x_a == x_b) return 0;
110
111 if(y_a <= y_b || y_a > y_c) return 1;
112
113 const float delta = (x_b - x_a) * (y_c - y_a) - (y_b - y_a) * (x_c - x_a);
114
115 if(delta > 0)
116 return -1;
117 else if(delta < 0)
118 return 1;
119 else
120 return 0;
121}
122
123static int _ellipse_point_in_polygon(float x, float y, float *points, int points_count)
124{
125 int t = -1;
126
127 t *= _ellipse_cross_test(x, y, points + 2 * (points_count - 1), points);
128
129 for(int i = 0; i < points_count - 2; i++)
130 t *= _ellipse_cross_test(x, y, points + 2 * i, points + 2 * (i + 1));
131
132 return t;
133}
134*/
135
136// check if point is close to path - segment by segment
137static int _ellipse_point_close_to_path(float x, float y, float mouse_radius, float *points, int points_count)
138{
139 float radius2 = mouse_radius * mouse_radius;
140
141 const float lastx = points[2 * (points_count - 1)];
142 const float lasty = points[2 * (points_count - 1) + 1];
143
144 for(int i = 0; i < points_count; i++)
145 {
146 const float px = points[2 * i];
147 const float py = points[2 * i + 1];
148
149 const float r1 = x - lastx;
150 const float r2 = y - lasty;
151 const float r3 = px - lastx;
152 const float r4 = py - lasty;
153
154 const float d = r1 * r3 + r2 * r4;
155 const float l = sqf(r3) + sqf(r4);
156 const float p = d / l;
157
158 float xx = 0.0f, yy = 0.0f;
159
160 if(p < 0 || (px == lastx && py == lasty))
161 {
162 xx = lastx;
163 yy = lasty;
164 }
165 else if(p > 1)
166 {
167 xx = px;
168 yy = py;
169 }
170 else
171 {
172 xx = lastx + p * r3;
173 yy = lasty + p * r4;
174 }
175
176 const float dx = x - xx;
177 const float dy = y - yy;
178
179 if(sqf(dx) + sqf(dy) < radius2) return 1;
180 }
181 return 0;
182}
183
184static void _ellipse_get_distance(float x, float y, float mouse_radius, dt_masks_form_gui_t *gui, int index,
185 int num_points, int *inside, int *inside_border, int *near_handle,
186 int *inside_source, float *dist)
187{
188 // initialise returned values
189 *inside_source = 0;
190 *inside = 0;
191 *inside_border = 0;
192 *near_handle = -1;
193 *dist = FLT_MAX;
194
195 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
196 if(IS_NULL_PTR(gpt)) return;
197 /* Nothing to walk when the cursor cannot reach a sample: the box every sample spans, grown by
198 * the cursor's reach, answers for the whole shape in four comparisons, and every answer
199 * initialised above is what the walk would have given -- nothing inside, nothing near. */
200 if(!dt_masks_gui_points_reach(gpt, x, y, 2.0f * mouse_radius)) return;
201
202 const float pt[2] = { x, y };
203
204 // we first check if we are inside the source form
205 if(gpt->source && gpt->source_count > 10)
206 {
207 if(dt_masks_point_in_form_exact(pt, 1, gpt->source, 10, gpt->source_count - 5, NULL, 0) >= 0)
208 {
209 *inside_source = 1;
210 *inside = 1;
211
212 // get the minial dist for center & control points
213 float min_dist_norm = FLT_MAX;
214 for(int k=0; k<5; k++)
215 {
216 const float center_dx = x - gpt->source[k * 2];
217 const float center_dy = y - gpt->source[k * 2 + 1];
218 const float dist2 = sqf(center_dx) + sqf(center_dy);
219 min_dist_norm = fminf(min_dist_norm, dist2);
220 }
221 *dist = min_dist_norm;
222 return;
223 }
224 }
225
226 if(IS_NULL_PTR(gpt->points) || gpt->points_count <= 5 || !gpt->border || gpt->border_count <= 5) return;
227
228 // distance from center
229 const float center_dx = x - gpt->points[0];
230 const float center_dy = y - gpt->points[1];
231 *dist = sqf(center_dx) + sqf(center_dy);
232
233 const gboolean close_to_border = _ellipse_point_close_to_path(x, y, mouse_radius * 1.5, gpt->border + 10, gpt->border_count - 5);
234 const gboolean in_border = dt_masks_point_in_form_exact(pt, 1, gpt->border + 10, 10, gpt->border_count - 5, NULL, 0) >= 0;
235 // we check if it's inside borders
236 if(!close_to_border && !in_border) return;
237 *inside = 1;
238
239 // and we check if it's inside form
240 const int in_form = _ellipse_point_close_to_path(x, y, mouse_radius, gpt->points + 10, gpt->points_count - 5);
241 *inside_border = !in_form;
242}
243
252
254{
255 const gboolean use_spot_defaults = dt_masks_form_uses_spot_defaults(form);
256 values->border = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/border"
257 : "plugins/darkroom/masks/ellipse/border");
258 values->flags = dt_conf_get_int(use_spot_defaults ? "plugins/darkroom/spots/ellipse/flags"
259 : "plugins/darkroom/masks/ellipse/flags");
260 values->radius_a = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/radius_a"
261 : "plugins/darkroom/masks/ellipse/radius_a");
262 values->radius_b = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/radius_b"
263 : "plugins/darkroom/masks/ellipse/radius_b");
264 values->rotation = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/rotation"
265 : "plugins/darkroom/masks/ellipse/rotation");
266}
267
269{
271 dt_masks_gui_cursor_to_raw_norm(gui->dev, gui, ellipse->center);
272 _ellipse_get_creation_values(form, &values);
273
274 ellipse->radius[0] = values.radius_a;
275 ellipse->radius[1] = values.radius_b;
276 ellipse->border = values.border;
277 ellipse->rotation = values.rotation;
278 ellipse->flags = values.flags;
279
280 if(dt_masks_form_is_clone(form))
282 else
284}
285
286static int _ellipse_get_points(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b,
287 float rotation, float **points, int *points_count);
288
289// Mirror the circle creation preview flow: gather defaults, build temp geometry once,
290// and let the expose path only draw the returned buffers.
293{
295 _ellipse_get_creation_values(form, &values);
296
297 const float border_a = (values.flags & DT_MASKS_ELLIPSE_PROPORTIONAL)
298 ? values.radius_a * (1.0f + values.border)
299 : values.radius_a + values.border;
300 const float border_b = (values.flags & DT_MASKS_ELLIPSE_PROPORTIONAL)
301 ? values.radius_b * (1.0f + values.border)
302 : values.radius_b + values.border;
303
304 float center[2];
305 dt_masks_gui_cursor_to_raw_norm(gui->dev, gui, center);
306
308 int err = _ellipse_get_points(gui->dev, center[0], center[1], values.radius_a, values.radius_b,
309 values.rotation, &preview->points, &preview->points_count);
310 if(!err && values.border > 0.0f)
311 err = _ellipse_get_points(gui->dev, center[0], center[1], border_a, border_b, values.rotation,
312 &preview->border, &preview->border_count);
313
314 if(!err && dt_masks_form_is_clone(form))
316
317 if(err)
319
320 return err;
321}
322
323static float *_points_to_transform(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b,
324 float rotation, float wd, float ht, int *points_count)
325{
326 // Calculate the rotation angle in radians
327 const float v = (rotation / 180.0f) * M_PI;
328
329 // Calculate the radii in pixels
330 const float a = radius_a * MIN(wd, ht);
331 const float b = radius_b * MIN(wd, ht);
332
333 const float sinv = sinf(v);
334 const float cosv = cosf(v);
335
336 // Number of points for the ellipse (take every nth point, interpolation for the GUI)
337 const int n = 10;
338 const float lambda = (a - b) / (a + b);
339 const int l = MAX(
340 100, (int)((M_PI * (a + b)
341 * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda)))) / n));
342
343 // buffer allocations
344 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (l + 5), 0);
345 if(IS_NULL_PTR(points))
346 {
347 *points_count = 0;
348 return 0;
349 }
350 *points_count = l + 5;
351
352 // Center of the ellipse
353 float center[2] = { xx, yy };
355 const float x = points[0] = center[0];
356 const float y = points[1] = center[1];
357
358 // Control points (main axes)
359 points[2] = x + a * cosv;
360 points[3] = y + a * sinv;
361 points[4] = x - a * cosv;
362 points[5] = y - a * sinv;
363
364 points[6] = x - b * sinv;
365 points[7] = y + b * cosv;
366 points[8] = x + b * sinv;
367 points[9] = y - b * cosv;
368 __OMP_PARALLEL_FOR_SIMD__(if(l > 100) aligned(points:64))
369 for(int i = 5; i < l + 5; i++)
370 {
371 const float alpha = (i - 5) * 2.0 * M_PI / (float)l;
372 points[i * 2] = x + a * cosf(alpha) * cosv - b * sinf(alpha) * sinv;
373 points[i * 2 + 1] = y + a * cosf(alpha) * sinv + b * sinf(alpha) * cosv;
374 }
375
376 return points;
377}
378
379static int _ellipse_get_points_source(dt_develop_t *dev, float xx, float yy, float xs, float ys, float radius_a,
380 float radius_b, float rotation, float **points, int *points_count,
381 const dt_iop_module_t *module)
382{
384 const float wd = geometry.raw_width;
385 const float ht = geometry.raw_height;
386
387 // compute the points of the target (center and circumference of ellipse)
388 // we get the point in RAW image reference
389 *points = _points_to_transform(dev, xx, yy, radius_a, radius_b, rotation, wd, ht, points_count);
390 if(IS_NULL_PTR(*points)) return 1;
391
392 return dt_masks_points_shift_to_source(dev, module, points, points_count, xs, ys, 5);
393}
394
395static int _ellipse_get_points(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b,
396 float rotation, float **points, int *points_count)
397{
399 const float wd = geometry.raw_width;
400 const float ht = geometry.raw_height;
401
402 *points = _points_to_transform(dev, xx, yy, radius_a, radius_b, rotation, wd, ht, points_count);
403 if(IS_NULL_PTR(*points)) return 1;
404
405 // and we transform them with all distorted modules
406 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
407 {
409 *points = NULL;
410 *points_count = 0;
411 return 1;
412 }
413
414 return 0;
415}
416
418 float **points,
419 int *points_count, float **border, int *border_count,
420 dt_masks_skip_range_t **border_skips, int *border_skip_count,
421 int source,
422 const dt_iop_module_t *module)
423{
424 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
425 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
426
427 // No geometry: an empty outline is the correct result here, not a failure. See the circle.
428 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
429 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
430 if(IS_NULL_PTR(ellipse)) return DT_MASKS_RASTER_EMPTY;
431 float x = 0.0f, y = 0.0f, a = 0.0f, b = 0.0f;
432 x = ellipse->center[0], y = ellipse->center[1];
433 a = ellipse->radius[0], b = ellipse->radius[1];
434
435 if(source)
436 {
437 float xs = form->source[0], ys = form->source[1];
439 _ellipse_get_points_source(dev, x, y, xs, ys, a, b, ellipse->rotation, points, points_count, module));
440 }
441 else
442 {
443 if(_ellipse_get_points(dev, x, y, a, b, ellipse->rotation, points, points_count) != 0)
445 if(!IS_NULL_PTR(border))
446 {
447 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
449 _ellipse_get_points(dev, x, y, (prop ? a * (1.0f + ellipse->border) : a + ellipse->border),
450 (prop ? b * (1.0f + ellipse->border) : b + ellipse->border), ellipse->rotation,
451 border, border_count));
452 }
453 return DT_MASKS_RASTER_OK;
454 }
455}
456
463static void _ellipse_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
464 float *node_x, float *node_y, void *user_data)
465{
466 const float *nodes = gui_points->points;
467 const float rotation = atan2f(nodes[3] - nodes[1], nodes[2] - nodes[0]);
468 const float sinr = sinf(rotation);
469 const float cosr = cosf(rotation);
470 _ellipse_point_transform(nodes[0], nodes[1], nodes[node_index * 2], nodes[node_index * 2 + 1],
471 sinr, cosr, node_x, node_y);
472}
473
477static void _ellipse_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
478 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
479 int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
480{
481 _ellipse_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
482 inside_border, near_handle, inside_source, dist);
483}
484
490static void _ellipse_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border,
491 int inside_source, void *user_data)
492{
493 mask_gui->pivot_selected = inside_border ? TRUE : FALSE; // cast to strict gboolean
494}
495
496static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
497{
498 if(mask_gui) mask_gui->pivot_selected = FALSE;
499 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, 5,
500 NULL, NULL, _ellipse_node_position_cb,
502}
503
504static int _init_fading(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
505{
507 increment, flow, _("Fading: %3.2f%%"), 100.0f);
508 return 1;
509}
510
511static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
512{
513 float mask_radius_a = dt_masks_get_set_conf_value(form, "radius_a", amount, FADING_MIN, FADING_MAX, increment, flow);
514 float mask_radius_b = dt_masks_get_set_conf_value(form, "radius_b", amount, FADING_MIN, FADING_MAX, increment, flow);
515 dt_toast_log(_("Size: %3.2f%%"), fmaxf(mask_radius_a, mask_radius_b) * 2.f * 100.f);
516 return 1;
517}
518
519static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
520{
521 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
522 increment, flow, _("Opacity: %3.2f%%"), 100.f);
523 return 1;
524}
525
526static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
527{
528 dt_masks_get_set_conf_value_with_toast(form, "rotation", amount, 0.f, 360.f,
529 increment, flow, _("Rotation: %3.2f\302\260"), 1.0f);
530 return 1;
531}
532
534{
535 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
536 const dt_masks_node_ellipse_t *ellipse = (const dt_masks_node_ellipse_t *)(form->points)->data;
537 if(IS_NULL_PTR(ellipse)) return NAN;
538
539 switch(interaction)
540 {
542 return fmaxf(ellipse->radius[0], ellipse->radius[1]);
544 return ellipse->border;
546 return ellipse->rotation;
547 default:
548 return NAN;
549 }
550}
551
552static gboolean _ellipse_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
553{
554 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
555 const dt_masks_node_ellipse_t *ellipse = (const dt_masks_node_ellipse_t *)(form->points)->data;
556 if(IS_NULL_PTR(ellipse)) return FALSE;
557 center[0] = ellipse->center[0];
558 center[1] = ellipse->center[1];
559 *area = M_PI_F * ellipse->radius[0] * ellipse->radius[1];
560 return TRUE;
561}
562
563static int _change_fading(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
564 int index, const float amount, const dt_masks_increment_t increment, const int flow);
565static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
566 int index, const float amount, const dt_masks_increment_t increment, const int flow);
567static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
568 int index, const float amount, const dt_masks_increment_t increment, const int flow);
569
571 dt_masks_increment_t increment, int flow,
572 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
573{
574 if(IS_NULL_PTR(form)) return NAN;
575 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
576 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
577 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
578 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
579
580 switch(interaction)
581 {
583 if(!_change_size(form, gui, module, index, value, increment, flow)) return NAN;
584 return _ellipse_get_interaction_value(form, interaction);
586 if(!_change_fading(form, gui, module, index, value, increment, flow)) return NAN;
587 return _ellipse_get_interaction_value(form, interaction);
589 if(!_change_rotation(form, gui, module, index, value, increment, flow)) return NAN;
590 return _ellipse_get_interaction_value(form, interaction);
591 default:
592 return NAN;
593 }
594}
595
596static 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)
597{
598 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
599 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
600 if(IS_NULL_PTR(ellipse)) return 0;
601
602 ellipse->border = CLAMPF(dt_masks_apply_increment(ellipse->border, amount, increment, flow),
604
605 _init_fading(form, amount, increment, flow);
606
607 // we recreate the form points
608 dt_masks_gui_form_create(form, gui, index, module);
609
610 return 1;
611}
612
613static 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)
614{
615 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
616 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
617 if(IS_NULL_PTR(ellipse)) return 0;
618
619 // Sanitize
620 // do not exceed upper limit of 1.0 and lower limit of 0.004
621 if(amount > 1.0f && (ellipse->border > 1.0f ))
622 return 1;
623
624 // adjust the sizes directly to avoid re-querying group/form
625 ellipse->radius[0] = dt_masks_apply_increment(ellipse->radius[0], amount, increment, flow);
626 ellipse->radius[1] = dt_masks_apply_increment(ellipse->radius[1], amount, increment, flow);
627
628 _init_size(form, amount, DT_MASKS_INCREMENT_SCALE, flow);
629
630 // we recreate the form points
631 dt_masks_gui_form_create(form, gui, index, module);
632
633 return 1;
634}
635
636static int _change_rotation(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)
637{
638 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
639 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
640 if(IS_NULL_PTR(ellipse)) return 0;
641
642 // Rotation
643 int flow_increased = (flow > 1) ? (flow - 1) * 5 : flow;
644 ellipse->rotation = dt_masks_apply_increment(ellipse->rotation, amount, increment, flow_increased);
645
646 // Ensure the rotation value warps within the interval [0, 360)
647 if(ellipse->rotation > 360.f) ellipse->rotation = fmodf(ellipse->rotation, 360.f);
648 else if(ellipse->rotation < 0.f) ellipse->rotation = 360.f - fmodf(-ellipse->rotation, 360.f);
649
650 _init_rotation(form, amount, DT_MASKS_INCREMENT_OFFSET, flow);
651
652 // we recreate the form points
653 dt_masks_gui_form_create(form, gui, index, module);
654
655 return 1;
656}
657
658/* Shape handlers receive widget-space coordinates, while normalized output-image
659 * coordinates come from `gui->rel_pos` and absolute output-image
660 * coordinates come from `gui->pos`. */
661static int _ellipse_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
662 uint32_t state, dt_masks_form_t *form, int parentid,
663 dt_masks_form_gui_t *gui, int index,
664 dt_masks_interaction_t interaction)
665{
666 // `state` is the caller's raw key state, kept for the callback signature: the property to
667 // act on was already resolved from it by dt_masks_scroll_get_interaction().
668 if(gui->creation)
669 {
670 // Adjusting the creation values also previews them under the cursor.
671 switch(interaction)
672 {
674 return _init_rotation(form, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
676 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
678 return _init_fading(form, (up ? 1.03f : 0.97f), DT_MASKS_INCREMENT_SCALE, flow);
680 return _init_size(form, (up ? 1.03f : 0.97f), DT_MASKS_INCREMENT_SCALE, flow);
681 default:
682 return 0;
683 }
684 }
685 else if(gui->form_selected)
686 {
687 switch(interaction)
688 {
690 return _change_rotation(form, gui, module, index, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
692 return dt_masks_form_change_opacity(gui->dev, form, parentid, up, flow);
694 return _change_fading(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
696 return _change_size(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
697 default:
698 return 0;
699 }
700 }
701
702 return 0;
703}
704
705static int _ellipse_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
706 double pressure, int which, int type, uint32_t state,
707 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui,
708 int index)
709{
710 if(gui->creation && which == 1
711 && ((dt_modifier_is(state, DT_PRIMARY_MASK | GDK_SHIFT_MASK)) || dt_modifier_is(state, GDK_SHIFT_MASK)))
712 {
713 // set some absolute or relative position for the source of the clone mask
715 return 1;
716 }
717
718 else if(which == 1)
719 {
720 if(gui->creation)
721 {
722 dt_iop_module_t *crea_module = gui->creation_module;
723 // we create the ellipse
725 if(IS_NULL_PTR(ellipse)) return 0;
726 _ellipse_init_new(form, gui, ellipse);
727 form->points = g_list_append(form->points, ellipse);
728 dt_masks_gui_form_save_creation(gui->dev, crea_module, form, gui);
729
730 return 1;
731 }
732
733 else // creation is false
734 {
735 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
736 if(IS_NULL_PTR(gpt)) return 0;
737
739 {
740 // we start the source dragging
741 gui->delta[0] = gpt->source[0] - gui->pos[0];
742 gui->delta[1] = gpt->source[1] - gui->pos[1];
743 return 1;
744 }
745 else if(gui->node_hovered >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
746 {
747 // we start the point dragging
748 gui->delta[0] = gpt->points[0] - gui->pos[0];
749 gui->delta[1] = gpt->points[1] - gui->pos[1];
750 return 1;
751 }
752 else if(gui->form_selected && gui->edit_mode == DT_MASKS_EDIT_FULL)
753 {
754 // we start the form dragging or rotating
755 if(gui->border_selected)
756 gui->form_rotating = TRUE;
757 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
758 gui->border_toggling = TRUE;
759 else
760 ;
761
762 // Pour la rotation: stocker position absolue du clic initial
763 // Pour le drag: stocker décalage
764 if(gui->form_rotating)
765 {
766 gui->delta[0] = gui->pos[0];
767 gui->delta[1] = gui->pos[1];
768 }
769 else
770 {
771 gui->delta[0] = gpt->points[0] - gui->pos[0];
772 gui->delta[1] = gpt->points[1] - gui->pos[1];
773 }
774 return 1;
775 }
776 }
777 }
778
779 return 0;
780}
781
782static int _ellipse_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
783 uint32_t state, dt_masks_form_t *form, int parentid,
784 dt_masks_form_gui_t *gui, int index)
785{
786
787
788
789
790
791
792 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
793
794 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
795 {
796 // we end the form dragging
797 return 1;
798 }
799 else if(gui->border_toggling && gui->edit_mode == DT_MASKS_EDIT_FULL)
800 {
801 // we get the ellipse
802 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
803 if(IS_NULL_PTR(ellipse)) return 0;
804
805 // we end the border toggling
806 gui->border_toggling = FALSE;
807
808 // toggle feathering type of border and adjust border radius accordingly
810 {
811 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
812 ellipse->border = ellipse->border * min_radius;
813 ellipse->border = CLAMP(ellipse->border, 0.001f, 1.0f);
814
815 ellipse->flags &= ~DT_MASKS_ELLIPSE_PROPORTIONAL;
816 }
817 else
818 {
819 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
820 ellipse->border = ellipse->border/min_radius;
821 ellipse->border = CLAMP(ellipse->border, 0.001f/min_radius, 1.0f/min_radius);
822
824 }
825
827 {
828 dt_conf_set_int("plugins/darkroom/spots/ellipse/flags", ellipse->flags);
829 dt_conf_set_float("plugins/darkroom/spots/ellipse/border", ellipse->border);
830 }
831 else
832 {
833 dt_conf_set_int("plugins/darkroom/masks/ellipse/flags", ellipse->flags);
834 dt_conf_set_float("plugins/darkroom/masks/ellipse/border", ellipse->border);
835 }
836
837 // we recreate the form points
838 dt_masks_gui_form_create(form, gui, index, module);
839
840 // we save the new parameters
841
842 return 1;
843 }
844 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
845 {
846 // we end the form rotating
847 gui->form_rotating = FALSE;
848 return 1;
849 }
850 else if(gui->node_dragging >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
851 {
852 // we end the node dragging
853 return 1;
854 }
855 else if(gui->source_dragging)
856 {
857 return 1;
858 }
859 return 0;
860}
861
862static int _ellipse_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
863 int parentid, dt_masks_form_gui_t *gui, int index)
864{
865 return 0;
866}
867
868static int _ellipse_events_mouse_moved(struct dt_iop_module_t *module, double x, double y,
869 double pressure, int which, dt_masks_form_t *form, int parentid,
870 dt_masks_form_gui_t *gui, int index)
871{
872 if(gui->creation)
873 {
874 // Let the cursor motion be redrawn as it moves in GUI
875 return 1;
876 }
877
878 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
879
880 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
881 if(IS_NULL_PTR(ellipse)) return 0;
882
883 // we need the reference points
884 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
885 if(IS_NULL_PTR(gpt)) return 0;
886
887 if(gui->form_dragging || gui->source_dragging)
888 {
889 dt_develop_t *dev = gui->dev;
890 // apply delta to the current mouse position
891 float pts[2];
892 dt_masks_gui_delta_to_raw_norm(dev, gui, pts);
893
894 if(gui->form_dragging)
895 {
896 ellipse->center[0] = pts[0];
897 ellipse->center[1] = pts[1];
898 }
899 else if(gui->source_dragging)
900 {
901 form->source[0] = pts[0];
902 form->source[1] = pts[1];
903 }
904
905 // we recreate the form points
906 dt_masks_gui_form_create(form, gui, index, module);
907
908 return 1;
909 }
910
911 else if(gui->node_dragging >= 1)
912 {
913 const int k = gui->node_dragging;
914
915 const float xref = gpt->points[0];
916 const float yref = gpt->points[1];
917 const float rx = gpt->points[k * 2] - xref;
918 const float ry = gpt->points[k * 2 + 1] - yref;
919 const float deltax = gui->pos[0] + gui->delta[0] - xref;
920 const float deltay = gui->pos[1] + gui->delta[1] - yref;
921
922 // we remap dx, dy to the right values, as it will be used in next movements
923 gui->delta[0] = xref - gui->pos[0];
924 gui->delta[1] = yref - gui->pos[1];
925
926 const float r = dt_fast_hypotf(rx, ry);
927 const float d = (rx * deltax + ry * deltay) / r;
928 const float s = fmaxf(r > 0.0f ? (r + d) / r : 0.0f, 0.0f);
929
930 if(k == 1 || k == 2)
931 {
932 ellipse->radius[0] = MAX(0.0002f, ellipse->radius[0] * s);
934 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_a", ellipse->radius[0]);
935 else
936 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_a", ellipse->radius[0]);
937 }
938 else if(k == 3 || k == 4)
939 {
940 ellipse->radius[1] = MAX(0.0002f, ellipse->radius[1] * s);
942 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_b", ellipse->radius[1]);
943 else
944 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_b", ellipse->radius[1]);
945 }
946
947 // we recreate the form points
948 dt_masks_gui_form_create(form, gui, index, module);
949
950 return 1;
951 }
952
953 // rotation of the ellipse with the mouse
954 else if(gui->form_rotating)
955 {
956
957 const float origin_point[2] = { gpt->points[0], gpt->points[1] };
958 const float angle = dt_masks_rotate_with_anchor(gui->dev, gui->pos, origin_point, gui);
959
960 _change_rotation(form, gui, module, index, angle , DT_MASKS_INCREMENT_OFFSET, 1);
961
962 // we recreate the form points
963 dt_masks_gui_form_create(form, gui, index, module);
964
965 return 1;
966 }
967
968 //if(gui->edit_mode != DT_MASKS_EDIT_FULL) return 0;
969 return 0;
970}
971
972static void _ellipse_draw_shape(dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source,
973 const dt_masks_skip_range_t *skips, const int skip_count)
974{
975 /* An ellipse has no self-intersections, so it never carries an exclusion list -- these are the
976 * shared shape_draw_function_t signature, not something to honour here. */
977 (void)dev; (void)nb; (void)border; (void)source; (void)skips; (void)skip_count;
978
979 /* Decimate to the device resolution, as the brush does; see dt_draw_min_emit_step(). */
980 const double min_step = dt_draw_min_emit_step(cr);
981 const double min_step2 = min_step * min_step;
982 double last_x = points[10], last_y = points[11];
983 cairo_move_to(cr, last_x, last_y);
984 for(int t = 6; t < points_count; t++)
985 {
986 const double x = points[t * 2];
987 const double y = points[t * 2 + 1];
988 const double dx = x - last_x, dy = y - last_y;
989 if((dx * dx + dy * dy) < min_step2) continue;
990 cairo_line_to(cr, x, y);
991 last_x = x; last_y = y;
992 }
993 cairo_close_path(cr);
994}
995
996static void _ellipse_draw_handles(const dt_masks_form_gui_t *gui, cairo_t *cr, const float zoom_scale,
997 dt_masks_form_gui_points_t *gpt, const int index)
998{
999 if(IS_NULL_PTR(gpt) || IS_NULL_PTR(gpt->points) || gpt->points_count < 5) return;
1000
1001 const int selected_node = dt_masks_gui_selected_node_index(gui);
1002 const float *nodes = gpt->points;
1003 float x, y;
1004
1005 const float r = atan2f(nodes[3] - nodes[1], nodes[2] - nodes[0]);
1006 const float sinr = sinf(r);
1007 const float cosr = cosf(r);
1008
1009 for(int i = 1; i < 5; i++)
1010 {
1011 _ellipse_point_transform(nodes[0], nodes[1], nodes[i * 2], nodes[i * 2 + 1], sinr, cosr, &x, &y);
1012 const gboolean selected = (i == gui->node_hovered || i == gui->node_dragging);
1013 const gboolean action = (i == selected_node);
1014 dt_draw_node(cr, TRUE, action, selected, zoom_scale, x, y);
1015 }
1016}
1017
1018static void _ellipse_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index,
1019 int num_points)
1020{
1021 // add a preview when creating an ellipse
1022 // in creation mode
1023 if(gui->creation)
1024 {
1027 if(_ellipse_get_creation_preview(form, gui, &preview)) return;
1028
1029 dt_masks_draw_preview_shape(gui->dev, cr, zoom_scale, num_points, preview.points, preview.points_count,
1030 preview.border, preview.border_count,
1031 &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1032 CAIRO_LINE_CAP_ROUND, TRUE, FALSE);
1033
1034
1035 // draw a cross where the source will be created
1036 if(dt_masks_form_is_clone(form))
1037 {
1038 dt_masks_draw_source_preview(cr, zoom_scale, gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1], FALSE);
1039 dt_masks_draw_preview_shape(gui->dev, cr, zoom_scale, num_points, preview.source_points, preview.points_count,
1040 NULL, 0, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1041 CAIRO_LINE_CAP_ROUND, FALSE, TRUE);
1042 }
1043
1045
1046 return;
1047 } // gui->creation
1048
1049 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1050 if(IS_NULL_PTR(gpt)) return;
1051
1052 // we draw the main shape
1053 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1054 if(gpt->points && gpt->points_count > 5)
1055 dt_draw_shape_lines(gui->dev, DT_MASKS_NO_DASH, FALSE, cr, num_points, selected, zoom_scale, gpt->points,
1056 gpt->points_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT, NULL, 0);
1057
1058 if(gui->group_selected == index)
1059 {
1060 // we draw the borders
1061 if(gpt->border && gpt->border_count > 5)
1062 dt_draw_shape_lines(gui->dev, DT_MASKS_DASH_STICK, FALSE, cr, num_points, (gui->border_selected), zoom_scale, gpt->border,
1063 gpt->border_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_ROUND, NULL, 0);
1064
1065 // draw handles
1066 _ellipse_draw_handles(gui, cr, zoom_scale, gpt, index);
1067 }
1068
1069 //draw the center point
1070 if(gui->group_selected == index && gui->pivot_selected && gpt->points && gpt->points_count > 0)
1071 dt_draw_node(cr, FALSE, FALSE, (gui->form_rotating), zoom_scale, gpt->points[0], gpt->points[1]);
1072
1073 // draw the source if any
1074 if(gpt->source && gpt->source_count > 10 && gpt->points && gpt->points_count > 0)
1075 {
1076 dt_masks_gui_center_point_t center_pt = { .main = { gpt->points[0], gpt->points[1] },
1077 .source = { gpt->source[0], gpt->source[1] }};
1078 dt_masks_draw_source(cr, gui, index, num_points, zoom_scale, &center_pt, &dt_masks_functions_ellipse.draw_shape);
1079 }
1080
1081}
1082
1083
1084static void _fill_mask(const size_t numpoints, float *const bufptr, const float *const points,
1085 const float *const center, const float a, const float b, const float ta, const float tb,
1086 const float alpha, const size_t out_scale)
1087{
1088 const float a2 = a * a;
1089 const float b2 = b * b;
1090 const float ta2 = ta * ta;
1091 const float tb2 = tb * tb;
1092 const float cos_alpha = cosf(alpha);
1093 const float sin_alpha = sinf(alpha);
1094
1095 // Determine the strength of the mask for each of the distorted points. If inside the border of the ellipse,
1096 // the strength is always 1.0; if outside the falloff region, it is 0.0, and in between it falls off quadratically.
1097 // To compute this, we need to do the equivalent of projecting the vector from the center of the ellipse to the
1098 // given point until it intersect the ellipse and the outer edge of the falloff, respectively. The ellipse can
1099 // be rotated, but we can compensate for that by applying a rotation matrix for the same rotation in the opposite
1100 // direction before projecting the vector.
1101 __OMP_PARALLEL_FOR_SIMD__(if(numpoints > 50000) aligned(points, bufptr : 64))
1102 for(size_t i = 0; i < numpoints; i++)
1103 {
1104 const float x = points[2 * i] - center[0];
1105 const float y = points[2 * i + 1] - center[1];
1106 // find the square of the distance from the center
1107 const float l2 = x * x + y * y;
1108 const float l = sqrtf(l2);
1109 // normalize the point's coordinate to form a unit vector, taking care not to divide by zero
1110 const float x_norm = l ? x / l : 0.0f;
1111 const float y_norm = l ? y / l : 1.0f; // ensure we don't get 0 for both sine and cosine below
1112 // apply the rotation matrix
1113 const float x_rot = x_norm * cos_alpha + y_norm * sin_alpha;
1114 const float y_rot = -x_norm * sin_alpha + y_norm * cos_alpha;
1115 // at this point, x_rot = cos(v) and y_rot = sin(v) since they are on the unit circle; we need the squared values
1116 const float cosv2 = x_rot * x_rot;
1117 const float sinv2 = y_rot * y_rot;
1118
1119 // project the rotated unit vector out to the ellipse and the outer border
1120 const float radius2 = a2 * b2 / (a2 * sinv2 + b2 * cosv2);
1121 const float total2 = ta2 * tb2 / (ta2 * sinv2 + tb2 * cosv2);
1122
1123 // quadratic falloff between the ellipses's radius and the radius of the outside of the feathering
1124 // ratio = 0.0 at the outer border, >= 1.0 within the ellipse, negative outside the falloff
1125 const float ratio = (total2 - l2) / (total2 - radius2);
1126 // enforce 1.0 inside the ellipse and 0.0 outside the feathering
1127 const float f = CLIP(ratio);
1128 bufptr[i << out_scale] = f * f;
1129 }
1130}
1131
1132static float *const _ellipse_points_to_transform(dt_develop_t *dev, const float center_x, const float center_y, const float dim1, const float dim2,
1133 const float rotation, const float wd, const float ht, size_t *point_count)
1134{
1135
1136 const float v1 = ((rotation) / 180.0f) * M_PI;
1137 const float v2 = ((rotation - 90.0f) / 180.0f) * M_PI;
1138 float a = 0.0f, b = 0.0f, v = 0.0f;
1139
1140 if(dim1 >= dim2)
1141 {
1142 a = dim1;
1143 b = dim2;
1144 v = v1;
1145 }
1146 else
1147 {
1148 a = dim2;
1149 b = dim1;
1150 v = v2;
1151 }
1152
1153 const float sinv = sinf(v);
1154 const float cosv = cosf(v);
1155
1156 // how many points do we need ?
1157 const float lambda = (a - b) / (a + b);
1158 const int l = (int)(M_PI * (a + b)
1159 * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1160
1161 // buffer allocation
1162 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t) 2 * (l + 5), 0);
1163 if(IS_NULL_PTR(points))
1164 return NULL;
1165 *point_count = l + 5;
1166
1167 // now we set the points - first the center
1168 float center[2] = { center_x, center_y };
1170 const float x = points[0] = center[0];
1171 const float y = points[1] = center[1];
1172 // then the control node points (ends of semimajor/semiminor axes)
1173 points[2] = x + a * cosf(v);
1174 points[3] = y + a * sinf(v);
1175 points[4] = x - a * cosf(v);
1176 points[5] = y - a * sinf(v);
1177 points[6] = x + b * cosf(v - M_PI / 2.0f);
1178 points[7] = y + b * sinf(v - M_PI / 2.0f);
1179 points[8] = x - b * cosf(v - M_PI / 2.0f);
1180 points[9] = y - b * sinf(v - M_PI / 2.0f);
1181 // and finally the regularly-spaced points on the circumference
1182 for(int i = 5; i < l + 5; i++)
1183 {
1184 float alpha = (i - 5) * 2.0 * M_PI / (float)l;
1185 points[i * 2] = x + a * cosf(alpha) * cosv - b * sinf(alpha) * sinv;
1186 points[i * 2 + 1] = y + a * cosf(alpha) * sinv + b * sinf(alpha) * cosv;
1187 }
1188 return points;
1189}
1190
1193 dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
1194{
1195 /* Every early exit below must leave the out-parameters defined: callers read them
1196 * whenever this reports anything but ERROR, and three of them used to report success
1197 * for a shape with no geometry while writing none of these. */
1198 *width = 0;
1199 *height = 0;
1200 *posx = 0;
1201 *posy = 0;
1202 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1203 // we get the ellipse values
1204 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1205 if(IS_NULL_PTR(ellipse)) return DT_MASKS_RASTER_EMPTY;
1206 const float wd = pipe->iwidth, ht = pipe->iheight;
1207 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1208 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1209 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1210
1211 // next we compute the points to be transformed
1212 size_t point_count = 0;
1213 float *const restrict points
1214 = _ellipse_points_to_transform(module->dev, form->source[0], form->source[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1215 if (IS_NULL_PTR(points))
1216 return DT_MASKS_RASTER_ERROR;
1217
1218 // and we transform them with all distorted modules
1219 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1220 {
1222 return DT_MASKS_RASTER_ERROR;
1223 }
1224
1225 // finally, find the extreme left/right and top/bottom points
1226 dt_masks_points_bounding_box(points, point_count, width, height, posx, posy);
1228 return DT_MASKS_RASTER_OK;
1229}
1230
1232 const dt_dev_pixelpipe_iop_t *const piece,
1233 dt_masks_form_t *const form,
1234 int *width, int *height, int *posx, int *posy)
1235{
1236 *width = 0;
1237 *height = 0;
1238 *posx = 0;
1239 *posy = 0;
1240 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1241 // we get the ellipse values
1242 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1243 if(IS_NULL_PTR(ellipse)) return DT_MASKS_RASTER_EMPTY;
1244 const float wd = pipe->iwidth, ht = pipe->iheight;
1245 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1246 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1247 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1248
1249 // next we compute the points to be transformed
1250 size_t point_count = 0;
1251 float *const restrict points
1252 = _ellipse_points_to_transform(module->dev, ellipse->center[0], ellipse->center[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1253 if (IS_NULL_PTR(points))
1254 return DT_MASKS_RASTER_ERROR;
1255
1256 // and we transform them with all distorted modules
1257 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1258 {
1260 return DT_MASKS_RASTER_ERROR;
1261 }
1262
1263 // finally, find the extreme left/right and top/bottom points
1264 dt_masks_points_bounding_box(points, point_count, width, height, posx, posy);
1266 return DT_MASKS_RASTER_OK;
1267}
1268
1270 const dt_dev_pixelpipe_iop_t *const piece,
1271 dt_masks_form_t *const form,
1272 float **buffer, int *width, int *height, int *posx, int *posy)
1273{
1274 *buffer = NULL;
1275 *width = 0;
1276 *height = 0;
1277 *posx = 0;
1278 *posy = 0;
1279 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1280 double start2 = 0.0;
1282
1283 // we get the area
1284 const dt_masks_raster_result_t area = _ellipse_get_area(module, pipe, piece, form, width, height, posx, posy);
1285 if(area != DT_MASKS_RASTER_OK) return area;
1286
1288 {
1289 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse area took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1290 start2 = dt_get_wtime();
1291 }
1292
1293 // we get the ellipse values
1294 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1295 if(IS_NULL_PTR(ellipse)) return DT_MASKS_RASTER_EMPTY;
1296 // we create a buffer of points with all points in the area
1297 int w = *width, h = *height;
1298 const int posx_ = *posx;
1299 const int posy_ = *posy;
1300 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * w * h, 0);
1301 if(IS_NULL_PTR(points))
1302 return DT_MASKS_RASTER_ERROR;
1303 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)w * h > 50000))
1304 for(int i = 0; i < h; i++)
1305 for(int j = 0; j < w; j++)
1306 {
1307 points[(i * w + j) * 2] = (j + posx_);
1308 points[(i * w + j) * 2 + 1] = (i + posy_);
1309 }
1310
1312 {
1313 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1314 start2 = dt_get_wtime();
1315 }
1316
1317 // we back transform all this points
1318 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)w * h))
1319 {
1321 return DT_MASKS_RASTER_ERROR;
1322 }
1323
1325 {
1326 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse transform took %0.04f sec\n", form->name,
1327 dt_get_wtime() - start2);
1328 start2 = dt_get_wtime();
1329 }
1330
1331 // we allocate the buffer
1332 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
1333 if(IS_NULL_PTR(*buffer))
1334 {
1336 return DT_MASKS_RASTER_ERROR;
1337 }
1338
1339 // we populate the buffer
1340 const int wi = pipe->iwidth, hi = pipe->iheight;
1341 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1342 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1343 const float total[2] = { (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wi, hi),
1344 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1345
1346 float a = 0.0F, b = 0.0F, ta = 0.0F, tb = 0.0F, alpha = 0.0F;
1347
1348 if(radius[0] >= radius[1])
1349 {
1350 a = radius[0];
1351 b = radius[1];
1352 ta = total[0];
1353 tb = total[1];
1354 alpha = (ellipse->rotation / 180.0f) * M_PI;
1355 }
1356 else
1357 {
1358 a = radius[1];
1359 b = radius[0];
1360 ta = total[1];
1361 tb = total[0];
1362 alpha = ((ellipse->rotation - 90.0f) / 180.0f) * M_PI;
1363 }
1364
1365 float *const bufptr = *buffer;
1366
1367 _fill_mask((size_t)(h)*w, bufptr, points, center, a, b, ta, tb, alpha, 0);
1368
1370
1372 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1373
1374 return DT_MASKS_RASTER_OK;
1375}
1376
1378 const dt_dev_pixelpipe_iop_t *const piece,
1379 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer,
1380 dt_iop_roi_t *touched)
1381{
1382 dt_masks_touched_none(touched);
1383 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1384
1385 double start1 = 0.0;
1386 double start2 = start1;
1387 if(dt_get_debug_flags() & DT_DEBUG_PERF) start2 = start1 = dt_get_wtime();
1388
1389 // we get the ellipse parameters
1390 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1391 if(IS_NULL_PTR(ellipse)) return DT_MASKS_RASTER_EMPTY;
1392 const int wi = pipe->iwidth, hi = pipe->iheight;
1393 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1394 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1395 const float total[2] = { (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wi, hi),
1396 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1397
1398 const float a = radius[0];
1399 const float b = radius[1];
1400 const float ta = total[0];
1401 const float tb = total[1];
1402 const float alpha = (ellipse->rotation / 180.0f) * M_PI;
1403 const float cosa = cosf(alpha);
1404 const float sina = sinf(alpha);
1405
1406 // we create a buffer of grid points for later interpolation: higher speed and reduced memory footprint;
1407 // we match size of buffer to bounding box around the shape
1408 const int w = roi->width;
1409 const int h = roi->height;
1410 const int px = roi->x;
1411 const int py = roi->y;
1412 const float iscale = 1.0f / roi->scale;
1413 // scale dependent resolution: when zoomed in (scale > 1), use finer grid to avoid interpolation holes
1414 const float grid_scale = 1.0f / MAX(roi->scale, 1e-6f);
1415 const int grid = CLAMP((10.0f * grid_scale + 2.0f) / 3.0f, 1, 4);
1416 const int gw = (w + grid - 1) / grid + 1; // grid dimension of total roi
1417 const int gh = (h + grid - 1) / grid + 1; // grid dimension of total roi
1418
1420 {
1421 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse init took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1422 start2 = dt_get_wtime();
1423 }
1424
1425 // we look at the outer line of the shape - no effects outside of this ellipse;
1426 // we need many points as we do not know how the ellipse might get distorted in the pixelpipe
1427 const float lambda = (ta - tb) / (ta + tb);
1428 const int l = (int)(M_PI * (ta + tb) * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1429 const size_t ellpts = MIN(360, l);
1430 float *ell = dt_pixelpipe_cache_alloc_align_float_cache(ellpts * 2, 0);
1431 if(IS_NULL_PTR(ell)) return DT_MASKS_RASTER_ERROR;
1432 __OMP_PARALLEL_FOR__(if(ellpts > 100))
1433 for(int n = 0; n < ellpts; n++)
1434 {
1435 const float phi = (2.0f * M_PI * n) / ellpts;
1436 const float cosp = cosf(phi);
1437 const float sinp = sinf(phi);
1438 ell[2 * n] = center[0] + ta * cosa * cosp - tb * sina * sinp;
1439 ell[2 * n + 1] = center[1] + ta * sina * cosp + tb * cosa * sinp;
1440 }
1441
1443 {
1444 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1445 start2 = dt_get_wtime();
1446 }
1447
1448 // we transform the outline from input image coordinates to current position in pixelpipe
1450 ellpts))
1451 {
1453 return DT_MASKS_RASTER_ERROR;
1454 }
1455
1457 {
1458 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline transform took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1459 start2 = dt_get_wtime();
1460 }
1461
1462 // we get the min/max values ...
1463 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
1464 for(int n = 0; n < ellpts; n++)
1465 {
1466 // just in case that transform throws surprising values
1467 if(!(isnormal(ell[2 * n]) && isnormal(ell[2 * n + 1]))) continue;
1468
1469 xmin = MIN(xmin, ell[2 * n]);
1470 xmax = MAX(xmax, ell[2 * n]);
1471 ymin = MIN(ymin, ell[2 * n + 1]);
1472 ymax = MAX(ymax, ell[2 * n + 1]);
1473 }
1474
1475#if 0
1476 printf("xmin %f, xmax %f, ymin %f, ymax %f\n", xmin, xmax, ymin, ymax);
1477 printf("wi %d, hi %d, iscale %f\n", wi, hi, iscale);
1478 printf("w %d, h %d, px %d, py %d\n", w, h, px, py);
1479#endif
1480
1481 // ... and calculate the bounding box with a bit of reserve
1482 const int bbxm = CLAMP((int)floorf(xmin / iscale - px) / grid - 1, 0, gw - 1);
1483 const int bbXM = CLAMP((int)ceilf(xmax / iscale - px) / grid + 2, 0, gw - 1);
1484 const int bbym = CLAMP((int)floorf(ymin / iscale - py) / grid - 1, 0, gh - 1);
1485 const int bbYM = CLAMP((int)ceilf(ymax / iscale - py) / grid + 2, 0, gh - 1);
1486 const int bbw = bbXM - bbxm + 1;
1487 const int bbh = bbYM - bbym + 1;
1488
1489#if 0
1490 printf("bbxm %d, bbXM %d, bbym %d, bbYM %d\n", bbxm, bbXM, bbym, bbYM);
1491 printf("gw %d, gh %d, bbw %d, bbh %d\n", gw, gh, bbw, bbh);
1492#endif
1493
1495
1497 {
1498 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse bounding box took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1499 start2 = dt_get_wtime();
1500 }
1501
1502 // check if there is anything to do at all;
1503 // only if width and height of bounding box is 2 or greater the shape lies inside of roi and requires action
1504 if(bbw <= 1 || bbh <= 1)
1505 return DT_MASKS_RASTER_EMPTY;
1506
1507 const dt_masks_sample_grid_t sample_grid
1508 = { .x0 = bbxm, .y0 = bbym, .width = bbw, .height = bbh,
1509 .step = grid, .px = px, .py = py, .iscale = iscale };
1510 float *points
1511 = dt_masks_sample_grid_backtransform(pipe, module->iop_order, &sample_grid, "ellipse", form->name);
1512 if(IS_NULL_PTR(points)) return DT_MASKS_RASTER_ERROR;
1513 start2 = dt_get_wtime();
1514
1515 // we calculate the mask values at the transformed points;
1516 // re-use the points array for results; this requires out_scale==1 to double the offsets at which they are stored
1517 _fill_mask((size_t)(bbh)*bbw, points, points, center, a, b, ta, tb, alpha, 1);
1518
1520 {
1521 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name,
1522 dt_get_wtime() - start2);
1523 start2 = dt_get_wtime();
1524 }
1525
1526 // we fill the pre-initialized output buffer by interpolation;
1527 // we only need to take the contents of our bounding box into account
1528 int endx = 0, endy = 0;
1529 dt_masks_sample_grid_interpolate(points, &sample_grid, buffer, w, h, &endx, &endy);
1530
1532
1533 dt_masks_touched_set(touched, bbxm * grid, bbym * grid, endx - 1, endy - 1, w, h);
1534
1536 {
1537 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1538 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse total render took %0.04f sec\n", form->name,
1539 dt_get_wtime() - start1);
1540 }
1541 return DT_MASKS_RASTER_OK;
1542}
1543
1544static void _ellipse_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1545{
1546 snprintf(form->name, sizeof(form->name), _("ellipse #%d"), (int)nb);
1547}
1548
1549static void _ellipse_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
1550{
1551 // unused arg, keep compiler from complaining
1553}
1554
1555static void _ellipse_initial_source_pos(dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
1556{
1557
1558
1559 const float radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1560 const float radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1561 float offset[2] = { radius_a, -radius_b };
1563 *x = offset[0];
1564 *y = offset[1];
1565}
1566
1567static void _ellipse_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1568 char *const restrict msgbuf, const size_t msgbuf_len)
1569{
1570 // Only gestures that cannot be discovered any other way. What the wheel does is the user's
1571 // own mapping now (masks_gui.h), shown in the Drawn tab, so it is not repeated here.
1572 if(gui->creation)
1573 {
1574 if(dt_masks_form_is_clone(form))
1575 g_strlcat(msgbuf, _("<b>Set source</b>: Shift+Click"), msgbuf_len);
1576 }
1577 else if(gui->source_selected)
1578 g_strlcat(msgbuf, _("<b>Move source</b>: Drag"), msgbuf_len);
1579 // Radius handles: the 4 axis nodes.
1580 else if(gui->node_hovered >= 1)
1581 g_strlcat(msgbuf, _("<b>Resize axis</b>: Drag"), msgbuf_len);
1582 // Clicking the border rotates instead of moving.
1583 else if(gui->border_selected)
1584 g_strlcat(msgbuf, _("<b>Rotate</b>: Drag"), msgbuf_len);
1585 else if(gui->form_selected)
1586 g_strlcat(msgbuf, _("<b>Move</b>: Drag, <b>Fading mode</b>: Shift+Click"), msgbuf_len);
1587}
1588
1590{
1591 int flags = -1;
1592 float radius_a = 0.0f;
1593 float radius_b = 0.0f;
1594 float border = 0.0f;
1596 {
1597 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/ellipse/rotation", 0.0f, 360.f);
1599 radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1600 radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1601 border = dt_conf_get_float("plugins/darkroom/spots/ellipse/border");
1602 }
1603 else
1604 {
1605 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/ellipse/rotation", 0.0f, 360.f);
1607 radius_a = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_a");
1608 radius_b = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_b");
1609 border = dt_conf_get_float("plugins/darkroom/masks/ellipse/border");
1610 }
1611
1612 const float ratio = radius_a / radius_b;
1613
1614 if(radius_a > radius_b)
1615 {
1616 radius_a = CLAMPS(radius_a, 0.001f, 0.5f);
1617 radius_b = radius_a / ratio;
1618 }
1619 else
1620 {
1621 radius_b = CLAMPS(radius_b, 0.001f, 0.5);
1622 radius_a = ratio * radius_b;
1623 }
1624
1625 const float reference = (flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? 1.0f / fmin(radius_a, radius_b) : 1.0f);
1626 border = CLAMPS(border, 0.001f * reference, reference);
1627
1629 {
1630 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_a", radius_a, 0.001f, 0.5f)
1631 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1632 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/border", border, 0.001f, reference);
1633 }
1634 else
1635 {
1636 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_a", radius_a, 0.001f, 0.5f);
1637 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1638 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/border", border, 0.001f, reference);
1639 }
1640}
1641
1642// The function table for ellipses. This must be public, i.e. no "static" keyword.
1645 .sanitize_config = _ellipse_sanitize_config,
1646 .set_form_name = _ellipse_set_form_name,
1647 .set_hint_message = _ellipse_set_hint_message,
1648 .duplicate_points = _ellipse_duplicate_points,
1649 .initial_source_pos = _ellipse_initial_source_pos,
1650 .get_distance = _ellipse_get_distance,
1651 .get_points = _ellipse_get_points,
1652 .get_points_border = _ellipse_get_points_border,
1653 .get_mask = _ellipse_get_mask,
1654 .get_mask_roi = _ellipse_get_mask_roi,
1655 .get_area = _ellipse_get_area,
1656 .get_source_area = _ellipse_get_source_area,
1657 .get_gravity_center = _ellipse_get_gravity_center,
1658 .get_interaction_value = _ellipse_get_interaction_value,
1659 .set_interaction_value = _ellipse_set_interaction_value,
1660 .update_hover = _find_closest_handle,
1661 .mouse_moved = _ellipse_events_mouse_moved,
1662 .mouse_scrolled = _ellipse_events_mouse_scrolled,
1663 .button_pressed = _ellipse_events_button_pressed,
1664 .button_released = _ellipse_events_button_released,
1665 .key_pressed = _ellipse_events_key_pressed,
1666 .post_expose = _ellipse_events_post_expose,
1667 .draw_shape = _ellipse_draw_shape
1668};
1669
1670// clang-format off
1671// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1672// vim: shiftwidth=2 expandtab tabstop=2 cindent
1673// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1674// 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 const float x
const float l2
const float f
const int t
const float v
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
int dt_conf_get_and_sanitize_int(const char *name, int min, int max)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
float dt_conf_get_and_sanitize_float(const char *name, float min, float max)
#define DT_CONF_SET_SANITIZED_FLOAT(name, val, min, max)
Clamp val to [min, max] and store it as a float.
Definition conf.h:262
void dt_toast_log(const char *msg,...)
Definition control.c:871
#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
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:669
static dt_masks_raster_result_t _ellipse_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer, dt_iop_roi_t *touched)
Definition ellipse.c:1377
static dt_masks_raster_result_t _ellipse_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 ellipse.c:1191
static void _ellipse_initial_source_pos(dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
Definition ellipse.c:1555
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
Definition ellipse.c:496
static int _ellipse_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 ellipse.c:868
static gboolean _ellipse_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
Definition ellipse.c:552
static void _ellipse_sanitize_config(dt_masks_type_t type)
Definition ellipse.c:1589
static dt_masks_raster_result_t _ellipse_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 ellipse.c:417
static dt_masks_raster_result_t _ellipse_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, int *width, int *height, int *posx, int *posy)
Definition ellipse.c:1231
static void _ellipse_draw_handles(const dt_masks_form_gui_t *gui, cairo_t *cr, const float zoom_scale, dt_masks_form_gui_points_t *gpt, const int index)
Definition ellipse.c:996
static void _ellipse_point_transform(const float xref, const float yref, const float x, const float y, const float sinr, const float cosr, float *xnew, float *ynew)
Definition ellipse.c:65
static int _ellipse_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 ellipse.c:862
static void _ellipse_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index, float *node_x, float *node_y, void *user_data)
Ellipse-specific node position lookup.
Definition ellipse.c:463
static dt_masks_raster_result_t _ellipse_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition ellipse.c:1269
static int _ellipse_get_creation_preview(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
Definition ellipse.c:291
#define FADING_MIN
Definition ellipse.c:53
static void _ellipse_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition ellipse.c:1549
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 ellipse.c:596
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:519
static void _fill_mask(const size_t numpoints, float *const bufptr, const float *const points, const float *const center, const float a, const float b, const float ta, const float tb, const float alpha, const size_t out_scale)
Definition ellipse.c:1084
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 ellipse.c:613
static void _ellipse_init_new(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_masks_node_ellipse_t *ellipse)
Definition ellipse.c:268
static void _ellipse_get_distance(float x, float y, float mouse_radius, 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 ellipse.c:184
static void _ellipse_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border, int inside_source, void *user_data)
Ellipse-specific post-selection hook.
Definition ellipse.c:490
static void _ellipse_get_creation_values(const dt_masks_form_t *form, dt_masks_ellipse_creation_values_t *values)
Definition ellipse.c:253
static float _ellipse_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition ellipse.c:533
static void _ellipse_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 ellipse.c:1567
static float * _points_to_transform(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b, float rotation, float wd, float ht, int *points_count)
Definition ellipse.c:323
static int _ellipse_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 ellipse.c:705
static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:511
#define FADING_MAX
Definition ellipse.c:54
static void _ellipse_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int num_points)
Definition ellipse.c:1018
static int _ellipse_get_points(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b, float rotation, float **points, int *points_count)
Definition ellipse.c:395
static void _ellipse_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition ellipse.c:1544
const dt_masks_functions_t dt_masks_functions_ellipse
Definition ellipse.c:1643
static int _ellipse_point_close_to_path(float x, float y, float mouse_radius, float *points, int points_count)
Definition ellipse.c:137
static float _ellipse_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 ellipse.c:570
static void _ellipse_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)
Ellipse-specific inside/border hit testing adapter.
Definition ellipse.c:477
static float *const _ellipse_points_to_transform(dt_develop_t *dev, const float center_x, const float center_y, const float dim1, const float dim2, const float rotation, const float wd, const float ht, size_t *point_count)
Definition ellipse.c:1132
static int _ellipse_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 ellipse.c:782
static int _change_rotation(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 ellipse.c:636
static int _ellipse_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 ellipse.c:661
static int _ellipse_get_points_source(dt_develop_t *dev, float xx, float yy, float xs, float ys, float radius_a, float radius_b, float rotation, float **points, int *points_count, const dt_iop_module_t *module)
Definition ellipse.c:379
static void _ellipse_draw_shape(dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source, const dt_masks_skip_range_t *skips, const int skip_count)
Definition ellipse.c:972
static int _init_fading(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:504
static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:526
_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.
float *const restrict const size_t k
#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
@ DT_MASKS_ELLIPSE_PROPORTIONAL
Definition masks.h:183
@ DT_MASKS_ELLIPSE_EQUIDISTANT
Definition masks.h:182
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 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
float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value, float value_min, float value_max, dt_masks_increment_t increment, int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
Definition masks_gui.c:5275
float dt_masks_rotate_with_anchor(dt_develop_t *develop, const float anchor[2], const float center[2], dt_masks_form_gui_t *mask_gui)
Compute rotation angle (degrees) around a center using an anchor point.
Definition masks_gui.c:5706
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 int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:235
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_INTERACTION_ROTATION
dt_masks_increment_t
@ DT_MASKS_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
#define CLAMPS(A, L, H)
Definition math.h:78
#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
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
static float gh(const float f)
static double reference(const op_t op, const double x)
#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
Definition imageop.h:311
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 border_toggling
Definition masks_gui.h:163
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 form_rotating
Definition masks_gui.h:162
gboolean border_selected
Definition masks_gui.h:152
gboolean pivot_selected
Definition masks_gui.h:154
struct dt_develop_t * dev
Definition masks_gui.h:101
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
shape_draw_function_t draw_shape
struct dt_masks_gui_center_point_t::@27 main
dt_masks_ellipse_flags_t flags
Definition masks.h:208
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
Telling the user something happened.
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)