Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gradient.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013, 2016, 2019-2021 Pascal Obry.
4 Copyright (C) 2013-2017 Tobias Ellinghaus.
5 Copyright (C) 2013-2015, 2019-2020 Ulrich Pegelow.
6 Copyright (C) 2014-2016, 2021 Roman Lebedev.
7 Copyright (C) 2016, 2019, 2021 Aldric Renaudin.
8 Copyright (C) 2018 Edgardo Hoszowski.
9 Copyright (C) 2018 johannes hanika.
10 Copyright (C) 2019 Andreas Schneider.
11 Copyright (C) 2019, 2023, 2025-2026 Aurélien PIERRE.
12 Copyright (C) 2020-2022 Chris Elston.
13 Copyright (C) 2020 GrahamByrnes.
14 Copyright (C) 2020-2021 Hubert Kowalski.
15 Copyright (C) 2020 Paolo DePetrillo.
16 Copyright (C) 2020-2021 Ralf Brown.
17 Copyright (C) 2022 Martin Bařinka.
18 Copyright (C) 2025-2026 Guillaume Stutin.
19
20 darktable is free software: you can redistribute it and/or modify
21 it under the terms of the GNU General Public License as published by
22 the Free Software Foundation, either version 3 of the License, or
23 (at your option) any later version.
24
25 darktable is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with darktable. If not, see <http://www.gnu.org/licenses/>.
32*/
33#include "math/math.h"
34#include "system/macros.h"
35#include "system/openmp.h"
36#include "common/logging.h"
37#include "common/times.h"
39#include "common/conf.h"
40#include "develop/imageop.h"
41#include "develop/masks.h"
42#include "develop/masks_gui.h"
45#include "math/openmp_maths.h"
47
48#define extent_MIN 0.0005f
49#define extent_MAX 1.0f
50#define CURVATURE_MIN -2.0f
51#define CURVATURE_MAX 2.0f
52
53#define BORDER_MIN 0.00005f
54#define BORDER_MAX 0.5f
55
56// Helper function to find the INFINITY separator in border array
57static int _find_border_separator(const float *border, int count)
58{
59
60 if(IS_NULL_PTR(border) || count <= 0) return -1;
61
62#ifdef _OPENMP
63 int found = count;
64#pragma omp parallel for reduction(min:found) if(count > 1000)
65 for(int i = 0; i < count; i++)
66 {
67 if(!isfinite(border[i * 2]) && !isfinite(border[i * 2 + 1]))
68 found = i;
69 }
70 return (found == count) ? -1 : found;
71#else
72 for(int i = 0; i < count; i++)
73 {
74 if(!isfinite(border[i * 2]) && !isfinite(border[i * 2 + 1]))
75 return i;
76 }
77 return -1;
78#endif
79}
80
81
82// Helper function to find closest point on a line segment to a given point
83static void _closest_point_on_segment(float px, float py, float x1, float y1, float x2, float y2,
84 float *closest_x, float *closest_y, float *distance_sq)
85{
86 const float seg_dx = x2 - x1;
87 const float seg_dy = y2 - y1;
88 const float seg_length_sq = seg_dx * seg_dx + seg_dy * seg_dy;
89
90 if(seg_length_sq < 1e-10f)
91 {
92 // Degenerate segment, return first point
93 *closest_x = x1;
94 *closest_y = y1;
95 *distance_sq = (px - x1) * (px - x1) + (py - y1) * (py - y1);
96 return;
97 }
98
99 // Project point onto line segment (clamped to [0,1])
100 const float t = fmaxf(0.0f, fminf(1.0f,
101 ((px - x1) * seg_dx + (py - y1) * seg_dy) / seg_length_sq));
102
103 *closest_x = x1 + t * seg_dx;
104 *closest_y = y1 + t * seg_dy;
105 *distance_sq = (px - *closest_x) * (px - *closest_x) + (py - *closest_y) * (py - *closest_y);
106}
107
108// Helper function to find closest point on a polyline to a given point
109static void _closest_point_on_line(float px, float py, const float *border, int start_idx, int end_idx,
110 float *closest_x, float *closest_y, float *min_distance_sq)
111{
112 *min_distance_sq = FLT_MAX;
113 *closest_x = *closest_y = 0.0f;
114
115 if(start_idx >= end_idx - 1) return;
116
117#ifdef _OPENMP
118 float global_min = FLT_MAX;
119 float global_x = 0.0f, global_y = 0.0f;
120
121#pragma omp parallel
122 {
123 float local_min = FLT_MAX;
124 float local_x = 0.0f, local_y = 0.0f;
125
126#pragma omp for nowait
127 for(int i = start_idx; i < end_idx - 1; i++)
128 {
129 float seg_closest_x, seg_closest_y, seg_dist_sq;
131 border[i * 2], border[i * 2 + 1],
132 border[(i + 1) * 2], border[(i + 1) * 2 + 1],
133 &seg_closest_x, &seg_closest_y, &seg_dist_sq);
134
135 if(seg_dist_sq < local_min)
136 {
137 local_min = seg_dist_sq;
138 local_x = seg_closest_x;
139 local_y = seg_closest_y;
140 }
141 }
142
143 if(local_min < global_min)
144 {
145#pragma omp critical
146 {
147 if(local_min < global_min)
148 {
149 global_min = local_min;
150 global_x = local_x;
151 global_y = local_y;
152 }
153 }
154 }
155 } // end parallel
156
157 *min_distance_sq = global_min;
158 *closest_x = global_x;
159 *closest_y = global_y;
160#else
161 for(int i = start_idx; i < end_idx - 1; i++)
162 {
163 float seg_closest_x, seg_closest_y, seg_dist_sq;
165 border[i * 2], border[i * 2 + 1],
166 border[(i + 1) * 2], border[(i + 1) * 2 + 1],
167 &seg_closest_x, &seg_closest_y, &seg_dist_sq);
168
169 if(seg_dist_sq < *min_distance_sq)
170 {
171 *min_distance_sq = seg_dist_sq;
172 *closest_x = seg_closest_x;
173 *closest_y = seg_closest_y;
174 }
175 }
176#endif
177}
178
180{
181 const float gradient_dx = gpt->points[2] - gpt->points[0];
182 const float gradient_dy = gpt->points[3] - gpt->points[1];
183 return gradient_dx * gradient_dx + gradient_dy * gradient_dy;
184}
185
192
194{
195 values->extent = CLAMPF(dt_conf_get_float("plugins/darkroom/masks/gradient/extent"),
197 values->curvature = CLAMPF(dt_conf_get_float("plugins/darkroom/masks/gradient/curvature"),
199 values->rotation = dt_conf_get_float("plugins/darkroom/masks/gradient/rotation");
200 if(!isfinite(values->rotation)) values->rotation = 0.0f;
201}
202
204{
206 dt_masks_gui_cursor_to_raw_norm(gui->dev, gui, gradient->center);
208 gradient->extent = values.extent;
209 gradient->curvature = values.curvature;
210 gradient->rotation = values.rotation;
211}
212
213static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature,
214 float **points, int *points_count);
215static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance,
216 float curvature, float **points, int *points_count);
217
218// Gradient creation preview uses the same temp-buffer contract as circle/ellipse,
219// with the shape-specific geometry generation kept here.
221{
224
225 float center[2];
226 dt_masks_gui_cursor_to_raw_norm(gui->dev, gui, center);
227
229 int err = _gradient_get_points(gui->dev, center[0], center[1], values.rotation,
230 values.curvature, &preview->points, &preview->points_count);
231 if(!err && values.extent > 0.0f)
232 err = _gradient_get_pts_border(gui->dev, center[0], center[1], values.rotation,
233 values.extent, values.curvature, &preview->border,
234 &preview->border_count);
235 return err;
236}
237
238static void _gradient_get_distance(float x, float y, float dist_mouse, dt_masks_form_gui_t *gui, int index,
239 int num_points, int *inside, int *inside_border, int *near_handle,
240 int *inside_source, float *dist)
241{
242 // initialise returned values
243 *inside_source = 0;
244 *inside = 0;
245 *inside_border = 0;
246 *near_handle = -1;
247 *dist = FLT_MAX;
248 const float sqr_dist_mouse = dist_mouse * dist_mouse;
249
250 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
251 if(IS_NULL_PTR(gpt)) return;
252
253 float min_dist = FLT_MAX;
254
255 // check if we are between the two border lines
256 if(!gui->form_rotating && !gui->form_dragging && gpt->border && gpt->border_count > 6
257 && gpt->points && gpt->points_count >= 4)
258 {
259 const int separator_idx = _find_border_separator(gpt->border, gpt->border_count);
260 if(separator_idx > 0 && separator_idx < gpt->border_count - 1)
261 {
262 // Get gradient direction from segment (points[0],points[1]) to (points[2],points[3])
263 const float gradient_len_sq = _gradient_get_border_len_sq(gpt);
264
265 if(gradient_len_sq > 1e-12f)
266 {
267 // Find closest points on both lines
268 float closest_x1, closest_y1, dist1_sq;
269 float closest_x2, closest_y2, dist2_sq;
270
271 _closest_point_on_line(x, y, gpt->border, 0, separator_idx,
272 &closest_x1, &closest_y1, &dist1_sq);
273
274 _closest_point_on_line(x, y, gpt->border, separator_idx + 1, gpt->border_count,
275 &closest_x2, &closest_y2, &dist2_sq);
276
277 // Check if we have valid closest points to both border lines.
278 if(dist1_sq < FLT_MAX && dist2_sq < FLT_MAX)
279 {
280 // Vectors from mouse to each closest point
281 const float to_line1_x = closest_x1 - x;
282 const float to_line1_y = closest_y1 - y;
283 const float to_line2_x = closest_x2 - x;
284 const float to_line2_y = closest_y2 - y;
285
286 const float gradient_dx = gpt->points[2] - gpt->points[0];
287 const float gradient_dy = gpt->points[3] - gpt->points[1];
288 // Project these vectors onto the (unnormalized) gradient direction.
289 // Using the unnormalized direction preserves sign, so we avoid sqrt().
290 const float proj1 = to_line1_x * gradient_dx + to_line1_y * gradient_dy;
291 const float proj2 = to_line2_x * gradient_dx + to_line2_y * gradient_dy;
292
293 // Mouse is between lines if projections have opposite signs.
294 const gboolean between_lines = (proj1 * proj2 < 0.0f);
295 if(between_lines) *inside_border = 1;
296
297 // Rotation handle: accept hits on the border lines and slightly beyond.
298 const float min_dist_sq = fminf(dist1_sq, dist2_sq);
299 float handle_radius_sq = CLAMPF(gradient_len_sq * 0.125f, sqr_dist_mouse, sqr_dist_mouse * 5);
300
301 if(min_dist_sq <= handle_radius_sq)
302 *inside = 1;
303 }
304 }
305 }
306 }
307
308 // and we check if we are near_handle a segment (single continuous segment starting at gpt->points[3])
309 if(gpt->points && gpt->points_count > 3)
310 {
311 for(int i = 3; i < gpt->points_count; i++)
312 {
313 const float xx = gpt->points[i * 2];
314 const float yy = gpt->points[i * 2 + 1];
315
316 const float dx = x - xx;
317 const float dy = y - yy;
318 const float dd = sqf(dx) + sqf(dy);
319
320 min_dist = fminf(min_dist, dd);
321
322 // only one segment present: if any guide point is within the mouse distance,
323 // mark the (only) segment as near_handle (index 0)
324 if(dd < sqr_dist_mouse)
325 *near_handle = 0;
326 }
327 }
328
329 *dist = min_dist;
330}
331
332static void _gradient_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
333 float *node_x, float *node_y, void *user_data)
334{
335 if(node_x) *node_x = NAN;
336 if(node_y) *node_y = NAN;
337}
338
339static void _gradient_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
340 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
341 int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
342{
343 _gradient_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
344 inside_border, near_handle, inside_source, dist);
345}
346
347static void _gradient_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border,
348 int inside_source, void *user_data)
349{
350 if(inside)
351 {
352 mask_gui->border_selected = FALSE;
353 mask_gui->pivot_selected = TRUE;
354 }
355 else if(inside_border)
356 {
357 mask_gui->pivot_selected = FALSE;
358 }
359}
360
361static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int index)
362{
363 if(mask_gui) mask_gui->pivot_selected = FALSE;
364 return dt_masks_find_closest_handle_common(mask_form, mask_gui, index, 1,
365 NULL, NULL, _gradient_node_position_cb,
367}
368
369
370static int _init_extent(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
371{
373 increment, flow, _("extent: %3.2f%%"), 100.0f);
374 return 1;
375}
376
377static int _init_curvature(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
378{
380 increment, flow, _("Curvature: %3.2f%%"), 50.f);
381 return 1;
382}
383
384static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
385{
386 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
387 increment, flow, _("Opacity: %3.2f%%"), 100.f);
388 return 1;
389}
390
391static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
392{
393 dt_masks_get_set_conf_value_with_toast(form, "rotation", amount, 0.f, 360.f,
394 increment, flow, _("Rotation: %3.2f\302\260"), 1.0f);
395 return 1;
396}
397
399{
400 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
401 const dt_masks_anchor_gradient_t *gradient = (const dt_masks_anchor_gradient_t *)(form->points)->data;
402 if(IS_NULL_PTR(gradient)) return NAN;
403
404 switch(interaction)
405 {
407 return gradient->extent;
409 return gradient->curvature;
411 return gradient->rotation;
412 default:
413 return NAN;
414 }
415}
416
417static gboolean _gradient_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
418{
419 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
420 const dt_masks_anchor_gradient_t *gradient = (const dt_masks_anchor_gradient_t *)(form->points)->data;
421 if(IS_NULL_PTR(gradient)) return FALSE;
422 center[0] = gradient->center[0];
423 center[1] = gradient->center[1];
424 *area = gradient->extent; // pretend it's a rectangle of unit width
425 return TRUE;
426}
427
428static int _change_extent(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
429 int index, const float amount, const dt_masks_increment_t increment, const int flow);
430static int _change_curvature(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
431 int index, const float amount, const dt_masks_increment_t increment, const int flow);
432static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
433 int index, const float amount, const dt_masks_increment_t increment, const int flow);
434
436 dt_masks_increment_t increment, int flow,
437 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
438{
439 if(IS_NULL_PTR(form)) return NAN;
440 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
441 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
442 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
443 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
444
445 switch(interaction)
446 {
448 if(!_change_extent(form, gui, module, index, value, increment, flow)) return NAN;
449 return _gradient_get_interaction_value(form, interaction);
451 if(!_change_curvature(form, gui, module, index, value, increment, flow)) return NAN;
452 return _gradient_get_interaction_value(form, interaction);
454 if(!_change_rotation(form, gui, module, index, value, increment, flow)) return NAN;
455 return _gradient_get_interaction_value(form, interaction);
456 default:
457 return NAN;
458 }
459}
460
461static int _change_extent(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
462{
463 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
465 if(IS_NULL_PTR(gradient)) return 0;
466
467 gradient->extent = CLAMPF(dt_masks_apply_increment(gradient->extent, amount, increment, flow),
469
470 _init_extent(form, amount, increment, flow);
471
472 // we recreate the form points
473 dt_masks_gui_form_create(form, gui, index, module);
474
475 return 1;
476}
477
478static int _change_curvature(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
479{
480 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
482 if(IS_NULL_PTR(gradient)) return 0;
483
484 // Sanitize
485 // do not exceed upper limit of 2.0 and lower limit of -2.0
486 if(amount > 2.0f && (gradient->curvature > 2.0f ))
487 return 1;
488
489 const int node_hovered = gui->node_hovered;
490
491 // bending
492 if(node_hovered == -1 || node_hovered == 0)
493 {
494 gradient->curvature = dt_masks_apply_increment(gradient->curvature, amount, increment, flow);
495 }
496
497 _init_curvature(form, amount, DT_MASKS_INCREMENT_SCALE, flow);
498
499 // we recreate the form points
500 dt_masks_gui_form_create(form, gui, index, module);
501
502 return 1;
503}
504
505static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
506{
507 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
509 if(IS_NULL_PTR(gradient)) return 0;
510
511 // Rotation
512 int flow_increased = (flow > 1) ? (flow - 1) * 5 : flow;
513 gradient->rotation = dt_masks_apply_increment(gradient->rotation, amount, increment, flow_increased);
514
515 // Ensure the rotation value warps within the interval [0, 360)
516 if(gradient->rotation > 360.f) gradient->rotation = fmodf(gradient->rotation, 360.f);
517 else if(gradient->rotation < 0.f) gradient->rotation = 360.f - fmodf(-gradient->rotation, 360.f);
518
519 _init_rotation(form, amount, DT_MASKS_INCREMENT_OFFSET, flow);
520
521 // we recreate the form points
522 dt_masks_gui_form_create(form, gui, index, module);
523
524 return 1;
525}
526
527/* Shape handlers receive widget-space coordinates, while normalized output-image
528 * coordinates come from `gui->rel_pos` and absolute output-image
529 * coordinates come from `gui->pos`. */
530static int _gradient_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
531 uint32_t state, dt_masks_form_t *form, int parentid,
532 dt_masks_form_gui_t *gui, int index, dt_masks_interaction_t interaction)
533{
534
535
536
537 /* `state` is the caller's raw key state, kept for the callback signature: the property to
538 * act on was already resolved from it by dt_masks_scroll_get_interaction(). A gradient
539 * spells the two shared properties its own way -- SIZE is the fade extent, FADING is the
540 * curvature -- which is also how the context menu names them (masks_gui.c). */
541 if(gui->creation)
542 {
543 switch(interaction)
544 {
546 return _init_rotation(form, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
548 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
550 return _init_curvature(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
552 return _init_extent(form, (up ? +1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
553 default:
554 return 0;
555 }
556 }
557 else if(gui->form_selected || gui->seg_selected || gui->pivot_selected)
558 {
559 switch(interaction)
560 {
562 return _change_rotation(form, gui, module, index, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
564 return dt_masks_form_change_opacity(gui->dev, form, parentid, up, flow);
566 return _change_curvature(form, gui, module, index, (up ? +0.02f : -0.02f), DT_MASKS_INCREMENT_OFFSET, flow);
568 return _change_extent(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
569 default:
570 return 0;
571 }
572 }
573 return 0;
574}
575
576static int _gradient_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
577 double pressure, int which, int type, uint32_t state,
578 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
579{
580 if(gui->creation)
581 {
582 if(which == 1)
583 {
584 if(dt_modifier_is(state, GDK_SHIFT_MASK))
585 {
586 gui->gradient_toggling = TRUE;
587 return 1;
588 }
589
590 dt_iop_module_t *crea_module = gui->creation_module;
591 // we create the gradient
593 if(IS_NULL_PTR(gradient)) return 0;
594 _gradient_init_new(gui, gradient);
595
596 form->points = g_list_append(form->points, gradient);
597 dt_masks_gui_form_save_creation(gui->dev, crea_module, form, gui);
598
599 return 1;
600 }
601 }
602
603 else if(which == 1)
604 {
605 // double-click resets curvature
606 if(type == GDK_2BUTTON_PRESS)
607 {
608 _change_curvature(form, gui, module, index, 0, DT_MASKS_INCREMENT_ABSOLUTE, 0);
609 dt_masks_gui_form_create(form, gui, index, module);
610 return 1;
611 }
612
613 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
614 if(IS_NULL_PTR(gpt)) return 0;
615
616 else if((gui->form_selected || gui->seg_hovered >= 0 || gui->seg_selected)
617 && gui->edit_mode == DT_MASKS_EDIT_FULL)
618 {
619 // we start the form dragging or rotating
620 if(gui->pivot_selected)
621 gui->form_rotating = TRUE;
622 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
623 gui->border_toggling = TRUE;
624 else if(gui->seg_hovered >= 0 || gui->seg_selected)
625 gui->form_selected = TRUE;
626
627 if(gui->form_rotating)
628 {
629 gui->delta[0] = gui->pos[0];
630 gui->delta[1] = gui->pos[1];
631 }
632 else
633 {
634 gui->delta[0] = gpt->points[0] - gui->pos[0];
635 gui->delta[1] = gpt->points[1] - gui->pos[1];
636 }
637
638 return 1;
639 }
640 }
641
642 return 0;
643}
644
645static int _gradient_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
646 uint32_t state, dt_masks_form_t *form, int parentid,
647 dt_masks_form_gui_t *gui, int index)
648{
649
650
651
652
653
654
655 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
656
657 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
658 {
659 // we end the form dragging
660 return 1;
661 }
662
663 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
664 {
665 // we end the form rotating
666 gui->form_rotating = FALSE;
667 return 1;
668 }
669 else if(gui->gradient_toggling)
670 {
671 // we get the gradient
672 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
673 if(IS_NULL_PTR(gradient)) return 0;
674 // we end the gradient toggling
676
677 // toggle transition type of gradient
680 else
682
683 dt_conf_set_int("plugins/darkroom/masks/gradient/state", gradient->state);
684
685 // we recreate the form points
686 dt_masks_gui_form_create(form, gui, index, module);
687
688 // we save the new parameters
689
690 return 1;
691 }
692 return 0;
693}
694
695static int _gradient_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
696 int parentid, dt_masks_form_gui_t *gui, int index)
697{
698 return 0;
699}
700
701static int _gradient_events_mouse_moved(struct dt_iop_module_t *module, double x, double y,
702 double pressure, int which, dt_masks_form_t *form, int parentid,
703 dt_masks_form_gui_t *gui, int index)
704{
705 if(gui->creation)
706 {
707 // Let the cursor motion be redrawn as it moves in GUI
708 return 1;
709 }
710
711 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
712
713 // we get the gradient
714 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
715 if(IS_NULL_PTR(gradient)) return 0;
716
717 // we need the reference points
718 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
719 if(IS_NULL_PTR(gpt)) return 0;
720
721 if(gui->form_dragging)
722 {
723 // we change the center value
724 float pts[2];
725 dt_masks_gui_delta_to_raw_norm(gui->dev, gui, pts);
726
727 gradient->center[0] = pts[0];
728 gradient->center[1] = pts[1];
729
730 // we recreate the form points
731 dt_masks_gui_form_create(form, gui, index, module);
732
733 return 1;
734 }
735
736 //rotation with the mouse
737 if(gui->form_rotating)
738 {
739 const float origin_point[2] = { gpt->points[0], gpt->points[1] };
740 const float angle = - dt_masks_rotate_with_anchor(gui->dev, gui->pos, origin_point, gui);
741 _change_rotation(form, gui, module, index, angle , DT_MASKS_INCREMENT_OFFSET, 1);
742
743 // we recreate the form points
744 dt_masks_gui_form_create(form, gui, index, module);
745
746 return 1;
747 }
748 return 0;
749}
750
751// check if (x,y) lies within reasonable limits relative to image frame
752static inline gboolean _gradient_is_canonical(const float x, const float y, const float wd, const float ht)
753{
754 return (isnormal(x) && isnormal(y) && (x >= -wd) && (x <= 2 * wd) && (y >= -ht) && (y <= 2 * ht)) ? TRUE : FALSE;
755}
756
765static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature,
766 float **points, int *points_count)
767{
768 *points = NULL;
769 *points_count = 0;
770
772 const float wd = geometry.raw_width;
773 const float ht = geometry.raw_height;
774 if(!isfinite(wd) || !isfinite(ht) || wd <= 0.0f || ht <= 0.0f) return 1;
775
776 const float scale = dt_fast_hypotf(wd, ht);
777 const float distance = 0.1f * fminf(wd, ht);
778
779 const float v = (-rotation / 180.0f) * M_PI;
780 const float cosv = cosf(v);
781 const float sinv = sinf(v);
782
783 const int count = dt_fast_hypotf(wd, ht) + 3;
784 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * count, 0);
785 if(IS_NULL_PTR(*points)) return 1;
786
787 // we set the anchor point
788 float center[2] = { x, y };
790 const float center_x = center[0];
791 const float center_y = center[1];
792 (*points)[0] = center_x;
793 (*points)[1] = center_y;
794
795 // we set the pivot points
796 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
797 const float x1 = center[0] + distance * cosf(v1);
798 const float y1 = center[1] + distance * sinf(v1);
799 (*points)[2] = x1;
800 (*points)[3] = y1;
801 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
802 const float x2 = center[0] + distance * cosf(v2);
803 const float y2 = center[1] + distance * sinf(v2);
804 (*points)[4] = x2;
805 (*points)[5] = y2;
806
807 const int nthreads = dt_get_num_openmp_threads();
808 size_t c_padded_size;
809 uint32_t *pts_count = dt_pixelpipe_cache_calloc_perthread(1, sizeof(uint32_t), &c_padded_size);
810 size_t pts_padded_size;
811 float *const restrict pts = dt_pixelpipe_cache_alloc_perthread_float((size_t)2 * count, &pts_padded_size);
812 if(IS_NULL_PTR(pts_count) || IS_NULL_PTR(pts))
813 {
817 *points = NULL;
818 *points_count = 0;
819 return 1;
820 }
821
822 // we set the line point
823 const float xstart = fabsf(curvature) > 1.0f ? -sqrtf(1.0f / fabsf(curvature)) : -1.0f;
824 const float xdelta = -2.0f * xstart / (count - 3);
825
826// gboolean in_frame = FALSE;
827 __OMP_PARALLEL_FOR__(if(count > 100) num_threads(nthreads))
828 for(int i = 3; i < count; i++)
829 {
830 const float xi = xstart + (i - 3) * xdelta;
831 const float yi = curvature * xi * xi;
832 const float xii = (cosv * xi + sinv * yi) * scale;
833 const float yii = (sinv * xi - cosv * yi) * scale;
834 const float xiii = xii + center_x;
835 const float yiii = yii + center_y;
836
837 // don't generate guide points if they extend too far beyond the image frame;
838 // this is to avoid that modules like lens correction fail on out of range coordinates
839 if(!(xiii < -wd || xiii > 2 * wd || yiii < -ht || yiii > 2 * ht))
840 {
841 uint32_t *tcount = dt_get_perthread(pts_count, c_padded_size);
842 float *const tpts = dt_get_perthread(pts, pts_padded_size);
843 tpts[*tcount * 2] = xiii;
844 tpts[*tcount * 2 + 1] = yiii;
845 (*tcount)++;
846 }
847 }
848
849 *points_count = 3;
850 for(int thread = 0; thread < nthreads; thread++)
851 {
852 const uint32_t tcount = *(uint32_t *)dt_get_bythread(pts_count, c_padded_size, thread);
853 const float *const tpts = dt_get_bythread(pts, pts_padded_size, thread);
854 // Merge only the retained in-frame samples. The source loop has at most
855 // count - 3 samples, so the three metadata points leave exactly that room.
856 for(uint32_t k = 0; k < tcount && *points_count < count; k++)
857 {
858 (*points)[(*points_count) * 2] = tpts[k * 2];
859 (*points)[(*points_count) * 2 + 1] = tpts[k * 2 + 1];
860 (*points_count)++;
861 }
862 }
863
866
867 // and we transform them with all distorted modules
868 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
869 {
871 *points = NULL;
872 *points_count = 0;
873 return 1;
874 }
875
876 return 0;
877}
878
879// Helper function to copy points, skipping the first 3 metadata points
880static void _copy_points(float *dest, const float *src, int count, int *k)
881{
882 for(int i = 3; i < count; i++, (*k)++)
883 {
884 dest[(*k) * 2] = src[i * 2];
885 dest[(*k) * 2 + 1] = src[i * 2 + 1];
886 }
887}
888
889static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance,
890 float curvature, float **points, int *points_count)
891{
892 *points = NULL;
893 *points_count = 0;
894 distance = CLAMPF(distance, extent_MIN, extent_MAX);
895
896 // Get border curve dimensions and scaling
898 const float wd = geometry.raw_width;
899 const float ht = geometry.raw_height;
900 const float scale = dt_fast_hypotf(wd, ht);
901
902 // Calculate perpendicular offsets (±90 degrees from rotation)
903 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
904 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
905
906 // Generate offset positions for both curves
907 float center[2] = { x, y };
909 float offsets[4] = { center[0] + distance * scale * cosf(v1),
910 center[1] + distance * scale * sinf(v1),
911 center[0] + distance * scale * cosf(v2),
912 center[1] + distance * scale * sinf(v2) };
914 const float x1 = offsets[0];
915 const float y1 = offsets[1];
916 const float x2 = offsets[2];
917 const float y2 = offsets[3];
918
919 // Get points for both curves
920 float *points1 = NULL, *points2 = NULL;
921 int points_count1 = 0, points_count2 = 0;
922 const int err1 = _gradient_get_points(dev, x1, y1, rotation, curvature, &points1, &points_count1);
923 const int err2 = _gradient_get_points(dev, x2, y2, rotation, curvature, &points2, &points_count2);
924
925 // Check which curves are valid (need more than 4 points: 3 metadata + at least 1 data)
926 const gboolean valid1 = (err1 == 0) && points_count1 > 4;
927 const gboolean valid2 = (err2 == 0) && points_count2 > 4;
928
929 int err = 1;
930
931 if(valid1 && valid2)
932 {
933 // Both curves valid - combine them with INFINITY separator
934 const int total_points = (points_count1 - 3) + (points_count2 - 3) + 1;
935 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * total_points, 0);
936 if(IS_NULL_PTR(*points)) goto cleanup;
937
938 *points_count = total_points;
939 int k = 0;
940
941 _copy_points(*points, points1, points_count1, &k);
942 (*points)[k * 2] = (*points)[k * 2 + 1] = INFINITY; // Separator
943 k++;
944 _copy_points(*points, points2, points_count2, &k);
945 err = 0;
946 }
947 else if(valid1)
948 {
949 // Only first curve valid
950 *points_count = points_count1 - 3;
951 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (*points_count), 0);
952 if(IS_NULL_PTR(*points)) goto cleanup;
953
954 int k = 0;
955 _copy_points(*points, points1, points_count1, &k);
956 err = 0;
957 }
958 else if(valid2)
959 {
960 // Only second curve valid
961 *points_count = points_count2 - 3;
962 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (*points_count), 0);
963 if(IS_NULL_PTR(*points)) goto cleanup;
964
965 int k = 0;
966 _copy_points(*points, points2, points_count2, &k);
967 err = 0;
968 }
969
970cleanup:
973 return err;
974}
975
976static void _gradient_draw_shape(struct dt_develop_t *dev, cairo_t *cr, const float *pts_line, const int pts_line_count, const int nb, const gboolean border, const gboolean source,
977 const dt_masks_skip_range_t *skips, const int skip_count)
978{
979 /* A gradient has no self-intersections, so it never carries an exclusion list -- these are the
980 * shared shape_draw_function_t signature, not something to honour here. */
981 (void)dev; (void)nb; (void)source; (void)skips; (void)skip_count;
982
983 // safeguard in case of malformed arrays of points
984 if(border && pts_line_count <= 3) return;
985 if(!border && pts_line_count <= 4) return;
986
987 const float *points = (border) ? pts_line : pts_line + 6;
988 const int points_count = (border) ? pts_line_count : pts_line_count - 3;
989
991 const float wd = geometry.raw_width;
992 const float ht = geometry.raw_height;
993
994 /* Decimate each segment to device resolution, as the brush does; see dt_draw_min_emit_step().
995 * The last point of a segment is always emitted so an open guide line keeps its true end. */
996 const double min_step = dt_draw_min_emit_step(cr);
997 const double min_step2 = min_step * min_step;
998 int i = 0;
999 while(i < points_count)
1000 {
1001 const float px = points[i * 2];
1002 const float py = points[i * 2 + 1];
1003
1004 if(!isnormal(px) || !_gradient_is_canonical(px, py, wd, ht))
1005 {
1006 i++;
1007 continue;
1008 }
1009
1010 cairo_move_to(cr, px, py);
1011 double last_x = px, last_y = py;
1012 i++;
1013
1014 // continue the current segment until a non-normal or out-of-range point
1015 while(i < points_count)
1016 {
1017 const double qx = points[i * 2];
1018 const double qy = points[i * 2 + 1];
1019 if(!isnormal((float)qx) || !_gradient_is_canonical((float)qx, (float)qy, wd, ht)) break;
1020 const double dx = qx - last_x, dy = qy - last_y;
1021 const gboolean is_last = (i + 1 >= points_count) || !isnormal(points[(i + 1) * 2])
1022 || !_gradient_is_canonical(points[(i + 1) * 2], points[(i + 1) * 2 + 1], wd, ht);
1023 if(!is_last && (dx * dx + dy * dy) < min_step2) { i++; continue; }
1024 cairo_line_to(cr, qx, qy);
1025 last_x = qx; last_y = qy;
1026 i++;
1027 }
1028 }
1029}
1030
1031static void _gradient_draw_arrow(cairo_t *cr, const gboolean selected, const gboolean pivot_selected, const gboolean is_rotating,
1032 const float zoom_scale, float *pts, int pts_count)
1033{
1034 if(pts_count < 3) return;
1035
1036 const float anchor_x = pts[0];
1037 const float anchor_y = pts[1];
1038 const float pivot_end_x = pts[2];
1039 const float pivot_end_y = pts[3];
1040 const float pivot_start_x = pts[4];
1041 const float pivot_start_y = pts[5];
1042
1043 // draw a dotted line across the gradient for better visibility while dragging
1044 if(is_rotating)
1045 {
1046 // extend the axis line beyond the pivot points
1047 const float scale = 1 / zoom_scale;
1048 const float dx = pivot_end_x - pivot_start_x;
1049 const float dy = pivot_end_y - pivot_start_y;
1050
1051 const float new_x1 = pivot_start_x - (dx * scale * 0.5f);
1052 const float new_y1 = pivot_start_y - (dy * scale * 0.5f);
1053 const float new_x2 = pivot_end_x + (dx * scale * 0.5f);
1054 const float new_y2 = pivot_end_y + (dy * scale * 0.5f);
1055 cairo_move_to(cr, new_x1, new_y1);
1056 cairo_line_to(cr, new_x2, new_y2);
1057
1058 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, FALSE, zoom_scale, CAIRO_LINE_CAP_ROUND);
1059 }
1060
1061 // always draw arrow to clearly display the direction
1062 {
1063 // size & width of the arrow
1064 const float arrow_angle = 0.25f;
1065 const float arrow_length = (DT_DRAW_SCALE_ARROW * 2) / zoom_scale;
1066
1067 // compute direction from anchor toward pivot_end and build an arrow
1068 const float dx = pivot_end_x - anchor_x;
1069 const float dy = pivot_end_y - anchor_y;
1070 const float angle_dir = atan2f(dy, dx); // direction the arrow should point to
1071
1072 // tip of the arrow (ahead of anchor along angle_dir)
1073 const float tip_x = anchor_x + arrow_length * cosf(angle_dir);
1074 const float tip_y = anchor_y + arrow_length * sinf(angle_dir);
1075
1076 // half width of the arrow head
1077 const float half_w = arrow_length * tanf(arrow_angle);
1078
1079 // perpendicular vector to the direction (unit)
1080 const float nx = -sinf(angle_dir);
1081 const float ny = cosf(angle_dir);
1082
1083 // two corner points of the arrow base, centered on (anchor_x, anchor_y)
1084 const float arrow_x1 = anchor_x + nx * half_w;
1085 const float arrow_y1 = anchor_y + ny * half_w;
1086 const float arrow_x2 = anchor_x - nx * half_w;
1087 const float arrow_y2 = anchor_y - ny * half_w;
1088
1089 // we will draw the triangle as tip -> base1 -> base2
1090 cairo_move_to(cr, tip_x, tip_y);
1091 cairo_line_to(cr, arrow_x1, arrow_y1);
1092 cairo_line_to(cr, arrow_x2, arrow_y2);
1093 cairo_close_path(cr);
1094
1096 cairo_fill_preserve(cr);
1097 double line_width = pivot_selected ? (DT_DRAW_SIZE_LINE_SELECTED / zoom_scale) : (DT_DRAW_SIZE_LINE / zoom_scale);
1098 cairo_set_line_width(cr, line_width);
1100 cairo_stroke(cr);
1101 }
1102
1103 // draw the origin anchor point on top of everything
1104 dt_draw_node(cr, FALSE, FALSE, pivot_selected, zoom_scale, anchor_x, anchor_y);
1105}
1106
1107static void _gradient_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int nb)
1108{
1109 // preview gradient creation
1110 if(gui->creation)
1111 {
1113 if(_gradient_get_creation_preview(gui, &preview)) return;
1114
1115 dt_masks_draw_preview_shape(gui->dev, cr, zoom_scale, nb, preview.points, preview.points_count,
1116 preview.border, preview.border_count,
1117 &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND,
1118 CAIRO_LINE_CAP_ROUND, FALSE, FALSE);
1119 _gradient_draw_arrow(cr, FALSE, FALSE, gui->form_rotating, zoom_scale, preview.points, preview.points_count);
1121
1122 return;
1123 }
1124
1125 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1126 if(IS_NULL_PTR(gpt)) return;
1127
1128 const gboolean seg_selected = (gui->group_selected == index) && gui->seg_selected;
1129 const gboolean all_selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1130 // draw main line
1131 if(gpt->points && gpt->points_count > 0)
1132 dt_draw_shape_lines(gui->dev, DT_MASKS_NO_DASH, FALSE, cr, nb, (seg_selected), zoom_scale, gpt->points,
1133 gpt->points_count, &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND, NULL, 0);
1134 // draw borders
1135 if(gui->group_selected == index)
1136 {
1137 if(gpt->border && gpt->border_count > 0)
1138 dt_draw_shape_lines(gui->dev, DT_MASKS_DASH_STICK, FALSE, cr, nb, (gui->border_selected), zoom_scale, gpt->border,
1139 gpt->border_count, &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND, NULL, 0);
1140 }
1141
1142 if(gpt->points && gpt->points_count >= 3)
1143 _gradient_draw_arrow(cr, (seg_selected || all_selected), ((gui->group_selected == index) && gui->pivot_selected),
1144 gui->form_rotating, zoom_scale, gpt->points, gpt->points_count);
1145}
1146
1148 float **points, int *points_count,
1149 float **border, int *border_count,
1150 dt_masks_skip_range_t **border_skips, int *border_skip_count,
1151 int source,
1152 const dt_iop_module_t *module)
1153{
1154 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
1155 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
1156
1157 // unused arg, keep compiler from complaining
1158 // No geometry: an empty outline is the correct result here, not a failure. See the circle.
1159 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1161 if(IS_NULL_PTR(gradient)) return DT_MASKS_RASTER_EMPTY;
1162 if(_gradient_get_points(dev, gradient->center[0], gradient->center[1], gradient->rotation, gradient->curvature,
1163 points, points_count) != 0)
1164 return DT_MASKS_RASTER_ERROR;
1165 if(border)
1167 _gradient_get_pts_border(dev, gradient->center[0], gradient->center[1],
1168 gradient->rotation, gradient->extent, gradient->curvature,
1169 border, border_count));
1170 return DT_MASKS_RASTER_OK;
1171}
1172
1174 const dt_dev_pixelpipe_iop_t *const piece,
1175 dt_masks_form_t *const form,
1176 int *width, int *height, int *posx, int *posy)
1177{
1178 *width = 0;
1179 *height = 0;
1180 *posx = 0;
1181 *posy = 0;
1182 const float wd = pipe->iwidth, ht = pipe->iheight;
1183
1184 float points[8] = { 0.0f, 0.0f, wd, 0.0f, wd, ht, 0.0f, ht };
1185
1186 // and we transform them with all distorted modules
1188 return DT_MASKS_RASTER_ERROR;
1189
1190 // now we search min and max
1191 float xmin = 0.0f, xmax = 0.0f, ymin = 0.0f, ymax = 0.0f;
1192 xmin = ymin = FLT_MAX;
1193 xmax = ymax = FLT_MIN;
1194 for(int i = 0; i < 4; i++)
1195 {
1196 xmin = fminf(points[i * 2], xmin);
1197 xmax = fmaxf(points[i * 2], xmax);
1198 ymin = fminf(points[i * 2 + 1], ymin);
1199 ymax = fmaxf(points[i * 2 + 1], ymax);
1200 }
1201
1202 // and we set values
1203 *posx = xmin;
1204 *posy = ymin;
1205 *width = (xmax - xmin);
1206 *height = (ymax - ymin);
1207 return DT_MASKS_RASTER_OK;
1208}
1209
1210// caller needs to make sure that input remains within bounds
1211static inline float dt_gradient_lookup(const float *lut, const float i)
1212{
1213 const int bin0 = i;
1214 const int bin1 = i + 1;
1215 const float f = i - bin0;
1216 return lut[bin1] * f + lut[bin0] * (1.0f - f);
1217}
1218
1220 const dt_dev_pixelpipe_iop_t *const piece,
1221 dt_masks_form_t *const form,
1222 float **buffer, int *width, int *height, int *posx, int *posy)
1223{
1224 *buffer = NULL;
1225 *width = 0;
1226 *height = 0;
1227 *posx = 0;
1228 *posy = 0;
1229 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1230 double start2 = 0.0;
1232 // we get the area
1233 const dt_masks_raster_result_t area = _gradient_get_area(module, pipe, piece, form, width, height, posx, posy);
1234 if(area != DT_MASKS_RASTER_OK) return area;
1235
1237 {
1238 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient area took %0.04f sec\n", form->name,
1239 dt_get_wtime() - start2);
1240 start2 = dt_get_wtime();
1241 }
1242
1243 // we get the gradient values
1244 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
1245 if(IS_NULL_PTR(gradient)) return DT_MASKS_RASTER_EMPTY;
1246 // we create a buffer of grid points for later interpolation. mainly in order to reduce memory footprint
1247 const int w = *width;
1248 const int h = *height;
1249 const int px = *posx;
1250 const int py = *posy;
1251 const int grid = 8;
1252 const int gw = (w + grid - 1) / grid + 1;
1253 const int gh = (h + grid - 1) / grid + 1;
1254
1255 // this path works in unscaled coordinates, which is iscale == 1 (an exact multiplication)
1256 const dt_masks_sample_grid_t sample_grid
1257 = { .x0 = 0, .y0 = 0, .width = gw, .height = gh,
1258 .step = grid, .px = px, .py = py, .iscale = 1.0f };
1259 float *points
1260 = dt_masks_sample_grid_backtransform(pipe, module->iop_order, &sample_grid, "gradient", form->name);
1261 if(IS_NULL_PTR(points)) return DT_MASKS_RASTER_ERROR;
1262 start2 = dt_get_wtime();
1263
1264 // we calculate the mask at grid points and recycle point buffer to store results
1265 const float wd = pipe->iwidth;
1266 const float ht = pipe->iheight;
1267 const float hwscale = 1.0f / dt_fast_hypotf(wd, ht);
1268 const float ihwscale = 1.0f / hwscale;
1269 const float v = (-gradient->rotation / 180.0f) * M_PI;
1270 const float sinv = sinf(v);
1271 const float cosv = cosf(v);
1272 const float xoffset = cosv * gradient->center[0] * wd + sinv * gradient->center[1] * ht;
1273 const float yoffset = sinv * gradient->center[0] * wd - cosv * gradient->center[1] * ht;
1274 const float extent = fmaxf(gradient->extent, 0.001f);
1275 const float normf = 1.0f / extent;
1276 const float curvature = gradient->curvature;
1277 const dt_masks_gradient_states_t state = gradient->state;
1278
1279 const int lutmax = ceilf(4 * extent * ihwscale);
1280 const int lutsize = 2 * lutmax + 2;
1282 if(IS_NULL_PTR(lut))
1283 {
1285 return DT_MASKS_RASTER_ERROR;
1286 }
1287 __OMP_PARALLEL_FOR_SIMD__(if(lutsize > 1000) aligned(lut : 64))
1288 for(int n = 0; n < lutsize; n++)
1289 {
1290 const float distance = (n - lutmax) * hwscale;
1291 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / extent));
1292 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1293 }
1294
1295 // center lut around zero
1296 float *clut = lut + lutmax;
1297
1298
1299 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1300 for(int j = 0; j < gh; j++)
1301 {
1302 for(int i = 0; i < gw; i++)
1303 {
1304 const float x = points[(j * gw + i) * 2];
1305 const float y = points[(j * gw + i) * 2 + 1];
1306
1307 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1308 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1309
1310 const float distance = y0 - curvature * x0 * x0;
1311
1312 points[(j * gw + i) * 2] = (distance <= -4.0f * extent) ? 0.0f :
1313 ((distance >= 4.0f * extent) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1314 }
1315 }
1316
1318
1319 // we allocate the buffer
1320 float *const bufptr = *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
1321 if(IS_NULL_PTR(*buffer))
1322 {
1324 return DT_MASKS_RASTER_ERROR;
1325 }
1326
1327 dt_masks_sample_grid_interpolate(points, &sample_grid, bufptr, w, h, NULL, NULL);
1328
1330
1332 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1333 dt_get_wtime() - start2);
1334
1335 return DT_MASKS_RASTER_OK;
1336}
1337
1338
1340 const dt_dev_pixelpipe_iop_t *const piece,
1341 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer,
1342 dt_iop_roi_t *touched)
1343{
1344 dt_masks_touched_none(touched);
1345 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
1346 double start2 = 0.0;
1348 // we get the gradient values
1349 const dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)(form->points->data);
1350 if(IS_NULL_PTR(gradient)) return DT_MASKS_RASTER_EMPTY;
1351
1352 // we create a buffer of grid points for later interpolation. mainly in order to reduce memory footprint
1353 const int w = roi->width;
1354 const int h = roi->height;
1355 const int px = roi->x;
1356 const int py = roi->y;
1357 const float iscale = 1.0f / roi->scale;
1358 const int grid = CLAMP((10.0f*roi->scale + 2.0f) / 3.0f, 1, 4);
1359 const int gw = (w + grid - 1) / grid + 1;
1360 const int gh = (h + grid - 1) / grid + 1;
1361
1362 const dt_masks_sample_grid_t sample_grid
1363 = { .x0 = 0, .y0 = 0, .width = gw, .height = gh,
1364 .step = grid, .px = px, .py = py, .iscale = iscale };
1365 float *points
1366 = dt_masks_sample_grid_backtransform(pipe, module->iop_order, &sample_grid, "gradient", form->name);
1367 if(IS_NULL_PTR(points)) return DT_MASKS_RASTER_ERROR;
1368 start2 = dt_get_wtime();
1369
1370 // we calculate the mask at grid points and recycle point buffer to store results
1371 const float wd = pipe->iwidth;
1372 const float ht = pipe->iheight;
1373 const float hwscale = 1.0f / dt_fast_hypotf(wd, ht);
1374 const float ihwscale = 1.0f / hwscale;
1375 const float v = (-gradient->rotation / 180.0f) * M_PI;
1376 const float sinv = sinf(v);
1377 const float cosv = cosf(v);
1378 const float xoffset = cosv * gradient->center[0] * wd + sinv * gradient->center[1] * ht;
1379 const float yoffset = sinv * gradient->center[0] * wd - cosv * gradient->center[1] * ht;
1380 const float extent = fmaxf(gradient->extent, 0.001f);
1381 const float normf = 1.0f / extent;
1382 const float curvature = gradient->curvature;
1383 const dt_masks_gradient_states_t state = gradient->state;
1384
1385 const int lutmax = ceilf(4 * extent * ihwscale);
1386 const int lutsize = 2 * lutmax + 2;
1388 if(IS_NULL_PTR(lut))
1389 {
1391 return DT_MASKS_RASTER_ERROR;
1392 }
1393 __OMP_PARALLEL_FOR_SIMD__(if(lutsize > 1000) aligned(lut : 64))
1394 for(int n = 0; n < lutsize; n++)
1395 {
1396 const float distance = (n - lutmax) * hwscale;
1397 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / extent));
1398 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1399 }
1400
1401 // center lut around zero
1402 float *clut = lut + lutmax;
1403
1404 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1405 for(int j = 0; j < gh; j++)
1406 {
1407 for(int i = 0; i < gw; i++)
1408 {
1409 const size_t index = (size_t)j * gw + i;
1410 const float x = points[index * 2];
1411 const float y = points[index * 2 + 1];
1412
1413 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1414 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1415
1416 const float distance = y0 - curvature * x0 * x0;
1417
1418 points[index * 2] = (distance <= -4.0f * extent) ? 0.0f : ((distance >= 4.0f * extent) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1419 }
1420 }
1421
1423
1424 dt_masks_sample_grid_interpolate(points, &sample_grid, buffer, w, h, NULL, NULL);
1425
1427
1428 // A gradient is non-zero over the whole ROI: there is no box smaller than the buffer.
1429 dt_masks_touched_full(touched, w, h);
1430
1432 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1433 dt_get_wtime() - start2);
1434
1435 return DT_MASKS_RASTER_OK;
1436}
1437
1439{
1440 // we always want to start with no curvature
1441 dt_conf_set_float("plugins/darkroom/masks/gradient/curvature", 0.0f);
1442}
1443
1444static void _gradient_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1445{
1446 snprintf(form->name, sizeof(form->name), _("gradient #%d"), (int)nb);
1447}
1448
1449static void _gradient_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1450 char *const restrict msgbuf, const size_t msgbuf_len)
1451{
1452 // Only gestures that cannot be discovered any other way. What the wheel does is the user's
1453 // own mapping now (masks_gui.h), shown in the Drawn tab, so it is not repeated here.
1454 if(gui->creation)
1455 g_strlcat(msgbuf, _("<b>Linear/sigmoidal fade</b>: Shift+Click"), msgbuf_len);
1456 // Hovering the fade lines arms the rotation pivot, so there dragging rotates instead of moving.
1457 else if(gui->pivot_selected)
1458 g_strlcat(msgbuf, _("<b>Rotate</b>: Drag"), msgbuf_len);
1459 else if(gui->form_selected || gui->seg_selected || gui->seg_hovered >= 0)
1460 g_strlcat(msgbuf, _("<b>Move</b>: Drag, <b>Reset curvature</b>: Double-click"), msgbuf_len);
1461}
1462
1464{
1465 // unused arg, keep compiler from complaining
1467}
1468
1469// The function table for gradients. This must be public, i.e. no "static" keyword.
1472 .sanitize_config = _gradient_sanitize_config,
1473 .set_form_name = _gradient_set_form_name,
1474 .set_hint_message = _gradient_set_hint_message,
1475 .duplicate_points = _gradient_duplicate_points,
1476 .get_distance = _gradient_get_distance,
1477 .get_points_border = _gradient_get_points_border,
1478 .get_mask = _gradient_get_mask,
1479 .get_mask_roi = _gradient_get_mask_roi,
1480 .get_area = _gradient_get_area,
1481 .get_gravity_center = _gradient_get_gravity_center,
1482 .get_interaction_value = _gradient_get_interaction_value,
1483 .set_interaction_value = _gradient_set_interaction_value,
1484 .update_hover = _find_closest_handle,
1485 .mouse_moved = _gradient_events_mouse_moved,
1486 .mouse_scrolled = _gradient_events_mouse_scrolled,
1487 .button_pressed = _gradient_events_button_pressed,
1488 .button_released = _gradient_events_button_released,
1489 .key_pressed = _gradient_events_key_pressed,
1490 .post_expose = _gradient_events_post_expose,
1491 .draw_shape = _gradient_draw_shape
1492};
1493
1494// clang-format off
1495// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1496// vim: shiftwidth=2 expandtab tabstop=2 cindent
1497// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1498// clang-format on
Handle default and user-set shortcuts (accelerators)
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const float x
const float f
const float *const lut
const int t
const float const int lutsize
const float v
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_int(const char *name, int val)
int dt_get_num_openmp_threads(void)
Number of OpenMP threads the application decided to use.
Definition darktable.c:518
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1765
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1255
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:1797
void dt_dev_coordinates_raw_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1237
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
GtkWidget * geometry
its size, under the preview
GtkWidget * preview
what the selected row actually captures
GHashTable * selected
set of checked row labels, mirrored to conf on every change
#define DT_DRAW_SIZE_LINE
Definition draw.h:83
static void dt_draw_stroke_line(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const gboolean selected, const float zoom_scale, const cairo_line_cap_t line_cap)
Definition draw.h:952
#define DT_DRAW_SIZE_LINE_SELECTED
Definition draw.h:84
static double dt_draw_min_emit_step(cairo_t *cr)
Stroke a line with style.
Definition draw.h:931
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:147
@ DT_MASKS_DASH_STICK
Definition draw.h:135
@ DT_MASKS_DASH_ROUND
Definition draw.h:136
@ DT_MASKS_NO_DASH
Definition draw.h:134
#define DT_DRAW_SCALE_ARROW
Definition draw.h:91
static void dt_draw_shape_lines(struct dt_develop_t *dev, const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap, const struct dt_masks_skip_range_t *skips, const int skip_count)
Draw the lines of a mask shape.
Definition draw.h:831
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:669
static void _gradient_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int nb)
Definition gradient.c:1107
static float _gradient_set_interaction_value(dt_masks_form_t *form, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
Definition gradient.c:435
static int _gradient_events_button_released(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition gradient.c:645
static float _gradient_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition gradient.c:398
static void _closest_point_on_segment(float px, float py, float x1, float y1, float x2, float y2, float *closest_x, float *closest_y, float *distance_sq)
Definition gradient.c:83
static void _copy_points(float *dest, const float *src, int count, int *k)
Definition gradient.c:880
static void _gradient_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index, float *node_x, float *node_y, void *user_data)
Definition gradient.c:332
static float dt_gradient_lookup(const float *lut, const float i)
Definition gradient.c:1211
#define CURVATURE_MIN
Definition gradient.c:50
static int _gradient_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition gradient.c:701
#define extent_MAX
Definition gradient.c:49
static int _gradient_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition gradient.c:695
static int _gradient_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure, int which, int type, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition gradient.c:576
static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance, float curvature, float **points, int *points_count)
Definition gradient.c:889
static int _find_border_separator(const float *border, int count)
Definition gradient.c:57
static int _change_extent(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:461
static int _change_curvature(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:478
static void _gradient_get_creation_values(dt_masks_gradient_creation_values_t *values)
Definition gradient.c:193
static void _closest_point_on_line(float px, float py, const float *border, int start_idx, int end_idx, float *closest_x, float *closest_y, float *min_distance_sq)
Definition gradient.c:109
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:384
static dt_masks_raster_result_t _gradient_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, int *width, int *height, int *posx, int *posy)
Definition gradient.c:1173
static void _gradient_draw_arrow(cairo_t *cr, const gboolean selected, const gboolean pivot_selected, const gboolean is_rotating, const float zoom_scale, float *pts, int pts_count)
Definition gradient.c:1031
static void _gradient_distance_cb(float pointer_x, float pointer_y, float cursor_radius, dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside, int *inside_border, int *near_handle, int *inside_source, float *dist, void *user_data)
Definition gradient.c:339
static dt_masks_raster_result_t _gradient_get_points_border(dt_develop_t *dev, dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int source, const dt_iop_module_t *module)
Definition gradient.c:1147
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int index)
Definition gradient.c:361
static int _init_extent(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:370
static int _gradient_get_creation_preview(dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
Definition gradient.c:220
static dt_masks_raster_result_t _gradient_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition gradient.c:1219
static int _init_curvature(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:377
static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature, float **points, int *points_count)
Build the distorted display polyline for a gradient mask.
Definition gradient.c:765
static gboolean _gradient_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
Definition gradient.c:417
static void _gradient_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition gradient.c:1444
static int _gradient_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow, uint32_t state, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index, dt_masks_interaction_t interaction)
Definition gradient.c:530
static float _gradient_get_border_len_sq(const dt_masks_form_gui_points_t *gpt)
Definition gradient.c:179
static void _gradient_init_new(dt_masks_form_gui_t *gui, dt_masks_anchor_gradient_t *gradient)
Definition gradient.c:203
static void _gradient_draw_shape(struct dt_develop_t *dev, cairo_t *cr, const float *pts_line, const int pts_line_count, const int nb, const gboolean border, const gboolean source, const dt_masks_skip_range_t *skips, const int skip_count)
Definition gradient.c:976
static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module, int index, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:505
static void _gradient_duplicate_points(dt_develop_t *dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition gradient.c:1463
static dt_masks_raster_result_t _gradient_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer, dt_iop_roi_t *touched)
Definition gradient.c:1339
static void _gradient_sanitize_config(dt_masks_type_t type)
Definition gradient.c:1438
#define extent_MIN
Definition gradient.c:48
static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:391
static gboolean _gradient_is_canonical(const float x, const float y, const float wd, const float ht)
Definition gradient.c:752
static void _gradient_get_distance(float x, float y, float dist_mouse, dt_masks_form_gui_t *gui, int index, int num_points, int *inside, int *inside_border, int *near_handle, int *inside_source, float *dist)
Definition gradient.c:238
static void _gradient_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border, int inside_source, void *user_data)
Definition gradient.c:347
const dt_masks_functions_t dt_masks_functions_gradient
Definition gradient.c:1470
static void _gradient_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form, char *const restrict msgbuf, const size_t msgbuf_len)
Definition gradient.c:1449
#define CURVATURE_MAX
Definition gradient.c:51
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
void dt_masks_sample_grid_interpolate(const float *const points, const dt_masks_sample_grid_t *const grid, float *const buffer, const int buf_width, const int buf_height, int *const endx, int *const endy)
Definition masks.c:393
float * dt_masks_sample_grid_backtransform(struct dt_dev_pixelpipe_t *pipe, const double iop_order, const dt_masks_sample_grid_t *const grid, const char *const shape, const char *const form_name)
Definition masks.c:345
static dt_masks_raster_result_t dt_masks_raster_from_status(const int status)
Definition masks.h:322
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_EMPTY
Definition masks.h:315
@ DT_MASKS_RASTER_OK
Definition masks.h:314
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
Definition masks.c:1332
int dt_masks_form_change_opacity(dt_develop_t *dev, dt_masks_form_t *form, int parentid, int up, const int flow)
Definition masks_gui.c:5840
dt_masks_gradient_states_t
Definition masks.h:161
@ DT_MASKS_GRADIENT_STATE_SIGMOIDAL
Definition masks.h:163
@ DT_MASKS_GRADIENT_STATE_LINEAR
Definition masks.h:162
The per-shape function table, private to the masks implementation.
float dt_masks_rotate_with_anchor(dt_develop_t *develop, const float anchor[2], const float center[2], dt_masks_form_gui_t *mask_gui)
Compute rotation angle (degrees) around a center using an anchor point.
Definition masks_gui.c:5706
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
Definition masks_gui.c:5246
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Centralized hit-testing for node/handle/segment selection across shapes.
Definition masks_gui.c:1183
void dt_masks_gui_form_create(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module)
Definition masks_gui.c:1914
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount, float value_min, float value_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
Definition masks_gui.c:5305
void dt_masks_gui_form_save_creation(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui)
Save the form creation right after a shape has been finished drawing.
Definition masks_gui.c:2525
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks_gui.h:304
static void dt_masks_draw_preview_shape(struct dt_develop_t *dev, cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, const shape_draw_function_t *draw_shape, const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks_gui.h:410
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks_gui.h:443
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks_gui.h:312
static void dt_masks_touched_full(dt_iop_roi_t *touched, const int width, const int height)
static void dt_masks_touched_none(dt_iop_roi_t *touched)
@ DT_MASKS_EDIT_FULL
dt_masks_type_t
Definition masks_types.h:62
dt_masks_interaction_t
@ DT_MASKS_INTERACTION_OPACITY
@ DT_MASKS_INTERACTION_SIZE
@ DT_MASKS_INTERACTION_FADING
@ DT_MASKS_INTERACTION_ROTATION
dt_masks_increment_t
@ DT_MASKS_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
@ DT_MASKS_INCREMENT_ABSOLUTE
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
float iscale
Definition mipmap_cache.c:2
static float gh(const float f)
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
#define dt_get_bythread(buf, padsize, tnum)
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_get_perthread(buf, padsize)
#define dt_pixelpipe_cache_calloc_perthread(n, objsize, padded_size)
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
Objective facts about the image a dev is working on.
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_masks_gradient_states_t state
Definition masks.h:241
gboolean border_toggling
Definition masks_gui.h:163
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
gboolean seg_selected
Definition masks_gui.h:147
gboolean form_dragging
Definition masks_gui.h:160
gboolean gradient_toggling
Definition masks_gui.h:164
gboolean form_selected
Definition masks_gui.h:151
gboolean form_rotating
Definition masks_gui.h:162
gboolean border_selected
Definition masks_gui.h:152
gboolean pivot_selected
Definition masks_gui.h:154
struct dt_develop_t * dev
Definition masks_gui.h:101
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
shape_draw_function_t draw_shape
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
static double dt_get_wtime(void)
Definition times.h:43
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)