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 const int index = 0;
1427
1428 switch(interaction)
1429 {
1431 if(!_change_size(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1432 return _brush_get_interaction_value(mask_form, interaction);
1434 if(!_change_hardness(mask_form, 0, mask_gui, module, index, value, increment, flow)) return NAN;
1435 return _brush_get_interaction_value(mask_form, interaction);
1436 default:
1437 return NAN;
1438 }
1439}
1440
1441static int _change_hardness(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1442 struct dt_iop_module_t *module, int index, const float amount,
1443 const dt_masks_increment_t increment, const int flow)
1444{
1445 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1446 int node_index = 0;
1447 const float scale_amount = 1.f / powf(amount, (float)flow);
1448 const float offset_amount = -amount * (float)flow;
1449 float result_amount = 0.0f;
1450 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1451 {
1452 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1453 {
1454 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1455 const float current_hardness = node->hardness;
1456 result_amount = dt_masks_apply_increment_precomputed(current_hardness, amount, scale_amount, offset_amount, increment);
1457
1458 node->hardness = CLAMPF(result_amount, HARDNESS_MIN, HARDNESS_MAX);
1459 }
1460 }
1461
1462 dt_masks_get_set_conf_value(mask_form, "hardness", result_amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
1463
1464 // we recreate the form points
1465 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1466
1467 return 1;
1468}
1469
1470static int _change_size(dt_masks_form_t *mask_form, int parentid, dt_masks_form_gui_t *mask_gui,
1471 struct dt_iop_module_t *module, int index, const float amount,
1472 const dt_masks_increment_t increment, const int flow)
1473{
1474 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1475 // Sanitize loop
1476 // do not exceed upper limit of 1.0 and lower limit of 0.004
1477 int node_index = 0;
1478 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1479 {
1480 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1481 {
1482 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1483 if(!node) continue;
1484 if(amount > 1.0f && (node->border[0] > 1.0f || node->border[1] > 1.0f))
1485 return 1;
1486 }
1487 }
1488
1489 // Growing/shrinking loop
1490 const float scale_amount = amount;
1491 const float offset_amount = amount;
1492 node_index = 0;
1493 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry), node_index++)
1494 {
1495 if(dt_masks_gui_change_affects_selected_node_or_all(mask_gui, node_index))
1496 {
1497 dt_masks_node_brush_t *node = (dt_masks_node_brush_t *)node_entry->data;
1498 if(!node) continue;
1499 node->border[0] = dt_masks_apply_increment_precomputed(node->border[0], amount,
1500 scale_amount, offset_amount, increment);
1501 node->border[1] = dt_masks_apply_increment_precomputed(node->border[1], amount,
1502 scale_amount, offset_amount, increment);
1503 }
1504 }
1505
1506 dt_masks_get_set_conf_value(mask_form, "border", amount, HARDNESS_MIN, HARDNESS_MAX, increment, flow);
1507
1508 // we recreate the form points
1509 if(!IS_NULL_PTR(mask_gui) && !IS_NULL_PTR(module)) dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1510
1511 return 1;
1512}
1513
1514/* Shape handlers receive widget-space coordinates, while normalized output-image
1515 * coordinates come from `mask_gui->rel_pos` and absolute output-image
1516 * coordinates come from `mask_gui->pos`. */
1517static int _brush_events_mouse_scrolled(struct dt_iop_module_t *module, double widget_x, double widget_y,
1518 int scroll_up, const int flow, uint32_t state, dt_masks_form_t *mask_form,
1519 int parentid, dt_masks_form_gui_t *mask_gui, int index,
1520 dt_masks_interaction_t interaction)
1521{
1522
1523
1524
1525 if(mask_gui->creation)
1526 {
1527 if(dt_modifier_is(state, GDK_SHIFT_MASK))
1528 return _init_hardness(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
1529 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
1530 return _init_opacity(mask_form, parentid, mask_gui, scroll_up ? +0.02f : -0.02f,
1532 else
1533 return _init_size(mask_form, parentid, mask_gui, scroll_up ? 1.02f : 0.98f, DT_MASKS_INCREMENT_SCALE, flow);
1534 }
1535 else if(dt_masks_is_anything_selected(mask_gui) || mask_gui->node_hovered >= 0)
1536 {
1537 // we register the current position
1538 if(mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
1539 {
1540 mask_gui->scrollx = mask_gui->pos[0];
1541 mask_gui->scrolly = mask_gui->pos[1];
1542 }
1543
1544 if(dt_modifier_is(state, GDK_CONTROL_MASK))
1545 return dt_masks_form_change_opacity(mask_form, parentid, scroll_up, flow);
1546 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
1547 return _change_hardness(mask_form, parentid, mask_gui, module, index, scroll_up ? -0.01f : 0.01f,
1549 else // resize don't care where the mouse is inside a shape
1550 return _change_size(mask_form, parentid, mask_gui, module, index, scroll_up ? 1.02f : 0.98f,
1552 }
1553 return 0;
1554}
1555
1556
1558{
1560 const char *psens = dt_conf_get_string_const("pressure_sensitivity");
1561 if(!IS_NULL_PTR(psens))
1562 {
1563 if(!strcmp(psens, "hardness (absolute)"))
1565 else if(!strcmp(psens, "hardness (relative)"))
1567 else if(!strcmp(psens, "opacity (absolute)"))
1569 else if(!strcmp(psens, "opacity (relative)"))
1571 else if(!strcmp(psens, "brush size (relative)"))
1573 }
1574}
1575
1576static void _add_node_to_segment(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parentid,
1577 dt_masks_form_gui_t *mask_gui, int index)
1578{
1579 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points) || IS_NULL_PTR(mask_gui)) return;
1580 const guint node_count = g_list_length(mask_form->points);
1581 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
1582 if(selected_segment < 0 || selected_segment >= (int)node_count) return;
1583
1584 // we add a new node to the brush
1585 dt_masks_node_brush_t *new_node = (dt_masks_node_brush_t *)(malloc(sizeof(dt_masks_node_brush_t)));
1586 if(IS_NULL_PTR(new_node)) return;
1587
1588 // set coordinates
1590 new_node->ctrl1[0] = new_node->ctrl1[1] = new_node->ctrl2[0] = new_node->ctrl2[1] = -1.0;
1592
1593 // set other attributes of the new node. we interpolate the starting and the end node of that
1594 // segment
1595 const float t = _brush_get_position_in_segment(new_node->node[0], new_node->node[1],
1596 mask_form, selected_segment);
1597 // start and end node of the segment
1598 GList *pt = g_list_nth(mask_form->points, selected_segment);
1599 if(IS_NULL_PTR(pt) || IS_NULL_PTR(pt->data))
1600 {
1601 dt_free(new_node);
1602 return;
1603 }
1604 dt_masks_node_brush_t *point0 = (dt_masks_node_brush_t *)pt->data;
1605 const GList *const next_pt = g_list_next_wraparound(pt, mask_form->points);
1606 if(IS_NULL_PTR(next_pt) || IS_NULL_PTR(next_pt->data))
1607 {
1608 dt_free(new_node);
1609 return;
1610 }
1611 dt_masks_node_brush_t *point1 = (dt_masks_node_brush_t *)next_pt->data;
1612 new_node->border[0] = point0->border[0] * (1.0f - t) + point1->border[0] * t;
1613 new_node->border[1] = point0->border[1] * (1.0f - t) + point1->border[1] * t;
1614 new_node->hardness = point0->hardness * (1.0f - t) + point1->hardness * t;
1615 new_node->density = point0->density * (1.0f - t) + point1->density * t;
1616
1617 mask_form->points = g_list_insert(mask_form->points, new_node, selected_segment + 1);
1618 _brush_init_ctrl_points(mask_form);
1619
1620 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
1621
1622 mask_gui->node_hovered = selected_segment + 1;
1623 mask_gui->node_selected = TRUE;
1624 mask_gui->node_selected_idx = selected_segment + 1;
1625 mask_gui->seg_hovered = -1;
1626 mask_gui->seg_selected = FALSE;
1627}
1628
1629static inline void _brush_translate_node(dt_masks_node_brush_t *node, const float delta_x, const float delta_y)
1630{
1631 dt_masks_translate_ctrl_node(node->node, node->ctrl1, node->ctrl2, delta_x, delta_y);
1632}
1633
1634static void _brush_translate_all_nodes(dt_masks_form_t *mask_form, const float delta_x, const float delta_y)
1635{
1636 for(GList *node_entry = mask_form->points; node_entry; node_entry = g_list_next(node_entry))
1637 _brush_translate_node((dt_masks_node_brush_t *)node_entry->data, delta_x, delta_y);
1638}
1639
1640static int _brush_events_button_pressed(struct dt_iop_module_t *module, double widget_x, double widget_y,
1641 double pressure, int which, int type, uint32_t state,
1642 dt_masks_form_t *mask_form, int parentid,
1643 dt_masks_form_gui_t *mask_gui, int index)
1644{
1645 // double click or triple click: ignore here
1646 if(type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return 1;
1647
1648 // always start with a mask density of 100%, it will be adjusted with pen pressure if used.
1649 const float masks_density = 1.0f;
1650
1651 if(mask_gui->creation)
1652 {
1653 if(which == 1)
1654 {
1655 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
1656 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
1658 float masks_hardness = dt_masks_get_set_conf_value(mask_form, "hardness", 1.0f,
1660
1661 if(dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK) || dt_modifier_is(state, GDK_SHIFT_MASK))
1662 {
1663 // set some absolute or relative position for the source of the clone mask
1664 if(mask_form->type & DT_MASKS_CLONE)
1666
1667 return 1;
1668 }
1669
1670 if(IS_NULL_PTR(mask_gui->guipoints)) mask_gui->guipoints = dt_masks_dynbuf_init(200000, "brush guipoints");
1671 if(IS_NULL_PTR(mask_gui->guipoints)) return 1;
1672 if(IS_NULL_PTR(mask_gui->guipoints_payload))
1673 mask_gui->guipoints_payload = dt_masks_dynbuf_init(400000, "brush guipoints_payload");
1674 if(IS_NULL_PTR(mask_gui->guipoints_payload)) return 1;
1675 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
1676 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_border, masks_hardness);
1677 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, masks_density, pressure);
1678
1679 mask_gui->guipoints_count = 1;
1680
1681 // add support for clone masks
1682 if(mask_form->type & DT_MASKS_CLONE)
1683 dt_masks_set_source_pos_initial_value(mask_gui, mask_form);
1684 // not used by regular masks
1685 else
1686 mask_form->source[0] = mask_form->source[1] = 0.0f;
1687
1688 _get_pressure_sensitivity(mask_gui);
1689
1690 return 1;
1691 }
1692 }
1693
1694 dt_masks_form_gui_points_t *gui_points
1695 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
1696 if(IS_NULL_PTR(gui_points)) return 0;
1697 const guint node_count = g_list_length(mask_form->points);
1698
1699 if(which == 1)
1700 {
1701 // The shape handler runs before the shared press-state selection update,
1702 // so concrete hovered targets must win over stale form/source selection.
1703 if(mask_gui->node_hovered >= 0)
1704 {
1705 // if ctrl is pressed, we change the type of point
1706 if(mask_gui->node_selected && dt_modifier_is(state, GDK_CONTROL_MASK))
1707 {
1709 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
1710 if(IS_NULL_PTR(node)) return 0;
1711 dt_masks_toggle_bezier_node_type(module, mask_form, mask_gui, index, gui_points,
1712 mask_gui->node_hovered, node->node, node->ctrl1, node->ctrl2,
1713 &node->state);
1714 return 1;
1715 }
1716 /*// we register the current position to avoid accidental move
1717 if(mask_gui->node_edited < 0 && mask_gui->scrollx == 0.0f && mask_gui->scrolly == 0.0f)
1718 {
1719 mask_gui->scrollx = pointer_x;
1720 mask_gui->scrolly = pointer_y;
1721 }*/
1722 mask_gui->delta[0] = gui_points->points[mask_gui->node_hovered * 6 + 2] - mask_gui->pos[0];
1723 mask_gui->delta[1] = gui_points->points[mask_gui->node_hovered * 6 + 3] - mask_gui->pos[1];
1724
1725 return 1;
1726 }
1727 else if(mask_gui->handle_hovered >= 0)
1728 {
1729 if(!dt_masks_node_is_cusp(gui_points, mask_gui->handle_hovered))
1730 {
1731 // we need to find the handle position
1732 float handle_x, handle_y;
1733 const int k = mask_gui->handle_hovered;
1734 _brush_ctrl2_to_handle(gui_points->points[k * 6 + 2], gui_points->points[k * 6 + 3],
1735 gui_points->points[k * 6 + 4], gui_points->points[k * 6 + 5],
1736 &handle_x, &handle_y, TRUE);
1737 // compute offsets
1738 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
1739 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
1740
1741 return 1;
1742 }
1743 }
1744 else if(mask_gui->handle_border_hovered >= 0)
1745 {
1746 float handle_x = NAN, handle_y = NAN;
1747 if(_brush_get_border_handle_mirrored(gui_points, node_count, mask_gui->handle_border_hovered,
1748 &handle_x, &handle_y))
1749 {
1750 mask_gui->delta[0] = handle_x - mask_gui->pos[0];
1751 mask_gui->delta[1] = handle_y - mask_gui->pos[1];
1752 }
1753
1754 return 1;
1755 }
1756 else if(mask_gui->seg_hovered >= 0)
1757 {
1758 mask_gui->node_hovered = -1;
1759
1760 if(dt_modifier_is(state, GDK_CONTROL_MASK))
1761 {
1762 _add_node_to_segment(module, mask_form, parentid, mask_gui, index);
1763 }
1764 else
1765 {
1766 // we move the entire segment
1767 mask_gui->delta[0] = gui_points->points[mask_gui->seg_hovered * 6 + 2] - mask_gui->pos[0];
1768 mask_gui->delta[1] = gui_points->points[mask_gui->seg_hovered * 6 + 3] - mask_gui->pos[1];
1769 }
1770 return 1;
1771 }
1772 else if(mask_gui->source_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
1773 {
1774 // we start the source dragging
1775 mask_gui->delta[0] = gui_points->source[2] - mask_gui->pos[0];
1776 mask_gui->delta[1] = gui_points->source[3] - mask_gui->pos[1];
1777 return 1;
1778 }
1779 else if(mask_gui->form_selected && mask_gui->edit_mode == DT_MASKS_EDIT_FULL)
1780 {
1781 // we start the form dragging
1782 mask_gui->delta[0] = gui_points->points[2] - mask_gui->pos[0];
1783 mask_gui->delta[1] = gui_points->points[3] - mask_gui->pos[1];
1784 return 1;
1785 }
1786 }
1787
1788 return 0;
1789}
1790
1792{
1793 float factor = 0.01f;
1794 const char *smoothing = dt_conf_get_string_const("brush_smoothing");
1795 if(!strcmp(smoothing, "low"))
1796 factor = 0.0025f;
1797 else if(!strcmp(smoothing, "medium"))
1798 factor = 0.01f;
1799 else if(!strcmp(smoothing, "high"))
1800 factor = 0.04f;
1801 return factor;
1802}
1803
1804static void _apply_pen_pressure(dt_masks_form_gui_t *mask_gui, float *payload_buffer)
1805{
1806 for(int i = 0; i < mask_gui->guipoints_count; i++)
1807 {
1808 float *payload = payload_buffer + 4 * i;
1809 float pressure = payload[3];
1810 payload[3] = 1.0f;
1811
1812 switch(mask_gui->pressure_sensitivity)
1813 {
1815 payload[0] = MAX(HARDNESS_MIN, payload[0] * pressure);
1816 break;
1818 payload[1] = MAX(HARDNESS_MIN, pressure);
1819 break;
1821 payload[1] = MAX(HARDNESS_MIN, payload[1] * pressure);
1822 break;
1824 payload[2] = MAX(0.05f, pressure);
1825 break;
1827 payload[2] = MAX(0.05f, payload[2] * pressure);
1828 break;
1829 default:
1831 // ignore pressure value
1832 break;
1833 }
1834 }
1835}
1836
1837
1838static int _brush_events_button_released(struct dt_iop_module_t *module, double widget_x, double widget_y,
1839 int which, uint32_t state, dt_masks_form_t *mask_form, int parentid,
1840 dt_masks_form_gui_t *mask_gui, int index)
1841{
1842
1843
1844 if(IS_NULL_PTR(mask_gui)) return 0;
1845 dt_masks_form_gui_points_t *gui_points
1846 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
1847 if(IS_NULL_PTR(gui_points)) return 0;
1848
1849 // The trick is to use the incremental setting, set to 1.0 to re-use the generic getter/setter without changing value
1850 float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f,
1852
1853 if(mask_gui->creation && which == 1)
1854 {
1855 if(dt_modifier_is(state, GDK_SHIFT_MASK) || dt_modifier_is(state, GDK_CONTROL_MASK | GDK_SHIFT_MASK))
1856 {
1857 // user just set the source position, so just return
1858 return 1;
1859 }
1860
1861 dt_iop_module_t *creation_module = mask_gui->creation_module;
1862
1863 if(mask_gui->guipoints && mask_gui->guipoints_count > 0)
1864 {
1865 // if the path consists only of one x/y pair we add a second one close so we don't need to deal with
1866 // this special case later
1867 if(mask_gui->guipoints_count == 1)
1868 {
1869 // add a helper node very close to the single spot
1870 const float x = dt_masks_dynbuf_get(mask_gui->guipoints, -2) + 0.01f;
1871 const float y = dt_masks_dynbuf_get(mask_gui->guipoints, -1) - 0.01f;
1872 dt_masks_dynbuf_add_2(mask_gui->guipoints, x, y);
1873 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
1874 const float hardness = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
1875 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
1876 const float pressure = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -1);
1878 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
1879 mask_gui->guipoints_count++;
1880 }
1881
1882 float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
1883 float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
1884
1885 // we transform the points
1887
1888 // we consolidate pen pressure readings into payload
1889 _apply_pen_pressure(mask_gui, guipoints_payload);
1890
1891 // accuracy level for node elimination, dependent on brush size
1892 const float epsilon2 = _get_brush_smoothing() * sqf(MAX(HARDNESS_MIN, masks_border));
1893
1894 // we simplify the path and generate the nodes
1895 mask_form->points = _brush_ramer_douglas_peucker(guipoints, mask_gui->guipoints_count,
1896 guipoints_payload, epsilon2);
1897
1898 // printf("guipoints_count %d, points %d\n", mask_gui->guipoints_count, g_list_length(mask_form->points));
1899
1900 _brush_init_ctrl_points(mask_form);
1901
1904 mask_gui->guipoints = NULL;
1905 mask_gui->guipoints_payload = NULL;
1906 mask_gui->guipoints_count = 0;
1907
1908 dt_masks_gui_form_save_creation(darktable.develop, creation_module, mask_form, mask_gui);
1909
1910 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
1911 {
1913 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return 1;
1914 int group_index = 0;
1915 int selected_index = -1;
1916 for(GList *group_entry = grp->points; group_entry; group_entry = g_list_next(group_entry))
1917 {
1918 dt_masks_form_group_t *group_form = (dt_masks_form_group_t *)group_entry->data;
1919 if(group_form->formid == mask_form->formid)
1920 {
1921 selected_index = group_index;
1922 break;
1923 }
1924 group_index++;
1925 }
1926 if(selected_index < 0) return 1;
1928 if(IS_NULL_PTR(visible_gui)) return 1;
1929 visible_gui->group_selected = selected_index;
1930
1931 dt_masks_select_form(creation_module, dt_masks_get_from_id(darktable.develop, mask_form->formid));
1932 }
1933 }
1934 else
1935 {
1936 // unlikely case of button released but no points gathered -> no form
1939 mask_gui->guipoints = NULL;
1940 mask_gui->guipoints_payload = NULL;
1941 mask_gui->guipoints_count = 0;
1942
1944 dt_masks_iop_update(module);
1945
1947 }
1948 return 1;
1949 }
1950
1951 else if(which == 1)
1952 {
1953 if(dt_masks_gui_is_dragging(mask_gui))
1954 return 1;
1955 }
1956 return 0;
1957}
1958
1959static int _brush_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event,
1960 dt_masks_form_t *mask_form, int parentid,
1961 dt_masks_form_gui_t *mask_gui, int index)
1962{
1963 return 0;
1964}
1965
1974static int _brush_events_mouse_moved(struct dt_iop_module_t *module, double widget_x, double widget_y,
1975 double pressure, int which, dt_masks_form_t *mask_form, int parentid,
1976 dt_masks_form_gui_t *mask_gui, int index)
1977{
1979 const int iwidth = darktable.develop->roi.raw_width;
1980 const int iheight = darktable.develop->roi.raw_height;
1981
1982 if(mask_gui->creation)
1983 {
1984 if(mask_gui->guipoints)
1985 {
1986 dt_masks_dynbuf_add_2(mask_gui->guipoints, mask_gui->pos[0], mask_gui->pos[1]);
1987 const float border = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -4);
1988 const float hardness = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -3);
1989 const float density = dt_masks_dynbuf_get(mask_gui->guipoints_payload, -2);
1991 dt_masks_dynbuf_add_2(mask_gui->guipoints_payload, density, pressure);
1992 mask_gui->guipoints_count++;
1993 return 1;
1994 }
1995
1996 // Let the cursor motion be redrawn as it moves in GUI
1997 return 1;
1998 }
1999
2000 if(IS_NULL_PTR(mask_form->points)) return 0;
2001 dt_masks_form_gui_points_t *gui_points
2002 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2003 if(IS_NULL_PTR(gui_points)) return 0;
2004 const guint node_count = g_list_length(mask_form->points);
2005
2006 if(mask_gui->node_dragging >= 0)
2007 {
2008 dt_masks_node_brush_t *dragged_node
2009 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_dragging);
2010 if(IS_NULL_PTR(dragged_node)) return 0;
2011
2012 float delta_x = 0.0f;
2013 float delta_y = 0.0f;
2014 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragged_node->node, &delta_x, &delta_y);
2015 _brush_translate_node(dragged_node, delta_x, delta_y);
2016
2017 // if first point, adjust the source position accordingly
2018 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->node_dragging == 0)
2019 dt_masks_translate_source(mask_form, delta_x, delta_y);
2020
2021 // we recreate the form points
2022 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2023 return 1;
2024 }
2025 else if(mask_gui->seg_dragging >= 0)
2026 {
2027 const GList *segment_start = g_list_nth(mask_form->points, mask_gui->seg_dragging);
2028 const GList *segment_end = g_list_next_wraparound(segment_start, mask_form->points);
2029 dt_masks_node_brush_t *segment_node = (dt_masks_node_brush_t *)segment_start->data;
2030 dt_masks_node_brush_t *segment_node_next = (dt_masks_node_brush_t *)segment_end->data;
2031 if(!segment_node || !segment_node_next) return 0;
2032
2033 float delta_x = 0.0f;
2034 float delta_y = 0.0f;
2035 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, segment_node->node, &delta_x, &delta_y);
2036 _brush_translate_node(segment_node, delta_x, delta_y);
2037 _brush_translate_node(segment_node_next, delta_x, delta_y);
2038
2039 // if first point's segment, adjust the source position accordingly
2040 if((mask_form->type & DT_MASKS_CLONE) && mask_gui->seg_dragging == 0)
2041 dt_masks_translate_source(mask_form, delta_x, delta_y);
2042
2043 // we recreate the form points
2044 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2045 return 1;
2046 }
2047 else if(mask_gui->handle_dragging >= 0)
2048 {
2050 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->handle_dragging);
2051 if(IS_NULL_PTR(node)) return 0;
2052
2053 float cursor_pos[2];
2054 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2055
2056 // compute ctrl points directly from new handle position
2057 float control_points[4];
2058 _brush_handle_to_ctrl(gui_points->points[mask_gui->handle_dragging * 6 + 2],
2059 gui_points->points[mask_gui->handle_dragging * 6 + 3], cursor_pos[0],
2060 cursor_pos[1], &control_points[0], &control_points[1], &control_points[2],
2061 &control_points[3], TRUE);
2062
2064
2065 // set new ctrl points
2066 dt_masks_set_ctrl_points(node->ctrl1, node->ctrl2, control_points);
2068
2069 _brush_init_ctrl_points(mask_form);
2070 // we recreate the form points
2071 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2072 return 1;
2073 }
2074 else if(mask_gui->handle_border_dragging >= 0)
2075 {
2076 const int node_index = mask_gui->handle_border_dragging;
2078 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, node_index);
2079 if(IS_NULL_PTR(node)) return 0;
2080
2081 float handle_x = NAN, handle_y = NAN;
2082 if(!_brush_get_border_handle_mirrored(gui_points, node_count, node_index, &handle_x, &handle_y))
2083 return 0;
2084
2085 const float node_px = gui_points->points[node_index * 6 + 2];
2086 const float node_py = gui_points->points[node_index * 6 + 3];
2087
2088 float pts[2];
2089 float cursor_pos[2];
2090 const float node_pos_gui[2] = { node_px, node_py };
2091 const float handle_pos[2] = { handle_x, handle_y };
2092 dt_masks_gui_delta_to_image_abs(mask_gui, cursor_pos);
2093 dt_masks_project_on_line(cursor_pos, node_pos_gui, handle_pos, pts);
2094
2095 const float border = dt_masks_border_from_projected_handle(dev, node->node, pts, fminf(iwidth, iheight));
2096
2097 node->border[0] = node->border[1] = border;
2098 // we recreate the form points
2099 dt_masks_gui_form_create_throttled(mask_form, mask_gui, index, module, mask_gui->pos[0], mask_gui->pos[1]);
2100 return 1;
2101 }
2102 else if(mask_gui->form_dragging || mask_gui->source_dragging)
2103 {
2104 dt_masks_node_brush_t *dragging_shape = (dt_masks_node_brush_t *)(mask_form->points)->data;
2105 if(IS_NULL_PTR(dragging_shape)) return 0;
2106
2107 if(mask_gui->form_dragging)
2108 {
2109 float delta_x = 0.0f;
2110 float delta_y = 0.0f;
2111 dt_masks_gui_delta_from_raw_anchor(dev, mask_gui, dragging_shape->node, &delta_x, &delta_y);
2112 _brush_translate_all_nodes(mask_form, delta_x, delta_y);
2113 }
2114 else
2115 {
2116 float raw_pos[2];
2118 mask_form->source[0] = raw_pos[0];
2119 mask_form->source[1] = raw_pos[1];
2120 }
2121
2122 // we recreate the form points
2123 dt_masks_gui_form_create(mask_form, mask_gui, index, module);
2124 return 1;
2125 }
2126 return 0;
2127}
2128
2129static void _brush_draw_shape(cairo_t *cr, const float *points, const int points_count, const int node_nb, const gboolean border, const gboolean source)
2130{
2131 // Find the first valid non-NaN point to start drawing
2132 // FIXME: Why not just avoid having NaN points in the array?
2133 int start_idx = -1;
2134 for(int i = node_nb * 3 + border; i < points_count; i++)
2135 {
2136 if(!isnan(points[i * 2]) && !isnan(points[i * 2 + 1]))
2137 {
2138 start_idx = i;
2139 break;
2140 }
2141 }
2142
2143 // Only draw if we have at least one valid point
2144 if(start_idx >= 0)
2145 {
2146 cairo_move_to(cr, points[start_idx * 2], points[start_idx * 2 + 1]);
2147
2148 // We don't want to draw the plain line twice, adapt the end index accordingly
2149 const int end_idx = border ? points_count : 0.5 * points_count;
2150
2151 for(int i = start_idx + 1; i < end_idx; i++)
2152 {
2153 if(!isnan(points[i * 2]) && !isnan(points[i * 2 + 1]))
2154 cairo_line_to(cr, points[i * 2], points[i * 2 + 1]);
2155 }
2156 }
2157}
2158
2159static float _brush_line_length(const float *line, const int first_pt, const int last_pt)
2160{
2161 float total_len = 0.0f;
2162 for(int i = first_pt; i < last_pt; i++)
2163 {
2164 const int i0 = i * 2;
2165 const int i1 = (i + 1) * 2;
2166 const float x0 = line[i0];
2167 const float y0 = line[i0 + 1];
2168 const float x1 = line[i1];
2169 const float y1 = line[i1 + 1];
2170 if(isnan(x0) || isnan(y0) || isnan(x1) || isnan(y1)) continue;
2171 const float dx = x1 - x0;
2172 const float dy = y1 - y0;
2173 const float len = dx * dx + dy * dy;
2174 if(len > 1e-12f) total_len += sqrtf(len);
2175 }
2176 return total_len;
2177}
2178
2179static gboolean _brush_line_point_at_length(const float *line, const int first_pt, const int last_pt,
2180 const float target_len, float *x, float *y)
2181{
2182 if(IS_NULL_PTR(line) || IS_NULL_PTR(x) || IS_NULL_PTR(y)) return FALSE;
2183 if(last_pt <= first_pt) return FALSE;
2184
2185 float acc = 0.0f;
2186 gboolean has_fallback = FALSE;
2187 float fallback_x = NAN, fallback_y = NAN;
2188
2189 for(int i = first_pt; i < last_pt; i++)
2190 {
2191 const int i0 = i * 2;
2192 const int i1 = (i + 1) * 2;
2193 const float x0 = line[i0];
2194 const float y0 = line[i0 + 1];
2195 const float x1 = line[i1];
2196 const float y1 = line[i1 + 1];
2197 if(isnan(x0) || isnan(y0) || isnan(x1) || isnan(y1)) continue;
2198
2199 const float dx = x1 - x0;
2200 const float dy = y1 - y0;
2201 const float len = sqrtf(dx * dx + dy * dy);
2202 if(len <= 1e-6f) continue;
2203
2204 has_fallback = TRUE;
2205 fallback_x = x1;
2206 fallback_y = y1;
2207
2208 if(acc + len >= target_len)
2209 {
2210 const float t = (target_len - acc) / len;
2211 *x = x0 + t * dx;
2212 *y = y0 + t * dy;
2213 return TRUE;
2214 }
2215 acc += len;
2216 }
2217
2218 if(!has_fallback || isnan(fallback_x) || isnan(fallback_y)) return FALSE;
2219 *x = fallback_x;
2220 *y = fallback_y;
2221 return TRUE;
2222}
2223
2224static gboolean _brush_get_line_midpoint(const float *line, const int first_pt, const int last_pt,
2225 float *mx, float *my)
2226{
2227 if(IS_NULL_PTR(line) || IS_NULL_PTR(mx) || IS_NULL_PTR(my)) return FALSE;
2228 const float total_len = _brush_line_length(line, first_pt, last_pt);
2229 if(total_len <= 1e-6f) return FALSE;
2230
2231 const float half_len = 0.5f * total_len;
2232 return _brush_line_point_at_length(line, first_pt, last_pt, half_len, mx, my);
2233}
2234
2235static gboolean _brush_get_source_center(const dt_masks_form_gui_points_t *gui_points, const int node_count,
2236 dt_masks_gui_center_point_t *center_pt)
2237{
2238 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(center_pt)) return FALSE;
2239
2240 // Work on the exact centerline span that is actually drawn (non-border path):
2241 // [node_count * 3, 0.5 * points_count)
2242 const int line_offset_pt = node_count * 3;
2243 const int points_line_end = gui_points->points_count / 2; // exclusive
2244 const int source_line_end = gui_points->source_count / 2; // exclusive
2245 const int line_end = MIN(points_line_end, source_line_end);
2246 const int line_count = line_end - line_offset_pt;
2247 if(line_count < 2) return FALSE;
2248
2249 const float *const points_line = gui_points->points + 2 * line_offset_pt;
2250 const float *const source_line = gui_points->source + 2 * line_offset_pt;
2251 const int first_pt = 0;
2252 const int last_pt = line_count - 1;
2253
2254 if(!_brush_get_line_midpoint(points_line, first_pt, last_pt,
2255 &center_pt->main.x, &center_pt->main.y))
2256 return FALSE;
2257 if(!_brush_get_line_midpoint(source_line, first_pt, last_pt,
2258 &center_pt->source.x, &center_pt->source.y))
2259 return FALSE;
2260 return TRUE;
2261}
2262
2263static void _brush_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *mask_gui, int index,
2264 int node_count)
2265{
2266 // in creation mode
2267 if(mask_gui->creation)
2268 {
2269 const float iwd = darktable.develop->roi.raw_width;
2270 const float iht = darktable.develop->roi.raw_height;
2271 const float min_iwd_iht = MIN(iwd, iht);
2272
2273 if(mask_gui->guipoints_count == 0)
2274 {
2276 if(IS_NULL_PTR(mask_form)) return;
2277
2278 const float masks_border = dt_masks_get_set_conf_value(mask_form, "border", 1.0f, BORDER_MIN, BORDER_MAX,
2280 const float masks_hardness = dt_masks_get_set_conf_value(mask_form, "hardness", 1.0f, HARDNESS_MIN,
2282 const float opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
2283
2284 const float radius1 = masks_border * masks_hardness * min_iwd_iht;
2285 const float radius2 = masks_border * min_iwd_iht;
2286
2287 float xpos = mask_gui->pos[0];
2288 float ypos = mask_gui->pos[1];
2289 if((xpos == -1.0f && ypos == -1.0f))
2290 {
2291 xpos = 0.f;
2292 ypos = 0.f;
2293 }
2294
2295 // draw brush circle at current mouse position
2296 cairo_save(cr);
2298 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2299 cairo_new_path(cr);
2300 cairo_arc(cr, xpos, ypos, radius1, 0, 2.0 * M_PI);
2301 cairo_fill_preserve(cr);
2302 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2303 cairo_stroke(cr);
2304 cairo_new_path(cr);
2305 cairo_arc(cr, xpos, ypos, radius2, 0, 2.0 * M_PI);
2306 dt_draw_stroke_line(DT_MASKS_DASH_STICK, FALSE, cr, FALSE, zoom_scale, CAIRO_LINE_CAP_ROUND);
2307
2308 if(mask_form->type & DT_MASKS_CLONE)
2309 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, xpos, ypos, xpos, ypos, FALSE);
2310
2311 cairo_restore(cr);
2312 }
2313 else
2314 {
2315 float masks_border = 0.0f, masks_hardness = 0.0f, masks_density = 0.0f;
2316 float radius = 0.0f, oldradius = 0.0f, opacity = 0.0f, oldopacity = 0.0f, pressure = 0.0f;
2317 int stroked = 1;
2318
2319 const float *guipoints = dt_masks_dynbuf_buffer(mask_gui->guipoints);
2320 const float *guipoints_payload = dt_masks_dynbuf_buffer(mask_gui->guipoints_payload);
2321
2322 cairo_save(cr);
2323 cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
2324 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
2325 masks_border = guipoints_payload[0];
2326 masks_hardness = guipoints_payload[1];
2327 masks_density = guipoints_payload[2];
2328 pressure = guipoints_payload[3];
2329
2330 switch(mask_gui->pressure_sensitivity)
2331 {
2333 masks_hardness = MAX(HARDNESS_MIN, pressure);
2334 break;
2336 masks_hardness = MAX(HARDNESS_MIN, masks_hardness * pressure);
2337 break;
2339 masks_density = MAX(0.05f, pressure);
2340 break;
2342 masks_density = MAX(0.05f, masks_density * pressure);
2343 break;
2345 masks_border = MAX(HARDNESS_MIN, masks_border * pressure);
2346 break;
2347 default:
2349 // ignore pressure value
2350 break;
2351 }
2352
2353 radius = oldradius = masks_border * masks_hardness * min_iwd_iht;
2354 opacity = oldopacity = masks_density;
2355
2356 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2358
2359 cairo_move_to(cr, guipoints[0], guipoints[1]);
2360 for(int i = 1; i < mask_gui->guipoints_count; i++)
2361 {
2362 cairo_line_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2363 stroked = 0;
2364 masks_border = guipoints_payload[i * 4];
2365 masks_hardness = guipoints_payload[i * 4 + 1];
2366 masks_density = guipoints_payload[i * 4 + 2];
2367 pressure = guipoints_payload[i * 4 + 3];
2368
2369 switch(mask_gui->pressure_sensitivity)
2370 {
2372 masks_hardness = MAX(HARDNESS_MIN, pressure);
2373 break;
2375 masks_hardness = MAX(HARDNESS_MIN, masks_hardness * pressure);
2376 break;
2378 masks_density = MAX(0.05f, pressure);
2379 break;
2381 masks_density = MAX(0.05f, masks_density * pressure);
2382 break;
2384 masks_border = MAX(HARDNESS_MIN, masks_border * pressure);
2385 break;
2386 default:
2388 // ignore pressure value
2389 break;
2390 }
2391
2392 radius = masks_border * masks_hardness * min_iwd_iht;
2393 opacity = masks_density;
2394
2395 if(radius != oldradius || opacity != oldopacity)
2396 {
2397 cairo_stroke(cr);
2398 stroked = 1;
2399 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2 * radius));
2401 oldradius = radius;
2402 oldopacity = opacity;
2403 cairo_move_to(cr, guipoints[i * 2], guipoints[i * 2 + 1]);
2404 }
2405 }
2406 if(!stroked) cairo_stroke(cr);
2407
2408 cairo_set_line_width(cr, DT_DRAW_SIZE_LINE / zoom_scale);
2410 cairo_new_path(cr);
2411 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2412 guipoints[2 * (mask_gui->guipoints_count - 1) + 1],
2413 radius, 0, 2.0 * M_PI);
2414 cairo_fill_preserve(cr);
2415 cairo_set_source_rgba(cr, .8, .8, .8, .8);
2416 cairo_stroke(cr);
2418 cairo_new_path(cr);
2419 cairo_arc(cr, guipoints[2 * (mask_gui->guipoints_count - 1)],
2420 guipoints[2 * (mask_gui->guipoints_count - 1) + 1], masks_border * min_iwd_iht, 0,
2421 2.0 * M_PI);
2422 cairo_stroke(cr);
2423
2425 if(visible_form && (visible_form->type & DT_MASKS_CLONE))
2426 {
2427 const int i = mask_gui->guipoints_count - 1;
2428 dt_masks_draw_source_preview(cr, zoom_scale, mask_gui, guipoints[0], guipoints[1],
2429 guipoints[i * 2], guipoints[i * 2 + 1], TRUE);
2430 }
2431
2432 cairo_restore(cr);
2433 }
2434 return;
2435 } // creation
2436
2437 dt_masks_form_gui_points_t *gui_points
2438 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, index);
2439 if(IS_NULL_PTR(gui_points)) return;
2440 if(IS_NULL_PTR(gui_points->points)) return;
2441
2442 const int selected_node = dt_masks_gui_selected_node_index(mask_gui);
2443 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
2444 const int selected_handle_border = dt_masks_gui_selected_handle_border_index(mask_gui);
2445 const int selected_segment = dt_masks_gui_selected_segment_index(mask_gui);
2446
2447 // minimum points
2448 if(gui_points->points_count <= node_count * 3 + 2) return;
2449
2450 // draw path
2451 if(node_count > 0 && gui_points->points_count > node_count * 3 + 6) // there must be something to draw
2452 {
2453 dt_masks_draw_path_seg_by_seg(cr, mask_gui, index, gui_points->points, 0.5f * gui_points->points_count,
2454 node_count, zoom_scale);
2455 }
2456
2457 if(0)
2458 {
2459 const int total_points = gui_points->points_count * 2;
2460 int seg1 = 1;
2461 int current_seg = 0;
2462
2463 /* Draw the line point-by-point up to the next node, then stroke it; repeat in a loop. */
2464 cairo_move_to(cr, gui_points->points[node_count * 6], gui_points->points[node_count * 6 + 1]);
2465
2466 const int end_idx = 0.5 * gui_points->points_count;
2467
2468 for(int i = node_count * 3; i < end_idx; i++)
2469 {
2470 const double x = gui_points->points[i * 2];
2471 const double y = gui_points->points[i * 2 + 1];
2472 cairo_line_to(cr, x, y);
2473
2474 int seg_idx = seg1 * 6;
2475 if((seg_idx + 3) < total_points)
2476 {
2477 const double segment_x = gui_points->points[seg_idx + 2];
2478 const double segment_y = gui_points->points[seg_idx + 3];
2479
2480 /* Is this point the next node? */
2481 if(x == segment_x && y == segment_y)
2482 {
2483 const gboolean seg_selected = (mask_gui->group_selected == index)
2484 && (selected_segment == current_seg);
2485 const gboolean all_selected = (mask_gui->group_selected == index)
2486 && !mask_gui->node_selected
2487 && !mask_gui->handle_selected
2488 && !mask_gui->handle_border_selected
2489 && !mask_gui->seg_selected
2490 && (mask_gui->form_selected || mask_gui->form_dragging);
2491 // creation mode: draw the current segment as round dotted line
2492 if(mask_gui->creation && current_seg == node_count - 2)
2493 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
2494 else
2495 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, (seg_selected || all_selected), zoom_scale, CAIRO_LINE_CAP_BUTT);
2496 seg1 = (seg1 + 1) % node_count;
2497 current_seg++;
2498 }
2499 }
2500 }
2501 }
2502
2503 // draw nodes and attached stuff
2504 if(mask_gui->group_selected == index)
2505 {
2506 // draw borders
2507 if(gui_points->border && gui_points->border_count > node_count * 3 + 2)
2508 {
2509 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, node_count, (mask_gui->border_selected), zoom_scale,
2510 gui_points->border, gui_points->border_count, &dt_masks_functions_brush.draw_shape,
2511 CAIRO_LINE_CAP_ROUND);
2512 }
2513
2514 // draw the current node's handle if it's a curve node
2515 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count
2516 && !dt_masks_node_is_cusp(gui_points, selected_node))
2517 {
2518 const int n = selected_node;
2519 float handle[2];
2520 _brush_ctrl2_to_handle(gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3],
2521 gui_points->points[n * 6 + 4], gui_points->points[n * 6 + 5], &handle[0], &handle[1],
2522 TRUE);
2523 const float pt[2] = { gui_points->points[n * 6 + 2], gui_points->points[n * 6 + 3] };
2524 const gboolean selected = (mask_gui->node_hovered == n || selected_handle == n || (mask_gui->handle_hovered == n));
2525 dt_draw_handle(cr, pt, zoom_scale, handle, selected, FALSE);
2526 }
2527
2528 // draw all nodes
2529 for(int k = 0; k < node_count; k++)
2530 {
2531 const gboolean corner = dt_masks_node_is_cusp(gui_points, k);
2532 const float x = gui_points->points[k * 6 + 2];
2533 const float y = gui_points->points[k * 6 + 3];
2534 const gboolean selected = (k == mask_gui->node_hovered || k == mask_gui->node_dragging);
2535 const gboolean action = (k == selected_node);
2536
2537 dt_draw_node(cr, corner, action, selected, zoom_scale, x, y);
2538 }
2539
2540 // Draw the current node's border handle, if needed
2541 if(mask_gui->node_selected && selected_node >= 0 && selected_node < node_count)
2542 {
2543 const int edited = selected_node;
2544 const gboolean selected = (mask_gui->node_hovered == edited
2545 || selected_handle_border == edited
2546 || mask_gui->handle_border_hovered == edited);
2547 float handle[2] = {NAN, NAN};
2548 // Show the border handle on the opposite side from the curve handle
2549 if(_brush_get_border_handle_mirrored(gui_points, node_count, edited, &handle[0], &handle[1]))
2550 {
2551 dt_draw_handle(cr, NULL, zoom_scale, handle, selected, TRUE);
2552 }
2553 }
2554 }
2555
2556 // Draw the source if needed
2557 if(gui_points->source && gui_points->source_count > node_count * 3 + 2)
2558 {
2560 if(_brush_get_source_center(gui_points, node_count, &center_pt))
2561 dt_masks_draw_source(cr, mask_gui, index, node_count, zoom_scale, &center_pt,
2563
2564 //draw the current node projection
2565 for(int k = 0; k < node_count; k++)
2566 {
2567 if(mask_gui->group_selected == index
2568 && (k == mask_gui->node_hovered || k == selected_node
2569 || (mask_gui->creation && k == node_count - 1)))
2570 {
2571 const int node_index = k * 6 + 2;
2572 const float proj[2] = { gui_points->source[node_index], gui_points->source[node_index + 1] };
2573 const gboolean selected = mask_gui->node_hovered == k;
2574 const gboolean squared = dt_masks_node_is_cusp(gui_points, k);
2575
2576 dt_draw_handle(cr, NULL, zoom_scale, proj, selected, squared);
2577 }
2578 }
2579 }
2580}
2581
2588static void _brush_bounding_box_raw(const float *const restrict points, const float *const restrict border,
2589 const int node_count, const int total_points, float *x_min, float *x_max,
2590 float *y_min, float *y_max)
2591{
2592 // now we want to find the area, so we search min/max points
2593 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2594 __OMP_PARALLEL_FOR__(reduction(min : min_x, min_y) reduction(max : max_x, max_y) if(total_points > 1000))
2595 // Skip control points and only consider actual polyline samples.
2596 for(int point_index = node_count * 3; point_index < total_points; point_index++)
2597 {
2598 // we look at the borders
2599 const float border_x = border[point_index * 2];
2600 const float border_y = border[point_index * 2 + 1];
2601 min_x = MIN(border_x, min_x);
2602 max_x = MAX(border_x, max_x);
2603 min_y = MIN(border_y, min_y);
2604 max_y = MAX(border_y, max_y);
2605 // we look at the brush too
2606 const float brush_x = points[point_index * 2];
2607 const float brush_y = points[point_index * 2 + 1];
2608 min_x = MIN(brush_x, min_x);
2609 max_x = MAX(brush_x, max_x);
2610 min_y = MIN(brush_y, min_y);
2611 max_y = MAX(brush_y, max_y);
2612 }
2613 *x_min = min_x;
2614 *x_max = max_x;
2615 *y_min = min_y;
2616 *y_max = max_y;
2617}
2618
2624static void _brush_bounding_box(const float *const points, const float *const border, const int node_count,
2625 const int total_points, int *width, int *height, int *offset_x, int *offset_y)
2626{
2627 float min_x = FLT_MAX, max_x = FLT_MIN, min_y = FLT_MAX, max_y = FLT_MIN;
2628 _brush_bounding_box_raw(points, border, node_count, total_points, &min_x, &max_x, &min_y, &max_y);
2629 *height = max_y - min_y + 4;
2630 *width = max_x - min_x + 4;
2631 *offset_x = min_x - 2;
2632 *offset_y = min_y - 2;
2633}
2634
2640static int _get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2641 const dt_dev_pixelpipe_iop_t *const piece,
2642 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x, int *offset_y,
2643 int include_source)
2644{
2645 if(IS_NULL_PTR(module)) return 1;
2646 // we get buffers for all points
2647 float *points = NULL, *border = NULL;
2648 int points_count, border_count;
2649 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2650 &points, &points_count, &border, &border_count, NULL, NULL, include_source) != 0)
2651 {
2654 return 1;
2655 }
2656
2657 const guint node_count = g_list_length(mask_form->points);
2658 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || points_count <= (int)(node_count * 3) || border_count <= (int)(node_count * 3))
2659 {
2662 *width = *height = *offset_x = *offset_y = 0;
2663 return 0;
2664 }
2665 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
2666
2669 return 0;
2670}
2671
2674 dt_masks_form_t *mask_form, int *width, int *height, int *offset_x, int *offset_y)
2675{
2676 return _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 1);
2677}
2678
2679static int _brush_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2680 const dt_dev_pixelpipe_iop_t *const piece,
2681 dt_masks_form_t *const mask_form, int *width, int *height, int *offset_x,
2682 int *offset_y)
2683{
2684 return _get_area(module, pipe, piece, mask_form, width, height, offset_x, offset_y, 0);
2685}
2686
2688static void _brush_falloff(float *const restrict buffer, int segment_start[2], int segment_end[2],
2689 int offset_x, int offset_y, int buffer_width, float hardness, float density)
2690{
2691 // segment length
2692 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
2693 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
2694 + 1;
2695 const int solid_length = (int)segment_length * hardness;
2696 const int soft_length = segment_length - solid_length;
2697
2698 const float segment_dx = segment_end[0] - segment_start[0];
2699 const float segment_dy = segment_end[1] - segment_start[1];
2700 const float inv_length = 1.0f / (float)segment_length;
2701 const float inv_soft = (soft_length > 0) ? 1.0f / (float)soft_length : 0.0f;
2702
2703 for(int step = 0; step < segment_length; step++)
2704 {
2705 // position
2706 const int x = (int)((float)step * segment_dx * inv_length) + segment_start[0] - offset_x;
2707 const int y = (int)((float)step * segment_dy * inv_length) + segment_start[1] - offset_y;
2708 const float opacity = density * ((step <= solid_length) ? 1.0f : 1.0f - (float)(step - solid_length) * inv_soft);
2709 const int buffer_index = y * buffer_width + x;
2710 buffer[buffer_index] = MAX(buffer[buffer_index], opacity);
2711 if(x > 0)
2712 buffer[buffer_index - 1] = MAX(buffer[buffer_index - 1], opacity); // avoid gaps from rounding
2713 if(y > 0)
2714 buffer[buffer_index - buffer_width] = MAX(buffer[buffer_index - buffer_width], opacity); // avoid gaps from rounding
2715 }
2716}
2717
2723static int _brush_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2724 const dt_dev_pixelpipe_iop_t *const piece,
2725 dt_masks_form_t *const mask_form,
2726 float **buffer, int *width, int *height, int *offset_x, int *offset_y)
2727{
2728 if(IS_NULL_PTR(module)) return 1;
2729 double timer_start = 0.0;
2730 double timer_step_start = 0.0;
2731 if(darktable.unmuted & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
2732
2733 // we get buffers for all points
2734 float *points = NULL, *border = NULL, *payload = NULL;
2735 int points_count, border_count, payload_count;
2736 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2737 &points, &points_count,
2738 &border, &border_count, &payload, &payload_count, 0) != 0)
2739 {
2743 return 1;
2744 }
2745
2747 {
2748 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
2749 dt_get_wtime() - timer_step_start);
2750 timer_step_start = dt_get_wtime();
2751 }
2752
2753 const guint node_count = g_list_length(mask_form->points);
2754 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
2755 || border_count <= (int)(node_count * 3))
2756 {
2760 *buffer = NULL;
2761 *width = *height = *offset_x = *offset_y = 0;
2762 return 0;
2763 }
2764 const gboolean use_sparse = (dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, NULL)
2765 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL);
2766 const int sparse_step = use_sparse ? 4 : 1;
2767 _brush_bounding_box(points, border, node_count, points_count, width, height, offset_x, offset_y);
2768
2770 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
2771 dt_get_wtime() - timer_step_start);
2772
2773 // we allocate the buffer
2774 const size_t buffer_size = (size_t)(*width) * (*height);
2775 // ensure that the buffer is zeroed, as the below code only fills in pixels in the falloff region
2776 *buffer = dt_pixelpipe_cache_alloc_align_float_cache(buffer_size, 0);
2777 if(IS_NULL_PTR(*buffer))
2778 {
2782 return 1;
2783 }
2784 memset(*buffer, 0, sizeof(float) * buffer_size);
2785
2786 // now we fill the falloff
2787 int segment_start[2], segment_end[2];
2788 int prev_start[2] = { 0, 0 };
2789 int prev_end[2] = { 0, 0 };
2790 float prev_payload[2] = { 0.0f, 0.0f };
2791 gboolean have_prev = FALSE;
2792
2793 // Walk all border samples and stamp a falloff segment for each.
2794 for(int border_index = node_count * 3; border_index < border_count; border_index++)
2795 {
2796 segment_start[0] = points[border_index * 2];
2797 segment_start[1] = points[border_index * 2 + 1];
2798 segment_end[0] = border[border_index * 2];
2799 segment_end[1] = border[border_index * 2 + 1];
2800
2801 if(use_sparse && have_prev
2802 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
2803 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
2804 {
2805 // In sparse mode (preview/thumbnail), interpolate missing segments to reduce holes.
2806 for(int step = 1; step < sparse_step; step++)
2807 {
2808 const float t = (float)step / (float)sparse_step;
2809 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
2810 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
2811 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
2812 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
2813 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
2814 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
2815 _brush_falloff(*buffer, interp_start, interp_end, *offset_x, *offset_y, *width, hard, dens);
2816 }
2817 }
2818
2819 _brush_falloff(*buffer, segment_start, segment_end, *offset_x, *offset_y, *width,
2820 payload[border_index * 2], payload[border_index * 2 + 1]);
2821
2822 prev_start[0] = segment_start[0];
2823 prev_start[1] = segment_start[1];
2824 prev_end[0] = segment_end[0];
2825 prev_end[1] = segment_end[1];
2826 prev_payload[0] = payload[border_index * 2];
2827 prev_payload[1] = payload[border_index * 2 + 1];
2828 have_prev = TRUE;
2829 }
2830
2834
2836 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
2837 dt_get_wtime() - timer_start);
2838
2839 return 0;
2840}
2841
2843static inline void _brush_falloff_roi(float *buffer, const int *segment_start, const int *segment_end,
2844 int buffer_width, int buffer_height, float hardness, float density)
2845{
2846 // segment length (increase by 1 to avoid division-by-zero special case handling)
2847 const int segment_length = sqrt((segment_end[0] - segment_start[0]) * (segment_end[0] - segment_start[0])
2848 + (segment_end[1] - segment_start[1]) * (segment_end[1] - segment_start[1]))
2849 + 1;
2850 const int solid_length = hardness * segment_length;
2851
2852 const float step_x = (float)(segment_end[0] - segment_start[0]) / (float)segment_length;
2853 const float step_y = (float)(segment_end[1] - segment_start[1]) / (float)segment_length;
2854
2855 const int direction_x = step_x <= 0 ? -1 : 1;
2856 const int direction_y = step_y <= 0 ? -1 : 1;
2857 const int neighbor_offset_x = direction_x;
2858 const int neighbor_offset_y = direction_y * buffer_width;
2859
2860 float cursor_x = segment_start[0];
2861 float cursor_y = segment_start[1];
2862
2863 float opacity = density;
2864 const float opacity_step = density / (float)(segment_length - solid_length);
2865
2866 const int start_x = segment_start[0], start_y = segment_start[1];
2867 const int end_x = segment_end[0], end_y = segment_end[1];
2868 if((start_x < 0 && end_x < 0) || (start_x >= buffer_width && end_x >= buffer_width)
2869 || (start_y < 0 && end_y < 0) || (start_y >= buffer_height && end_y >= buffer_height))
2870 return;
2871 const int fully_inside = (start_x >= 0 && start_x < buffer_width && end_x >= 0 && end_x < buffer_width
2872 && start_y >= 0 && start_y < buffer_height && end_y >= 0
2873 && end_y < buffer_height);
2874
2875 for(int step = 0; step < segment_length; step++)
2876 {
2877 const int x = cursor_x;
2878 const int y = cursor_y;
2879
2880 cursor_x += step_x;
2881 cursor_y += step_y;
2882 if(step > solid_length) opacity -= opacity_step;
2883
2884 if(!fully_inside && (x < 0 || x >= buffer_width || y < 0 || y >= buffer_height)) continue;
2885
2886 float *buf = buffer + (size_t)y * buffer_width + x;
2887
2888 *buf = MAX(*buf, opacity);
2889 if(x + direction_x >= 0 && x + direction_x < buffer_width)
2890 buf[neighbor_offset_x] = MAX(buf[neighbor_offset_x], opacity); // avoid gaps from rounding
2891 if(y + direction_y >= 0 && y + direction_y < buffer_height)
2892 buf[neighbor_offset_y] = MAX(buf[neighbor_offset_y], opacity); // avoid gaps from rounding
2893 }
2894}
2895
2896// build a stamp which can be combined with other shapes in the same group
2897// prerequisite: 'buffer' is all zeros
2903static int _brush_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
2904 const dt_dev_pixelpipe_iop_t *const piece,
2905 dt_masks_form_t *const mask_form, const dt_iop_roi_t *roi, float *buffer)
2906{
2907 if(IS_NULL_PTR(module)) return 1;
2908 double timer_start = 0.0;
2909 double timer_step_start = 0.0;
2910 if(darktable.unmuted & DT_DEBUG_PERF) timer_start = timer_step_start = dt_get_wtime();
2911
2912 const int roi_offset_x = roi->x;
2913 const int roi_offset_y = roi->y;
2914 const int roi_width = roi->width;
2915 const int roi_height = roi->height;
2916 const float roi_scale = roi->scale;
2917 const gboolean use_sparse = (dt_dev_pixelpipe_has_preview_output(piece->module->dev, pipe, roi)
2918 || pipe->type == DT_DEV_PIXELPIPE_THUMBNAIL);
2919 const int sparse_step = use_sparse ? 4 : 1;
2920
2921 // we get buffers for all points
2922 float *points = NULL, *border = NULL, *payload = NULL;
2923
2924 int points_count, border_count, payload_count;
2925
2926 if(_brush_get_pts_border(module->dev, mask_form, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, pipe,
2927 &points, &points_count, &border, &border_count, &payload, &payload_count, 0) != 0)
2928 {
2932 return 1;
2933 }
2934
2936 {
2937 dt_print(DT_DEBUG_MASKS, "[masks %s] brush points took %0.04f sec\n", mask_form->name,
2938 dt_get_wtime() - timer_step_start);
2939 timer_step_start = dt_get_wtime();
2940 }
2941
2942 const guint node_count = g_list_length(mask_form->points);
2943 if(IS_NULL_PTR(points) || IS_NULL_PTR(border) || IS_NULL_PTR(payload) || points_count <= (int)(node_count * 3)
2944 || border_count <= (int)(node_count * 3))
2945 {
2949 return 0;
2950 }
2951
2952 // we shift and scale down brush and border
2953 // Shift/scale border samples into ROI space.
2954 for(int border_index = node_count * 3; border_index < border_count; border_index++)
2955 {
2956 const float border_x = border[2 * border_index];
2957 const float border_y = border[2 * border_index + 1];
2958 border[2 * border_index] = border_x * roi_scale - roi_offset_x;
2959 border[2 * border_index + 1] = border_y * roi_scale - roi_offset_y;
2960 }
2961
2962 // Shift/scale centerline samples into ROI space.
2963 for(int point_index = node_count * 3; point_index < points_count; point_index++)
2964 {
2965 const float point_x = points[2 * point_index];
2966 const float point_y = points[2 * point_index + 1];
2967 points[2 * point_index] = point_x * roi_scale - roi_offset_x;
2968 points[2 * point_index + 1] = point_y * roi_scale - roi_offset_y;
2969 }
2970
2971
2972 float min_x = 0.0f, max_x = 0.0f, min_y = 0.0f, max_y = 0.0f;
2973 _brush_bounding_box_raw(points, border, node_count, points_count, &min_x, &max_x, &min_y, &max_y);
2974
2976 {
2977 dt_print(DT_DEBUG_MASKS, "[masks %s] brush_fill min max took %0.04f sec\n", mask_form->name,
2978 dt_get_wtime() - timer_step_start);
2979 timer_step_start = dt_get_wtime();
2980 }
2981
2982 // check if the path completely lies outside of roi -> we're done/mask remains empty
2983 if(max_x < 0 || max_y < 0 || min_x >= roi_width || min_y >= roi_height)
2984 {
2988 return 0;
2989 }
2990
2991 // now we fill the falloff
2992 if(!use_sparse)
2993 {
2994 __OMP_PARALLEL_FOR__(if(border_count - node_count * 3 > 1000))
2995 // Stamp each border segment directly (full sampling).
2996 for(int border_index = node_count * 3; border_index < border_count; border_index++)
2997 {
2998 const int segment_start[] = { points[border_index * 2], points[border_index * 2 + 1] };
2999 const int segment_end[] = { border[border_index * 2], border[border_index * 2 + 1] };
3000
3001 if(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3002 || MAX(segment_start[1], segment_end[1]) < 0
3003 || MIN(segment_start[1], segment_end[1]) >= roi_height)
3004 continue;
3005
3006 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height, payload[border_index * 2],
3007 payload[border_index * 2 + 1]);
3008 }
3009 }
3010 else
3011 {
3012 int prev_start[2] = { 0, 0 };
3013 int prev_end[2] = { 0, 0 };
3014 float prev_payload[2] = { 0.0f, 0.0f };
3015 gboolean have_prev = FALSE;
3016
3017 // Sparse mode: interpolate between consecutive segments to reduce artifacts.
3018 for(int border_index = node_count * 3; border_index < border_count; border_index++)
3019 {
3020 int segment_start[2] = { points[border_index * 2], points[border_index * 2 + 1] };
3021 int segment_end[2] = { border[border_index * 2], border[border_index * 2 + 1] };
3022
3023 if(use_sparse && have_prev
3024 && (prev_start[0] != segment_start[0] || prev_start[1] != segment_start[1]
3025 || prev_end[0] != segment_end[0] || prev_end[1] != segment_end[1]))
3026 {
3027 for(int step = 1; step < sparse_step; step++)
3028 {
3029 const float t = (float)step / (float)sparse_step;
3030 int interp_start[2] = { (int)floorf(prev_start[0] + t * (segment_start[0] - prev_start[0]) + 0.5f),
3031 (int)floorf(prev_start[1] + t * (segment_start[1] - prev_start[1]) + 0.5f) };
3032 int interp_end[2] = { (int)floorf(prev_end[0] + t * (segment_end[0] - prev_end[0]) + 0.5f),
3033 (int)floorf(prev_end[1] + t * (segment_end[1] - prev_end[1]) + 0.5f) };
3034 if(!(MAX(interp_start[0], interp_end[0]) < 0 || MIN(interp_start[0], interp_end[0]) >= roi_width
3035 || MAX(interp_start[1], interp_end[1]) < 0 || MIN(interp_start[1], interp_end[1]) >= roi_height))
3036 {
3037 const float hard = prev_payload[0] + t * (payload[border_index * 2] - prev_payload[0]);
3038 const float dens = prev_payload[1] + t * (payload[border_index * 2 + 1] - prev_payload[1]);
3039 _brush_falloff_roi(buffer, interp_start, interp_end, roi_width, roi_height, hard, dens);
3040 }
3041 }
3042 }
3043
3044 if(!(MAX(segment_start[0], segment_end[0]) < 0 || MIN(segment_start[0], segment_end[0]) >= roi_width
3045 || MAX(segment_start[1], segment_end[1]) < 0
3046 || MIN(segment_start[1], segment_end[1]) >= roi_height))
3047 _brush_falloff_roi(buffer, segment_start, segment_end, roi_width, roi_height,
3048 payload[border_index * 2], payload[border_index * 2 + 1]);
3049
3050 prev_start[0] = segment_start[0];
3051 prev_start[1] = segment_start[1];
3052 prev_end[0] = segment_end[0];
3053 prev_end[1] = segment_end[1];
3054 prev_payload[0] = payload[border_index * 2];
3055 prev_payload[1] = payload[border_index * 2 + 1];
3056 have_prev = TRUE;
3057 }
3058 }
3059
3063
3065 {
3066 dt_print(DT_DEBUG_MASKS, "[masks %s] brush set falloff took %0.04f sec\n", mask_form->name,
3067 dt_get_wtime() - timer_step_start);
3068 dt_print(DT_DEBUG_MASKS, "[masks %s] brush fill buffer took %0.04f sec\n", mask_form->name,
3069 dt_get_wtime() - timer_start);
3070 }
3071
3072 return 0;
3073}
3074
3076{
3077 // nothing to do (yet?)
3078}
3079
3080static void _brush_set_form_name(struct dt_masks_form_t *const mask_form, const size_t form_number)
3081{
3082 snprintf(mask_form->name, sizeof(mask_form->name), _("brush #%d"), (int)form_number);
3083}
3084
3085static void _brush_set_hint_message(const dt_masks_form_gui_t *const mask_gui,
3086 const dt_masks_form_t *const mask_form,
3087 const int opacity, char *const restrict msgbuf,
3088 const size_t msgbuf_len)
3089{
3090 if(mask_gui->creation || mask_gui->form_selected)
3091 g_snprintf(msgbuf, msgbuf_len,
3092 _("<b>Size</b>: scroll, <b>Hardness</b>: shift+scroll\n"
3093 "<b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
3094 else if(mask_gui->border_selected)
3095 g_strlcat(msgbuf, _("<b>Size</b>: scroll"), msgbuf_len);
3096}
3097
3098static void _brush_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
3099{
3100 // unused arg, keep compiler from complaining
3102}
3103
3104static void _brush_initial_source_pos(const float iwd, const float iht, float *x, float *y)
3105{
3106
3107
3108 float offset[2] = { 0.01f, 0.01f };
3110 *x = offset[0];
3111 *y = offset[1];
3112}
3113
3114static void _brush_switch_node_callback(GtkWidget *widget, gpointer user_data)
3115{
3116 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3117 if(IS_NULL_PTR(mask_gui)) return;
3118
3119 dt_iop_module_t *module = darktable.develop->gui_module;
3120 if(IS_NULL_PTR(module)) return;
3121
3122 mask_gui->node_selected = TRUE;
3123 mask_gui->node_selected_idx = mask_gui->node_hovered;
3124
3125 const int form_id = mask_gui->formid;
3126 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, form_id);
3127 if(IS_NULL_PTR(selected_form)) return;
3128 dt_masks_form_gui_points_t *gui_points
3129 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3130 const int node_index = dt_masks_gui_selected_node_index(mask_gui);
3132 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3133 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3134 dt_masks_toggle_bezier_node_type(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3135 node_index, node->node, node->ctrl1, node->ctrl2, &node->state);
3136}
3137
3138static void _brush_reset_round_node_callback(GtkWidget *widget, gpointer user_data)
3139{
3140 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3141 if(IS_NULL_PTR(mask_gui)) return;
3142
3143 dt_iop_module_t *module = darktable.develop->gui_module;
3144 if(IS_NULL_PTR(module)) return;
3145
3146 mask_gui->node_selected = TRUE;
3147 mask_gui->node_selected_idx = mask_gui->node_hovered;
3148
3149 const int form_id = mask_gui->formid;
3150 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, form_id);
3151 if(IS_NULL_PTR(selected_form)) return;
3152 dt_masks_form_gui_points_t *gui_points
3153 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3154 const int selected_handle = dt_masks_gui_selected_handle_index(mask_gui);
3155 const int node_index = MAX(mask_gui->node_hovered, selected_handle);
3157 = (dt_masks_node_brush_t *)g_list_nth_data(selected_form->points, node_index);
3158 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(node)) return;
3159 dt_masks_reset_bezier_ctrl_points(module, selected_form, mask_gui, mask_gui->group_selected, gui_points,
3160 node_index, &node->state);
3161}
3162
3163static void _brush_add_node_callback(GtkWidget *menu, gpointer user_data)
3164{
3165 dt_masks_form_gui_t *mask_gui = (dt_masks_form_gui_t *)user_data;
3166 if(IS_NULL_PTR(mask_gui)) return;
3168 if(IS_NULL_PTR(visible_forms)) return;
3169
3170 dt_iop_module_t *module = darktable.develop->gui_module;
3171 if(IS_NULL_PTR(module)) return;
3172
3173 dt_masks_form_group_t *group_entry = dt_masks_form_get_selected_group(visible_forms, mask_gui);
3174 if(IS_NULL_PTR(group_entry)) return;
3175 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3176 if(selected_form)
3177 {
3178 _add_node_to_segment(module, selected_form, group_entry->parentid, mask_gui, mask_gui->group_selected);
3179 }
3180
3182}
3183
3184static int _brush_populate_context_menu(GtkWidget *menu, struct dt_masks_form_t *mask_form,
3185 struct dt_masks_form_gui_t *mask_gui,
3186 const float pointer_x, const float pointer_y)
3187{
3188
3189
3190 GtkWidget *menu_item = NULL;
3191 gchar *accel = g_strdup_printf(_("%s+Click"), gtk_accelerator_get_label(0, GDK_CONTROL_MASK));
3192
3193 gboolean ret = FALSE;
3194
3195 if(mask_gui->node_hovered >= 0)
3196 {
3197 dt_masks_form_gui_points_t *gui_points
3198 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, mask_gui->group_selected);
3199 if(IS_NULL_PTR(gui_points)) goto end;
3201 = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, mask_gui->node_hovered);
3202 if(IS_NULL_PTR(node)) goto end;
3203 const gboolean is_corner = dt_masks_node_is_cusp(gui_points, mask_gui->node_hovered);
3204
3205 {
3206 gchar *to_change_type = g_strdup_printf(_("Switch to %s node"), (is_corner) ? _("round") : _("cusp"));
3207 const dt_menu_icon_t icon = is_corner ? DT_MENU_ICON_CIRCLE : DT_MENU_ICON_SQUARE;
3208 menu_item = ctx_gtk_menu_item_new_with_icon_and_shortcut(to_change_type, accel, menu,
3209 _brush_switch_node_callback, mask_gui, icon);
3210 dt_free(to_change_type);
3211 }
3212
3213 {
3214 menu_item = ctx_gtk_menu_item_new_with_markup(_("Reset round node"), menu,
3216 gtk_widget_set_sensitive(menu_item, !is_corner && node->state != DT_MASKS_POINT_STATE_NORMAL);
3217 }
3218 ret = TRUE;
3219 }
3220
3221 if(mask_gui->seg_selected)
3222 {
3223 menu_item = ctx_gtk_menu_item_new_with_markup_and_shortcut(_("Add a node here"), accel,
3224 menu, _brush_add_node_callback, mask_gui);
3225 ret = TRUE;
3226 }
3227
3228 end:
3229 dt_free(accel);
3230 return ret;
3231}
3232
3233// The function table for brushes. This must be public, i.e. no "static" keyword.
3235 .point_struct_size = sizeof(struct dt_masks_node_brush_t),
3236 .sanitize_config = _brush_sanitize_config,
3237 .set_form_name = _brush_set_form_name,
3238 .set_hint_message = _brush_set_hint_message,
3239 .duplicate_points = _brush_duplicate_points,
3240 .initial_source_pos = _brush_initial_source_pos,
3241 .get_distance = _brush_get_distance,
3242 .get_points_border = _brush_get_points_border,
3243 .get_mask = _brush_get_mask,
3244 .get_mask_roi = _brush_get_mask_roi,
3245 .get_area = _brush_get_area,
3246 .get_source_area = _brush_get_source_area,
3247 .get_gravity_center = _brush_get_gravity_center,
3248 .get_interaction_value = _brush_get_interaction_value,
3249 .set_interaction_value = _brush_set_interaction_value,
3250 .update_hover = _find_closest_handle,
3251 .mouse_moved = _brush_events_mouse_moved,
3252 .mouse_scrolled = _brush_events_mouse_scrolled,
3253 .button_pressed = _brush_events_button_pressed,
3254 .button_released = _brush_events_button_released,
3255 .key_pressed = _brush_events_key_pressed,
3256 .post_expose = _brush_events_post_expose,
3257 .draw_shape = _brush_draw_shape,
3258 .init_ctrl_points = _brush_init_ctrl_points,
3259 .populate_context_menu = _brush_populate_context_menu
3260
3261};
3262
3263
3264// clang-format off
3265// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3266// vim: shiftwidth=2 expandtab tabstop=2 cindent
3267// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3268// 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:3743
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)
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
@ DT_DEBUG_PERF
Definition darktable.h:719
@ DT_DEBUG_MASKS
Definition darktable.h:727
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
static float * dt_alloc_align_float(size_t pixels)
Definition darktable.h:494
static const GList * g_list_next_wraparound(const GList *list, const GList *head)
Definition darktable.h:957
#define dt_free(ptr)
Definition darktable.h:456
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
static GList * g_list_next_bounded(GList *list)
Definition darktable.h:952
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static double dt_get_wtime(void)
Definition darktable.h:914
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition darktable.h:939
#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:1109
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1557
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:367
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1138
@ 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:448
@ 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:618
static int dt_masks_gui_selected_segment_index(const dt_masks_form_gui_t *gui)
Definition masks.h:549
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks.h:534
static int dt_masks_gui_selected_handle_index(const dt_masks_form_gui_t *gui)
Definition masks.h:539
static float * dt_masks_dynbuf_buffer(dt_masks_dynbuf_t *a)
Definition masks.h:1333
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:682
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:1260
@ DT_MASKS_EDIT_FULL
Definition masks.h:202
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:1111
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:1240
static void dt_masks_translate_source(dt_masks_form_t *form, const float delta_x, const float delta_y)
Definition masks.h:652
static float dt_masks_dynbuf_get(dt_masks_dynbuf_t *a, int offset)
Definition masks.h:1315
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:658
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:563
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:1275
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:544
float dt_masks_get_set_conf_value(dt_masks_form_t *form, char *feature, float new_value, float v_min, float v_max, dt_masks_increment_t increment, const int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *gui, const int index, const int nb, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_NON_CLONE
Definition masks.h:138
@ DT_MASKS_CLONE
Definition masks.h:134
@ DT_MASKS_GROUP
Definition masks.h:133
dt_masks_interaction_t
Definition masks.h:302
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:305
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:304
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:603
dt_masks_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:757
static gboolean dt_masks_center_of_gravity_from_points(const float *points, const int points_count, float center[2], float *area)
Definition masks.h:1339
static size_t dt_masks_dynbuf_position(dt_masks_dynbuf_t *a)
Definition masks.h:1410
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:554
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:626
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:1295
dt_masks_increment_t
Definition masks.h:193
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:195
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:196
@ DT_MASKS_PRESSURE_OPACITY_REL
Definition masks.h:211
@ DT_MASKS_PRESSURE_OPACITY_ABS
Definition masks.h:212
@ DT_MASKS_PRESSURE_BRUSHSIZE_REL
Definition masks.h:213
@ DT_MASKS_PRESSURE_HARDNESS_REL
Definition masks.h:209
@ DT_MASKS_PRESSURE_OFF
Definition masks.h:208
@ DT_MASKS_PRESSURE_HARDNESS_ABS
Definition masks.h:210
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
@ DT_MASKS_POINT_STATE_NORMAL
Definition masks.h:182
@ DT_MASKS_POINT_STATE_USER
Definition masks.h:183
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:732
static float * dt_masks_dynbuf_harvest(dt_masks_dynbuf_t *a)
Definition masks.h:1422
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:1324
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:710
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:611
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *gui, const uint32_t state)
Decide initial source positioning mode for clone masks.
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
Definition masks.h:1432
static void dt_masks_set_ctrl_points(float ctrl1[2], float ctrl2[2], const float control_points[4])
Definition masks.h:669
#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:130
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:173
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:188
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:760
struct dt_develop_t * develop
Definition darktable.h:770
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:327
struct dt_develop_t::@17 roi
struct dt_develop_t * dev
Definition imageop.h:296
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
gboolean node_selected
Definition masks.h:470
gboolean handle_border_selected
Definition masks.h:473
dt_masks_dynbuf_t * guipoints_payload
Definition masks.h:435
gboolean source_selected
Definition masks.h:478
int handle_border_dragging
Definition masks.h:493
int handle_border_hovered
Definition masks.h:468
gboolean source_dragging
Definition masks.h:486
dt_masks_edit_mode_t edit_mode
Definition masks.h:463
dt_iop_module_t * creation_module
Definition masks.h:505
dt_masks_pressure_sensitivity_t pressure_sensitivity
Definition masks.h:513
gboolean seg_selected
Definition masks.h:472
gboolean form_dragging
Definition masks.h:485
gboolean creation
Definition masks.h:503
gboolean form_selected
Definition masks.h:476
gboolean handle_selected
Definition masks.h:471
dt_masks_dynbuf_t * guipoints
Definition masks.h:435
gboolean border_selected
Definition masks.h:477
float delta[2]
Definition masks.h:455
dt_masks_type_t type
Definition masks.h:378
float source[2]
Definition masks.h:385
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:368
struct dt_masks_gui_center_point_t::@34 main
struct dt_masks_gui_center_point_t::@35 source
dt_masks_points_states_t state
Definition masks.h:266
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29