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*/
36#include "bauhaus/bauhaus.h"
37#include "common/debug.h"
38#include "common/undo.h"
39#include "control/conf.h"
40#include "develop/blend.h"
41#include "develop/imageop.h"
42#include "develop/masks.h"
44
45
46#define HARDNESS_MIN 0.0005f
47#define HARDNESS_MAX 1.0f
48
49#define BORDER_MIN 0.00005f
50#define BORDER_MAX 0.5f
51
52#define RADIUS_CLONE_MIN 0.00005f
53#define RADIUS_CLONE_MAX 0.5f
54
55#define RADIUS_NON_CLONE_MIN 0.0005f
56#define RADIUS_NON_CLONE_MAX 1.0f
57
58static inline void _ellipse_point_transform(const float xref, const float yref, const float x, const float y,
59 const float sinr, const float cosr, float *xnew, float *ynew)
60{
61 const float xtmp = (sinr * sinr + cosr * cosr) * (x - xref) + (cosr * sinr - cosr * sinr) * (y - yref);
62 const float ytmp = (cosr * sinr - cosr * sinr) * (x - xref) + (sinr * sinr + cosr * cosr) * (y - yref);
63
64 *xnew = xref + xtmp;
65 *ynew = yref + ytmp;
66}
67
68/*
69// Jordan's point in polygon test
70static int _ellipse_cross_test(float x, float y, float *point_1, float *point_2)
71{
72 float x_b = point_1[0];
73 float y_b = point_1[1];
74 float x_c = point_2[0];
75 float y_c = point_2[1];
76
77 // Early exit : bounding box test
78 float min_y = fminf(y_b, y_c);
79 float max_y = fmaxf(y_b, y_c);
80 if (y <= min_y || y > max_y) return 1;
81
82 const float x_a = x;
83 const float y_a = y;
84
85 // special case : horizontal line
86 if(y_a == y_b && y_b == y_c)
87 {
88 if((x_b <= x_a && x_a <= x_c) || (x_c <= x_a && x_a <= x_b))
89 return 0;
90 else
91 return 1;
92 }
93
94 // order points b and c by y
95 if(y_b > y_c)
96 {
97 float tmp;
98 tmp = x_b, x_b = x_c, x_c = tmp;
99 tmp = y_b, y_b = y_c, y_c = tmp;
100 }
101
102 if(y_a == y_b && x_a == x_b) return 0;
103
104 if(y_a <= y_b || y_a > y_c) return 1;
105
106 const float delta = (x_b - x_a) * (y_c - y_a) - (y_b - y_a) * (x_c - x_a);
107
108 if(delta > 0)
109 return -1;
110 else if(delta < 0)
111 return 1;
112 else
113 return 0;
114}
115
116static int _ellipse_point_in_polygon(float x, float y, float *points, int points_count)
117{
118 int t = -1;
119
120 t *= _ellipse_cross_test(x, y, points + 2 * (points_count - 1), points);
121
122 for(int i = 0; i < points_count - 2; i++)
123 t *= _ellipse_cross_test(x, y, points + 2 * i, points + 2 * (i + 1));
124
125 return t;
126}
127*/
128
129// check if point is close to path - segment by segment
130static int _ellipse_point_close_to_path(float x, float y, float mouse_radius, float *points, int points_count)
131{
132 float radius2 = mouse_radius * mouse_radius;
133
134 const float lastx = points[2 * (points_count - 1)];
135 const float lasty = points[2 * (points_count - 1) + 1];
136
137 for(int i = 0; i < points_count; i++)
138 {
139 const float px = points[2 * i];
140 const float py = points[2 * i + 1];
141
142 const float r1 = x - lastx;
143 const float r2 = y - lasty;
144 const float r3 = px - lastx;
145 const float r4 = py - lasty;
146
147 const float d = r1 * r3 + r2 * r4;
148 const float l = sqf(r3) + sqf(r4);
149 const float p = d / l;
150
151 float xx = 0.0f, yy = 0.0f;
152
153 if(p < 0 || (px == lastx && py == lasty))
154 {
155 xx = lastx;
156 yy = lasty;
157 }
158 else if(p > 1)
159 {
160 xx = px;
161 yy = py;
162 }
163 else
164 {
165 xx = lastx + p * r3;
166 yy = lasty + p * r4;
167 }
168
169 const float dx = x - xx;
170 const float dy = y - yy;
171
172 if(sqf(dx) + sqf(dy) < radius2) return 1;
173 }
174 return 0;
175}
176
177static void _ellipse_get_distance(float x, float y, float mouse_radius, dt_masks_form_gui_t *gui, int index,
178 int num_points, int *inside, int *inside_border, int *near,
179 int *inside_source, float *dist)
180{
181 // initialise returned values
182 *inside_source = 0;
183 *inside = 0;
184 *inside_border = 0;
185 *near = -1;
186 *dist = FLT_MAX;
187
188 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
189 if(IS_NULL_PTR(gpt)) return;
190
191 const float pt[2] = { x, y };
192
193 // we first check if we are inside the source form
194 if(gpt->source && gpt->source_count > 10)
195 {
196 if(dt_masks_point_in_form_exact(pt, 1, gpt->source, 10, gpt->source_count - 5) >= 0)
197 {
198 *inside_source = 1;
199 *inside = 1;
200
201 // get the minial dist for center & control points
202 float min_dist_norm = FLT_MAX;
203 for(int k=0; k<5; k++)
204 {
205 const float center_dx = x - gpt->source[k * 2];
206 const float center_dy = y - gpt->source[k * 2 + 1];
207 const float dist2 = sqf(center_dx) + sqf(center_dy);
208 min_dist_norm = fminf(min_dist_norm, dist2);
209 }
210 *dist = min_dist_norm;
211 return;
212 }
213 }
214
215 if(IS_NULL_PTR(gpt->points) || gpt->points_count <= 5 || !gpt->border || gpt->border_count <= 5) return;
216
217 // distance from center
218 const float center_dx = x - gpt->points[0];
219 const float center_dy = y - gpt->points[1];
220 *dist = sqf(center_dx) + sqf(center_dy);
221
222 const gboolean close_to_border = _ellipse_point_close_to_path(x, y, mouse_radius * 1.5, gpt->border + 10, gpt->border_count - 5);
223 const gboolean in_border = dt_masks_point_in_form_exact(pt, 1, gpt->border + 10, 10, gpt->border_count - 5) >= 0;
224 // we check if it's inside borders
225 if(!close_to_border && !in_border) return;
226 *inside = 1;
227
228 // and we check if it's inside form
229 const int in_form = _ellipse_point_close_to_path(x, y, mouse_radius, gpt->points + 10, gpt->points_count - 5);
230 *inside_border = !in_form;
231}
232
241
243{
244 const gboolean use_spot_defaults = dt_masks_form_uses_spot_defaults(form);
245 values->border = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/border"
246 : "plugins/darkroom/masks/ellipse/border");
247 values->flags = dt_conf_get_int(use_spot_defaults ? "plugins/darkroom/spots/ellipse/flags"
248 : "plugins/darkroom/masks/ellipse/flags");
249 values->radius_a = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/radius_a"
250 : "plugins/darkroom/masks/ellipse/radius_a");
251 values->radius_b = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/radius_b"
252 : "plugins/darkroom/masks/ellipse/radius_b");
253 values->rotation = dt_conf_get_float(use_spot_defaults ? "plugins/darkroom/spots/ellipse/rotation"
254 : "plugins/darkroom/masks/ellipse/rotation");
255}
256
258{
261 _ellipse_get_creation_values(form, &values);
262
263 ellipse->radius[0] = values.radius_a;
264 ellipse->radius[1] = values.radius_b;
265 ellipse->border = values.border;
266 ellipse->rotation = values.rotation;
267 ellipse->flags = values.flags;
268
269 if(dt_masks_form_is_clone(form))
271 else
273}
274
275static int _ellipse_get_points(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b,
276 float rotation, float **points, int *points_count);
277
278// Mirror the circle creation preview flow: gather defaults, build temp geometry once,
279// and let the expose path only draw the returned buffers.
282{
284 _ellipse_get_creation_values(form, &values);
285
286 const float border_a = (values.flags & DT_MASKS_ELLIPSE_PROPORTIONAL)
287 ? values.radius_a * (1.0f + values.border)
288 : values.radius_a + values.border;
289 const float border_b = (values.flags & DT_MASKS_ELLIPSE_PROPORTIONAL)
290 ? values.radius_b * (1.0f + values.border)
291 : values.radius_b + values.border;
292
293 float center[2];
295
296 *preview = (dt_masks_preview_buffers_t){ 0 };
297 int err = _ellipse_get_points(darktable.develop, center[0], center[1], values.radius_a, values.radius_b,
298 values.rotation, &preview->points, &preview->points_count);
299 if(!err && values.border > 0.0f)
300 err = _ellipse_get_points(darktable.develop, center[0], center[1], border_a, border_b, values.rotation,
301 &preview->border, &preview->border_count);
302
303 if(!err && dt_masks_form_is_clone(form))
304 {
305 float source_pos[2] = { 0.0f, 0.0f };
306 dt_masks_calculate_source_pos_origin(gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1],
307 &source_pos[0], &source_pos[1], FALSE);
308 const float center_source[2] = { source_pos[0] - gui->pos[0], source_pos[1] - gui->pos[1] };
309
311 if(IS_NULL_PTR(preview->source_points))
312 err = 1;
313
314 for(int i = 0; !err && i < preview->points_count; i++)
315 {
316 preview->source_points[i * 2] = preview->points[i * 2] + center_source[0];
317 preview->source_points[i * 2 + 1] = preview->points[i * 2 + 1] + center_source[1];
318 }
319 }
320
321 if(err)
323
324 return err;
325}
326
327static float *_points_to_transform(float xx, float yy, float radius_a, float radius_b, float rotation, float wd,
328 float ht, int *points_count)
329{
330 // Calculate the rotation angle in radians
331 const float v = (rotation / 180.0f) * M_PI;
332
333 // Calculate the radii in pixels
334 const float a = radius_a * MIN(wd, ht);
335 const float b = radius_b * MIN(wd, ht);
336
337 const float sinv = sinf(v);
338 const float cosv = cosf(v);
339
340 // Number of points for the ellipse (take every nth point, interpolation for the GUI)
341 const int n = 10;
342 const float lambda = (a - b) / (a + b);
343 const int l = MAX(
344 100, (int)((M_PI * (a + b)
345 * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda)))) / n));
346
347 // buffer allocations
348 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (l + 5), 0);
349 if(IS_NULL_PTR(points))
350 {
351 *points_count = 0;
352 return 0;
353 }
354 *points_count = l + 5;
355
356 // Center of the ellipse
357 float center[2] = { xx, yy };
359 const float x = points[0] = center[0];
360 const float y = points[1] = center[1];
361
362 // Control points (main axes)
363 points[2] = x + a * cosv;
364 points[3] = y + a * sinv;
365 points[4] = x - a * cosv;
366 points[5] = y - a * sinv;
367
368 points[6] = x - b * sinv;
369 points[7] = y + b * cosv;
370 points[8] = x + b * sinv;
371 points[9] = y - b * cosv;
372 __OMP_PARALLEL_FOR_SIMD__(if(l > 100) aligned(points:64))
373 for(int i = 5; i < l + 5; i++)
374 {
375 const float alpha = (i - 5) * 2.0 * M_PI / (float)l;
376 points[i * 2] = x + a * cosf(alpha) * cosv - b * sinf(alpha) * sinv;
377 points[i * 2 + 1] = y + a * cosf(alpha) * sinv + b * sinf(alpha) * cosv;
378 }
379
380 return points;
381}
382
383static int _ellipse_get_points_source(dt_develop_t *dev, float xx, float yy, float xs, float ys, float radius_a,
384 float radius_b, float rotation, float **points, int *points_count,
385 const dt_iop_module_t *module)
386{
387 const float wd = dev->roi.raw_width;
388 const float ht = dev->roi.raw_height;
389
390 // compute the points of the target (center and circumference of ellipse)
391 // we get the point in RAW image reference
392 *points = _points_to_transform(xx, yy, radius_a, radius_b, rotation, wd, ht, points_count);
393 if(IS_NULL_PTR(*points)) return 1;
394
395 // we transform with all distortion that happen *before* the module
396 // so we have now the TARGET points in module input reference
398 *points, *points_count))
399 goto error;
400
401 // now we move all the points by the shift
402 // so we have now the SOURCE points in module input reference
403 float pts[2] = { xs, ys };
406 pts, 1))
407 goto error;
408
409 {
410 const float dx = pts[0] - (*points)[0];
411 const float dy = pts[1] - (*points)[1];
412 (*points)[0] = pts[0];
413 (*points)[1] = pts[1];
414 __OMP_PARALLEL_FOR_SIMD__(if(*points_count > 100) aligned(points:64))
415 for(int i = 5; i < *points_count; i++)
416 {
417 (*points)[i * 2] += dx;
418 (*points)[i * 2 + 1] += dy;
419 }
420 }
421
422 // we apply the rest of the distortions (those after the module)
423 // so we have now the SOURCE points in final image reference
425 *points, *points_count))
426 goto error;
427
428 return 0;
429
430 // if we failed, then free all and return
431error:
433 *points = NULL;
434 *points_count = 0;
435 return 1;
436}
437
438static int _ellipse_get_points(dt_develop_t *dev, float xx, float yy, float radius_a, float radius_b,
439 float rotation, float **points, int *points_count)
440{
441 const float wd = dev->roi.raw_width;
442 const float ht = dev->roi.raw_height;
443
444 *points = _points_to_transform(xx, yy, radius_a, radius_b, rotation, wd, ht, points_count);
445 if(IS_NULL_PTR(*points)) return 1;
446
447 // and we transform them with all distorted modules
448 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
449 {
451 *points = NULL;
452 *points_count = 0;
453 return 1;
454 }
455
456 return 0;
457}
458
459static int _ellipse_get_points_border(dt_develop_t *dev, struct dt_masks_form_t *form, float **points,
460 int *points_count, float **border, int *border_count, int source,
461 const dt_iop_module_t *module)
462{
463 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
464 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
465 if(IS_NULL_PTR(ellipse)) return 0;
466 float x = 0.0f, y = 0.0f, a = 0.0f, b = 0.0f;
467 x = ellipse->center[0], y = ellipse->center[1];
468 a = ellipse->radius[0], b = ellipse->radius[1];
469
470 if(source)
471 {
472 float xs = form->source[0], ys = form->source[1];
473 return _ellipse_get_points_source(dev, x, y, xs, ys, a, b, ellipse->rotation, points, points_count, module);
474 }
475 else
476 {
477 if(_ellipse_get_points(dev, x, y, a, b, ellipse->rotation, points, points_count) != 0)
478 return 1;
479 if(!IS_NULL_PTR(border))
480 {
481 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
482 return _ellipse_get_points(dev, x, y, (prop ? a * (1.0f + ellipse->border) : a + ellipse->border),
483 (prop ? b * (1.0f + ellipse->border) : b + ellipse->border), ellipse->rotation,
484 border, border_count);
485 }
486 return 0;
487 }
488 return 1;
489}
490
497static void _ellipse_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
498 float *node_x, float *node_y, void *user_data)
499{
500 const float *nodes = gui_points->points;
501 const float rotation = atan2f(nodes[3] - nodes[1], nodes[2] - nodes[0]);
502 const float sinr = sinf(rotation);
503 const float cosr = cosf(rotation);
504 _ellipse_point_transform(nodes[0], nodes[1], nodes[node_index * 2], nodes[node_index * 2 + 1],
505 sinr, cosr, node_x, node_y);
506}
507
511static void _ellipse_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
512 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
513 int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
514{
515 _ellipse_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
516 inside_border, near, inside_source, dist);
517}
518
524static void _ellipse_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border,
525 int inside_source, void *user_data)
526{
527 mask_gui->pivot_selected = inside_border ? TRUE : FALSE; // cast to strict gboolean
528}
529
530static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
531{
532 if(mask_gui) mask_gui->pivot_selected = FALSE;
533 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, 5,
534 NULL, NULL, _ellipse_node_position_cb,
536}
537
538static int _init_hardness(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
539{
541 increment, flow, _("Hardness: %3.2f%%"), 100.0f);
542 return 1;
543}
544
545static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
546{
547 float mask_radius_a = dt_masks_get_set_conf_value(form, "radius_a", amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
548 float mask_radius_b = dt_masks_get_set_conf_value(form, "radius_b", amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
549 dt_toast_log(_("Size: %3.2f%%"), fmaxf(mask_radius_a, mask_radius_b) * 2.f * 100.f);
550 return 1;
551}
552
553static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
554{
555 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
556 increment, flow, _("Opacity: %3.2f%%"), 100.f);
557 return 1;
558}
559
560static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
561{
562 dt_masks_get_set_conf_value_with_toast(form, "rotation", amount, 0.f, 360.f,
563 increment, flow, _("Rotation: %3.2f\302\260"), 1.0f);
564 return 1;
565}
566
568{
569 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
570 const dt_masks_node_ellipse_t *ellipse = (const dt_masks_node_ellipse_t *)(form->points)->data;
571 if(IS_NULL_PTR(ellipse)) return NAN;
572
573 switch(interaction)
574 {
576 return fmaxf(ellipse->radius[0], ellipse->radius[1]);
578 return ellipse->border;
579 default:
580 return NAN;
581 }
582}
583
584static gboolean _ellipse_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
585{
586 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
587 const dt_masks_node_ellipse_t *ellipse = (const dt_masks_node_ellipse_t *)(form->points)->data;
588 if(IS_NULL_PTR(ellipse)) return FALSE;
589 center[0] = ellipse->center[0];
590 center[1] = ellipse->center[1];
591 *area = M_PI_F * ellipse->radius[0] * ellipse->radius[1];
592 return TRUE;
593}
594
595static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
596 int index, const float amount, const dt_masks_increment_t increment, const int flow);
597static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
598 int index, const float amount, const dt_masks_increment_t increment, const int flow);
599
601 dt_masks_increment_t increment, int flow,
602 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
603{
604 if(IS_NULL_PTR(form)) return NAN;
605 const int index = 0;
606
607 switch(interaction)
608 {
610 if(!_change_size(form, gui, module, index, value, increment, flow)) return NAN;
611 return _ellipse_get_interaction_value(form, interaction);
613 if(!_change_hardness(form, gui, module, index, value, increment, flow)) return NAN;
614 return _ellipse_get_interaction_value(form, interaction);
615 default:
616 return NAN;
617 }
618}
619
620static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
621{
622 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
623 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
624 if(IS_NULL_PTR(ellipse)) return 0;
625
626 ellipse->border = CLAMPF(dt_masks_apply_increment(ellipse->border, amount, increment, flow),
628
629 _init_hardness(form, amount, increment, flow);
630
631 // we recreate the form points
632 dt_masks_gui_form_create(form, gui, index, module);
633
634 return 1;
635}
636
637static 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)
638{
639 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
640 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
641 if(IS_NULL_PTR(ellipse)) return 0;
642
643 // Sanitize
644 // do not exceed upper limit of 1.0 and lower limit of 0.004
645 if(amount > 1.0f && (ellipse->border > 1.0f ))
646 return 1;
647
648 // adjust the sizes directly to avoid re-querying group/form
649 ellipse->radius[0] = dt_masks_apply_increment(ellipse->radius[0], amount, increment, flow);
650 ellipse->radius[1] = dt_masks_apply_increment(ellipse->radius[1], amount, increment, flow);
651
652 _init_size(form, amount, DT_MASKS_INCREMENT_SCALE, flow);
653
654 // we recreate the form points
655 dt_masks_gui_form_create(form, gui, index, module);
656
657 return 1;
658}
659
660static 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)
661{
662 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
663 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
664 if(IS_NULL_PTR(ellipse)) return 0;
665
666 // Rotation
667 int flow_increased = (flow > 1) ? (flow - 1) * 5 : flow;
668 ellipse->rotation = dt_masks_apply_increment(ellipse->rotation, amount, increment, flow_increased);
669
670 // Ensure the rotation value warps within the interval [0, 360)
671 if(ellipse->rotation > 360.f) ellipse->rotation = fmodf(ellipse->rotation, 360.f);
672 else if(ellipse->rotation < 0.f) ellipse->rotation = 360.f - fmodf(-ellipse->rotation, 360.f);
673
674 _init_rotation(form, amount, DT_MASKS_INCREMENT_OFFSET, flow);
675
676 // we recreate the form points
677 dt_masks_gui_form_create(form, gui, index, module);
678
679 return 1;
680}
681
682/* Shape handlers receive widget-space coordinates, while normalized output-image
683 * coordinates come from `gui->rel_pos` and absolute output-image
684 * coordinates come from `gui->pos`. */
685static int _ellipse_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
686 uint32_t state, dt_masks_form_t *form, int parentid,
687 dt_masks_form_gui_t *gui, int index,
688 dt_masks_interaction_t interaction)
689{
690 // add a preview when creating an ellipse
691 if(gui->creation)
692 {
693 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
694 return _init_rotation(form, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
695 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
696 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
697 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
698 return _init_hardness(form, (up ? 1.03f : 0.97f), DT_MASKS_INCREMENT_SCALE, flow);
699 else
700 return _init_size(form, (up ? 1.03f :0.97f), DT_MASKS_INCREMENT_SCALE, flow);
701 }
702 else if(gui->form_selected)
703 {
704 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
705 return _change_rotation(form, gui, module, index, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
706 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
707 return dt_masks_form_change_opacity(form, parentid, up, flow);
708 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
709 return _change_hardness(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
710 else
711 return _change_size(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
712 }
713
714 return 0;
715}
716
717static int _ellipse_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
718 double pressure, int which, int type, uint32_t state,
719 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui,
720 int index)
721{
722 if(gui->creation && which == 1
723 && ((dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK)) || dt_modifier_is(state, GDK_SHIFT_MASK)))
724 {
725 // set some absolute or relative position for the source of the clone mask
727 return 1;
728 }
729
730 else if(which == 1)
731 {
732 if(gui->creation)
733 {
734 dt_iop_module_t *crea_module = gui->creation_module;
735 // we create the ellipse
737 if(IS_NULL_PTR(ellipse)) return 0;
738 _ellipse_init_new(form, gui, ellipse);
739 form->points = g_list_append(form->points, ellipse);
740 dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui);
741
742 return 1;
743 }
744
745 else // creation is false
746 {
747 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
748 if(IS_NULL_PTR(gpt)) return 0;
749
751 {
752 // we start the source dragging
753 gui->delta[0] = gpt->source[0] - gui->pos[0];
754 gui->delta[1] = gpt->source[1] - gui->pos[1];
755 return 1;
756 }
757 else if(gui->node_hovered >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
758 {
759 // we start the point dragging
760 gui->delta[0] = gpt->points[0] - gui->pos[0];
761 gui->delta[1] = gpt->points[1] - gui->pos[1];
762 return 1;
763 }
764 else if(gui->form_selected && gui->edit_mode == DT_MASKS_EDIT_FULL)
765 {
766 // we start the form dragging or rotating
767 if(gui->border_selected)
768 gui->form_rotating = TRUE;
769 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
770 gui->border_toggling = TRUE;
771 else
772 ;
773
774 // Pour la rotation: stocker position absolue du clic initial
775 // Pour le drag: stocker décalage
776 if(gui->form_rotating)
777 {
778 gui->delta[0] = gui->pos[0];
779 gui->delta[1] = gui->pos[1];
780 }
781 else
782 {
783 gui->delta[0] = gpt->points[0] - gui->pos[0];
784 gui->delta[1] = gpt->points[1] - gui->pos[1];
785 }
786 return 1;
787 }
788 }
789 }
790
791 return 0;
792}
793
794static int _ellipse_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
795 uint32_t state, dt_masks_form_t *form, int parentid,
796 dt_masks_form_gui_t *gui, int index)
797{
798
799
800
801
802
803
804 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
805
806 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
807 {
808 // we end the form dragging
809 return 1;
810 }
811 else if(gui->border_toggling && gui->edit_mode == DT_MASKS_EDIT_FULL)
812 {
813 // we get the ellipse
814 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
815 if(IS_NULL_PTR(ellipse)) return 0;
816
817 // we end the border toggling
818 gui->border_toggling = FALSE;
819
820 // toggle feathering type of border and adjust border radius accordingly
822 {
823 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
824 ellipse->border = ellipse->border * min_radius;
825 ellipse->border = CLAMP(ellipse->border, 0.001f, 1.0f);
826
827 ellipse->flags &= ~DT_MASKS_ELLIPSE_PROPORTIONAL;
828 }
829 else
830 {
831 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
832 ellipse->border = ellipse->border/min_radius;
833 ellipse->border = CLAMP(ellipse->border, 0.001f/min_radius, 1.0f/min_radius);
834
836 }
837
839 {
840 dt_conf_set_int("plugins/darkroom/spots/ellipse/flags", ellipse->flags);
841 dt_conf_set_float("plugins/darkroom/spots/ellipse/border", ellipse->border);
842 }
843 else
844 {
845 dt_conf_set_int("plugins/darkroom/masks/ellipse/flags", ellipse->flags);
846 dt_conf_set_float("plugins/darkroom/masks/ellipse/border", ellipse->border);
847 }
848
849 // we recreate the form points
850 dt_masks_gui_form_create(form, gui, index, module);
851
852 // we save the new parameters
853
854 return 1;
855 }
856 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
857 {
858 // we end the form rotating
859 gui->form_rotating = FALSE;
860 return 1;
861 }
862 else if(gui->node_dragging >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
863 {
864 // we end the node dragging
865 return 1;
866 }
867 else if(gui->source_dragging)
868 {
869 return 1;
870 }
871 return 0;
872}
873
874static int _ellipse_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
875 int parentid, dt_masks_form_gui_t *gui, int index)
876{
877 return 0;
878}
879
880static int _ellipse_events_mouse_moved(struct dt_iop_module_t *module, double x, double y,
881 double pressure, int which, dt_masks_form_t *form, int parentid,
882 dt_masks_form_gui_t *gui, int index)
883{
884 if(gui->creation)
885 {
886 // Let the cursor motion be redrawn as it moves in GUI
887 return 1;
888 }
889
890 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
891
892 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
893 if(IS_NULL_PTR(ellipse)) return 0;
894
895 // we need the reference points
896 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
897 if(IS_NULL_PTR(gpt)) return 0;
898
899 if(gui->form_dragging || gui->source_dragging)
900 {
902 // apply delta to the current mouse position
903 float pts[2];
904 dt_masks_gui_delta_to_raw_norm(dev, gui, pts);
905
906 if(gui->form_dragging)
907 {
908 ellipse->center[0] = pts[0];
909 ellipse->center[1] = pts[1];
910 }
911 else if(gui->source_dragging)
912 {
913 form->source[0] = pts[0];
914 form->source[1] = pts[1];
915 }
916
917 // we recreate the form points
918 dt_masks_gui_form_create(form, gui, index, module);
919
920 return 1;
921 }
922
923 else if(gui->node_dragging >= 1)
924 {
925 const int k = gui->node_dragging;
926
927 const float xref = gpt->points[0];
928 const float yref = gpt->points[1];
929 const float rx = gpt->points[k * 2] - xref;
930 const float ry = gpt->points[k * 2 + 1] - yref;
931 const float deltax = gui->pos[0] + gui->delta[0] - xref;
932 const float deltay = gui->pos[1] + gui->delta[1] - yref;
933
934 // we remap dx, dy to the right values, as it will be used in next movements
935 gui->delta[0] = xref - gui->pos[0];
936 gui->delta[1] = yref - gui->pos[1];
937
938 const float r = sqrtf(rx * rx + ry * ry);
939 const float d = (rx * deltax + ry * deltay) / r;
940 const float s = fmaxf(r > 0.0f ? (r + d) / r : 0.0f, 0.0f);
941
942 if(k == 1 || k == 2)
943 {
944 ellipse->radius[0] = MAX(0.0002f, ellipse->radius[0] * s);
946 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_a", ellipse->radius[0]);
947 else
948 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_a", ellipse->radius[0]);
949 }
950 else if(k == 3 || k == 4)
951 {
952 ellipse->radius[1] = MAX(0.0002f, ellipse->radius[1] * s);
954 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_b", ellipse->radius[1]);
955 else
956 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_b", ellipse->radius[1]);
957 }
958
959 // we recreate the form points
960 dt_masks_gui_form_create(form, gui, index, module);
961
962 return 1;
963 }
964
965 // rotation of the ellipse with the mouse
966 else if(gui->form_rotating)
967 {
968
969 const float origin_point[2] = { gpt->points[0], gpt->points[1] };
970 const float angle = dt_masks_rotate_with_anchor(darktable.develop, gui->pos, origin_point, gui);
971
972 _change_rotation(form, gui, module, index, angle , DT_MASKS_INCREMENT_OFFSET, 1);
973
974 // we recreate the form points
975 dt_masks_gui_form_create(form, gui, index, module);
976
977 return 1;
978 }
979
980 //if(gui->edit_mode != DT_MASKS_EDIT_FULL) return 0;
981 return 0;
982}
983
984static void _ellipse_draw_shape(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
985{
986 cairo_move_to(cr, points[10], points[11]);
987 for(int t = 6; t < points_count; t++)
988 {
989 const float x = points[t * 2];
990 const float y = points[t * 2 + 1];
991
992 cairo_line_to(cr, x, y);
993 }
994 cairo_close_path(cr);
995}
996
997static void _ellipse_draw_handles(const dt_masks_form_gui_t *gui, cairo_t *cr, const float zoom_scale,
998 dt_masks_form_gui_points_t *gpt, const int index)
999{
1000 if(IS_NULL_PTR(gpt) || IS_NULL_PTR(gpt->points) || gpt->points_count < 5) return;
1001
1002 const int selected_node = dt_masks_gui_selected_node_index(gui);
1003 const float *nodes = gpt->points;
1004 float x, y;
1005
1006 const float r = atan2f(nodes[3] - nodes[1], nodes[2] - nodes[0]);
1007 const float sinr = sinf(r);
1008 const float cosr = cosf(r);
1009
1010 for(int i = 1; i < 5; i++)
1011 {
1012 _ellipse_point_transform(nodes[0], nodes[1], nodes[i * 2], nodes[i * 2 + 1], sinr, cosr, &x, &y);
1013 const gboolean selected = (i == gui->node_hovered || i == gui->node_dragging);
1014 const gboolean action = (i == selected_node);
1015 dt_draw_node(cr, TRUE, action, selected, zoom_scale, x, y);
1016 }
1017}
1018
1019static void _ellipse_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index,
1020 int num_points)
1021{
1022 // add a preview when creating an ellipse
1023 // in creation mode
1024 if(gui->creation)
1025 {
1028 if(_ellipse_get_creation_preview(form, gui, &preview)) return;
1029
1030 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.points, preview.points_count,
1031 preview.border, preview.border_count,
1032 &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1033 CAIRO_LINE_CAP_ROUND, TRUE, FALSE);
1034
1035
1036 // draw a cross where the source will be created
1037 if(dt_masks_form_is_clone(form))
1038 {
1039 dt_masks_draw_source_preview(cr, zoom_scale, gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1], FALSE);
1040 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.source_points, preview.points_count,
1041 NULL, 0, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1042 CAIRO_LINE_CAP_ROUND, FALSE, TRUE);
1043 }
1044
1046
1047 return;
1048 } // gui->creation
1049
1050 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1051 if(IS_NULL_PTR(gpt)) return;
1052
1053 // we draw the main shape
1054 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1055 if(gpt->points && gpt->points_count > 5)
1056 dt_draw_shape_lines(DT_MASKS_NO_DASH, FALSE, cr, num_points, selected, zoom_scale, gpt->points,
1057 gpt->points_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT);
1058
1059 if(gui->group_selected == index)
1060 {
1061 // we draw the borders
1062 if(gpt->border && gpt->border_count > 5)
1063 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, num_points, (gui->border_selected), zoom_scale, gpt->border,
1064 gpt->border_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_ROUND);
1065
1066 // draw handles
1067 _ellipse_draw_handles(gui, cr, zoom_scale, gpt, index);
1068 }
1069
1070 //draw the center point
1071 if(gui->group_selected == index && gui->pivot_selected && gpt->points && gpt->points_count > 0)
1072 dt_draw_node(cr, FALSE, FALSE, (gui->form_rotating), zoom_scale, gpt->points[0], gpt->points[1]);
1073
1074 // draw the source if any
1075 if(gpt->source && gpt->source_count > 10 && gpt->points && gpt->points_count > 0)
1076 {
1077 dt_masks_gui_center_point_t center_pt = { .main = { gpt->points[0], gpt->points[1] },
1078 .source = { gpt->source[0], gpt->source[1] }};
1079 dt_masks_draw_source(cr, gui, index, num_points, zoom_scale, &center_pt, &dt_masks_functions_ellipse.draw_shape);
1080 }
1081
1082}
1083
1084static void _bounding_box(const float *const points, int num_points, int *width, int *height, int *posx, int *posy)
1085{
1086 // search for min/max X and Y coordinates
1087 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
1088 for(int i = 1; i < num_points; i++) // skip point[0], which is circle's center
1089 {
1090 xmin = fminf(points[i * 2], xmin);
1091 xmax = fmaxf(points[i * 2], xmax);
1092 ymin = fminf(points[i * 2 + 1], ymin);
1093 ymax = fmaxf(points[i * 2 + 1], ymax);
1094 }
1095 // set the min/max values we found
1096 *posx = xmin;
1097 *posy = ymin;
1098 *width = (xmax - xmin);
1099 *height = (ymax - ymin);
1100}
1101
1102static void _fill_mask(const size_t numpoints, float *const bufptr, const float *const points,
1103 const float *const center, const float a, const float b, const float ta, const float tb,
1104 const float alpha, const size_t out_scale)
1105{
1106 const float a2 = a * a;
1107 const float b2 = b * b;
1108 const float ta2 = ta * ta;
1109 const float tb2 = tb * tb;
1110 const float cos_alpha = cosf(alpha);
1111 const float sin_alpha = sinf(alpha);
1112
1113 // Determine the strength of the mask for each of the distorted points. If inside the border of the ellipse,
1114 // the strength is always 1.0; if outside the falloff region, it is 0.0, and in between it falls off quadratically.
1115 // To compute this, we need to do the equivalent of projecting the vector from the center of the ellipse to the
1116 // given point until it intersect the ellipse and the outer edge of the falloff, respectively. The ellipse can
1117 // be rotated, but we can compensate for that by applying a rotation matrix for the same rotation in the opposite
1118 // direction before projecting the vector.
1119 __OMP_PARALLEL_FOR_SIMD__(if(numpoints > 50000) aligned(points, bufptr : 64))
1120 for(size_t i = 0; i < numpoints; i++)
1121 {
1122 const float x = points[2 * i] - center[0];
1123 const float y = points[2 * i + 1] - center[1];
1124 // find the square of the distance from the center
1125 const float l2 = x * x + y * y;
1126 const float l = sqrtf(l2);
1127 // normalize the point's coordinate to form a unit vector, taking care not to divide by zero
1128 const float x_norm = l ? x / l : 0.0f;
1129 const float y_norm = l ? y / l : 1.0f; // ensure we don't get 0 for both sine and cosine below
1130 // apply the rotation matrix
1131 const float x_rot = x_norm * cos_alpha + y_norm * sin_alpha;
1132 const float y_rot = -x_norm * sin_alpha + y_norm * cos_alpha;
1133 // at this point, x_rot = cos(v) and y_rot = sin(v) since they are on the unit circle; we need the squared values
1134 const float cosv2 = x_rot * x_rot;
1135 const float sinv2 = y_rot * y_rot;
1136
1137 // project the rotated unit vector out to the ellipse and the outer border
1138 const float radius2 = a2 * b2 / (a2 * sinv2 + b2 * cosv2);
1139 const float total2 = ta2 * tb2 / (ta2 * sinv2 + tb2 * cosv2);
1140
1141 // quadratic falloff between the ellipses's radius and the radius of the outside of the feathering
1142 // ratio = 0.0 at the outer border, >= 1.0 within the ellipse, negative outside the falloff
1143 const float ratio = (total2 - l2) / (total2 - radius2);
1144 // enforce 1.0 inside the ellipse and 0.0 outside the feathering
1145 const float f = CLIP(ratio);
1146 bufptr[i << out_scale] = f * f;
1147 }
1148}
1149
1150static float *const _ellipse_points_to_transform(const float center_x, const float center_y, const float dim1, const float dim2,
1151 const float rotation, const float wd, const float ht, size_t *point_count)
1152{
1153
1154 const float v1 = ((rotation) / 180.0f) * M_PI;
1155 const float v2 = ((rotation - 90.0f) / 180.0f) * M_PI;
1156 float a = 0.0f, b = 0.0f, v = 0.0f;
1157
1158 if(dim1 >= dim2)
1159 {
1160 a = dim1;
1161 b = dim2;
1162 v = v1;
1163 }
1164 else
1165 {
1166 a = dim2;
1167 b = dim1;
1168 v = v2;
1169 }
1170
1171 const float sinv = sinf(v);
1172 const float cosv = cosf(v);
1173
1174 // how many points do we need ?
1175 const float lambda = (a - b) / (a + b);
1176 const int l = (int)(M_PI * (a + b)
1177 * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1178
1179 // buffer allocation
1180 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t) 2 * (l + 5), 0);
1181 if(IS_NULL_PTR(points))
1182 return NULL;
1183 *point_count = l + 5;
1184
1185 // now we set the points - first the center
1186 float center[2] = { center_x, center_y };
1188 const float x = points[0] = center[0];
1189 const float y = points[1] = center[1];
1190 // then the control node points (ends of semimajor/semiminor axes)
1191 points[2] = x + a * cosf(v);
1192 points[3] = y + a * sinf(v);
1193 points[4] = x - a * cosf(v);
1194 points[5] = y - a * sinf(v);
1195 points[6] = x + b * cosf(v - M_PI / 2.0f);
1196 points[7] = y + b * sinf(v - M_PI / 2.0f);
1197 points[8] = x - b * cosf(v - M_PI / 2.0f);
1198 points[9] = y - b * sinf(v - M_PI / 2.0f);
1199 // and finally the regularly-spaced points on the circumference
1200 for(int i = 5; i < l + 5; i++)
1201 {
1202 float alpha = (i - 5) * 2.0 * M_PI / (float)l;
1203 points[i * 2] = x + a * cosf(alpha) * cosv - b * sinf(alpha) * sinv;
1204 points[i * 2 + 1] = y + a * cosf(alpha) * sinv + b * sinf(alpha) * cosv;
1205 }
1206 return points;
1207}
1208
1211 dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
1212{
1213 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1214 // we get the ellipse values
1215 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1216 if(IS_NULL_PTR(ellipse)) return 0;
1217 const float wd = pipe->iwidth, ht = pipe->iheight;
1218 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1219 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1220 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1221
1222 // next we compute the points to be transformed
1223 size_t point_count = 0;
1224 float *const restrict points
1225 = _ellipse_points_to_transform(form->source[0], form->source[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1226 if (IS_NULL_PTR(points))
1227 return 1;
1228
1229 // and we transform them with all distorted modules
1230 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1231 {
1233 return 1;
1234 }
1235
1236 // finally, find the extreme left/right and top/bottom points
1237 _bounding_box(points, point_count, width, height, posx, posy);
1239 return 0;
1240}
1241
1242static int _ellipse_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1243 const dt_dev_pixelpipe_iop_t *const piece,
1244 dt_masks_form_t *const form,
1245 int *width, int *height, int *posx, int *posy)
1246{
1247 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1248 // we get the ellipse values
1249 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1250 if(IS_NULL_PTR(ellipse)) return 0;
1251 const float wd = pipe->iwidth, ht = pipe->iheight;
1252 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1253 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1254 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1255
1256 // next we compute the points to be transformed
1257 size_t point_count = 0;
1258 float *const restrict points
1259 = _ellipse_points_to_transform(ellipse->center[0], ellipse->center[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1260 if (IS_NULL_PTR(points))
1261 return 1;
1262
1263 // and we transform them with all distorted modules
1264 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1265 {
1267 return 1;
1268 }
1269
1270 // finally, find the extreme left/right and top/bottom points
1271 _bounding_box(points, point_count, width, height, posx, posy);
1273 return 0;
1274}
1275
1276static int _ellipse_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1277 const dt_dev_pixelpipe_iop_t *const piece,
1278 dt_masks_form_t *const form,
1279 float **buffer, int *width, int *height, int *posx, int *posy)
1280{
1281 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1282 double start2 = 0.0;
1284
1285 // we get the area
1286 if(_ellipse_get_area(module, pipe, piece, form, width, height, posx, posy) != 0) return 1;
1287
1289 {
1290 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse area took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1291 start2 = dt_get_wtime();
1292 }
1293
1294 // we get the ellipse values
1295 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1296 if(IS_NULL_PTR(ellipse)) return 0;
1297 // we create a buffer of points with all points in the area
1298 int w = *width, h = *height;
1299 const int posx_ = *posx;
1300 const int posy_ = *posy;
1301 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * w * h, 0);
1302 if(IS_NULL_PTR(points))
1303 return 1;
1304 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)w * h > 50000))
1305 for(int i = 0; i < h; i++)
1306 for(int j = 0; j < w; j++)
1307 {
1308 points[(i * w + j) * 2] = (j + posx_);
1309 points[(i * w + j) * 2 + 1] = (i + posy_);
1310 }
1311
1313 {
1314 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1315 start2 = dt_get_wtime();
1316 }
1317
1318 // we back transform all this points
1319 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)w * h))
1320 {
1322 return 1;
1323 }
1324
1326 {
1327 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse transform took %0.04f sec\n", form->name,
1328 dt_get_wtime() - start2);
1329 start2 = dt_get_wtime();
1330 }
1331
1332 // we allocate the buffer
1333 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
1334 if(IS_NULL_PTR(*buffer))
1335 {
1337 return 1;
1338 }
1339
1340 // we populate the buffer
1341 const int wi = pipe->iwidth, hi = pipe->iheight;
1342 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1343 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1344 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),
1345 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1346
1347 float a = 0.0F, b = 0.0F, ta = 0.0F, tb = 0.0F, alpha = 0.0F;
1348
1349 if(radius[0] >= radius[1])
1350 {
1351 a = radius[0];
1352 b = radius[1];
1353 ta = total[0];
1354 tb = total[1];
1355 alpha = (ellipse->rotation / 180.0f) * M_PI;
1356 }
1357 else
1358 {
1359 a = radius[1];
1360 b = radius[0];
1361 ta = total[1];
1362 tb = total[0];
1363 alpha = ((ellipse->rotation - 90.0f) / 180.0f) * M_PI;
1364 }
1365
1366 float *const bufptr = *buffer;
1367
1368 _fill_mask((size_t)(h)*w, bufptr, points, center, a, b, ta, tb, alpha, 0);
1369
1371
1373 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1374
1375 return 0;
1376}
1377
1378static int _ellipse_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1379 const dt_dev_pixelpipe_iop_t *const piece,
1380 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer)
1381{
1382 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1383
1384 double start1 = 0.0;
1385 double start2 = start1;
1386 if(darktable.unmuted & DT_DEBUG_PERF) start2 = start1 = dt_get_wtime();
1387
1388 // we get the ellipse parameters
1389 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1390 if(IS_NULL_PTR(ellipse)) return 0;
1391 const int wi = pipe->iwidth, hi = pipe->iheight;
1392 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1393 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1394 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),
1395 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1396
1397 const float a = radius[0];
1398 const float b = radius[1];
1399 const float ta = total[0];
1400 const float tb = total[1];
1401 const float alpha = (ellipse->rotation / 180.0f) * M_PI;
1402 const float cosa = cosf(alpha);
1403 const float sina = sinf(alpha);
1404
1405 // we create a buffer of grid points for later interpolation: higher speed and reduced memory footprint;
1406 // we match size of buffer to bounding box around the shape
1407 const int w = roi->width;
1408 const int h = roi->height;
1409 const int px = roi->x;
1410 const int py = roi->y;
1411 const float iscale = 1.0f / roi->scale;
1412 // scale dependent resolution: when zoomed in (scale > 1), use finer grid to avoid interpolation holes
1413 const float grid_scale = 1.0f / MAX(roi->scale, 1e-6f);
1414 const int grid = CLAMP((10.0f * grid_scale + 2.0f) / 3.0f, 1, 4);
1415 const int gw = (w + grid - 1) / grid + 1; // grid dimension of total roi
1416 const int gh = (h + grid - 1) / grid + 1; // grid dimension of total roi
1417
1419 {
1420 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse init took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1421 start2 = dt_get_wtime();
1422 }
1423
1424 // we look at the outer line of the shape - no effects outside of this ellipse;
1425 // we need many points as we do not know how the ellipse might get distorted in the pixelpipe
1426 const float lambda = (ta - tb) / (ta + tb);
1427 const int l = (int)(M_PI * (ta + tb) * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1428 const size_t ellpts = MIN(360, l);
1429 float *ell = dt_pixelpipe_cache_alloc_align_float_cache(ellpts * 2, 0);
1430 if(IS_NULL_PTR(ell)) return 1;
1431 __OMP_PARALLEL_FOR__(if(ellpts > 100))
1432 for(int n = 0; n < ellpts; n++)
1433 {
1434 const float phi = (2.0f * M_PI * n) / ellpts;
1435 const float cosp = cosf(phi);
1436 const float sinp = sinf(phi);
1437 ell[2 * n] = center[0] + ta * cosa * cosp - tb * sina * sinp;
1438 ell[2 * n + 1] = center[1] + ta * sina * cosp + tb * cosa * sinp;
1439 }
1440
1442 {
1443 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1444 start2 = dt_get_wtime();
1445 }
1446
1447 // we transform the outline from input image coordinates to current position in pixelpipe
1449 ellpts))
1450 {
1452 return 1;
1453 }
1454
1456 {
1457 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline transform took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1458 start2 = dt_get_wtime();
1459 }
1460
1461 // we get the min/max values ...
1462 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
1463 for(int n = 0; n < ellpts; n++)
1464 {
1465 // just in case that transform throws surprising values
1466 if(!(isnormal(ell[2 * n]) && isnormal(ell[2 * n + 1]))) continue;
1467
1468 xmin = MIN(xmin, ell[2 * n]);
1469 xmax = MAX(xmax, ell[2 * n]);
1470 ymin = MIN(ymin, ell[2 * n + 1]);
1471 ymax = MAX(ymax, ell[2 * n + 1]);
1472 }
1473
1474#if 0
1475 printf("xmin %f, xmax %f, ymin %f, ymax %f\n", xmin, xmax, ymin, ymax);
1476 printf("wi %d, hi %d, iscale %f\n", wi, hi, iscale);
1477 printf("w %d, h %d, px %d, py %d\n", w, h, px, py);
1478#endif
1479
1480 // ... and calculate the bounding box with a bit of reserve
1481 const int bbxm = CLAMP((int)floorf(xmin / iscale - px) / grid - 1, 0, gw - 1);
1482 const int bbXM = CLAMP((int)ceilf(xmax / iscale - px) / grid + 2, 0, gw - 1);
1483 const int bbym = CLAMP((int)floorf(ymin / iscale - py) / grid - 1, 0, gh - 1);
1484 const int bbYM = CLAMP((int)ceilf(ymax / iscale - py) / grid + 2, 0, gh - 1);
1485 const int bbw = bbXM - bbxm + 1;
1486 const int bbh = bbYM - bbym + 1;
1487
1488#if 0
1489 printf("bbxm %d, bbXM %d, bbym %d, bbYM %d\n", bbxm, bbXM, bbym, bbYM);
1490 printf("gw %d, gh %d, bbw %d, bbh %d\n", gw, gh, bbw, bbh);
1491#endif
1492
1494
1496 {
1497 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse bounding box took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1498 start2 = dt_get_wtime();
1499 }
1500
1501 // check if there is anything to do at all;
1502 // only if width and height of bounding box is 2 or greater the shape lies inside of roi and requires action
1503 if(bbw <= 1 || bbh <= 1)
1504 return 0;
1505
1506 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * bbw * bbh, 0);
1507 if(IS_NULL_PTR(points)) return 1;
1508
1509 // we populate the grid points in module coordinates
1510 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)bbw * bbh > 50000))
1511 for(int j = bbym; j <= bbYM; j++)
1512 for(int i = bbxm; i <= bbXM; i++)
1513 {
1514 const size_t index = (size_t)(j - bbym) * bbw + i - bbxm;
1515 points[index * 2] = (grid * i + px) * iscale;
1516 points[index * 2 + 1] = (grid * j + py) * iscale;
1517 }
1518
1520 {
1521 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse grid took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1522 start2 = dt_get_wtime();
1523 }
1524
1525 // we back transform all these points to the input image coordinates
1527 (size_t)bbw * bbh))
1528 {
1530 return 1;
1531 }
1532
1534 {
1535 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse transform took %0.04f sec\n", form->name,
1536 dt_get_wtime() - start2);
1537 start2 = dt_get_wtime();
1538 }
1539
1540 // we calculate the mask values at the transformed points;
1541 // re-use the points array for results; this requires out_scale==1 to double the offsets at which they are stored
1542 _fill_mask((size_t)(bbh)*bbw, points, points, center, a, b, ta, tb, alpha, 1);
1543
1545 {
1546 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name,
1547 dt_get_wtime() - start2);
1548 start2 = dt_get_wtime();
1549 }
1550
1551 // we fill the pre-initialized output buffer by interpolation;
1552 // we only need to take the contents of our bounding box into account
1553 const int endx = MIN(w, bbXM * grid);
1554 const int endy = MIN(h, bbYM * grid);
1555 const float inv_grid2 = 1.0f / (grid * grid);
1556 float w0[4], w1[4];
1557 for(int i = 0; i < grid; i++)
1558 {
1559 w0[i] = (float)(grid - i);
1560 w1[i] = (float)i;
1561 }
1562 __OMP_PARALLEL_FOR__(if((size_t)(endy - bbym * grid) * (size_t)(endx - bbxm * grid) > 50000))
1563 for(int j = bbym * grid; j < endy; j++)
1564 {
1565 const int jj = j % grid;
1566 const int mj = j / grid - bbym;
1567 const float wj0 = w0[jj];
1568 const float wj1 = w1[jj];
1569 const size_t row_base = (size_t)mj * bbw;
1570 float *const row = buffer + (size_t)j * w;
1571 int ii = 0;
1572 int mi = 0;
1573 for(int i = bbxm * grid; i < endx; i++)
1574 {
1575 const size_t mindex = row_base + mi;
1576 const float wii0 = w0[ii];
1577 const float wii1 = w1[ii];
1578 row[i] = (points[mindex * 2] * wii0 * wj0
1579 + points[(mindex + 1) * 2] * wii1 * wj0
1580 + points[(mindex + bbw) * 2] * wii0 * wj1
1581 + points[(mindex + bbw + 1) * 2] * wii1 * wj1) * inv_grid2;
1582 ii++;
1583 if(ii == grid)
1584 {
1585 ii = 0;
1586 mi++;
1587 }
1588 }
1589 }
1590
1592
1594 {
1595 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1596 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse total render took %0.04f sec\n", form->name,
1597 dt_get_wtime() - start1);
1598 }
1599 return 0;
1600}
1601
1602static void _ellipse_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1603{
1604 snprintf(form->name, sizeof(form->name), _("ellipse #%d"), (int)nb);
1605}
1606
1607static void _ellipse_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
1608{
1609 // unused arg, keep compiler from complaining
1611}
1612
1613static void _ellipse_initial_source_pos(const float iwd, const float iht, float *x, float *y)
1614{
1615
1616
1617 const float radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1618 const float radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1619 float offset[2] = { radius_a, -radius_b };
1621 *x = offset[0];
1622 *y = offset[1];
1623}
1624
1625static void _ellipse_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1626 const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
1627{
1628 if(gui->creation)
1629 g_snprintf(msgbuf, msgbuf_len,
1630 _("<b>Size</b>: scroll, <b>Hardness</b>: shift+scroll\n"
1631 "<b>Rotate</b>: ctrl+shift+scroll, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1632 else if(gui->form_selected || gui->border_selected)
1633 g_snprintf(msgbuf, msgbuf_len,
1634 _("<b>Hardness mode</b>: shift+click, <b>Size</b>: scroll\n"
1635 "<b>Hardness</b>: shift+scroll, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1636}
1637
1639{
1640 int flags = -1;
1641 float radius_a = 0.0f;
1642 float radius_b = 0.0f;
1643 float border = 0.0f;
1645 {
1646 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/ellipse/rotation", 0.0f, 360.f);
1648 radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1649 radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1650 border = dt_conf_get_float("plugins/darkroom/spots/ellipse/border");
1651 }
1652 else
1653 {
1654 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/ellipse/rotation", 0.0f, 360.f);
1656 radius_a = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_a");
1657 radius_b = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_b");
1658 border = dt_conf_get_float("plugins/darkroom/masks/ellipse/border");
1659 }
1660
1661 const float ratio = radius_a / radius_b;
1662
1663 if(radius_a > radius_b)
1664 {
1665 radius_a = CLAMPS(radius_a, 0.001f, 0.5f);
1666 radius_b = radius_a / ratio;
1667 }
1668 else
1669 {
1670 radius_b = CLAMPS(radius_b, 0.001f, 0.5);
1671 radius_a = ratio * radius_b;
1672 }
1673
1674 const float reference = (flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? 1.0f / fmin(radius_a, radius_b) : 1.0f);
1675 border = CLAMPS(border, 0.001f * reference, reference);
1676
1678 {
1679 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_a", radius_a, 0.001f, 0.5f)
1680 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1681 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/border", border, 0.001f, reference);
1682 }
1683 else
1684 {
1685 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_a", radius_a, 0.001f, 0.5f);
1686 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1687 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/border", border, 0.001f, reference);
1688 }
1689}
1690
1691// The function table for ellipses. This must be public, i.e. no "static" keyword.
1694 .sanitize_config = _ellipse_sanitize_config,
1695 .set_form_name = _ellipse_set_form_name,
1696 .set_hint_message = _ellipse_set_hint_message,
1697 .duplicate_points = _ellipse_duplicate_points,
1698 .initial_source_pos = _ellipse_initial_source_pos,
1699 .get_distance = _ellipse_get_distance,
1700 .get_points = _ellipse_get_points,
1701 .get_points_border = _ellipse_get_points_border,
1702 .get_mask = _ellipse_get_mask,
1703 .get_mask_roi = _ellipse_get_mask_roi,
1704 .get_area = _ellipse_get_area,
1705 .get_source_area = _ellipse_get_source_area,
1706 .get_gravity_center = _ellipse_get_gravity_center,
1707 .get_interaction_value = _ellipse_get_interaction_value,
1708 .set_interaction_value = _ellipse_set_interaction_value,
1709 .update_hover = _find_closest_handle,
1710 .mouse_moved = _ellipse_events_mouse_moved,
1711 .mouse_scrolled = _ellipse_events_mouse_scrolled,
1712 .button_pressed = _ellipse_events_button_pressed,
1713 .button_released = _ellipse_events_button_released,
1714 .key_pressed = _ellipse_events_key_pressed,
1715 .post_expose = _ellipse_events_post_expose,
1716 .draw_shape = _ellipse_draw_shape
1717};
1718
1719// clang-format off
1720// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1721// vim: shiftwidth=2 expandtab tabstop=2 cindent
1722// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1723// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
const dt_aligned_pixel_t f
static const int row
int type
#define DT_CONF_SET_SANITIZED_FLOAT(name, val, min, max)
Definition conf.h:135
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
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)
float dt_conf_get_and_sanitize_float(const char *name, float min, float max)
void dt_toast_log(const char *msg,...)
Definition control.c:808
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_PERF
Definition darktable.h:719
@ DT_DEBUG_MASKS
Definition darktable.h:727
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static double dt_get_wtime(void)
Definition darktable.h:914
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define M_PI_F
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1525
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1109
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:1557
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:1586
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:106
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_MASKS_DASH_STICK
Definition draw.h:94
@ DT_MASKS_NO_DASH
Definition draw.h:93
static void dt_draw_shape_lines(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap)
Draw the lines of a mask shape.
Definition draw.h:734
static 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:597
static int _ellipse_get_points_border(dt_develop_t *dev, struct dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, int source, const dt_iop_module_t *module)
Definition ellipse.c:459
static gboolean _ellipse_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
Definition ellipse.c:584
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
Definition ellipse.c:530
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:880
#define HARDNESS_MIN
Definition ellipse.c:46
static int _init_hardness(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:538
static float *const _ellipse_points_to_transform(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:1150
static void _ellipse_sanitize_config(dt_masks_type_t type)
Definition ellipse.c:1638
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:997
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:58
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:874
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:497
static void _ellipse_initial_source_pos(const float iwd, const float iht, float *x, float *y)
Definition ellipse.c:1613
static int _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:1276
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, int *inside_source, float *dist, void *user_data)
Ellipse-specific inside/border hit testing adapter.
Definition ellipse.c:511
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:280
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:1607
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:553
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:1102
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:637
#define HARDNESS_MAX
Definition ellipse.c:47
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:257
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:524
static void _ellipse_get_creation_values(const dt_masks_form_t *form, dt_masks_ellipse_creation_values_t *values)
Definition ellipse.c:242
static float _ellipse_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition ellipse.c:567
static int _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)
Definition ellipse.c:1378
static int _change_hardness(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:620
static void _ellipse_draw_shape(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition ellipse.c:984
static int _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:1242
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, int *inside_source, float *dist)
Definition ellipse.c:177
static void _ellipse_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form, const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
Definition ellipse.c:1625
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:717
static int _init_size(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:545
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:1019
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:438
static void _ellipse_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition ellipse.c:1602
const dt_masks_functions_t dt_masks_functions_ellipse
Definition ellipse.c:1692
static int _ellipse_point_close_to_path(float x, float y, float mouse_radius, float *points, int points_count)
Definition ellipse.c:130
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:600
static float * _points_to_transform(float xx, float yy, float radius_a, float radius_b, float rotation, float wd, float ht, int *points_count)
Definition ellipse.c:327
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:794
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:660
static int _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:1209
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:685
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:383
static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition ellipse.c:560
static void _bounding_box(const float *const points, int num_points, int *width, int *height, int *posx, int *posy)
Definition ellipse.c:1084
static const float x
const float l2
const int t
const float v
#define w1
Definition lmmse.c:59
float *const restrict const size_t k
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks.h:534
void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, float *px, float *py, const int adding)
Compute preview-space source position for drawing the clone indicator.
void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module)
@ DT_MASKS_EDIT_FULL
Definition masks.h:202
static void dt_masks_draw_source_preview(cairo_t *cr, const float zoom_scale, dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, const int adding)
Definition masks.h:1111
int dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up, const int flow)
int dt_masks_point_in_form_exact(const float *pts, int num_pts, const float *points, int points_start, int points_count)
Check whether any 2D point in pts[] lies inside the form points[].
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
float dt_masks_get_set_conf_value(dt_masks_form_t *form, char *feature, float new_value, float v_min, float v_max, dt_masks_increment_t increment, const int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *gui, const int index, const int nb, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_NON_CLONE
Definition masks.h:138
@ DT_MASKS_CLONE
Definition masks.h:134
dt_masks_interaction_t
Definition masks.h:302
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:305
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:304
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:603
@ DT_MASKS_ELLIPSE_PROPORTIONAL
Definition masks.h:219
@ DT_MASKS_ELLIPSE_EQUIDISTANT
Definition masks.h:218
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks.h:807
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
static gboolean dt_masks_form_is_clone(const dt_masks_form_t *form)
Definition masks.h:641
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *form, const char *feature, float amount, float v_min, float v_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
float dt_masks_rotate_with_anchor(dt_develop_t *dev, const float anchor[2], const float center[2], dt_masks_form_gui_t *gui)
Rotate a mask shape around its center. WARNING: gui->delta will be updated with the new position afte...
dt_masks_increment_t
Definition masks.h:193
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:195
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:196
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *gui, dt_masks_form_t *form)
Initialize the clone source position based on current GUI state.
static void dt_masks_draw_preview_shape(cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, void(*const *draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source), const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks.h:773
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Shared selection logic for node/handle/segment hit testing.
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:611
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state)
Decide initial source positioning mode for clone masks.
static gboolean dt_masks_form_uses_spot_defaults(const dt_masks_form_t *form)
Definition masks.h:636
static void dt_masks_reset_source(dt_masks_form_t *form)
Definition masks.h:646
#define CLAMPS(A, L, H)
Definition math.h:76
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define CLIP(x)
Definition math.h:81
#define M_PI
Definition math.h:45
float iscale
Definition mipmap_cache.c:2
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
static float gh(const float f)
const float uint32_t state[4]
const float r
int32_t unmuted
Definition darktable.h:760
struct dt_develop_t * develop
Definition darktable.h:770
int32_t raw_height
Definition develop.h:228
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
struct dt_develop_t::@17 roi
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
gboolean border_toggling
Definition masks.h:488
gboolean source_selected
Definition masks.h:478
gboolean source_dragging
Definition masks.h:486
dt_masks_edit_mode_t edit_mode
Definition masks.h:463
dt_iop_module_t * creation_module
Definition masks.h:505
gboolean form_dragging
Definition masks.h:485
gboolean creation
Definition masks.h:503
gboolean form_selected
Definition masks.h:476
gboolean form_rotating
Definition masks.h:487
gboolean border_selected
Definition masks.h:477
gboolean pivot_selected
Definition masks.h:479
float delta[2]
Definition masks.h:455
dt_masks_type_t type
Definition masks.h:378
float source[2]
Definition masks.h:385
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:368
struct dt_masks_gui_center_point_t::@34 main
dt_masks_ellipse_flags_t flags
Definition masks.h:244
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29