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 "common/darktable.h"
38#include "bauhaus/bauhaus.h"
39#include "common/debug.h"
40#include "common/imagebuf.h"
41#include "common/undo.h"
42#include "control/conf.h"
43#include "develop/blend.h"
44#include "develop/imageop.h"
45#include "develop/masks.h"
47#include "gui/actions/menu.h"
48
49#define HARDNESS_MIN 0.00001f
50#define HARDNESS_MAX 1.0f
51
52#define BORDER_MIN 0.00005f
53#define BORDER_MAX 0.5f
54
61static float _brush_point_line_distance2(int point_index, int point_count,
62 const float *point_buffer, const float *payload_buffer)
63{
64 const float point_x = point_buffer[2 * point_index];
65 const float point_y = point_buffer[2 * point_index + 1];
66 const float point_border = payload_buffer[4 * point_index];
67 const float point_hardness = payload_buffer[4 * point_index + 1];
68 const float point_density = payload_buffer[4 * point_index + 2];
69 const float start_x = point_buffer[0];
70 const float start_y = point_buffer[1];
71 const float start_border = payload_buffer[0];
72 const float start_hardness = payload_buffer[1];
73 const float start_density = payload_buffer[2];
74 const float end_x = point_buffer[2 * (point_count - 1)];
75 const float end_y = point_buffer[2 * (point_count - 1) + 1];
76 const float end_border = payload_buffer[4 * (point_count - 1)];
77 const float end_hardness = payload_buffer[4 * (point_count - 1) + 1];
78 const float end_density = payload_buffer[4 * (point_count - 1) + 2];
79 const float bweight = 1.0f;
80 const float hweight = 0.01f;
81 const float dweight = 0.01f;
82
83 const float segment_dx = end_x - start_x;
84 const float segment_dy = end_y - start_y;
85 const float segment_db = end_border - start_border;
86 const float segment_dh = end_hardness - start_hardness;
87 const float segment_dd = end_density - start_density;
88
89 const float dot = (point_x - start_x) * segment_dx + (point_y - start_y) * segment_dy;
90 const float segment_len2 = sqf(segment_dx) + sqf(segment_dy);
91 const float t = dot / segment_len2;
92
93 float dx = 0.0f, dy = 0.0f, db = 0.0f, dh = 0.0f, dd = 0.0f;
94
95 if(segment_len2 == 0.0f)
96 {
97 dx = point_x - start_x;
98 dy = point_y - start_y;
99 db = point_border - start_border;
100 dh = point_hardness - start_hardness;
101 dd = point_density - start_density;
102 }
103 else if(t < 0.0f)
104 {
105 dx = point_x - start_x;
106 dy = point_y - start_y;
107 db = point_border - start_border;
108 dh = point_hardness - start_hardness;
109 dd = point_density - start_density;
110 }
111 else if(t > 1.0f)
112 {
113 dx = point_x - end_x;
114 dy = point_y - end_y;
115 db = point_border - end_border;
116 dh = point_hardness - end_hardness;
117 dd = point_density - end_density;
118 }
119 else
120 {
121 dx = point_x - (start_x + t * segment_dx);
122 dy = point_y - (start_y + t * segment_dy);
123 db = point_border - (start_border + t * segment_db);
124 dh = point_hardness - (start_hardness + t * segment_dh);
125 dd = point_density - (start_density + t * segment_dd);
126 }
127
128 return sqf(dx) + sqf(dy) + bweight * sqf(db) + hweight * dh * dh + dweight * sqf(dd);
129}
130
134static GList *_brush_ramer_douglas_peucker(const float *point_buffer, int point_count,
135 const float *payload_buffer, float epsilon2)
136{
137 GList *result_list = NULL;
138
139 float dmax2 = 0.0f;
140 int split_index = 0;
141
142 for(int point_index = 1; point_index < point_count - 1; point_index++)
143 {
144 const float d2 = _brush_point_line_distance2(point_index, point_count,
145 point_buffer, payload_buffer);
146 if(d2 > dmax2)
147 {
148 split_index = point_index;
149 dmax2 = d2;
150 }
151 }
152
153 if(dmax2 >= epsilon2)
154 {
155 GList *left_list = _brush_ramer_douglas_peucker(point_buffer, split_index + 1,
156 payload_buffer, epsilon2);
157 GList *right_list = _brush_ramer_douglas_peucker(point_buffer + split_index * 2,
158 point_count - split_index,
159 payload_buffer + split_index * 4, epsilon2);
160
161 // remove last element from left_list to avoid duplication at the split
162 GList *end1 = g_list_last(left_list);
163 dt_free(end1->data);
164 left_list = g_list_delete_link(left_list, end1);
165
166 result_list = g_list_concat(left_list, right_list);
167 }
168 else
169 {
170 dt_masks_node_brush_t *first_node = malloc(sizeof(dt_masks_node_brush_t));
171 first_node->node[0] = point_buffer[0];
172 first_node->node[1] = point_buffer[1];
173 first_node->ctrl1[0] = first_node->ctrl1[1] = first_node->ctrl2[0] = first_node->ctrl2[1] = -1.0f;
174 first_node->border[0] = first_node->border[1] = payload_buffer[0];
175 first_node->hardness = payload_buffer[1];
176 first_node->density = payload_buffer[2];
178 result_list = g_list_append(result_list, (gpointer)first_node);
179
180 dt_masks_node_brush_t *last_node = malloc(sizeof(dt_masks_node_brush_t));
181 last_node->node[0] = point_buffer[(point_count - 1) * 2];
182 last_node->node[1] = point_buffer[(point_count - 1) * 2 + 1];
183 last_node->ctrl1[0] = last_node->ctrl1[1] = last_node->ctrl2[0] = last_node->ctrl2[1] = -1.0f;
184 last_node->border[0] = last_node->border[1] = payload_buffer[(point_count - 1) * 4];
185 last_node->hardness = payload_buffer[(point_count - 1) * 4 + 1];
186 last_node->density = payload_buffer[(point_count - 1) * 4 + 2];
188 result_list = g_list_append(result_list, (gpointer)last_node);
189 }
190
191 return result_list;
192}
193
197static 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,
198 float p3_y, float t, float *out_x, float *out_y)
199{
200 const float ti = 1.0f - t;
201 const float a = ti * ti * ti;
202 const float b = 3.0f * t * ti * ti;
203 const float c = 3.0f * sqf(t) * ti;
204 const float d = t * t * t;
205 *out_x = p0_x * a + p1_x * b + p2_x * c + p3_x * d;
206 *out_y = p0_y * a + p1_y * b + p2_y * c + p3_y * d;
207}
208
212static void _brush_border_get_XY(float p0_x, float p0_y, float p1_x, float p1_y, float p2_x, float p2_y,
213 float p3_x, float p3_y, float t, float radius,
214 float *point_x, float *point_y, float *border_x, float *border_y)
215{
216 // we get the point
217 _brush_get_XY(p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, t, point_x, point_y);
218
219 // now we get derivative points
220 const float ti = 1.0f - t;
221 const float a = 3.0f * ti * ti;
222 const float b = 3.0f * (ti * ti - 2.0f * t * ti);
223 const float c = 3.0f * (2.0f * t * ti - t * t);
224 const float d = 3.0f * sqf(t);
225
226 const float dx = -p0_x * a + p1_x * b + p2_x * c + p3_x * d;
227 const float dy = -p0_y * a + p1_y * b + p2_y * c + p3_y * d;
228
229 // so we can have the resulting point
230 if(dx == 0 && dy == 0)
231 {
232 *border_x = NAN;
233 *border_y = NAN;
234 return;
235 }
236 const float l = 1.0f / sqrtf(dx * dx + dy * dy);
237 *border_x = (*point_x) + radius * dy * l;
238 *border_y = (*point_y) - radius * dx * l;
239}
240
244static void _brush_ctrl2_to_handle(float point_x, float point_y, float ctrl_x, float ctrl_y,
245 float *handle_x, float *handle_y, gboolean clockwise)
246{
247 if(clockwise)
248 {
249 *handle_x = point_x + ctrl_y - point_y;
250 *handle_y = point_y + point_x - ctrl_x;
251 }
252 else
253 {
254 *handle_x = point_x - ctrl_y + point_y;
255 *handle_y = point_y - point_x + ctrl_x;
256 }
257}
258
273static gboolean _brush_get_border_handle_resampled(const dt_masks_form_gui_points_t *gui_points, int node_count,
274 int node_index, float *handle_x, float *handle_y)
275{
276 if(IS_NULL_PTR(gui_points) || node_count <= 0 || node_index < 0 || node_index >= node_count) return FALSE;
277
278 const int start = node_count * 3;
279 const int max_points = MIN(gui_points->points_count, gui_points->border_count);
280 if(max_points <= start) return FALSE;
281
282 const float node_x = gui_points->points[node_index * 6 + 2];
283 const float node_y = gui_points->points[node_index * 6 + 3];
284
285 float best_dist2 = FLT_MAX;
286 int best_idx = -1;
287
288 for(int i = start; i < max_points; i++)
289 {
290 const float px = gui_points->points[i * 2];
291 const float py = gui_points->points[i * 2 + 1];
292 if(isnan(px) || isnan(py)) continue;
293
294 const float dx = node_x - px;
295 const float dy = node_y - py;
296 const float dist2 = dx * dx + dy * dy;
297 if(dist2 < best_dist2)
298 {
299 best_dist2 = dist2;
300 best_idx = i;
301 }
302 }
303
304 if(best_idx < 0) return FALSE;
305
306 *handle_x = gui_points->border[best_idx * 2];
307 *handle_y = gui_points->border[best_idx * 2 + 1];
308 return !(isnan(*handle_x) || isnan(*handle_y));
309}
310
311static gboolean _brush_get_border_handle_mirrored(const dt_masks_form_gui_points_t *gui_points, int node_count,
312 int node_index, float *handle_x, float *handle_y)
313{
314 float resampled_x = NAN;
315 float resampled_y = NAN;
316 if(!_brush_get_border_handle_resampled(gui_points, node_count, node_index, &resampled_x, &resampled_y)) return FALSE;
317
318 const float node_x = gui_points->points[node_index * 6 + 2];
319 const float node_y = gui_points->points[node_index * 6 + 3];
320
321 *handle_x = node_x - (resampled_x - node_x);
322 *handle_y = node_y - (resampled_y - node_y);
323 return !(isnan(*handle_x) || isnan(*handle_y));
324}
325
328static void _brush_handle_to_ctrl(float ptx, float pty, float fx, float fy,
329 float *ctrl1x, float *ctrl1y,
330 float *ctrl2x, float *ctrl2y, gboolean clockwise)
331{
332 if(clockwise)
333 {
334 *ctrl2x = ptx + pty - fy;
335 *ctrl2y = pty + fx - ptx;
336 *ctrl1x = ptx - pty + fy;
337 *ctrl1y = pty - fx + ptx;
338 }
339 else
340 {
341 *ctrl1x = ptx + pty - fy;
342 *ctrl1y = pty + fx - ptx;
343 *ctrl2x = ptx - pty + fy;
344 *ctrl2y = pty - fx + ptx;
345 }
346}
347
351static void _brush_catmull_to_bezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4,
352 float y4, float *bezier1_x, float *bezier1_y,
353 float *bezier2_x, float *bezier2_y)
354{
355 *bezier1_x = (-x1 + 6 * x2 + x3) / 6;
356 *bezier1_y = (-y1 + 6 * y2 + y3) / 6;
357 *bezier2_x = (x2 + 6 * x3 - x4) / 6;
358 *bezier2_y = (y2 + 6 * y3 - y4) / 6;
359}
360
367{
368 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return;
369 // if we have less than 2 points, what to do ??
370 if(g_list_shorter_than(mask_form->points, 2)) return;
371
372 // we need extra points to deal with curve ends
373 dt_masks_node_brush_t start_point[2], end_point[2];
374
375 for(GList *form_points = mask_form->points; form_points; form_points = g_list_next(form_points))
376 {
377 dt_masks_node_brush_t *point3 = (dt_masks_node_brush_t *)form_points->data;
378 if(IS_NULL_PTR(point3)) continue;
379 // if the point has not been set manually, we redefine it
381 {
382 // we want to get point-2, point-1, point+1, point+2
383 GList *const prev = g_list_previous(form_points); // point-1
384 GList *const prevprev = prev ? g_list_previous(prev) : NULL; // point-2
385 GList *const next = g_list_next(form_points); // point+1
386 GList *const nextnext = next ? g_list_next(next) : NULL; // point+2
387 dt_masks_node_brush_t *point1 = prevprev ? prevprev->data : NULL;
388 dt_masks_node_brush_t *point2 = prev ? prev->data : NULL;
389 dt_masks_node_brush_t *point4 = next ? next->data : NULL;
390 dt_masks_node_brush_t *point5 = nextnext ? nextnext->data : NULL;
391
392 // deal with end points: make both extending points mirror their neighborhood
393 if(IS_NULL_PTR(point1) && IS_NULL_PTR(point2))
394 {
395 start_point[0].node[0] = start_point[1].node[0] = 2 * point3->node[0] - point4->node[0];
396 start_point[0].node[1] = start_point[1].node[1] = 2 * point3->node[1] - point4->node[1];
397 point1 = &(start_point[0]);
398 point2 = &(start_point[1]);
399 }
400 else if(IS_NULL_PTR(point1))
401 {
402 start_point[0].node[0] = 2 * point2->node[0] - point3->node[0];
403 start_point[0].node[1] = 2 * point2->node[1] - point3->node[1];
404 point1 = &(start_point[0]);
405 }
406
407 if(IS_NULL_PTR(point4) && IS_NULL_PTR(point5))
408 {
409 end_point[0].node[0] = end_point[1].node[0] = 2 * point3->node[0] - point2->node[0];
410 end_point[0].node[1] = end_point[1].node[1] = 2 * point3->node[1] - point2->node[1];
411 point4 = &(end_point[0]);
412 point5 = &(end_point[1]);
413 }
414 else if(IS_NULL_PTR(point5))
415 {
416 end_point[0].node[0] = 2 * point4->node[0] - point3->node[0];
417 end_point[0].node[1] = 2 * point4->node[1] - point3->node[1];
418 point5 = &(end_point[0]);
419 }
420
421
422 float bx1 = 0.0f, by1 = 0.0f, bx2 = 0.0f, by2 = 0.0f;
423 _brush_catmull_to_bezier(point1->node[0], point1->node[1], point2->node[0], point2->node[1],
424 point3->node[0], point3->node[1], point4->node[0], point4->node[1],
425 &bx1, &by1, &bx2, &by2);
426 if(point2->ctrl2[0] == -1.0) point2->ctrl2[0] = bx1;
427 if(point2->ctrl2[1] == -1.0) point2->ctrl2[1] = by1;
428 point3->ctrl1[0] = bx2;
429 point3->ctrl1[1] = by2;
430 _brush_catmull_to_bezier(point2->node[0], point2->node[1], point3->node[0], point3->node[1],
431 point4->node[0], point4->node[1], point5->node[0], point5->node[1],
432 &bx1, &by1, &bx2, &by2);
433 if(point4->ctrl1[0] == -1.0) point4->ctrl1[0] = bx2;
434 if(point4->ctrl1[1] == -1.0) point4->ctrl1[1] = by2;
435 point3->ctrl2[0] = bx1;
436 point3->ctrl2[1] = by1;
437 }
438 }
439}
440
441
444static void _brush_points_recurs_border_gaps(float *cmax, float *bmin, float *bmin2, float *bmax,
445 dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
446 gboolean clockwise)
447{
448 // we want to find the start and end angles
449 float a1 = atan2f(bmin[1] - cmax[1], bmin[0] - cmax[0]);
450 float a2 = atan2f(bmax[1] - cmax[1], bmax[0] - cmax[0]);
451
452 if(a1 == a2) return;
453
454 // we have to be sure that we turn in the correct direction
455 if(a2 < a1 && clockwise)
456 {
457 a2 += 2.0f * M_PI;
458 }
459 if(a2 > a1 && !clockwise)
460 {
461 a1 += 2.0f * M_PI;
462 }
463
464 // we determine start and end radius too
465 float r1 = sqrtf((bmin[1] - cmax[1]) * (bmin[1] - cmax[1]) + (bmin[0] - cmax[0]) * (bmin[0] - cmax[0]));
466 float r2 = sqrtf((bmax[1] - cmax[1]) * (bmax[1] - cmax[1]) + (bmax[0] - cmax[0]) * (bmax[0] - cmax[0]));
467
468 // and the max length of the circle arc
469 const int l = fabsf(a2 - a1) * fmaxf(r1, r2);
470 if(l < 2) return;
471
472 // and now we add the points
473 const float incra = (a2 - a1) / l;
474 const float incrr = (r2 - r1) / l;
475
476 // Use incremental rotation to avoid repeated cosf/sinf calls
477 const float cos_incra = cosf(incra);
478 const float sin_incra = sinf(incra);
479 float rr = r1 + incrr;
480 float cos_aa = cosf(a1 + incra);
481 float sin_aa = sinf(a1 + incra);
482
483 // allocate entries in the dynbufs
484 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
485 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
486 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
487 // successive angular positions for dborder
488 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
489 {
490 for(int i = 1; i < l; i++)
491 {
492 *dpoints_ptr++ = cmax[0];
493 *dpoints_ptr++ = cmax[1];
494 *dborder_ptr++ = cmax[0] + rr * cos_aa;
495 *dborder_ptr++ = cmax[1] + rr * sin_aa;
496
497 // Incremental rotation: rotate by incra using addition formulas
498 const float new_cos = cos_aa * cos_incra - sin_aa * sin_incra;
499 const float new_sin = sin_aa * cos_incra + cos_aa * sin_incra;
500 cos_aa = new_cos;
501 sin_aa = new_sin;
502 rr += incrr;
503 }
504 }
505}
506
510static void _brush_points_recurs_border_small_gaps(float *cmax, float *bmin, float *bmin2, float *bmax,
511 dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder)
512{
513 // we want to find the start and end angles
514 const float a1 = fmodf(atan2f(bmin[1] - cmax[1], bmin[0] - cmax[0]) + 2.0f * M_PI, 2.0f * M_PI);
515 const float a2 = fmodf(atan2f(bmax[1] - cmax[1], bmax[0] - cmax[0]) + 2.0f * M_PI, 2.0f * M_PI);
516
517 if(a1 == a2) return;
518
519 // we determine start and end radius too
520 const float r1 = sqrtf((bmin[1] - cmax[1]) * (bmin[1] - cmax[1]) + (bmin[0] - cmax[0]) * (bmin[0] - cmax[0]));
521 const float r2 = sqrtf((bmax[1] - cmax[1]) * (bmax[1] - cmax[1]) + (bmax[0] - cmax[0]) * (bmax[0] - cmax[0]));
522
523 // we close the gap in the shortest direction
524 float delta = a2 - a1;
525 if(fabsf(delta) > M_PI) delta = delta - copysignf(2.0f * M_PI, delta);
526
527 // get the max length of the circle arc
528 const int l = fabsf(delta) * fmaxf(r1, r2);
529 if(l < 2) return;
530
531 // and now we add the points
532 const float incra = delta / l;
533 const float incrr = (r2 - r1) / l;
534
535 // Use incremental rotation to avoid repeated cosf/sinf calls
536 const float cos_incra = cosf(incra);
537 const float sin_incra = sinf(incra);
538 float rr = r1 + incrr;
539 float cos_aa = cosf(a1 + incra);
540 float sin_aa = sinf(a1 + incra);
541
542 // allocate entries in the dynbufs
543 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
544 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
545 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
546 // successive angular positions for dborder
547 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
548 {
549 for(int i = 1; i < l; i++)
550 {
551 *dpoints_ptr++ = cmax[0];
552 *dpoints_ptr++ = cmax[1];
553 *dborder_ptr++ = cmax[0] + rr * cos_aa;
554 *dborder_ptr++ = cmax[1] + rr * sin_aa;
555
556 // Incremental rotation: rotate by incra using addition formulas
557 const float new_cos = cos_aa * cos_incra - sin_aa * sin_incra;
558 const float new_sin = sin_aa * cos_incra + cos_aa * sin_incra;
559 cos_aa = new_cos;
560 sin_aa = new_sin;
561 rr += incrr;
562 }
563 }
564}
565
566
569static void _brush_points_stamp(float *cmax, float *bmin, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
570 gboolean clockwise)
571{
572 // we want to find the start angle
573 const float a1 = atan2f(bmin[1] - cmax[1], bmin[0] - cmax[0]);
574
575 // we determine the radius too
576 const float rad = sqrtf((bmin[1] - cmax[1]) * (bmin[1] - cmax[1]) + (bmin[0] - cmax[0]) * (bmin[0] - cmax[0]));
577
578 // determine the max length of the circle arc
579 const int l = 2.0f * M_PI * rad;
580 if(l < 2) return;
581
582 // and now we add the points
583 const float incra = 2.0f * M_PI / l;
584 float aa = a1 + incra;
585 // allocate entries in the dynbuf
586 float *dpoints_ptr = dt_masks_dynbuf_reserve_n(dpoints, 2*(l-1));
587 float *dborder_ptr = dt_masks_dynbuf_reserve_n(dborder, 2*(l-1));
588 // and fill them in: the same center pos for each point in dpoints, and the corresponding border point at
589 // successive angular positions for dborder
590 if (!IS_NULL_PTR(dpoints_ptr) && !IS_NULL_PTR(dborder_ptr))
591 {
592 for(int i = 0; i < l; i++)
593 {
594 *dpoints_ptr++ = cmax[0];
595 *dpoints_ptr++ = cmax[1];
596 *dborder_ptr++ = cmax[0] + rad * cosf(aa);
597 *dborder_ptr++ = cmax[1] + rad * sinf(aa);
598 aa += incra;
599 }
600 }
601}
602
603static inline gboolean _is_within_pxl_threshold(float *min, float *max, int pixel_threshold)
604{
605 return abs((int)min[0] - (int)max[0]) < pixel_threshold &&
606 abs((int)min[1] - (int)max[1]) < pixel_threshold;
607}
608
609static inline void _brush_payload_sync(dt_masks_dynbuf_t *dpayload, dt_masks_dynbuf_t *dpoints,
610 const float v0, const float v1)
611{
612 size_t payload_pos = dt_masks_dynbuf_position(dpayload);
613 const size_t target_pos = dt_masks_dynbuf_position(dpoints);
614 while(payload_pos < target_pos)
615 {
616 dt_masks_dynbuf_add_2(dpayload, v0, v1);
617 payload_pos += 2;
618 }
619}
620
623static void _brush_points_recurs(float *p1, float *p2, double tmin, double tmax, float *points_min,
624 float *points_max, float *border_min, float *border_max, float *rpoints,
625 float *rborder, float *rpayload, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder,
626 dt_masks_dynbuf_t *dpayload, const int pixel_threshold)
627{
628 const gboolean withborder = (!IS_NULL_PTR(dborder));
629 const gboolean withpayload = (!IS_NULL_PTR(dpayload));
630
631 // we calculate points if needed
632 if(isnan(points_min[0]))
633 {
634 _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], tmin,
635 p1[4] + (p2[4] - p1[4]) * tmin * tmin * (3.0 - 2.0 * tmin), points_min,
636 points_min + 1, border_min, border_min + 1);
637 }
638 if(isnan(points_max[0]))
639 {
640 _brush_border_get_XY(p1[0], p1[1], p1[2], p1[3], p2[2], p2[3], p2[0], p2[1], tmax,
641 p1[4] + (p2[4] - p1[4]) * tmax * tmax * (3.0 - 2.0 * tmax), points_max,
642 points_max + 1, border_max, border_max + 1);
643 }
644
645 // are the points near ?
646 if((tmax - tmin < 0.0001f)
647 || (_is_within_pxl_threshold(points_min, points_max, pixel_threshold)
648 && (!withborder || (_is_within_pxl_threshold(border_min, border_max, pixel_threshold)))))
649 {
650 rpoints[0] = points_max[0];
651 rpoints[1] = points_max[1];
652 dt_masks_dynbuf_add_2(dpoints, rpoints[0], rpoints[1]);
653
654 if(withborder)
655 {
656 if(isnan(border_max[0]))
657 {
658 border_max[0] = border_min[0];
659 border_max[1] = border_min[1];
660 }
661 else if(isnan(border_min[0]))
662 {
663 border_min[0] = border_max[0];
664 border_min[1] = border_max[1];
665 }
666
667 // we check gaps in the border (sharp edges)
668 if(abs((int)border_max[0] - (int)border_min[0]) > 2 || abs((int)border_max[1] - (int)border_min[1]) > 2)
669 {
670 _brush_points_recurs_border_small_gaps(points_max, border_min, NULL, border_max, dpoints, dborder);
671 }
672
673 rborder[0] = border_max[0];
674 rborder[1] = border_max[1];
675 dt_masks_dynbuf_add_2(dborder, rborder[0], rborder[1]);
676 }
677
678 if(withpayload)
679 {
680 rpayload[0] = p1[5] + tmax * (p2[5] - p1[5]);
681 rpayload[1] = p1[6] + tmax * (p2[6] - p1[6]);
682 _brush_payload_sync(dpayload, dpoints, rpayload[0], rpayload[1]);
683 }
684
685 return;
686 }
687
688 // we split in two part
689 double tx = (tmin + tmax) / 2.0;
690 float c[2] = { NAN, NAN }, b[2] = { NAN, NAN };
691 float rc[2], rb[2], rp[2];
692 _brush_points_recurs(p1, p2, tmin, tx, points_min, c, border_min, b, rc, rb, rp, dpoints, dborder, dpayload,
693 pixel_threshold);
694 _brush_points_recurs(p1, p2, tx, tmax, rc, points_max, rb, border_max, rpoints, rborder, rpayload, dpoints,
695 dborder, dpayload, pixel_threshold);
696}
697
698
701static inline int _brush_cyclic_cursor(int n, int nb)
702{
703 const int o = n % (2 * nb);
704 const int p = o % nb;
705
706 return (o <= p) ? o : o - 2 * p - 1;
707}
708
709
712// Brush points are stored in a cyclic way because the border goes around the main line.
713// This means that it record the main line twice (up and down) while the border only once (around).
714static int _brush_get_pts_border(dt_develop_t *develop, dt_masks_form_t *mask_form,
715 const double iop_order, const int transform_direction,
716 dt_dev_pixelpipe_t *pipe, float **point_buffer, int *point_count,
717 float **border_buffer, int *border_count, float **payload_buffer,
718 int *payload_count, int use_source)
719{
720 *point_buffer = NULL;
721 *point_count = 0;
722 if(border_buffer) *border_buffer = NULL;
723 if(!IS_NULL_PTR(border_buffer)) *border_count = 0;
724 if(payload_buffer) *payload_buffer = NULL;
725 if(!IS_NULL_PTR(payload_buffer)) *payload_count = 0;
726
727 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
728 double start2 = 0.0;
730
731 const float iwd = pipe->iwidth;
732 const float iht = pipe->iheight;
733 const int pixel_threshold = (dt_dev_pixelpipe_has_preview_output(darktable.develop, pipe, NULL)
734 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL) ? 3 : 1;
735
736 dt_masks_dynbuf_t *dpoints = NULL, *dborder = NULL, *dpayload = NULL;
737
738 dpoints = dt_masks_dynbuf_init(1000000, "brush dpoints");
739 if(IS_NULL_PTR(dpoints)) return 1;
740
741 if(!IS_NULL_PTR(border_buffer))
742 {
743 dborder = dt_masks_dynbuf_init(1000000, "brush dborder");
744 if(IS_NULL_PTR(dborder))
745 {
746 dt_masks_dynbuf_free(dpoints);
747 return 1;
748 }
749 }
750
751 if(!IS_NULL_PTR(payload_buffer))
752 {
753 dpayload = dt_masks_dynbuf_init(1000000, "brush dpayload");
754 if(IS_NULL_PTR(dpayload))
755 {
756 dt_masks_dynbuf_free(dpoints);
757 dt_masks_dynbuf_free(dborder);
758 return 1;
759 }
760 }
761
762 // we store all points
763 float dx = 0.0f, dy = 0.0f;
764
765 if(use_source && mask_form->points && transform_direction != DT_DEV_TRANSFORM_DIR_ALL)
766 {
767 dt_masks_node_brush_t *pt = (dt_masks_node_brush_t *)mask_form->points->data;
768 dx = (pt->node[0] - mask_form->source[0]) * iwd;
769 dy = (pt->node[1] - mask_form->source[1]) * iht;
770 }
771
772 const guint node_count = g_list_length(mask_form->points);
773
774 dt_masks_node_brush_t **nodes = malloc((size_t)node_count * sizeof(*nodes));
775 if(!nodes)
776 {
777 dt_masks_dynbuf_free(dpoints);
778 dt_masks_dynbuf_free(dborder);
779 dt_masks_dynbuf_free(dpayload);
780 return 1;
781 }
782
783 guint node_index = 0;
784 for(GList *form_points = mask_form->points; form_points; form_points = g_list_next(form_points))
785 {
786 const dt_masks_node_brush_t *const pt = (dt_masks_node_brush_t *)form_points->data;
787 nodes[node_index++] = (dt_masks_node_brush_t *)form_points->data;
788 float *const buf = dt_masks_dynbuf_reserve_n(dpoints, 6);
789 if (buf)
790 {
791 buf[0] = pt->ctrl1[0] * iwd - dx;
792 buf[1] = pt->ctrl1[1] * iht - dy;
793 buf[2] = pt->node[0] * iwd - dx;
794 buf[3] = pt->node[1] * iht - dy;
795 buf[4] = pt->ctrl2[0] * iwd - dx;
796 buf[5] = pt->ctrl2[1] * iht - dy;
797 }
798 }
799
800 // for the border, we store value too
801 if(!IS_NULL_PTR(dborder))
802 {
803 dt_masks_dynbuf_add_zeros(dborder, 6 * node_count); // we need six zeros for each border point
804 }
805
806 // for the payload, we reserve an equivalent number of cells to keep it in sync
807 if(!IS_NULL_PTR(dpayload))
808 {
809 dt_masks_dynbuf_add_zeros(dpayload, 6 * node_count); // we need six zeros for each border point
810 }
811
812 int cw = 1;
813 int start_stamp = 0;
814
816 {
817 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points init took %0.04f sec\n", mask_form->name,
818 dt_get_wtime() - start2);
819 start2 = dt_get_wtime();
820 }
821
822 // we render all segments first upwards, then downwards
823 for(int n = 0; n < 2 * node_count; n++)
824 {
825 float p1[7], p2[7], p3[7], p4[7];
826 const int k = _brush_cyclic_cursor(n, node_count);
827 const int k1 = _brush_cyclic_cursor(n + 1, node_count);
828 const int k2 = _brush_cyclic_cursor(n + 2, node_count);
829 const gboolean allow_border_gap_rounding = FALSE;
830
831 dt_masks_node_brush_t *point1 = nodes[k];
832 dt_masks_node_brush_t *point2 = nodes[k1];
833 dt_masks_node_brush_t *point3 = nodes[k2];
834 if(cw > 0)
835 {
836 const float pa[7] = { point1->node[0] * iwd - dx, point1->node[1] * iht - dy, point1->ctrl2[0] * iwd - dx,
837 point1->ctrl2[1] * iht - dy, point1->border[1] * MIN(iwd, iht), point1->hardness,
838 point1->density };
839 const float pb[7] = { point2->node[0] * iwd - dx, point2->node[1] * iht - dy, point2->ctrl1[0] * iwd - dx,
840 point2->ctrl1[1] * iht - dy, point2->border[0] * MIN(iwd, iht), point2->hardness,
841 point2->density };
842 const float pc[7] = { point2->node[0] * iwd - dx, point2->node[1] * iht - dy, point2->ctrl2[0] * iwd - dx,
843 point2->ctrl2[1] * iht - dy, point2->border[1] * MIN(iwd, iht), point2->hardness,
844 point2->density };
845 const float pd[7] = { point3->node[0] * iwd - dx, point3->node[1] * iht - dy, point3->ctrl1[0] * iwd - dx,
846 point3->ctrl1[1] * iht - dy, point3->border[0] * MIN(iwd, iht), point3->hardness,
847 point3->density };
848 memcpy(p1, pa, sizeof(float) * 7);
849 memcpy(p2, pb, sizeof(float) * 7);
850 memcpy(p3, pc, sizeof(float) * 7);
851 memcpy(p4, pd, sizeof(float) * 7);
852 }
853 else
854 {
855 const float pa[7] = { point1->node[0] * iwd - dx, point1->node[1] * iht - dy, point1->ctrl1[0] * iwd - dx,
856 point1->ctrl1[1] * iht - dy, point1->border[1] * MIN(iwd, iht), point1->hardness,
857 point1->density };
858 const float pb[7] = { point2->node[0] * iwd - dx, point2->node[1] * iht - dy, point2->ctrl2[0] * iwd - dx,
859 point2->ctrl2[1] * iht - dy, point2->border[0] * MIN(iwd, iht), point2->hardness,
860 point2->density };
861 const float pc[7] = { point2->node[0] * iwd - dx, point2->node[1] * iht - dy, point2->ctrl1[0] * iwd - dx,
862 point2->ctrl1[1] * iht - dy, point2->border[1] * MIN(iwd, iht), point2->hardness,
863 point2->density };
864 const float pd[7] = { point3->node[0] * iwd - dx, point3->node[1] * iht - dy, point3->ctrl2[0] * iwd - dx,
865 point3->ctrl2[1] * iht - dy, point3->border[0] * MIN(iwd, iht), point3->hardness,
866 point3->density };
867 memcpy(p1, pa, sizeof(float) * 7);
868 memcpy(p2, pb, sizeof(float) * 7);
869 memcpy(p3, pc, sizeof(float) * 7);
870 memcpy(p4, pd, sizeof(float) * 7);
871 }
872
873 // 1st. special case: render abrupt transitions between different opacity and/or hardness values
874 if((fabsf(p1[5] - p2[5]) > 0.05f || fabsf(p1[6] - p2[6]) > 0.05f)
875 || (start_stamp && n == 2 * node_count - 1))
876 {
877 if(n == 0)
878 {
879 start_stamp = 1; // remember to deal with the first node as a final step
880 }
881 else
882 {
883 if(!IS_NULL_PTR(dborder))
884 {
885 float bmin[2] = { dt_masks_dynbuf_get(dborder, -2), dt_masks_dynbuf_get(dborder, -1) };
886 float cmax[2] = { dt_masks_dynbuf_get(dpoints, -2), dt_masks_dynbuf_get(dpoints, -1) };
887 _brush_points_stamp(cmax, bmin, dpoints, dborder, TRUE);
888 }
889
890 if(!IS_NULL_PTR(dpayload))
891 {
892 _brush_payload_sync(dpayload, dpoints, p1[5], p1[6]);
893 }
894 }
895 }
896
897 // 2nd. special case: render transition point between different brush sizes
898 if(fabsf(p1[4] - p2[4]) > 0.0001f && n > 0)
899 {
900 if(!IS_NULL_PTR(dborder))
901 {
902 float bmin[2] = { dt_masks_dynbuf_get(dborder, -2), dt_masks_dynbuf_get(dborder, -1) };
903 float cmax[2] = { dt_masks_dynbuf_get(dpoints, -2), dt_masks_dynbuf_get(dpoints, -1) };
904 float bmax[2] = { 2 * cmax[0] - bmin[0], 2 * cmax[1] - bmin[1] };
905 if(allow_border_gap_rounding)
906 _brush_points_recurs_border_gaps(cmax, bmin, NULL, bmax, dpoints, dborder, TRUE);
907 }
908
909 if(!IS_NULL_PTR(dpayload))
910 {
911 _brush_payload_sync(dpayload, dpoints, p1[5], p1[6]);
912 }
913 }
914
915 // 3rd. special case: render endpoints
916 if(k == k1)
917 {
918 if(!IS_NULL_PTR(dborder))
919 {
920 float bmin[2] = { dt_masks_dynbuf_get(dborder, -2), dt_masks_dynbuf_get(dborder, -1) };
921 float cmax[2] = { dt_masks_dynbuf_get(dpoints, -2), dt_masks_dynbuf_get(dpoints, -1) };
922 float bmax[2] = { 2 * cmax[0] - bmin[0], 2 * cmax[1] - bmin[1] };
923 _brush_points_recurs_border_gaps(cmax, bmin, NULL, bmax, dpoints, dborder, TRUE);
924 }
925
926 if(!IS_NULL_PTR(dpayload))
927 {
928 _brush_payload_sync(dpayload, dpoints, p1[5], p1[6]);
929 }
930
931 cw *= -1;
932 continue;
933 }
934
935 // and we determine all points by recursion (to be sure the distance between 2 points is <=1)
936 float rc[2], rb[2], rp[2];
937 float bmin[2] = { NAN, NAN };
938 float bmax[2] = { NAN, NAN };
939 float cmin[2] = { NAN, NAN };
940 float cmax[2] = { NAN, NAN };
941
942 _brush_points_recurs(p1, p2, 0.0, 1.0, cmin, cmax, bmin, bmax, rc, rb, rp, dpoints, dborder, dpayload,
943 pixel_threshold);
944
945 dt_masks_dynbuf_add_2(dpoints, rc[0], rc[1]);
946
947 if(!IS_NULL_PTR(dpayload))
948 {
949 dt_masks_dynbuf_add_2(dpayload, rp[0], rp[1]);
950 }
951
952 if(!IS_NULL_PTR(dborder))
953 {
954 if(isnan(rb[0]))
955 {
956 float lastb0 = dt_masks_dynbuf_get(dborder, -2);
957 float lastb1 = dt_masks_dynbuf_get(dborder, -1);
958 if(isnan(lastb0))
959 {
960 lastb0 = dt_masks_dynbuf_get(dborder, -4);
961 lastb1 = dt_masks_dynbuf_get(dborder, -3);
962 dt_masks_dynbuf_set(dborder, -2, lastb0);
963 dt_masks_dynbuf_set(dborder, -1, lastb1);
964 }
965 rb[0] = lastb0;
966 rb[1] = lastb1;
967 }
968 dt_masks_dynbuf_add_2(dborder, rb[0], rb[1]);
969 }
970
971 // we first want to be sure that there are no gaps in border
972 if(!IS_NULL_PTR(dborder) && node_count >= 3)
973 {
974 // we get the next point (start of the next segment)
975 _brush_border_get_XY(p3[0], p3[1], p3[2], p3[3], p4[2], p4[3], p4[0], p4[1], 0, p3[4], cmin, cmin + 1,
976 bmax, bmax + 1);
977 if(isnan(bmax[0]))
978 {
979 _brush_border_get_XY(p3[0], p3[1], p3[2], p3[3], p4[2], p4[3], p4[0], p4[1], 0.0001, p3[4], cmin,
980 cmin + 1, bmax, bmax + 1);
981 }
982 if(bmax[0] - rb[0] > 1 || bmax[0] - rb[0] < -1 || bmax[1] - rb[1] > 1 || bmax[1] - rb[1] < -1)
983 {
984 // float bmin2[2] = {(*border)[posb-22],(*border)[posb-21]};
985 if(allow_border_gap_rounding)
986 _brush_points_recurs_border_gaps(rc, rb, NULL, bmax, dpoints, dborder, cw);
987 }
988 }
989
990 if(!IS_NULL_PTR(dpayload))
991 {
992 _brush_payload_sync(dpayload, dpoints, rp[0], rp[1]);
993 }
994 }
995
996 dt_free(nodes);
997
998 *point_count = dt_masks_dynbuf_position(dpoints) / 2;
999 *point_buffer = dt_masks_dynbuf_harvest(dpoints);
1000 dt_masks_dynbuf_free(dpoints);
1001
1002 if(!IS_NULL_PTR(dborder))
1003 {
1004 *border_count = dt_masks_dynbuf_position(dborder) / 2;
1005 *border_buffer = dt_masks_dynbuf_harvest(dborder);
1006 dt_masks_dynbuf_free(dborder);
1007 }
1008
1009 if(!IS_NULL_PTR(dpayload))
1010 {
1011 *payload_count = dt_masks_dynbuf_position(dpayload) / 2;
1012 *payload_buffer = dt_masks_dynbuf_harvest(dpayload);
1013 dt_masks_dynbuf_free(dpayload);
1014 }
1015 // printf("points %d, border %d, playload %d\n", *points_count, border ? *border_count : -1, payload ?
1016 // *payload_count : -1);
1017
1019 {
1020 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points point recurs %0.04f sec\n", mask_form->name,
1021 dt_get_wtime() - start2);
1022 start2 = dt_get_wtime();
1023 }
1024
1025 // and we transform them with all distorted modules
1026 if(use_source && transform_direction == DT_DEV_TRANSFORM_DIR_ALL)
1027 {
1028 // we transform with all distortion that happen *before* the module
1029 // so we have now the TARGET points in module input reference
1031 *point_buffer, *point_count))
1032 {
1033 // now we move all the points by the shift
1034 // so we have now the SOURCE points in module input reference
1035 float pts[2] = { mask_form->source[0], mask_form->source[1] };
1038 goto fail;
1039
1040 dx = pts[0] - (*point_buffer)[2];
1041 dy = pts[1] - (*point_buffer)[3];
1042 __OMP_PARALLEL_FOR_SIMD__(if(*point_count > 100) aligned(point_buffer:64))
1043 for(int i = 0; i < *point_count; i++)
1044 {
1045 (*point_buffer)[i * 2] += dx;
1046 (*point_buffer)[i * 2 + 1] += dy;
1047 }
1048
1049 // we apply the rest of the distortions (those after the module)
1050 // so we have now the SOURCE points in final image reference
1052 *point_buffer, *point_count))
1053 goto fail;
1054 }
1055
1057 dt_print(DT_DEBUG_MASKS, "[masks %s] path_points end took %0.04f sec\n",
1058 mask_form->name, dt_get_wtime() - start2);
1059
1060 return 0;
1061 }
1062 else if(dt_dev_distort_transform_plus(pipe, iop_order, transform_direction,
1063 *point_buffer, *point_count))
1064 {
1065 if(!border_buffer
1066 || dt_dev_distort_transform_plus(pipe, iop_order, transform_direction,
1067 *border_buffer, *border_count))
1068 {
1070 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_points transform took %0.04f sec\n",
1071 mask_form->name,
1072 dt_get_wtime() - start2);
1073 return 0;
1074 }
1075 }
1076
1077 // if we failed, then free all and return
1078fail:
1079 dt_pixelpipe_cache_free_align(*point_buffer);
1080 *point_buffer = NULL;
1081 *point_count = 0;
1082 if(!IS_NULL_PTR(border_buffer))
1083 {
1084 dt_pixelpipe_cache_free_align(*border_buffer);
1085 *border_buffer = NULL;
1086 *border_count = 0;
1087 }
1088 if(!IS_NULL_PTR(payload_buffer))
1089 {
1090 dt_pixelpipe_cache_free_align(*payload_buffer);
1091 *payload_buffer = NULL;
1092 *payload_count = 0;
1093 }
1094 return 1;
1095}
1096
1100static void _brush_get_distance(float point_x, float point_y, float radius,
1101 dt_masks_form_gui_t *mask_gui, int form_index,
1102 int corner_count, int *inside, int *inside_border,
1103 int *near, int *inside_source, float *distance)
1104{
1105 if(IS_NULL_PTR(mask_gui)) return;
1106
1107 // initialise returned values
1108 *inside_source = 0;
1109 *inside = 0;
1110 *inside_border = 0;
1111 *near = -1;
1112 *distance = FLT_MAX;
1113
1114 dt_masks_form_gui_points_t *gui_points
1115 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
1116 if(IS_NULL_PTR(gui_points)) return;
1117
1118 float min_dist = FLT_MAX;
1119
1120 const float radius2 = radius * radius;
1121
1122 // we first check if we are inside the source form
1123
1124 // add support for clone masks
1125 if(gui_points->points && gui_points->source
1126 && gui_points->points_count > 2 + corner_count * 3
1127 && gui_points->source_count > 2 + corner_count * 3)
1128 {
1129 // distance from form origin and source origin
1130 const float dx = -gui_points->points[2] + gui_points->source[2];
1131 const float dy = -gui_points->points[3] + gui_points->source[3];
1132
1133 int current_seg = 1;
1134 for(int i = corner_count * 3; i < gui_points->points_count; i++)
1135 {
1136 // do we change of path segment ?
1137 if(gui_points->points[i * 2 + 1] == gui_points->points[current_seg * 6 + 3]
1138 && gui_points->points[i * 2] == gui_points->points[current_seg * 6 + 2])
1139 {
1140 current_seg = (current_seg + 1) % corner_count;
1141 }
1142 // distance from tested point to current form point
1143 const float yy = gui_points->points[i * 2 + 1] + dy;
1144 const float xx = gui_points->points[i * 2] + dx;
1145
1146 const float sdx = point_x - xx;
1147 const float sdy = point_y - yy;
1148 const float dd = (sdx * sdx) + (sdy * sdy);
1149 if(dd < min_dist)
1150 {
1151 min_dist = dd;
1152
1153 if(dd < radius2 && *inside == 0)
1154 {
1155 if(current_seg == 0)
1156 *inside_source = corner_count - 1;
1157 else
1158 *inside_source = current_seg - 1;
1159
1160 if(*inside_source)
1161 {
1162 *inside = 1;
1163 }
1164 }
1165 }
1166 }
1167 }
1168
1169 // we check if it's inside borders
1170 if(gui_points->border && gui_points->border_count > 2 + corner_count * 3)
1171 {
1172 int nearest = -1;
1173
1174 const int start = corner_count * 3;
1175 const float *const border = gui_points->border;
1176 float last_y = border[gui_points->border_count * 2 - 1];
1177 int crossings = 0;
1178
1179 for(int i = start; i < gui_points->border_count; i++)
1180 {
1181 const int idx = i * 2;
1182 const float xx = border[idx];
1183 const float yy = border[idx + 1];
1184
1185 const float dx = point_x - xx;
1186 const float dy = point_y - yy;
1187 if(dx * dx + dy * dy < radius2) nearest = idx;
1188
1189 if(((point_y <= yy && point_y > last_y) || (point_y >= yy && point_y < last_y)) && (xx > point_x))
1190 crossings++;
1191
1192 last_y = yy;
1193 }
1194
1195 *inside = *inside_border = (nearest != -1 || (crossings & 1));
1196 }
1197
1198 // and we check if we are near a segment
1199 if(gui_points->points && gui_points->points_count > 2 + corner_count * 3)
1200 {
1201 int current_seg = 1;
1202 for(int i = corner_count * 3; i < gui_points->points_count; i++)
1203 {
1204 // do we change of path segment ?
1205 if(gui_points->points[i * 2 + 1] == gui_points->points[current_seg * 6 + 3]
1206 && gui_points->points[i * 2] == gui_points->points[current_seg * 6 + 2])
1207 {
1208 current_seg = (current_seg + 1) % corner_count;
1209 }
1210 //distance from tested point to current form point
1211 const float yy = gui_points->points[i * 2 + 1];
1212 const float xx = gui_points->points[i * 2];
1213
1214 const float dx = point_x - xx;
1215 const float dy = point_y - yy;
1216 const float dd = (dx * dx) + (dy * dy);
1217 if(dd < min_dist)
1218 {
1219 min_dist = dd;
1220
1221 if(current_seg > 0 && dd < radius2)
1222 {
1223 *near = current_seg - 1;
1224 }
1225 }
1226 }
1227 }
1228
1229 *distance = min_dist;
1230}
1231
1233 float **point_buffer, int *point_count,
1234 float **border_buffer, int *border_count,
1235 int use_source, const dt_iop_module_t *module)
1236{
1237 if(use_source && IS_NULL_PTR(module)) return 1;
1238 const double ioporder = (module) ? module->iop_order : 0.0f;
1239 return _brush_get_pts_border(develop, mask_form, ioporder, DT_DEV_TRANSFORM_DIR_ALL,
1240 develop->virtual_pipe, point_buffer, point_count, border_buffer,
1241 border_count, NULL, NULL, use_source);
1242}
1243
1247static float _brush_get_position_in_segment(float point_x, float point_y,
1248 dt_masks_form_t *mask_form, int segment_index)
1249{
1250 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1251 GList *first_node_entry = g_list_nth(mask_form->points, segment_index);
1252 dt_masks_node_brush_t *point0 = (dt_masks_node_brush_t *)first_node_entry->data;
1253 // advance to next node in list, if not already on the last
1254 GList *next_node_entry = g_list_next_bounded(first_node_entry);
1255 dt_masks_node_brush_t *point1 = (dt_masks_node_brush_t *)next_node_entry->data;
1256 next_node_entry = g_list_next_bounded(next_node_entry);
1257 dt_masks_node_brush_t *point2 = (dt_masks_node_brush_t *)next_node_entry->data;
1258 next_node_entry = g_list_next_bounded(next_node_entry);
1259 dt_masks_node_brush_t *point3 = (dt_masks_node_brush_t *)next_node_entry->data;
1260
1261 float best_t = 0.0f;
1262 float best_dist2 = FLT_MAX;
1263
1264 for(int sample_index = 0; sample_index <= 100; sample_index++)
1265 {
1266 const float t = sample_index / 100.0f;
1267 float sample_x = 0.0f;
1268 float sample_y = 0.0f;
1269 _brush_get_XY(point0->node[0], point0->node[1], point1->node[0], point1->node[1],
1270 point2->node[0], point2->node[1], point3->node[0], point3->node[1], t, &sample_x, &sample_y);
1271
1272 const float dist2 = (point_x - sample_x) * (point_x - sample_x)
1273 + (point_y - sample_y) * (point_y - sample_y);
1274 if(dist2 < best_dist2)
1275 {
1276 best_dist2 = dist2;
1277 best_t = t;
1278 }
1279 }
1280
1281 return best_t;
1282}
1283
1289static gboolean _brush_border_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index,
1290 float *handle_x, float *handle_y, void *user_data)
1291{
1292 return _brush_get_border_handle_mirrored(gui_points, node_count, node_index, handle_x, handle_y);
1293}
1294
1300static void _brush_curve_handle_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
1301 float *handle_x, float *handle_y, void *user_data)
1302{
1303 _brush_ctrl2_to_handle(gui_points->points[node_index * 6 + 2], gui_points->points[node_index * 6 + 3],
1304 gui_points->points[node_index * 6 + 4], gui_points->points[node_index * 6 + 5],
1305 handle_x, handle_y, TRUE);
1306}
1307
1311static void _brush_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
1312 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
1313 int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
1314{
1315 _brush_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, node_count,
1316 inside, inside_border, near, inside_source, dist);
1317}
1318
1319static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
1320{
1321 return dt_masks_find_closest_handle_common(mask_form, mask_gui, form_index, -1,
1323 _brush_distance_cb, NULL, NULL);
1324}
1325
1326
1327static int _init_hardness(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1328 const float amount, const dt_masks_increment_t increment, const int flow)
1329{
1330 const float masks_hardness = dt_masks_get_set_conf_value_with_toast(mask_form, "hardness", amount,
1331 HARDNESS_MIN, HARDNESS_MAX, increment, flow,
1332 _("hardness: %3.2f%%"), 100.0f);
1333 if(mask_gui->guipoints_count > 0)
1334 dt_masks_dynbuf_set(mask_gui->guipoints_payload, -3, masks_hardness);
1335 return 1;
1336}
1337
1338static int _init_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1339 const float amount, const dt_masks_increment_t increment, const int flow)
1340{
1341 const float masks_border = dt_masks_get_set_conf_value_with_toast(mask_form, "border", amount,
1342 HARDNESS_MIN, HARDNESS_MAX, increment, flow,
1343 _("size: %3.2f%%"), 2.f * 100.f);
1344 if(mask_gui->guipoints_count > 0)
1345 dt_masks_dynbuf_set(mask_gui->guipoints_payload, -4, masks_border);
1346 return 1;
1347}
1348
1349static int _init_opacity(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1350 const float amount, const dt_masks_increment_t increment, const int flow)
1351{
1352 dt_masks_get_set_conf_value_with_toast(mask_form, "opacity", amount, 0.f, 1.f,
1353 increment, flow, _("opacity: %3.2f%%"), 100.f);
1354 return 1;
1355}
1356
1358{
1359 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return NAN;
1360
1361 switch(interaction)
1362 {
1364 {
1365 const float size = dt_masks_get_form_size_from_nodes(mask_form->points);
1366 if(size <= 0.0f) return NAN;
1367 return size;
1368 }
1370 {
1371 float hardness_sum = 0.0f;
1372 int hardness_count = 0;
1373
1374 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1375 {
1376 const dt_masks_node_brush_t *node = (const dt_masks_node_brush_t *)point_node->data;
1377 if(IS_NULL_PTR(node)) continue;
1378 hardness_sum += node->hardness;
1379 hardness_count++;
1380 }
1381
1382 return hardness_count > 0 ? hardness_sum / (float)hardness_count : NAN;
1383 }
1384 default:
1385 return NAN;
1386 }
1387}
1388
1389static gboolean _brush_get_gravity_center(const dt_masks_form_t *mask_form, float center[2], float *area)
1390{
1391 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return FALSE;
1392
1393 const int points_count = g_list_length(mask_form->points);
1394 if(points_count <= 0) return FALSE;
1395
1396 float *point_buffer = dt_alloc_align_float((size_t)points_count * 2);
1397 if(IS_NULL_PTR(point_buffer)) return FALSE;
1398
1399 int i = 0;
1400 for(const GList *l = mask_form->points; l; l = g_list_next(l))
1401 {
1402 const dt_masks_node_brush_t *point = (const dt_masks_node_brush_t *)l->data;
1403 if(IS_NULL_PTR(point)) continue;
1404 point_buffer[2 * i] = point->node[0];
1405 point_buffer[2 * i + 1] = point->node[1];
1406 i++;
1407 }
1408
1409 const gboolean ok = dt_masks_center_of_gravity_from_points(point_buffer, i, center, area);
1410 dt_free_align(point_buffer);
1411 return ok;
1412}
1413
1414static int _change_hardness(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1415 struct dt_iop_module_t *module, int index, const float amount,
1416 const dt_masks_increment_t increment, const int flow);
1417static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1418 struct dt_iop_module_t *module, int index, const float amount,
1419 const dt_masks_increment_t increment, const int flow);
1420
1422 dt_masks_increment_t increment, int flow,
1423 dt_masks_form_gui_t *mask_gui, struct dt_iop_module_t *module)
1424{
1425 if(IS_NULL_PTR(mask_form)) return NAN;
1426 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
1427 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
1428 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
1429 const int index = (!IS_NULL_PTR(mask_gui) && mask_gui->group_selected >= 0) ? mask_gui->group_selected : 0;
1430
1431 switch(interaction)
1432 {
1434 if(!_change_size(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1435 return _brush_get_interaction_value(mask_form, interaction);
1437 if(!_change_hardness(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1438 return _brush_get_interaction_value(mask_form, interaction);
1439 default:
1440 return NAN;
1441 }
1442}
1443
1444static int _change_hardness(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1445 struct dt_iop_module_t *module, int index, const float amount,
1446 const dt_masks_increment_t increment, const int flow)
1447{
1448 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1449 int node_index = 0;
1450 const float scale_amount = 1.f / powf(amount, (float)flow);
1451 const float offset_amount = -amount * (float)flow;
1452 float result_amount = 0.0f;
1453 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1454 {
1455 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1456 {
1457 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1458 const float current_hardness = node->hardness;
1459 result_amount = dt_masks_apply_increment_precomputed(current_hardness, amount, scale_amount, offset_amount, increment);
1460
1461 node->hardness = CLAMPF(result_amount, HARDNESS_MIN, HARDNESS_MAX);
1462 }
1463 }
1464
1465 dt_masks_get_set_conf_value(mask_form, "hardness", result_amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
1466
1467 // we recreate the form points
1468 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1469
1470 return 1;
1471}
1472
1473static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1474 struct dt_iop_module_t *module, int index, const float amount,
1475 const dt_masks_increment_t increment, const int flow)
1476{
1477 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1478 // Sanitize loop
1479 // do not exceed upper limit of 1.0 and lower limit of 0.004
1480 int node_index = 0;
1481 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1482 {
1483 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1484 {
1485 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1486 if(!node) continue;
1487 if(amount > 1.0f && (node->border[0] > 1.0f || node->border[1] > 1.0f))
1488 return 1;
1489 }
1490 }
1491
1492 // Growing/shrinking loop
1493 const float scale_amount = amount;
1494 const float offset_amount = amount;
1495 node_index = 0;
1496 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1497 {
1498 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1499 {
1500 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1501 if(!node) continue;
1502 node->border[0] = dt_masks_apply_increment_precomputed(node->border[0], amount,
1503 scale_amount, offset_amount, increment);
1504 node->border[1] = dt_masks_apply_increment_precomputed(node->border[1], amount,
1505 scale_amount, offset_amount, increment);
1506 }
1507 }
1508
1509 dt_masks_get_set_conf_value(mask_form, "border", amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
1510
1511 // we recreate the form points
1512 if(!IS_NULL_PTR(mask_gui) && !IS_NULL_PTR(module)) dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1513
1514 return 1;
1515}
1516
1517/* Shape handlers receive widget-space coordinates, while normalized output-image
1518 * coordinates come from `mask_gui->rel_pos` and absolute output-image
1519 * coordinates come from `mask_gui->pos`. */
1520static int _brush_events_mouse_scrolled(struct dt_iop_module_t *module, double widget_x, double widget_y,
1521 int scroll_up, const int flow, uint32_t state, dt_masks_form_t *mask_form,
1522 int parentid, dt_masks_form_gui_t *mask_gui, int index,
1523 dt_masks_interaction_t interaction)
1524{
1525
1526
1527
1528 if(mask_gui->creation)
1529 {
1530 if(dt_modifier_is(state, GDK_SHIFT_MASK))
1531 return _init_hardness(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
1532 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
1533 return _init_opacity(mask_form, parentid, mask_gui, scroll_up ? +0.02f : -0.02f,
1535 else
1536 return _init_size(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
1537 }
1538 else if(dt_masks_is_anything_selected(mask_gui) || mask_gui->node_hovered >= 0)
1539 {
1540 // we register the current position
1541 if(mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
1542 {
1543 mask_gui->scrollx = mask_gui->pos[0];
1544 mask_gui->scrolly = mask_gui->pos[1];
1545 }
1546
1547 if(dt_modifier_is(state, GDK_CONTROL_MASK))
1548 return dt_masks_form_change_opacity(mask_form, parentid, scroll_up, flow);
1549 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
1550 return _change_hardness(mask_form, parentid, mask_gui, module, index, scroll_up ? -0.01f : 0.01f,
1552 else // resize don't care where the mouse is inside a shape
1553 return _change_size(mask_form, parentid, mask_gui, module, index, scroll_up ? 1.02f : 0.98f,
1555 }
1556 return 0;
1557}
1558
1559
1561{
1563 const char *psens = dt_conf_get_string_const("pressure_sensitivity");
1564 if(!IS_NULL_PTR(psens))
1565 {
1566 if(!strcmp(psens, "hardness (absolute)"))
1568 else if(!strcmp(psens, "hardness (relative)"))
1570 else if(!strcmp(psens, "opacity (absolute)"))
1572 else if(!strcmp(psens, "opacity (relative)"))
1574 else if(!strcmp(psens, "brush size (relative)"))
1576 }
1577}
1578
1579static void _add_node_to_segment(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parentid,
1580 dt_masks_form_gui_t *mask_gui, int index)
1581{
1582 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points) || IS_NULL_PTR(mask_gui)) return;
1583 const guint node_count = g_list_length(mask_form->points);
1584 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
1585 if(selected_segment < 0 || selected_segment >= (int)node_count) return;
1586
1587 // we add a new node to the brush
1588 dt_masks_node_brush_t *new_node = (dt_masks_node_brush_t *)(malloc(sizeof(dt_masks_node_brush_t)));
1589 if(IS_NULL_PTR(new_node)) return;
1590
1591 // set coordinates
1593 new_node->ctrl1[0] = new_node->ctrl1[1] = new_node->ctrl2[0] = new_node->ctrl2[1] = -1.0;
1595
1596 // set other attributes of the new node. we interpolate the starting and the end node of that
1597 // segment
1598 const float t = _brush_get_position_in_segment(new_node->node[0], new_node->node[1],
1599 mask_form, selected_segment);
1600 // start and end node of the segment
1601 GList *pt = g_list_nth(mask_form->points, selected_segment);
1602 if(IS_NULL_PTR(pt) || IS_NULL_PTR(pt->data))
1603 {
1604 dt_free(new_node);
1605 return;
1606 }
1607 dt_masks_node_brush_t *point0 = (dt_masks_node_brush_t *)pt->data;
1608 const GList *const next_pt = g_list_next_wraparound(pt, mask_form->points);
1609 if(IS_NULL_PTR(next_pt) || IS_NULL_PTR(next_pt->data))
1610 {
1611 dt_free(new_node);
1612 return;
1613 }
1614 dt_masks_node_brush_t *point1 = (dt_masks_node_brush_t *)next_pt->data;
1615 new_node->border[0] = point0->border[0] * (1.0f - t) + point1->border[0] * t;
1616 new_node->border[1] = point0->border[1] * (1.0f - t) + point1->border[1] * t;
1617 new_node->hardness = point0->hardness * (1.0f - t) + point1->hardness * t;
1618 new_node->density = point0->density * (1.0f - t) + point1->density * t;
1619
1620 mask_form->points = g_list_insert(mask_form->points, new_node, selected_segment + 1);
1621 _brush_init_ctrl_points(mask_form);
1622
1623 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1624
1625 mask_gui->node_hovered = selected_segment + 1;
1626 mask_gui->node_selected = TRUE;
1627 mask_gui->node_selected_idx = selected_segment + 1;
1628 mask_gui->seg_hovered = -1;
1629 mask_gui->seg_selected = FALSE;
1630}
1631
1632static inline void _brush_translate_node(dt_masks_node_brush_t *node, const float delta_x, const float delta_y)
1633{
1634 dt_masks_translate_ctrl_node(node->node, node->ctrl1, node->ctrl2, delta_x, delta_y);
1635}
1636
1637static void _brush_translate_all_nodes(dt_masks_form_t *mask_form, const float delta_x, const float delta_y)
1638{
1639 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry))
1640 _brush_translate_node((dt_masks_node_brush_t *)node_entry->data, delta_x, delta_y);
1641}
1642
1643static int _brush_events_button_pressed(struct dt_iop_module_t *module, double widget_x, double widget_y,
1644 double pressure, int which, int type, uint32_t state,
1645 dt_masks_form_t *mask_form, int parentid,
1646 dt_masks_form_gui_t *mask_gui, int index)
1647{
1648 // double click or triple click: ignore here
1649 if(type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return 1;
1650
1651 // always start with a mask density of 100%, it will be adjusted with pen pressure if used.
1652 const float masks_density = 1.0f;
1653
1654 if(mask_gui->creation)
1655 {
1656 if(which == 1)
1657 {
1658 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
1659 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
1661 float masks_hardness = dt_masks_get_set_conf_value(mask_form, "hardness", 1.0f,
1663
1664 if(dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK) || dt_modifier_is(state, GDK_SHIFT_MASK))
1665 {
1666 // set some absolute or relative position for the source of the clone mask
1667 if(mask_form->type & DT_MASKS_CLONE)
1669
1670 return 1;
1671 }
1672
1673 if(IS_NULL_PTR(mask_gui->guipoints)) mask_gui->guipoints = dt_masks_dynbuf_init(200000, "brush guipoints");
1674 if(IS_NULL_PTR(mask_gui->guipoints)) return 1;
1675 if(IS_NULL_PTR(mask_gui->guipoints_payload))
1676 mask_gui->guipoints_payload = dt_masks_dynbuf_init(400000, "brush guipoints_payload");
1677 if(IS_NULL_PTR(mask_gui->guipoints_payload)) return 1;
1678 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
1679 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_border, masks_hardness);
1680 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_density, pressure);
1682 mask_gui->guipoints_count = 1;
1683
1684 // add support for clone masks
1685 if(mask_form->type & DT_MASKS_CLONE)
1686 dt_masks_set_source_pos_initial_value(mask_gui, mask_form);
1687 // not used by regular masks
1688 else
1689 mask_form->source[0] = mask_form->source[1] = 0.0f;
1690
1691 _get_pressure_sensitivity(mask_gui);
1692
1693 return 1;
1694 }
1695 }
1696
1697 dt_masks_form_gui_points_t *gui_points
1698 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
1699 if(IS_NULL_PTR(gui_points)) return 0;
1700 const guint node_count = g_list_length(mask_form->points);
1701
1702 if(which == 1)
1703 {
1704 // The shape handler runs before the shared press-state selection update,
1705 // so concrete hovered targets must win over stale form/source selection.
1706 if(mask_gui->node_hovered >= 0)
1707 {
1708 // if ctrl is pressed, we change the type of point
1709 if(mask_gui->node_selected && dt_modifier_is(state, GDK_CONTROL_MASK))
1710 {
1712 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
1713 if(IS_NULL_PTR(node)) return 0;
1714 dt_masks_toggle_bezier_node_type(module, mask_form, mask_gui, index, gui_points,
1715 mask_gui->node_hovered, node->node, node->ctrl1, node->ctrl2,
1716 &node->state);
1717 return 1;
1718 }
1719 /*// we register the current position to avoid accidental move
1720 if(mask_gui->node_edited < 0 && mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
1721 {
1722 mask_gui->scrollx = pointer_x;
1723 mask_gui->scrolly = pointer_y;
1724 }*/
1725 mask_gui->delta[0] = gui_points->points[mask_gui->node_hovered * 6 + 2] - mask_gui->pos[0];
1726 mask_gui->delta[1] = gui_points->points[mask_gui->node_hovered * 6 + 3] - mask_gui->pos[1];
1727
1728 return 1;
1729 }
1730 else if(mask_gui->handle_hovered >= 0)
1731 {
1732 if(!dt_masks_node_is_cusp(gui_points, mask_gui->handle_hovered))
1733 {
1734 // we need to find the handle position
1735 float handle_x, handle_y;
1736 const int k = mask_gui->handle_hovered;
1737 _brush_ctrl2_to_handle(gui_points->points[k * 6 + 2], gui_points->points[k * 6 + 3],
1738 gui_points->points[k * 6 + 4], gui_points->points[k * 6 + 5],
1739 &handle_x, &handle_y, TRUE);
1740 // compute offsets
1741 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
1742 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
1743
1744 return 1;
1745 }
1746 }
1747 else if(mask_gui->handle_border_hovered >= 0)
1748 {
1749 float handle_x = NAN, handle_y = NAN;
1750 if(_brush_get_border_handle_mirrored(gui_points, node_count, mask_gui->handle_border_hovered,
1751 &handle_x, &handle_y))
1752 {
1753 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
1754 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
1755 }
1756
1757 return 1;
1758 }
1759 else if(mask_gui->seg_hovered >= 0)
1760 {
1761 mask_gui->node_hovered = -1;
1762
1763 if(dt_modifier_is(state, GDK_CONTROL_MASK))
1764 {
1765 _add_node_to_segment(module, mask_form, parentid, mask_gui, index);
1766 }
1767 else
1768 {
1769 // we move the entire segment
1770 mask_gui->delta[0] = gui_points->points[mask_gui->seg_hovered * 6 + 2] - mask_gui->pos[0];
1771 mask_gui->delta[1] = gui_points->points[mask_gui->seg_hovered * 6 + 3] - mask_gui->pos[1];
1772 }
1773 return 1;
1774 }
1775 else if(mask_gui->source_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
1776 {
1777 // we start the source dragging
1778 mask_gui->delta[0] = gui_points->source[2] - mask_gui->pos[0];
1779 mask_gui->delta[1] = gui_points->source[3] - mask_gui->pos[1];
1780 return 1;
1781 }
1782 else if(mask_gui->form_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
1783 {
1784 // we start the form dragging
1785 mask_gui->delta[0] = gui_points->points[2] - mask_gui->pos[0];
1786 mask_gui->delta[1] = gui_points->points[3] - mask_gui->pos[1];
1787 return 1;
1788 }
1789 }
1790
1791 return 0;
1792}
1793
1795{
1796 float factor = 0.01f;
1797 const char *smoothing = dt_conf_get_string_const("brush_smoothing");
1798 if(!strcmp(smoothing, "low"))
1799 factor = 0.0025f;
1800 else if(!strcmp(smoothing, "medium"))
1801 factor = 0.01f;
1802 else if(!strcmp(smoothing, "high"))
1803 factor = 0.04f;
1804 return factor;
1805}
1806
1807static void _apply_pen_pressure(dt_masks_form_gui_t *mask_gui, float *payload_buffer)
1808{
1809 for(int i = 0; i < mask_gui->guipoints_count; i++)
1810 {
1811 float *payload = payload_buffer + 4 * i;
1812 float pressure = payload[3];
1813 payload[3] = 1.0f;
1814
1815 switch(mask_gui->pressure_sensitivity)
1816 {
1818 payload[0] = MAX(HARDNESS_MIN, payload[0] * pressure);
1819 break;
1821 payload[1] = MAX(HARDNESS_MIN, pressure);
1822 break;
1824 payload[1] = MAX(HARDNESS_MIN, payload[1] * pressure);
1825 break;
1827 payload[2] = MAX(0.05f, pressure);
1828 break;
1830 payload[2] = MAX(0.05f, payload[2] * pressure);
1831 break;
1832 default:
1834 // ignore pressure value
1835 break;
1836 }
1837 }
1838}
1839
1840
1841static int _brush_events_button_released(struct dt_iop_module_t *module, double widget_x, double widget_y,
1842 int which, uint32_t state, dt_masks_form_t *mask_form, int parentid,
1843 dt_masks_form_gui_t *mask_gui, int index)
1844{
1845
1846
1847 if(IS_NULL_PTR(mask_gui)) return 0;
1848 dt_masks_form_gui_points_t *gui_points
1849 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
1850 if(IS_NULL_PTR(gui_points)) return 0;
1851
1852 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
1853 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
1855
1856 if(mask_gui->creation && which == 1)
1857 {
1858 if(dt_modifier_is(state, GDK_SHIFT_MASK) || dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK))
1859 {
1860 // user just set the source position, so just return
1861 return 1;
1862 }
1863
1864 dt_iop_module_t *creation_module = mask_gui->creation_module;
1865
1866 if(mask_gui->guipoints && mask_gui->guipoints_count > 0)
1867 {
1868 // if the path consists only of one x/y pair we add a second one close so we don't need to deal with
1869 // this special case later
1870 if(mask_gui->guipoints_count == 1)
1871 {
1872 // add a helper node very close to the single spot
1873 const float x = dt_masks_dynbuf_get(mask_gui->guipoints, -2) + 0.01f;
1874 const float y = dt_masks_dynbuf_get(mask_gui->guipoints, -1) - 0.01f;
1875 dt_masks_dynbuf_add_2(mask_gui->guipoints, x, y);
1876 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
1877 const float hardness = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
1878 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
1879 const float pressure = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -1);
1881 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
1882 mask_gui->guipoints_count++;
1883 }
1884
1885 float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
1886 float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
1887
1888 // we transform the points
1890
1891 // we consolidate pen pressure readings into payload
1892 _apply_pen_pressure(mask_gui, guipoints_payload);
1893
1894 // accuracy level for node elimination, dependent on brush size
1895 const float epsilon2 = _get_brush_smoothing() * sqf(MAX(HARDNESS_MIN, masks_border));
1896
1897 // we simplify the path and generate the nodes
1898 mask_form->points = _brush_ramer_douglas_peucker(guipoints, mask_gui->guipoints_count,
1899 guipoints_payload, epsilon2);
1900
1901 // printf("guipoints_count %d, points %d\n", mask_gui->guipoints_count, g_list_length(mask_form->points));
1902
1903 _brush_init_ctrl_points(mask_form);
1904
1907 mask_gui->guipoints = NULL;
1908 mask_gui->guipoints_payload = NULL;
1909 mask_gui->guipoints_count = 0;
1910
1911 dt_masks_gui_form_save_creation(darktable.develop, creation_module, mask_form, mask_gui);
1912
1913 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
1914 {
1916 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return 1;
1917 int group_index = 0;
1918 int selected_index = -1;
1919 for(GList *group_entry = grp->points; group_entry; group_entry = g_list_next(group_entry))
1920 {
1921 dt_masks_form_group_t *group_form = (dt_masks_form_group_t *)group_entry->data;
1922 if(group_form->formid == mask_form->formid)
1923 {
1924 selected_index = group_index;
1925 break;
1926 }
1927 group_index++;
1928 }
1929 if(selected_index < 0) return 1;
1931 if(IS_NULL_PTR(visible_gui)) return 1;
1932 visible_gui->group_selected = selected_index;
1933
1934 dt_masks_select_form(creation_module, dt_masks_get_from_id(darktable.develop, mask_form->formid));
1935 }
1936 }
1937 else
1938 {
1939 // unlikely case of button released but no points gathered -> no form
1942 mask_gui->guipoints = NULL;
1943 mask_gui->guipoints_payload = NULL;
1944 mask_gui->guipoints_count = 0;
1945
1947 dt_masks_iop_update(module);
1948
1950 }
1951 return 1;
1952 }
1953
1954 else if(which == 1)
1955 {
1956 if(dt_masks_gui_is_dragging(mask_gui))
1957 return 1;
1958 }
1959 return 0;
1960}
1961
1962static int _brush_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event,
1963 dt_masks_form_t *mask_form, int parentid,
1964 dt_masks_form_gui_t *mask_gui, int index)
1965{
1966 return 0;
1967}
1968
1977static int _brush_events_mouse_moved(struct dt_iop_module_t *module, double widget_x, double widget_y,
1978 double pressure, int which, dt_masks_form_t *mask_form, int parentid,
1979 dt_masks_form_gui_t *mask_gui, int index)
1980{
1982 const int iwidth = darktable.develop->roi.raw_width;
1983 const int iheight = darktable.develop->roi.raw_height;
1984
1985 if(mask_gui->creation)
1986 {
1987 if(mask_gui->guipoints)
1988 {
1989 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
1990 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
1991 const float hardness = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
1992 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
1994 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
1995 mask_gui->guipoints_count++;
1996 return 1;
1997 }
1998
1999 // Let the cursor motion be redrawn as it moves in GUI
2000 return 1;
2001 }
2002
2003 if(IS_NULL_PTR(mask_form->points)) return 0;
2004 dt_masks_form_gui_points_t *gui_points
2005 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2006 if(IS_NULL_PTR(gui_points)) return 0;
2007 const guint node_count = g_list_length(mask_form->points);
2008
2009 if(mask_gui->node_dragging >= 0)
2010 {
2011 dt_masks_node_brush_t *dragged_node
2012 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_dragging);
2013 if(IS_NULL_PTR(dragged_node)) return 0;
2014
2015 float delta_x = 0.0f;
2016 float delta_y = 0.0f;
2017 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragged_node->node, &delta_x, &delta_y);
2018 _brush_translate_node(dragged_node, delta_x, delta_y);
2019
2020 // if first point, adjust the source position accordingly
2021 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->node_dragging == 0)
2022 dt_masks_translate_source(mask_form, delta_x, delta_y);
2023
2024 // we recreate the form points
2025 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2026 return 1;
2027 }
2028 else if(mask_gui->seg_dragging >= 0)
2029 {
2030 const GList *segment_start = g_list_nth(mask_form->points, mask_gui->seg_dragging);
2031 const GList *segment_end = g_list_next_wraparound(segment_start, mask_form->points);
2032 dt_masks_node_brush_t *segment_node = (dt_masks_node_brush_t *)segment_start->data;
2033 dt_masks_node_brush_t *segment_node_next = (dt_masks_node_brush_t *)segment_end->data;
2034 if(!segment_node || !segment_node_next) return 0;
2035
2036 float delta_x = 0.0f;
2037 float delta_y = 0.0f;
2038 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, segment_node->node, &delta_x, &delta_y);
2039 _brush_translate_node(segment_node, delta_x, delta_y);
2040 _brush_translate_node(segment_node_next, delta_x, delta_y);
2041
2042 // if first point's segment, adjust the source position accordingly
2043 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->seg_dragging == 0)
2044 dt_masks_translate_source(mask_form, delta_x, delta_y);
2045
2046 // we recreate the form points
2047 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2048 return 1;
2049 }
2050 else if(mask_gui->handle_dragging >= 0)
2051 {
2053 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->handle_dragging);
2054 if(IS_NULL_PTR(node)) return 0;
2055
2056 float cursor_pos[2];
2057 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2058
2059 // compute ctrl points directly from new handle position
2060 float control_points[4];
2061 _brush_handle_to_ctrl(gui_points->points[mask_gui->handle_dragging * 6 + 2],
2062 gui_points->points[mask_gui->handle_dragging * 6 + 3], cursor_pos[0],
2063 cursor_pos[1], &control_points[0], &control_points[1], &control_points[2],
2064 &control_points[3], TRUE);
2065
2067
2068 // set new ctrl points
2069 dt_masks_set_ctrl_points(node->ctrl1, node->ctrl2, control_points);
2071
2072 _brush_init_ctrl_points(mask_form);
2073 // we recreate the form points
2074 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2075 return 1;
2076 }
2077 else if(mask_gui->handle_border_dragging >= 0)
2078 {
2079 const int node_index = mask_gui->handle_border_dragging;
2081 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, node_index);
2082 if(IS_NULL_PTR(node)) return 0;
2083
2084 float handle_x = NAN, handle_y = NAN;
2085 if(!_brush_get_border_handle_mirrored(gui_points, node_count, node_index, &handle_x, &handle_y))
2086 return 0;
2087
2088 const float node_px = gui_points->points[node_index * 6 + 2];
2089 const float node_py = gui_points->points[node_index * 6 + 3];
2090
2091 float pts[2];
2092 float cursor_pos[2];
2093 const float node_pos_gui[2] = { node_px, node_py };
2094 const float handle_pos[2] = { handle_x, handle_y };
2095 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2096 dt_masks_project_on_line(cursor_pos, node_pos_gui, handle_pos, pts);
2097
2098 const float border = dt_masks_border_from_projected_handle(dev, node->node, pts, fminf(iwidth, iheight));
2099
2100 node->border[0] = node->border[1] = border;
2101 // we recreate the form points
2102 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2103 return 1;
2104 }
2105 else if(mask_gui->form_dragging || mask_gui->source_dragging)
2106 {
2107 dt_masks_node_brush_t *dragging_shape = (dt_masks_node_brush_t *)(mask_form->points)->data;
2108 if(IS_NULL_PTR(dragging_shape)) return 0;
2109
2110 if(mask_gui->form_dragging)
2111 {
2112 float delta_x = 0.0f;
2113 float delta_y = 0.0f;
2114 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragging_shape->node, &delta_x, &delta_y);
2115 _brush_translate_all_nodes(mask_form, delta_x, delta_y);
2116 }
2117 else
2118 {
2119 float raw_pos[2];
2121 mask_form->source[0] = raw_pos[0];
2122 mask_form->source[1] = raw_pos[1];
2123 }
2124
2125 // we recreate the form points
2126 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
2127 return 1;
2128 }
2129 return 0;
2130}
2131
2132static void _brush_draw_shape(cairo_t *cr, const float *points, const int points_count, const int node_nb, const gboolean border, const gboolean source)
2133{
2134 // Find the first valid non-NaN point to start drawing
2135 // FIXME: Why not just avoid having NaN points in the array?
2136 int start_idx = -1;
2137 for(int i = node_nb * 3 + border; i < points_count; i++)
2138 {
2139 if(!isnan(points[i * 2]) && !isnan(points[i * 2 + 1]))
2140 {
2141 start_idx = i;
2142 break;
2143 }
2144 }
2145
2146 // Only draw if we have at least one valid point
2147 if(start_idx >= 0)
2148 {
2149 cairo_move_to(cr, points[start_idx * 2], points[start_idx * 2 + 1]);
2150
2151 // We don't want to draw the plain line twice, adapt the end index accordingly
2152 const int end_idx = border ? points_count : 0.5 * points_count;
2153
2154 for(int i = start_idx + 1; i < end_idx; i++)
2155 {
2156 if(!isnan(points[i * 2]) && !isnan(points[i * 2 + 1]))
2157 cairo_line_to(cr, points[i * 2], points[i * 2 + 1]);
2158 }
2159 }
2160}
2161
2162static float _brush_line_length(const float *line, const int first_pt, const int last_pt)
2163{
2164 float total_len = 0.0f;
2165 for(int i = first_pt; i < last_pt; i++)
2166 {
2167 const int i0 = i * 2;
2168 const int i1 = (i + 1) * 2;
2169 const float x0 = line[i0];
2170 const float y0 = line[i0 + 1];
2171 const float x1 = line[i1];
2172 const float y1 = line[i1 + 1];
2173 if(isnan(x0) || isnan(y0) || isnan(x1) || isnan(y1)) continue;
2174 const float dx = x1 - x0;
2175 const float dy = y1 - y0;
2176 const float len = dx * dx + dy * dy;
2177 if(len > 1e-12f) total_len += sqrtf(len);
2178 }
2179 return total_len;
2180}
2181
2182static gboolean _brush_line_point_at_length(const float *line, const int first_pt, const int last_pt,
2183 const float target_len, float *x, float *y)
2184{
2185 if(IS_NULL_PTR(line) || IS_NULL_PTR(x) || IS_NULL_PTR(y)) return FALSE;
2186 if(last_pt <= first_pt) return FALSE;
2187
2188 float acc = 0.0f;
2189 gboolean has_fallback = FALSE;
2190 float fallback_x = NAN, fallback_y = NAN;
2191
2192 for(int i = first_pt; i < last_pt; i++)
2193 {
2194 const int i0 = i * 2;
2195 const int i1 = (i + 1) * 2;
2196 const float x0 = line[i0];
2197 const float y0 = line[i0 + 1];
2198 const float x1 = line[i1];
2199 const float y1 = line[i1 + 1];
2200 if(isnan(x0) || isnan(y0) || isnan(x1) || isnan(y1)) continue;
2201
2202 const float dx = x1 - x0;
2203 const float dy = y1 - y0;
2204 const float len = sqrtf(dx * dx + dy * dy);
2205 if(len <= 1e-6f) continue;
2206
2207 has_fallback = TRUE;
2208 fallback_x = x1;
2209 fallback_y = y1;
2210
2211 if(acc + len >= target_len)
2212 {
2213 const float t = (target_len - acc) / len;
2214 *x = x0 + t * dx;
2215 *y = y0 + t * dy;
2216 return TRUE;
2217 }
2218 acc += len;
2219 }
2220
2221 if(!has_fallback || isnan(fallback_x) || isnan(fallback_y)) return FALSE;
2222 *x = fallback_x;
2223 *y = fallback_y;
2224 return TRUE;
2225}
2226
2227static gboolean _brush_get_line_midpoint(const float *line, const int first_pt, const int last_pt,
2228 float *mx, float *my)
2229{
2230 if(IS_NULL_PTR(line) || IS_NULL_PTR(mx) || IS_NULL_PTR(my)) return FALSE;
2231 const float total_len = _brush_line_length(line, first_pt, last_pt);
2232 if(total_len <= 1e-6f) return FALSE;
2233
2234 const float half_len = 0.5f * total_len;
2235 return _brush_line_point_at_length(line, first_pt, last_pt, half_len, mx, my);
2236}
2237
2238static gboolean _brush_get_source_center(const dt_masks_form_gui_points_t *gui_points, const int node_count,
2239 dt_masks_gui_center_point_t *center_pt)
2240{
2241 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(center_pt)) return FALSE;
2242
2243 // Work on the exact centerline span that is actually drawn (non-border path):
2244 // [node_count * 3, 0.5 * points_count)
2245 const int line_offset_pt = node_count * 3;
2246 const int points_line_end = gui_points->points_count / 2; // exclusive
2247 const int source_line_end = gui_points->source_count / 2; // exclusive
2248 const int line_end = MIN(points_line_end, source_line_end);
2249 const int line_count = line_end - line_offset_pt;
2250 if(line_count < 2) return FALSE;
2251
2252 const float *const points_line = gui_points->points + 2 * line_offset_pt;
2253 const float *const source_line = gui_points->source + 2 * line_offset_pt;
2254 const int first_pt = 0;
2255 const int last_pt = line_count - 1;
2256
2257 if(!_brush_get_line_midpoint(points_line, first_pt, last_pt,
2258 &center_pt->main.x, &center_pt->main.y))
2259 return FALSE;
2260 if(!_brush_get_line_midpoint(source_line, first_pt, last_pt,
2261 &center_pt->source.x, &center_pt->source.y))
2262 return FALSE;
2263 return TRUE;
2264}
2265
2266static void _brush_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *mask_gui, int index,
2267 int node_count)
2268{
2269 // in creation mode
2270 if(mask_gui->creation)
2271 {
2272 const float iwd = darktable.develop->roi.raw_width;
2273 const float iht = darktable.develop->roi.raw_height;
2274 const float min_iwd_iht = MIN(iwd, iht);
2275
2276 if(mask_gui->guipoints_count == 0)
2277 {
2279 if(IS_NULL_PTR(mask_form)) return;
2280
2281 const float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f, BORDER_MIN, BORDER_MAX,
2283 const float masks_hardness = dt_masks_get_set_conf_value(mask_form, "hardness", 1.0f, HARDNESS_MIN,
2285 const float opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
2286
2287 const float radius1 = masks_border * masks_hardness * min_iwd_iht;
2288 const float radius2 = masks_border * min_iwd_iht;
2289
2290 float xpos = mask_gui->pos[0];
2291 float ypos = mask_gui->pos[1];
2292 if((xpos == -1.0f && ypos == -1.0f))
2293 {
2294 xpos = 0.f;
2295 ypos = 0.f;
2296 }
2297
2298 // draw brush circle at current mouse position
2299 cairo_save(cr);
2301 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2302 cairo_new_path(cr);
2303 cairo_arc(cr, xpos, ypos, radius1, 0, 2.0 * M_PI);
2304 cairo_fill_preserve(cr);
2305 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2306 cairo_stroke(cr);
2307 cairo_new_path(cr);
2308 cairo_arc(cr, xpos, ypos, radius2, 0, 2.0 * M_PI);
2309 dt_draw_stroke_line(DT_MASKS_DASH_STICK, FALSE, cr, FALSE, zoom_scale, CAIRO_LINE_CAP_ROUND);
2310
2311 if(mask_form->type & DT_MASKS_CLONE)
2312 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, xpos, ypos, xpos, ypos, FALSE);
2313
2314 cairo_restore(cr);
2315 }
2316 else
2317 {
2318 float masks_border = 0.0f, masks_hardness = 0.0f, masks_density = 0.0f;
2319 float radius = 0.0f, oldradius = 0.0f, opacity = 0.0f, oldopacity = 0.0f, pressure = 0.0f;
2320 int stroked = 1;
2321
2322 const float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
2323 const float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
2324
2325 cairo_save(cr);
2326 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
2327 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
2328 masks_border = guipoints_payload[0];
2329 masks_hardness = guipoints_payload[1];
2330 masks_density = guipoints_payload[2];
2331 pressure = guipoints_payload[3];
2332
2333 switch(mask_gui->pressure_sensitivity)
2334 {
2336 masks_hardness = MAX(HARDNESS_MIN, pressure);
2337 break;
2339 masks_hardness = MAX(HARDNESS_MIN, masks_hardness * pressure);
2340 break;
2342 masks_density = MAX(0.05f, pressure);
2343 break;
2345 masks_density = MAX(0.05f, masks_density * pressure);
2346 break;
2348 masks_border = MAX(HARDNESS_MIN, masks_border * pressure);
2349 break;
2350 default:
2352 // ignore pressure value
2353 break;
2354 }
2355
2356 radius = oldradius = masks_border * masks_hardness * min_iwd_iht;
2357 opacity = oldopacity = masks_density;
2358
2359 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2361
2362 cairo_move_to(cr, guipoints[0], guipoints[1]);
2363 for(int i = 1; i < mask_gui->guipoints_count; i++)
2364 {
2365 cairo_line_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2366 stroked = 0;
2367 masks_border = guipoints_payload[i * 4];
2368 masks_hardness = guipoints_payload[i * 4 + 1];
2369 masks_density = guipoints_payload[i * 4 + 2];
2370 pressure = guipoints_payload[i * 4 + 3];
2371
2372 switch(mask_gui->pressure_sensitivity)
2373 {
2375 masks_hardness = MAX(HARDNESS_MIN, pressure);
2376 break;
2378 masks_hardness = MAX(HARDNESS_MIN, masks_hardness * pressure);
2379 break;
2381 masks_density = MAX(0.05f, pressure);
2382 break;
2384 masks_density = MAX(0.05f, masks_density * pressure);
2385 break;
2387 masks_border = MAX(HARDNESS_MIN, masks_border * pressure);
2388 break;
2389 default:
2391 // ignore pressure value
2392 break;
2393 }
2394
2395 radius = masks_border * masks_hardness * min_iwd_iht;
2396 opacity = masks_density;
2397
2398 if(radius != oldradius || opacity != oldopacity)
2399 {
2400 cairo_stroke(cr);
2401 stroked = 1;
2402 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2404 oldradius = radius;
2405 oldopacity = opacity;
2406 cairo_move_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2407 }
2408 }
2409 if(!stroked) cairo_stroke(cr);
2410
2411 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2413 cairo_new_path(cr);
2414 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2415 guipoints[2 * (mask_gui->guipoints_count - 1) + 1],
2416 radius, 0, 2.0 * M_PI);
2417 cairo_fill_preserve(cr);
2418 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2419 cairo_stroke(cr);
2421 cairo_new_path(cr);
2422 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2423 guipoints[2 * (mask_gui->guipoints_count - 1) + 1], masks_border * min_iwd_iht, 0,
2424 2.0 * M_PI);
2425 cairo_stroke(cr);
2426
2428 if(visible_form && (visible_form->type & DT_MASKS_CLONE))
2429 {
2430 const int i = mask_gui->guipoints_count - 1;
2431 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, guipoints[0], guipoints[1],
2432 guipoints[i * 2], guipoints[i * 2 + 1], TRUE);
2433 }
2434
2435 cairo_restore(cr);
2436 }
2437 return;
2438 } // creation
2439
2440 dt_masks_form_gui_points_t *gui_points
2441 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2442 if(IS_NULL_PTR(gui_points)) return;
2443 if(IS_NULL_PTR(gui_points->points)) return;
2444
2445 const int selected_node = dt_masks_gui_selected_node_index(mask_gui);
2446 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
2447 const int selected_handle_border = dt_masks_gui_selected_handle_border_index(mask_gui);
2448 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
2449
2450 // minimum points
2451 if(gui_points->points_count <= node_count * 3 + 2) return;
2452
2453 // draw path
2454 if(node_count > 0 && gui_points->points_count > node_count * 3 + 6) // there must be something to draw
2455 {
2456 dt_masks_draw_path_seg_by_seg(cr, mask_gui, index, gui_points->points, 0.5f * gui_points->points_count,
2457 node_count, zoom_scale);
2458 }
2459
2460 if(0)
2461 {
2462 const int total_points = gui_points->points_count * 2;
2463 int seg1 = 1;
2464 int current_seg = 0;
2465
2466 /* Draw the line point-by-point up to the next node, then stroke it; repeat in a loop. */
2467 cairo_move_to(cr, gui_points->points[node_count * 6], gui_points->points[node_count * 6 + 1]);
2468
2469 const int end_idx = 0.5 * gui_points->points_count;
2470
2471 for(int i = node_count * 3; i < end_idx; i++)
2472 {
2473 const double x = gui_points->points[i * 2];
2474 const double y = gui_points->points[i * 2 + 1];
2475 cairo_line_to(cr, x, y);
2476
2477 int seg_idx = seg1 * 6;
2478 if((seg_idx + 3) < total_points)
2479 {
2480 const double segment_x = gui_points->points[seg_idx + 2];
2481 const double segment_y = gui_points->points[seg_idx + 3];
2482
2483 /* Is this point the next node? */
2484 if(x == segment_x && y == segment_y)
2485 {
2486 const gboolean seg_selected = (mask_gui->group_selected == index)
2487 && (selected_segment == current_seg);
2488 const gboolean all_selected = (mask_gui->group_selected == index)
2489 && !mask_gui->node_selected
2490 && !mask_gui->handle_selected
2491 && !mask_gui->handle_border_selected
2492 && !mask_gui->seg_selected
2493 && (mask_gui->form_selected || mask_gui->form_dragging);
2494 // creation mode: draw the current segment as round dotted line
2495 if(mask_gui->creation && current_seg == node_count - 2)
2496 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
2497 else
2498 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, (seg_selected || all_selected), zoom_scale, CAIRO_LINE_CAP_BUTT);
2499 seg1 = (seg1 + 1) % node_count;
2500 current_seg++;
2501 }
2502 }
2503 }
2504 }
2505
2506 // draw nodes and attached stuff
2507 if(mask_gui->group_selected == index)
2508 {
2509 // draw borders
2510 if(gui_points->border && gui_points->border_count > node_count * 3 + 2)
2511 {
2512 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, node_count, (mask_gui->border_selected), zoom_scale,
2513 gui_points->border, gui_points->border_count, &dt_masks_functions_brush.draw_shape,
2514 CAIRO_LINE_CAP_ROUND);
2515 }
2516
2517 // draw the current node's handle if it's a curve node
2518 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count
2519 && !dt_masks_node_is_cusp(gui_points, selected_node))
2520 {
2521 const int n = selected_node;
2522 float handle[2];
2523 _brush_ctrl2_to_handle(gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3],
2524 gui_points->points[n * 6 + 4], gui_points->points[n * 6 + 5], &handle[0], &handle[1],
2525 TRUE);
2526 const float pt[2] = { gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3] };
2527 const gboolean selected = (mask_gui->node_hovered == n || selected_handle == n || (mask_gui->handle_hovered == n));
2528 dt_draw_handle(cr, pt, zoom_scale, handle, selected, FALSE);
2529 }
2530
2531 // draw all nodes
2532 for(int k = 0; k < node_count; k++)
2533 {
2534 const gboolean corner = dt_masks_node_is_cusp(gui_points, k);
2535 const float x = gui_points->points[k * 6 + 2];
2536 const float y = gui_points->points[k * 6 + 3];
2537 const gboolean selected = (k == mask_gui->node_hovered || k == mask_gui->node_dragging);
2538 const gboolean action = (k == selected_node);
2539
2540 dt_draw_node(cr, corner, action, selected, zoom_scale, x, y);
2541 }
2542
2543 // Draw the current node's border handle, if needed
2544 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count)
2545 {
2546 const int edited = selected_node;
2547 const gboolean selected = (mask_gui->node_hovered == edited
2548 || selected_handle_border == edited
2549 || mask_gui->handle_border_hovered == edited);
2550 float handle[2] = {NAN, NAN};
2551 // Show the border handle on the opposite side from the curve handle
2552 if(_brush_get_border_handle_mirrored(gui_points, node_count, edited, &handle[0], &handle[1]))
2553 {
2554 dt_draw_handle(cr, NULL, zoom_scale, handle, selected, TRUE);
2555 }
2556 }
2557 }
2558
2559 // Draw the source if needed
2560 if(gui_points->source && gui_points->source_count > node_count * 3 + 2)
2561 {
2563 if(_brush_get_source_center(gui_points, node_count, &center_pt))
2564 dt_masks_draw_source(cr, mask_gui, index, node_count, zoom_scale, &center_pt,
2566
2567 //draw the current node projection
2568 for(int k = 0; k < node_count; k++)
2569 {
2570 if(mask_gui->group_selected == index
2571 && (k == mask_gui->node_hovered || k == selected_node
2572 || (mask_gui->creation && k == node_count - 1)))
2573 {
2574 const int node_index = k * 6 + 2;
2575 const float proj[2] = { gui_points->source[node_index], gui_points->source[node_index + 1] };
2576 const gboolean selected = mask_gui->node_hovered == k;
2577 const gboolean squared = dt_masks_node_is_cusp(gui_points, k);
2578
2579 dt_draw_handle(cr, NULL, zoom_scale, proj, selected, squared);
2580 }
2581 }
2582 }
2583}
2584
2591static void _brush_bounding_box_raw(const float *const restrict points, const float *const restrict border,
2592 const int node_count, const int total_points, float *x_min, float *x_max,
2593 float *y_min, float *y_max)
2594{
2595 // now we want to find the area, so we search min/max points
2596 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2597 __OMP_PARALLEL_FOR__(reduction(min : min_x, min_y) reduction(max : max_x, max_y) if(total_points > 1000))
2598 // Skip control points and only consider actual polyline samples.
2599 for(int point_index = node_count * 3; point_index < total_points; point_index++)
2600 {
2601 // we look at the borders
2602 const float border_x = border[point_index * 2];
2603 const float border_y = border[point_index * 2 + 1];
2604 min_x = MIN(border_x, min_x);
2605 max_x = MAX(border_x, max_x);
2606 min_y = MIN(border_y, min_y);
2607 max_y = MAX(border_y, max_y);
2608 // we look at the brush too
2609 const float brush_x = points[point_index * 2];
2610 const float brush_y = points[point_index * 2 + 1];
2611 min_x = MIN(brush_x, min_x);
2612 max_x = MAX(brush_x, max_x);
2613 min_y = MIN(brush_y, min_y);
2614 max_y = MAX(brush_y, max_y);
2615 }
2616 *x_min = min_x;
2617 *x_max = max_x;
2618 *y_min = min_y;
2619 *y_max = max_y;
2620}
2621
2627static void _brush_bounding_box(const float *const points, const float *const border, const int node_count,
2628 const int total_points, int *width, int *height, int *offset_x, int *offset_y)
2629{
2630 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2631 _brush_bounding_box_raw(points, border, node_count, total_points, &min_x, &max_x, &min_y, &max_y);
2632 *height = max_y - min_y + 4;
2633 *width = max_x - min_x + 4;
2634 *offset_x = min_x - 2;
2635 *offset_y = min_y - 2;
2636}
2637
2643static int _get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2644 const dt_dev_pixelpipe_iop_t *const piece,
2645 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x, int *offset_y,
2646 int include_source)
2647{
2648 if(IS_NULL_PTR(module)) return 1;
2649 // we get buffers for all points
2650 float *points = NULL, *border = NULL;
2651 int points_count, border_count;
2652 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2653 &points, &points_count, &border, &border_count, NULL, NULL, include_source) != 0)
2654 {
2657 return 1;
2658 }
2659
2660 const guint node_count = g_list_length(mask_form->points);
2661 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || points_count <= (int)(node_count * 3) || border_count <= (int)(node_count * 3))
2662 {
2665 *width = *height = *offset_x = *offset_y = 0;
2666 return 0;
2667 }
2668 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
2669
2672 return 0;
2673}
2674
2677 dt_masks_form_t *mask_form, int *width, int *height, int *offset_x, int *offset_y)
2678{
2679 return _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 1);
2680}
2681
2682static int _brush_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2683 const dt_dev_pixelpipe_iop_t *const piece,
2684 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x,
2685 int *offset_y)
2686{
2687 return _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 0);
2688}
2689
2691static void _brush_falloff(float *const restrict buffer, int segment_start[2], int segment_end[2],
2692 int offset_x, int offset_y, int buffer_width, float hardness, float density)
2693{
2694 // segment length
2695 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
2696 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
2697 + 1;
2698 const int solid_length = (int)segment_length * hardness;
2699 const int soft_length = segment_length - solid_length;
2700
2701 const float segment_dx = segment_end[0] - segment_start[0];
2702 const float segment_dy = segment_end[1] - segment_start[1];
2703 const float inv_length = 1.0f / (float)segment_length;
2704 const float inv_soft = (soft_length > 0) ? 1.0f / (float)soft_length : 0.0f;
2705
2706 for(int step = 0; step < segment_length; step++)
2707 {
2708 // position
2709 const int x = (int)((float)step * segment_dx * inv_length) + segment_start[0] - offset_x;
2710 const int y = (int)((float)step * segment_dy * inv_length) + segment_start[1] - offset_y;
2711 const float opacity = density * ((step <= solid_length) ? 1.0f : 1.0f - (float)(step - solid_length) * inv_soft);
2712 const int buffer_index = y * buffer_width + x;
2713 buffer[buffer_index] = MAX(buffer[buffer_index], opacity);
2714 if(x > 0)
2715 buffer[buffer_index - 1] = MAX(buffer[buffer_index - 1], opacity); // avoid gaps from rounding
2716 if(y > 0)
2717 buffer[buffer_index - buffer_width] = MAX(buffer[buffer_index - buffer_width], opacity); // avoid gaps from rounding
2718 }
2719}
2720
2726static int _brush_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2727 const dt_dev_pixelpipe_iop_t *const piece,
2728 dt_masks_form_t *const mask_form,
2729 float **buffer, int *width, int *height, int *offset_x, int *offset_y)
2730{
2731 if(IS_NULL_PTR(module)) return 1;
2732 double timer_start = 0.0;
2733 double timer_step_start = 0.0;
2734 if(darktable.unmuted & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
2735
2736 // we get buffers for all points
2737 float *points = NULL, *border = NULL, *payload = NULL;
2738 int points_count, border_count, payload_count;
2739 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2740 &points, &points_count,
2741 &border, &border_count, &payload, &payload_count, 0) != 0)
2742 {
2746 return 1;
2747 }
2748
2750 {
2751 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
2752 dt_get_wtime() - timer_step_start);
2753 timer_step_start = dt_get_wtime();
2754 }
2755
2756 const guint node_count = g_list_length(mask_form->points);
2757 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
2758 || border_count <= (int)(node_count * 3))
2759 {
2763 *buffer = NULL;
2764 *width = *height = *offset_x = *offset_y = 0;
2765 return 0;
2766 }
2767 const gboolean use_sparse = (dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, NULL)
2768 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL);
2769 const int sparse_step = use_sparse ? 4 : 1;
2770 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
2771
2773 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
2774 dt_get_wtime() - timer_step_start);
2775
2776 // we allocate the buffer
2777 const size_t buffer_size = (size_t)(*width) * (*height);
2778 // ensure that the buffer is zeroed, as the below code only fills in pixels in the falloff region
2779 *buffer = dt_pixelpipe_cache_alloc_align_float_cache(buffer_size, 0);
2780 if(IS_NULL_PTR(*buffer))
2781 {
2785 return 1;
2786 }
2787 memset(*buffer, 0, sizeof(float) * buffer_size);
2788
2789 // now we fill the falloff
2790 int segment_start[2], segment_end[2];
2791 int prev_start[2] = { 0, 0 };
2792 int prev_end[2] = { 0, 0 };
2793 float prev_payload[2] = { 0.0f, 0.0f };
2794 gboolean have_prev = FALSE;
2795
2796 // Walk all border samples and stamp a falloff segment for each.
2797 for(int border_index = node_count * 3; border_index < border_count; border_index++)
2798 {
2799 segment_start[0] = points[border_index * 2];
2800 segment_start[1] = points[border_index * 2 + 1];
2801 segment_end[0] = border[border_index * 2];
2802 segment_end[1] = border[border_index * 2 + 1];
2803
2804 if(use_sparse && have_prev
2805 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
2806 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
2807 {
2808 // In sparse mode (preview/thumbnail), interpolate missing segments to reduce holes.
2809 for(int step = 1; step < sparse_step; step++)
2810 {
2811 const float t = (float)step / (float)sparse_step;
2812 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
2813 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
2814 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
2815 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
2816 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
2817 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
2818 _brush_falloff(*buffer, interp_start, interp_end, *offset_x, *offset_y, *width, hard, dens);
2819 }
2820 }
2821
2822 _brush_falloff(*buffer, segment_start, segment_end, *offset_x, *offset_y, *width,
2823 payload[border_index * 2], payload[border_index * 2 + 1]);
2824
2825 prev_start[0] = segment_start[0];
2826 prev_start[1] = segment_start[1];
2827 prev_end[0] = segment_end[0];
2828 prev_end[1] = segment_end[1];
2829 prev_payload[0] = payload[border_index * 2];
2830 prev_payload[1] = payload[border_index * 2 + 1];
2831 have_prev = TRUE;
2832 }
2833
2837
2839 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
2840 dt_get_wtime() - timer_start);
2841
2842 return 0;
2843}
2844
2846static inline void _brush_falloff_roi(float *buffer, const int *segment_start, const int *segment_end,
2847 int buffer_width, int buffer_height, float hardness, float density)
2848{
2849 // segment length (increase by 1 to avoid division-by-zero special case handling)
2850 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
2851 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
2852 + 1;
2853 const int solid_length = hardness * segment_length;
2854
2855 const float step_x = (float)(segment_end[0] - segment_start[0]) / (float)segment_length;
2856 const float step_y = (float)(segment_end[1] - segment_start[1]) / (float)segment_length;
2857
2858 const int direction_x = step_x <= 0 ? -1 : 1;
2859 const int direction_y = step_y <= 0 ? -1 : 1;
2860 const int neighbor_offset_x = direction_x;
2861 const int neighbor_offset_y = direction_y * buffer_width;
2862
2863 float cursor_x = segment_start[0];
2864 float cursor_y = segment_start[1];
2865
2866 float opacity = density;
2867 const float opacity_step = density / (float)(segment_length - solid_length);
2868
2869 const int start_x = segment_start[0], start_y = segment_start[1];
2870 const int end_x = segment_end[0], end_y = segment_end[1];
2871 if((start_x < 0 && end_x < 0) || (start_x >= buffer_width && end_x >= buffer_width)
2872 || (start_y < 0 && end_y < 0) || (start_y >= buffer_height && end_y >= buffer_height))
2873 return;
2874 const int fully_inside = (start_x >= 0 && start_x < buffer_width && end_x >= 0 && end_x < buffer_width
2875 && start_y >= 0 && start_y < buffer_height && end_y >= 0
2876 && end_y < buffer_height);
2877
2878 for(int step = 0; step < segment_length; step++)
2879 {
2880 const int x = cursor_x;
2881 const int y = cursor_y;
2882
2883 cursor_x += step_x;
2884 cursor_y += step_y;
2885 if(step > solid_length) opacity -= opacity_step;
2886
2887 if(!fully_inside && (x < 0 || x >= buffer_width || y < 0 || y >= buffer_height)) continue;
2888
2889 float *buf = buffer + (size_t)y * buffer_width + x;
2890
2891 *buf = MAX(*buf, opacity);
2892 if(x + direction_x >= 0 && x + direction_x < buffer_width)
2893 buf[neighbor_offset_x] = MAX(buf[neighbor_offset_x], opacity); // avoid gaps from rounding
2894 if(y + direction_y >= 0 && y + direction_y < buffer_height)
2895 buf[neighbor_offset_y] = MAX(buf[neighbor_offset_y], opacity); // avoid gaps from rounding
2896 }
2897}
2898
2899// build a stamp which can be combined with other shapes in the same group
2900// prerequisite: 'buffer' is all zeros
2906static int _brush_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2907 const dt_dev_pixelpipe_iop_t *const piece,
2908 dt_masks_form_t *const mask_form, const dt_iop_roi_t *roi, float *buffer)
2909{
2910 if(IS_NULL_PTR(module)) return 1;
2911 double timer_start = 0.0;
2912 double timer_step_start = 0.0;
2913 if(darktable.unmuted & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
2914
2915 const int roi_offset_x = roi->x;
2916 const int roi_offset_y = roi->y;
2917 const int roi_width = roi->width;
2918 const int roi_height = roi->height;
2919 const float roi_scale = roi->scale;
2920 const gboolean use_sparse = (dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, roi)
2921 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL);
2922 const int sparse_step = use_sparse ? 4 : 1;
2923
2924 // we get buffers for all points
2925 float *points = NULL, *border = NULL, *payload = NULL;
2926
2927 int points_count, border_count, payload_count;
2928
2929 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2930 &points, &points_count, &border, &border_count, &payload, &payload_count, 0) != 0)
2931 {
2935 return 1;
2936 }
2937
2939 {
2940 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
2941 dt_get_wtime() - timer_step_start);
2942 timer_step_start = dt_get_wtime();
2943 }
2944
2945 const guint node_count = g_list_length(mask_form->points);
2946 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
2947 || border_count <= (int)(node_count * 3))
2948 {
2952 return 0;
2953 }
2954
2955 // we shift and scale down brush and border
2956 // Shift/scale border samples into ROI space.
2957 for(int border_index = node_count * 3; border_index < border_count; border_index++)
2958 {
2959 const float border_x = border[2 * border_index];
2960 const float border_y = border[2 * border_index + 1];
2961 border[2 * border_index] = border_x * roi_scale - roi_offset_x;
2962 border[2 * border_index + 1] = border_y * roi_scale - roi_offset_y;
2963 }
2964
2965 // Shift/scale centerline samples into ROI space.
2966 for(int point_index = node_count * 3; point_index < points_count; point_index++)
2967 {
2968 const float point_x = points[2 * point_index];
2969 const float point_y = points[2 * point_index + 1];
2970 points[2 * point_index] = point_x * roi_scale - roi_offset_x;
2971 points[2 * point_index + 1] = point_y * roi_scale - roi_offset_y;
2972 }
2973
2974
2975 float min_x = 0.0f, max_x = 0.0f, min_y = 0.0f, max_y = 0.0f;
2976 _brush_bounding_box_raw(points, border, node_count, points_count, &min_x, &max_x, &min_y, &max_y);
2977
2979 {
2980 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
2981 dt_get_wtime() - timer_step_start);
2982 timer_step_start = dt_get_wtime();
2983 }
2984
2985 // check if the path completely lies outside of roi -> we're done/mask remains empty
2986 if(max_x < 0 || max_y < 0 || min_x >= roi_width || min_y >= roi_height)
2987 {
2991 return 0;
2992 }
2993
2994 // now we fill the falloff
2995 if(!use_sparse)
2996 {
2997 __OMP_PARALLEL_FOR__(if(border_count - node_count * 3 > 1000))
2998 // Stamp each border segment directly (full sampling).
2999 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3000 {
3001 const int segment_start[] = { points[border_index * 2], points[border_index * 2 + 1] };
3002 const int segment_end[] = { border[border_index * 2], border[border_index * 2 + 1] };
3003
3004 if(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3005 || MAX(segment_start[1], segment_end[1]) < 0
3006 || MIN(segment_start[1], segment_end[1]) >= roi_height)
3007 continue;
3008
3009 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height, payload[border_index * 2],
3010 payload[border_index * 2 + 1]);
3011 }
3012 }
3013 else
3014 {
3015 int prev_start[2] = { 0, 0 };
3016 int prev_end[2] = { 0, 0 };
3017 float prev_payload[2] = { 0.0f, 0.0f };
3018 gboolean have_prev = FALSE;
3019
3020 // Sparse mode: interpolate between consecutive segments to reduce artifacts.
3021 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3022 {
3023 int segment_start[2] = { points[border_index * 2], points[border_index * 2 + 1] };
3024 int segment_end[2] = { border[border_index * 2], border[border_index * 2 + 1] };
3025
3026 if(use_sparse && have_prev
3027 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
3028 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
3029 {
3030 for(int step = 1; step < sparse_step; step++)
3031 {
3032 const float t = (float)step / (float)sparse_step;
3033 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
3034 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
3035 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
3036 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
3037 if(!(MAX(interp_start[0], interp_end[0]) < 0 || MIN(interp_start[0], interp_end[0]) >= roi_width
3038 || MAX(interp_start[1], interp_end[1]) < 0 || MIN(interp_start[1], interp_end[1]) >= roi_height))
3039 {
3040 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
3041 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
3042 _brush_falloff_roi(buffer, interp_start, interp_end, roi_width, roi_height, hard, dens);
3043 }
3044 }
3045 }
3046
3047 if(!(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3048 || MAX(segment_start[1], segment_end[1]) < 0
3049 || MIN(segment_start[1], segment_end[1]) >= roi_height))
3050 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height,
3051 payload[border_index * 2], payload[border_index * 2 + 1]);
3052
3053 prev_start[0] = segment_start[0];
3054 prev_start[1] = segment_start[1];
3055 prev_end[0] = segment_end[0];
3056 prev_end[1] = segment_end[1];
3057 prev_payload[0] = payload[border_index * 2];
3058 prev_payload[1] = payload[border_index * 2 + 1];
3059 have_prev = TRUE;
3060 }
3061 }
3062
3066
3068 {
3069 dt_print(DT_DEBUG_MASKS, "[masks %s] brush set falloff took %0.04f sec\n", mask_form->name,
3070 dt_get_wtime() - timer_step_start);
3071 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
3072 dt_get_wtime() - timer_start);
3073 }
3074
3075 return 0;
3076}
3077
3079{
3080 // nothing to do (yet?)
3081}
3082
3083static void _brush_set_form_name(struct dt_masks_form_t *const mask_form, const size_t form_number)
3084{
3085 snprintf(mask_form->name, sizeof(mask_form->name), _("brush #%d"), (int)form_number);
3086}
3087
3088static void _brush_set_hint_message(const dt_masks_form_gui_t *const mask_gui,
3089 const dt_masks_form_t *const mask_form,
3090 const int opacity, char *const restrict msgbuf,
3091 const size_t msgbuf_len)
3092{
3093 if(mask_gui->creation || mask_gui->form_selected)
3094 g_snprintf(msgbuf, msgbuf_len,
3095 _("<b>Size</b>: scroll, <b>Hardness</b>: shift+scroll\n"
3096 "<b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
3097 else if(mask_gui->border_selected)
3098 g_strlcat(msgbuf, _("<b>Size</b>: scroll"), msgbuf_len);
3099}
3100
3101static void _brush_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
3102{
3103 // unused arg, keep compiler from complaining
3105}
3106
3107static void _brush_initial_source_pos(const float iwd, const float iht, float *x, float *y)
3108{
3109
3110
3111 float offset[2] = { 0.01f, 0.01f };
3113 *x = offset[0];
3114 *y = offset[1];
3115}
3116
3117static void _brush_switch_node_callback(GtkWidget *widget, gpointer user_data)
3118{
3119 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3120 if(IS_NULL_PTR(mask_gui)) return;
3121
3122 dt_iop_module_t *module = darktable.develop->gui_module;
3123 if(IS_NULL_PTR(module)) return;
3124
3125 mask_gui->node_selected = TRUE;
3126 mask_gui->node_selected_idx = mask_gui->node_hovered;
3127
3128 const int form_id = mask_gui->formid;
3129 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, form_id);
3130 if(IS_NULL_PTR(selected_form)) return;
3131 dt_masks_form_gui_points_t *gui_points
3132 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3133 const int node_index = dt_masks_gui_selected_node_index(mask_gui);
3135 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3136 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3137 dt_masks_toggle_bezier_node_type(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3138 node_index, node->node, node->ctrl1, node->ctrl2, &node->state);
3139}
3140
3141static void _brush_reset_round_node_callback(GtkWidget *widget, gpointer user_data)
3142{
3143 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3144 if(IS_NULL_PTR(mask_gui)) return;
3145
3146 dt_iop_module_t *module = darktable.develop->gui_module;
3147 if(IS_NULL_PTR(module)) return;
3148
3149 mask_gui->node_selected = TRUE;
3150 mask_gui->node_selected_idx = mask_gui->node_hovered;
3151
3152 const int form_id = mask_gui->formid;
3153 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, form_id);
3154 if(IS_NULL_PTR(selected_form)) return;
3155 dt_masks_form_gui_points_t *gui_points
3156 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3157 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
3158 const int node_index = MAX(mask_gui->node_hovered, selected_handle);
3160 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3161 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3162 dt_masks_reset_bezier_ctrl_points(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3163 node_index, &node->state);
3164}
3165
3166static void _brush_add_node_callback(GtkWidget *menu, gpointer user_data)
3167{
3168 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3169 if(IS_NULL_PTR(mask_gui)) return;
3171 if(IS_NULL_PTR(visible_forms)) return;
3172
3173 dt_iop_module_t *module = darktable.develop->gui_module;
3174 if(IS_NULL_PTR(module)) return;
3175
3176 dt_masks_form_group_t *group_entry = dt_masks_form_get_selected_group(visible_forms, mask_gui);
3177 if(IS_NULL_PTR(group_entry)) return;
3178 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3179 if(selected_form)
3180 {
3181 _add_node_to_segment(module, selected_form, group_entry->parentid, mask_gui, mask_gui->group_selected);
3182 }
3183
3185}
3186
3187static int _brush_populate_context_menu(GtkWidget *menu, struct dt_masks_form_t *mask_form,
3188 struct dt_masks_form_gui_t *mask_gui,
3189 const float pointer_x, const float pointer_y)
3190{
3191
3192
3193 GtkWidget *menu_item = NULL;
3194 gchar *accel = g_strdup_printf(_("%s+Click"), gtk_accelerator_get_label(0, GDK_CONTROL_MASK));
3195
3196 gboolean ret = FALSE;
3197
3198 if(mask_gui->node_hovered >= 0)
3199 {
3200 dt_masks_form_gui_points_t *gui_points
3201 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3202 if(IS_NULL_PTR(gui_points)) goto end;
3204 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
3205 if(IS_NULL_PTR(node)) goto end;
3206 const gboolean is_corner = dt_masks_node_is_cusp(gui_points, mask_gui->node_hovered);
3207
3208 {
3209 gchar *to_change_type = g_strdup_printf(_("Switch to %s node"), (is_corner) ? _("round") : _("cusp"));
3210 const dt_menu_icon_t icon = is_corner ? DT_MENU_ICON_CIRCLE : DT_MENU_ICON_SQUARE;
3211 menu_item = ctx_gtk_menu_item_new_with_icon_and_shortcut(to_change_type, accel, menu,
3212 _brush_switch_node_callback, mask_gui, icon);
3213 dt_free(to_change_type);
3214 }
3215
3216 {
3217 menu_item = ctx_gtk_menu_item_new_with_markup(_("Reset round node"), menu,
3219 gtk_widget_set_sensitive(menu_item, !is_corner && node->state != DT_MASKS_POINT_STATE_NORMAL);
3220 }
3221 ret = TRUE;
3222 }
3223
3224 if(mask_gui->seg_selected)
3225 {
3226 menu_item = ctx_gtk_menu_item_new_with_markup_and_shortcut(_("Add a node here"), accel,
3227 menu, _brush_add_node_callback, mask_gui);
3228 ret = TRUE;
3229 }
3230
3231 end:
3232 dt_free(accel);
3233 return ret;
3234}
3235
3236// The function table for brushes. This must be public, i.e. no "static" keyword.
3238 .point_struct_size = sizeof(struct dt_masks_node_brush_t),
3239 .sanitize_config = _brush_sanitize_config,
3240 .set_form_name = _brush_set_form_name,
3241 .set_hint_message = _brush_set_hint_message,
3242 .duplicate_points = _brush_duplicate_points,
3243 .initial_source_pos = _brush_initial_source_pos,
3244 .get_distance = _brush_get_distance,
3245 .get_points_border = _brush_get_points_border,
3246 .get_mask = _brush_get_mask,
3247 .get_mask_roi = _brush_get_mask_roi,
3248 .get_area = _brush_get_area,
3249 .get_source_area = _brush_get_source_area,
3250 .get_gravity_center = _brush_get_gravity_center,
3251 .get_interaction_value = _brush_get_interaction_value,
3252 .set_interaction_value = _brush_set_interaction_value,
3253 .update_hover = _find_closest_handle,
3254 .mouse_moved = _brush_events_mouse_moved,
3255 .mouse_scrolled = _brush_events_mouse_scrolled,
3256 .button_pressed = _brush_events_button_pressed,
3257 .button_released = _brush_events_button_released,
3258 .key_pressed = _brush_events_key_pressed,
3259 .post_expose = _brush_events_post_expose,
3260 .draw_shape = _brush_draw_shape,
3261 .init_ctrl_points = _brush_init_ctrl_points,
3262 .populate_context_menu = _brush_populate_context_menu
3263
3264};
3265
3266
3267// clang-format off
3268// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3269// vim: shiftwidth=2 expandtab tabstop=2 cindent
3270// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3271// clang-format on
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
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
void dt_masks_iop_update(dt_iop_module_t *module)
Definition blend_gui.c:3775
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static const float const float const float min
const float max
const float delta
int type
float dt_conf_get_float(const char *name)
const char * dt_conf_get_string_const(const char *name)
void dt_control_mouse_is_painting(gboolean state)
Definition control.c:428
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define dt_free_align(ptr)
Definition darktable.h:503
@ DT_DEBUG_PERF
Definition darktable.h:741
@ DT_DEBUG_MASKS
Definition darktable.h:749
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:516
static const GList * g_list_next_wraparound(const GList *list, const GList *head)
Definition darktable.h:1019
#define dt_free(ptr)
Definition darktable.h:478
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
static GList * g_list_next_bounded(GList *list)
Definition darktable.h:1014
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition darktable.h:1001
#define dt_dev_add_history_item(dev, module, enable, redraw)
static void _brush_initial_source_pos(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 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, int *inside_source, float *dist, void *user_data)
Brush-specific inside/border/segment hit testing.
static void _brush_points_recurs_border_gaps(float *cmax, float *bmin, float *bmin2, float *bmax, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, gboolean clockwise)
static int _brush_get_pts_border(dt_develop_t *develop, dt_masks_form_t *mask_form, const double iop_order, const int transform_direction, dt_dev_pixelpipe_t *pipe, float **point_buffer, int *point_count, float **border_buffer, int *border_count, float **payload_buffer, int *payload_count, int use_source)
static float _get_brush_smoothing()
static int _init_hardness(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 int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
static int _change_hardness(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_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)
#define HARDNESS_MIN
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 void _brush_points_stamp(float *cmax, float *bmin, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, gboolean clockwise)
static int _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, int use_source, const dt_iop_module_t *module)
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 void _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 *point_x, float *point_y, float *border_x, float *border_y)
Evaluate a cubic Bezier and compute its offset border point.
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 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 void _apply_pen_pressure(dt_masks_form_gui_t *mask_gui, float *payload_buffer)
static int _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_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_falloff(float *const restrict buffer, int segment_start[2], int segment_end[2], int offset_x, int offset_y, int buffer_width, float hardness, float density)
static gboolean _is_within_pxl_threshold(float *min, float *max, int pixel_threshold)
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 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 int _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)
Build a brush mask directly into an ROI-sized buffer.
static int _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.
#define HARDNESS_MAX
static float _brush_line_length(const float *line, const int first_pt, const int last_pt)
static void _brush_points_recurs_border_small_gaps(float *cmax, float *bmin, float *bmin2, float *bmax, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder)
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_falloff_roi(float *buffer, const int *segment_start, const int *segment_end, int buffer_width, int buffer_height, float hardness, float density)
static void _brush_draw_shape(cairo_t *cr, const float *points, const int points_count, const int node_nb, const gboolean border, const gboolean source)
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 int _brush_cyclic_cursor(int n, int nb)
static float _brush_get_position_in_segment(float point_x, float point_y, dt_masks_form_t *mask_form, int segment_index)
#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 int _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_set_form_name(struct dt_masks_form_t *const mask_form, const size_t form_number)
static void _brush_points_recurs(float *p1, float *p2, double tmin, double tmax, float *points_min, float *points_max, float *border_min, float *border_max, float *rpoints, float *rborder, float *rpayload, dt_masks_dynbuf_t *dpoints, dt_masks_dynbuf_t *dborder, dt_masks_dynbuf_t *dpayload, const int pixel_threshold)
#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)
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 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_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, int *inside_source, float *distance)
Get the distance between a point and the brush path/border.
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 void _brush_sanitize_config(dt_masks_type_t type)
static void _brush_set_hint_message(const dt_masks_form_gui_t *const mask_gui, const dt_masks_form_t *const mask_form, const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
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_get_gravity_center(const dt_masks_form_t *mask_form, float center[2], float *area)
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 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 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:1162
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1621
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:414
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1191
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:106
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:103
@ DT_DEV_TRANSFORM_DIR_ALL
Definition develop.h:102
#define DT_DRAW_SIZE_LINE
Definition draw.h:72
static void dt_draw_set_dash_style(cairo_t *cr, dt_draw_dash_type_t type, float zoom_scale)
Definition draw.h:549
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)
Stroke a line with style.
Definition draw.h:784
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:648
@ DT_MASKS_DASH_STICK
Definition draw.h:94
@ DT_MASKS_DASH_ROUND
Definition draw.h:95
@ DT_MASKS_NO_DASH
Definition draw.h:93
static void dt_draw_shape_lines(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap)
Draw the lines of a mask shape.
Definition draw.h:734
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:597
void dt_gui_gtk_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
Definition gtk.c:529
@ DT_GUI_COLOR_BRUSH_CURSOR
Definition gtk.h:134
@ DT_GUI_COLOR_BRUSH_TRACE
Definition gtk.h:135
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static const float x
const int t
float *const restrict const size_t k
static void dt_masks_gui_delta_to_image_abs(const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:632
static int dt_masks_gui_selected_segment_index(const dt_masks_form_gui_t *gui)
Definition masks.h:563
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks.h:548
static int dt_masks_gui_selected_handle_index(const dt_masks_form_gui_t *gui)
Definition masks.h:553
static float * dt_masks_dynbuf_buffer(dt_masks_dynbuf_t *a)
Definition masks.h:1367
static 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.h:696
void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module)
void dt_masks_change_form_gui(dt_masks_form_t *newform)
static void dt_masks_dynbuf_add_2(dt_masks_dynbuf_t *a, float value1, float value2)
Definition masks.h:1294
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
gboolean dt_masks_node_is_cusp(const dt_masks_form_gui_points_t *gpt, const int index)
returns wether a node is a corner or not. A node is a corner if its 2 control handles are at the same...
static void dt_masks_draw_source_preview(cairo_t *cr, const float zoom_scale, dt_masks_form_gui_t *gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, const int adding)
Definition masks.h:1141
int dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up, const int flow)
static dt_masks_dynbuf_t * dt_masks_dynbuf_init(size_t size, const char *tag)
Definition masks.h:1274
static void dt_masks_translate_source(dt_masks_form_t *form, const float delta_x, const float delta_y)
Definition masks.h:666
static float dt_masks_dynbuf_get(dt_masks_dynbuf_t *a, int offset)
Definition masks.h:1349
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:672
gboolean dt_masks_is_anything_selected(const dt_masks_form_gui_t *mask_gui)
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.
static float dt_masks_get_form_size_from_nodes(const GList *points)
Definition masks.h:577
gboolean dt_masks_gui_form_create_throttled(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module, float posx, float posy)
static float * dt_masks_dynbuf_reserve_n(dt_masks_dynbuf_t *a, const int n)
Definition masks.h:1309
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
void dt_masks_select_form(struct dt_iop_module_t *module, dt_masks_form_t *sel)
Select or clear the current mask form, notifying the owning module if needed.
static int dt_masks_gui_selected_handle_border_index(const dt_masks_form_gui_t *gui)
Definition masks.h:558
float dt_masks_get_set_conf_value(dt_masks_form_t *form, char *feature, float new_value, float v_min, float v_max, dt_masks_increment_t increment, const int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *gui, const int index, const int nb, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
dt_masks_type_t
Definition masks.h:130
@ DT_MASKS_NON_CLONE
Definition masks.h:139
@ DT_MASKS_CLONE
Definition masks.h:135
@ DT_MASKS_GROUP
Definition masks.h:134
dt_masks_interaction_t
Definition masks.h:303
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:306
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:305
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:617
dt_masks_form_group_t * dt_masks_form_get_selected_group(const struct dt_masks_form_t *form, const struct dt_masks_form_gui_t *gui)
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.h:771
static gboolean dt_masks_center_of_gravity_from_points(const float *points, const int points_count, float center[2], float *area)
Definition masks.h:1373
static size_t dt_masks_dynbuf_position(dt_masks_dynbuf_t *a)
Definition masks.h:1444
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
static gboolean dt_masks_gui_change_affects_selected_node_or_all(const dt_masks_form_gui_t *gui, const int index)
Definition masks.h:568
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.h:640
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *form, const char *feature, float amount, float v_min, float v_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
float dt_masks_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.
static void dt_masks_dynbuf_add_zeros(dt_masks_dynbuf_t *a, const int n)
Definition masks.h:1329
dt_masks_increment_t
Definition masks.h:194
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:196
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:197
@ DT_MASKS_PRESSURE_OPACITY_REL
Definition masks.h:212
@ DT_MASKS_PRESSURE_OPACITY_ABS
Definition masks.h:213
@ DT_MASKS_PRESSURE_BRUSHSIZE_REL
Definition masks.h:214
@ DT_MASKS_PRESSURE_HARDNESS_REL
Definition masks.h:210
@ DT_MASKS_PRESSURE_OFF
Definition masks.h:209
@ DT_MASKS_PRESSURE_HARDNESS_ABS
Definition masks.h:211
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
@ DT_MASKS_POINT_STATE_NORMAL
Definition masks.h:183
@ DT_MASKS_POINT_STATE_USER
Definition masks.h:184
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *gui, dt_masks_form_t *form)
Initialize the clone source position based on current GUI state.
static void dt_masks_project_on_line(const float cursor[2], const float node[2], const float handle[2], float point[2])
Definition masks.h:746
static float * dt_masks_dynbuf_harvest(dt_masks_dynbuf_t *a)
Definition masks.h:1456
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Shared selection logic for node/handle/segment hit testing.
dt_masks_form_t * dt_masks_get_visible_form(const struct dt_develop_t *dev)
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
static void dt_masks_dynbuf_set(dt_masks_dynbuf_t *a, int offset, float value)
Definition masks.h:1358
static 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.h:724
void dt_masks_draw_path_seg_by_seg(cairo_t *cr, dt_masks_form_gui_t *gui, const int index, const float *points, const int points_count, const int node_count, const float zoom_scale)
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:625
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state)
Decide initial source positioning mode for clone masks.
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
Definition masks.h:1466
static void dt_masks_set_ctrl_points(float ctrl1[2], float ctrl2[2], const float control_points[4])
Definition masks.h:683
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define M_PI
Definition math.h:45
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:129
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:172
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:187
dt_menu_icon_t
Definition menu.h:30
@ DT_MENU_ICON_CIRCLE
Definition menu.h:32
@ DT_MENU_ICON_SQUARE
Definition menu.h:33
size_t size
Definition mipmap_cache.c:3
const float factor
Definition pdf.h:90
@ DT_DEV_PIXELPIPE_THUMBNAIL
Definition pixelpipe.h:41
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
dt_dev_pixelpipe_type_t type
int32_t raw_height
Definition develop.h:228
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
struct dt_masks_form_gui_t * form_gui
Definition develop.h:333
struct dt_develop_t::@17 roi
struct dt_develop_t * dev
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
gboolean node_selected
Definition masks.h:484
gboolean handle_border_selected
Definition masks.h:487
dt_masks_dynbuf_t * guipoints_payload
Definition masks.h:449
gboolean source_selected
Definition masks.h:492
int handle_border_dragging
Definition masks.h:507
int handle_border_hovered
Definition masks.h:482
gboolean source_dragging
Definition masks.h:500
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
dt_iop_module_t * creation_module
Definition masks.h:519
dt_masks_pressure_sensitivity_t pressure_sensitivity
Definition masks.h:527
gboolean seg_selected
Definition masks.h:486
gboolean form_dragging
Definition masks.h:499
gboolean creation
Definition masks.h:517
gboolean form_selected
Definition masks.h:490
gboolean handle_selected
Definition masks.h:485
dt_masks_dynbuf_t * guipoints
Definition masks.h:449
gboolean border_selected
Definition masks.h:491
float delta[2]
Definition masks.h:469
dt_masks_type_t type
Definition masks.h:380
float source[2]
Definition masks.h:387
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:370
struct dt_masks_gui_center_point_t::@34 main
struct dt_masks_gui_center_point_t::@35 source
dt_masks_points_states_t state
Definition masks.h:267
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29