Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
develop/masks/brush.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2016, 2021 Aldric Renaudin.
4 Copyright (C) 2013 Jean-Sébastien Pédron.
5 Copyright (C) 2013, 2017, 2019-2022 Pascal Obry.
6 Copyright (C) 2013 Simon Spannagel.
7 Copyright (C) 2013-2017 Tobias Ellinghaus.
8 Copyright (C) 2013-2016, 2019 Ulrich Pegelow.
9 Copyright (C) 2014-2016 Roman Lebedev.
10 Copyright (C) 2017-2019 Edgardo Hoszowski.
11 Copyright (C) 2017 Matthieu Moy.
12 Copyright (C) 2018 johannes hanika.
13 Copyright (C) 2020, 2022 Chris Elston.
14 Copyright (C) 2020 GrahamByrnes.
15 Copyright (C) 2020 Heiko Bauke.
16 Copyright (C) 2020-2021 Hubert Kowalski.
17 Copyright (C) 2020-2021 Ralf Brown.
18 Copyright (C) 2021 Diederik Ter Rahe.
19 Copyright (C) 2021 luzpaz.
20 Copyright (C) 2022 Martin Bařinka.
21 Copyright (C) 2023, 2025-2026 Aurélien PIERRE.
22 Copyright (C) 2025-2026 Guillaume Stutin.
23
24 darktable is free software: you can redistribute it and/or modify
25 it under the terms of the GNU General Public License as published by
26 the Free Software Foundation, either version 3 of the License, or
27 (at your option) any later version.
28
29 darktable is distributed in the hope that it will be useful,
30 but WITHOUT ANY WARRANTY; without even the implied warranty of
31 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 GNU General Public License for more details.
33
34 You should have received a copy of the GNU General Public License
35 along with darktable. If not, see <http://www.gnu.org/licenses/>.
36*/
37#include "control/control.h"
38#include "math/math.h"
39#include "system/macros.h"
40#include "system/openmp.h"
41#include "system/mem_alloc.h"
42#include "common/logging.h"
43#include "common/times.h"
44#include "common/glib_utils.h"
46#include "common/conf.h"
47#include "develop/imageop.h"
49#include "develop/masks.h"
50#include "develop/masks_gui.h"
51#include "develop/blend_gui.h" // dt_iop_gui_blend_masks_update()
54#include "math/openmp_maths.h"
55#include "gui/actions/menu.h"
58
59#define FADING_MIN 0.00001f
60#define FADING_MAX 1.0f
61
62#define BORDER_MIN 0.00005f
63#define BORDER_MAX 0.5f
64
71static float _brush_point_line_distance2(int point_index, int point_count,
72 const float *point_buffer, const float *payload_buffer)
73{
74 const float point_x = point_buffer[2 * point_index];
75 const float point_y = point_buffer[2 * point_index + 1];
76 const float point_border = payload_buffer[4 * point_index];
77 const float point_fading = payload_buffer[4 * point_index + 1];
78 const float point_density = payload_buffer[4 * point_index + 2];
79 const float start_x = point_buffer[0];
80 const float start_y = point_buffer[1];
81 const float start_border = payload_buffer[0];
82 const float start_fading = payload_buffer[1];
83 const float start_density = payload_buffer[2];
84 const float end_x = point_buffer[2 * (point_count - 1)];
85 const float end_y = point_buffer[2 * (point_count - 1) + 1];
86 const float end_border = payload_buffer[4 * (point_count - 1)];
87 const float end_fading = payload_buffer[4 * (point_count - 1) + 1];
88 const float end_density = payload_buffer[4 * (point_count - 1) + 2];
89 const float bweight = 1.0f;
90 const float hweight = 0.01f;
91 const float dweight = 0.01f;
92
93 const float segment_dx = end_x - start_x;
94 const float segment_dy = end_y - start_y;
95 const float segment_db = end_border - start_border;
96 const float segment_dh = end_fading - start_fading;
97 const float segment_dd = end_density - start_density;
98
99 const float dot = (point_x - start_x) * segment_dx + (point_y - start_y) * segment_dy;
100 const float segment_len2 = sqf(segment_dx) + sqf(segment_dy);
101 const float t = dot / segment_len2;
102
103 float dx = 0.0f, dy = 0.0f, db = 0.0f, dh = 0.0f, dd = 0.0f;
104
105 if(segment_len2 == 0.0f)
106 {
107 dx = point_x - start_x;
108 dy = point_y - start_y;
109 db = point_border - start_border;
110 dh = point_fading - start_fading;
111 dd = point_density - start_density;
112 }
113 else if(t < 0.0f)
114 {
115 dx = point_x - start_x;
116 dy = point_y - start_y;
117 db = point_border - start_border;
118 dh = point_fading - start_fading;
119 dd = point_density - start_density;
120 }
121 else if(t > 1.0f)
122 {
123 dx = point_x - end_x;
124 dy = point_y - end_y;
125 db = point_border - end_border;
126 dh = point_fading - end_fading;
127 dd = point_density - end_density;
128 }
129 else
130 {
131 dx = point_x - (start_x + t * segment_dx);
132 dy = point_y - (start_y + t * segment_dy);
133 db = point_border - (start_border + t * segment_db);
134 dh = point_fading - (start_fading + t * segment_dh);
135 dd = point_density - (start_density + t * segment_dd);
136 }
137
138 return sqf(dx) + sqf(dy) + bweight * sqf(db) + hweight * dh * dh + dweight * sqf(dd);
139}
140
144static GList *_brush_ramer_douglas_peucker(const float *point_buffer, int point_count,
145 const float *payload_buffer, float epsilon2)
146{
147 GList *result_list = NULL;
148
149 float dmax2 = 0.0f;
150 int split_index = 0;
151
152 for(int point_index = 1; point_index < point_count - 1; point_index++)
153 {
154 const float d2 = _brush_point_line_distance2(point_index, point_count,
155 point_buffer, payload_buffer);
156 if(d2 > dmax2)
157 {
158 split_index = point_index;
159 dmax2 = d2;
160 }
161 }
162
163 if(dmax2 >= epsilon2)
164 {
165 GList *left_list = _brush_ramer_douglas_peucker(point_buffer, split_index + 1,
166 payload_buffer, epsilon2);
167 GList *right_list = _brush_ramer_douglas_peucker(point_buffer + split_index * 2,
168 point_count - split_index,
169 payload_buffer + split_index * 4, epsilon2);
170
171 // remove last element from left_list to avoid duplication at the split
172 GList *end1 = g_list_last(left_list);
173 dt_free(end1->data);
174 left_list = g_list_delete_link(left_list, end1);
175
176 result_list = g_list_concat(left_list, right_list);
177 }
178 else
179 {
180 dt_masks_node_brush_t *first_node = malloc(sizeof(dt_masks_node_brush_t));
181 first_node->node[0] = point_buffer[0];
182 first_node->node[1] = point_buffer[1];
183 first_node->ctrl1[0] = first_node->ctrl1[1] = first_node->ctrl2[0] = first_node->ctrl2[1] = -1.0f;
184 first_node->border[0] = first_node->border[1] = payload_buffer[0];
185 first_node->fading = payload_buffer[1];
186 first_node->density = payload_buffer[2];
188 result_list = g_list_append(result_list, (gpointer)first_node);
189
190 dt_masks_node_brush_t *last_node = malloc(sizeof(dt_masks_node_brush_t));
191 last_node->node[0] = point_buffer[(point_count - 1) * 2];
192 last_node->node[1] = point_buffer[(point_count - 1) * 2 + 1];
193 last_node->ctrl1[0] = last_node->ctrl1[1] = last_node->ctrl2[0] = last_node->ctrl2[1] = -1.0f;
194 last_node->border[0] = last_node->border[1] = payload_buffer[(point_count - 1) * 4];
195 last_node->fading = payload_buffer[(point_count - 1) * 4 + 1];
196 last_node->density = payload_buffer[(point_count - 1) * 4 + 2];
198 result_list = g_list_append(result_list, (gpointer)last_node);
199 }
200
201 return result_list;
202}
203
207static void _brush_get_XY(float p0_x, float p0_y, float p1_x, float p1_y, float p2_x, float p2_y, float p3_x,
208 float p3_y, float t, float *out_x, float *out_y)
209{
210 const float ti = 1.0f - t;
211 const float a = ti * ti * ti;
212 const float b = 3.0f * t * ti * ti;
213 const float c = 3.0f * sqf(t) * ti;
214 const float d = t * t * t;
215 *out_x = p0_x * a + p1_x * b + p2_x * c + p3_x * d;
216 *out_y = p0_y * a + p1_y * b + p2_y * c + p3_y * d;
217}
218
230static gboolean _brush_border_get_XY(float p0_x, float p0_y, float p1_x, float p1_y, float p2_x, float p2_y,
231 float p3_x, float p3_y, float t, float radius, float radius_rate,
232 float *point_x, float *point_y, float *border_x, float *border_y)
233{
234 // we get the point
235 _brush_get_XY(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, t, point_x, point_y);
236
237 // now we get derivative points
238 const float ti = 1.0f - t;
239 const float a = 3.0f * ti * ti;
240 const float b = 3.0f * (ti * ti - 2.0f * t * ti);
241 const float c = 3.0f * (2.0f * t * ti - t * t);
242 const float d = 3.0f * sqf(t);
243
244 /* not const: a degenerate first-order term is replaced by the limit direction below */
245 float dx = -p0_x * a + p1_x * b + p2_x * c + p3_x * d;
246 float dy = -p0_y * a + p1_y * b + p2_y * c + p3_y * d;
247
248 /* A vanishing derivative does NOT mean the tangent is undefined -- it means the first-order
249 * term is degenerate and the direction is given by the limit, i.e. by the first non-zero term.
250 * That is exactly how a CUSP is stored: both of the node's handles sit on the node itself, so
251 * the cubic's endpoint derivative 3*(p1 - p0) (or 3*(p3 - p2)) is zero there.
252 *
253 * Two separate defects lived in the three lines this replaces, and only the pair of them
254 * explains issue #1313's follow-up ("the brush loses its radius in the direction of the point
255 * of the cusp and so we have a sharp V hole").
256 *
257 * FIRST: the test was `== 0', and a vanishing derivative does not arrive as zero. These are
258 * image pixels, so at the reported cusp -- where p2 == p3 bit for bit -- 3*(p3 - p2) evaluates
259 * to 1.22e-4, because the products 3*1327.8497 are each rounded to float before the
260 * subtraction. So the guard never fired at all. Execution fell through and NORMALISED that
261 * residue: (1.22e-4, 0) normalises to (1, 0), and the border direction at the cusp became pure
262 * rounding noise pointing along +x. The tell, once you know it, is a border sample sitting
263 * exactly one radius due east of its node. The threshold below is therefore RELATIVE, scaled
264 * to the control polygon because that is what the derivative scales with -- a genuine tangent
265 * near the cusp is orders of magnitude larger (measured 1.06 at t = 0.999 against a 0.2 noise
266 * floor), so the two separate cleanly and no real direction can be swallowed.
267 *
268 * SECOND: even when it did fire, returning NaN was the wrong answer. Every consumer falls back
269 * to the previous border point, so the offset curve never rotated through the exterior angle
270 * at the tip, the two sides of the joint coincided -- the gap between them measured (0.0, 0.0)
271 * and the turn 0.0 degrees -- and the arc filler that exists to bridge exactly that wedge was
272 * never triggered. Since a brush mask is the union of centreline->border spokes, the wedge at
273 * the point of the cusp was simply never painted.
274 *
275 * For a cubic, if the first-order term vanishes the second-order one gives the limit
276 * direction: (p2 - p0) at the start, (p3 - p1) at the end. If that is degenerate too, the
277 * chord is the last honest answer. Only a curve collapsed to a single point has no direction
278 * at all, and that one still reports NaN, because there is genuinely nothing to offset. */
279 const float span = fmaxf(fmaxf(fabsf(p3_x - p0_x), fabsf(p3_y - p0_y)),
280 fmaxf(fabsf(p2_x - p1_x), fabsf(p2_y - p1_y)));
281 const float degenerate = fmaxf(span, 1.0f) * 1e-3f;
282
283 if(fabsf(dx) < degenerate && fabsf(dy) < degenerate)
284 {
285 if(t < 0.5f) { dx = p2_x - p0_x; dy = p2_y - p0_y; }
286 else { dx = p3_x - p1_x; dy = p3_y - p1_y; }
287
288 if(fabsf(dx) < degenerate && fabsf(dy) < degenerate) { dx = p3_x - p0_x; dy = p3_y - p0_y; }
289
290 if(dx == 0.0f && dy == 0.0f) return FALSE; // a single point: no direction to offset along
291
292 /* a limit direction has a direction and no speed, so no rate can be taken against it */
293 radius_rate = 0.0f;
294 }
295
296 /* on the envelope of the discs, not on the normal: see dt_masks_outline_envelope_offset() */
297 const float centre[2] = { *point_x, *point_y };
298 float border[2];
299 dt_masks_outline_envelope_offset(centre, dx, dy, radius, radius_rate, border);
300 *border_x = border[0];
301 *border_y = border[1];
302 return TRUE;
303}
304
305/* The radius along a segment eases from one node's to the other's, r1 + (r2 - r1) * t^2 (3 - 2t),
306 * and its rate by t is what the envelope needs: (r2 - r1) * 6 t (1 - t), zero at both ends, so a
307 * segment's end samples stay on the normal and the caps and joint arcs built from them are
308 * unchanged. */
309static inline float _brush_radius_at(const float r1, const float r2, const double t)
310{
311 return r1 + (r2 - r1) * t * t * (3.0 - 2.0 * t);
312}
313
314static inline float _brush_radius_rate_at(const float r1, const float r2, const double t)
315{
316 return (r2 - r1) * 6.0 * t * (1.0 - t);
317}
318
322static void _brush_ctrl2_to_handle(float point_x, float point_y, float ctrl_x, float ctrl_y,
323 float *handle_x, float *handle_y, gboolean clockwise)
324{
325 if(clockwise)
326 {
327 *handle_x = point_x + ctrl_y - point_y;
328 *handle_y = point_y + point_x - ctrl_x;
329 }
330 else
331 {
332 *handle_x = point_x - ctrl_y + point_y;
333 *handle_y = point_y - point_x + ctrl_x;
334 }
335}
336
351static gboolean _brush_get_border_handle_resampled(const dt_masks_form_gui_points_t *gui_points, int node_count,
352 int node_index, float *handle_x, float *handle_y)
353{
354 if(IS_NULL_PTR(gui_points) || node_count <= 0 || node_index < 0 || node_index >= node_count) return FALSE;
355
356 const int start = node_count * 3;
357 const int max_points = MIN(gui_points->points_count, gui_points->border_count);
358 if(max_points <= start) return FALSE;
359
360 const float node_x = gui_points->points[node_index * 6 + 2];
361 const float node_y = gui_points->points[node_index * 6 + 3];
362
363 float best_dist2 = FLT_MAX;
364 int best_idx = -1;
365
366 for(int i = start; i < max_points; i++)
367 {
368 /* Skip an index inside an EXCLUDED span, because the border sample there is one the outline
369 * does not show -- the offset curve doubles back on itself, and a node can sit opposite such
370 * a span; a cusp does. This used to be a NaN test against the buffer, back when the excluded
371 * samples were blanked in place, and it cost the node its handle outright when it fired: no
372 * dash drawn, and no hover or drag either, since both interactions resolve through here.
373 * Asking the list instead both keeps the handle and stops this function caring how the
374 * drawer marks its exclusions. */
375 if(dt_masks_skip_contains(gui_points->border_skips, gui_points->border_skip_count, i)) continue;
376
377 const float px = gui_points->points[i * 2];
378 const float py = gui_points->points[i * 2 + 1];
379 const float dx = node_x - px;
380 const float dy = node_y - py;
381 const float dist2 = dx * dx + dy * dy;
382 if(dist2 < best_dist2)
383 {
384 best_dist2 = dist2;
385 best_idx = i;
386 }
387 }
388
389 if(best_idx < 0) return FALSE;
390
391 *handle_x = gui_points->border[best_idx * 2];
392 *handle_y = gui_points->border[best_idx * 2 + 1];
393 return TRUE;
394}
395
396static gboolean _brush_get_border_handle_mirrored(const dt_masks_form_gui_points_t *gui_points, int node_count,
397 int node_index, float *handle_x, float *handle_y)
398{
399 float resampled_x = 0.0f;
400 float resampled_y = 0.0f;
401 if(!_brush_get_border_handle_resampled(gui_points, node_count, node_index, &resampled_x, &resampled_y)) return FALSE;
402
403 const float node_x = gui_points->points[node_index * 6 + 2];
404 const float node_y = gui_points->points[node_index * 6 + 3];
405
406 *handle_x = node_x - (resampled_x - node_x);
407 *handle_y = node_y - (resampled_y - node_y);
408 return TRUE;
409}
410
413static void _brush_handle_to_ctrl(float ptx, float pty, float fx, float fy,
414 float *ctrl1x, float *ctrl1y,
415 float *ctrl2x, float *ctrl2y, gboolean clockwise)
416{
417 if(clockwise)
418 {
419 *ctrl2x = ptx + pty - fy;
420 *ctrl2y = pty + fx - ptx;
421 *ctrl1x = ptx - pty + fy;
422 *ctrl1y = pty - fx + ptx;
423 }
424 else
425 {
426 *ctrl1x = ptx + pty - fy;
427 *ctrl1y = pty + fx - ptx;
428 *ctrl2x = ptx - pty + fy;
429 *ctrl2y = pty - fx + ptx;
430 }
431}
432
436static void _brush_catmull_to_bezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4,
437 float y4, float *bezier1_x, float *bezier1_y,
438 float *bezier2_x, float *bezier2_y)
439{
440 *bezier1_x = (-x1 + 6 * x2 + x3) / 6;
441 *bezier1_y = (-y1 + 6 * y2 + y3) / 6;
442 *bezier2_x = (x2 + 6 * x3 - x4) / 6;
443 *bezier2_y = (y2 + 6 * y3 - y4) / 6;
444}
445
452{
453 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return;
454 // if we have less than 2 points, what to do ??
455 if(g_list_shorter_than(mask_form->points, 2)) return;
456
457 // we need extra points to deal with curve ends
458 dt_masks_node_brush_t start_point[2], end_point[2];
459
460 for(GList *form_points = mask_form->points; form_points; form_points = g_list_next(form_points))
461 {
462 dt_masks_node_brush_t *point3 = (dt_masks_node_brush_t *)form_points->data;
463 if(IS_NULL_PTR(point3)) continue;
464 // if the point has not been set manually, we redefine it
466 {
467 // we want to get point-2, point-1, point+1, point+2
468 GList *const prev = g_list_previous(form_points); // point-1
469 GList *const prevprev = prev ? g_list_previous(prev) : NULL; // point-2
470 GList *const next = g_list_next(form_points); // point+1
471 GList *const nextnext = next ? g_list_next(next) : NULL; // point+2
472 dt_masks_node_brush_t *point1 = prevprev ? prevprev->data : NULL;
473 dt_masks_node_brush_t *point2 = prev ? prev->data : NULL;
474 dt_masks_node_brush_t *point4 = next ? next->data : NULL;
475 dt_masks_node_brush_t *point5 = nextnext ? nextnext->data : NULL;
476
477 // deal with end points: make both extending points mirror their neighborhood
478 if(IS_NULL_PTR(point1) && IS_NULL_PTR(point2))
479 {
480 start_point[0].node[0] = start_point[1].node[0] = 2 * point3->node[0] - point4->node[0];
481 start_point[0].node[1] = start_point[1].node[1] = 2 * point3->node[1] - point4->node[1];
482 point1 = &(start_point[0]);
483 point2 = &(start_point[1]);
484 }
485 else if(IS_NULL_PTR(point1))
486 {
487 start_point[0].node[0] = 2 * point2->node[0] - point3->node[0];
488 start_point[0].node[1] = 2 * point2->node[1] - point3->node[1];
489 point1 = &(start_point[0]);
490 }
491
492 if(IS_NULL_PTR(point4) && IS_NULL_PTR(point5))
493 {
494 end_point[0].node[0] = end_point[1].node[0] = 2 * point3->node[0] - point2->node[0];
495 end_point[0].node[1] = end_point[1].node[1] = 2 * point3->node[1] - point2->node[1];
496 point4 = &(end_point[0]);
497 point5 = &(end_point[1]);
498 }
499 else if(IS_NULL_PTR(point5))
500 {
501 end_point[0].node[0] = 2 * point4->node[0] - point3->node[0];
502 end_point[0].node[1] = 2 * point4->node[1] - point3->node[1];
503 point5 = &(end_point[0]);
504 }
505
506
507 float bx1 = 0.0f, by1 = 0.0f, bx2 = 0.0f, by2 = 0.0f;
508 _brush_catmull_to_bezier(point1->node[0], point1->node[1], point2->node[0], point2->node[1],
509 point3->node[0], point3->node[1], point4->node[0], point4->node[1],
510 &bx1, &by1, &bx2, &by2);
511 if(point2->ctrl2[0] == -1.0) point2->ctrl2[0] = bx1;
512 if(point2->ctrl2[1] == -1.0) point2->ctrl2[1] = by1;
513 point3->ctrl1[0] = bx2;
514 point3->ctrl1[1] = by2;
515 _brush_catmull_to_bezier(point2->node[0], point2->node[1], point3->node[0], point3->node[1],
516 point4->node[0], point4->node[1], point5->node[0], point5->node[1],
517 &bx1, &by1, &bx2, &by2);
518 if(point4->ctrl1[0] == -1.0) point4->ctrl1[0] = bx2;
519 if(point4->ctrl1[1] == -1.0) point4->ctrl1[1] = by2;
520 point3->ctrl2[0] = bx1;
521 point3->ctrl2[1] = by1;
522 }
523 }
524}
525
526
529static void _brush_points_recurs_border_gaps(float *cmax, float *bmin, float *bmax,
530 dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
531 gboolean clockwise, const int step)
532{
533 // we want to find the start and end angles
534 float a1 = atan2f(bmin[1] - cmax[1], bmin[0] - cmax[0]);
535 float a2 = atan2f(bmax[1] - cmax[1], bmax[0] - cmax[0]);
536
537 if(a1 == a2) return;
538
539 // we have to be sure that we turn in the correct direction
540 if(a2 < a1 && clockwise)
541 {
542 a2 += 2.0f * M_PI;
543 }
544 if(a2 > a1 && !clockwise)
545 {
546 a1 += 2.0f * M_PI;
547 }
548
549 // we determine start and end radius too
550 float r1 = dt_fast_hypotf(bmin[1] - cmax[1], bmin[0] - cmax[0]);
551 float r2 = dt_fast_hypotf(bmax[1] - cmax[1], bmax[0] - cmax[0]);
552
553 // and the max length of the circle arc, in samples
554 const int l = fabsf(a2 - a1) * fmaxf(r1, r2) / (float)step;
555 if(l < 2) return;
556
557 // and now we add the points
558 const float incra = (a2 - a1) / l;
559 const float incrr = (r2 - r1) / l;
560
561 // Use incremental rotation to avoid repeated cosf/sinf calls
562 const float cos_incra = cosf(incra);
563 const float sin_incra = sinf(incra);
564 float rr = r1 + incrr;
565 float cos_aa = cosf(a1 + incra);
566 float sin_aa = sinf(a1 + incra);
567
568 // allocate entries in the dynbufs
569 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
570 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
571 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
572 // successive angular positions for dborder
573 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
574 {
575 for(int i = 1; i < l; i++)
576 {
577 *dpoints_ptr++ = cmax[0];
578 *dpoints_ptr++ = cmax[1];
579 *dborder_ptr++ = cmax[0] + rr * cos_aa;
580 *dborder_ptr++ = cmax[1] + rr * sin_aa;
581
582 // Incremental rotation: rotate by incra using addition formulas
583 const float new_cos = cos_aa * cos_incra - sin_aa * sin_incra;
584 const float new_sin = sin_aa * cos_incra + cos_aa * sin_incra;
585 cos_aa = new_cos;
586 sin_aa = new_sin;
587 rr += incrr;
588 }
589 }
590}
591
595static void _brush_points_recurs_border_small_gaps(float *cmax, float *bmin, float *bmax,
596 dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
597 const int step)
598{
599 // we want to find the start and end angles
600 const float a1 = fmodf(atan2f(bmin[1] - cmax[1], bmin[0] - cmax[0]) + 2.0f * M_PI, 2.0f * M_PI);
601 const float a2 = fmodf(atan2f(bmax[1] - cmax[1], bmax[0] - cmax[0]) + 2.0f * M_PI, 2.0f * M_PI);
602
603 if(a1 == a2) return;
604
605 // we determine start and end radius too
606 const float r1 = dt_fast_hypotf(bmin[1] - cmax[1], bmin[0] - cmax[0]);
607 const float r2 = dt_fast_hypotf(bmax[1] - cmax[1], bmax[0] - cmax[0]);
608
609 // we close the gap in the shortest direction
610 float delta = a2 - a1;
611 if(fabsf(delta) > M_PI) delta = delta - copysignf(2.0f * M_PI, delta);
612
613 // get the max length of the circle arc, in samples
614 const int l = fabsf(delta) * fmaxf(r1, r2) / (float)step;
615 if(l < 2) return;
616
617 // and now we add the points
618 const float incra = delta / l;
619 const float incrr = (r2 - r1) / l;
620
621 // Use incremental rotation to avoid repeated cosf/sinf calls
622 const float cos_incra = cosf(incra);
623 const float sin_incra = sinf(incra);
624 float rr = r1 + incrr;
625 float cos_aa = cosf(a1 + incra);
626 float sin_aa = sinf(a1 + incra);
627
628 // allocate entries in the dynbufs
629 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
630 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
631 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
632 // successive angular positions for dborder
633 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
634 {
635 for(int i = 1; i < l; i++)
636 {
637 *dpoints_ptr++ = cmax[0];
638 *dpoints_ptr++ = cmax[1];
639 *dborder_ptr++ = cmax[0] + rr * cos_aa;
640 *dborder_ptr++ = cmax[1] + rr * sin_aa;
641
642 // Incremental rotation: rotate by incra using addition formulas
643 const float new_cos = cos_aa * cos_incra - sin_aa * sin_incra;
644 const float new_sin = sin_aa * cos_incra + cos_aa * sin_incra;
645 cos_aa = new_cos;
646 sin_aa = new_sin;
647 rr += incrr;
648 }
649 }
650}
651
652
665static void _brush_points_stamp(const float *const centre, const float *const from_border, const float radius,
666 dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, const int step)
667{
668 const float a1 = atan2f(from_border[1] - centre[1], from_border[0] - centre[0]);
669
670 // determine the max length of the circle arc, in samples
671 const int l = 2.0f * M_PI * radius / (float)step;
672 if(l < 2) return;
673
674 // and now we add the points
675 const float incra = 2.0f * M_PI / l;
676 float aa = a1 + incra;
677 // allocate entries in the dynbuf
678 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
679 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
680 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
681 // successive angular positions for dborder
682 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
683 {
684 for(int i = 0; i < l; i++)
685 {
686 *dpoints_ptr++ = centre[0];
687 *dpoints_ptr++ = centre[1];
688 *dborder_ptr++ = centre[0] + radius * cosf(aa);
689 *dborder_ptr++ = centre[1] + radius * sinf(aa);
690 aa += incra;
691 }
692 }
693}
694
695static inline gboolean _is_within_pxl_threshold(const float *const min, const float *const max, const int pixel_threshold)
696{
697 return abs((int)min[0] - (int)max[0]) < pixel_threshold &&
698 abs((int)min[1] - (int)max[1]) < pixel_threshold;
699}
700
701static inline void _brush_payload_sync(dt_masks_dynbuf_t *dpayload, dt_masks_dynbuf_t *dpoints,
702 const float v0, const float v1)
703{
704 size_t payload_pos = dt_masks_dynbuf_position(dpayload);
705 const size_t target_pos = dt_masks_dynbuf_position(dpoints);
706 while(payload_pos < target_pos)
707 {
708 dt_masks_dynbuf_add_2(dpayload, v0, v1);
709 payload_pos += 2;
710 }
711}
712
713/* Is the span [tmin, tmax] fine enough to write as one sample: its ends within the pixel
714 * threshold of each other, on the centreline and -- when a border is being built, @p border_min
715 * non-NULL -- on the border too. */
716static inline gboolean _brush_span_is_leaf(const double tmin, const double tmax, const float *const points_min,
717 const float *const points_max, const float *const border_min,
718 const float *const border_max, const int pixel_threshold)
719{
720 if(tmax - tmin < 0.0001f) return TRUE;
721 if(!_is_within_pxl_threshold(points_min, points_max, pixel_threshold)) return FALSE;
722 if(IS_NULL_PTR(border_min)) return TRUE;
723 return _is_within_pxl_threshold(border_min, border_max, pixel_threshold);
724}
725
726/* Give a leaf's far end a border when the span's ends did not both come with one: borrow
727 * the near end's; and if neither end has a direction, a spoke of the right LENGTH along the
728 * chord's normal.
729 *
730 * What used to happen in that last case is the whole of issue #1360: border_max was the
731 * caller's zero-initialised scratch, and (0, 0) -- the image origin -- went into the buffer
732 * as if it were geometry. A spoke of the right length in some direction is always a valid
733 * piece of the disc union; a spoke to the corner of the frame never is. The walk only hands
734 * the recursion segments that have a direction at an end, so the case is unreachable in
735 * practice; it is kept safe rather than asserted. */
736static inline void _brush_leaf_borrow_border(float *const border_min, float *const border_max,
737 const gboolean have_min, const gboolean have_max,
738 const float *const centre, const float *const chord, const float radius)
739{
740 if(have_max)
741 {
742 /* only have_max is reported upward, so a borrowed min needs no flag */
743 if(!have_min)
744 {
745 border_min[0] = border_max[0];
746 border_min[1] = border_max[1];
747 }
748 return;
749 }
750 if(have_min)
751 {
752 border_max[0] = border_min[0];
753 border_max[1] = border_min[1];
754 return;
755 }
756 dt_masks_outline_offset_along(centre, chord[1], -chord[0], radius, border_max);
757}
758
759/* The frame the walk works in: the image size the normalised node coordinates scale to, and
760 * the shift that moves a clone source's outline onto its target. */
761typedef struct _brush_frame_t
762{
763 float iwd;
764 float iht;
765 float dx;
766 float dy;
768
769/* THE WALK.
770 *
771 * A brush is the union of a disc of the local radius over every point of its centreline, and
772 * the rasteriser paints that union as spokes: from every centreline sample out to its border
773 * sample, on both sides. So the centreline is walked twice, forward with the border on the
774 * right and backward with the border on the right again -- the other side -- and the two
775 * sides are joined by a cap at each end. Where the direction, the radius or the payload jumps
776 * at a node, the spokes of the two segments meeting there leave a wedge; a disc or an arc,
777 * centred on that node at that node's radius, fills it.
778 *
779 * Everything a joint or a cap needs is taken from the two segment END SAMPLES that meet
780 * there, and from the node data. Nothing is read back out of the buffers. The previous walk
781 * took "the last centreline sample and the last border sample written" as the centre and
782 * radius of every cap, arc and stamp, on the assumption that they belonged to one spoke; a
783 * degenerate segment broke that assumption once and the damage compounded through every
784 * later joint of the stroke (issue #1360: a circle 2058 px across, centred on the stroke,
785 * grown from a border sample that was the image origin).
786 *
787 * A DEGENERATE segment -- its four control points one point, which is what a pen resting
788 * under rising pressure produces -- has no direction and so no spokes, and contributes
789 * nothing: its disc is the cap of whichever neighbour has a direction. The walk skips it and
790 * joins the segments either side of it as if it were not there, which for the disc union is
791 * exactly right. */
792typedef struct _brush_walk_t
793{
798 /* Image pixels between consecutive samples of an arc or a stamp. The GUI's outline takes the
799 * density it is shown at, like its curves; the pipe keeps one per pixel whatever spoke
800 * budget it was given, so its raster does not change under a preference. */
803 dt_masks_dynbuf_t *dborder; /* NULL when no border is wanted */
804 dt_masks_dynbuf_t *dpayload; /* NULL when no payload is wanted */
806
807/* One span [tmin, tmax] of a segment and what is known at its ends: the spine and border
808 * samples there, whether each end was evaluated already, and whether a border came with it.
809 * Those flags used to be read out of the arrays as NaN -- "not computed" and "no border"
810 * sharing one representation with each other and with real geometry. Which of the three a NaN
811 * meant depended on where you were standing, and the arrays are the same ones that end up in
812 * the outline. A leaf always writes a border now (see _brush_leaf_borrow_border()). */
813typedef struct _brush_span_t
814{
815 double tmin;
816 double tmax;
821 gboolean have_min;
822 gboolean have_max;
826
827/* The sample a span's far end resolved to: the next span's near end, and the segment's end
828 * sample for the walk. have_border is TRUE whenever a border is being built at all. */
829typedef struct _brush_sample_t
830{
831 float point[2];
832 float border[2];
833 float payload[2];
834 gboolean have_border;
836
839static void _brush_points_recurs(const float *const p1, const float *const p2, const _brush_span_t *const span,
840 const _brush_walk_t *const w, _brush_sample_t *const out)
841{
842 dt_masks_dynbuf_t *const dpoints = w->dpoints;
843 dt_masks_dynbuf_t *const dborder = w->dborder;
844 dt_masks_dynbuf_t *const dpayload = w->dpayload;
845 const gboolean withborder = (!IS_NULL_PTR(dborder));
846 float *const points_min = span->points_min;
847 float *const points_max = span->points_max;
848 float *const border_min = span->border_min;
849 float *const border_max = span->border_max;
850 gboolean have_border_min = span->have_border_min;
851 gboolean have_border_max = span->have_border_max;
852
853 // we calculate points if needed
854 if(!span->have_min)
855 have_border_min = _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], span->tmin,
856 _brush_radius_at(p1[4], p2[4], span->tmin),
857 _brush_radius_rate_at(p1[4], p2[4], span->tmin), points_min,
858 points_min + 1, border_min, border_min + 1);
859 if(!span->have_max)
860 have_border_max = _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], span->tmax,
861 _brush_radius_at(p1[4], p2[4], span->tmax),
862 _brush_radius_rate_at(p1[4], p2[4], span->tmax), points_max,
863 points_max + 1, border_max, border_max + 1);
864
865 if(!_brush_span_is_leaf(span->tmin, span->tmax, points_min, points_max, withborder ? border_min : NULL,
866 border_max, w->pixel_threshold))
867 {
868 // we split in two part
869 const double tx = (span->tmin + span->tmax) / 2.0;
870 float c[2] = { 0.0f, 0.0f };
871 float b[2] = { 0.0f, 0.0f };
872 _brush_sample_t mid = { 0 };
873 /* the left half inherits our min end, now evaluated, and computes the midpoint; the right
874 * half is then handed that midpoint already evaluated, and inherits our max end */
875 const _brush_span_t left = { span->tmin, tx, points_min, c, border_min, b, TRUE, FALSE, have_border_min, FALSE };
876 _brush_points_recurs(p1, p2, &left, w, &mid);
877 const _brush_span_t right
878 = { tx, span->tmax, mid.point, points_max, mid.border, border_max, TRUE, TRUE, TRUE, have_border_max };
879 _brush_points_recurs(p1, p2, &right, w, out);
880 return;
881 }
882
883 // the leaf: one sample, at the far end of the span
884 out->point[0] = points_max[0];
885 out->point[1] = points_max[1];
886 out->have_border = FALSE;
887 dt_masks_dynbuf_add_2(dpoints, out->point[0], out->point[1]);
888
889 if(withborder)
890 {
891 const float radius = _brush_radius_at(p1[4], p2[4], span->tmax);
892 const float chord[2] = { p2[0] - p1[0], p2[1] - p1[1] };
893 _brush_leaf_borrow_border(border_min, border_max, have_border_min, have_border_max, points_max, chord, radius);
894
895 // we check gaps in the border (sharp edges), at the density the arc will be sampled at
896 const int gap = 2 * w->arc_step;
897 if(abs((int)border_max[0] - (int)border_min[0]) > gap || abs((int)border_max[1] - (int)border_min[1]) > gap)
898 _brush_points_recurs_border_small_gaps(points_max, border_min, border_max, dpoints, dborder, w->arc_step);
899
900 out->border[0] = border_max[0];
901 out->border[1] = border_max[1];
902 out->have_border = TRUE;
903 dt_masks_dynbuf_add_2(dborder, out->border[0], out->border[1]);
904 }
905
906 if(!IS_NULL_PTR(dpayload))
907 {
908 out->payload[0] = p1[5] + span->tmax * (p2[5] - p1[5]);
909 out->payload[1] = p1[6] + span->tmax * (p2[6] - p1[6]);
910 _brush_payload_sync(dpayload, dpoints, out->payload[0], out->payload[1]);
911 }
912}
913
914/* The two ends of one segment of the walk, in image pixels: node, the control point that
915 * faces the other end, radius, fading, density. Travelling forward the segment leaves node
916 * `from` through its ctrl2 and enters `to` through its ctrl1; backward it is the other pair.
917 * The radii are read the way the walk always has: the start of a segment carries its node's
918 * border[1] and the end its node's border[0]. Every writer in this file sets the two together,
919 * so they agree; the walk takes the larger wherever it has to choose one for a node. */
920static void _brush_segment_load(const dt_masks_node_brush_t *const from, const dt_masks_node_brush_t *const to,
921 const gboolean forward, const _brush_frame_t *const frame, float p1[7],
922 float p2[7])
923{
924 const float *const from_ctrl = forward ? from->ctrl2 : from->ctrl1;
925 const float *const to_ctrl = forward ? to->ctrl1 : to->ctrl2;
926 const float radius_scale = MIN(frame->iwd, frame->iht);
927
928 p1[0] = from->node[0] * frame->iwd - frame->dx;
929 p1[1] = from->node[1] * frame->iht - frame->dy;
930 p1[2] = from_ctrl[0] * frame->iwd - frame->dx;
931 p1[3] = from_ctrl[1] * frame->iht - frame->dy;
932 p1[4] = from->border[1] * radius_scale;
933 p1[5] = from->fading;
934 p1[6] = from->density;
935
936 p2[0] = to->node[0] * frame->iwd - frame->dx;
937 p2[1] = to->node[1] * frame->iht - frame->dy;
938 p2[2] = to_ctrl[0] * frame->iwd - frame->dx;
939 p2[3] = to_ctrl[1] * frame->iht - frame->dy;
940 p2[4] = to->border[0] * radius_scale;
941 p2[5] = to->fading;
942 p2[6] = to->density;
943}
944
945/* The arc that bridges a joint, the short way round; at a cusp the pass's rotation decides
946 * (see dt_masks_outline_short_way()). The filler is the brush's own. */
947static void _brush_joint_arc(const float *const centre, const float *const from, const float *const to,
948 const gboolean forward, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
949 const int step)
950{
951 const gboolean clockwise = dt_masks_outline_short_way(centre, from, to, forward);
952 float f[2] = { from[0], from[1] };
953 float t[2] = { to[0], to[1] };
954 float c[2] = { centre[0], centre[1] };
955 _brush_points_recurs_border_gaps(c, f, t, dpoints, dborder, clockwise, step);
956}
957
958/* The walk at the node the next segment starts from: the end sample of the previous
959 * non-degenerate segment of this pass. */
961{
962 gboolean have_prev;
963 float c[2];
964 float b[2];
965 float r;
966 float payload[2];
968
969static inline void _brush_walk_sync_payload(const _brush_walk_t *const w, const float *const payload)
970{
971 if(!IS_NULL_PTR(w->dpayload)) _brush_payload_sync(w->dpayload, w->dpoints, payload[0], payload[1]);
972}
973
974/* What the node a segment starts from needs, once both its segments are known: a disc where
975 * the stroke changes attribute, an arc where it changes direction. @p p1/@p p2 are the segment
976 * about to be walked, @p c0/@p b0 its start sample. */
977static void _brush_walk_node(const _brush_walk_t *const w, const _brush_walk_state_t *const s,
978 const float *const p1, const float *const p2, const float *const c0,
979 const float *const b0, const gboolean forward)
980{
981 if(IS_NULL_PTR(w->dborder)) return;
982
983 /* The stroke changes attribute along this segment: the node it starts from gets a full disc
984 * at the attribute it leaves behind, so an opacity or size step reads as a round junction
985 * and not as a seam across the stroke. Same triggers as ever; the radius is the larger of the
986 * two the node carries, which is the disc the union actually has there. */
987 const gboolean payload_step = (fabsf(p1[5] - p2[5]) > 0.05f || fabsf(p1[6] - p2[6]) > 0.05f);
988 const gboolean radius_step = (fabsf(p1[4] - p2[4]) > 0.0001f || fabsf(s->r - p1[4]) > 0.0001f);
989 if(payload_step || radius_step)
990 {
991 _brush_points_stamp(s->c, s->b, fmaxf(s->r, p1[4]), w->dpoints, w->dborder, w->arc_step);
993 }
994
995 /* The border of the previous segment ends on one side of the joint and this one's starts on
996 * the other; this arc bridges the wedge between them, centred on the node. Without it the
997 * rasteriser has no spokes across the joint and the stroke loses its radius toward the node
998 * -- at a cusp that is a sharp V hole in the exported mask (issue #1313's follow-up). On the
999 * concave side of a joint the short sweep runs through the inside of the stroke, which
1000 * paints nothing new; those samples are inside the disc union, so the boundary pass hides
1001 * them from the outline. */
1002 if(fabsf(b0[0] - s->b[0]) > 1.0f || fabsf(b0[1] - s->b[1]) > 1.0f)
1003 {
1004 _brush_joint_arc(c0, s->b, b0, forward, w->dpoints, w->dborder, w->arc_step);
1006 }
1007}
1008
1009/* One segment of one pass: the node work at its start, then every sample within a pixel of
1010 * the last and its border. Returns FALSE for a degenerate segment, which contributes nothing. */
1011static gboolean _brush_walk_segment(const _brush_walk_t *const w, _brush_walk_state_t *const s, const int k,
1012 const int k1, const gboolean forward)
1013{
1014 float p1[7];
1015 float p2[7];
1016 _brush_segment_load(w->nodes[k], w->nodes[k1], forward, &w->frame, p1, p2);
1017
1018 /* the segment's own end samples; a segment with a direction at neither end is a point */
1019 float c0[2];
1020 float b0[2];
1021 float c1[2];
1022 float b1[2];
1023 const gboolean have_b0 = _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], 0.0f,
1024 p1[4], 0.0f, c0, c0 + 1, b0, b0 + 1);
1025 const gboolean have_b1 = _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], 1.0f,
1026 p2[4], 0.0f, c1, c1 + 1, b1, b1 + 1);
1027 if(!have_b0 && !have_b1) return FALSE;
1028 if(!have_b0) dt_masks_outline_offset_along(c0, b1[0] - c1[0], b1[1] - c1[1], p1[4], b0);
1029 if(!have_b1) dt_masks_outline_offset_along(c1, b0[0] - c0[0], b0[1] - c0[1], p2[4], b1);
1030
1031 if(s->have_prev) _brush_walk_node(w, s, p1, p2, c0, b0, forward);
1032
1033 float bmin[2] = { b0[0], b0[1] };
1034 float bmax[2] = { b1[0], b1[1] };
1035 float cmin[2] = { c0[0], c0[1] };
1036 float cmax[2] = { c1[0], c1[1] };
1037 const _brush_span_t span = { 0.0, 1.0, cmin, cmax, bmin, bmax, TRUE, TRUE, TRUE, TRUE };
1038 _brush_sample_t end = { 0 };
1039 _brush_points_recurs(p1, p2, &span, w, &end);
1040
1041 dt_masks_dynbuf_add_2(w->dpoints, end.point[0], end.point[1]);
1043 if(!IS_NULL_PTR(w->dborder))
1044 {
1045 if(!end.have_border)
1046 dt_masks_outline_offset_along(end.point, b1[0] - c1[0], b1[1] - c1[1], p2[4], end.border);
1047 dt_masks_dynbuf_add_2(w->dborder, end.border[0], end.border[1]);
1048 }
1049
1050 s->c[0] = end.point[0];
1051 s->c[1] = end.point[1];
1052 s->b[0] = end.border[0];
1053 s->b[1] = end.border[1];
1054 s->r = p2[4];
1055
1056 s->payload[0] = end.payload[0];
1057 s->payload[1] = end.payload[1];
1058 s->have_prev = TRUE;
1059 return TRUE;
1060}
1061
1062/* The cap: from the last border sample of a pass around the outside of the end node to the
1063 * opposite side, where the other pass takes over. */
1064static void _brush_walk_cap(const _brush_walk_t *const w, const _brush_walk_state_t *const s)
1065{
1066 if(IS_NULL_PTR(w->dborder)) return;
1067 float centre[2] = { s->c[0], s->c[1] };
1068 float from[2] = { s->b[0], s->b[1] };
1069 float opposite[2] = { 2.0f * s->c[0] - s->b[0], 2.0f * s->c[1] - s->b[1] };
1070 _brush_points_recurs_border_gaps(centre, from, opposite, w->dpoints, w->dborder, TRUE, w->arc_step);
1072}
1073
1074/* One pass over the segments, forward or backward, with the border on the right of travel.
1075 * Returns whether any segment had a direction, i.e. whether anything at all was walked. */
1076static gboolean _brush_walk_pass(const _brush_walk_t *const w, const gboolean forward)
1077{
1078 _brush_walk_state_t s = { 0 };
1079 const int last = w->node_count - 1;
1080 for(int step = 0; step < last; step++)
1081 {
1082 const int k = forward ? step : last - step;
1083 const int k1 = forward ? step + 1 : last - 1 - step;
1084 _brush_walk_segment(w, &s, k, k1, forward);
1085 }
1086 if(s.have_prev) _brush_walk_cap(w, &s);
1087 return s.have_prev;
1088}
1089
1090/* Every segment is a point: the whole brush is one dab. It still has a radius and a payload,
1091 * so it is still a disc; stamp it once, from the first node. */
1092static void _brush_walk_single_dab(const _brush_walk_t *const w)
1093{
1094 const dt_masks_node_brush_t *const n0 = w->nodes[0];
1095 const float centre[2] = { n0->node[0] * w->frame.iwd - w->frame.dx, n0->node[1] * w->frame.iht - w->frame.dy };
1096 const float radius = fmaxf(n0->border[0], n0->border[1]) * MIN(w->frame.iwd, w->frame.iht);
1097 const float from[2] = { centre[0] + radius, centre[1] };
1098 dt_masks_dynbuf_add_2(w->dpoints, centre[0], centre[1]);
1099 if(!IS_NULL_PTR(w->dborder))
1100 {
1101 dt_masks_dynbuf_add_2(w->dborder, from[0], from[1]);
1102 _brush_points_stamp(centre, from, radius, w->dpoints, w->dborder, w->arc_step);
1103 }
1104 const float payload[2] = { n0->fading, n0->density };
1105 _brush_walk_sync_payload(w, payload);
1106}
1107
1108/* The three sample buffers of a walk: points always, border and payload only when the caller
1109 * asked for them. All-or-nothing: on failure nothing is left allocated. */
1110static gboolean _brush_bufs_alloc(const gboolean want_border, const gboolean want_payload,
1111 dt_masks_dynbuf_t **dpoints, dt_masks_dynbuf_t **dborder,
1112 dt_masks_dynbuf_t **dpayload)
1113{
1114 *dpoints = dt_masks_dynbuf_init(1000000, "brush dpoints");
1115 *dborder = want_border ? dt_masks_dynbuf_init(1000000, "brush dborder") : NULL;
1116 *dpayload = want_payload ? dt_masks_dynbuf_init(1000000, "brush dpayload") : NULL;
1117 if(!IS_NULL_PTR(*dpoints) && (!want_border || !IS_NULL_PTR(*dborder))
1118 && (!want_payload || !IS_NULL_PTR(*dpayload)))
1119 return TRUE;
1120 dt_masks_dynbuf_free(*dpoints);
1121 dt_masks_dynbuf_free(*dborder);
1122 dt_masks_dynbuf_free(*dpayload);
1123 *dpoints = NULL;
1124 *dborder = NULL;
1125 *dpayload = NULL;
1126 return FALSE;
1127}
1128
1129/* A harvested outline, the two arrays a transform acts on. */
1130typedef struct _brush_outline_t
1131{
1132 float *points;
1134 float *border; /* NULL when no border was built */
1137
1138/* Transform the harvested outline with the pipeline's distortions. Two cases: the source of a
1139 * clone, which is walked in place and then carried onto its own position -- backwards through
1140 * the modules before the owner, shifted, forward through the rest -- and everything else, which
1141 * takes the one direction it was asked for, border included. Returns FALSE when a transform
1142 * failed; the caller owns the buffers either way. */
1143static gboolean _brush_outline_transform(dt_develop_t *develop, const dt_masks_form_t *const mask_form,
1144 const double iop_order, const int transform_direction,
1145 const dt_masks_distort_t *const dist, const int use_source,
1146 const _brush_outline_t *const o)
1147{
1148 if(use_source && transform_direction == DT_DEV_TRANSFORM_DIR_ALL)
1149 {
1150 // we transform with all distortion that happen *before* the module
1151 // so we have now the TARGET points in module input reference
1153 return TRUE;
1154
1155 // now we move all the points by the shift
1156 // so we have now the SOURCE points in module input reference
1157 float pts[2] = { mask_form->source[0], mask_form->source[1] };
1160
1161 const float dx = pts[0] - o->points[2];
1162 const float dy = pts[1] - o->points[3];
1163 float *const points = o->points;
1164 const int count = o->point_count;
1165 __OMP_PARALLEL_FOR_SIMD__(if(count > 100) aligned(points:64))
1166 for(int i = 0; i < count; i++)
1167 {
1168 points[i * 2] += dx;
1169 points[i * 2 + 1] += dy;
1170 }
1171
1172 // we apply the rest of the distortions (those after the module)
1173 // so we have now the SOURCE points in final image reference
1175 }
1176
1177 if(!dt_masks_distort_transform(dist, iop_order, transform_direction, o->points, o->point_count)) return FALSE;
1178 if(IS_NULL_PTR(o->border)) return TRUE;
1179 return dt_masks_distort_transform(dist, iop_order, transform_direction, o->border, o->border_count);
1180}
1181
1184// Brush points are stored in a cyclic way because the border goes around the main line.
1185// This means that it record the main line twice (up and down) while the border only once (around).
1187 const double iop_order, const int transform_direction,
1188 const dt_masks_distort_t *const dist, float **point_buffer, int *point_count,
1189 float **border_buffer, int *border_count, float **payload_buffer,
1190 int *payload_count, dt_masks_skip_range_t **border_skips,
1191 int *border_skip_count, int use_source)
1192{
1193 *point_buffer = NULL;
1194 *point_count = 0;
1195 if(border_buffer) *border_buffer = NULL;
1196 if(!IS_NULL_PTR(border_buffer)) *border_count = 0;
1197 if(payload_buffer) *payload_buffer = NULL;
1198 if(!IS_NULL_PTR(payload_buffer)) *payload_count = 0;
1199 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
1200 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
1201
1202 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1203 double start2 = 0.0;
1205
1206 const float iwd = dist->iwidth;
1207 const float iht = dist->iheight;
1208 // How far apart consecutive samples of the outline may land, in image pixels. Decided by the
1209 // pipe, never here; see dt_dev_pixelpipe_t::mask_rasterization_step. Above 1 the samples
1210 // spread out and the radial spokes stamped from them leave gaps between each pair (#1116).
1211 const int pixel_threshold = dist->rasterization_step;
1212
1213 dt_masks_dynbuf_t *dpoints = NULL;
1214 dt_masks_dynbuf_t *dborder = NULL;
1215 dt_masks_dynbuf_t *dpayload = NULL;
1216 if(!_brush_bufs_alloc(!IS_NULL_PTR(border_buffer), !IS_NULL_PTR(payload_buffer), &dpoints, &dborder, &dpayload))
1217 return 1;
1218
1219 // we store all points
1220 float dx = 0.0f;
1221 float dy = 0.0f;
1222
1223 if(use_source && mask_form->points && transform_direction != DT_DEV_TRANSFORM_DIR_ALL)
1224 {
1225 dt_masks_node_brush_t *pt = (dt_masks_node_brush_t *)mask_form->points->data;
1226 dx = (pt->node[0] - mask_form->source[0]) * iwd;
1227 dy = (pt->node[1] - mask_form->source[1]) * iht;
1228 }
1229
1230 const guint node_count = g_list_length(mask_form->points);
1231
1232 dt_masks_node_brush_t **nodes = dt_alloc_align((size_t)node_count * sizeof(*nodes));
1233 if(!nodes)
1234 {
1235 dt_masks_dynbuf_free(dpoints);
1236 dt_masks_dynbuf_free(dborder);
1237 dt_masks_dynbuf_free(dpayload);
1238 return 1;
1239 }
1240
1241 guint node_index = 0;
1242 for(GList *form_points = mask_form->points; form_points; form_points = g_list_next(form_points))
1243 {
1244 const dt_masks_node_brush_t *const pt = (dt_masks_node_brush_t *)form_points->data;
1245 nodes[node_index++] = (dt_masks_node_brush_t *)form_points->data;
1246 float *const buf = dt_masks_dynbuf_reserve_n(dpoints, 6);
1247 if (buf)
1248 {
1249 buf[0] = pt->ctrl1[0] * iwd - dx;
1250 buf[1] = pt->ctrl1[1] * iht - dy;
1251 buf[2] = pt->node[0] * iwd - dx;
1252 buf[3] = pt->node[1] * iht - dy;
1253 buf[4] = pt->ctrl2[0] * iwd - dx;
1254 buf[5] = pt->ctrl2[1] * iht - dy;
1255 }
1256 }
1257
1258 // for the border, we store value too
1259 if(!IS_NULL_PTR(dborder))
1260 {
1261 dt_masks_dynbuf_add_zeros(dborder, 6 * node_count); // we need six zeros for each border point
1262 }
1263
1264 // for the payload, we reserve an equivalent number of cells to keep it in sync
1265 if(!IS_NULL_PTR(dpayload))
1266 {
1267 dt_masks_dynbuf_add_zeros(dpayload, 6 * node_count); // we need six zeros for each border point
1268 }
1269
1271 {
1272 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points init took %0.04f sec\n", mask_form->name,
1273 dt_get_wtime() - start2);
1274 start2 = dt_get_wtime();
1275 }
1276
1277 const _brush_walk_t walk = { .nodes = nodes,
1278 .node_count = (int)node_count,
1279 .frame = { iwd, iht, dx, dy },
1280 .pixel_threshold = pixel_threshold,
1281 .arc_step = IS_NULL_PTR(dist->pipe) ? pixel_threshold : 1,
1282 .dpoints = dpoints,
1283 .dborder = dborder,
1284 .dpayload = dpayload };
1285 if(_brush_walk_pass(&walk, TRUE))
1286 _brush_walk_pass(&walk, FALSE);
1287 else
1289
1290 dt_free_align(nodes);
1291
1292 *point_count = dt_masks_dynbuf_position(dpoints) / 2;
1293 *point_buffer = dt_masks_dynbuf_harvest(dpoints);
1294 dt_masks_dynbuf_free(dpoints);
1295
1296 if(!IS_NULL_PTR(dborder))
1297 {
1298 *border_count = dt_masks_dynbuf_position(dborder) / 2;
1299 *border_buffer = dt_masks_dynbuf_harvest(dborder);
1300 dt_masks_dynbuf_free(dborder);
1301 }
1302
1303 if(!IS_NULL_PTR(dpayload))
1304 {
1305 *payload_count = dt_masks_dynbuf_position(dpayload) / 2;
1306 *payload_buffer = dt_masks_dynbuf_harvest(dpayload);
1307 dt_masks_dynbuf_free(dpayload);
1308 }
1309
1310 // printf("points %d, border %d, playload %d\n", *points_count, border ? *border_count : -1, payload ?
1311 // *payload_count : -1);
1312
1314 {
1315 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points point recurs %0.04f sec\n", mask_form->name,
1316 dt_get_wtime() - start2);
1317 start2 = dt_get_wtime();
1318 }
1319
1320 // and we transform them with all distorted modules
1321 const _brush_outline_t outline = { .points = *point_buffer,
1322 .point_count = *point_count,
1323 .border = IS_NULL_PTR(border_buffer) ? NULL : *border_buffer,
1324 .border_count = IS_NULL_PTR(border_buffer) ? 0 : *border_count };
1325 if(_brush_outline_transform(develop, mask_form, iop_order, transform_direction, dist, use_source, &outline))
1326 {
1328 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points transform took %0.04f sec\n", mask_form->name,
1329 dt_get_wtime() - start2);
1330 return 0;
1331 }
1332
1333 // if we failed, then free all and return
1334 dt_pixelpipe_cache_free_align(*point_buffer);
1335 *point_buffer = NULL;
1336 *point_count = 0;
1337 if(!IS_NULL_PTR(border_buffer))
1338 {
1339 dt_pixelpipe_cache_free_align(*border_buffer);
1340 *border_buffer = NULL;
1341 *border_count = 0;
1342 }
1343 if(!IS_NULL_PTR(payload_buffer))
1344 {
1345 dt_pixelpipe_cache_free_align(*payload_buffer);
1346 *payload_buffer = NULL;
1347 *payload_count = 0;
1348 }
1349 return 1;
1350}
1351
1355static void _brush_get_distance(float point_x, float point_y, float radius,
1356 dt_masks_form_gui_t *mask_gui, int form_index,
1357 int corner_count, int *inside, int *inside_border,
1358 int *near_handle, int *inside_source, float *distance)
1359{
1360 if(IS_NULL_PTR(mask_gui)) return;
1361
1362 // initialise returned values
1363 *inside_source = 0;
1364 *inside = 0;
1365 *inside_border = 0;
1366 *near_handle = -1;
1367 *distance = FLT_MAX;
1368
1369 dt_masks_form_gui_points_t *gui_points
1370 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
1371 if(IS_NULL_PTR(gui_points)) return;
1372 /* Nothing to walk when the cursor cannot reach a sample: the box every sample spans, grown by
1373 * the cursor's reach, answers for the whole shape in four comparisons, and every answer
1374 * initialised above is what the walk would have given -- nothing inside, nothing near. */
1375 if(!dt_masks_gui_points_reach(gui_points, point_x, point_y, 2.0f * radius)) return;
1376
1377 float min_dist = FLT_MAX;
1378
1379 const float radius2 = radius * radius;
1380
1381 // we first check if we are inside the source form
1382
1383 // add support for clone masks
1384 if(gui_points->points && gui_points->source
1385 && gui_points->points_count > 2 + corner_count * 3
1386 && gui_points->source_count > 2 + corner_count * 3)
1387 {
1388 // distance from form origin and source origin
1389 const float dx = -gui_points->points[2] + gui_points->source[2];
1390 const float dy = -gui_points->points[3] + gui_points->source[3];
1391
1392 int current_seg = 1;
1393 for(int i = corner_count * 3; i < gui_points->points_count; i++)
1394 {
1395 // do we change of path segment ?
1396 if(gui_points->points[i * 2 + 1] == gui_points->points[current_seg * 6 + 3]
1397 && gui_points->points[i * 2] == gui_points->points[current_seg * 6 + 2])
1398 {
1399 current_seg = (current_seg + 1) % corner_count;
1400 }
1401 // distance from tested point to current form point
1402 const float yy = gui_points->points[i * 2 + 1] + dy;
1403 const float xx = gui_points->points[i * 2] + dx;
1404
1405 const float sdx = point_x - xx;
1406 const float sdy = point_y - yy;
1407 const float dd = (sdx * sdx) + (sdy * sdy);
1408 if(dd < min_dist)
1409 {
1410 min_dist = dd;
1411
1412 if(dd < radius2 && *inside == 0)
1413 {
1414 if(current_seg == 0)
1415 *inside_source = corner_count - 1;
1416 else
1417 *inside_source = current_seg - 1;
1418
1419 if(*inside_source)
1420 {
1421 *inside = 1;
1422 }
1423 }
1424 }
1425 }
1426 }
1427
1428 // we check if we are near a segment of the centerline. This runs BEFORE the outline test:
1429 // the outline wraps the whole stroke, so on a thin brush both are within cursor reach at
1430 // once, and the segment is the one carrying an action (move it, Ctrl+Click to add a node)
1431 // while the outline has none of its own.
1432 if(gui_points->points && gui_points->points_count > 2 + corner_count * 3)
1433 {
1434 // Own accumulator: the source pass above walks a different outline, and its distances
1435 // must not veto a segment hit on the form itself.
1436 float min_dist_points = FLT_MAX;
1437 int current_seg = 1;
1438 for(int i = corner_count * 3; i < gui_points->points_count; i++)
1439 {
1440 // do we change of path segment ?
1441 if(gui_points->points[i * 2 + 1] == gui_points->points[current_seg * 6 + 3]
1442 && gui_points->points[i * 2] == gui_points->points[current_seg * 6 + 2])
1443 {
1444 current_seg = (current_seg + 1) % corner_count;
1445 }
1446 //distance from tested point to current form point
1447 const float yy = gui_points->points[i * 2 + 1];
1448 const float xx = gui_points->points[i * 2];
1449
1450 const float dx = point_x - xx;
1451 const float dy = point_y - yy;
1452 const float dd = (dx * dx) + (dy * dy);
1453 if(dd < min_dist_points)
1454 {
1455 min_dist_points = dd;
1456
1457 if(current_seg > 0 && dd < radius2)
1458 {
1459 *near_handle = current_seg - 1;
1460 }
1461 }
1462 }
1463 min_dist = MIN(min_dist, min_dist_points);
1464 }
1465
1466 // we check if it's inside borders
1467 if(gui_points->border && gui_points->border_count > 2 + corner_count * 3)
1468 {
1469 int nearest = -1;
1470
1471 const int start = corner_count * 3;
1472 const float *const border = gui_points->border;
1473 float last_y = border[gui_points->border_count * 2 - 1];
1474 int crossings = 0;
1475
1476 for(int i = start; i < gui_points->border_count; i++)
1477 {
1478 const int idx = i * 2;
1479 const float xx = border[idx];
1480 const float yy = border[idx + 1];
1481
1482 const float dx = point_x - xx;
1483 const float dy = point_y - yy;
1484 if(dx * dx + dy * dy < radius2) nearest = idx;
1485
1486 if(((point_y <= yy && point_y > last_y) || (point_y >= yy && point_y < last_y)) && (xx > point_x))
1487 crossings++;
1488
1489 last_y = yy;
1490 }
1491
1492 // Being enclosed by the outline is what makes the stroke hoverable at all, but it is not
1493 // a hit on the border itself: only proximity to the outline is, and only where no
1494 // centerline segment is closer. Reporting the whole band as border made the segment
1495 // unreachable, since the shared hit test answers on the border before the segment.
1496 *inside = (nearest != -1 || (crossings & 1));
1497 *inside_border = (nearest != -1) && (*near_handle < 0);
1498 }
1499
1500 *distance = min_dist;
1501}
1502
1503
1507 float **point_buffer, int *point_count,
1508 float **border_buffer, int *border_count,
1509 dt_masks_skip_range_t **border_skips, int *border_skip_count,
1510 int use_source, const dt_iop_module_t *module)
1511{
1512 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
1513 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
1514
1515 // Asking for the source outline without a module is a programming error, not an empty shape.
1516 if(use_source && IS_NULL_PTR(module)) return DT_MASKS_RASTER_ERROR;
1517 const double ioporder = (module) ? module->iop_order : 0.0f;
1518 const dt_masks_distort_t gui_dist = dt_masks_distort_for_gui(develop);
1519 const int status
1520 = _brush_get_pts_border(develop, mask_form, ioporder, DT_DEV_TRANSFORM_DIR_ALL,
1521 &gui_dist, point_buffer, point_count, border_buffer,
1522 border_count, NULL, NULL, border_skips, border_skip_count, use_source);
1523
1524 /* This outline feeds the GUI only -- the rasterisers build their own from the pixel path and
1525 * paint every spoke. What the outline shows is the BOUNDARY of what they paint, decided per
1526 * sample by dt_masks_outline_boundary_skips(); the excluded samples travel out-of-band as skip
1527 * ranges, which is what every consumer of this outline already reads. */
1528 if(status == 0 && !IS_NULL_PTR(border_buffer) && !IS_NULL_PTR(*border_buffer)
1529 && !IS_NULL_PTR(border_skips) && !IS_NULL_PTR(border_skip_count))
1530 {
1531 const int header = (int)g_list_length(mask_form->points) * 3;
1532 dt_pixelpipe_cache_free_align(*border_skips);
1533 *border_skips = NULL;
1534 *border_skip_count = dt_masks_outline_boundary_skips(*point_buffer, *border_buffer, *border_count, header,
1535 border_skips);
1536 }
1537
1539}
1540
1544static float _brush_get_position_in_segment(float point_x, float point_y,
1545 dt_masks_form_t *mask_form, int segment_index)
1546{
1547 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1548 GList *first_node_entry = g_list_nth(mask_form->points, segment_index);
1549 dt_masks_node_brush_t *point0 = (dt_masks_node_brush_t *)first_node_entry->data;
1550 // advance to next node in list, if not already on the last
1551 GList *next_node_entry = g_list_next_bounded(first_node_entry);
1552 dt_masks_node_brush_t *point1 = (dt_masks_node_brush_t *)next_node_entry->data;
1553 next_node_entry = g_list_next_bounded(next_node_entry);
1554 dt_masks_node_brush_t *point2 = (dt_masks_node_brush_t *)next_node_entry->data;
1555 next_node_entry = g_list_next_bounded(next_node_entry);
1556 dt_masks_node_brush_t *point3 = (dt_masks_node_brush_t *)next_node_entry->data;
1557
1558 float best_t = 0.0f;
1559 float best_dist2 = FLT_MAX;
1560
1561 for(int sample_index = 0; sample_index <= 100; sample_index++)
1562 {
1563 const float t = sample_index / 100.0f;
1564 float sample_x = 0.0f;
1565 float sample_y = 0.0f;
1566 _brush_get_XY(point0->node[0], point0->node[1], point1->node[0], point1->node[1],
1567 point2->node[0], point2->node[1], point3->node[0], point3->node[1], t, &sample_x, &sample_y);
1568
1569 const float dist2 = (point_x - sample_x) * (point_x - sample_x)
1570 + (point_y - sample_y) * (point_y - sample_y);
1571 if(dist2 < best_dist2)
1572 {
1573 best_dist2 = dist2;
1574 best_t = t;
1575 }
1576 }
1577
1578 return best_t;
1579}
1580
1586static gboolean _brush_border_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index,
1587 float *handle_x, float *handle_y, void *user_data)
1588{
1589 return _brush_get_border_handle_mirrored(gui_points, node_count, node_index, handle_x, handle_y);
1590}
1591
1597static void _brush_curve_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
1598 float *handle_x, float *handle_y, void *user_data)
1599{
1600 _brush_ctrl2_to_handle(gui_points->points[node_index * 6 + 2], gui_points->points[node_index * 6 + 3],
1601 gui_points->points[node_index * 6 + 4], gui_points->points[node_index * 6 + 5],
1602 handle_x, handle_y, TRUE);
1603}
1604
1608static void _brush_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
1609 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
1610 int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
1611{
1612 _brush_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, node_count,
1613 inside, inside_border, near_handle, inside_source, dist);
1614}
1615
1616static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
1617{
1618 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, -1,
1620 _brush_distance_cb, NULL, NULL);
1621}
1622
1623
1624static int _init_fading(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1625 const float amount, const dt_masks_increment_t increment, const int flow)
1626{
1627 const float masks_fading = dt_masks_get_set_conf_value_with_toast(mask_form, "fading", amount,
1628 FADING_MIN, FADING_MAX, increment, flow,
1629 _("fading: %3.2f%%"), 100.0f);
1630 if(mask_gui->guipoints_count > 0)
1631 dt_masks_dynbuf_set(mask_gui->guipoints_payload, -3, masks_fading);
1632 return 1;
1633}
1634
1635static int _init_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1636 const float amount, const dt_masks_increment_t increment, const int flow)
1637{
1638 const float masks_border = dt_masks_get_set_conf_value_with_toast(mask_form, "border", amount,
1639 FADING_MIN, FADING_MAX, increment, flow,
1640 _("size: %3.2f%%"), 2.f * 100.f);
1641 if(mask_gui->guipoints_count > 0)
1642 dt_masks_dynbuf_set(mask_gui->guipoints_payload, -4, masks_border);
1643 return 1;
1644}
1645
1646static int _init_opacity(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1647 const float amount, const dt_masks_increment_t increment, const int flow)
1648{
1649 dt_masks_get_set_conf_value_with_toast(mask_form, "opacity", amount, 0.f, 1.f,
1650 increment, flow, _("opacity: %3.2f%%"), 100.f);
1651 return 1;
1652}
1653
1655{
1656 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return NAN;
1657
1658 switch(interaction)
1659 {
1661 {
1662 const float size = dt_masks_get_form_size_from_nodes(mask_form->points);
1663 if(size <= 0.0f) return NAN;
1664 return size;
1665 }
1667 {
1668 float fading_sum = 0.0f;
1669 int fading_count = 0;
1670
1671 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1672 {
1673 const dt_masks_node_brush_t *node = (const dt_masks_node_brush_t *)point_node->data;
1674 if(IS_NULL_PTR(node)) continue;
1675 fading_sum += node->fading;
1676 fading_count++;
1677 }
1678
1679 return fading_count > 0 ? fading_sum / (float)fading_count : NAN;
1680 }
1681 default:
1682 return NAN;
1683 }
1684}
1685
1686static gboolean _brush_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
1687{
1688 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return FALSE;
1689
1690 const int points_count = g_list_length(mask_form->points);
1691 if(points_count <= 0) return FALSE;
1692
1693 float *point_buffer = dt_alloc_align_float((size_t)points_count * 2);
1694 if(IS_NULL_PTR(point_buffer)) return FALSE;
1695
1696 int i = 0;
1697 for(const GList *l = mask_form->points; l; l = g_list_next(l))
1698 {
1699 const dt_masks_node_brush_t *point = (const dt_masks_node_brush_t *)l->data;
1700 if(IS_NULL_PTR(point)) continue;
1701 point_buffer[2 * i] = point->node[0];
1702 point_buffer[2 * i + 1] = point->node[1];
1703 i++;
1704 }
1705
1706 const gboolean ok = dt_masks_center_of_gravity_from_points(point_buffer, i, center, area);
1707 dt_free_align(point_buffer);
1708 return ok;
1709}
1710
1711static int _change_fading(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1712 struct dt_iop_module_t *module, int index, const float amount,
1713 const dt_masks_increment_t increment, const int flow);
1714static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1715 struct dt_iop_module_t *module, int index, const float amount,
1716 const dt_masks_increment_t increment, const int flow);
1717
1719 dt_masks_increment_t increment, int flow,
1720 dt_masks_form_gui_t *mask_gui, struct dt_iop_module_t *module)
1721{
1722 if(IS_NULL_PTR(mask_form)) return NAN;
1723 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
1724 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
1725 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
1726 const int index = (!IS_NULL_PTR(mask_gui) && mask_gui->group_selected >= 0) ? mask_gui->group_selected : 0;
1727
1728 switch(interaction)
1729 {
1731 if(!_change_size(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1732 return _brush_get_interaction_value(mask_form, interaction);
1734 if(!_change_fading(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1735 return _brush_get_interaction_value(mask_form, interaction);
1736 default:
1737 return NAN;
1738 }
1739}
1740
1741static int _change_fading(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1742 struct dt_iop_module_t *module, int index, const float amount,
1743 const dt_masks_increment_t increment, const int flow)
1744{
1745 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1746 int node_index = 0;
1747 const float scale_amount = 1.f / powf(amount, (float)flow);
1748 const float offset_amount = -amount * (float)flow;
1749 float result_amount = 0.0f;
1750 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1751 {
1752 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1753 {
1754 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1755 const float current_fading = node->fading;
1756 result_amount = dt_masks_apply_increment_precomputed(current_fading, amount, scale_amount, offset_amount, increment);
1757
1758 node->fading = CLAMPF(result_amount, FADING_MIN, FADING_MAX);
1759 }
1760 }
1761
1762 dt_masks_get_set_conf_value(mask_form, "fading", result_amount, FADING_MIN, FADING_MAX, increment, flow);
1763
1764 // we recreate the form points
1765 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1766
1767 return 1;
1768}
1769
1770static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1771 struct dt_iop_module_t *module, int index, const float amount,
1772 const dt_masks_increment_t increment, const int flow)
1773{
1774 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1775 // Sanitize loop
1776 // do not exceed upper limit of 1.0 and lower limit of 0.004
1777 int node_index = 0;
1778 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1779 {
1780 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1781 {
1782 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1783 if(!node) continue;
1784 if(amount > 1.0f && (node->border[0] > 1.0f || node->border[1] > 1.0f))
1785 return 1;
1786 }
1787 }
1788
1789 // Growing/shrinking loop
1790 const float scale_amount = amount;
1791 const float offset_amount = amount;
1792 node_index = 0;
1793 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1794 {
1795 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1796 {
1797 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1798 if(!node) continue;
1799 node->border[0] = dt_masks_apply_increment_precomputed(node->border[0], amount,
1800 scale_amount, offset_amount, increment);
1801 node->border[1] = dt_masks_apply_increment_precomputed(node->border[1], amount,
1802 scale_amount, offset_amount, increment);
1803 }
1804 }
1805
1806 dt_masks_get_set_conf_value(mask_form, "border", amount, FADING_MIN, FADING_MAX, increment, flow);
1807
1808 // we recreate the form points
1809 if(!IS_NULL_PTR(mask_gui) && !IS_NULL_PTR(module)) dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1810
1811 return 1;
1812}
1813
1814/* Shape handlers receive widget-space coordinates, while normalized output-image
1815 * coordinates come from `mask_gui->rel_pos` and absolute output-image
1816 * coordinates come from `mask_gui->pos`. */
1817static int _brush_events_mouse_scrolled(struct dt_iop_module_t *module, double widget_x, double widget_y,
1818 int scroll_up, const int flow, uint32_t state, dt_masks_form_t *mask_form,
1819 int parentid, dt_masks_form_gui_t *mask_gui, int index,
1820 dt_masks_interaction_t interaction)
1821{
1822
1823
1824
1825 /* `state` is the caller's raw key state, kept for the callback signature: the property to
1826 * act on was already resolved from it by dt_masks_scroll_get_interaction(). A brush owns no
1827 * rotation, so that mapping falls through and the wheel does nothing here. Size and fading
1828 * apply to the selected node when there is one, to every node otherwise -- that scoping is
1829 * the selection's business (dt_masks_gui_change_affects_selected_node_or_all), not the
1830 * wheel's. */
1831 if(mask_gui->creation)
1832 {
1833 switch(interaction)
1834 {
1836 return _init_fading(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f,
1839 return _init_opacity(mask_form, parentid, mask_gui, scroll_up ? +0.02f : -0.02f,
1842 return _init_size(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f,
1844 default:
1845 return 0;
1846 }
1847 }
1848 else if(dt_masks_is_anything_selected(mask_gui) || mask_gui->node_hovered >= 0)
1849 {
1850 // we register the current position
1851 if(mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
1852 {
1853 mask_gui->scrollx = mask_gui->pos[0];
1854 mask_gui->scrolly = mask_gui->pos[1];
1855 }
1856
1857 switch(interaction)
1858 {
1860 return dt_masks_form_change_opacity(mask_gui->dev, mask_form, parentid, scroll_up, flow);
1862 return _change_fading(mask_form, parentid, mask_gui, module, index, scroll_up ? -0.01f : 0.01f,
1865 return _change_size(mask_form, parentid, mask_gui, module, index, scroll_up ? 1.02f : 0.98f,
1867 default:
1868 return 0;
1869 }
1870 }
1871 return 0;
1872}
1873
1874
1876{
1878 const char *psens = dt_conf_get_string_const("pressure_sensitivity");
1879 if(!IS_NULL_PTR(psens))
1880 {
1881 if(!strcmp(psens, "fading (absolute)"))
1883 else if(!strcmp(psens, "fading (relative)"))
1885 else if(!strcmp(psens, "opacity (absolute)"))
1887 else if(!strcmp(psens, "opacity (relative)"))
1889 else if(!strcmp(psens, "brush size (relative)"))
1891 }
1892}
1893
1894static void _add_node_to_segment(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parentid,
1895 dt_masks_form_gui_t *mask_gui, int index)
1896{
1897 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points) || IS_NULL_PTR(mask_gui)) return;
1898 const guint node_count = g_list_length(mask_form->points);
1899 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
1900 if(selected_segment < 0 || selected_segment >= (int)node_count) return;
1901
1902 // we add a new node to the brush
1903 dt_masks_node_brush_t *new_node = (dt_masks_node_brush_t *)(malloc(sizeof(dt_masks_node_brush_t)));
1904 if(IS_NULL_PTR(new_node)) return;
1905
1906 // set coordinates
1907 dt_masks_gui_cursor_to_raw_norm(mask_gui->dev, mask_gui, new_node->node);
1908 new_node->ctrl1[0] = new_node->ctrl1[1] = new_node->ctrl2[0] = new_node->ctrl2[1] = -1.0;
1910
1911 // set other attributes of the new node. we interpolate the starting and the end node of that
1912 // segment
1913 const float t = _brush_get_position_in_segment(new_node->node[0], new_node->node[1],
1914 mask_form, selected_segment);
1915 // start and end node of the segment
1916 GList *pt = g_list_nth(mask_form->points, selected_segment);
1917 if(IS_NULL_PTR(pt) || IS_NULL_PTR(pt->data))
1918 {
1919 dt_free(new_node);
1920 return;
1921 }
1922 dt_masks_node_brush_t *point0 = (dt_masks_node_brush_t *)pt->data;
1923 const GList *const next_pt = g_list_next_wraparound(pt, mask_form->points);
1924 if(IS_NULL_PTR(next_pt) || IS_NULL_PTR(next_pt->data))
1925 {
1926 dt_free(new_node);
1927 return;
1928 }
1929 dt_masks_node_brush_t *point1 = (dt_masks_node_brush_t *)next_pt->data;
1930 new_node->border[0] = point0->border[0] * (1.0f - t) + point1->border[0] * t;
1931 new_node->border[1] = point0->border[1] * (1.0f - t) + point1->border[1] * t;
1932 new_node->fading = point0->fading * (1.0f - t) + point1->fading * t;
1933 new_node->density = point0->density * (1.0f - t) + point1->density * t;
1934
1935 mask_form->points = g_list_insert(mask_form->points, new_node, selected_segment + 1);
1936 _brush_init_ctrl_points(mask_form);
1937
1938 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1939
1940 mask_gui->node_hovered = selected_segment + 1;
1941 mask_gui->node_selected = TRUE;
1942 mask_gui->node_selected_idx = selected_segment + 1;
1943 mask_gui->seg_hovered = -1;
1944 mask_gui->seg_selected = FALSE;
1945}
1946
1947static inline void _brush_translate_node(dt_masks_node_brush_t *node, const float delta_x, const float delta_y)
1948{
1949 dt_masks_translate_ctrl_node(node->node, node->ctrl1, node->ctrl2, delta_x, delta_y);
1950}
1951
1952static void _brush_translate_all_nodes(dt_masks_form_t *mask_form, const float delta_x, const float delta_y)
1953{
1954 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry))
1955 _brush_translate_node((dt_masks_node_brush_t *)node_entry->data, delta_x, delta_y);
1956}
1957
1958static int _brush_events_button_pressed(struct dt_iop_module_t *module, double widget_x, double widget_y,
1959 double pressure, int which, int type, uint32_t state,
1960 dt_masks_form_t *mask_form, int parentid,
1961 dt_masks_form_gui_t *mask_gui, int index)
1962{
1963 // double click or triple click: ignore here
1964 if(type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return 1;
1965
1966 // always start with a mask density of 100%, it will be adjusted with pen pressure if used.
1967 const float masks_density = 1.0f;
1968
1969 if(mask_gui->creation)
1970 {
1971 if(which == 1)
1972 {
1973 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
1974 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
1976 float masks_fading = dt_masks_get_set_conf_value(mask_form, "fading", 1.0f,
1978
1979 if(dt_modifier_is(state, DT_PRIMARY_MASK | GDK_SHIFT_MASK) || dt_modifier_is(state, GDK_SHIFT_MASK))
1980 {
1981 // set some absolute or relative position for the source of the clone mask
1982 if(mask_form->type & DT_MASKS_CLONE)
1984
1985 return 1;
1986 }
1987
1988 if(IS_NULL_PTR(mask_gui->guipoints)) mask_gui->guipoints = dt_masks_dynbuf_init(200000, "brush guipoints");
1989 if(IS_NULL_PTR(mask_gui->guipoints)) return 1;
1990 if(IS_NULL_PTR(mask_gui->guipoints_payload))
1991 mask_gui->guipoints_payload = dt_masks_dynbuf_init(400000, "brush guipoints_payload");
1992 if(IS_NULL_PTR(mask_gui->guipoints_payload)) return 1;
1993 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
1994 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_border, masks_fading);
1995 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_density, pressure);
1997 mask_gui->guipoints_count = 1;
1998
1999 // add support for clone masks
2000 if(mask_form->type & DT_MASKS_CLONE)
2001 dt_masks_set_source_pos_initial_value(mask_gui, mask_form);
2002 // not used by regular masks
2003 else
2004 mask_form->source[0] = mask_form->source[1] = 0.0f;
2005
2006 _get_pressure_sensitivity(mask_gui);
2007
2008 return 1;
2009 }
2010 }
2011
2012 dt_masks_form_gui_points_t *gui_points
2013 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2014 if(IS_NULL_PTR(gui_points)) return 0;
2015 const guint node_count = g_list_length(mask_form->points);
2016
2017 if(which == 1)
2018 {
2019 // The shape handler runs before the shared press-state selection update,
2020 // so concrete hovered targets must win over stale form/source selection.
2021 if(mask_gui->node_hovered >= 0)
2022 {
2023 // if ctrl is pressed, we change the type of point
2025 {
2027 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
2028 if(IS_NULL_PTR(node)) return 0;
2029 dt_masks_toggle_bezier_node_type(module, mask_form, mask_gui, index, gui_points,
2030 mask_gui->node_hovered, node->node, node->ctrl1, node->ctrl2,
2031 &node->state);
2032 return 1;
2033 }
2034 /*// we register the current position to avoid accidental move
2035 if(mask_gui->node_edited < 0 && mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
2036 {
2037 mask_gui->scrollx = pointer_x;
2038 mask_gui->scrolly = pointer_y;
2039 }*/
2040 mask_gui->delta[0] = gui_points->points[mask_gui->node_hovered * 6 + 2] - mask_gui->pos[0];
2041 mask_gui->delta[1] = gui_points->points[mask_gui->node_hovered * 6 + 3] - mask_gui->pos[1];
2042
2043 return 1;
2044 }
2045 else if(mask_gui->handle_hovered >= 0)
2046 {
2047 if(!dt_masks_node_is_cusp(gui_points, mask_gui->handle_hovered))
2048 {
2049 // we need to find the handle position
2050 float handle_x, handle_y;
2051 const int k = mask_gui->handle_hovered;
2052 _brush_ctrl2_to_handle(gui_points->points[k * 6 + 2], gui_points->points[k * 6 + 3],
2053 gui_points->points[k * 6 + 4], gui_points->points[k * 6 + 5],
2054 &handle_x, &handle_y, TRUE);
2055 // compute offsets
2056 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
2057 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
2058
2059 return 1;
2060 }
2061 }
2062 else if(mask_gui->handle_border_hovered >= 0)
2063 {
2064 float handle_x = 0.0f, handle_y = 0.0f;
2065 if(_brush_get_border_handle_mirrored(gui_points, node_count, mask_gui->handle_border_hovered,
2066 &handle_x, &handle_y))
2067 {
2068 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
2069 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
2070 }
2071
2072 return 1;
2073 }
2074 else if(mask_gui->seg_hovered >= 0)
2075 {
2076 mask_gui->node_hovered = -1;
2077
2079 {
2080 _add_node_to_segment(module, mask_form, parentid, mask_gui, index);
2081 }
2082 else
2083 {
2084 // we move the entire segment
2085 mask_gui->delta[0] = gui_points->points[mask_gui->seg_hovered * 6 + 2] - mask_gui->pos[0];
2086 mask_gui->delta[1] = gui_points->points[mask_gui->seg_hovered * 6 + 3] - mask_gui->pos[1];
2087 }
2088 return 1;
2089 }
2090 else if(mask_gui->source_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
2091 {
2092 // we start the source dragging
2093 mask_gui->delta[0] = gui_points->source[2] - mask_gui->pos[0];
2094 mask_gui->delta[1] = gui_points->source[3] - mask_gui->pos[1];
2095 return 1;
2096 }
2097 else if(mask_gui->form_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
2098 {
2099 // we start the form dragging
2100 mask_gui->delta[0] = gui_points->points[2] - mask_gui->pos[0];
2101 mask_gui->delta[1] = gui_points->points[3] - mask_gui->pos[1];
2102 return 1;
2103 }
2104 }
2105
2106 return 0;
2107}
2108
2110{
2111 float factor = 0.01f;
2112 const char *smoothing = dt_conf_get_string_const("brush_smoothing");
2113 if(!strcmp(smoothing, "low"))
2114 factor = 0.0025f;
2115 else if(!strcmp(smoothing, "medium"))
2116 factor = 0.01f;
2117 else if(!strcmp(smoothing, "high"))
2118 factor = 0.04f;
2119 return factor;
2120}
2121
2122static void _apply_pen_pressure(dt_masks_form_gui_t *mask_gui, float *payload_buffer)
2123{
2124 for(int i = 0; i < mask_gui->guipoints_count; i++)
2125 {
2126 float *payload = payload_buffer + 4 * i;
2127 float pressure = payload[3];
2128 payload[3] = 1.0f;
2129
2130 switch(mask_gui->pressure_sensitivity)
2131 {
2133 payload[0] = MAX(FADING_MIN, payload[0] * pressure);
2134 break;
2136 payload[1] = MAX(FADING_MIN, pressure);
2137 break;
2139 payload[1] = MAX(FADING_MIN, payload[1] * pressure);
2140 break;
2142 payload[2] = MAX(0.05f, pressure);
2143 break;
2145 payload[2] = MAX(0.05f, payload[2] * pressure);
2146 break;
2147 default:
2149 // ignore pressure value
2150 break;
2151 }
2152 }
2153}
2154
2155
2156static int _brush_events_button_released(struct dt_iop_module_t *module, double widget_x, double widget_y,
2157 int which, uint32_t state, dt_masks_form_t *mask_form, int parentid,
2158 dt_masks_form_gui_t *mask_gui, int index)
2159{
2160
2161
2162 if(IS_NULL_PTR(mask_gui)) return 0;
2163 dt_masks_form_gui_points_t *gui_points
2164 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2165 if(IS_NULL_PTR(gui_points)) return 0;
2166
2167 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
2168 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
2170
2171 if(mask_gui->creation && which == 1)
2172 {
2173 if(dt_modifier_is(state, GDK_SHIFT_MASK) || dt_modifier_is(state, DT_PRIMARY_MASK | GDK_SHIFT_MASK))
2174 {
2175 // user just set the source position, so just return
2176 return 1;
2177 }
2178
2179 dt_iop_module_t *creation_module = mask_gui->creation_module;
2180
2181 if(mask_gui->guipoints && mask_gui->guipoints_count > 0)
2182 {
2183 // if the path consists only of one x/y pair we add a second one close so we don't need to deal with
2184 // this special case later
2185 if(mask_gui->guipoints_count == 1)
2186 {
2187 // add a helper node very close to the single spot
2188 const float x = dt_masks_dynbuf_get(mask_gui->guipoints, -2) + 0.01f;
2189 const float y = dt_masks_dynbuf_get(mask_gui->guipoints, -1) - 0.01f;
2190 dt_masks_dynbuf_add_2(mask_gui->guipoints, x, y);
2191 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
2192 const float fading = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
2193 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
2194 const float pressure = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -1);
2195 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, border, fading);
2196 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
2197 mask_gui->guipoints_count++;
2198 }
2199
2200 float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
2201 float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
2202
2203 // we transform the points
2204 dt_dev_coordinates_image_abs_to_raw_norm(mask_gui->dev, guipoints, mask_gui->guipoints_count);
2205
2206 // we consolidate pen pressure readings into payload
2207 _apply_pen_pressure(mask_gui, guipoints_payload);
2208
2209 // accuracy level for node elimination, dependent on brush size
2210 const float epsilon2 = _get_brush_smoothing() * sqf(MAX(FADING_MIN, masks_border));
2211
2212 // we simplify the path and generate the nodes
2213 mask_form->points = _brush_ramer_douglas_peucker(guipoints, mask_gui->guipoints_count,
2214 guipoints_payload, epsilon2);
2215
2216 // printf("guipoints_count %d, points %d\n", mask_gui->guipoints_count, g_list_length(mask_form->points));
2217
2218 _brush_init_ctrl_points(mask_form);
2219
2222 mask_gui->guipoints = NULL;
2223 mask_gui->guipoints_payload = NULL;
2224 mask_gui->guipoints_count = 0;
2225
2226 dt_masks_gui_form_save_creation(mask_gui->dev, creation_module, mask_form, mask_gui);
2227
2228 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
2229 {
2231 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return 1;
2232 int group_index = 0;
2233 int selected_index = -1;
2234 for(GList *group_entry = grp->points; group_entry; group_entry = g_list_next(group_entry))
2235 {
2236 dt_masks_form_group_t *group_form = (dt_masks_form_group_t *)group_entry->data;
2237 if(group_form->formid == mask_form->formid)
2238 {
2239 selected_index = group_index;
2240 break;
2241 }
2242 group_index++;
2243 }
2244 if(selected_index < 0) return 1;
2245 dt_masks_form_gui_t *visible_gui = mask_gui->dev->form_gui;
2246 if(IS_NULL_PTR(visible_gui)) return 1;
2247 visible_gui->group_selected = selected_index;
2248
2249 dt_masks_select_form(mask_gui->dev, creation_module, dt_masks_get_from_id(mask_gui->dev, mask_form->formid));
2250 }
2251 }
2252 else
2253 {
2254 // unlikely case of button released but no points gathered -> no form
2257 mask_gui->guipoints = NULL;
2258 mask_gui->guipoints_payload = NULL;
2259 mask_gui->guipoints_count = 0;
2260
2263
2264 dt_masks_change_form_gui(mask_gui->dev, NULL);
2265 }
2266 return 1;
2267 }
2268
2269 else if(which == 1)
2270 {
2271 if(dt_masks_gui_is_dragging(mask_gui))
2272 return 1;
2273 }
2274 return 0;
2275}
2276
2277static int _brush_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event,
2278 dt_masks_form_t *mask_form, int parentid,
2279 dt_masks_form_gui_t *mask_gui, int index)
2280{
2281 return 0;
2282}
2283
2292static int _brush_events_mouse_moved(struct dt_iop_module_t *module, double widget_x, double widget_y,
2293 double pressure, int which, dt_masks_form_t *mask_form, int parentid,
2294 dt_masks_form_gui_t *mask_gui, int index)
2295{
2296 dt_develop_t *dev = mask_gui->dev;
2298 const int iwidth = geometry.raw_width;
2299 const int iheight = geometry.raw_height;
2300
2301 if(mask_gui->creation)
2302 {
2303 if(mask_gui->guipoints)
2304 {
2305 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
2306 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
2307 const float fading = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
2308 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
2309 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, border, fading);
2310 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
2311 mask_gui->guipoints_count++;
2312 return 1;
2313 }
2314
2315 // Let the cursor motion be redrawn as it moves in GUI
2316 return 1;
2317 }
2318
2319 if(IS_NULL_PTR(mask_form->points)) return 0;
2320 dt_masks_form_gui_points_t *gui_points
2321 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2322 if(IS_NULL_PTR(gui_points)) return 0;
2323 const guint node_count = g_list_length(mask_form->points);
2324
2325 if(mask_gui->node_dragging >= 0)
2326 {
2327 dt_masks_node_brush_t *dragged_node
2328 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_dragging);
2329 if(IS_NULL_PTR(dragged_node)) return 0;
2330
2331 float delta_x = 0.0f;
2332 float delta_y = 0.0f;
2333 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragged_node->node, &delta_x, &delta_y);
2334 _brush_translate_node(dragged_node, delta_x, delta_y);
2335
2336 // if first point, adjust the source position accordingly
2337 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->node_dragging == 0)
2338 dt_masks_translate_source(mask_form, delta_x, delta_y);
2339
2340 // we recreate the form points
2341 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2342 return 1;
2343 }
2344 else if(mask_gui->seg_dragging >= 0)
2345 {
2346 const GList *segment_start = g_list_nth(mask_form->points, mask_gui->seg_dragging);
2347 const GList *segment_end = g_list_next_wraparound(segment_start, mask_form->points);
2348 dt_masks_node_brush_t *segment_node = (dt_masks_node_brush_t *)segment_start->data;
2349 dt_masks_node_brush_t *segment_node_next = (dt_masks_node_brush_t *)segment_end->data;
2350 if(!segment_node || !segment_node_next) return 0;
2351
2352 float delta_x = 0.0f;
2353 float delta_y = 0.0f;
2354 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, segment_node->node, &delta_x, &delta_y);
2355 _brush_translate_node(segment_node, delta_x, delta_y);
2356 _brush_translate_node(segment_node_next, delta_x, delta_y);
2357
2358 // if first point's segment, adjust the source position accordingly
2359 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->seg_dragging == 0)
2360 dt_masks_translate_source(mask_form, delta_x, delta_y);
2361
2362 // we recreate the form points
2363 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2364 return 1;
2365 }
2366 else if(mask_gui->handle_dragging >= 0)
2367 {
2369 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->handle_dragging);
2370 if(IS_NULL_PTR(node)) return 0;
2371
2372 float cursor_pos[2];
2373 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2374
2375 // compute ctrl points directly from new handle position
2376 float control_points[4];
2377 _brush_handle_to_ctrl(gui_points->points[mask_gui->handle_dragging * 6 + 2],
2378 gui_points->points[mask_gui->handle_dragging * 6 + 3], cursor_pos[0],
2379 cursor_pos[1], &control_points[0], &control_points[1], &control_points[2],
2380 &control_points[3], TRUE);
2381
2382 dt_dev_coordinates_image_abs_to_raw_norm(dev, control_points, 2);
2383
2384 // set new ctrl points
2385 dt_masks_set_ctrl_points(node->ctrl1, node->ctrl2, control_points);
2387
2388 _brush_init_ctrl_points(mask_form);
2389 // we recreate the form points
2390 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2391 return 1;
2392 }
2393 else if(mask_gui->handle_border_dragging >= 0)
2394 {
2395 const int node_index = mask_gui->handle_border_dragging;
2397 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, node_index);
2398 if(IS_NULL_PTR(node)) return 0;
2399
2400 float handle_x = 0.0f, handle_y = 0.0f;
2401 if(!_brush_get_border_handle_mirrored(gui_points, node_count, node_index, &handle_x, &handle_y))
2402 return 0;
2403
2404 const float node_px = gui_points->points[node_index * 6 + 2];
2405 const float node_py = gui_points->points[node_index * 6 + 3];
2406
2407 float pts[2];
2408 float cursor_pos[2];
2409 const float node_pos_gui[2] = { node_px, node_py };
2410 const float handle_pos[2] = { handle_x, handle_y };
2411 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2412 dt_masks_project_on_line(cursor_pos, node_pos_gui, handle_pos, pts);
2413
2414 const float border = dt_masks_border_from_projected_handle(dev, node->node, pts, fminf(iwidth, iheight));
2415
2416 node->border[0] = node->border[1] = border;
2417 // we recreate the form points
2418 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2419 return 1;
2420 }
2421 else if(mask_gui->form_dragging || mask_gui->source_dragging)
2422 {
2423 dt_masks_node_brush_t *dragging_shape = (dt_masks_node_brush_t *)(mask_form->points)->data;
2424 if(IS_NULL_PTR(dragging_shape)) return 0;
2425
2426 if(mask_gui->form_dragging)
2427 {
2428 float delta_x = 0.0f;
2429 float delta_y = 0.0f;
2430 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragging_shape->node, &delta_x, &delta_y);
2431 _brush_translate_all_nodes(mask_form, delta_x, delta_y);
2432 }
2433 else
2434 {
2435 float raw_pos[2];
2436 dt_masks_gui_delta_to_raw_norm(dev, mask_gui, raw_pos);
2437 mask_form->source[0] = raw_pos[0];
2438 mask_form->source[1] = raw_pos[1];
2439 }
2440
2441 // we recreate the form points
2442 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
2443 return 1;
2444 }
2445 return 0;
2446}
2447
2460static inline int _brush_centerline_end(const int points_count, const int node_count)
2461{
2462 const int first = node_count * 3;
2463 if(points_count <= first) return points_count;
2464 return first + (points_count - first) / 2;
2465}
2466
2467static void _brush_draw_shape(struct dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int node_nb, const gboolean border, const gboolean source,
2468 const dt_masks_skip_range_t *skips, const int skip_count)
2469{
2470 /* dev and source are the shared shape_draw_function_t signature. */
2471 (void)dev; (void)source;
2472
2473 /* The centreline is walked there and back (see _brush_centerline_end): stroking the whole
2474 * array would draw it twice. The border wraps the stroke and is walked once. */
2475 const int first = node_nb * 3 + border;
2476 const int last = border ? points_count : _brush_centerline_end(points_count, node_nb);
2477 dt_masks_draw_outline_runs(cr, points, first, last, skips, skip_count);
2478}
2479
2480static float _brush_line_length(const float *line, const int first_pt, const int last_pt)
2481{
2482 float total_len = 0.0f;
2483 for(int i = first_pt; i < last_pt; i++)
2484 {
2485 const int i0 = i * 2;
2486 const int i1 = (i + 1) * 2;
2487 const float x0 = line[i0];
2488 const float y0 = line[i0 + 1];
2489 const float x1 = line[i1];
2490 const float y1 = line[i1 + 1];
2491 const float dx = x1 - x0;
2492 const float dy = y1 - y0;
2493 const float len = dx * dx + dy * dy;
2494 if(len > 1e-12f) total_len += sqrtf(len);
2495 }
2496 return total_len;
2497}
2498
2499static gboolean _brush_line_point_at_length(const float *line, const int first_pt, const int last_pt,
2500 const float target_len, float *x, float *y)
2501{
2502 if(IS_NULL_PTR(line) || IS_NULL_PTR(x) || IS_NULL_PTR(y)) return FALSE;
2503 if(last_pt <= first_pt) return FALSE;
2504
2505 float acc = 0.0f;
2506 gboolean has_fallback = FALSE;
2507 float fallback_x = 0.0f, fallback_y = 0.0f;
2508
2509 for(int i = first_pt; i < last_pt; i++)
2510 {
2511 const int i0 = i * 2;
2512 const int i1 = (i + 1) * 2;
2513 const float x0 = line[i0];
2514 const float y0 = line[i0 + 1];
2515 const float x1 = line[i1];
2516 const float y1 = line[i1 + 1];
2517
2518 const float dx = x1 - x0;
2519 const float dy = y1 - y0;
2520 const float len = dt_fast_hypotf(dx, dy);
2521 if(len <= 1e-6f) continue;
2522
2523 has_fallback = TRUE;
2524 fallback_x = x1;
2525 fallback_y = y1;
2526
2527 if(acc + len >= target_len)
2528 {
2529 const float t = (target_len - acc) / len;
2530 *x = x0 + t * dx;
2531 *y = y0 + t * dy;
2532 return TRUE;
2533 }
2534 acc += len;
2535 }
2536
2537 if(!has_fallback) return FALSE;
2538 *x = fallback_x;
2539 *y = fallback_y;
2540 return TRUE;
2541}
2542
2543static gboolean _brush_get_line_midpoint(const float *line, const int first_pt, const int last_pt,
2544 float *mx, float *my)
2545{
2546 if(IS_NULL_PTR(line) || IS_NULL_PTR(mx) || IS_NULL_PTR(my)) return FALSE;
2547 const float total_len = _brush_line_length(line, first_pt, last_pt);
2548 if(total_len <= 1e-6f) return FALSE;
2549
2550 const float half_len = 0.5f * total_len;
2551 return _brush_line_point_at_length(line, first_pt, last_pt, half_len, mx, my);
2552}
2553
2554static gboolean _brush_get_source_center(const dt_masks_form_gui_points_t *gui_points, const int node_count,
2555 dt_masks_gui_center_point_t *center_pt)
2556{
2557 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(center_pt)) return FALSE;
2558
2559 // Work on the exact centerline span that is actually drawn (non-border path):
2560 // [node_count * 3, _brush_centerline_end())
2561 const int line_offset_pt = node_count * 3;
2562 const int points_line_end = _brush_centerline_end(gui_points->points_count, node_count); // exclusive
2563 const int source_line_end = _brush_centerline_end(gui_points->source_count, node_count); // exclusive
2564 const int line_end = MIN(points_line_end, source_line_end);
2565 const int line_count = line_end - line_offset_pt;
2566 if(line_count < 2) return FALSE;
2567
2568 const float *const points_line = gui_points->points + 2 * line_offset_pt;
2569 const float *const source_line = gui_points->source + 2 * line_offset_pt;
2570 const int first_pt = 0;
2571 const int last_pt = line_count - 1;
2572
2573 if(!_brush_get_line_midpoint(points_line, first_pt, last_pt,
2574 &center_pt->main.x, &center_pt->main.y))
2575 return FALSE;
2576 if(!_brush_get_line_midpoint(source_line, first_pt, last_pt,
2577 &center_pt->source.x, &center_pt->source.y))
2578 return FALSE;
2579 return TRUE;
2580}
2581
2582static void _brush_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *mask_gui, int index,
2583 int node_count)
2584{
2585 // in creation mode
2586 if(mask_gui->creation)
2587 {
2589 const float iwd = geometry.raw_width;
2590 const float iht = geometry.raw_height;
2591 const float min_iwd_iht = MIN(iwd, iht);
2592
2593 if(mask_gui->guipoints_count == 0)
2594 {
2595 dt_masks_form_t *mask_form = dt_masks_get_visible_form(mask_gui->dev);
2596 if(IS_NULL_PTR(mask_form)) return;
2597
2598 const float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f, BORDER_MIN, BORDER_MAX,
2600 const float masks_fading = dt_masks_get_set_conf_value(mask_form, "fading", 1.0f, FADING_MIN,
2602 const float opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
2603
2604 const float radius1 = masks_border * masks_fading * min_iwd_iht;
2605 const float radius2 = masks_border * min_iwd_iht;
2606
2607 float xpos = mask_gui->pos[0];
2608 float ypos = mask_gui->pos[1];
2609 if((xpos == -1.0f && ypos == -1.0f))
2610 {
2611 xpos = 0.f;
2612 ypos = 0.f;
2613 }
2614
2615 // draw brush circle at current mouse position
2616 cairo_save(cr);
2618 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2619 cairo_new_path(cr);
2620 cairo_arc(cr, xpos, ypos, radius1, 0, 2.0 * M_PI);
2621 cairo_fill_preserve(cr);
2622 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2623 cairo_stroke(cr);
2624 cairo_new_path(cr);
2625 cairo_arc(cr, xpos, ypos, radius2, 0, 2.0 * M_PI);
2626 dt_draw_stroke_line(DT_MASKS_DASH_STICK, FALSE, cr, FALSE, zoom_scale, CAIRO_LINE_CAP_ROUND);
2627
2628 if(mask_form->type & DT_MASKS_CLONE)
2629 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, xpos, ypos, xpos, ypos, FALSE);
2630
2631 cairo_restore(cr);
2632 }
2633 else
2634 {
2635 float masks_border = 0.0f, masks_fading = 0.0f, masks_density = 0.0f;
2636 float radius = 0.0f, oldradius = 0.0f, opacity = 0.0f, oldopacity = 0.0f, pressure = 0.0f;
2637 int stroked = 1;
2638
2639 const float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
2640 const float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
2641
2642 cairo_save(cr);
2643 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
2644 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
2645 masks_border = guipoints_payload[0];
2646 masks_fading = guipoints_payload[1];
2647 masks_density = guipoints_payload[2];
2648 pressure = guipoints_payload[3];
2649
2650 switch(mask_gui->pressure_sensitivity)
2651 {
2653 masks_fading = MAX(FADING_MIN, pressure);
2654 break;
2656 masks_fading = MAX(FADING_MIN, masks_fading * pressure);
2657 break;
2659 masks_density = MAX(0.05f, pressure);
2660 break;
2662 masks_density = MAX(0.05f, masks_density * pressure);
2663 break;
2665 masks_border = MAX(FADING_MIN, masks_border * pressure);
2666 break;
2667 default:
2669 // ignore pressure value
2670 break;
2671 }
2672
2673 radius = oldradius = masks_border * masks_fading * min_iwd_iht;
2674 opacity = oldopacity = masks_density;
2675
2676 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2678
2679 cairo_move_to(cr, guipoints[0], guipoints[1]);
2680 for(int i = 1; i < mask_gui->guipoints_count; i++)
2681 {
2682 cairo_line_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2683 stroked = 0;
2684 masks_border = guipoints_payload[i * 4];
2685 masks_fading = guipoints_payload[i * 4 + 1];
2686 masks_density = guipoints_payload[i * 4 + 2];
2687 pressure = guipoints_payload[i * 4 + 3];
2688
2689 switch(mask_gui->pressure_sensitivity)
2690 {
2692 masks_fading = MAX(FADING_MIN, pressure);
2693 break;
2695 masks_fading = MAX(FADING_MIN, masks_fading * pressure);
2696 break;
2698 masks_density = MAX(0.05f, pressure);
2699 break;
2701 masks_density = MAX(0.05f, masks_density * pressure);
2702 break;
2704 masks_border = MAX(FADING_MIN, masks_border * pressure);
2705 break;
2706 default:
2708 // ignore pressure value
2709 break;
2710 }
2711
2712 radius = masks_border * masks_fading * min_iwd_iht;
2713 opacity = masks_density;
2714
2715 if(radius != oldradius || opacity != oldopacity)
2716 {
2717 cairo_stroke(cr);
2718 stroked = 1;
2719 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2721 oldradius = radius;
2722 oldopacity = opacity;
2723 cairo_move_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2724 }
2725 }
2726 if(!stroked) cairo_stroke(cr);
2727
2728 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2730 cairo_new_path(cr);
2731 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2732 guipoints[2 * (mask_gui->guipoints_count - 1) + 1],
2733 radius, 0, 2.0 * M_PI);
2734 cairo_fill_preserve(cr);
2735 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2736 cairo_stroke(cr);
2738 cairo_new_path(cr);
2739 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2740 guipoints[2 * (mask_gui->guipoints_count - 1) + 1], masks_border * min_iwd_iht, 0,
2741 2.0 * M_PI);
2742 cairo_stroke(cr);
2743
2744 dt_masks_form_t *visible_form = dt_masks_get_visible_form(mask_gui->dev);
2745 if(visible_form && (visible_form->type & DT_MASKS_CLONE))
2746 {
2747 const int i = mask_gui->guipoints_count - 1;
2748 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, guipoints[0], guipoints[1],
2749 guipoints[i * 2], guipoints[i * 2 + 1], TRUE);
2750 }
2751
2752 cairo_restore(cr);
2753 }
2754 return;
2755 } // creation
2756
2757 dt_masks_form_gui_points_t *gui_points
2758 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2759 if(IS_NULL_PTR(gui_points)) return;
2760 if(IS_NULL_PTR(gui_points->points)) return;
2761
2762 const int selected_node = dt_masks_gui_selected_node_index(mask_gui);
2763 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
2764 const int selected_handle_border = dt_masks_gui_selected_handle_border_index(mask_gui);
2765 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
2766
2767 // minimum points
2768 if(gui_points->points_count <= node_count * 3 + 2) return;
2769
2770 // draw path
2771 if(node_count > 0 && gui_points->points_count > node_count * 3 + 6) // there must be something to draw
2772 {
2773 // Rounded, not flat: a brush stroke is a paintbrush, not a ruled polygon edge, and a butt
2774 // cap on the outline shown here (the node-to-node centerline, not the painted falloff)
2775 // looked cut off square at its two open ends. Only the two true ends of the whole stroke,
2776 // not every node's own joint -- see dt_masks_draw_path_seg_by_seg().
2777 // The whole array, not the forward half: the walk stops itself on the last node (an open path
2778 // has node_count - 1 segments), and it must be able to REACH that node. Handing it a count
2779 // derived from the array length instead left it short of the last node on every brush, so the
2780 // last segment was never stroked -- it stayed a pending path in the cairo context.
2781 dt_masks_draw_path_seg_by_seg(cr, mask_gui, index, gui_points->points, gui_points->points_count,
2782 node_count, zoom_scale, TRUE);
2783 }
2784
2785 if(0)
2786 {
2787 const int total_points = gui_points->points_count * 2;
2788 int seg1 = 1;
2789 int current_seg = 0;
2790
2791 /* Draw the line point-by-point up to the next node, then stroke it; repeat in a loop. */
2792 cairo_move_to(cr, gui_points->points[node_count * 6], gui_points->points[node_count * 6 + 1]);
2793
2794 const int end_idx = 0.5 * gui_points->points_count;
2795
2796 for(int i = node_count * 3; i < end_idx; i++)
2797 {
2798 const double x = gui_points->points[i * 2];
2799 const double y = gui_points->points[i * 2 + 1];
2800 cairo_line_to(cr, x, y);
2801
2802 int seg_idx = seg1 * 6;
2803 if((seg_idx + 3) < total_points)
2804 {
2805 const double segment_x = gui_points->points[seg_idx + 2];
2806 const double segment_y = gui_points->points[seg_idx + 3];
2807
2808 /* Is this point the next node? */
2809 if(x == segment_x && y == segment_y)
2810 {
2811 const gboolean seg_selected = (mask_gui->group_selected == index)
2812 && (selected_segment == current_seg);
2813 const gboolean all_selected = (mask_gui->group_selected == index)
2814 && !mask_gui->node_selected
2815 && !mask_gui->handle_selected
2816 && !mask_gui->handle_border_selected
2817 && !mask_gui->seg_selected
2818 && (mask_gui->form_selected || mask_gui->form_dragging);
2819 // creation mode: draw the current segment as round dotted line
2820 if(mask_gui->creation && current_seg == node_count - 2)
2821 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
2822 else
2823 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, (seg_selected || all_selected), zoom_scale, CAIRO_LINE_CAP_BUTT);
2824 seg1 = (seg1 + 1) % node_count;
2825 current_seg++;
2826 }
2827 }
2828 }
2829 }
2830
2831 // draw nodes and attached stuff
2832 if(mask_gui->group_selected == index)
2833 {
2834 // draw borders
2835 if(gui_points->border && gui_points->border_count > node_count * 3 + 2)
2836 {
2837 dt_draw_shape_lines(mask_gui->dev, DT_MASKS_DASH_STICK, FALSE, cr, node_count, (mask_gui->border_selected), zoom_scale,
2838 gui_points->border, gui_points->border_count, &dt_masks_functions_brush.draw_shape,
2839 CAIRO_LINE_CAP_ROUND, gui_points->border_skips, gui_points->border_skip_count);
2840 }
2841
2842 // draw the current node's handle if it's a curve node
2843 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count
2844 && !dt_masks_node_is_cusp(gui_points, selected_node))
2845 {
2846 const int n = selected_node;
2847 float handle[2];
2848 _brush_ctrl2_to_handle(gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3],
2849 gui_points->points[n * 6 + 4], gui_points->points[n * 6 + 5], &handle[0], &handle[1],
2850 TRUE);
2851 const float pt[2] = { gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3] };
2852 const gboolean selected = (mask_gui->node_hovered == n || selected_handle == n || (mask_gui->handle_hovered == n));
2853 dt_draw_handle(cr, pt, zoom_scale, handle, selected, FALSE);
2854 }
2855
2856 // draw all nodes
2857 for(int k = 0; k < node_count; k++)
2858 {
2859 const gboolean corner = dt_masks_node_is_cusp(gui_points, k);
2860 const float x = gui_points->points[k * 6 + 2];
2861 const float y = gui_points->points[k * 6 + 3];
2862 const gboolean selected = (k == mask_gui->node_hovered || k == mask_gui->node_dragging);
2863 const gboolean action = (k == selected_node);
2864
2865 dt_draw_node(cr, corner, action, selected, zoom_scale, x, y);
2866 }
2867
2868 // Draw the current node's border handle, if needed
2869 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count)
2870 {
2871 const int edited = selected_node;
2872 const gboolean selected = (mask_gui->node_hovered == edited
2873 || selected_handle_border == edited
2874 || mask_gui->handle_border_hovered == edited);
2875 float handle[2] = { 0.0f, 0.0f };
2876 // Show the border handle on the opposite side from the curve handle
2877 if(_brush_get_border_handle_mirrored(gui_points, node_count, edited, &handle[0], &handle[1]))
2878 {
2879 dt_draw_handle(cr, NULL, zoom_scale, handle, selected, TRUE);
2880 }
2881 }
2882 }
2883
2884 // Draw the source if needed
2885 if(gui_points->source && gui_points->source_count > node_count * 3 + 2)
2886 {
2888 if(_brush_get_source_center(gui_points, node_count, &center_pt))
2889 dt_masks_draw_source(cr, mask_gui, index, node_count, zoom_scale, &center_pt,
2891
2892 //draw the current node projection
2893 for(int k = 0; k < node_count; k++)
2894 {
2895 if(mask_gui->group_selected == index
2896 && (k == mask_gui->node_hovered || k == selected_node
2897 || (mask_gui->creation && k == node_count - 1)))
2898 {
2899 const int node_index = k * 6 + 2;
2900 const float proj[2] = { gui_points->source[node_index], gui_points->source[node_index + 1] };
2901 const gboolean selected = mask_gui->node_hovered == k;
2902 const gboolean squared = dt_masks_node_is_cusp(gui_points, k);
2903
2904 dt_draw_handle(cr, NULL, zoom_scale, proj, selected, squared);
2905 }
2906 }
2907 }
2908}
2909
2916static void _brush_bounding_box_raw(const float *const restrict points, const float *const restrict border,
2917 const int node_count, const int total_points, float *x_min, float *x_max,
2918 float *y_min, float *y_max)
2919{
2920 // now we want to find the area, so we search min/max points
2921 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2922 __OMP_PARALLEL_FOR__(reduction(min : min_x, min_y) reduction(max : max_x, max_y) if(total_points > 1000))
2923 // Skip control points and only consider actual polyline samples.
2924 for(int point_index = node_count * 3; point_index < total_points; point_index++)
2925 {
2926 // we look at the borders
2927 const float border_x = border[point_index * 2];
2928 const float border_y = border[point_index * 2 + 1];
2929 min_x = MIN(border_x, min_x);
2930 max_x = MAX(border_x, max_x);
2931 min_y = MIN(border_y, min_y);
2932 max_y = MAX(border_y, max_y);
2933 // we look at the brush too
2934 const float brush_x = points[point_index * 2];
2935 const float brush_y = points[point_index * 2 + 1];
2936 min_x = MIN(brush_x, min_x);
2937 max_x = MAX(brush_x, max_x);
2938 min_y = MIN(brush_y, min_y);
2939 max_y = MAX(brush_y, max_y);
2940 }
2941 *x_min = min_x;
2942 *x_max = max_x;
2943 *y_min = min_y;
2944 *y_max = max_y;
2945}
2946
2952static void _brush_bounding_box(const float *const points, const float *const border, const int node_count,
2953 const int total_points, int *width, int *height, int *offset_x, int *offset_y)
2954{
2955 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2956 _brush_bounding_box_raw(points, border, node_count, total_points, &min_x, &max_x, &min_y, &max_y);
2957 *height = max_y - min_y + 4;
2958 *width = max_x - min_x + 4;
2959 *offset_x = min_x - 2;
2960 *offset_y = min_y - 2;
2961}
2962
2968static int _get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2969 const dt_dev_pixelpipe_iop_t *const piece,
2970 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x, int *offset_y,
2971 int include_source)
2972{
2973 if(IS_NULL_PTR(module)) return 1;
2974 // we get buffers for all points
2975 float *points = NULL, *border = NULL;
2976 int points_count, border_count;
2977 const dt_masks_distort_t pipe_dist = dt_masks_distort_for_pipe(pipe, module->dev);
2978 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &pipe_dist,
2979 &points, &points_count, &border, &border_count, NULL, NULL, NULL, NULL, include_source) != 0)
2980 {
2983 return 1;
2984 }
2985
2986 const guint node_count = g_list_length(mask_form->points);
2987 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || points_count <= (int)(node_count * 3) || border_count <= (int)(node_count * 3))
2988 {
2991 *width = 0;
2992 *height = 0;
2993 *offset_x = 0;
2994 *offset_y = 0;
2995 return 0;
2996 }
2997 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
2998
3001 return 0;
3002}
3003
3006 dt_masks_form_t *mask_form, int *width, int *height, int *offset_x, int *offset_y)
3007{
3008 *width = 0;
3009 *height = 0;
3010 *offset_x = 0;
3011 *offset_y = 0;
3013 _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 1));
3014}
3015
3017 const dt_dev_pixelpipe_iop_t *const piece,
3018 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x,
3019 int *offset_y)
3020{
3021 *width = 0;
3022 *height = 0;
3023 *offset_x = 0;
3024 *offset_y = 0;
3026 _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 0));
3027}
3028
3030static void _brush_falloff(float *const restrict buffer, int segment_start[2], int segment_end[2],
3031 int offset_x, int offset_y, int buffer_width, float fading, float density)
3032{
3033 // segment length
3034 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
3035 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
3036 + 1;
3037 const int solid_length = (int)segment_length * fading;
3038 const int soft_length = segment_length - solid_length;
3039
3040 const float segment_dx = segment_end[0] - segment_start[0];
3041 const float segment_dy = segment_end[1] - segment_start[1];
3042 const float inv_length = 1.0f / (float)segment_length;
3043 const float inv_soft = (soft_length > 0) ? 1.0f / (float)soft_length : 0.0f;
3044
3045 for(int step = 0; step < segment_length; step++)
3046 {
3047 // position
3048 const int x = (int)((float)step * segment_dx * inv_length) + segment_start[0] - offset_x;
3049 const int y = (int)((float)step * segment_dy * inv_length) + segment_start[1] - offset_y;
3050 const float opacity = density * ((step <= solid_length) ? 1.0f : 1.0f - (float)(step - solid_length) * inv_soft);
3051 const int buffer_index = y * buffer_width + x;
3052 buffer[buffer_index] = MAX(buffer[buffer_index], opacity);
3053 if(x > 0)
3054 buffer[buffer_index - 1] = MAX(buffer[buffer_index - 1], opacity); // avoid gaps from rounding
3055 if(y > 0)
3056 buffer[buffer_index - buffer_width] = MAX(buffer[buffer_index - buffer_width], opacity); // avoid gaps from rounding
3057 }
3058}
3059
3066 const dt_dev_pixelpipe_iop_t *const piece,
3067 dt_masks_form_t *const mask_form,
3068 float **buffer, int *width, int *height, int *offset_x, int *offset_y)
3069{
3070 *buffer = NULL;
3071 *width = 0;
3072 *height = 0;
3073 *offset_x = 0;
3074 *offset_y = 0;
3075 if(IS_NULL_PTR(module)) return DT_MASKS_RASTER_ERROR;
3076 double timer_start = 0.0;
3077 double timer_step_start = 0.0;
3078 if(dt_get_debug_flags() & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
3079
3080 // we get buffers for all points
3081 float *points = NULL, *border = NULL, *payload = NULL;
3082 int points_count, border_count, payload_count;
3083 const dt_masks_distort_t pipe_dist = dt_masks_distort_for_pipe(pipe, module->dev);
3084 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &pipe_dist,
3085 &points, &points_count,
3086 &border, &border_count, &payload, &payload_count, NULL, NULL, 0) != 0)
3087 {
3091 return DT_MASKS_RASTER_ERROR;
3092 }
3093
3095 {
3096 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
3097 dt_get_wtime() - timer_step_start);
3098 timer_step_start = dt_get_wtime();
3099 }
3100
3101 const guint node_count = g_list_length(mask_form->points);
3102 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
3103 || border_count <= (int)(node_count * 3))
3104 {
3108 *buffer = NULL;
3109 *width = 0;
3110 *height = 0;
3111 *offset_x = 0;
3112 *offset_y = 0;
3113 return DT_MASKS_RASTER_EMPTY;
3114 }
3115 const gboolean use_sparse = (pipe->mask_rasterization_step > 1);
3116 const int sparse_step = pipe->mask_rasterization_step;
3117 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
3118
3120 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
3121 dt_get_wtime() - timer_step_start);
3122
3123 // we allocate the buffer
3124 const size_t buffer_size = (size_t)(*width) * (*height);
3125 // ensure that the buffer is zeroed, as the below code only fills in pixels in the falloff region
3126 *buffer = dt_pixelpipe_cache_alloc_align_float_cache(buffer_size, 0);
3127 if(IS_NULL_PTR(*buffer))
3128 {
3132 return DT_MASKS_RASTER_ERROR;
3133 }
3134 memset(*buffer, 0, sizeof(float) * buffer_size);
3135
3136 // now we fill the falloff
3137 int segment_start[2], segment_end[2];
3138 int prev_start[2] = { 0, 0 };
3139 int prev_end[2] = { 0, 0 };
3140 float prev_payload[2] = { 0.0f, 0.0f };
3141 gboolean have_prev = FALSE;
3142
3143 // Walk all border samples and stamp a falloff segment for each.
3144 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3145 {
3146 segment_start[0] = points[border_index * 2];
3147 segment_start[1] = points[border_index * 2 + 1];
3148 segment_end[0] = border[border_index * 2];
3149 segment_end[1] = border[border_index * 2 + 1];
3150
3151 if(use_sparse && have_prev
3152 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
3153 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
3154 {
3155 // In sparse mode (preview/thumbnail), interpolate missing segments to reduce holes.
3156 for(int step = 1; step < sparse_step; step++)
3157 {
3158 const float t = (float)step / (float)sparse_step;
3159 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
3160 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
3161 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
3162 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
3163 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
3164 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
3165 _brush_falloff(*buffer, interp_start, interp_end, *offset_x, *offset_y, *width, hard, dens);
3166 }
3167 }
3168
3169 _brush_falloff(*buffer, segment_start, segment_end, *offset_x, *offset_y, *width,
3170 payload[border_index * 2], payload[border_index * 2 + 1]);
3171
3172 prev_start[0] = segment_start[0];
3173 prev_start[1] = segment_start[1];
3174 prev_end[0] = segment_end[0];
3175 prev_end[1] = segment_end[1];
3176 prev_payload[0] = payload[border_index * 2];
3177 prev_payload[1] = payload[border_index * 2 + 1];
3178 have_prev = TRUE;
3179 }
3180
3184
3186 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
3187 dt_get_wtime() - timer_start);
3188
3189 return DT_MASKS_RASTER_OK;
3190}
3191
3193static inline void _brush_falloff_roi(float *buffer, const int *segment_start, const int *segment_end,
3194 int buffer_width, int buffer_height, float fading, float density)
3195{
3196 // segment length (increase by 1 to avoid division-by-zero special case handling)
3197 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
3198 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
3199 + 1;
3200 const int solid_length = fading * segment_length;
3201
3202 const float step_x = (float)(segment_end[0] - segment_start[0]) / (float)segment_length;
3203 const float step_y = (float)(segment_end[1] - segment_start[1]) / (float)segment_length;
3204
3205 const int direction_x = step_x <= 0 ? -1 : 1;
3206 const int direction_y = step_y <= 0 ? -1 : 1;
3207 const int neighbor_offset_x = direction_x;
3208 const int neighbor_offset_y = direction_y * buffer_width;
3209
3210 float cursor_x = segment_start[0];
3211 float cursor_y = segment_start[1];
3212
3213 float opacity = density;
3214 const float opacity_step = density / (float)(segment_length - solid_length);
3215
3216 const int start_x = segment_start[0], start_y = segment_start[1];
3217 const int end_x = segment_end[0], end_y = segment_end[1];
3218 if((start_x < 0 && end_x < 0) || (start_x >= buffer_width && end_x >= buffer_width)
3219 || (start_y < 0 && end_y < 0) || (start_y >= buffer_height && end_y >= buffer_height))
3220 return;
3221 const int fully_inside = (start_x >= 0 && start_x < buffer_width && end_x >= 0 && end_x < buffer_width
3222 && start_y >= 0 && start_y < buffer_height && end_y >= 0
3223 && end_y < buffer_height);
3224
3225 for(int step = 0; step < segment_length; step++)
3226 {
3227 const int x = cursor_x;
3228 const int y = cursor_y;
3229
3230 cursor_x += step_x;
3231 cursor_y += step_y;
3232 if(step > solid_length) opacity -= opacity_step;
3233
3234 if(!fully_inside && (x < 0 || x >= buffer_width || y < 0 || y >= buffer_height)) continue;
3235
3236 float *buf = buffer + (size_t)y * buffer_width + x;
3237
3238 *buf = MAX(*buf, opacity);
3239 if(x + direction_x >= 0 && x + direction_x < buffer_width)
3240 buf[neighbor_offset_x] = MAX(buf[neighbor_offset_x], opacity); // avoid gaps from rounding
3241 if(y + direction_y >= 0 && y + direction_y < buffer_height)
3242 buf[neighbor_offset_y] = MAX(buf[neighbor_offset_y], opacity); // avoid gaps from rounding
3243 }
3244}
3245
3246// build a stamp which can be combined with other shapes in the same group
3247// prerequisite: 'buffer' is all zeros
3254 const dt_dev_pixelpipe_iop_t *const piece,
3255 dt_masks_form_t *const mask_form, const dt_iop_roi_t *roi, float *buffer,
3256 dt_iop_roi_t *touched)
3257{
3258 dt_masks_touched_none(touched);
3259 if(IS_NULL_PTR(module)) return DT_MASKS_RASTER_ERROR;
3260 double timer_start = 0.0;
3261 double timer_step_start = 0.0;
3262 if(dt_get_debug_flags() & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
3263
3264 const int roi_offset_x = roi->x;
3265 const int roi_offset_y = roi->y;
3266 const int roi_width = roi->width;
3267 const int roi_height = roi->height;
3268 const float roi_scale = roi->scale;
3269 const gboolean use_sparse = (pipe->mask_rasterization_step > 1);
3270 const int sparse_step = pipe->mask_rasterization_step;
3271
3272 // we get buffers for all points
3273 float *points = NULL, *border = NULL, *payload = NULL;
3274
3275 int points_count, border_count, payload_count;
3276
3277 const dt_masks_distort_t pipe_dist = dt_masks_distort_for_pipe(pipe, module->dev);
3278 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &pipe_dist,
3279 &points, &points_count, &border, &border_count, &payload, &payload_count, NULL, NULL, 0) != 0)
3280 {
3284 return DT_MASKS_RASTER_ERROR;
3285 }
3286
3288 {
3289 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
3290 dt_get_wtime() - timer_step_start);
3291 timer_step_start = dt_get_wtime();
3292 }
3293
3294 const guint node_count = g_list_length(mask_form->points);
3295 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
3296 || border_count <= (int)(node_count * 3))
3297 {
3301 return DT_MASKS_RASTER_EMPTY;
3302 }
3303
3304 // we shift and scale down brush and border
3305 // Shift/scale border samples into ROI space.
3306 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3307 {
3308 const float border_x = border[2 * border_index];
3309 const float border_y = border[2 * border_index + 1];
3310 border[2 * border_index] = border_x * roi_scale - roi_offset_x;
3311 border[2 * border_index + 1] = border_y * roi_scale - roi_offset_y;
3312 }
3313
3314 // Shift/scale centerline samples into ROI space.
3315 for(int point_index = node_count * 3; point_index < points_count; point_index++)
3316 {
3317 const float point_x = points[2 * point_index];
3318 const float point_y = points[2 * point_index + 1];
3319 points[2 * point_index] = point_x * roi_scale - roi_offset_x;
3320 points[2 * point_index + 1] = point_y * roi_scale - roi_offset_y;
3321 }
3322
3323
3324 float min_x = 0.0f, max_x = 0.0f, min_y = 0.0f, max_y = 0.0f;
3325 _brush_bounding_box_raw(points, border, node_count, points_count, &min_x, &max_x, &min_y, &max_y);
3326
3328 {
3329 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
3330 dt_get_wtime() - timer_step_start);
3331 timer_step_start = dt_get_wtime();
3332 }
3333
3334 // check if the path completely lies outside of roi -> we're done/mask remains empty
3335 if(max_x < 0 || max_y < 0 || min_x >= roi_width || min_y >= roi_height)
3336 {
3340 return DT_MASKS_RASTER_EMPTY;
3341 }
3342
3343 // now we fill the falloff
3344 if(!use_sparse)
3345 {
3346 __OMP_PARALLEL_FOR__(if(border_count - node_count * 3 > 1000))
3347 // Stamp each border segment directly (full sampling).
3348 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3349 {
3350 const int segment_start[] = { points[border_index * 2], points[border_index * 2 + 1] };
3351 const int segment_end[] = { border[border_index * 2], border[border_index * 2 + 1] };
3352
3353 if(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3354 || MAX(segment_start[1], segment_end[1]) < 0
3355 || MIN(segment_start[1], segment_end[1]) >= roi_height)
3356 continue;
3357
3358 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height, payload[border_index * 2],
3359 payload[border_index * 2 + 1]);
3360 }
3361 }
3362 else
3363 {
3364 int prev_start[2] = { 0, 0 };
3365 int prev_end[2] = { 0, 0 };
3366 float prev_payload[2] = { 0.0f, 0.0f };
3367 gboolean have_prev = FALSE;
3368
3369 // Sparse mode: interpolate between consecutive segments to reduce artifacts.
3370 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3371 {
3372 int segment_start[2] = { points[border_index * 2], points[border_index * 2 + 1] };
3373 int segment_end[2] = { border[border_index * 2], border[border_index * 2 + 1] };
3374
3375 if(use_sparse && have_prev
3376 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
3377 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
3378 {
3379 for(int step = 1; step < sparse_step; step++)
3380 {
3381 const float t = (float)step / (float)sparse_step;
3382 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
3383 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
3384 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
3385 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
3386 if(!(MAX(interp_start[0], interp_end[0]) < 0 || MIN(interp_start[0], interp_end[0]) >= roi_width
3387 || MAX(interp_start[1], interp_end[1]) < 0 || MIN(interp_start[1], interp_end[1]) >= roi_height))
3388 {
3389 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
3390 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
3391 _brush_falloff_roi(buffer, interp_start, interp_end, roi_width, roi_height, hard, dens);
3392 }
3393 }
3394 }
3395
3396 if(!(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3397 || MAX(segment_start[1], segment_end[1]) < 0
3398 || MIN(segment_start[1], segment_end[1]) >= roi_height))
3399 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height,
3400 payload[border_index * 2], payload[border_index * 2 + 1]);
3401
3402 prev_start[0] = segment_start[0];
3403 prev_start[1] = segment_start[1];
3404 prev_end[0] = segment_end[0];
3405 prev_end[1] = segment_end[1];
3406 prev_payload[0] = payload[border_index * 2];
3407 prev_payload[1] = payload[border_index * 2 + 1];
3408 have_prev = TRUE;
3409 }
3410 }
3411
3415
3416 /* Every stamp lies between a centreline sample and its border sample, and writes one extra
3417 * neighbour pixel in each direction to close rounding gaps -- hence the margin. */
3418 dt_masks_touched_set(touched, (int)floorf(min_x) - 2, (int)floorf(min_y) - 2, (int)ceilf(max_x) + 2,
3419 (int)ceilf(max_y) + 2, roi_width, roi_height);
3420
3422 {
3423 dt_print(DT_DEBUG_MASKS, "[masks %s] brush set falloff took %0.04f sec\n", mask_form->name,
3424 dt_get_wtime() - timer_step_start);
3425 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
3426 dt_get_wtime() - timer_start);
3427 }
3428
3429 return DT_MASKS_RASTER_OK;
3430}
3431
3433{
3434 // nothing to do (yet?)
3435}
3436
3437static void _brush_set_form_name(struct dt_masks_form_t *const mask_form, const size_t form_number)
3438{
3439 snprintf(mask_form->name, sizeof(mask_form->name), _("brush #%d"), (int)form_number);
3440}
3441
3442static void _brush_set_hint_message(const dt_masks_form_gui_t *const mask_gui,
3443 const dt_masks_form_t *const mask_form,
3444 char *const restrict msgbuf, const size_t msgbuf_len)
3445{
3446 // Ordered like the hit test in dt_masks_find_closest_handle_common(): the innermost target
3447 // under the cursor is the one the next click will act on, so it is the one we describe.
3448 // Only gestures that cannot be discovered any other way -- what the wheel does is the user's
3449 // own mapping now (masks_gui.h), shown in the Drawn tab, so it is not repeated here.
3450 if(mask_gui->creation)
3451 {
3452 if(dt_masks_form_is_clone(mask_form))
3453 g_strlcat(msgbuf, _("<b>Set source</b>: Shift+Click"), msgbuf_len);
3454 }
3455 else if(mask_gui->source_selected)
3456 g_strlcat(msgbuf, _("<b>Move source</b>: Drag"), msgbuf_len);
3457 else if(mask_gui->handle_border_hovered >= 0)
3458 g_strlcat(msgbuf, _("<b>Node size</b>: Drag"), msgbuf_len);
3459 else if(mask_gui->handle_hovered >= 0)
3460 g_strlcat(msgbuf, _("<b>Node curvature</b>: Drag"), msgbuf_len);
3461 // The node operations below need the node to be selected, which the first click does.
3462 else if(mask_gui->node_hovered >= 0 && mask_gui->node_selected)
3463 g_strlcat(msgbuf, _("<b>Move node</b>: Drag, <b>Switch smooth/sharp</b>: Ctrl+Click\n"
3464 "<b>Delete node</b>: Right-click or Del"), msgbuf_len);
3465 else if(mask_gui->node_hovered >= 0)
3466 g_strlcat(msgbuf, _("<b>Move node</b>: Drag, <b>Delete node</b>: Right-click"), msgbuf_len);
3467 else if(mask_gui->seg_hovered >= 0 || mask_gui->seg_selected)
3468 g_strlcat(msgbuf, _("<b>Move segment</b>: Drag, <b>Add node</b>: Ctrl+Click"), msgbuf_len);
3469 else if(mask_gui->form_selected || mask_gui->border_selected)
3470 g_strlcat(msgbuf, _("<b>Move</b>: Drag"), msgbuf_len);
3471}
3472
3473static void _brush_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
3474{
3475 // unused arg, keep compiler from complaining
3477}
3478
3479static void _brush_initial_source_pos(struct dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
3480{
3481
3482
3483 float offset[2] = { 0.01f, 0.01f };
3485 *x = offset[0];
3486 *y = offset[1];
3487}
3488
3489static void _brush_switch_node_callback(GtkWidget *widget, gpointer user_data)
3490{
3491 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3492 if(IS_NULL_PTR(mask_gui)) return;
3493
3494 dt_iop_module_t *module = mask_gui->dev->gui_module;
3495 if(IS_NULL_PTR(module)) return;
3496
3497 mask_gui->node_selected = TRUE;
3498 mask_gui->node_selected_idx = mask_gui->node_hovered;
3499
3500 const int form_id = mask_gui->formid;
3501 dt_masks_form_t *selected_form = dt_masks_get_from_id(mask_gui->dev, form_id);
3502 if(IS_NULL_PTR(selected_form)) return;
3503 dt_masks_form_gui_points_t *gui_points
3504 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3505 const int node_index = dt_masks_gui_selected_node_index(mask_gui);
3507 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3508 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3509 dt_masks_toggle_bezier_node_type(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3510 node_index, node->node, node->ctrl1, node->ctrl2, &node->state);
3511}
3512
3513static void _brush_reset_round_node_callback(GtkWidget *widget, gpointer user_data)
3514{
3515 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3516 if(IS_NULL_PTR(mask_gui)) return;
3517
3518 dt_iop_module_t *module = mask_gui->dev->gui_module;
3519 if(IS_NULL_PTR(module)) return;
3520
3521 mask_gui->node_selected = TRUE;
3522 mask_gui->node_selected_idx = mask_gui->node_hovered;
3523
3524 const int form_id = mask_gui->formid;
3525 dt_masks_form_t *selected_form = dt_masks_get_from_id(mask_gui->dev, form_id);
3526 if(IS_NULL_PTR(selected_form)) return;
3527 dt_masks_form_gui_points_t *gui_points
3528 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3529 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
3530 const int node_index = MAX(mask_gui->node_hovered, selected_handle);
3532 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3533 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3534 dt_masks_reset_bezier_ctrl_points(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3535 node_index, &node->state);
3536}
3537
3538static void _brush_add_node_callback(GtkWidget *menu, gpointer user_data)
3539{
3540 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3541 if(IS_NULL_PTR(mask_gui)) return;
3542 dt_masks_form_t *visible_forms = dt_masks_get_visible_form(mask_gui->dev);
3543 if(IS_NULL_PTR(visible_forms)) return;
3544
3545 dt_iop_module_t *module = mask_gui->dev->gui_module;
3546 if(IS_NULL_PTR(module)) return;
3547
3548 dt_masks_form_group_t *group_entry = dt_masks_form_get_selected_group(visible_forms, mask_gui);
3549 if(IS_NULL_PTR(group_entry)) return;
3550 dt_masks_form_t *selected_form = dt_masks_get_from_id(mask_gui->dev, group_entry->formid);
3551 if(selected_form)
3552 {
3553 _add_node_to_segment(module, selected_form, group_entry->parentid, mask_gui, mask_gui->group_selected);
3554 }
3555
3556 dt_dev_add_history_item(mask_gui->dev, module, TRUE, TRUE);
3557}
3558
3559static int _brush_populate_context_menu(GtkWidget *menu, struct dt_masks_form_t *mask_form,
3560 struct dt_masks_form_gui_t *mask_gui,
3561 const float pointer_x, const float pointer_y)
3562{
3563
3564
3565 GtkWidget *menu_item = NULL;
3566 gchar *accel = g_strdup_printf(_("%s+Click"), gtk_accelerator_get_label(0, dt_accels_display_mods(DT_PRIMARY_MASK)));
3567
3568 gboolean ret = FALSE;
3569
3570 if(mask_gui->node_hovered >= 0)
3571 {
3572 dt_masks_form_gui_points_t *gui_points
3573 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3574 if(IS_NULL_PTR(gui_points)) goto end;
3576 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
3577 if(IS_NULL_PTR(node)) goto end;
3578 const gboolean is_corner = dt_masks_node_is_cusp(gui_points, mask_gui->node_hovered);
3579
3580 {
3581 gchar *to_change_type = g_strdup_printf(_("Switch to %s node"), (is_corner) ? _("round") : _("cusp"));
3582 const dt_menu_icon_t icon = is_corner ? DT_MENU_ICON_CIRCLE : DT_MENU_ICON_SQUARE;
3583 menu_item = ctx_gtk_menu_item_new_with_icon_and_shortcut(to_change_type, accel, menu,
3584 _brush_switch_node_callback, mask_gui, icon);
3585 dt_free(to_change_type);
3586 }
3587
3588 {
3589 menu_item = ctx_gtk_menu_item_new_with_markup(_("Reset round node"), menu,
3591 gtk_widget_set_sensitive(menu_item, !is_corner && node->state != DT_MASKS_POINT_STATE_NORMAL);
3592 }
3593 ret = TRUE;
3594 }
3595
3596 if(mask_gui->seg_selected)
3597 {
3598 menu_item = ctx_gtk_menu_item_new_with_markup_and_shortcut(_("Add a node here"), accel,
3599 menu, _brush_add_node_callback, mask_gui);
3600 ret = TRUE;
3601 }
3602
3603 end:
3604 dt_free(accel);
3605 return ret;
3606}
3607
3608// The function table for brushes. This must be public, i.e. no "static" keyword.
3610 .point_struct_size = sizeof(struct dt_masks_node_brush_t),
3611 .sanitize_config = _brush_sanitize_config,
3612 .set_form_name = _brush_set_form_name,
3613 .set_hint_message = _brush_set_hint_message,
3614 .duplicate_points = _brush_duplicate_points,
3615 .initial_source_pos = _brush_initial_source_pos,
3616 .get_distance = _brush_get_distance,
3617 .get_points_border = _brush_get_points_border,
3618 .get_mask = _brush_get_mask,
3619 .get_mask_roi = _brush_get_mask_roi,
3620 .get_area = _brush_get_area,
3621 .get_source_area = _brush_get_source_area,
3622 .get_gravity_center = _brush_get_gravity_center,
3623 .get_interaction_value = _brush_get_interaction_value,
3624 .set_interaction_value = _brush_set_interaction_value,
3625 .update_hover = _find_closest_handle,
3626 .mouse_moved = _brush_events_mouse_moved,
3627 .mouse_scrolled = _brush_events_mouse_scrolled,
3628 .button_pressed = _brush_events_button_pressed,
3629 .button_released = _brush_events_button_released,
3630 .key_pressed = _brush_events_key_pressed,
3631 .post_expose = _brush_events_post_expose,
3632 .draw_shape = _brush_draw_shape,
3633 .init_ctrl_points = _brush_init_ctrl_points,
3634 .populate_context_menu = _brush_populate_context_menu
3635
3636};
3637
3638
3639// clang-format off
3640// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3641// vim: shiftwidth=2 expandtab tabstop=2 cindent
3642// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3643// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
Definition blend_gui.c:3700
The blending panel's GUI state and API, cut out of develop/blend.h.
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const float x
const float f
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:437
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
#define dt_dev_add_history_item(dev, module, enable, redraw)
static dt_masks_raster_result_t _brush_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 mask_form, const dt_iop_roi_t *roi, float *buffer, dt_iop_roi_t *touched)
Build a brush mask directly into an ROI-sized buffer.
static dt_masks_raster_result_t _brush_get_source_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form, int *width, int *height, int *offset_x, int *offset_y)
static void _brush_initial_source_pos(struct dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
static int _brush_populate_context_menu(GtkWidget *menu, struct dt_masks_form_t *mask_form, struct dt_masks_form_gui_t *mask_gui, const float pointer_x, const float pointer_y)
static gboolean _brush_line_point_at_length(const float *line, const int first_pt, const int last_pt, const float target_len, float *x, float *y)
static float _brush_radius_at(const float r1, const float r2, const double t)
static gboolean _brush_walk_pass(const _brush_walk_t *const w, const gboolean forward)
static float _get_brush_smoothing()
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
static int _init_fading(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, const float amount, const dt_masks_increment_t increment, const int flow)
static float _brush_get_interaction_value(const dt_masks_form_t *mask_form, dt_masks_interaction_t interaction)
static int _brush_events_button_released(struct dt_iop_module_t *module, double widget_x, double widget_y, int which, uint32_t state, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index)
static void _add_node_to_segment(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index)
static float _brush_set_interaction_value(dt_masks_form_t *mask_form, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, dt_masks_form_gui_t *mask_gui, struct dt_iop_module_t *module)
static gboolean _brush_get_border_handle_resampled(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index, float *handle_x, float *handle_y)
get the border handle position corresponding to a node, by looking for the closest border point in th...
static int _init_opacity(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, const float amount, const dt_masks_increment_t increment, const int flow)
static void _brush_translate_all_nodes(dt_masks_form_t *mask_form, const float delta_x, const float delta_y)
static int _change_fading(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
static void _get_pressure_sensitivity(dt_masks_form_gui_t *mask_gui)
static void _brush_translate_node(dt_masks_node_brush_t *node, const float delta_x, const float delta_y)
static gboolean _brush_walk_segment(const _brush_walk_t *const w, _brush_walk_state_t *const s, const int k, const int k1, const gboolean forward)
static gboolean _brush_outline_transform(dt_develop_t *develop, const dt_masks_form_t *const mask_form, const double iop_order, const int transform_direction, const dt_masks_distort_t *const dist, const int use_source, const _brush_outline_t *const o)
static void _brush_reset_round_node_callback(GtkWidget *widget, gpointer user_data)
static void _brush_bounding_box_raw(const float *const restrict points, const float *const restrict border, const int node_count, const int total_points, float *x_min, float *x_max, float *y_min, float *y_max)
Compute the bounding box (min/max) for both brush centerline and border points.
static gboolean _brush_get_line_midpoint(const float *line, const int first_pt, const int last_pt, float *mx, float *my)
static gboolean _brush_border_get_XY(float p0_x, float p0_y, float p1_x, float p1_y, float p2_x, float p2_y, float p3_x, float p3_y, float t, float radius, float radius_rate, float *point_x, float *point_y, float *border_x, float *border_y)
Evaluate a cubic Bezier and compute its offset border point.
static void _apply_pen_pressure(dt_masks_form_gui_t *mask_gui, float *payload_buffer)
#define FADING_MIN
static void _brush_points_recurs_border_small_gaps(float *cmax, float *bmin, float *bmax, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, const int step)
static void _brush_bounding_box(const float *const points, const float *const border, const int node_count, const int total_points, int *width, int *height, int *offset_x, int *offset_y)
Compute integer bounds used for buffer allocation.
static void _brush_set_hint_message(const dt_masks_form_gui_t *const mask_gui, const dt_masks_form_t *const mask_form, char *const restrict msgbuf, const size_t msgbuf_len)
static dt_masks_raster_result_t _brush_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 mask_form, int *width, int *height, int *offset_x, int *offset_y)
static void _brush_falloff(float *const restrict buffer, int segment_start[2], int segment_end[2], int offset_x, int offset_y, int buffer_width, float fading, float density)
static void _brush_curve_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_index, float *handle_x, float *handle_y, void *user_data)
Brush-specific curve handle lookup.
static int _brush_get_pts_border(dt_develop_t *develop, dt_masks_form_t *mask_form, const double iop_order, const int transform_direction, const dt_masks_distort_t *const dist, float **point_buffer, int *point_count, float **border_buffer, int *border_count, float **payload_buffer, int *payload_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int use_source)
static gboolean _brush_get_source_center(const dt_masks_form_gui_points_t *gui_points, const int node_count, dt_masks_gui_center_point_t *center_pt)
static dt_masks_raster_result_t _brush_get_points_border(dt_develop_t *develop, dt_masks_form_t *mask_form, float **point_buffer, int *point_count, float **border_buffer, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int use_source, const dt_iop_module_t *module)
static float _brush_line_length(const float *line, const int first_pt, const int last_pt)
static void _brush_points_stamp(const float *const centre, const float *const from_border, const float radius, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, const int step)
static gboolean _is_within_pxl_threshold(const float *const min, const float *const max, const int pixel_threshold)
static void _brush_ctrl2_to_handle(float point_x, float point_y, float ctrl_x, float ctrl_y, float *handle_x, float *handle_y, gboolean clockwise)
Convert control point 2 into the handle position (orthonormal space).
static void _brush_leaf_borrow_border(float *const border_min, float *const border_max, const gboolean have_min, const gboolean have_max, const float *const centre, const float *const chord, const float radius)
static int _brush_events_mouse_moved(struct dt_iop_module_t *module, double widget_x, double widget_y, double pressure, int which, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index)
Brush mouse-move handler.
static int _brush_events_mouse_scrolled(struct dt_iop_module_t *module, double widget_x, double widget_y, int scroll_up, const int flow, uint32_t state, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index, dt_masks_interaction_t interaction)
static int _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 mask_form, int *width, int *height, int *offset_x, int *offset_y, int include_source)
Compute the minimal bounding box for a brush (optionally including the source path).
static void _brush_walk_node(const _brush_walk_t *const w, const _brush_walk_state_t *const s, const float *const p1, const float *const p2, const float *const c0, const float *const b0, const gboolean forward)
static float _brush_get_position_in_segment(float point_x, float point_y, dt_masks_form_t *mask_form, int segment_index)
static void _brush_distance_cb(float pointer_x, float pointer_y, float cursor_radius, dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside, int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
Brush-specific inside/border/segment hit testing.
static gboolean _brush_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
#define BORDER_MIN
static void _brush_handle_to_ctrl(float ptx, float pty, float fx, float fy, float *ctrl1x, float *ctrl1y, float *ctrl2x, float *ctrl2y, gboolean clockwise)
static void _brush_set_form_name(struct dt_masks_form_t *const mask_form, const size_t form_number)
static void _brush_segment_load(const dt_masks_node_brush_t *const from, const dt_masks_node_brush_t *const to, const gboolean forward, const _brush_frame_t *const frame, float p1[7], float p2[7])
#define BORDER_MAX
static void _brush_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *mask_gui, int index, int node_count)
static void _brush_add_node_callback(GtkWidget *menu, gpointer user_data)
#define FADING_MAX
static void _brush_points_recurs_border_gaps(float *cmax, float *bmin, float *bmax, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, gboolean clockwise, const int step)
static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
static float _brush_radius_rate_at(const float r1, const float r2, const double t)
static void _brush_joint_arc(const float *const centre, const float *const from, const float *const to, const gboolean forward, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, const int step)
static void _brush_points_recurs(const float *const p1, const float *const p2, const _brush_span_t *const span, const _brush_walk_t *const w, _brush_sample_t *const out)
static void _brush_switch_node_callback(GtkWidget *widget, gpointer user_data)
static int _brush_events_button_pressed(struct dt_iop_module_t *module, double widget_x, double widget_y, double pressure, int which, int type, uint32_t state, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index)
static GList * _brush_ramer_douglas_peucker(const float *point_buffer, int point_count, const float *payload_buffer, float epsilon2)
Remove unneeded points (Ramer-Douglas-Peucker) and return the reduced path.
static void _brush_walk_sync_payload(const _brush_walk_t *const w, const float *const payload)
static void _brush_init_ctrl_points(dt_masks_form_t *mask_form)
Initialize all control points to match a Catmull-Rom-like spline.
static int _brush_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, int index)
static void _brush_catmull_to_bezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float *bezier1_x, float *bezier1_y, float *bezier2_x, float *bezier2_y)
Get Bezier control points that match a Catmull-Rom segment.
const dt_masks_functions_t dt_masks_functions_brush
static void _brush_payload_sync(dt_masks_dynbuf_t *dpayload, dt_masks_dynbuf_t *dpoints, const float v0, const float v1)
static gboolean _brush_span_is_leaf(const double tmin, const double tmax, const float *const points_min, const float *const points_max, const float *const border_min, const float *const border_max, const int pixel_threshold)
static void _brush_walk_single_dab(const _brush_walk_t *const w)
static void _brush_falloff_roi(float *buffer, const int *segment_start, const int *segment_end, int buffer_width, int buffer_height, float fading, float density)
static void _brush_sanitize_config(dt_masks_type_t type)
static void _brush_get_distance(float point_x, float point_y, float radius, dt_masks_form_gui_t *mask_gui, int form_index, int corner_count, int *inside, int *inside_border, int *near_handle, int *inside_source, float *distance)
Get the distance between a point and the brush path/border.
static void _brush_get_XY(float p0_x, float p0_y, float p1_x, float p1_y, float p2_x, float p2_y, float p3_x, float p3_y, float t, float *out_x, float *out_y)
Evaluate a cubic Bezier at t in [0, 1].
static gboolean _brush_bufs_alloc(const gboolean want_border, const gboolean want_payload, dt_masks_dynbuf_t **dpoints, dt_masks_dynbuf_t **dborder, dt_masks_dynbuf_t **dpayload)
static int _init_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui, const float amount, const dt_masks_increment_t increment, const int flow)
static void _brush_walk_cap(const _brush_walk_t *const w, const _brush_walk_state_t *const s)
static int _brush_centerline_end(const int points_count, const int node_count)
Index (exclusive) where the centerline's forward pass ends in a brush point array.
static float _brush_point_line_distance2(int point_index, int point_count, const float *point_buffer, const float *payload_buffer)
Get squared distance of a point to the segment, including payload deltas.
static gboolean _brush_get_border_handle_mirrored(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index, float *handle_x, float *handle_y)
static dt_masks_raster_result_t _brush_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 mask_form, float **buffer, int *width, int *height, int *offset_x, int *offset_y)
Build a full-resolution brush mask into a newly allocated buffer.
static void _brush_draw_shape(struct dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int node_nb, const gboolean border, const gboolean source, const dt_masks_skip_range_t *skips, const int skip_count)
static void _brush_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
static gboolean _brush_border_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index, float *handle_x, float *handle_y, void *user_data)
Brush-specific border handle lookup.
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1255
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1285
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:111
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
@ DT_DEV_TRANSFORM_DIR_ALL
Definition develop.h:107
GtkWidget * geometry
its size, under the preview
GHashTable * selected
set of checked row labels, mirrored to conf on every change
GtkWidget * status
result of the last capture
#define DT_DRAW_SIZE_LINE
Definition draw.h:83
static void dt_draw_set_dash_style(cairo_t *cr, dt_draw_dash_type_t type, float zoom_scale)
Definition draw.h:621
static void dt_draw_stroke_line(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const gboolean selected, const float zoom_scale, const cairo_line_cap_t line_cap)
Definition draw.h:952
static void dt_draw_handle(cairo_t *cr, const float pt[2], const float zoom_scale, const float handle[2], const gboolean selected, const gboolean square)
Draw a control handle attached to a point with a tail between the node and the handle.
Definition draw.h:725
@ DT_MASKS_DASH_STICK
Definition draw.h:135
@ DT_MASKS_DASH_ROUND
Definition draw.h:136
@ DT_MASKS_NO_DASH
Definition draw.h:134
static void dt_draw_shape_lines(struct dt_develop_t *dev, const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap, const struct dt_masks_skip_range_t *skips, const int skip_count)
Draw the lines of a mask shape.
Definition draw.h:831
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:669
static const GList * g_list_next_wraparound(const GList *list, const GList *head)
Definition glib_utils.h:52
static GList * g_list_next_bounded(GList *list)
Definition glib_utils.h:47
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition glib_utils.h:34
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
gboolean dt_masks_skip_contains(const dt_masks_skip_range_t *skips, const int skip_count, const int index)
Definition masks.c:306
static float * dt_masks_dynbuf_buffer(dt_masks_dynbuf_t *a)
Definition masks.h:655
static void dt_masks_dynbuf_add_2(dt_masks_dynbuf_t *a, float value1, float value2)
Definition masks.h:582
static dt_masks_raster_result_t dt_masks_raster_from_status(const int status)
Definition masks.h:322
static dt_masks_dynbuf_t * dt_masks_dynbuf_init(size_t size, const char *tag)
Definition masks.h:562
static void dt_masks_translate_source(dt_masks_form_t *form, const float delta_x, const float delta_y)
Definition masks.h:367
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_EMPTY
Definition masks.h:315
@ DT_MASKS_RASTER_OK
Definition masks.h:314
static float dt_masks_dynbuf_get(dt_masks_dynbuf_t *a, int offset)
Definition masks.h:637
static void dt_masks_translate_ctrl_node(float node[2], float ctrl1[2], float ctrl2[2], const float delta_x, const float delta_y)
Definition masks.h:373
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
Definition masks.c:1332
static float * dt_masks_dynbuf_reserve_n(dt_masks_dynbuf_t *a, const int n)
Definition masks.h:597
static gboolean dt_masks_center_of_gravity_from_points(const float *points, const int points_count, float center[2], float *area)
Definition masks.h:661
static size_t dt_masks_dynbuf_position(dt_masks_dynbuf_t *a)
Definition masks.h:732
int dt_masks_form_change_opacity(dt_develop_t *dev, dt_masks_form_t *form, int parentid, int up, const int flow)
Definition masks_gui.c:5840
static gboolean dt_masks_form_is_clone(const dt_masks_form_t *form)
Definition masks.h:356
static void dt_masks_dynbuf_add_zeros(dt_masks_dynbuf_t *a, const int n)
Definition masks.h:617
@ DT_MASKS_PRESSURE_OPACITY_REL
Definition masks.h:175
@ DT_MASKS_PRESSURE_OPACITY_ABS
Definition masks.h:176
@ DT_MASKS_PRESSURE_BRUSHSIZE_REL
Definition masks.h:177
@ DT_MASKS_PRESSURE_FADING_REL
Definition masks.h:173
@ DT_MASKS_PRESSURE_OFF
Definition masks.h:172
@ DT_MASKS_PRESSURE_FADING_ABS
Definition masks.h:174
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
@ DT_MASKS_POINT_STATE_NORMAL
Definition masks.h:156
@ DT_MASKS_POINT_STATE_USER
Definition masks.h:157
static float * dt_masks_dynbuf_harvest(dt_masks_dynbuf_t *a)
Definition masks.h:744
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
static void dt_masks_dynbuf_set(dt_masks_dynbuf_t *a, int offset, float value)
Definition masks.h:646
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
Definition masks.h:754
static void dt_masks_set_ctrl_points(float ctrl1[2], float ctrl2[2], const float control_points[4])
Definition masks.h:384
What a mask outline needs in order to place itself, and who supplies it.
static dt_masks_distort_t dt_masks_distort_for_pipe(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev)
The rendering supplier: compose this pipe, sample as this pipe was told to.
static dt_masks_distort_t dt_masks_distort_for_gui(dt_develop_t *dev)
The GUI supplier: compose through the geometry service, at full resolution, sampled at the density th...
static int dt_masks_distort_transform(const dt_masks_distort_t *const d, const double iop_order, const int transf_direction, float *points, size_t points_count)
Compose forward, bounded exactly as dt_dev_distort_transform_plus() is.
The per-shape function table, private to the masks implementation.
void dt_masks_outline_envelope_offset(const float *centre, float dx, float dy, float radius, float radius_rate, float *out)
int dt_masks_outline_boundary_skips(const float *const points, const float *const border, const int count, const int header, dt_masks_skip_range_t **skips_out)
void dt_masks_outline_offset_along(const float *const centre, float dx, float dy, const float radius, float *const border)
gboolean dt_masks_outline_short_way(const float *const centre, const float *const from, const float *const to, const gboolean default_clockwise)
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index, const int node_count, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
Definition masks_gui.c:3368
gboolean dt_masks_reset_bezier_ctrl_points(struct dt_iop_module_t *module, struct dt_masks_form_t *mask_form, struct dt_masks_form_gui_t *mask_gui, const int form_index, const struct dt_masks_form_gui_points_t *gui_points, const int node_index, dt_masks_points_states_t *state)
Definition masks_gui.c:1123
float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value, float value_min, float value_max, dt_masks_increment_t increment, int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
Definition masks_gui.c:5275
gboolean dt_masks_toggle_bezier_node_type(struct dt_iop_module_t *module, struct dt_masks_form_t *mask_form, struct dt_masks_form_gui_t *mask_gui, const int form_index, const struct dt_masks_form_gui_points_t *gui_points, const int node_index, float node[2], float ctrl1[2], float ctrl2[2], dt_masks_points_states_t *state)
Definition masks_gui.c:1095
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
gboolean dt_masks_gui_form_create_throttled(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module, float posx, float posy)
Definition masks_gui.c:1976
dt_masks_form_group_t * dt_masks_form_get_selected_group(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Get the selected group entry from the GUI selection index.
Definition masks_gui.c:1522
gboolean dt_masks_is_anything_selected(const dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2658
gboolean dt_masks_node_is_cusp(const dt_masks_form_gui_points_t *gui_points, const int node_index)
returns wether a node is a corner or not. A node is a corner if its 2 control handles are at the same...
Definition masks_gui.c:3282
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
void dt_masks_draw_path_seg_by_seg(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index, const float *points, const int points_count, const int node_count, const float zoom_scale, const gboolean round_ends)
Definition masks_gui.c:3483
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
Definition masks_gui.c:1475
float dt_masks_apply_increment_precomputed(float current, float amount, float scale_amount, float offset_amount, dt_masks_increment_t increment)
Apply a scroll increment using precomputed scale/offset factors.
Definition masks_gui.c:5260
void dt_masks_draw_outline_runs(cairo_t *cr, const float *const points, const int first, const int last, const dt_masks_skip_range_t *skips, const int skip_count)
Definition masks_gui.c:6049
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *mask_gui, dt_masks_form_t *mask_form)
Initialize the clone source position based on current GUI state.
Definition masks_gui.c:5570
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Centralized hit-testing for node/handle/segment selection across shapes.
Definition masks_gui.c:1183
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *mask_gui, const uint32_t key_state)
Decide initial source positioning mode for clone masks.
Definition masks_gui.c:5545
void dt_masks_gui_form_create(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module)
Definition masks_gui.c:1914
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount, float value_min, float value_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
Definition masks_gui.c:5305
void dt_masks_select_form(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *selected_form)
Select or clear the current mask form, notifying the owning module if needed.
Definition masks_gui.c:5527
void dt_masks_gui_form_save_creation(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui)
Save the form creation right after a shape has been finished drawing.
Definition masks_gui.c:2525
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
static void dt_masks_gui_delta_to_image_abs(const dt_masks_form_gui_t *gui, float point[2])
Definition masks_gui.h:319
static int dt_masks_gui_selected_segment_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:250
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:235
static int dt_masks_gui_selected_handle_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:240
static void dt_masks_draw_source_preview(cairo_t *cr, const float zoom_scale, dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, const int adding)
Definition masks_gui.h:719
static float dt_masks_get_form_size_from_nodes(const GList *points)
Definition masks_gui.h:264
static int dt_masks_gui_selected_handle_border_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:245
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks_gui.h:304
static float dt_masks_border_from_projected_handle(dt_develop_t *dev, const float node[2], const float projected_image_pos[2], const float scale_ref)
Definition masks_gui.h:386
static gboolean dt_masks_gui_change_affects_selected_node_or_all(const dt_masks_form_gui_t *gui, const int index)
Definition masks_gui.h:255
static void dt_masks_gui_delta_from_raw_anchor(dt_develop_t *dev, const dt_masks_form_gui_t *gui, const float anchor[2], float *delta_x, float *delta_y)
Definition masks_gui.h:327
static void dt_masks_project_on_line(const float cursor[2], const float node[2], const float handle[2], float point[2])
Definition masks_gui.h:361
static gboolean dt_masks_gui_points_reach(const dt_masks_form_gui_points_t *gp, const float x, const float y, const float reach)
Definition masks_gui.h:83
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks_gui.h:312
static void dt_masks_touched_set(dt_iop_roi_t *touched, int x0, int y0, int x1, int y1, const int width, const int height)
static void dt_masks_touched_none(dt_iop_roi_t *touched)
@ DT_MASKS_EDIT_FULL
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_GROUP
Definition masks_types.h:66
dt_masks_interaction_t
@ DT_MASKS_INTERACTION_OPACITY
@ DT_MASKS_INTERACTION_SIZE
@ DT_MASKS_INTERACTION_FADING
dt_masks_increment_t
@ DT_MASKS_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GtkWidget * ctx_gtk_menu_item_new_with_icon_and_shortcut(const char *label, const char *shortcut, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, dt_menu_icon_t icon)
Definition menu.c:127
GtkWidget * ctx_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:170
GtkWidget * ctx_gtk_menu_item_new_with_markup_and_shortcut(const char *label, const char *shortcut, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:185
dt_menu_icon_t
Definition menu.h:30
@ DT_MENU_ICON_CIRCLE
Definition menu.h:32
@ DT_MENU_ICON_SQUARE
Definition menu.h:33
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const float factor
Definition pdf.h:91
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
dt_masks_dynbuf_t * dpayload
_brush_frame_t frame
dt_masks_dynbuf_t * dpoints
dt_masks_dynbuf_t * dborder
dt_masks_node_brush_t ** nodes
Objective facts about the image a dev is working on.
int mask_rasterization_step
Rasterisation step for drawn masks, in image pixels. Always >= 1.
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_develop_t * dev
Definition imageop.h:311
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
Where an outline builder gets its geometry from.
dt_masks_skip_range_t * border_skips
Definition masks_gui.h:70
gboolean node_selected
Definition masks_gui.h:145
gboolean handle_border_selected
Definition masks_gui.h:148
dt_masks_dynbuf_t * guipoints_payload
Definition masks_gui.h:110
gboolean source_selected
Definition masks_gui.h:153
gboolean source_dragging
Definition masks_gui.h:161
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
dt_masks_pressure_sensitivity_t pressure_sensitivity
Definition masks_gui.h:188
gboolean seg_selected
Definition masks_gui.h:147
gboolean form_dragging
Definition masks_gui.h:160
gboolean form_selected
Definition masks_gui.h:151
gboolean handle_selected
Definition masks_gui.h:146
dt_masks_dynbuf_t * guipoints
Definition masks_gui.h:110
gboolean border_selected
Definition masks_gui.h:152
struct dt_develop_t * dev
Definition masks_gui.h:101
dt_masks_type_t type
Definition masks.h:254
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
shape_draw_function_t draw_shape
struct dt_masks_gui_center_point_t::@27 main
struct dt_masks_gui_center_point_t::@28 source
dt_masks_points_states_t state
Definition masks.h:230
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
void dt_widget_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
static GdkModifierType dt_accels_display_mods(GdkModifierType mods)
@ DT_GUI_COLOR_BRUSH_CURSOR
@ DT_GUI_COLOR_BRUSH_TRACE
#define DT_PIXEL_APPLY_DPI(value)