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;
580 return ellipse->rotation;
581 default:
582 return NAN;
583 }
584}
585
586static gboolean _ellipse_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
587{
588 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
589 const dt_masks_node_ellipse_t *ellipse = (const dt_masks_node_ellipse_t *)(form->points)->data;
590 if(IS_NULL_PTR(ellipse)) return FALSE;
591 center[0] = ellipse->center[0];
592 center[1] = ellipse->center[1];
593 *area = M_PI_F * ellipse->radius[0] * ellipse->radius[1];
594 return TRUE;
595}
596
597static int _change_hardness(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);
599static int _change_size(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
600 int index, const float amount, const dt_masks_increment_t increment, const int flow);
601static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
602 int index, const float amount, const dt_masks_increment_t increment, const int flow);
603
605 dt_masks_increment_t increment, int flow,
606 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
607{
608 if(IS_NULL_PTR(form)) return NAN;
609 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
610 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
611 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
612 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
613
614 switch(interaction)
615 {
617 if(!_change_size(form, gui, module, index, value, increment, flow)) return NAN;
618 return _ellipse_get_interaction_value(form, interaction);
620 if(!_change_hardness(form, gui, module, index, value, increment, flow)) return NAN;
621 return _ellipse_get_interaction_value(form, interaction);
623 if(!_change_rotation(form, gui, module, index, value, increment, flow)) return NAN;
624 return _ellipse_get_interaction_value(form, interaction);
625 default:
626 return NAN;
627 }
628}
629
630static 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)
631{
632 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
633 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
634 if(IS_NULL_PTR(ellipse)) return 0;
635
636 ellipse->border = CLAMPF(dt_masks_apply_increment(ellipse->border, amount, increment, flow),
638
639 _init_hardness(form, amount, increment, flow);
640
641 // we recreate the form points
642 dt_masks_gui_form_create(form, gui, index, module);
643
644 return 1;
645}
646
647static 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)
648{
649 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
650 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
651 if(IS_NULL_PTR(ellipse)) return 0;
652
653 // Sanitize
654 // do not exceed upper limit of 1.0 and lower limit of 0.004
655 if(amount > 1.0f && (ellipse->border > 1.0f ))
656 return 1;
657
658 // adjust the sizes directly to avoid re-querying group/form
659 ellipse->radius[0] = dt_masks_apply_increment(ellipse->radius[0], amount, increment, flow);
660 ellipse->radius[1] = dt_masks_apply_increment(ellipse->radius[1], amount, increment, flow);
661
662 _init_size(form, amount, DT_MASKS_INCREMENT_SCALE, flow);
663
664 // we recreate the form points
665 dt_masks_gui_form_create(form, gui, index, module);
666
667 return 1;
668}
669
670static 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)
671{
672 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
673 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)(form->points)->data;
674 if(IS_NULL_PTR(ellipse)) return 0;
675
676 // Rotation
677 int flow_increased = (flow > 1) ? (flow - 1) * 5 : flow;
678 ellipse->rotation = dt_masks_apply_increment(ellipse->rotation, amount, increment, flow_increased);
679
680 // Ensure the rotation value warps within the interval [0, 360)
681 if(ellipse->rotation > 360.f) ellipse->rotation = fmodf(ellipse->rotation, 360.f);
682 else if(ellipse->rotation < 0.f) ellipse->rotation = 360.f - fmodf(-ellipse->rotation, 360.f);
683
684 _init_rotation(form, amount, DT_MASKS_INCREMENT_OFFSET, flow);
685
686 // we recreate the form points
687 dt_masks_gui_form_create(form, gui, index, module);
688
689 return 1;
690}
691
692/* Shape handlers receive widget-space coordinates, while normalized output-image
693 * coordinates come from `gui->rel_pos` and absolute output-image
694 * coordinates come from `gui->pos`. */
695static int _ellipse_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
696 uint32_t state, dt_masks_form_t *form, int parentid,
697 dt_masks_form_gui_t *gui, int index,
698 dt_masks_interaction_t interaction)
699{
700 // add a preview when creating an ellipse
701 if(gui->creation)
702 {
703 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
704 return _init_rotation(form, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
705 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
706 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
707 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
708 return _init_hardness(form, (up ? 1.03f : 0.97f), DT_MASKS_INCREMENT_SCALE, flow);
709 else
710 return _init_size(form, (up ? 1.03f :0.97f), DT_MASKS_INCREMENT_SCALE, flow);
711 }
712 else if(gui->form_selected)
713 {
714 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
715 return _change_rotation(form, gui, module, index, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
716 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
717 return dt_masks_form_change_opacity(form, parentid, up, flow);
718 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
719 return _change_hardness(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
720 else
721 return _change_size(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
722 }
723
724 return 0;
725}
726
727static int _ellipse_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
728 double pressure, int which, int type, uint32_t state,
729 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui,
730 int index)
731{
732 if(gui->creation && which == 1
733 && ((dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK)) || dt_modifier_is(state, GDK_SHIFT_MASK)))
734 {
735 // set some absolute or relative position for the source of the clone mask
737 return 1;
738 }
739
740 else if(which == 1)
741 {
742 if(gui->creation)
743 {
744 dt_iop_module_t *crea_module = gui->creation_module;
745 // we create the ellipse
747 if(IS_NULL_PTR(ellipse)) return 0;
748 _ellipse_init_new(form, gui, ellipse);
749 form->points = g_list_append(form->points, ellipse);
750 dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui);
751
752 return 1;
753 }
754
755 else // creation is false
756 {
757 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
758 if(IS_NULL_PTR(gpt)) return 0;
759
761 {
762 // we start the source dragging
763 gui->delta[0] = gpt->source[0] - gui->pos[0];
764 gui->delta[1] = gpt->source[1] - gui->pos[1];
765 return 1;
766 }
767 else if(gui->node_hovered >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
768 {
769 // we start the point dragging
770 gui->delta[0] = gpt->points[0] - gui->pos[0];
771 gui->delta[1] = gpt->points[1] - gui->pos[1];
772 return 1;
773 }
774 else if(gui->form_selected && gui->edit_mode == DT_MASKS_EDIT_FULL)
775 {
776 // we start the form dragging or rotating
777 if(gui->border_selected)
778 gui->form_rotating = TRUE;
779 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
780 gui->border_toggling = TRUE;
781 else
782 ;
783
784 // Pour la rotation: stocker position absolue du clic initial
785 // Pour le drag: stocker décalage
786 if(gui->form_rotating)
787 {
788 gui->delta[0] = gui->pos[0];
789 gui->delta[1] = gui->pos[1];
790 }
791 else
792 {
793 gui->delta[0] = gpt->points[0] - gui->pos[0];
794 gui->delta[1] = gpt->points[1] - gui->pos[1];
795 }
796 return 1;
797 }
798 }
799 }
800
801 return 0;
802}
803
804static int _ellipse_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
805 uint32_t state, dt_masks_form_t *form, int parentid,
806 dt_masks_form_gui_t *gui, int index)
807{
808
809
810
811
812
813
814 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
815
816 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
817 {
818 // we end the form dragging
819 return 1;
820 }
821 else if(gui->border_toggling && gui->edit_mode == DT_MASKS_EDIT_FULL)
822 {
823 // we get the ellipse
824 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
825 if(IS_NULL_PTR(ellipse)) return 0;
826
827 // we end the border toggling
828 gui->border_toggling = FALSE;
829
830 // toggle feathering type of border and adjust border radius accordingly
832 {
833 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
834 ellipse->border = ellipse->border * min_radius;
835 ellipse->border = CLAMP(ellipse->border, 0.001f, 1.0f);
836
837 ellipse->flags &= ~DT_MASKS_ELLIPSE_PROPORTIONAL;
838 }
839 else
840 {
841 const float min_radius = fmin(ellipse->radius[0], ellipse->radius[1]);
842 ellipse->border = ellipse->border/min_radius;
843 ellipse->border = CLAMP(ellipse->border, 0.001f/min_radius, 1.0f/min_radius);
844
846 }
847
849 {
850 dt_conf_set_int("plugins/darkroom/spots/ellipse/flags", ellipse->flags);
851 dt_conf_set_float("plugins/darkroom/spots/ellipse/border", ellipse->border);
852 }
853 else
854 {
855 dt_conf_set_int("plugins/darkroom/masks/ellipse/flags", ellipse->flags);
856 dt_conf_set_float("plugins/darkroom/masks/ellipse/border", ellipse->border);
857 }
858
859 // we recreate the form points
860 dt_masks_gui_form_create(form, gui, index, module);
861
862 // we save the new parameters
863
864 return 1;
865 }
866 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
867 {
868 // we end the form rotating
869 gui->form_rotating = FALSE;
870 return 1;
871 }
872 else if(gui->node_dragging >= 1 && gui->edit_mode == DT_MASKS_EDIT_FULL)
873 {
874 // we end the node dragging
875 return 1;
876 }
877 else if(gui->source_dragging)
878 {
879 return 1;
880 }
881 return 0;
882}
883
884static int _ellipse_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
885 int parentid, dt_masks_form_gui_t *gui, int index)
886{
887 return 0;
888}
889
890static int _ellipse_events_mouse_moved(struct dt_iop_module_t *module, double x, double y,
891 double pressure, int which, dt_masks_form_t *form, int parentid,
892 dt_masks_form_gui_t *gui, int index)
893{
894 if(gui->creation)
895 {
896 // Let the cursor motion be redrawn as it moves in GUI
897 return 1;
898 }
899
900 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
901
902 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
903 if(IS_NULL_PTR(ellipse)) return 0;
904
905 // we need the reference points
906 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
907 if(IS_NULL_PTR(gpt)) return 0;
908
909 if(gui->form_dragging || gui->source_dragging)
910 {
912 // apply delta to the current mouse position
913 float pts[2];
914 dt_masks_gui_delta_to_raw_norm(dev, gui, pts);
915
916 if(gui->form_dragging)
917 {
918 ellipse->center[0] = pts[0];
919 ellipse->center[1] = pts[1];
920 }
921 else if(gui->source_dragging)
922 {
923 form->source[0] = pts[0];
924 form->source[1] = pts[1];
925 }
926
927 // we recreate the form points
928 dt_masks_gui_form_create(form, gui, index, module);
929
930 return 1;
931 }
932
933 else if(gui->node_dragging >= 1)
934 {
935 const int k = gui->node_dragging;
936
937 const float xref = gpt->points[0];
938 const float yref = gpt->points[1];
939 const float rx = gpt->points[k * 2] - xref;
940 const float ry = gpt->points[k * 2 + 1] - yref;
941 const float deltax = gui->pos[0] + gui->delta[0] - xref;
942 const float deltay = gui->pos[1] + gui->delta[1] - yref;
943
944 // we remap dx, dy to the right values, as it will be used in next movements
945 gui->delta[0] = xref - gui->pos[0];
946 gui->delta[1] = yref - gui->pos[1];
947
948 const float r = sqrtf(rx * rx + ry * ry);
949 const float d = (rx * deltax + ry * deltay) / r;
950 const float s = fmaxf(r > 0.0f ? (r + d) / r : 0.0f, 0.0f);
951
952 if(k == 1 || k == 2)
953 {
954 ellipse->radius[0] = MAX(0.0002f, ellipse->radius[0] * s);
956 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_a", ellipse->radius[0]);
957 else
958 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_a", ellipse->radius[0]);
959 }
960 else if(k == 3 || k == 4)
961 {
962 ellipse->radius[1] = MAX(0.0002f, ellipse->radius[1] * s);
964 dt_conf_set_float("plugins/darkroom/spots/ellipse/radius_b", ellipse->radius[1]);
965 else
966 dt_conf_set_float("plugins/darkroom/masks/ellipse/radius_b", ellipse->radius[1]);
967 }
968
969 // we recreate the form points
970 dt_masks_gui_form_create(form, gui, index, module);
971
972 return 1;
973 }
974
975 // rotation of the ellipse with the mouse
976 else if(gui->form_rotating)
977 {
978
979 const float origin_point[2] = { gpt->points[0], gpt->points[1] };
980 const float angle = dt_masks_rotate_with_anchor(darktable.develop, gui->pos, origin_point, gui);
981
982 _change_rotation(form, gui, module, index, angle , DT_MASKS_INCREMENT_OFFSET, 1);
983
984 // we recreate the form points
985 dt_masks_gui_form_create(form, gui, index, module);
986
987 return 1;
988 }
989
990 //if(gui->edit_mode != DT_MASKS_EDIT_FULL) return 0;
991 return 0;
992}
993
994static void _ellipse_draw_shape(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
995{
996 cairo_move_to(cr, points[10], points[11]);
997 for(int t = 6; t < points_count; t++)
998 {
999 const float x = points[t * 2];
1000 const float y = points[t * 2 + 1];
1001
1002 cairo_line_to(cr, x, y);
1003 }
1004 cairo_close_path(cr);
1005}
1006
1007static void _ellipse_draw_handles(const dt_masks_form_gui_t *gui, cairo_t *cr, const float zoom_scale,
1008 dt_masks_form_gui_points_t *gpt, const int index)
1009{
1010 if(IS_NULL_PTR(gpt) || IS_NULL_PTR(gpt->points) || gpt->points_count < 5) return;
1011
1012 const int selected_node = dt_masks_gui_selected_node_index(gui);
1013 const float *nodes = gpt->points;
1014 float x, y;
1015
1016 const float r = atan2f(nodes[3] - nodes[1], nodes[2] - nodes[0]);
1017 const float sinr = sinf(r);
1018 const float cosr = cosf(r);
1019
1020 for(int i = 1; i < 5; i++)
1021 {
1022 _ellipse_point_transform(nodes[0], nodes[1], nodes[i * 2], nodes[i * 2 + 1], sinr, cosr, &x, &y);
1023 const gboolean selected = (i == gui->node_hovered || i == gui->node_dragging);
1024 const gboolean action = (i == selected_node);
1025 dt_draw_node(cr, TRUE, action, selected, zoom_scale, x, y);
1026 }
1027}
1028
1029static void _ellipse_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index,
1030 int num_points)
1031{
1032 // add a preview when creating an ellipse
1033 // in creation mode
1034 if(gui->creation)
1035 {
1038 if(_ellipse_get_creation_preview(form, gui, &preview)) return;
1039
1040 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.points, preview.points_count,
1041 preview.border, preview.border_count,
1042 &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1043 CAIRO_LINE_CAP_ROUND, TRUE, FALSE);
1044
1045
1046 // draw a cross where the source will be created
1047 if(dt_masks_form_is_clone(form))
1048 {
1049 dt_masks_draw_source_preview(cr, zoom_scale, gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1], FALSE);
1050 dt_masks_draw_preview_shape(cr, zoom_scale, num_points, preview.source_points, preview.points_count,
1051 NULL, 0, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT,
1052 CAIRO_LINE_CAP_ROUND, FALSE, TRUE);
1053 }
1054
1056
1057 return;
1058 } // gui->creation
1059
1060 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1061 if(IS_NULL_PTR(gpt)) return;
1062
1063 // we draw the main shape
1064 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1065 if(gpt->points && gpt->points_count > 5)
1066 dt_draw_shape_lines(DT_MASKS_NO_DASH, FALSE, cr, num_points, selected, zoom_scale, gpt->points,
1067 gpt->points_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_BUTT);
1068
1069 if(gui->group_selected == index)
1070 {
1071 // we draw the borders
1072 if(gpt->border && gpt->border_count > 5)
1073 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, num_points, (gui->border_selected), zoom_scale, gpt->border,
1074 gpt->border_count, &dt_masks_functions_ellipse.draw_shape, CAIRO_LINE_CAP_ROUND);
1075
1076 // draw handles
1077 _ellipse_draw_handles(gui, cr, zoom_scale, gpt, index);
1078 }
1079
1080 //draw the center point
1081 if(gui->group_selected == index && gui->pivot_selected && gpt->points && gpt->points_count > 0)
1082 dt_draw_node(cr, FALSE, FALSE, (gui->form_rotating), zoom_scale, gpt->points[0], gpt->points[1]);
1083
1084 // draw the source if any
1085 if(gpt->source && gpt->source_count > 10 && gpt->points && gpt->points_count > 0)
1086 {
1087 dt_masks_gui_center_point_t center_pt = { .main = { gpt->points[0], gpt->points[1] },
1088 .source = { gpt->source[0], gpt->source[1] }};
1089 dt_masks_draw_source(cr, gui, index, num_points, zoom_scale, &center_pt, &dt_masks_functions_ellipse.draw_shape);
1090 }
1091
1092}
1093
1094static void _bounding_box(const float *const points, int num_points, int *width, int *height, int *posx, int *posy)
1095{
1096 // search for min/max X and Y coordinates
1097 float xmin = FLT_MAX, xmax = FLT_MIN, ymin = FLT_MAX, ymax = FLT_MIN;
1098 for(int i = 1; i < num_points; i++) // skip point[0], which is circle's center
1099 {
1100 xmin = fminf(points[i * 2], xmin);
1101 xmax = fmaxf(points[i * 2], xmax);
1102 ymin = fminf(points[i * 2 + 1], ymin);
1103 ymax = fmaxf(points[i * 2 + 1], ymax);
1104 }
1105 // set the min/max values we found
1106 *posx = xmin;
1107 *posy = ymin;
1108 *width = (xmax - xmin);
1109 *height = (ymax - ymin);
1110}
1111
1112static void _fill_mask(const size_t numpoints, float *const bufptr, const float *const points,
1113 const float *const center, const float a, const float b, const float ta, const float tb,
1114 const float alpha, const size_t out_scale)
1115{
1116 const float a2 = a * a;
1117 const float b2 = b * b;
1118 const float ta2 = ta * ta;
1119 const float tb2 = tb * tb;
1120 const float cos_alpha = cosf(alpha);
1121 const float sin_alpha = sinf(alpha);
1122
1123 // Determine the strength of the mask for each of the distorted points. If inside the border of the ellipse,
1124 // the strength is always 1.0; if outside the falloff region, it is 0.0, and in between it falls off quadratically.
1125 // To compute this, we need to do the equivalent of projecting the vector from the center of the ellipse to the
1126 // given point until it intersect the ellipse and the outer edge of the falloff, respectively. The ellipse can
1127 // be rotated, but we can compensate for that by applying a rotation matrix for the same rotation in the opposite
1128 // direction before projecting the vector.
1129 __OMP_PARALLEL_FOR_SIMD__(if(numpoints > 50000) aligned(points, bufptr : 64))
1130 for(size_t i = 0; i < numpoints; i++)
1131 {
1132 const float x = points[2 * i] - center[0];
1133 const float y = points[2 * i + 1] - center[1];
1134 // find the square of the distance from the center
1135 const float l2 = x * x + y * y;
1136 const float l = sqrtf(l2);
1137 // normalize the point's coordinate to form a unit vector, taking care not to divide by zero
1138 const float x_norm = l ? x / l : 0.0f;
1139 const float y_norm = l ? y / l : 1.0f; // ensure we don't get 0 for both sine and cosine below
1140 // apply the rotation matrix
1141 const float x_rot = x_norm * cos_alpha + y_norm * sin_alpha;
1142 const float y_rot = -x_norm * sin_alpha + y_norm * cos_alpha;
1143 // at this point, x_rot = cos(v) and y_rot = sin(v) since they are on the unit circle; we need the squared values
1144 const float cosv2 = x_rot * x_rot;
1145 const float sinv2 = y_rot * y_rot;
1146
1147 // project the rotated unit vector out to the ellipse and the outer border
1148 const float radius2 = a2 * b2 / (a2 * sinv2 + b2 * cosv2);
1149 const float total2 = ta2 * tb2 / (ta2 * sinv2 + tb2 * cosv2);
1150
1151 // quadratic falloff between the ellipses's radius and the radius of the outside of the feathering
1152 // ratio = 0.0 at the outer border, >= 1.0 within the ellipse, negative outside the falloff
1153 const float ratio = (total2 - l2) / (total2 - radius2);
1154 // enforce 1.0 inside the ellipse and 0.0 outside the feathering
1155 const float f = CLIP(ratio);
1156 bufptr[i << out_scale] = f * f;
1157 }
1158}
1159
1160static float *const _ellipse_points_to_transform(const float center_x, const float center_y, const float dim1, const float dim2,
1161 const float rotation, const float wd, const float ht, size_t *point_count)
1162{
1163
1164 const float v1 = ((rotation) / 180.0f) * M_PI;
1165 const float v2 = ((rotation - 90.0f) / 180.0f) * M_PI;
1166 float a = 0.0f, b = 0.0f, v = 0.0f;
1167
1168 if(dim1 >= dim2)
1169 {
1170 a = dim1;
1171 b = dim2;
1172 v = v1;
1173 }
1174 else
1175 {
1176 a = dim2;
1177 b = dim1;
1178 v = v2;
1179 }
1180
1181 const float sinv = sinf(v);
1182 const float cosv = cosf(v);
1183
1184 // how many points do we need ?
1185 const float lambda = (a - b) / (a + b);
1186 const int l = (int)(M_PI * (a + b)
1187 * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1188
1189 // buffer allocation
1190 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t) 2 * (l + 5), 0);
1191 if(IS_NULL_PTR(points))
1192 return NULL;
1193 *point_count = l + 5;
1194
1195 // now we set the points - first the center
1196 float center[2] = { center_x, center_y };
1198 const float x = points[0] = center[0];
1199 const float y = points[1] = center[1];
1200 // then the control node points (ends of semimajor/semiminor axes)
1201 points[2] = x + a * cosf(v);
1202 points[3] = y + a * sinf(v);
1203 points[4] = x - a * cosf(v);
1204 points[5] = y - a * sinf(v);
1205 points[6] = x + b * cosf(v - M_PI / 2.0f);
1206 points[7] = y + b * sinf(v - M_PI / 2.0f);
1207 points[8] = x - b * cosf(v - M_PI / 2.0f);
1208 points[9] = y - b * sinf(v - M_PI / 2.0f);
1209 // and finally the regularly-spaced points on the circumference
1210 for(int i = 5; i < l + 5; i++)
1211 {
1212 float alpha = (i - 5) * 2.0 * M_PI / (float)l;
1213 points[i * 2] = x + a * cosf(alpha) * cosv - b * sinf(alpha) * sinv;
1214 points[i * 2 + 1] = y + a * cosf(alpha) * sinv + b * sinf(alpha) * cosv;
1215 }
1216 return points;
1217}
1218
1221 dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
1222{
1223 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1224 // we get the ellipse values
1225 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1226 if(IS_NULL_PTR(ellipse)) return 0;
1227 const float wd = pipe->iwidth, ht = pipe->iheight;
1228 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1229 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1230 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1231
1232 // next we compute the points to be transformed
1233 size_t point_count = 0;
1234 float *const restrict points
1235 = _ellipse_points_to_transform(form->source[0], form->source[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1236 if (IS_NULL_PTR(points))
1237 return 1;
1238
1239 // and we transform them with all distorted modules
1240 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1241 {
1243 return 1;
1244 }
1245
1246 // finally, find the extreme left/right and top/bottom points
1247 _bounding_box(points, point_count, width, height, posx, posy);
1249 return 0;
1250}
1251
1252static int _ellipse_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1253 const dt_dev_pixelpipe_iop_t *const piece,
1254 dt_masks_form_t *const form,
1255 int *width, int *height, int *posx, int *posy)
1256{
1257 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1258 // we get the ellipse values
1259 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1260 if(IS_NULL_PTR(ellipse)) return 0;
1261 const float wd = pipe->iwidth, ht = pipe->iheight;
1262 const int prop = ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL;
1263 const float total[2] = { (prop ? ellipse->radius[0] * (1.0f + ellipse->border) : ellipse->radius[0] + ellipse->border) * MIN(wd, ht),
1264 (prop ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wd, ht) };
1265
1266 // next we compute the points to be transformed
1267 size_t point_count = 0;
1268 float *const restrict points
1269 = _ellipse_points_to_transform(ellipse->center[0], ellipse->center[1], total[0], total[1], ellipse->rotation, wd, ht, &point_count);
1270 if (IS_NULL_PTR(points))
1271 return 1;
1272
1273 // and we transform them with all distorted modules
1274 if(!dt_dev_distort_transform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, point_count))
1275 {
1277 return 1;
1278 }
1279
1280 // finally, find the extreme left/right and top/bottom points
1281 _bounding_box(points, point_count, width, height, posx, posy);
1283 return 0;
1284}
1285
1286static int _ellipse_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1287 const dt_dev_pixelpipe_iop_t *const piece,
1288 dt_masks_form_t *const form,
1289 float **buffer, int *width, int *height, int *posx, int *posy)
1290{
1291 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1292 double start2 = 0.0;
1294
1295 // we get the area
1296 if(_ellipse_get_area(module, pipe, piece, form, width, height, posx, posy) != 0) return 1;
1297
1299 {
1300 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse area took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1301 start2 = dt_get_wtime();
1302 }
1303
1304 // we get the ellipse values
1305 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1306 if(IS_NULL_PTR(ellipse)) return 0;
1307 // we create a buffer of points with all points in the area
1308 int w = *width, h = *height;
1309 const int posx_ = *posx;
1310 const int posy_ = *posy;
1311 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * w * h, 0);
1312 if(IS_NULL_PTR(points))
1313 return 1;
1314 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)w * h > 50000))
1315 for(int i = 0; i < h; i++)
1316 for(int j = 0; j < w; j++)
1317 {
1318 points[(i * w + j) * 2] = (j + posx_);
1319 points[(i * w + j) * 2 + 1] = (i + posy_);
1320 }
1321
1323 {
1324 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1325 start2 = dt_get_wtime();
1326 }
1327
1328 // we back transform all this points
1329 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)w * h))
1330 {
1332 return 1;
1333 }
1334
1336 {
1337 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse transform took %0.04f sec\n", form->name,
1338 dt_get_wtime() - start2);
1339 start2 = dt_get_wtime();
1340 }
1341
1342 // we allocate the buffer
1343 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
1344 if(IS_NULL_PTR(*buffer))
1345 {
1347 return 1;
1348 }
1349
1350 // we populate the buffer
1351 const int wi = pipe->iwidth, hi = pipe->iheight;
1352 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1353 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1354 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),
1355 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1356
1357 float a = 0.0F, b = 0.0F, ta = 0.0F, tb = 0.0F, alpha = 0.0F;
1358
1359 if(radius[0] >= radius[1])
1360 {
1361 a = radius[0];
1362 b = radius[1];
1363 ta = total[0];
1364 tb = total[1];
1365 alpha = (ellipse->rotation / 180.0f) * M_PI;
1366 }
1367 else
1368 {
1369 a = radius[1];
1370 b = radius[0];
1371 ta = total[1];
1372 tb = total[0];
1373 alpha = ((ellipse->rotation - 90.0f) / 180.0f) * M_PI;
1374 }
1375
1376 float *const bufptr = *buffer;
1377
1378 _fill_mask((size_t)(h)*w, bufptr, points, center, a, b, ta, tb, alpha, 0);
1379
1381
1383 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1384
1385 return 0;
1386}
1387
1388static int _ellipse_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1389 const dt_dev_pixelpipe_iop_t *const piece,
1390 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer)
1391{
1392 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1393
1394 double start1 = 0.0;
1395 double start2 = start1;
1396 if(darktable.unmuted & DT_DEBUG_PERF) start2 = start1 = dt_get_wtime();
1397
1398 // we get the ellipse parameters
1399 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)((form->points)->data);
1400 if(IS_NULL_PTR(ellipse)) return 0;
1401 const int wi = pipe->iwidth, hi = pipe->iheight;
1402 const float center[2] = { ellipse->center[0] * wi, ellipse->center[1] * hi };
1403 const float radius[2] = { ellipse->radius[0] * MIN(wi, hi), ellipse->radius[1] * MIN(wi, hi) };
1404 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),
1405 (ellipse->flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? ellipse->radius[1] * (1.0f + ellipse->border) : ellipse->radius[1] + ellipse->border) * MIN(wi, hi) };
1406
1407 const float a = radius[0];
1408 const float b = radius[1];
1409 const float ta = total[0];
1410 const float tb = total[1];
1411 const float alpha = (ellipse->rotation / 180.0f) * M_PI;
1412 const float cosa = cosf(alpha);
1413 const float sina = sinf(alpha);
1414
1415 // we create a buffer of grid points for later interpolation: higher speed and reduced memory footprint;
1416 // we match size of buffer to bounding box around the shape
1417 const int w = roi->width;
1418 const int h = roi->height;
1419 const int px = roi->x;
1420 const int py = roi->y;
1421 const float iscale = 1.0f / roi->scale;
1422 // scale dependent resolution: when zoomed in (scale > 1), use finer grid to avoid interpolation holes
1423 const float grid_scale = 1.0f / MAX(roi->scale, 1e-6f);
1424 const int grid = CLAMP((10.0f * grid_scale + 2.0f) / 3.0f, 1, 4);
1425 const int gw = (w + grid - 1) / grid + 1; // grid dimension of total roi
1426 const int gh = (h + grid - 1) / grid + 1; // grid dimension of total roi
1427
1429 {
1430 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse init took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1431 start2 = dt_get_wtime();
1432 }
1433
1434 // we look at the outer line of the shape - no effects outside of this ellipse;
1435 // we need many points as we do not know how the ellipse might get distorted in the pixelpipe
1436 const float lambda = (ta - tb) / (ta + tb);
1437 const int l = (int)(M_PI * (ta + tb) * (1.0f + (3.0f * lambda * lambda) / (10.0f + sqrtf(4.0f - 3.0f * lambda * lambda))));
1438 const size_t ellpts = MIN(360, l);
1439 float *ell = dt_pixelpipe_cache_alloc_align_float_cache(ellpts * 2, 0);
1440 if(IS_NULL_PTR(ell)) return 1;
1441 __OMP_PARALLEL_FOR__(if(ellpts > 100))
1442 for(int n = 0; n < ellpts; n++)
1443 {
1444 const float phi = (2.0f * M_PI * n) / ellpts;
1445 const float cosp = cosf(phi);
1446 const float sinp = sinf(phi);
1447 ell[2 * n] = center[0] + ta * cosa * cosp - tb * sina * sinp;
1448 ell[2 * n + 1] = center[1] + ta * sina * cosp + tb * cosa * sinp;
1449 }
1450
1452 {
1453 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1454 start2 = dt_get_wtime();
1455 }
1456
1457 // we transform the outline from input image coordinates to current position in pixelpipe
1459 ellpts))
1460 {
1462 return 1;
1463 }
1464
1466 {
1467 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse outline transform took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1468 start2 = dt_get_wtime();
1469 }
1470
1471 // we get the min/max values ...
1472 float xmin = FLT_MAX, ymin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN;
1473 for(int n = 0; n < ellpts; n++)
1474 {
1475 // just in case that transform throws surprising values
1476 if(!(isnormal(ell[2 * n]) && isnormal(ell[2 * n + 1]))) continue;
1477
1478 xmin = MIN(xmin, ell[2 * n]);
1479 xmax = MAX(xmax, ell[2 * n]);
1480 ymin = MIN(ymin, ell[2 * n + 1]);
1481 ymax = MAX(ymax, ell[2 * n + 1]);
1482 }
1483
1484#if 0
1485 printf("xmin %f, xmax %f, ymin %f, ymax %f\n", xmin, xmax, ymin, ymax);
1486 printf("wi %d, hi %d, iscale %f\n", wi, hi, iscale);
1487 printf("w %d, h %d, px %d, py %d\n", w, h, px, py);
1488#endif
1489
1490 // ... and calculate the bounding box with a bit of reserve
1491 const int bbxm = CLAMP((int)floorf(xmin / iscale - px) / grid - 1, 0, gw - 1);
1492 const int bbXM = CLAMP((int)ceilf(xmax / iscale - px) / grid + 2, 0, gw - 1);
1493 const int bbym = CLAMP((int)floorf(ymin / iscale - py) / grid - 1, 0, gh - 1);
1494 const int bbYM = CLAMP((int)ceilf(ymax / iscale - py) / grid + 2, 0, gh - 1);
1495 const int bbw = bbXM - bbxm + 1;
1496 const int bbh = bbYM - bbym + 1;
1497
1498#if 0
1499 printf("bbxm %d, bbXM %d, bbym %d, bbYM %d\n", bbxm, bbXM, bbym, bbYM);
1500 printf("gw %d, gh %d, bbw %d, bbh %d\n", gw, gh, bbw, bbh);
1501#endif
1502
1504
1506 {
1507 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse bounding box took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1508 start2 = dt_get_wtime();
1509 }
1510
1511 // check if there is anything to do at all;
1512 // only if width and height of bounding box is 2 or greater the shape lies inside of roi and requires action
1513 if(bbw <= 1 || bbh <= 1)
1514 return 0;
1515
1516 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * bbw * bbh, 0);
1517 if(IS_NULL_PTR(points)) return 1;
1518
1519 // we populate the grid points in module coordinates
1520 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)bbw * bbh > 50000))
1521 for(int j = bbym; j <= bbYM; j++)
1522 for(int i = bbxm; i <= bbXM; i++)
1523 {
1524 const size_t index = (size_t)(j - bbym) * bbw + i - bbxm;
1525 points[index * 2] = (grid * i + px) * iscale;
1526 points[index * 2 + 1] = (grid * j + py) * iscale;
1527 }
1528
1530 {
1531 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse grid took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1532 start2 = dt_get_wtime();
1533 }
1534
1535 // we back transform all these points to the input image coordinates
1537 (size_t)bbw * bbh))
1538 {
1540 return 1;
1541 }
1542
1544 {
1545 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse transform took %0.04f sec\n", form->name,
1546 dt_get_wtime() - start2);
1547 start2 = dt_get_wtime();
1548 }
1549
1550 // we calculate the mask values at the transformed points;
1551 // re-use the points array for results; this requires out_scale==1 to double the offsets at which they are stored
1552 _fill_mask((size_t)(bbh)*bbw, points, points, center, a, b, ta, tb, alpha, 1);
1553
1555 {
1556 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse draw took %0.04f sec\n", form->name,
1557 dt_get_wtime() - start2);
1558 start2 = dt_get_wtime();
1559 }
1560
1561 // we fill the pre-initialized output buffer by interpolation;
1562 // we only need to take the contents of our bounding box into account
1563 const int endx = MIN(w, bbXM * grid);
1564 const int endy = MIN(h, bbYM * grid);
1565 const float inv_grid2 = 1.0f / (grid * grid);
1566 float w0[4], w1[4];
1567 for(int i = 0; i < grid; i++)
1568 {
1569 w0[i] = (float)(grid - i);
1570 w1[i] = (float)i;
1571 }
1572 __OMP_PARALLEL_FOR__(if((size_t)(endy - bbym * grid) * (size_t)(endx - bbxm * grid) > 50000))
1573 for(int j = bbym * grid; j < endy; j++)
1574 {
1575 const int jj = j % grid;
1576 const int mj = j / grid - bbym;
1577 const float wj0 = w0[jj];
1578 const float wj1 = w1[jj];
1579 const size_t row_base = (size_t)mj * bbw;
1580 float *const row = buffer + (size_t)j * w;
1581 int ii = 0;
1582 int mi = 0;
1583 for(int i = bbxm * grid; i < endx; i++)
1584 {
1585 const size_t mindex = row_base + mi;
1586 const float wii0 = w0[ii];
1587 const float wii1 = w1[ii];
1588 row[i] = (points[mindex * 2] * wii0 * wj0
1589 + points[(mindex + 1) * 2] * wii1 * wj0
1590 + points[(mindex + bbw) * 2] * wii0 * wj1
1591 + points[(mindex + bbw + 1) * 2] * wii1 * wj1) * inv_grid2;
1592 ii++;
1593 if(ii == grid)
1594 {
1595 ii = 0;
1596 mi++;
1597 }
1598 }
1599 }
1600
1602
1604 {
1605 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse fill took %0.04f sec\n", form->name, dt_get_wtime() - start2);
1606 dt_print(DT_DEBUG_MASKS, "[masks %s] ellipse total render took %0.04f sec\n", form->name,
1607 dt_get_wtime() - start1);
1608 }
1609 return 0;
1610}
1611
1612static void _ellipse_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1613{
1614 snprintf(form->name, sizeof(form->name), _("ellipse #%d"), (int)nb);
1615}
1616
1617static void _ellipse_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
1618{
1619 // unused arg, keep compiler from complaining
1621}
1622
1623static void _ellipse_initial_source_pos(const float iwd, const float iht, float *x, float *y)
1624{
1625
1626
1627 const float radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1628 const float radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1629 float offset[2] = { radius_a, -radius_b };
1631 *x = offset[0];
1632 *y = offset[1];
1633}
1634
1635static void _ellipse_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1636 const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
1637{
1638 if(gui->creation)
1639 g_snprintf(msgbuf, msgbuf_len,
1640 _("<b>Size</b>: scroll, <b>Hardness</b>: shift+scroll\n"
1641 "<b>Rotate</b>: ctrl+shift+scroll, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1642 else if(gui->form_selected || gui->border_selected)
1643 g_snprintf(msgbuf, msgbuf_len,
1644 _("<b>Hardness mode</b>: shift+click, <b>Size</b>: scroll\n"
1645 "<b>Hardness</b>: shift+scroll, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1646}
1647
1649{
1650 int flags = -1;
1651 float radius_a = 0.0f;
1652 float radius_b = 0.0f;
1653 float border = 0.0f;
1655 {
1656 dt_conf_get_and_sanitize_float("plugins/darkroom/spots/ellipse/rotation", 0.0f, 360.f);
1658 radius_a = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_a");
1659 radius_b = dt_conf_get_float("plugins/darkroom/spots/ellipse/radius_b");
1660 border = dt_conf_get_float("plugins/darkroom/spots/ellipse/border");
1661 }
1662 else
1663 {
1664 dt_conf_get_and_sanitize_float("plugins/darkroom/masks/ellipse/rotation", 0.0f, 360.f);
1666 radius_a = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_a");
1667 radius_b = dt_conf_get_float("plugins/darkroom/masks/ellipse/radius_b");
1668 border = dt_conf_get_float("plugins/darkroom/masks/ellipse/border");
1669 }
1670
1671 const float ratio = radius_a / radius_b;
1672
1673 if(radius_a > radius_b)
1674 {
1675 radius_a = CLAMPS(radius_a, 0.001f, 0.5f);
1676 radius_b = radius_a / ratio;
1677 }
1678 else
1679 {
1680 radius_b = CLAMPS(radius_b, 0.001f, 0.5);
1681 radius_a = ratio * radius_b;
1682 }
1683
1684 const float reference = (flags & DT_MASKS_ELLIPSE_PROPORTIONAL ? 1.0f / fmin(radius_a, radius_b) : 1.0f);
1685 border = CLAMPS(border, 0.001f * reference, reference);
1686
1688 {
1689 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_a", radius_a, 0.001f, 0.5f)
1690 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1691 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/spots/ellipse/border", border, 0.001f, reference);
1692 }
1693 else
1694 {
1695 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_a", radius_a, 0.001f, 0.5f);
1696 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/radius_b", radius_b, 0.001f, 0.5f);
1697 DT_CONF_SET_SANITIZED_FLOAT("plugins/darkroom/masks/ellipse/border", border, 0.001f, reference);
1698 }
1699}
1700
1701// The function table for ellipses. This must be public, i.e. no "static" keyword.
1704 .sanitize_config = _ellipse_sanitize_config,
1705 .set_form_name = _ellipse_set_form_name,
1706 .set_hint_message = _ellipse_set_hint_message,
1707 .duplicate_points = _ellipse_duplicate_points,
1708 .initial_source_pos = _ellipse_initial_source_pos,
1709 .get_distance = _ellipse_get_distance,
1710 .get_points = _ellipse_get_points,
1711 .get_points_border = _ellipse_get_points_border,
1712 .get_mask = _ellipse_get_mask,
1713 .get_mask_roi = _ellipse_get_mask_roi,
1714 .get_area = _ellipse_get_area,
1715 .get_source_area = _ellipse_get_source_area,
1716 .get_gravity_center = _ellipse_get_gravity_center,
1717 .get_interaction_value = _ellipse_get_interaction_value,
1718 .set_interaction_value = _ellipse_set_interaction_value,
1719 .update_hover = _find_closest_handle,
1720 .mouse_moved = _ellipse_events_mouse_moved,
1721 .mouse_scrolled = _ellipse_events_mouse_scrolled,
1722 .button_pressed = _ellipse_events_button_pressed,
1723 .button_released = _ellipse_events_button_released,
1724 .key_pressed = _ellipse_events_key_pressed,
1725 .post_expose = _ellipse_events_post_expose,
1726 .draw_shape = _ellipse_draw_shape
1727};
1728
1729// clang-format off
1730// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1731// vim: shiftwidth=2 expandtab tabstop=2 cindent
1732// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1733// 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:824
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_PERF
Definition darktable.h:741
@ DT_DEBUG_MASKS
Definition darktable.h:749
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#define M_PI_F
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1589
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1162
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1621
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1650
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:106
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_MASKS_DASH_STICK
Definition draw.h:94
@ DT_MASKS_NO_DASH
Definition draw.h:93
static void dt_draw_shape_lines(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap)
Draw the lines of a mask shape.
Definition draw.h:734
static 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:586
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:890
#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:1160
static void _ellipse_sanitize_config(dt_masks_type_t type)
Definition ellipse.c:1648
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:1007
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:884
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:1623
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:1286
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:1617
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:1112
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:647
#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:1388
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:630
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:994
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:1252
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:1635
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:727
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:1029
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:1612
const dt_masks_functions_t dt_masks_functions_ellipse
Definition ellipse.c:1702
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:604
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:804
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:670
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:1219
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:695
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:1094
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:548
void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, float *px, float *py, const int adding)
Compute preview-space source position for drawing the clone indicator.
void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module)
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
static void dt_masks_draw_source_preview(cairo_t *cr, const float zoom_scale, dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, const int adding)
Definition masks.h:1141
int dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up, const int flow)
int dt_masks_point_in_form_exact(const float *pts, int num_pts, const float *points, int points_start, int points_count)
Check whether any 2D point in pts[] lies inside the form points[].
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
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:130
@ DT_MASKS_NON_CLONE
Definition masks.h:139
@ DT_MASKS_CLONE
Definition masks.h:135
dt_masks_interaction_t
Definition masks.h:303
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:306
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:305
@ DT_MASKS_INTERACTION_ROTATION
Definition masks.h:308
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:617
@ DT_MASKS_ELLIPSE_PROPORTIONAL
Definition masks.h:220
@ DT_MASKS_ELLIPSE_EQUIDISTANT
Definition masks.h:219
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks.h:821
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
static gboolean dt_masks_form_is_clone(const dt_masks_form_t *form)
Definition masks.h:655
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *form, const char *feature, float amount, float v_min, float v_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
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:194
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:196
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:197
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *gui, dt_masks_form_t *form)
Initialize the clone source position based on current GUI state.
static void dt_masks_draw_preview_shape(cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, void(*const *draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source), const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks.h:787
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Shared selection logic for node/handle/segment hit testing.
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:625
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state)
Decide initial source positioning mode for clone masks.
static gboolean dt_masks_form_uses_spot_defaults(const dt_masks_form_t *form)
Definition masks.h:650
static void dt_masks_reset_source(dt_masks_form_t *form)
Definition masks.h:660
#define 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:788
struct dt_develop_t * develop
Definition darktable.h:798
int32_t raw_height
Definition develop.h:228
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
struct dt_develop_t::@17 roi
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
gboolean border_toggling
Definition masks.h:502
gboolean source_selected
Definition masks.h:492
gboolean source_dragging
Definition masks.h:500
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
dt_iop_module_t * creation_module
Definition masks.h:519
gboolean form_dragging
Definition masks.h:499
gboolean creation
Definition masks.h:517
gboolean form_selected
Definition masks.h:490
gboolean form_rotating
Definition masks.h:501
gboolean border_selected
Definition masks.h:491
gboolean pivot_selected
Definition masks.h:493
float delta[2]
Definition masks.h:469
dt_masks_type_t type
Definition masks.h:380
float source[2]
Definition masks.h:387
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:370
struct dt_masks_gui_center_point_t::@34 main
dt_masks_ellipse_flags_t flags
Definition masks.h:245
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29