1 /*
2 This file is part of darktable,
3 Copyright (C) 2013-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "bauhaus/bauhaus.h"
19 #include "common/debug.h"
20 #include "common/undo.h"
21 #include "control/conf.h"
22 #include "control/control.h"
23 #include "develop/blend.h"
24 #include "develop/imageop.h"
25 #include "develop/masks.h"
26 #include "develop/openmp_maths.h"
27
_gradient_get_distance(float x,float y,float as,dt_masks_form_gui_t * gui,int index,int num_points,int * inside,int * inside_border,int * near,int * inside_source,float * dist)28 static void _gradient_get_distance(float x, float y, float as, dt_masks_form_gui_t *gui, int index,
29 int num_points, int *inside, int *inside_border, int *near, int *inside_source, float *dist)
30 {
31 (void)num_points; // unused arg, keep compiler from complaining
32 if(!gui) return;
33
34 *inside = *inside_border = *inside_source = 0;
35 *near = -1;
36 *dist = FLT_MAX;
37
38 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
39 if(!gpt) return;
40
41 const float as2 = as * as;
42
43 float close_to_controls = FALSE;
44
45 // compute distances with the three control points
46 for(int k = 0; k<3; k++)
47 {
48 const float dx = x - gpt->points[k * 2];
49 const float dy = y - gpt->points[k * 2 + 1];
50 const float dd = sqf(dx) + sqf(dy);
51 *dist = fminf(*dist, dd);
52
53 close_to_controls = close_to_controls || (dd < as2);
54 }
55
56 // check if we are close to pivot or anchor
57 if(close_to_controls)
58 {
59 *inside = 1;
60 return;
61 }
62
63 // check if we are close to borders
64 for(int i = 0; i < gpt->border_count; i++)
65 {
66 if((x - gpt->border[i * 2]) * (x - gpt->border[i * 2])
67 + (y - gpt->border[i * 2 + 1]) * (y - gpt->border[i * 2 + 1]) < as2)
68 {
69 *inside_border = 1;
70 return;
71 }
72 }
73
74 // check if we are close to main line
75 for(int i = 3; i < gpt->points_count; i++)
76 {
77 if((x - gpt->points[i * 2]) * (x - gpt->points[i * 2])
78 + (y - gpt->points[i * 2 + 1]) * (y - gpt->points[i * 2 + 1]) < as2)
79 {
80 *inside = 1;
81 return;
82 }
83 }
84 }
85
86
_gradient_events_mouse_scrolled(struct dt_iop_module_t * module,float pzx,float pzy,int up,uint32_t state,dt_masks_form_t * form,int parentid,dt_masks_form_gui_t * gui,int index)87 static int _gradient_events_mouse_scrolled(struct dt_iop_module_t *module, float pzx, float pzy, int up,
88 uint32_t state, dt_masks_form_t *form, int parentid,
89 dt_masks_form_gui_t *gui, int index)
90 {
91 if(gui->creation)
92 {
93 if(dt_modifier_is(state, GDK_SHIFT_MASK))
94 {
95 float compression = MIN(1.0f, dt_conf_get_float("plugins/darkroom/masks/gradient/compression"));
96 if(up)
97 compression = fminf(fmaxf(compression, 0.001f) * 1.0f / 0.8f, 1.0f);
98 else
99 compression = fmaxf(compression, 0.001f) * 0.8f;
100 dt_conf_set_float("plugins/darkroom/masks/gradient/compression", compression);
101 dt_toast_log(_("compression: %3.2f%%"), compression*100.0f);
102 }
103 else if (dt_modifier_is(state, 0)) // simple scroll to adjust curvature, calling func adjusts opacity with Ctrl
104 {
105 float curvature = dt_conf_get_float("plugins/darkroom/masks/gradient/curvature");
106 if(up)
107 curvature = fminf(curvature + 0.01f, 2.0f);
108 else
109 curvature = fmaxf(curvature - 0.01f, -2.0f);
110 dt_conf_set_float("plugins/darkroom/masks/gradient/curvature", curvature);
111 dt_toast_log(_("curvature: %3.2f%%"), curvature * 50.0f);
112 }
113 return 1;
114 }
115
116 if(gui->form_selected)
117 {
118 // we register the current position
119 if(gui->scrollx == 0.0f && gui->scrolly == 0.0f)
120 {
121 gui->scrollx = pzx;
122 gui->scrolly = pzy;
123 }
124 if(dt_modifier_is(state, GDK_CONTROL_MASK))
125 {
126 // we try to change the opacity
127 dt_masks_form_change_opacity(form, parentid, up);
128 }
129 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
130 {
131 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
132 if(up)
133 gradient->compression = fminf(fmaxf(gradient->compression, 0.001f) * 1.0f / 0.8f, 1.0f);
134 else
135 gradient->compression = fmaxf(gradient->compression, 0.001f) * 0.8f;
136 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
137 dt_masks_gui_form_remove(form, gui, index);
138 dt_masks_gui_form_create(form, gui, index, module);
139 dt_conf_set_float("plugins/darkroom/masks/gradient/compression", gradient->compression);
140 dt_toast_log(_("compression: %3.2f%%"), gradient->compression*100.0f);
141 dt_masks_update_image(darktable.develop);
142 }
143 else if(gui->edit_mode == DT_MASKS_EDIT_FULL)
144 {
145 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
146 if(up)
147 gradient->curvature = fminf(gradient->curvature + 0.01f, 2.0f);
148 else
149 gradient->curvature = fmaxf(gradient->curvature - 0.01f, -2.0f);
150 dt_toast_log(_("curvature: %3.2f%%"), gradient->curvature*50.0f);
151 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
152 dt_masks_gui_form_remove(form, gui, index);
153 dt_masks_gui_form_create(form, gui, index, module);
154 dt_masks_update_image(darktable.develop);
155 }
156 return 1;
157 }
158 return 0;
159 }
160
_gradient_events_button_pressed(struct dt_iop_module_t * module,float pzx,float pzy,double pressure,int which,int type,uint32_t state,dt_masks_form_t * form,int parentid,dt_masks_form_gui_t * gui,int index)161 static int _gradient_events_button_pressed(struct dt_iop_module_t *module, float pzx, float pzy,
162 double pressure, int which, int type, uint32_t state,
163 dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
164 {
165 if(!gui) return 0;
166
167 if(which == 1 && type == GDK_2BUTTON_PRESS)
168 {
169 // double-click resets curvature
170 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
171
172 gradient->curvature = 0.0f;
173 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
174
175 dt_masks_gui_form_remove(form, gui, index);
176 dt_masks_gui_form_create(form, gui, index, module);
177
178 dt_masks_update_image(darktable.develop);
179
180 return 1;
181 }
182 else if(!gui->creation && dt_modifier_is(state, GDK_SHIFT_MASK))
183 {
184 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
185 if(!gpt) return 0;
186
187 gui->gradient_toggling = TRUE;
188
189 return 1;
190 }
191 else if(!gui->creation && gui->edit_mode == DT_MASKS_EDIT_FULL)
192 {
193 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
194 if(!gpt) return 0;
195 // we start the form rotating or dragging
196 if(gui->pivot_selected)
197 gui->form_rotating = TRUE;
198 else
199 gui->form_dragging = TRUE;
200 gui->dx = gpt->points[0] - gui->posx;
201 gui->dy = gpt->points[1] - gui->posy;
202 return 1;
203 }
204 else if(gui->creation && (which == 3))
205 {
206 dt_masks_set_edit_mode(module, DT_MASKS_EDIT_FULL);
207 dt_masks_iop_update(module);
208 dt_control_queue_redraw_center();
209 return 1;
210 }
211 else if(gui->creation)
212 {
213 gui->posx_source = gui->posx;
214 gui->posy_source = gui->posy;
215 gui->form_dragging = TRUE;
216 }
217 return 0;
218 }
219
_gradient_init_values(float zoom_scale,dt_masks_form_gui_t * gui,float xpos,float ypos,float pzx,float pzy,float * anchorx,float * anchory,float * rotation,float * compression,float * curvature)220 static void _gradient_init_values(float zoom_scale, dt_masks_form_gui_t *gui, float xpos, float ypos, float pzx,
221 float pzy, float *anchorx, float *anchory, float *rotation, float *compression,
222 float *curvature)
223 {
224 const float pr_d = darktable.develop->preview_downsampling;
225 const float diff = 3.0f * zoom_scale * (pr_d / 2.0);
226 float x0 = 0.0f, y0 = 0.0f;
227 float dx = 0.0f, dy = 0.0f;
228
229 if(!gui->form_dragging
230 || (gui->posx_source - xpos > -diff && gui->posx_source - xpos < diff && gui->posy_source - ypos > -diff
231 && gui->posy_source - ypos < diff))
232 {
233 x0 = pzx;
234 y0 = pzy;
235 // rotation not updated and not yet dragged, in this case let's
236 // pretend that we are using a neutral dx, dy (where the rotation will
237 // still be unchanged). We do that as we don't know the actual rotation
238 // because those points must go through the backtransform.
239 dx = x0 + 100.0f;
240 dy = y0;
241 }
242 else
243 {
244 x0 = gui->posx_source;
245 y0 = gui->posy_source;
246 dx = pzx;
247 dy = pzy;
248 }
249
250 // we change the offset value
251 float pts[8] = { x0, y0, dx, dy, x0 + 10.0f, y0, x0, y0 + 10.0f };
252 dt_dev_distort_backtransform(darktable.develop, pts, 4);
253 *anchorx = pts[0] / darktable.develop->preview_pipe->iwidth;
254 *anchory = pts[1] / darktable.develop->preview_pipe->iheight;
255
256 float rot = atan2f(pts[3] - pts[1], pts[2] - pts[0]);
257 // If the transform has flipped the image about one axis, then the
258 // 'handedness' of the coordinate system is changed. In this case the
259 // rotation angle must be offset by 180 degrees so that the gradient points
260 // in the correct direction as dragged. We test for this by checking the
261 // angle between two vectors that should be 90 degrees apart. If the angle
262 // is -90 degrees, then the image is flipped.
263 float check_angle = atan2f(pts[7] - pts[1], pts[6] - pts[0]) - atan2f(pts[5] - pts[1], pts[4] - pts[0]);
264 // Normalize to the range -180 to 180 degrees
265 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
266 if(check_angle < 0.0f) rot -= M_PI;
267
268 const float compr = MIN(1.0f, dt_conf_get_float("plugins/darkroom/masks/gradient/compression"));
269
270 *rotation = -rot / M_PI * 180.0f;
271 *compression = MAX(0.0f, compr);
272 *curvature = MAX(-2.0f, MIN(2.0f, dt_conf_get_float("plugins/darkroom/masks/gradient/curvature")));
273 }
274
_gradient_events_button_released(struct dt_iop_module_t * module,float pzx,float pzy,int which,uint32_t state,dt_masks_form_t * form,int parentid,dt_masks_form_gui_t * gui,int index)275 static int _gradient_events_button_released(struct dt_iop_module_t *module, float pzx, float pzy, int which,
276 uint32_t state, dt_masks_form_t *form, int parentid,
277 dt_masks_form_gui_t *gui, int index)
278 {
279 if(which == 3 && parentid > 0 && gui->edit_mode == DT_MASKS_EDIT_FULL)
280 {
281 // we hide the form
282 if(!(darktable.develop->form_visible->type & DT_MASKS_GROUP))
283 dt_masks_change_form_gui(NULL);
284 else if(g_list_shorter_than(darktable.develop->form_visible->points, 2))
285 dt_masks_change_form_gui(NULL);
286 else
287 {
288 dt_masks_clear_form_gui(darktable.develop);
289 for(GList *forms = darktable.develop->form_visible->points; forms; forms = g_list_next(forms))
290 {
291 dt_masks_point_group_t *gpt = (dt_masks_point_group_t *)forms->data;
292 if(gpt->formid == form->formid)
293 {
294 darktable.develop->form_visible->points
295 = g_list_remove(darktable.develop->form_visible->points, gpt);
296 free(gpt);
297 break;
298 }
299 }
300 gui->edit_mode = DT_MASKS_EDIT_FULL;
301 }
302
303 // we remove the shape
304 dt_masks_form_remove(module, dt_masks_get_from_id(darktable.develop, parentid), form);
305 return 1;
306 }
307
308 if(gui->form_dragging && gui->edit_mode == DT_MASKS_EDIT_FULL)
309 {
310 // we get the gradient
311 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
312
313 // we end the form dragging
314 gui->form_dragging = FALSE;
315
316 // we change the center value
317 const float wd = darktable.develop->preview_pipe->backbuf_width;
318 const float ht = darktable.develop->preview_pipe->backbuf_height;
319 float pts[2] = { pzx * wd + gui->dx, pzy * ht + gui->dy };
320 dt_dev_distort_backtransform(darktable.develop, pts, 1);
321
322 gradient->anchor[0] = pts[0] / darktable.develop->preview_pipe->iwidth;
323 gradient->anchor[1] = pts[1] / darktable.develop->preview_pipe->iheight;
324 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
325
326 // we recreate the form points
327 dt_masks_gui_form_remove(form, gui, index);
328 dt_masks_gui_form_create(form, gui, index, module);
329
330 // we save the move
331 dt_masks_update_image(darktable.develop);
332
333 return 1;
334 }
335 else if(gui->form_rotating && gui->edit_mode == DT_MASKS_EDIT_FULL)
336 {
337 // we get the gradient
338 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
339
340 // we end the form rotating
341 gui->form_rotating = FALSE;
342
343 const float wd = darktable.develop->preview_pipe->backbuf_width;
344 const float ht = darktable.develop->preview_pipe->backbuf_height;
345 const float x = pzx * wd;
346 const float y = pzy * ht;
347
348 // we need the reference point
349 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
350 if(!gpt) return 0;
351 const float xref = gpt->points[0];
352 const float yref = gpt->points[1];
353
354 float pts[8] = { xref, yref, x , y, 0, 0, gui->dx, gui->dy };
355
356 const float dv = atan2f(pts[3] - pts[1], pts[2] - pts[0]) - atan2f(-(pts[7] - pts[5]), -(pts[6] - pts[4]));
357
358 float pts2[8] = { xref, yref, x , y, xref+10.0f, yref, xref, yref+10.0f };
359
360 dt_dev_distort_backtransform(darktable.develop, pts2, 4);
361
362 float check_angle = atan2f(pts2[7] - pts2[1], pts2[6] - pts2[0]) - atan2f(pts2[5] - pts2[1], pts2[4] - pts2[0]);
363 // Normalize to the range -180 to 180 degrees
364 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
365 if (check_angle < 0)
366 gradient->rotation += dv / M_PI * 180.0f;
367 else
368 gradient->rotation -= dv / M_PI * 180.0f;
369
370 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
371
372 // we recreate the form points
373 dt_masks_gui_form_remove(form, gui, index);
374 dt_masks_gui_form_create(form, gui, index, module);
375
376 // we save the rotation
377 dt_masks_update_image(darktable.develop);
378
379 return 1;
380 }
381 else if(gui->gradient_toggling)
382 {
383 // we get the gradient
384 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
385
386 // we end the gradient toggling
387 gui->gradient_toggling = FALSE;
388
389 // toggle transition type of gradient
390 if(gradient->state == DT_MASKS_GRADIENT_STATE_LINEAR)
391 gradient->state = DT_MASKS_GRADIENT_STATE_SIGMOIDAL;
392 else
393 gradient->state = DT_MASKS_GRADIENT_STATE_LINEAR;
394
395 dt_dev_add_masks_history_item(darktable.develop, module, TRUE);
396
397 // we recreate the form points
398 dt_masks_gui_form_remove(form, gui, index);
399 dt_masks_gui_form_create(form, gui, index, module);
400
401 // we save the new parameters
402 dt_masks_update_image(darktable.develop);
403
404 return 1;
405 }
406 else if(gui->creation)
407 {
408 const float wd = darktable.develop->preview_pipe->backbuf_width;
409 const float ht = darktable.develop->preview_pipe->backbuf_height;
410
411 // get the rotation angle only if we are not too close from starting point
412 const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
413 const int closeup = dt_control_get_dev_closeup();
414 const float zoom_scale = dt_dev_get_zoom_scale(darktable.develop, zoom, 1 << closeup, 1);
415
416 dt_iop_module_t *crea_module = gui->creation_module;
417 // we create the gradient
418 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)(malloc(sizeof(dt_masks_point_gradient_t)));
419
420 _gradient_init_values(zoom_scale, gui, gui->posx, gui->posy, pzx * wd, pzy * ht, &gradient->anchor[0],
421 &gradient->anchor[1], &gradient->rotation, &gradient->compression, &gradient->curvature);
422
423 gui->form_dragging = FALSE;
424
425 gradient->steepness = 0.0f;
426 gradient->state = DT_MASKS_GRADIENT_STATE_SIGMOIDAL;
427 // not used for masks
428 form->source[0] = form->source[1] = 0.0f;
429
430 form->points = g_list_append(form->points, gradient);
431 dt_masks_gui_form_save_creation(darktable.develop, crea_module, form, gui);
432
433 if(crea_module)
434 {
435 // we save the move
436 dt_dev_add_history_item(darktable.develop, crea_module, TRUE);
437 // and we switch in edit mode to show all the forms
438 dt_masks_set_edit_mode(crea_module, DT_MASKS_EDIT_FULL);
439 dt_masks_iop_update(crea_module);
440 dt_dev_masks_selection_change(darktable.develop, crea_module, form->formid, TRUE);
441 gui->creation_module = NULL;
442 }
443 else
444 {
445 // we select the new form
446 dt_dev_masks_selection_change(darktable.develop, NULL, form->formid, TRUE);
447 }
448
449 if(crea_module && gui->creation_continuous)
450 {
451 dt_iop_gui_blend_data_t *bd = (dt_iop_gui_blend_data_t *)crea_module->blend_data;
452 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
453 if(bd->masks_type[n] == form->type)
454 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), TRUE);
455
456 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), FALSE);
457 dt_masks_form_t *newform = dt_masks_create(form->type);
458 dt_masks_change_form_gui(newform);
459 darktable.develop->form_gui->creation = TRUE;
460 darktable.develop->form_gui->creation_module = crea_module;
461 darktable.develop->form_gui->creation_continuous = TRUE;
462 darktable.develop->form_gui->creation_continuous_module = crea_module;
463 }
464 return 1;
465 }
466
467 return 0;
468 }
469
_gradient_events_mouse_moved(struct dt_iop_module_t * module,float pzx,float pzy,double pressure,int which,dt_masks_form_t * form,int parentid,dt_masks_form_gui_t * gui,int index)470 static int _gradient_events_mouse_moved(struct dt_iop_module_t *module, float pzx, float pzy,
471 double pressure, int which, dt_masks_form_t *form, int parentid,
472 dt_masks_form_gui_t *gui, int index)
473 {
474 if(gui->creation && gui->form_dragging)
475 {
476 dt_control_queue_redraw_center();
477 return 1;
478 }
479 else if(gui->form_dragging)
480 {
481 // we get the gradient
482 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
483
484 // we change the center value
485 const float wd = darktable.develop->preview_pipe->backbuf_width;
486 const float ht = darktable.develop->preview_pipe->backbuf_height;
487 float pts[2] = { pzx * wd + gui->dx, pzy * ht + gui->dy };
488 dt_dev_distort_backtransform(darktable.develop, pts, 1);
489
490 gradient->anchor[0] = pts[0] / darktable.develop->preview_pipe->iwidth;
491 gradient->anchor[1] = pts[1] / darktable.develop->preview_pipe->iheight;
492
493 // we recreate the form points
494 dt_masks_gui_form_remove(form, gui, index);
495 dt_masks_gui_form_create(form, gui, index, module);
496 dt_control_queue_redraw_center();
497 return 1;
498 }
499 if(gui->form_rotating)
500 {
501 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
502
503 const float wd = darktable.develop->preview_pipe->backbuf_width;
504 const float ht = darktable.develop->preview_pipe->backbuf_height;
505 const float x = pzx * wd;
506 const float y = pzy * ht;
507
508 // we need the reference point
509 dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
510 if(!gpt) return 0;
511 const float xref = gpt->points[0];
512 const float yref = gpt->points[1];
513
514 float pts[8] = { xref, yref, x, y, 0, 0, gui->dx, gui->dy };
515
516 // we remap dx, dy to the right values, as it will be used in next movements
517 gui->dx = xref - gui->posx;
518 gui->dy = yref - gui->posy;
519
520 const float dv = atan2f(pts[3] - pts[1], pts[2] - pts[0]) - atan2f(-(pts[7] - pts[5]), -(pts[6] - pts[4]));
521
522 float pts2[8] = { xref, yref, x, y, xref + 10.0f, yref, xref, yref + 10.0f };
523 dt_dev_distort_backtransform(darktable.develop, pts2, 4);
524
525 float check_angle = atan2f(pts2[7] - pts2[1], pts2[6] - pts2[0]) - atan2f(pts2[5] - pts2[1], pts2[4] - pts2[0]);
526 // Normalize to the range -180 to 180 degrees
527 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
528 if(check_angle < 0.0f)
529 gradient->rotation += dv / M_PI * 180.0f;
530 else
531 gradient->rotation -= dv / M_PI * 180.0f;
532
533 // we recreate the form points
534 dt_masks_gui_form_remove(form, gui, index);
535 dt_masks_gui_form_create(form, gui, index, module);
536 dt_control_queue_redraw_center();
537 return 1;
538 }
539 else if(!gui->creation)
540 {
541 const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
542 const int closeup = dt_control_get_dev_closeup();
543 const float zoom_scale = dt_dev_get_zoom_scale(darktable.develop, zoom, 1<<closeup, 1);
544 const float pr_d = darktable.develop->preview_downsampling;
545 const float as = DT_PIXEL_APPLY_DPI(20) / (pr_d * zoom_scale); // transformed to backbuf dimensions
546 const float x = pzx * darktable.develop->preview_pipe->backbuf_width;
547 const float y = pzy * darktable.develop->preview_pipe->backbuf_height;
548 int in, inb, near, ins;
549 float dist;
550 _gradient_get_distance(x, y, as, gui, index, 0, &in, &inb, &near, &ins, &dist);
551
552 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
553
554 if(gpt
555 && (x - gpt->points[2]) * (x - gpt->points[2]) + (y - gpt->points[3]) * (y - gpt->points[3]) < as)
556 {
557 gui->pivot_selected = TRUE;
558 gui->form_selected = TRUE;
559 gui->border_selected = FALSE;
560 }
561 else if(gpt
562 && (x - gpt->points[4]) * (x - gpt->points[4]) + (y - gpt->points[5]) * (y - gpt->points[5])
563 < as)
564 {
565 gui->pivot_selected = TRUE;
566 gui->form_selected = TRUE;
567 gui->border_selected = FALSE;
568 }
569 else if(in)
570 {
571 gui->pivot_selected = FALSE;
572 gui->form_selected = TRUE;
573 gui->border_selected = FALSE;
574 }
575 else if(inb)
576 {
577 gui->pivot_selected = FALSE;
578 gui->form_selected = TRUE;
579 gui->border_selected = TRUE;
580 }
581 else
582 {
583 gui->pivot_selected = FALSE;
584 gui->form_selected = FALSE;
585 gui->border_selected = FALSE;
586 }
587
588 dt_control_queue_redraw_center();
589 if(!gui->form_selected && !gui->border_selected) return 0;
590 if(gui->edit_mode != DT_MASKS_EDIT_FULL) return 0;
591 return 1;
592 }
593 // add a preview when creating a gradient
594 else if(gui->creation)
595 {
596 dt_control_queue_redraw_center();
597 return 1;
598 }
599
600 return 0;
601 }
602
603 // check if (x,y) lies within reasonable limits relative to image frame
_gradient_is_canonical(const float x,const float y,const float wd,const float ht)604 static inline int _gradient_is_canonical(const float x, const float y, const float wd, const float ht)
605 {
606 return (isnormal(x) && isnormal(y) && x >= -wd && x <= 2 * wd && y >= -ht && y <= 2 * ht) ? TRUE : FALSE;
607 }
608
_gradient_get_points(dt_develop_t * dev,float x,float y,float rotation,float curvature,float ** points,int * points_count)609 static int _gradient_get_points(dt_develop_t *dev, float x, float y, float rotation, float curvature,
610 float **points, int *points_count)
611 {
612 *points = NULL;
613 *points_count = 0;
614
615 const float wd = dev->preview_pipe->iwidth;
616 const float ht = dev->preview_pipe->iheight;
617 const float scale = sqrtf(wd * wd + ht * ht);
618 const float distance = 0.1f * fminf(wd, ht);
619
620 const float v = (-rotation / 180.0f) * M_PI;
621 const float cosv = cosf(v);
622 const float sinv = sinf(v);
623
624 const int count = sqrtf(wd * wd + ht * ht) + 3;
625 *points = dt_alloc_align_float((size_t)2 * count);
626 if(*points == NULL) return 0;
627
628 // we set the anchor point
629 (*points)[0] = x * wd;
630 (*points)[1] = y * ht;
631
632 // we set the pivot points
633 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
634 const float x1 = x * wd + distance * cosf(v1);
635 const float y1 = y * ht + distance * sinf(v1);
636 (*points)[2] = x1;
637 (*points)[3] = y1;
638 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
639 const float x2 = x * wd + distance * cosf(v2);
640 const float y2 = y * ht + distance * sinf(v2);
641 (*points)[4] = x2;
642 (*points)[5] = y2;
643
644 const int nthreads = omp_get_max_threads();
645 size_t c_padded_size;
646 uint32_t *pts_count = dt_calloc_perthread(nthreads, sizeof(uint32_t), &c_padded_size);
647 float *const restrict pts = dt_alloc_align_float((size_t)2 * count * nthreads);
648
649 // we set the line point
650 const float xstart = fabsf(curvature) > 1.0f ? -sqrtf(1.0f / fabsf(curvature)) : -1.0f;
651 const float xdelta = -2.0f * xstart / (count - 3);
652
653 // gboolean in_frame = FALSE;
654 #ifdef _OPENMP
655 #pragma omp parallel for default(none) \
656 dt_omp_firstprivate(nthreads, pts, pts_count, count, cosv, sinv, xstart, xdelta, curvature, scale, x, y, wd, \
657 ht, c_padded_size, points) schedule(static) if(count > 100)
658 #endif
659 for(int i = 3; i < count; i++)
660 {
661 const float xi = xstart + (i - 3) * xdelta;
662 const float yi = curvature * xi * xi;
663 const float xii = (cosv * xi + sinv * yi) * scale;
664 const float yii = (sinv * xi - cosv * yi) * scale;
665 const float xiii = xii + x * wd;
666 const float yiii = yii + y * ht;
667
668 // don't generate guide points if they extend too far beyond the image frame;
669 // this is to avoid that modules like lens correction fail on out of range coordinates
670 if(!(xiii < -wd || xiii > 2 * wd || yiii < -ht || yiii > 2 * ht))
671 {
672 const int thread = omp_get_thread_num();
673 uint32_t *tcount = dt_get_perthread(pts_count, c_padded_size);
674 pts[(thread * count) + *tcount * 2] = xiii;
675 pts[(thread * count) + *tcount * 2 + 1] = yiii;
676 (*tcount)++;
677 }
678 }
679
680 *points_count = 3;
681 for(int thread = 0; thread < nthreads; thread++)
682 {
683 const uint32_t tcount = *(uint32_t *)dt_get_bythread(pts_count, c_padded_size, thread);
684 for(int k = 0; k < tcount; k++)
685 {
686 (*points)[(*points_count) * 2] = pts[(thread * count) + k * 2];
687 (*points)[(*points_count) * 2 + 1] = pts[(thread * count) + k * 2 + 1];
688 (*points_count)++;
689 }
690 }
691
692 dt_free_align(pts_count);
693 dt_free_align(pts);
694
695 // and we transform them with all distorted modules
696 if(dt_dev_distort_transform(dev, *points, *points_count)) return 1;
697
698 // if we failed, then free all and return
699 dt_free_align(*points);
700 *points = NULL;
701 *points_count = 0;
702 return 0;
703 }
704
_gradient_get_pts_border(dt_develop_t * dev,float x,float y,float rotation,float distance,float curvature,float ** points,int * points_count)705 static int _gradient_get_pts_border(dt_develop_t *dev, float x, float y, float rotation, float distance,
706 float curvature, float **points, int *points_count)
707 {
708 *points = NULL;
709 *points_count = 0;
710
711 float *points1 = NULL, *points2 = NULL;
712 int points_count1 = 0, points_count2 = 0;
713
714 const float wd = dev->preview_pipe->iwidth;
715 const float ht = dev->preview_pipe->iheight;
716 const float scale = sqrtf(wd * wd + ht * ht);
717
718 const float v1 = (-(rotation - 90.0f) / 180.0f) * M_PI;
719
720 const float x1 = (x * wd + distance * scale * cosf(v1)) / wd;
721 const float y1 = (y * ht + distance * scale * sinf(v1)) / ht;
722
723 const int r1 = _gradient_get_points(dev, x1, y1, rotation, curvature, &points1, &points_count1);
724
725 const float v2 = (-(rotation + 90.0f) / 180.0f) * M_PI;
726
727 const float x2 = (x * wd + distance * scale * cosf(v2)) / wd;
728 const float y2 = (y * ht + distance * scale * sinf(v2)) / ht;
729
730 const int r2 = _gradient_get_points(dev, x2, y2, rotation, curvature, &points2, &points_count2);
731
732 int res = 0;
733
734 if(r1 && r2 && points_count1 > 4 && points_count2 > 4)
735 {
736 int k = 0;
737 *points = dt_alloc_align_float((size_t)2 * ((points_count1 - 3) + (points_count2 - 3) + 1));
738 if(*points == NULL) goto end;
739 *points_count = (points_count1 - 3) + (points_count2 - 3) + 1;
740 for(int i = 3; i < points_count1; i++)
741 {
742 (*points)[k * 2] = points1[i * 2];
743 (*points)[k * 2 + 1] = points1[i * 2 + 1];
744 k++;
745 }
746 (*points)[k * 2] = (*points)[k * 2 + 1] = INFINITY;
747 k++;
748 for(int i = 3; i < points_count2; i++)
749 {
750 (*points)[k * 2] = points2[i * 2];
751 (*points)[k * 2 + 1] = points2[i * 2 + 1];
752 k++;
753 }
754 res = 1;
755 goto end;
756 }
757 else if(r1 && points_count1 > 4)
758 {
759 int k = 0;
760 *points = dt_alloc_align_float((size_t)2 * ((points_count1 - 3)));
761 if(*points == NULL) goto end;
762 *points_count = points_count1 - 3;
763 for(int i = 3; i < points_count1; i++)
764 {
765 (*points)[k * 2] = points1[i * 2];
766 (*points)[k * 2 + 1] = points1[i * 2 + 1];
767 k++;
768 }
769 res = 1;
770 goto end;
771 }
772 else if(r2 && points_count2 > 4)
773 {
774 int k = 0;
775 *points = dt_alloc_align_float((size_t)2 * ((points_count2 - 3)));
776 if(*points == NULL) goto end;
777 *points_count = points_count2 - 3;
778
779 for(int i = 3; i < points_count2; i++)
780 {
781 (*points)[k * 2] = points2[i * 2];
782 (*points)[k * 2 + 1] = points2[i * 2 + 1];
783 k++;
784 }
785 res = 1;
786 goto end;
787 }
788
789 end:
790 dt_free_align(points1);
791 dt_free_align(points2);
792
793 return res;
794 }
795
_gradient_draw_lines(gboolean borders,cairo_t * cr,double * dashed,const float len,const gboolean selected,const float zoom_scale,float * pts_line,int pts_line_count,const float xref,const float yref)796 static void _gradient_draw_lines(gboolean borders, cairo_t *cr, double *dashed, const float len,
797 const gboolean selected, const float zoom_scale, float *pts_line,
798 int pts_line_count, const float xref, const float yref)
799 {
800 // safeguard in case of malformed arrays of points
801 if(borders && pts_line_count <= 3) return;
802 if(!borders && pts_line_count <= 4) return;
803
804 const float *points = (borders) ? pts_line : pts_line + 6;
805 const int points_count = (borders) ? pts_line_count : pts_line_count - 3;
806 const float wd = darktable.develop->preview_pipe->iwidth;
807 const float ht = darktable.develop->preview_pipe->iheight;
808
809 int count = 0;
810 float x = 0.0f, y = 0.0f;
811
812 while(count < points_count)
813 {
814 if(!isnormal(points[count * 2]))
815 {
816 count++;
817 continue;
818 }
819
820 x = points[count * 2];
821 y = points[count * 2 + 1];
822
823 if(!_gradient_is_canonical(x, y, wd, ht))
824 {
825 count++;
826 continue;
827 }
828
829 if(borders)
830 cairo_set_dash(cr, dashed, len, 0);
831 else
832 cairo_set_dash(cr, dashed, 0, 0);
833 if(selected)
834 {
835 if(borders)
836 cairo_set_line_width(cr, 2.0 / zoom_scale);
837 else
838 cairo_set_line_width(cr, 5.0 / zoom_scale);
839 }
840 else
841 {
842 if(borders)
843 cairo_set_line_width(cr, 1.0 / zoom_scale);
844 else
845 cairo_set_line_width(cr, 3.0 / zoom_scale);
846 }
847 dt_draw_set_color_overlay(cr, 0.3, 0.8);
848
849 cairo_move_to(cr, x, y);
850
851 count++;
852 for(; count < points_count && isnormal(points[count * 2]); count++)
853 {
854 if(!_gradient_is_canonical(points[count * 2], points[count * 2 + 1], wd, ht)) break;
855
856 cairo_line_to(cr, points[count * 2], points[count * 2 + 1]);
857 }
858 cairo_stroke_preserve(cr);
859 if(selected)
860 cairo_set_line_width(cr, 2.0 / zoom_scale);
861 else
862 cairo_set_line_width(cr, 1.0 / zoom_scale);
863 dt_draw_set_color_overlay(cr, 0.8, 0.8);
864 cairo_stroke(cr);
865 }
866 }
867
_gradient_draw_arrow(cairo_t * cr,double * dashed,const float len,const gboolean selected,const gboolean border_selected,const float zoom_scale,float * pts,int pts_count)868 static void _gradient_draw_arrow(cairo_t *cr, double *dashed, const float len, const gboolean selected,
869 const gboolean border_selected, const float zoom_scale, float *pts, int pts_count)
870 {
871 if(pts_count < 3) return;
872
873 const float anchor_x = pts[0];
874 const float anchor_y = pts[1];
875 const float pivot_end_x = pts[2];
876 const float pivot_end_y = pts[3];
877 const float pivot_start_x = pts[4];
878 const float pivot_start_y = pts[5];
879
880 // draw anchor point
881 {
882 cairo_set_dash(cr, dashed, 0, 0);
883 const float anchor_size = (selected) ? 7.0f / zoom_scale : 5.0f / zoom_scale;
884 dt_draw_set_color_overlay(cr, 0.8, 0.8);
885 cairo_rectangle(cr, anchor_x - (anchor_size * 0.5), anchor_y - (anchor_size * 0.5), anchor_size, anchor_size);
886 cairo_fill_preserve(cr);
887
888 if(selected)
889 cairo_set_line_width(cr, 2.0 / zoom_scale);
890 else
891 cairo_set_line_width(cr, 1.0 / zoom_scale);
892 dt_draw_set_color_overlay(cr, 0.3, 0.8);
893 cairo_stroke(cr);
894 }
895
896
897 // draw pivot points
898 {
899 cairo_set_dash(cr, dashed, 0, 0);
900 if(border_selected)
901 cairo_set_line_width(cr, 2.0 / zoom_scale);
902 else
903 cairo_set_line_width(cr, 1.0 / zoom_scale);
904 dt_draw_set_color_overlay(cr, 0.3, 0.8);
905
906 // from start to end
907 dt_draw_set_color_overlay(cr, 0.8, 0.8);
908 cairo_move_to(cr, pivot_start_x, pivot_start_y);
909 cairo_line_to(cr, pivot_end_x, pivot_end_y);
910 cairo_stroke(cr);
911
912 // start side of the gradient
913 dt_draw_set_color_overlay(cr, 0.3, 0.8);
914 cairo_arc(cr, pivot_start_x, pivot_start_y, 3.0f / zoom_scale, 0, 2.0f * M_PI);
915 cairo_fill_preserve(cr);
916 cairo_stroke(cr);
917
918 // end side of the gradient
919 cairo_arc(cr, pivot_end_x, pivot_end_y, 1.0f / zoom_scale, 0, 2.0f * M_PI);
920 cairo_fill_preserve(cr);
921 dt_draw_set_color_overlay(cr, 0.3, 0.8);
922 cairo_stroke(cr);
923
924 // draw arrow on the end of the gradient to clearly display the direction
925
926 // size & width of the arrow
927 const float arrow_angle = 0.25f;
928 const float arrow_length = 15.0f / zoom_scale;
929
930 const float a_dx = anchor_x - pivot_end_x;
931 const float a_dy = pivot_end_y - anchor_y;
932 const float angle = atan2f(a_dx, a_dy) - M_PI / 2.0f;
933
934 const float arrow_x1 = pivot_end_x + (arrow_length * cosf(angle + arrow_angle));
935 const float arrow_x2 = pivot_end_x + (arrow_length * cosf(angle - arrow_angle));
936 const float arrow_y1 = pivot_end_y + (arrow_length * sinf(angle + arrow_angle));
937 const float arrow_y2 = pivot_end_y + (arrow_length * sinf(angle - arrow_angle));
938
939 dt_draw_set_color_overlay(cr, 0.8, 0.8);
940 cairo_move_to(cr, pivot_end_x, pivot_end_y);
941 cairo_line_to(cr, arrow_x1, arrow_y1);
942 cairo_line_to(cr, arrow_x2, arrow_y2);
943 cairo_line_to(cr, pivot_end_x, pivot_end_y);
944 cairo_close_path(cr);
945 cairo_fill_preserve(cr);
946 cairo_stroke(cr);
947 }
948 }
949
_gradient_events_post_expose(cairo_t * cr,float zoom_scale,dt_masks_form_gui_t * gui,int index,int nb)950 static void _gradient_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_gui_t *gui, int index, int nb)
951 {
952 (void)nb; // unused arg, keep compiler from complaining
953 double dashed[] = { 4.0, 4.0 };
954 dashed[0] /= zoom_scale;
955 dashed[1] /= zoom_scale;
956 const int len = sizeof(dashed) / sizeof(dashed[0]);
957
958 // preview gradient creation
959 if(gui->creation)
960 {
961 const float zoom_x = dt_control_get_dev_zoom_x();
962 const float zoom_y = dt_control_get_dev_zoom_y();
963
964 float xpos = 0.0f, ypos = 0.0f;
965 if((gui->posx == -1.0f && gui->posy == -1.0f) || gui->mouse_leaved_center)
966 {
967 xpos = (.5f + zoom_x) * darktable.develop->preview_pipe->backbuf_width;
968 ypos = (.5f + zoom_y) * darktable.develop->preview_pipe->backbuf_height;
969 }
970 else
971 {
972 xpos = gui->posx;
973 ypos = gui->posy;
974 }
975
976 float xx = 0.0f, yy = 0.0f, rotation = 0.0f, compression = 0.0f, curvature = 0.0f;
977 _gradient_init_values(zoom_scale, gui, xpos, ypos, xpos, ypos, &xx, &yy, &rotation, &compression, &curvature);
978
979 float *points = NULL;
980 int points_count = 0;
981 float *border = NULL;
982 int border_count = 0;
983 int draw = _gradient_get_points(darktable.develop, xx, yy, rotation, curvature, &points, &points_count);
984 if(draw && compression > 0.0)
985 {
986 draw = _gradient_get_pts_border(darktable.develop, xx, yy, rotation, compression, curvature, &border,
987 &border_count);
988 }
989
990 cairo_save(cr);
991 // draw main line
992 _gradient_draw_lines(FALSE, cr, dashed, len, FALSE, zoom_scale, points, points_count, points[0], points[1]);
993 // draw borders
994 _gradient_draw_lines(TRUE, cr, dashed, len, FALSE, zoom_scale, border, border_count, points[0], points[1]);
995 // draw arrow
996 _gradient_draw_arrow(cr, dashed, len, FALSE, FALSE, zoom_scale, points, points_count);
997 cairo_restore(cr);
998
999 if(points) dt_free_align(points);
1000 if(border) dt_free_align(border);
1001 return;
1002 }
1003 const dt_masks_form_gui_points_t *gpt = (dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, index);
1004 if(!gpt) return;
1005 const float xref = gpt->points[0];
1006 const float yref = gpt->points[1];
1007
1008 const gboolean selected = (gui->group_selected == index) && (gui->form_selected || gui->form_dragging);
1009 // draw main line
1010 _gradient_draw_lines(FALSE, cr, dashed, len, selected, zoom_scale, gpt->points, gpt->points_count, xref, yref);
1011 // draw borders
1012 if(gui->group_selected == index)
1013 _gradient_draw_lines(TRUE, cr, dashed, len, gui->border_selected, zoom_scale, gpt->border, gpt->border_count,
1014 xref, yref);
1015
1016 _gradient_draw_arrow(cr, dashed, len, selected, ((gui->group_selected == index) && (gui->border_selected)),
1017 zoom_scale, gpt->points, gpt->points_count);
1018 }
1019
_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)1020 static int _gradient_get_points_border(dt_develop_t *dev, dt_masks_form_t *form, float **points, int *points_count,
1021 float **border, int *border_count, int source,
1022 const dt_iop_module_t *module)
1023 {
1024 (void)source; // unused arg, keep compiler from complaining
1025 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)form->points->data;
1026 if(_gradient_get_points(dev, gradient->anchor[0], gradient->anchor[1], gradient->rotation, gradient->curvature,
1027 points, points_count))
1028 {
1029 if(border)
1030 return _gradient_get_pts_border(dev, gradient->anchor[0], gradient->anchor[1],
1031 gradient->rotation, gradient->compression, gradient->curvature,
1032 border, border_count);
1033 else
1034 return 1;
1035 }
1036 return 0;
1037 }
1038
_gradient_get_area(const dt_iop_module_t * const module,const dt_dev_pixelpipe_iop_t * const piece,dt_masks_form_t * const form,int * width,int * height,int * posx,int * posy)1039 static int _gradient_get_area(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
1040 dt_masks_form_t *const form,
1041 int *width, int *height, int *posx, int *posy)
1042 {
1043 const float wd = piece->pipe->iwidth, ht = piece->pipe->iheight;
1044
1045 float points[8] = { 0.0f, 0.0f, wd, 0.0f, wd, ht, 0.0f, ht };
1046
1047 // and we transform them with all distorted modules
1048 if(!dt_dev_distort_transform_plus(module->dev, piece->pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, 4)) return 0;
1049
1050 // now we search min and max
1051 float xmin = 0.0f, xmax = 0.0f, ymin = 0.0f, ymax = 0.0f;
1052 xmin = ymin = FLT_MAX;
1053 xmax = ymax = FLT_MIN;
1054 for(int i = 0; i < 4; i++)
1055 {
1056 xmin = fminf(points[i * 2], xmin);
1057 xmax = fmaxf(points[i * 2], xmax);
1058 ymin = fminf(points[i * 2 + 1], ymin);
1059 ymax = fmaxf(points[i * 2 + 1], ymax);
1060 }
1061
1062 // and we set values
1063 *posx = xmin;
1064 *posy = ymin;
1065 *width = (xmax - xmin);
1066 *height = (ymax - ymin);
1067 return 1;
1068 }
1069
1070 // caller needs to make sure that input remains within bounds
dt_gradient_lookup(const float * lut,const float i)1071 static inline float dt_gradient_lookup(const float *lut, const float i)
1072 {
1073 const int bin0 = i;
1074 const int bin1 = i + 1;
1075 const float f = i - bin0;
1076 return lut[bin1] * f + lut[bin0] * (1.0f - f);
1077 }
1078
_gradient_get_mask(const dt_iop_module_t * const module,const dt_dev_pixelpipe_iop_t * const piece,dt_masks_form_t * const form,float ** buffer,int * width,int * height,int * posx,int * posy)1079 static int _gradient_get_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
1080 dt_masks_form_t *const form,
1081 float **buffer, int *width, int *height, int *posx, int *posy)
1082 {
1083 double start2 = 0.0;
1084 if(darktable.unmuted & DT_DEBUG_PERF) start2 = dt_get_wtime();
1085 // we get the area
1086 if(!_gradient_get_area(module, piece, form, width, height, posx, posy)) return 0;
1087
1088 if(darktable.unmuted & DT_DEBUG_PERF)
1089 {
1090 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient area took %0.04f sec\n", form->name,
1091 dt_get_wtime() - start2);
1092 start2 = dt_get_wtime();
1093 }
1094
1095 // we get the gradient values
1096 dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)((form->points)->data);
1097
1098 // we create a buffer of grid points for later interpolation. mainly in order to reduce memory footprint
1099 const int w = *width;
1100 const int h = *height;
1101 const int px = *posx;
1102 const int py = *posy;
1103 const int grid = 8;
1104 const int gw = (w + grid - 1) / grid + 1;
1105 const int gh = (h + grid - 1) / grid + 1;
1106
1107 float *points = dt_alloc_align_float((size_t)2 * gw * gh);
1108 if(points == NULL) return 0;
1109
1110 #ifdef _OPENMP
1111 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1112 #pragma omp parallel for default(none) \
1113 dt_omp_firstprivate(grid, gh, gw, px, py) \
1114 shared(points) schedule(static) collapse(2)
1115 #else
1116 #pragma omp parallel for shared(points)
1117 #endif
1118 #endif
1119 for(int j = 0; j < gh; j++)
1120 for(int i = 0; i < gw; i++)
1121 {
1122 points[(j * gw + i) * 2] = (grid * i + px);
1123 points[(j * gw + i) * 2 + 1] = (grid * j + py);
1124 }
1125
1126 if(darktable.unmuted & DT_DEBUG_PERF)
1127 {
1128 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient draw took %0.04f sec\n", form->name,
1129 dt_get_wtime() - start2);
1130 start2 = dt_get_wtime();
1131 }
1132
1133 // we backtransform all these points
1134 if(!dt_dev_distort_backtransform_plus(module->dev, piece->pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, (size_t)gw * gh))
1135 {
1136 dt_free_align(points);
1137 return 0;
1138 }
1139
1140 if(darktable.unmuted & DT_DEBUG_PERF)
1141 {
1142 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient transform took %0.04f sec\n", form->name,
1143 dt_get_wtime() - start2);
1144 start2 = dt_get_wtime();
1145 }
1146
1147 // we calculate the mask at grid points and recycle point buffer to store results
1148 const float wd = piece->pipe->iwidth;
1149 const float ht = piece->pipe->iheight;
1150 const float hwscale = 1.0f / sqrtf(wd * wd + ht * ht);
1151 const float ihwscale = 1.0f / hwscale;
1152 const float v = (-gradient->rotation / 180.0f) * M_PI;
1153 const float sinv = sinf(v);
1154 const float cosv = cosf(v);
1155 const float xoffset = cosv * gradient->anchor[0] * wd + sinv * gradient->anchor[1] * ht;
1156 const float yoffset = sinv * gradient->anchor[0] * wd - cosv * gradient->anchor[1] * ht;
1157 const float compression = fmaxf(gradient->compression, 0.001f);
1158 const float normf = 1.0f / compression;
1159 const float curvature = gradient->curvature;
1160 const dt_masks_gradient_states_t state = gradient->state;
1161
1162 const int lutmax = ceilf(4 * compression * ihwscale);
1163 const int lutsize = 2 * lutmax + 2;
1164 float *lut = dt_alloc_align_float((size_t)lutsize);
1165 if(lut == NULL)
1166 {
1167 dt_free_align(points);
1168 return 0;
1169 }
1170
1171 #ifdef _OPENMP
1172 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1173 #pragma omp parallel for default(none) \
1174 dt_omp_firstprivate(lutsize, lutmax, hwscale, state, normf, compression) \
1175 shared(lut) schedule(static)
1176 #else
1177 #pragma omp parallel for shared(points)
1178 #endif
1179 #endif
1180 for(int n = 0; n < lutsize; n++)
1181 {
1182 const float distance = (n - lutmax) * hwscale;
1183 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / compression));
1184 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1185 }
1186
1187 // center lut around zero
1188 float *clut = lut + lutmax;
1189
1190
1191 #ifdef _OPENMP
1192 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1193 #pragma omp parallel for default(none) \
1194 dt_omp_firstprivate(gh, gw, sinv, cosv, xoffset, yoffset, hwscale, ihwscale, curvature, compression) \
1195 shared(points, clut) schedule(static) collapse(2)
1196 #else
1197 #pragma omp parallel for shared(points)
1198 #endif
1199 #endif
1200 for(int j = 0; j < gh; j++)
1201 {
1202 for(int i = 0; i < gw; i++)
1203 {
1204 const float x = points[(j * gw + i) * 2];
1205 const float y = points[(j * gw + i) * 2 + 1];
1206
1207 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1208 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1209
1210 const float distance = y0 - curvature * x0 * x0;
1211
1212 points[(j * gw + i) * 2] = (distance <= -4.0f * compression) ? 0.0f :
1213 ((distance >= 4.0f * compression) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1214 }
1215 }
1216
1217 dt_free_align(lut);
1218
1219 // we allocate the buffer
1220 float *const bufptr = *buffer = dt_alloc_align_float((size_t)w * h);
1221 if(*buffer == NULL)
1222 {
1223 dt_free_align(points);
1224 return 0;
1225 }
1226
1227 // we fill the mask buffer by interpolation
1228 #ifdef _OPENMP
1229 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1230 #pragma omp parallel for default(none) \
1231 dt_omp_firstprivate(h, w, gw, grid, bufptr) \
1232 shared(points) schedule(simd:static)
1233 #else
1234 #pragma omp parallel for shared(points, buffer)
1235 #endif
1236 #endif
1237 for(int j = 0; j < h; j++)
1238 {
1239 const int jj = j % grid;
1240 const int mj = j / grid;
1241 const int grid_jj = grid - jj;
1242 for(int i = 0; i < w; i++)
1243 {
1244 const int ii = i % grid;
1245 const int mi = i / grid;
1246 const int grid_ii = grid - ii;
1247 const size_t pt_index = mj * gw + mi;
1248 bufptr[j * w + i] = (points[2 * pt_index] * grid_ii * grid_jj
1249 + points[2 * (pt_index + 1)] * ii * grid_jj
1250 + points[2 * (pt_index + gw)] * grid_ii * jj
1251 + points[2 * (pt_index + gw + 1)] * ii * jj) / (grid * grid);
1252 }
1253 }
1254
1255 dt_free_align(points);
1256
1257 if(darktable.unmuted & DT_DEBUG_PERF)
1258 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1259 dt_get_wtime() - start2);
1260
1261 return 1;
1262 }
1263
1264
_gradient_get_mask_roi(const dt_iop_module_t * const module,const dt_dev_pixelpipe_iop_t * const piece,dt_masks_form_t * const form,const dt_iop_roi_t * roi,float * buffer)1265 static int _gradient_get_mask_roi(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
1266 dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer)
1267 {
1268 double start2 = 0.0;
1269 if(darktable.unmuted & DT_DEBUG_PERF) start2 = dt_get_wtime();
1270 // we get the gradient values
1271 const dt_masks_point_gradient_t *gradient = (dt_masks_point_gradient_t *)(form->points->data);
1272
1273 // we create a buffer of grid points for later interpolation. mainly in order to reduce memory footprint
1274 const int w = roi->width;
1275 const int h = roi->height;
1276 const int px = roi->x;
1277 const int py = roi->y;
1278 const float iscale = 1.0f / roi->scale;
1279 const int grid = CLAMP((10.0f*roi->scale + 2.0f) / 3.0f, 1, 4);
1280 const int gw = (w + grid - 1) / grid + 1;
1281 const int gh = (h + grid - 1) / grid + 1;
1282
1283 float *points = dt_alloc_align_float((size_t)2 * gw * gh);
1284 if(points == NULL) return 0;
1285
1286 #ifdef _OPENMP
1287 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1288 #pragma omp parallel for default(none) \
1289 dt_omp_firstprivate(iscale, gh, gw, py, px, grid) \
1290 shared(points) schedule(static) collapse(2)
1291 #else
1292 #pragma omp parallel for shared(points)
1293 #endif
1294 #endif
1295 for(int j = 0; j < gh; j++)
1296 for(int i = 0; i < gw; i++)
1297 {
1298
1299 const size_t index = (size_t)j * gw + i;
1300 points[index * 2] = (grid * i + px) * iscale;
1301 points[index * 2 + 1] = (grid * j + py) * iscale;
1302 }
1303
1304 if(darktable.unmuted & DT_DEBUG_PERF)
1305 {
1306 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient draw took %0.04f sec\n", form->name,
1307 dt_get_wtime() - start2);
1308 start2 = dt_get_wtime();
1309 }
1310
1311 // we backtransform all these points
1312 if(!dt_dev_distort_backtransform_plus(module->dev, piece->pipe, module->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points,
1313 (size_t)gw * gh))
1314 {
1315 dt_free_align(points);
1316 return 0;
1317 }
1318
1319 if(darktable.unmuted & DT_DEBUG_PERF)
1320 {
1321 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient transform took %0.04f sec\n", form->name,
1322 dt_get_wtime() - start2);
1323 start2 = dt_get_wtime();
1324 }
1325
1326 // we calculate the mask at grid points and recycle point buffer to store results
1327 const float wd = piece->pipe->iwidth;
1328 const float ht = piece->pipe->iheight;
1329 const float hwscale = 1.0f / sqrtf(wd * wd + ht * ht);
1330 const float ihwscale = 1.0f / hwscale;
1331 const float v = (-gradient->rotation / 180.0f) * M_PI;
1332 const float sinv = sinf(v);
1333 const float cosv = cosf(v);
1334 const float xoffset = cosv * gradient->anchor[0] * wd + sinv * gradient->anchor[1] * ht;
1335 const float yoffset = sinv * gradient->anchor[0] * wd - cosv * gradient->anchor[1] * ht;
1336 const float compression = fmaxf(gradient->compression, 0.001f);
1337 const float normf = 1.0f / compression;
1338 const float curvature = gradient->curvature;
1339 const dt_masks_gradient_states_t state = gradient->state;
1340
1341 const int lutmax = ceilf(4 * compression * ihwscale);
1342 const int lutsize = 2 * lutmax + 2;
1343 float *lut = dt_alloc_align_float((size_t)lutsize);
1344 if(lut == NULL)
1345 {
1346 dt_free_align(points);
1347 return 0;
1348 }
1349
1350 #ifdef _OPENMP
1351 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1352 #pragma omp parallel for default(none) \
1353 dt_omp_firstprivate(lutsize, lutmax, hwscale, state, normf, compression) \
1354 shared(lut) schedule(static)
1355 #else
1356 #pragma omp parallel for shared(points)
1357 #endif
1358 #endif
1359 for(int n = 0; n < lutsize; n++)
1360 {
1361 const float distance = (n - lutmax) * hwscale;
1362 const float value = 0.5f + 0.5f * ((state == DT_MASKS_GRADIENT_STATE_LINEAR) ? normf * distance: erff(distance / compression));
1363 lut[n] = (value < 0.0f) ? 0.0f : ((value > 1.0f) ? 1.0f : value);
1364 }
1365
1366 // center lut around zero
1367 float *clut = lut + lutmax;
1368
1369 #ifdef _OPENMP
1370 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1371 #pragma omp parallel for default(none) \
1372 dt_omp_firstprivate(gh, gw, sinv, cosv, xoffset, yoffset, hwscale, ihwscale, curvature, compression) \
1373 shared(points, clut) schedule(static) collapse(2)
1374 #else
1375 #pragma omp parallel for shared(points)
1376 #endif
1377 #endif
1378 for(int j = 0; j < gh; j++)
1379 {
1380 for(int i = 0; i < gw; i++)
1381 {
1382 const size_t index = (size_t)j * gw + i;
1383 const float x = points[index * 2];
1384 const float y = points[index * 2 + 1];
1385
1386 const float x0 = (cosv * x + sinv * y - xoffset) * hwscale;
1387 const float y0 = (sinv * x - cosv * y - yoffset) * hwscale;
1388
1389 const float distance = y0 - curvature * x0 * x0;
1390
1391 points[index * 2] = (distance <= -4.0f * compression) ? 0.0f : ((distance >= 4.0f * compression) ? 1.0f : dt_gradient_lookup(clut, distance * ihwscale));
1392 }
1393 }
1394
1395 dt_free_align(lut);
1396
1397 // we fill the mask buffer by interpolation
1398 #ifdef _OPENMP
1399 #if !defined(__SUNOS__) && !defined(__NetBSD__)
1400 #pragma omp parallel for default(none) \
1401 dt_omp_firstprivate(h, w, grid, gw) \
1402 shared(buffer, points) schedule(simd:static)
1403 #else
1404 #pragma omp parallel for shared(points, buffer)
1405 #endif
1406 #endif
1407 for(int j = 0; j < h; j++)
1408 {
1409 const int jj = j % grid;
1410 const int mj = j / grid;
1411 const int grid_jj = grid - jj;
1412 for(int i = 0; i < w; i++)
1413 {
1414 const int ii = i % grid;
1415 const int mi = i / grid;
1416 const int grid_ii = grid - ii;
1417 const size_t mindex = (size_t)mj * gw + mi;
1418 buffer[(size_t)j * w + i]
1419 = (points[mindex * 2] * grid_ii * grid_jj
1420 + points[(mindex + 1) * 2] * ii * grid_jj
1421 + points[(mindex + gw) * 2] * grid_ii * jj
1422 + points[(mindex + gw + 1) * 2] * ii * jj)
1423 / (grid * grid);
1424 }
1425 }
1426
1427 dt_free_align(points);
1428
1429 if(darktable.unmuted & DT_DEBUG_PERF)
1430 dt_print(DT_DEBUG_MASKS, "[masks %s] gradient fill took %0.04f sec\n", form->name,
1431 dt_get_wtime() - start2);
1432
1433 return 1;
1434 }
1435
_gradient_setup_mouse_actions(const struct dt_masks_form_t * const form)1436 static GSList *_gradient_setup_mouse_actions(const struct dt_masks_form_t *const form)
1437 {
1438 GSList *lm = NULL;
1439 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_LEFT_DRAG, 0, _("[GRADIENT on pivot] rotate shape"));
1440 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_LEFT_DRAG, 0, _("[GRADIENT creation] set rotation"));
1441 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, 0, _("[GRADIENT] change curvature"));
1442 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, GDK_SHIFT_MASK, _("[GRADIENT] change compression"));
1443 lm = dt_mouse_action_create_simple(lm, DT_MOUSE_ACTION_SCROLL, GDK_CONTROL_MASK, _("[GRADIENT] change opacity"));
1444 return lm;
1445 }
1446
_gradient_sanitize_config(dt_masks_type_t type)1447 static void _gradient_sanitize_config(dt_masks_type_t type)
1448 {
1449 // we always want to start with no curvature
1450 dt_conf_set_float("plugins/darkroom/masks/gradient/curvature", 0.0f);
1451 }
1452
_gradient_set_form_name(struct dt_masks_form_t * const form,const size_t nb)1453 static void _gradient_set_form_name(struct dt_masks_form_t *const form, const size_t nb)
1454 {
1455 snprintf(form->name, sizeof(form->name), _("gradient #%d"), (int)nb);
1456 }
1457
_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)1458 static void _gradient_set_hint_message(const dt_masks_form_gui_t *const gui, const dt_masks_form_t *const form,
1459 const int opacity, char *const restrict msgbuf, const size_t msgbuf_len)
1460 {
1461 if(gui->creation)
1462 g_snprintf(msgbuf, msgbuf_len,
1463 _("<b>curvature</b>: scroll, <b>compression</b>: shift+scroll\n"
1464 "<b>rotation</b>: click+drag, <b>opacity</b>: ctrl+scroll (%d%%)"),
1465 opacity);
1466 else if(gui->form_selected)
1467 g_snprintf(msgbuf, msgbuf_len, _("<b>curvature</b>: scroll, <b>compression</b>: shift+scroll\n"
1468 "<b>opacity</b>: ctrl+scroll (%d%%)"), opacity);
1469 else if(gui->pivot_selected)
1470 g_strlcat(msgbuf, _("<b>rotate</b>: drag"), msgbuf_len);
1471 }
1472
_gradient_duplicate_points(dt_develop_t * dev,dt_masks_form_t * const base,dt_masks_form_t * const dest)1473 static void _gradient_duplicate_points(dt_develop_t *dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
1474 {
1475 (void)dev; // unused arg, keep compiler from complaining
1476 for(GList *pts = base->points; pts; pts = g_list_next(pts))
1477 {
1478 dt_masks_point_gradient_t *pt = (dt_masks_point_gradient_t *)pts->data;
1479 dt_masks_point_gradient_t *npt = (dt_masks_point_gradient_t *)malloc(sizeof(dt_masks_point_gradient_t));
1480 memcpy(npt, pt, sizeof(dt_masks_point_gradient_t));
1481 dest->points = g_list_append(dest->points, npt);
1482 }
1483 }
1484
1485 // The function table for gradients. This must be public, i.e. no "static" keyword.
1486 const dt_masks_functions_t dt_masks_functions_gradient = {
1487 .point_struct_size = sizeof(struct dt_masks_point_gradient_t),
1488 .sanitize_config = _gradient_sanitize_config,
1489 .setup_mouse_actions = _gradient_setup_mouse_actions,
1490 .set_form_name = _gradient_set_form_name,
1491 .set_hint_message = _gradient_set_hint_message,
1492 .duplicate_points = _gradient_duplicate_points,
1493 .get_distance = _gradient_get_distance,
1494 .get_points_border = _gradient_get_points_border,
1495 .get_mask = _gradient_get_mask,
1496 .get_mask_roi = _gradient_get_mask_roi,
1497 .get_area = _gradient_get_area,
1498 .mouse_moved = _gradient_events_mouse_moved,
1499 .mouse_scrolled = _gradient_events_mouse_scrolled,
1500 .button_pressed = _gradient_events_button_pressed,
1501 .button_released = _gradient_events_button_released,
1502 .post_expose = _gradient_events_post_expose
1503 };
1504
1505 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
1506 // vim: shiftwidth=2 expandtab tabstop=2 cindent
1507 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1508