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