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 "bauhaus/bauhaus.h"
34#include "common/debug.h"
35#include "common/undo.h"
36#include "control/conf.h"
37#include "develop/blend.h"
38#include "develop/imageop.h"
39#include "develop/masks.h"
41
42#define extent_MIN 0.0005f
43#define extent_MAX 1.0f
44#define CURVATURE_MIN -2.0f
45#define CURVATURE_MAX 2.0f
46
47#define BORDER_MIN 0.00005f
48#define BORDER_MAX 0.5f
49
50// Helper function to find the INFINITY separator in border array
51static int _find_border_separator(const float *border, int count)
52{
53
54 if(IS_NULL_PTR(border) || count <= 0) return -1;
55
56#ifdef _OPENMP
57 int found = count;
58#pragma omp parallel for reduction(min:found) if(count > 1000)
59 for(int i = 0; i < count; i++)
60 {
61 if(!isfinite(border[i * 2]) && !isfinite(border[i * 2 + 1]))
62 found = i;
63 }
64 return (found == count) ? -1 : found;
65#else
66 for(int i = 0; i < count; i++)
67 {
68 if(!isfinite(border[i * 2]) && !isfinite(border[i * 2 + 1]))
69 return i;
70 }
71 return -1;
72#endif
73}
74
75
76// Helper function to find closest point on a line segment to a given point
77static void _closest_point_on_segment(float px, float py, float x1, float y1, float x2, float y2,
78 float *closest_x, float *closest_y, float *distance_sq)
79{
80 const float seg_dx = x2 - x1;
81 const float seg_dy = y2 - y1;
82 const float seg_length_sq = seg_dx * seg_dx + seg_dy * seg_dy;
83
84 if(seg_length_sq < 1e-10f)
85 {
86 // Degenerate segment, return first point
87 *closest_x = x1;
88 *closest_y = y1;
89 *distance_sq = (px - x1) * (px - x1) + (py - y1) * (py - y1);
90 return;
91 }
92
93 // Project point onto line segment (clamped to [0,1])
94 const float t = fmaxf(0.0f, fminf(1.0f,
95 ((px - x1) * seg_dx + (py - y1) * seg_dy) / seg_length_sq));
96
97 *closest_x = x1 + t * seg_dx;
98 *closest_y = y1 + t * seg_dy;
99 *distance_sq = (px - *closest_x) * (px - *closest_x) + (py - *closest_y) * (py - *closest_y);
100}
101
102// Helper function to find closest point on a polyline to a given point
103static void _closest_point_on_line(float px, float py, const float *border, int start_idx, int end_idx,
104 float *closest_x, float *closest_y, float *min_distance_sq)
105{
106 *min_distance_sq = FLT_MAX;
107 *closest_x = *closest_y = 0.0f;
108
109 if(start_idx >= end_idx - 1) return;
110
111#ifdef _OPENMP
112 float global_min = FLT_MAX;
113 float global_x = 0.0f, global_y = 0.0f;
114
115#pragma omp parallel
116 {
117 float local_min = FLT_MAX;
118 float local_x = 0.0f, local_y = 0.0f;
119
120#pragma omp for nowait
121 for(int i = start_idx; i < end_idx - 1; i++)
122 {
123 float seg_closest_x, seg_closest_y, seg_dist_sq;
125 border[i * 2], border[i * 2 + 1],
126 border[(i + 1) * 2], border[(i + 1) * 2 + 1],
127 &seg_closest_x, &seg_closest_y, &seg_dist_sq);
128
129 if(seg_dist_sq < local_min)
130 {
131 local_min = seg_dist_sq;
132 local_x = seg_closest_x;
133 local_y = seg_closest_y;
134 }
135 }
136
137 if(local_min < global_min)
138 {
139#pragma omp critical
140 {
141 if(local_min < global_min)
142 {
143 global_min = local_min;
144 global_x = local_x;
145 global_y = local_y;
146 }
147 }
148 }
149 } // end parallel
150
151 *min_distance_sq = global_min;
152 *closest_x = global_x;
153 *closest_y = global_y;
154#else
155 for(int i = start_idx; i < end_idx - 1; i++)
156 {
157 float seg_closest_x, seg_closest_y, seg_dist_sq;
159 border[i * 2], border[i * 2 + 1],
160 border[(i + 1) * 2], border[(i + 1) * 2 + 1],
161 &seg_closest_x, &seg_closest_y, &seg_dist_sq);
162
163 if(seg_dist_sq < *min_distance_sq)
164 {
165 *min_distance_sq = seg_dist_sq;
166 *closest_x = seg_closest_x;
167 *closest_y = seg_closest_y;
168 }
169 }
170#endif
171}
172
174{
175 const float gradient_dx = gpt->points[2] - gpt->points[0];
176 const float gradient_dy = gpt->points[3] - gpt->points[1];
177 return gradient_dx * gradient_dx + gradient_dy * gradient_dy;
178}
179
186
188{
189 values->extent = CLAMPF(dt_conf_get_float("plugins/darkroom/masks/gradient/extent"),
191 values->curvature = CLAMPF(dt_conf_get_float("plugins/darkroom/masks/gradient/curvature"),
193 values->rotation = dt_conf_get_float("plugins/darkroom/masks/gradient/rotation");
194 if(!isfinite(values->rotation)) values->rotation = 0.0f;
195}
196
198{
202 gradient->extent = values.extent;
203 gradient->curvature = values.curvature;
204 gradient->rotation = values.rotation;
205}
206
207static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature,
208 float **points, int *points_count);
209static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance,
210 float curvature, float **points, int *points_count);
211
212// Gradient creation preview uses the same temp-buffer contract as circle/ellipse,
213// with the shape-specific geometry generation kept here.
215{
218
219 float center[2];
221
222 *preview = (dt_masks_preview_buffers_t){ 0 };
223 int err = _gradient_get_points(darktable.develop, center[0], center[1], values.rotation,
224 values.curvature, &preview->points, &preview->points_count);
225 if(!err && values.extent > 0.0f)
226 err = _gradient_get_pts_border(darktable.develop, center[0], center[1], values.rotation,
227 values.extent, values.curvature, &preview->border,
228 &preview->border_count);
229 return err;
230}
231
232static void _gradient_get_distance(float x, float y, float dist_mouse, dt_masks_form_gui_t *gui, int index,
233 int num_points, int *inside, int *inside_border, int *near,
234 int *inside_source, float *dist)
235{
236 // initialise returned values
237 *inside_source = 0;
238 *inside = 0;
239 *inside_border = 0;
240 *near = -1;
241 *dist = FLT_MAX;
242 const float sqr_dist_mouse = dist_mouse * dist_mouse;
243
244 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
245 if(IS_NULL_PTR(gpt)) return;
246
247 float min_dist = FLT_MAX;
248
249 // check if we are between the two border lines
250 if(!gui->form_rotating && !gui->form_dragging && gpt->border && gpt->border_count > 6
251 && gpt->points && gpt->points_count >= 4)
252 {
253 const int separator_idx = _find_border_separator(gpt->border, gpt->border_count);
254 if(separator_idx > 0 && separator_idx < gpt->border_count - 1)
255 {
256 // Get gradient direction from segment (points[0],points[1]) to (points[2],points[3])
257 const float gradient_len_sq = _gradient_get_border_len_sq(gpt);
258
259 if(gradient_len_sq > 1e-12f)
260 {
261 // Find closest points on both lines
262 float closest_x1, closest_y1, dist1_sq;
263 float closest_x2, closest_y2, dist2_sq;
264
265 _closest_point_on_line(x, y, gpt->border, 0, separator_idx,
266 &closest_x1, &closest_y1, &dist1_sq);
267
268 _closest_point_on_line(x, y, gpt->border, separator_idx + 1, gpt->border_count,
269 &closest_x2, &closest_y2, &dist2_sq);
270
271 // Check if we have valid closest points to both border lines.
272 if(dist1_sq < FLT_MAX && dist2_sq < FLT_MAX)
273 {
274 // Vectors from mouse to each closest point
275 const float to_line1_x = closest_x1 - x;
276 const float to_line1_y = closest_y1 - y;
277 const float to_line2_x = closest_x2 - x;
278 const float to_line2_y = closest_y2 - y;
279
280 const float gradient_dx = gpt->points[2] - gpt->points[0];
281 const float gradient_dy = gpt->points[3] - gpt->points[1];
282 // Project these vectors onto the (unnormalized) gradient direction.
283 // Using the unnormalized direction preserves sign, so we avoid sqrt().
284 const float proj1 = to_line1_x * gradient_dx + to_line1_y * gradient_dy;
285 const float proj2 = to_line2_x * gradient_dx + to_line2_y * gradient_dy;
286
287 // Mouse is between lines if projections have opposite signs.
288 const gboolean between_lines = (proj1 * proj2 < 0.0f);
289 if(between_lines) *inside_border = 1;
290
291 // Rotation handle: accept hits on the border lines and slightly beyond.
292 const float min_dist_sq = fminf(dist1_sq, dist2_sq);
293 float handle_radius_sq = CLAMPF(gradient_len_sq * 0.125f, sqr_dist_mouse, sqr_dist_mouse * 5);
294
295 if(min_dist_sq <= handle_radius_sq)
296 *inside = 1;
297 }
298 }
299 }
300 }
301
302 // and we check if we are near a segment (single continuous segment starting at gpt->points[3])
303 if(gpt->points && gpt->points_count > 3)
304 {
305 for(int i = 3; i < gpt->points_count; i++)
306 {
307 const float xx = gpt->points[i * 2];
308 const float yy = gpt->points[i * 2 + 1];
309
310 const float dx = x - xx;
311 const float dy = y - yy;
312 const float dd = sqf(dx) + sqf(dy);
313
314 min_dist = fminf(min_dist, dd);
315
316 // only one segment present: if any guide point is within the mouse distance,
317 // mark the (only) segment as near (index 0)
318 if(dd < sqr_dist_mouse)
319 *near = 0;
320 }
321 }
322
323 *dist = min_dist;
324}
325
326static void _gradient_node_position_cb(const dt_masks_form_gui_points_t *gui_points, int node_index,
327 float *node_x, float *node_y, void *user_data)
328{
329 if(node_x) *node_x = NAN;
330 if(node_y) *node_y = NAN;
331}
332
333static void _gradient_distance_cb(float pointer_x, float pointer_y, float cursor_radius,
334 dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside,
335 int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
336{
337 _gradient_get_distance(pointer_x, pointer_y, cursor_radius, mask_gui, form_index, 0, inside,
338 inside_border, near, inside_source, dist);
339}
340
341static void _gradient_post_select_cb(dt_masks_form_gui_t *mask_gui, int inside, int inside_border,
342 int inside_source, void *user_data)
343{
344 if(inside)
345 {
346 mask_gui->border_selected = FALSE;
347 mask_gui->pivot_selected = TRUE;
348 }
349 else if(inside_border)
350 {
351 mask_gui->pivot_selected = FALSE;
352 }
353}
354
355static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int index)
356{
357 if(mask_gui) mask_gui->pivot_selected = FALSE;
358 return dt_masks_find_closest_handle_common(mask_form, mask_gui, index, 1,
359 NULL, NULL, _gradient_node_position_cb,
361}
362
363
364static int _init_extent(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
365{
367 increment, flow, _("extent: %3.2f%%"), 100.0f);
368 return 1;
369}
370
371static int _init_curvature(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
372{
374 increment, flow, _("Curvature: %3.2f%%"), 50.f);
375 return 1;
376}
377
378static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
379{
380 dt_masks_get_set_conf_value_with_toast(form, "opacity", amount, 0.f, 1.f,
381 increment, flow, _("Opacity: %3.2f%%"), 100.f);
382 return 1;
383}
384
385static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
386{
387 dt_masks_get_set_conf_value_with_toast(form, "rotation", amount, 0.f, 360.f,
388 increment, flow, _("Rotation: %3.2f\302\260"), 1.0f);
389 return 1;
390}
391
393{
394 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return NAN;
395 const dt_masks_anchor_gradient_t *gradient = (const dt_masks_anchor_gradient_t *)(form->points)->data;
396 if(IS_NULL_PTR(gradient)) return NAN;
397
398 switch(interaction)
399 {
401 return gradient->extent;
403 return gradient->curvature;
405 return gradient->rotation;
406 default:
407 return NAN;
408 }
409}
410
411static gboolean _gradient_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
412{
413 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
414 const dt_masks_anchor_gradient_t *gradient = (const dt_masks_anchor_gradient_t *)(form->points)->data;
415 if(IS_NULL_PTR(gradient)) return FALSE;
416 center[0] = gradient->center[0];
417 center[1] = gradient->center[1];
418 *area = gradient->extent; // pretend it's a rectangle of unit width
419 return TRUE;
420}
421
422static int _change_extent(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
423 int index, const float amount, const dt_masks_increment_t increment, const int flow);
424static int _change_curvature(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
425 int index, const float amount, const dt_masks_increment_t increment, const int flow);
426static int _change_rotation(dt_masks_form_t *form, dt_masks_form_gui_t *gui, struct dt_iop_module_t *module,
427 int index, const float amount, const dt_masks_increment_t increment, const int flow);
428
430 dt_masks_increment_t increment, int flow,
431 dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
432{
433 if(IS_NULL_PTR(form)) return NAN;
434 // Mirrors _dt_masks_events_get_dispatch_form()'s form_index: this shape's position in the
435 // currently displayed group, so dt_masks_gui_form_create() below refreshes the right
436 // mask_gui->points slot instead of clobbering whatever shape sits at index 0.
437 const int index = (!IS_NULL_PTR(gui) && gui->group_selected >= 0) ? gui->group_selected : 0;
438
439 switch(interaction)
440 {
442 if(!_change_extent(form, gui, module, index, value, increment, flow)) return NAN;
443 return _gradient_get_interaction_value(form, interaction);
445 if(!_change_curvature(form, gui, module, index, value, increment, flow)) return NAN;
446 return _gradient_get_interaction_value(form, interaction);
448 if(!_change_rotation(form, gui, module, index, value, increment, flow)) return NAN;
449 return _gradient_get_interaction_value(form, interaction);
450 default:
451 return NAN;
452 }
453}
454
455static 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)
456{
457 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
459 if(IS_NULL_PTR(gradient)) return 0;
460
461 gradient->extent = CLAMPF(dt_masks_apply_increment(gradient->extent, amount, increment, flow),
463
464 _init_extent(form, amount, increment, flow);
465
466 // we recreate the form points
467 dt_masks_gui_form_create(form, gui, index, module);
468
469 return 1;
470}
471
472static 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)
473{
474 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
476 if(IS_NULL_PTR(gradient)) return 0;
477
478 // Sanitize
479 // do not exceed upper limit of 2.0 and lower limit of -2.0
480 if(amount > 2.0f && (gradient->curvature > 2.0f ))
481 return 1;
482
483 const int node_hovered = gui->node_hovered;
484
485 // bending
486 if(node_hovered == -1 || node_hovered == 0)
487 {
488 gradient->curvature = dt_masks_apply_increment(gradient->curvature, amount, increment, flow);
489 }
490
491 _init_curvature(form, amount, DT_MASKS_INCREMENT_SCALE, flow);
492
493 // we recreate the form points
494 dt_masks_gui_form_create(form, gui, index, module);
495
496 return 1;
497}
498
499static 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)
500{
501 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
503 if(IS_NULL_PTR(gradient)) return 0;
504
505 // Rotation
506 int flow_increased = (flow > 1) ? (flow - 1) * 5 : flow;
507 gradient->rotation = dt_masks_apply_increment(gradient->rotation, amount, increment, flow_increased);
508
509 // Ensure the rotation value warps within the interval [0, 360)
510 if(gradient->rotation > 360.f) gradient->rotation = fmodf(gradient->rotation, 360.f);
511 else if(gradient->rotation < 0.f) gradient->rotation = 360.f - fmodf(-gradient->rotation, 360.f);
512
513 _init_rotation(form, amount, DT_MASKS_INCREMENT_OFFSET, flow);
514
515 // we recreate the form points
516 dt_masks_gui_form_create(form, gui, index, module);
517
518 return 1;
519}
520
521/* Shape handlers receive widget-space coordinates, while normalized output-image
522 * coordinates come from `gui->rel_pos` and absolute output-image
523 * coordinates come from `gui->pos`. */
524static int _gradient_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
525 uint32_t state, dt_masks_form_t *form, int parentid,
526 dt_masks_form_gui_t *gui, int index, dt_masks_interaction_t interaction)
527{
528
529
530
531 if(gui->creation)
532 {
533 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
534 return _init_rotation(form, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
535 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
536 return _init_opacity(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
537 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
538 return _init_curvature(form, up ? +0.02f : -0.02f, DT_MASKS_INCREMENT_OFFSET, flow);
539 else
540 return _init_extent(form, (up ? +1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow); // simple scroll to adjust curvature, calling func adjusts opacity with Ctrl
541 }
542 else if(gui->form_selected || gui->seg_selected || gui->pivot_selected)
543 {
544 if(dt_modifier_is(state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
545 return _change_rotation(form, gui, module, index, (up ? +0.2f : -0.2f), DT_MASKS_INCREMENT_OFFSET, flow);
546 else if(dt_modifier_is(state, GDK_CONTROL_MASK))
547 return dt_masks_form_change_opacity(form, parentid, up, flow);
548 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
549 return _change_curvature(form, gui, module, index, (up ? +0.02f : -0.02f), DT_MASKS_INCREMENT_OFFSET, flow);
550 else
551 return _change_extent(form, gui, module, index, (up ? 1.02f : 0.98f), DT_MASKS_INCREMENT_SCALE, flow);
552 }
553 return 0;
554}
555
556static int _gradient_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
557 double pressure, int which, int type, uint32_t state,
558 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
559{
560 if(gui->creation)
561 {
562 if(which == 1)
563 {
564 if(dt_modifier_is(state, GDK_SHIFT_MASK))
565 {
566 gui->gradient_toggling = TRUE;
567 return 1;
568 }
569
570 dt_iop_module_t *crea_module = gui->creation_module;
571 // we create the gradient
573 if(IS_NULL_PTR(gradient)) return 0;
574 _gradient_init_new(gui, gradient);
575
576 form->points = g_list_append(form->points, gradient);
577 dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui);
578
579 return 1;
580 }
581 }
582
583 else if(which == 1)
584 {
585 // double-click resets curvature
586 if(type == GDK_2BUTTON_PRESS)
587 {
588 _change_curvature(form, gui, module, index, 0, DT_MASKS_INCREMENT_ABSOLUTE, 0);
589 dt_masks_gui_form_create(form, gui, index, module);
590 return 1;
591 }
592
593 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
594 if(IS_NULL_PTR(gpt)) return 0;
595
596 else if((gui->form_selected || gui->seg_hovered >= 0 || gui->seg_selected)
597 && gui->edit_mode == DT_MASKS_EDIT_FULL)
598 {
599 // we start the form dragging or rotating
600 if(gui->pivot_selected)
601 gui->form_rotating = TRUE;
602 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
603 gui->border_toggling = TRUE;
604 else if(gui->seg_hovered >= 0 || gui->seg_selected)
605 gui->form_selected = TRUE;
606
607 if(gui->form_rotating)
608 {
609 gui->delta[0] = gui->pos[0];
610 gui->delta[1] = gui->pos[1];
611 }
612 else
613 {
614 gui->delta[0] = gpt->points[0] - gui->pos[0];
615 gui->delta[1] = gpt->points[1] - gui->pos[1];
616 }
617
618 return 1;
619 }
620 }
621
622 return 0;
623}
624
625static int _gradient_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
626 uint32_t state, dt_masks_form_t *form, int parentid,
627 dt_masks_form_gui_t *gui, int index)
628{
629
630
631
632
633
634
635 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
636
637 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
638 {
639 // we end the form dragging
640 return 1;
641 }
642
643 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
644 {
645 // we end the form rotating
646 gui->form_rotating = FALSE;
647 return 1;
648 }
649 else if(gui->gradient_toggling)
650 {
651 // we get the gradient
652 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
653 if(IS_NULL_PTR(gradient)) return 0;
654 // we end the gradient toggling
656
657 // toggle transition type of gradient
660 else
662
663 dt_conf_set_int("plugins/darkroom/masks/gradient/state", gradient->state);
664
665 // we recreate the form points
666 dt_masks_gui_form_create(form, gui, index, module);
667
668 // we save the new parameters
669
670 return 1;
671 }
672 return 0;
673}
674
675static int _gradient_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form,
676 int parentid, dt_masks_form_gui_t *gui, int index)
677{
678 return 0;
679}
680
681static int _gradient_events_mouse_moved(struct dt_iop_module_t *module, double x, double y,
682 double pressure, int which, dt_masks_form_t *form, int parentid,
683 dt_masks_form_gui_t *gui, int index)
684{
685 if(gui->creation)
686 {
687 // Let the cursor motion be redrawn as it moves in GUI
688 return 1;
689 }
690
691 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
692
693 // we get the gradient
694 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
695 if(IS_NULL_PTR(gradient)) return 0;
696
697 // we need the reference points
698 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
699 if(IS_NULL_PTR(gpt)) return 0;
700
701 if(gui->form_dragging)
702 {
703 // we change the center value
704 float pts[2];
706
707 gradient->center[0] = pts[0];
708 gradient->center[1] = pts[1];
709
710 // we recreate the form points
711 dt_masks_gui_form_create(form, gui, index, module);
712
713 return 1;
714 }
715
716 //rotation with the mouse
717 if(gui->form_rotating)
718 {
719 const float origin_point[2] = { gpt->points[0], gpt->points[1] };
720 const float angle = - dt_masks_rotate_with_anchor(darktable.develop, gui->pos, origin_point, gui);
721 _change_rotation(form, gui, module, index, angle , DT_MASKS_INCREMENT_OFFSET, 1);
722
723 // we recreate the form points
724 dt_masks_gui_form_create(form, gui, index, module);
725
726 return 1;
727 }
728 return 0;
729}
730
731// check if (x,y) lies within reasonable limits relative to image frame
732static inline gboolean _gradient_is_canonical(const float x, const float y, const float wd, const float ht)
733{
734 return (isnormal(x) && isnormal(y) && (x >= -wd) && (x <= 2 * wd) && (y >= -ht) && (y <= 2 * ht)) ? TRUE : FALSE;
735}
736
745static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature,
746 float **points, int *points_count)
747{
748 *points = NULL;
749 *points_count = 0;
750
751 const float wd = dev->roi.raw_width;
752 const float ht = dev->roi.raw_height;
753 if(!isfinite(wd) || !isfinite(ht) || wd <= 0.0f || ht <= 0.0f) return 1;
754
755 const float scale = sqrtf(wd * wd + ht * ht);
756 const float distance = 0.1f * fminf(wd, ht);
757
758 const float v = (-rotation / 180.0f) * M_PI;
759 const float cosv = cosf(v);
760 const float sinv = sinf(v);
761
762 const int count = sqrtf(wd * wd + ht * ht) + 3;
763 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * count, 0);
764 if(IS_NULL_PTR(*points)) return 1;
765
766 // we set the anchor point
767 float center[2] = { x, y };
769 const float center_x = center[0];
770 const float center_y = center[1];
771 (*points)[0] = center_x;
772 (*points)[1] = center_y;
773
774 // we set the pivot points
775 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
776 const float x1 = center[0] + distance * cosf(v1);
777 const float y1 = center[1] + distance * sinf(v1);
778 (*points)[2] = x1;
779 (*points)[3] = y1;
780 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
781 const float x2 = center[0] + distance * cosf(v2);
782 const float y2 = center[1] + distance * sinf(v2);
783 (*points)[4] = x2;
784 (*points)[5] = y2;
785
786 const int nthreads = darktable.num_openmp_threads;
787 size_t c_padded_size;
788 uint32_t *pts_count = dt_pixelpipe_cache_calloc_perthread(1, sizeof(uint32_t), &c_padded_size);
789 size_t pts_padded_size;
790 float *const restrict pts = dt_pixelpipe_cache_alloc_perthread_float((size_t)2 * count, &pts_padded_size);
791 if(IS_NULL_PTR(pts_count) || IS_NULL_PTR(pts))
792 {
796 *points = NULL;
797 *points_count = 0;
798 return 1;
799 }
800
801 // we set the line point
802 const float xstart = fabsf(curvature) > 1.0f ? -sqrtf(1.0f / fabsf(curvature)) : -1.0f;
803 const float xdelta = -2.0f * xstart / (count - 3);
804
805// gboolean in_frame = FALSE;
806 __OMP_PARALLEL_FOR__(if(count > 100) num_threads(nthreads))
807 for(int i = 3; i < count; i++)
808 {
809 const float xi = xstart + (i - 3) * xdelta;
810 const float yi = curvature * xi * xi;
811 const float xii = (cosv * xi + sinv * yi) * scale;
812 const float yii = (sinv * xi - cosv * yi) * scale;
813 const float xiii = xii + center_x;
814 const float yiii = yii + center_y;
815
816 // don't generate guide points if they extend too far beyond the image frame;
817 // this is to avoid that modules like lens correction fail on out of range coordinates
818 if(!(xiii < -wd || xiii > 2 * wd || yiii < -ht || yiii > 2 * ht))
819 {
820 uint32_t *tcount = dt_get_perthread(pts_count, c_padded_size);
821 float *const tpts = dt_get_perthread(pts, pts_padded_size);
822 tpts[*tcount * 2] = xiii;
823 tpts[*tcount * 2 + 1] = yiii;
824 (*tcount)++;
825 }
826 }
827
828 *points_count = 3;
829 for(int thread = 0; thread < nthreads; thread++)
830 {
831 const uint32_t tcount = *(uint32_t *)dt_get_bythread(pts_count, c_padded_size, thread);
832 const float *const tpts = dt_get_bythread(pts, pts_padded_size, thread);
833 // Merge only the retained in-frame samples. The source loop has at most
834 // count - 3 samples, so the three metadata points leave exactly that room.
835 for(uint32_t k = 0; k < tcount && *points_count < count; k++)
836 {
837 (*points)[(*points_count) * 2] = tpts[k * 2];
838 (*points)[(*points_count) * 2 + 1] = tpts[k * 2 + 1];
839 (*points_count)++;
840 }
841 }
842
845
846 // and we transform them with all distorted modules
847 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, *points, *points_count))
848 {
850 *points = NULL;
851 *points_count = 0;
852 return 1;
853 }
854
855 return 0;
856}
857
858// Helper function to copy points, skipping the first 3 metadata points
859static void _copy_points(float *dest, const float *src, int count, int *k)
860{
861 for(int i = 3; i < count; i++, (*k)++)
862 {
863 dest[(*k) * 2] = src[i * 2];
864 dest[(*k) * 2 + 1] = src[i * 2 + 1];
865 }
866}
867
868static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance,
869 float curvature, float **points, int *points_count)
870{
871 *points = NULL;
872 *points_count = 0;
873 distance = CLAMPF(distance, extent_MIN, extent_MAX);
874
875 // Get border curve dimensions and scaling
876 const float wd = dev->roi.raw_width;
877 const float ht = dev->roi.raw_height;
878 const float scale = sqrtf(wd * wd + ht * ht);
879
880 // Calculate perpendicular offsets (±90 degrees from rotation)
881 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
882 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
883
884 // Generate offset positions for both curves
885 float center[2] = { x, y };
887 float offsets[4] = { center[0] + distance * scale * cosf(v1),
888 center[1] + distance * scale * sinf(v1),
889 center[0] + distance * scale * cosf(v2),
890 center[1] + distance * scale * sinf(v2) };
892 const float x1 = offsets[0];
893 const float y1 = offsets[1];
894 const float x2 = offsets[2];
895 const float y2 = offsets[3];
896
897 // Get points for both curves
898 float *points1 = NULL, *points2 = NULL;
899 int points_count1 = 0, points_count2 = 0;
900 const int err1 = _gradient_get_points(dev, x1, y1, rotation, curvature, &points1, &points_count1);
901 const int err2 = _gradient_get_points(dev, x2, y2, rotation, curvature, &points2, &points_count2);
902
903 // Check which curves are valid (need more than 4 points: 3 metadata + at least 1 data)
904 const gboolean valid1 = (err1 == 0) && points_count1 > 4;
905 const gboolean valid2 = (err2 == 0) && points_count2 > 4;
906
907 int err = 1;
908
909 if(valid1 && valid2)
910 {
911 // Both curves valid - combine them with INFINITY separator
912 const int total_points = (points_count1 - 3) + (points_count2 - 3) + 1;
913 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * total_points, 0);
914 if(IS_NULL_PTR(*points)) goto cleanup;
915
916 *points_count = total_points;
917 int k = 0;
918
919 _copy_points(*points, points1, points_count1, &k);
920 (*points)[k * 2] = (*points)[k * 2 + 1] = INFINITY; // Separator
921 k++;
922 _copy_points(*points, points2, points_count2, &k);
923 err = 0;
924 }
925 else if(valid1)
926 {
927 // Only first curve valid
928 *points_count = points_count1 - 3;
929 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (*points_count), 0);
930 if(IS_NULL_PTR(*points)) goto cleanup;
931
932 int k = 0;
933 _copy_points(*points, points1, points_count1, &k);
934 err = 0;
935 }
936 else if(valid2)
937 {
938 // Only second curve valid
939 *points_count = points_count2 - 3;
940 *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * (*points_count), 0);
941 if(IS_NULL_PTR(*points)) goto cleanup;
942
943 int k = 0;
944 _copy_points(*points, points2, points_count2, &k);
945 err = 0;
946 }
947
948cleanup:
951 return err;
952}
953
954static void _gradient_draw_shape(cairo_t *cr, const float *pts_line, const int pts_line_count, const int nb, const gboolean border, const gboolean source)
955{
956 // safeguard in case of malformed arrays of points
957 if(border && pts_line_count <= 3) return;
958 if(!border && pts_line_count <= 4) return;
959
960 const float *points = (border) ? pts_line : pts_line + 6;
961 const int points_count = (border) ? pts_line_count : pts_line_count - 3;
962
963 const float wd = darktable.develop->roi.raw_width;
964 const float ht = darktable.develop->roi.raw_height;
965
966 int i = 0;
967 while(i < points_count)
968 {
969 const float px = points[i * 2];
970 const float py = points[i * 2 + 1];
971
972 if(!isnormal(px) || !_gradient_is_canonical(px, py, wd, ht))
973 {
974 i++;
975 continue;
976 }
977
978 cairo_move_to(cr, px, py);
979 i++;
980
981 // continue the current segment until a non-normal or out-of-range point
982 while(i < points_count)
983 {
984 const float qx = points[i * 2];
985 const float qy = points[i * 2 + 1];
986 if(!isnormal(qx) || !_gradient_is_canonical(qx, qy, wd, ht)) break;
987 cairo_line_to(cr, qx, qy);
988 i++;
989 }
990 }
991}
992
993static void _gradient_draw_arrow(cairo_t *cr, const gboolean selected, const gboolean pivot_selected, const gboolean is_rotating,
994 const float zoom_scale, float *pts, int pts_count)
995{
996 if(pts_count < 3) return;
997
998 const float anchor_x = pts[0];
999 const float anchor_y = pts[1];
1000 const float pivot_end_x = pts[2];
1001 const float pivot_end_y = pts[3];
1002 const float pivot_start_x = pts[4];
1003 const float pivot_start_y = pts[5];
1004
1005 // draw a dotted line across the gradient for better visibility while dragging
1006 if(is_rotating)
1007 {
1008 // extend the axis line beyond the pivot points
1009 const float scale = 1 / zoom_scale;
1010 const float dx = pivot_end_x - pivot_start_x;
1011 const float dy = pivot_end_y - pivot_start_y;
1012
1013 const float new_x1 = pivot_start_x - (dx * scale * 0.5f);
1014 const float new_y1 = pivot_start_y - (dy * scale * 0.5f);
1015 const float new_x2 = pivot_end_x + (dx * scale * 0.5f);
1016 const float new_y2 = pivot_end_y + (dy * scale * 0.5f);
1017 cairo_move_to(cr, new_x1, new_y1);
1018 cairo_line_to(cr, new_x2, new_y2);
1019
1020 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, FALSE, zoom_scale, CAIRO_LINE_CAP_ROUND);
1021 }
1022
1023 // always draw arrow to clearly display the direction
1024 {
1025 // size & width of the arrow
1026 const float arrow_angle = 0.25f;
1027 const float arrow_length = (DT_DRAW_SCALE_ARROW * 2) / zoom_scale;
1028
1029 // compute direction from anchor toward pivot_end and build an arrow
1030 const float dx = pivot_end_x - anchor_x;
1031 const float dy = pivot_end_y - anchor_y;
1032 const float angle_dir = atan2f(dy, dx); // direction the arrow should point to
1033
1034 // tip of the arrow (ahead of anchor along angle_dir)
1035 const float tip_x = anchor_x + arrow_length * cosf(angle_dir);
1036 const float tip_y = anchor_y + arrow_length * sinf(angle_dir);
1037
1038 // half width of the arrow head
1039 const float half_w = arrow_length * tanf(arrow_angle);
1040
1041 // perpendicular vector to the direction (unit)
1042 const float nx = -sinf(angle_dir);
1043 const float ny = cosf(angle_dir);
1044
1045 // two corner points of the arrow base, centered on (anchor_x, anchor_y)
1046 const float arrow_x1 = anchor_x + nx * half_w;
1047 const float arrow_y1 = anchor_y + ny * half_w;
1048 const float arrow_x2 = anchor_x - nx * half_w;
1049 const float arrow_y2 = anchor_y - ny * half_w;
1050
1051 // we will draw the triangle as tip -> base1 -> base2
1052 cairo_move_to(cr, tip_x, tip_y);
1053 cairo_line_to(cr, arrow_x1, arrow_y1);
1054 cairo_line_to(cr, arrow_x2, arrow_y2);
1055 cairo_close_path(cr);
1056
1058 cairo_fill_preserve(cr);
1059 double line_width = pivot_selected ? (DT_DRAW_SIZE_LINE_SELECTED / zoom_scale) : (DT_DRAW_SIZE_LINE / zoom_scale);
1060 cairo_set_line_width(cr, line_width);
1062 cairo_stroke(cr);
1063 }
1064
1065 // draw the origin anchor point on top of everything
1066 dt_draw_node(cr, FALSE, FALSE, pivot_selected, zoom_scale, anchor_x, anchor_y);
1067}
1068
1069static void _gradient_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int nb)
1070{
1071 // preview gradient creation
1072 if(gui->creation)
1073 {
1075 if(_gradient_get_creation_preview(gui, &preview)) return;
1076
1077 dt_masks_draw_preview_shape(cr, zoom_scale, nb, preview.points, preview.points_count,
1078 preview.border, preview.border_count,
1079 &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND,
1080 CAIRO_LINE_CAP_ROUND, FALSE, FALSE);
1081 _gradient_draw_arrow(cr, FALSE, FALSE, gui->form_rotating, zoom_scale, preview.points, preview.points_count);
1083
1084 return;
1085 }
1086
1087 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1088 if(IS_NULL_PTR(gpt)) return;
1089
1090 const gboolean seg_selected = (gui->group_selected == index) && gui->seg_selected;
1091 const gboolean all_selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1092 // draw main line
1093 if(gpt->points && gpt->points_count > 0)
1094 dt_draw_shape_lines(DT_MASKS_NO_DASH, FALSE, cr, nb, (seg_selected), zoom_scale, gpt->points,
1095 gpt->points_count, &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND);
1096 // draw borders
1097 if(gui->group_selected == index)
1098 {
1099 if(gpt->border && gpt->border_count > 0)
1100 dt_draw_shape_lines(DT_MASKS_DASH_STICK, FALSE, cr, nb, (gui->border_selected), zoom_scale, gpt->border,
1101 gpt->border_count, &dt_masks_functions_gradient.draw_shape, CAIRO_LINE_CAP_ROUND);
1102 }
1103
1104 if(gpt->points && gpt->points_count >= 3)
1105 _gradient_draw_arrow(cr, (seg_selected || all_selected), ((gui->group_selected == index) && gui->pivot_selected),
1106 gui->form_rotating, zoom_scale, gpt->points, gpt->points_count);
1107}
1108
1109static int _gradient_get_points_border(dt_develop_t *dev, dt_masks_form_t *form, float **points, int *points_count,
1110 float **border, int *border_count, int source,
1111 const dt_iop_module_t *module)
1112{
1113 // unused arg, keep compiler from complaining
1114 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1116 if(IS_NULL_PTR(gradient)) return 0;
1117 if(_gradient_get_points(dev, gradient->center[0], gradient->center[1], gradient->rotation, gradient->curvature,
1118 points, points_count) != 0)
1119 return 1;
1120 if(border)
1121 return _gradient_get_pts_border(dev, gradient->center[0], gradient->center[1],
1122 gradient->rotation, gradient->extent, gradient->curvature,
1123 border, border_count);
1124 return 0;
1125}
1126
1127static int _gradient_get_area(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1128 const dt_dev_pixelpipe_iop_t *const piece,
1129 dt_masks_form_t *const form,
1130 int *width, int *height, int *posx, int *posy)
1131{
1132 const float wd = pipe->iwidth, ht = pipe->iheight;
1133
1134 float points[8] = { 0.0f, 0.0f, wd, 0.0f, wd, ht, 0.0f, ht };
1135
1136 // and we transform them with all distorted modules
1138 return 1;
1139
1140 // now we search min and max
1141 float xmin = 0.0f, xmax = 0.0f, ymin = 0.0f, ymax = 0.0f;
1142 xmin = ymin = FLT_MAX;
1143 xmax = ymax = FLT_MIN;
1144 for(int i = 0; i < 4; i++)
1145 {
1146 xmin = fminf(points[i * 2], xmin);
1147 xmax = fmaxf(points[i * 2], xmax);
1148 ymin = fminf(points[i * 2 + 1], ymin);
1149 ymax = fmaxf(points[i * 2 + 1], ymax);
1150 }
1151
1152 // and we set values
1153 *posx = xmin;
1154 *posy = ymin;
1155 *width = (xmax - xmin);
1156 *height = (ymax - ymin);
1157 return 0;
1158}
1159
1160// caller needs to make sure that input remains within bounds
1161static inline float dt_gradient_lookup(const float *lut, const float i)
1162{
1163 const int bin0 = i;
1164 const int bin1 = i + 1;
1165 const float f = i - bin0;
1166 return lut[bin1] * f + lut[bin0] * (1.0f - f);
1167}
1168
1169static int _gradient_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1170 const dt_dev_pixelpipe_iop_t *const piece,
1171 dt_masks_form_t *const form,
1172 float **buffer, int *width, int *height, int *posx, int *posy)
1173{
1174 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
1175 double start2 = 0.0;
1177 // we get the area
1178 if(_gradient_get_area(module, pipe, piece, form, width, height, posx, posy) != 0) return 1;
1179
1181 {
1182 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient area took %0.04f sec\n", form->name,
1183 dt_get_wtime() - start2);
1184 start2 = dt_get_wtime();
1185 }
1186
1187 // we get the gradient values
1188 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)((form->points)->data);
1189 if(IS_NULL_PTR(gradient)) return 0;
1190 // we create a buffer of grid points for later interpolation. mainly in order to reduce memory footprint
1191 const int w = *width;
1192 const int h = *height;
1193 const int px = *posx;
1194 const int py = *posy;
1195 const int grid = 8;
1196 const int gw = (w + grid - 1) / grid + 1;
1197 const int gh = (h + grid - 1) / grid + 1;
1198
1199 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * gw * gh, 0);
1200 if(IS_NULL_PTR(points)) return 1;
1201 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1202 for(int j = 0; j < gh; j++)
1203 for(int i = 0; i < gw; i++)
1204 {
1205 points[(j * gw + i) * 2] = (grid * i + px);
1206 points[(j * gw + i) * 2 + 1] = (grid * j + py);
1207 }
1208
1210 {
1211 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient draw took %0.04f sec\n", form->name,
1212 dt_get_wtime() - start2);
1213 start2 = dt_get_wtime();
1214 }
1215
1216 // we backtransform all these points
1217 if(!dt_dev_distort_backtransform_plus(pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)gw * gh))
1218 {
1220 return 1;
1221 }
1222
1224 {
1225 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient transform took %0.04f sec\n", form->name,
1226 dt_get_wtime() - start2);
1227 start2 = dt_get_wtime();
1228 }
1229
1230 // we calculate the mask at grid points and recycle point buffer to store results
1231 const float wd = pipe->iwidth;
1232 const float ht = pipe->iheight;
1233 const float hwscale = 1.0f / sqrtf(wd * wd + ht * ht);
1234 const float ihwscale = 1.0f / hwscale;
1235 const float v = (-gradient->rotation / 180.0f) * M_PI;
1236 const float sinv = sinf(v);
1237 const float cosv = cosf(v);
1238 const float xoffset = cosv * gradient->center[0] * wd + sinv * gradient->center[1] * ht;
1239 const float yoffset = sinv * gradient->center[0] * wd - cosv * gradient->center[1] * ht;
1240 const float extent = fmaxf(gradient->extent, 0.001f);
1241 const float normf = 1.0f / extent;
1242 const float curvature = gradient->curvature;
1243 const dt_masks_gradient_states_t state = gradient->state;
1244
1245 const int lutmax = ceilf(4 * extent * ihwscale);
1246 const int lutsize = 2 * lutmax + 2;
1248 if(IS_NULL_PTR(lut))
1249 {
1251 return 1;
1252 }
1253 __OMP_PARALLEL_FOR_SIMD__(if(lutsize > 1000) aligned(lut : 64))
1254 for(int n = 0; n < lutsize; n++)
1255 {
1256 const float distance = (n - lutmax) * hwscale;
1257 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / extent));
1258 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1259 }
1260
1261 // center lut around zero
1262 float *clut = lut + lutmax;
1263
1264
1265 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1266 for(int j = 0; j < gh; j++)
1267 {
1268 for(int i = 0; i < gw; i++)
1269 {
1270 const float x = points[(j * gw + i) * 2];
1271 const float y = points[(j * gw + i) * 2 + 1];
1272
1273 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1274 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1275
1276 const float distance = y0 - curvature * x0 * x0;
1277
1278 points[(j * gw + i) * 2] = (distance <= -4.0f * extent) ? 0.0f :
1279 ((distance >= 4.0f * extent) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1280 }
1281 }
1282
1284
1285 // we allocate the buffer
1286 float *const bufptr = *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)w * h, 0);
1287 if(IS_NULL_PTR(*buffer))
1288 {
1290 return 1;
1291 }
1292
1293 const float inv_grid2 = 1.0f / (grid * grid);
1294 float w0[8], w1[8];
1295 for(int i = 0; i < grid; i++)
1296 {
1297 w0[i] = (float)(grid - i);
1298 w1[i] = (float)i;
1299 }
1300
1301// we fill the mask buffer by interpolation
1302 __OMP_PARALLEL_FOR__(if((size_t)w * h > 50000))
1303 for(int j = 0; j < h; j++)
1304 {
1305 const int jj = j % grid;
1306 const int mj = j / grid;
1307 const float wj0 = w0[jj];
1308 const float wj1 = w1[jj];
1309 const size_t row_base = (size_t)mj * gw;
1310 float *const row = bufptr + (size_t)j * w;
1311 int ii = 0;
1312 int mi = 0;
1313 for(int i = 0; i < w; i++)
1314 {
1315 const size_t pt_index = row_base + mi;
1316 const float wii0 = w0[ii];
1317 const float wii1 = w1[ii];
1318 row[i] = (points[2 * pt_index] * wii0 * wj0
1319 + points[2 * (pt_index + 1)] * wii1 * wj0
1320 + points[2 * (pt_index + gw)] * wii0 * wj1
1321 + points[2 * (pt_index + gw + 1)] * wii1 * wj1) * inv_grid2;
1322 ii++;
1323 if(ii == grid)
1324 {
1325 ii = 0;
1326 mi++;
1327 }
1328 }
1329 }
1330
1332
1334 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1335 dt_get_wtime() - start2);
1336
1337 return 0;
1338}
1339
1340
1341static int _gradient_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
1342 const dt_dev_pixelpipe_iop_t *const piece,
1343 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer)
1344{
1345 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
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 0;
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 float *points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * gw * gh, 0);
1363 if(IS_NULL_PTR(points)) return 1;
1364 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1365 for(int j = 0; j < gh; j++)
1366 for(int i = 0; i < gw; i++)
1367 {
1368
1369 const size_t index = (size_t)j * gw + i;
1370 points[index * 2] = (grid * i + px) * iscale;
1371 points[index * 2 + 1] = (grid * j + py) * iscale;
1372 }
1373
1375 {
1376 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient draw took %0.04f sec\n", form->name,
1377 dt_get_wtime() - start2);
1378 start2 = dt_get_wtime();
1379 }
1380
1381 // we backtransform all these points
1383 (size_t)gw * gh))
1384 {
1386 return 1;
1387 }
1388
1390 {
1391 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient transform took %0.04f sec\n", form->name,
1392 dt_get_wtime() - start2);
1393 start2 = dt_get_wtime();
1394 }
1395
1396 // we calculate the mask at grid points and recycle point buffer to store results
1397 const float wd = pipe->iwidth;
1398 const float ht = pipe->iheight;
1399 const float hwscale = 1.0f / sqrtf(wd * wd + ht * ht);
1400 const float ihwscale = 1.0f / hwscale;
1401 const float v = (-gradient->rotation / 180.0f) * M_PI;
1402 const float sinv = sinf(v);
1403 const float cosv = cosf(v);
1404 const float xoffset = cosv * gradient->center[0] * wd + sinv * gradient->center[1] * ht;
1405 const float yoffset = sinv * gradient->center[0] * wd - cosv * gradient->center[1] * ht;
1406 const float extent = fmaxf(gradient->extent, 0.001f);
1407 const float normf = 1.0f / extent;
1408 const float curvature = gradient->curvature;
1409 const dt_masks_gradient_states_t state = gradient->state;
1410
1411 const int lutmax = ceilf(4 * extent * ihwscale);
1412 const int lutsize = 2 * lutmax + 2;
1414 if(IS_NULL_PTR(lut))
1415 {
1417 return 1;
1418 }
1419 __OMP_PARALLEL_FOR_SIMD__(if(lutsize > 1000) aligned(lut : 64))
1420 for(int n = 0; n < lutsize; n++)
1421 {
1422 const float distance = (n - lutmax) * hwscale;
1423 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / extent));
1424 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1425 }
1426
1427 // center lut around zero
1428 float *clut = lut + lutmax;
1429
1430 __OMP_PARALLEL_FOR__(collapse(2) if((size_t)gw * gh > 50000))
1431 for(int j = 0; j < gh; j++)
1432 {
1433 for(int i = 0; i < gw; i++)
1434 {
1435 const size_t index = (size_t)j * gw + i;
1436 const float x = points[index * 2];
1437 const float y = points[index * 2 + 1];
1438
1439 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1440 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1441
1442 const float distance = y0 - curvature * x0 * x0;
1443
1444 points[index * 2] = (distance <= -4.0f * extent) ? 0.0f : ((distance >= 4.0f * extent) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1445 }
1446 }
1447
1449
1450 const float inv_grid2 = 1.0f / (grid * grid);
1451 float w0[8], w1[8];
1452 for(int i = 0; i < grid; i++)
1453 {
1454 w0[i] = (float)(grid - i);
1455 w1[i] = (float)i;
1456 }
1457
1458// we fill the mask buffer by interpolation
1459 __OMP_PARALLEL_FOR__(if((size_t)w * h > 50000))
1460 for(int j = 0; j < h; j++)
1461 {
1462 const int jj = j % grid;
1463 const int mj = j / grid;
1464 const float wj0 = w0[jj];
1465 const float wj1 = w1[jj];
1466 const size_t row_base = (size_t)mj * gw;
1467 float *const row = buffer + (size_t)j * w;
1468 int ii = 0;
1469 int mi = 0;
1470 for(int i = 0; i < w; i++)
1471 {
1472 const size_t mindex = row_base + mi;
1473 const float wii0 = w0[ii];
1474 const float wii1 = w1[ii];
1475 row[i] = (points[mindex * 2] * wii0 * wj0
1476 + points[(mindex + 1) * 2] * wii1 * wj0
1477 + points[(mindex + gw) * 2] * wii0 * wj1
1478 + points[(mindex + gw + 1) * 2] * wii1 * wj1) * inv_grid2;
1479 ii++;
1480 if(ii == grid)
1481 {
1482 ii = 0;
1483 mi++;
1484 }
1485 }
1486 }
1487
1489
1491 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1492 dt_get_wtime() - start2);
1493
1494 return 0;
1495}
1496
1498{
1499 // we always want to start with no curvature
1500 dt_conf_set_float("plugins/darkroom/masks/gradient/curvature", 0.0f);
1501}
1502
1503static void _gradient_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1504{
1505 snprintf(form->name, sizeof(form->name), _("gradient #%d"), (int)nb);
1506}
1507
1508static void _gradient_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1509 const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
1510{
1511 if(gui->creation)
1512 g_snprintf(msgbuf, msgbuf_len, _("<b>Extent</b>: scroll, <b>Curvature</b>: shift+scroll\n"
1513 "<b>Rotate</b>: shift+drag, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1514 else if(gui->form_selected || gui->seg_selected)
1515 g_snprintf(msgbuf, msgbuf_len, _("<b>Extent</b>: scroll, <b>Curvature</b>: shift+scroll\n"
1516 "<b>Reset curvature</b>: double-click, <b>Opacity</b>: ctrl+scroll (%d%%)"), opacity);
1517}
1518
1520{
1521 // unused arg, keep compiler from complaining
1523}
1524
1525// The function table for gradients. This must be public, i.e. no "static" keyword.
1528 .sanitize_config = _gradient_sanitize_config,
1529 .set_form_name = _gradient_set_form_name,
1530 .set_hint_message = _gradient_set_hint_message,
1531 .duplicate_points = _gradient_duplicate_points,
1532 .get_distance = _gradient_get_distance,
1533 .get_points_border = _gradient_get_points_border,
1534 .get_mask = _gradient_get_mask,
1535 .get_mask_roi = _gradient_get_mask_roi,
1536 .get_area = _gradient_get_area,
1537 .get_gravity_center = _gradient_get_gravity_center,
1538 .get_interaction_value = _gradient_get_interaction_value,
1539 .set_interaction_value = _gradient_set_interaction_value,
1540 .update_hover = _find_closest_handle,
1541 .mouse_moved = _gradient_events_mouse_moved,
1542 .mouse_scrolled = _gradient_events_mouse_scrolled,
1543 .button_pressed = _gradient_events_button_pressed,
1544 .button_released = _gradient_events_button_released,
1545 .key_pressed = _gradient_events_key_pressed,
1546 .post_expose = _gradient_events_post_expose,
1547 .draw_shape = _gradient_draw_shape
1548};
1549
1550// clang-format off
1551// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1552// vim: shiftwidth=2 expandtab tabstop=2 cindent
1553// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1554// 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
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:164
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const dt_aligned_pixel_t f
static const int row
int type
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
void dt_conf_set_int(const char *name, int val)
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_PERF
Definition darktable.h:741
@ DT_DEBUG_MASKS
Definition darktable.h:749
#define dt_get_bythread(buf, padsize, tnum)
Definition darktable.h:1100
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:459
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define dt_get_perthread(buf, padsize)
Definition darktable.h:1097
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
#define dt_pixelpipe_cache_calloc_perthread(n, objsize, padded_size)
Definition darktable.h:1081
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:271
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
Definition darktable.h:1092
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1589
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1162
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1621
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1650
void dt_dev_coordinates_raw_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1145
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:105
#define DT_DRAW_SIZE_LINE
Definition draw.h:72
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
#define DT_DRAW_SIZE_LINE_SELECTED
Definition draw.h:73
static void dt_draw_set_color_overlay(cairo_t *cr, gboolean bright, double alpha)
Definition draw.h:106
@ DT_MASKS_DASH_STICK
Definition draw.h:94
@ DT_MASKS_DASH_ROUND
Definition draw.h:95
@ DT_MASKS_NO_DASH
Definition draw.h:93
#define DT_DRAW_SCALE_ARROW
Definition draw.h:80
static void dt_draw_shape_lines(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const int nb, const gboolean selected, const float zoom_scale, const float *points, const int points_count, const shape_draw_function_t *draw_shape_func, const cairo_line_cap_t line_cap)
Draw the lines of a mask shape.
Definition draw.h:734
static void dt_draw_node(cairo_t *cr, const gboolean square, const gboolean point_action, const gboolean selected, const float zoom_scale, const float x, const float y)
Draw an node point of a mask.
Definition draw.h:597
static void _gradient_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int nb)
Definition gradient.c:1069
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:429
static int _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:1127
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, int *inside_source, float *dist)
Definition gradient.c:232
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:625
static float _gradient_get_interaction_value(const dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition gradient.c:392
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:77
static void _copy_points(float *dest, const float *src, int count, int *k)
Definition gradient.c:859
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:326
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, int *inside_source, float *dist, void *user_data)
Definition gradient.c:333
static float dt_gradient_lookup(const float *lut, const float i)
Definition gradient.c:1161
#define CURVATURE_MIN
Definition gradient.c:44
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:681
#define extent_MAX
Definition gradient.c:43
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:675
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:556
static void _gradient_draw_shape(cairo_t *cr, const float *pts_line, const int pts_line_count, const int nb, const gboolean border, const gboolean source)
Definition gradient.c:954
static void _gradient_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form, const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
Definition gradient.c:1508
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:868
static int _find_border_separator(const float *border, int count)
Definition gradient.c:51
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:455
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:472
static void _gradient_get_creation_values(dt_masks_gradient_creation_values_t *values)
Definition gradient.c:187
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:103
static int _init_opacity(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:378
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:993
static int _find_closest_handle(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int index)
Definition gradient.c:355
static int _init_extent(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:364
static int _gradient_get_creation_preview(dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
Definition gradient.c:214
static int _init_curvature(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:371
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:745
static void _gradient_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
Definition gradient.c:1503
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:524
static int _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:1169
static float _gradient_get_border_len_sq(const dt_masks_form_gui_points_t *gpt)
Definition gradient.c:173
static gboolean _gradient_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
Definition gradient.c:411
static void _gradient_init_new(dt_masks_form_gui_t *gui, dt_masks_anchor_gradient_t *gradient)
Definition gradient.c:197
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:499
static void _gradient_duplicate_points(dt_develop_t *dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition gradient.c:1519
static int _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)
Definition gradient.c:1341
static void _gradient_sanitize_config(dt_masks_type_t type)
Definition gradient.c:1497
#define extent_MIN
Definition gradient.c:42
static int _init_rotation(dt_masks_form_t *form, const float amount, const dt_masks_increment_t increment, const int flow)
Definition gradient.c:385
static gboolean _gradient_is_canonical(const float x, const float y, const float wd, const float ht)
Definition gradient.c:732
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:341
const dt_masks_functions_t dt_masks_functions_gradient
Definition gradient.c:1526
static int _gradient_get_points_border(dt_develop_t *dev, dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, int source, const dt_iop_module_t *module)
Definition gradient.c:1109
#define CURVATURE_MAX
Definition gradient.c:45
static const float x
const float *const lut
const float const int lutsize
const int t
const float v
#define w1
Definition lmmse.c:59
float *const restrict const size_t k
void dt_masks_gui_form_create(dt_masks_form_t *form, dt_masks_form_gui_t *gui, int index, struct dt_iop_module_t *module)
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
int dt_masks_form_change_opacity(dt_masks_form_t *form, int parentid, int up, const int flow)
void dt_masks_duplicate_points(const dt_masks_form_t *base, dt_masks_form_t *dest, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
void dt_masks_gui_form_save_creation(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Save the form creation right after a shape has been finished drawing.
dt_masks_type_t
Definition masks.h:130
dt_masks_interaction_t
Definition masks.h:303
@ DT_MASKS_INTERACTION_HARDNESS
Definition masks.h:306
@ DT_MASKS_INTERACTION_SIZE
Definition masks.h:305
@ DT_MASKS_INTERACTION_ROTATION
Definition masks.h:308
static void dt_masks_gui_cursor_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:617
static void dt_masks_preview_buffers_cleanup(dt_masks_preview_buffers_t *buffers)
Definition masks.h:821
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *form, const char *feature, float amount, float v_min, float v_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
float dt_masks_rotate_with_anchor(dt_develop_t *dev, const float anchor[2], const float center[2], dt_masks_form_gui_t *gui)
Rotate a mask shape around its center. WARNING: gui->delta will be updated with the new position afte...
dt_masks_increment_t
Definition masks.h:194
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:196
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:197
@ DT_MASKS_INCREMENT_ABSOLUTE
Definition masks.h:195
dt_masks_gradient_states_t
Definition masks.h:188
@ DT_MASKS_GRADIENT_STATE_SIGMOIDAL
Definition masks.h:190
@ DT_MASKS_GRADIENT_STATE_LINEAR
Definition masks.h:189
static void dt_masks_draw_preview_shape(cairo_t *cr, const float zoom_scale, const int num_points, float *points, const int points_count, float *border, const int border_count, void(*const *draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source), const cairo_line_cap_t shape_cap, const cairo_line_cap_t border_cap, const gboolean save_restore, const gboolean source)
Definition masks.h:787
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Shared selection logic for node/handle/segment hit testing.
static void dt_masks_gui_delta_to_raw_norm(dt_develop_t *dev, const dt_masks_form_gui_t *gui, float point[2])
Definition masks.h:625
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define M_PI
Definition math.h:45
float iscale
Definition mipmap_cache.c:2
static float gh(const float f)
const float uint32_t state[4]
int32_t num_openmp_threads
Definition darktable.h:786
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
int32_t raw_height
Definition develop.h:228
int32_t raw_width
Definition develop.h:228
struct dt_develop_t::@17 roi
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
dt_masks_gradient_states_t state
Definition masks.h:278
gboolean border_toggling
Definition masks.h:502
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
dt_iop_module_t * creation_module
Definition masks.h:519
gboolean seg_selected
Definition masks.h:486
gboolean form_dragging
Definition masks.h:499
gboolean gradient_toggling
Definition masks.h:503
gboolean creation
Definition masks.h:517
gboolean form_selected
Definition masks.h:490
gboolean form_rotating
Definition masks.h:501
gboolean border_selected
Definition masks.h:491
gboolean pivot_selected
Definition masks.h:493
float delta[2]
Definition masks.h:469
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
void(* draw_shape)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition masks.h:370