1 /* GIMP - The GNU Image Manipulation Program
2  *
3  * gimpcagetool.c
4  * Copyright (C) 2010 Michael Muré <batolettre@gmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include <string.h>
23 
24 #include <gegl.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27 
28 #include "libgimpmath/gimpmath.h"
29 #include "libgimpwidgets/gimpwidgets.h"
30 
31 #include "tools-types.h"
32 
33 #include "config/gimpguiconfig.h"
34 
35 #include "gegl/gimp-gegl-utils.h"
36 
37 #include "operations/gimpcageconfig.h"
38 
39 #include "core/gimp.h"
40 #include "core/gimpdrawablefilter.h"
41 #include "core/gimperror.h"
42 #include "core/gimpimage.h"
43 #include "core/gimpitem.h"
44 #include "core/gimpprogress.h"
45 #include "core/gimpprojection.h"
46 
47 #include "widgets/gimphelp-ids.h"
48 #include "widgets/gimpwidgets-utils.h"
49 
50 #include "display/gimpcanvasitem.h"
51 #include "display/gimpdisplay.h"
52 
53 #include "gimpcagetool.h"
54 #include "gimpcageoptions.h"
55 #include "gimptoolcontrol.h"
56 #include "gimptools-utils.h"
57 
58 #include "gimp-intl.h"
59 
60 
61 /* XXX: if this state list is updated, in particular if for some reason,
62    a new CAGE_STATE_* was to be inserted after CAGE_STATE_CLOSING, check
63    if the function gimp_cage_tool_is_complete() has to be updated.
64    Current algorithm is that all DEFORM_* states are complete states,
65    and all CAGE_* states are incomplete states. */
66 enum
67 {
68   CAGE_STATE_INIT,
69   CAGE_STATE_WAIT,
70   CAGE_STATE_MOVE_HANDLE,
71   CAGE_STATE_SELECTING,
72   CAGE_STATE_CLOSING,
73   DEFORM_STATE_WAIT,
74   DEFORM_STATE_MOVE_HANDLE,
75   DEFORM_STATE_SELECTING
76 };
77 
78 
79 static gboolean   gimp_cage_tool_initialize         (GimpTool              *tool,
80                                                      GimpDisplay           *display,
81                                                      GError               **error);
82 static void       gimp_cage_tool_control            (GimpTool              *tool,
83                                                      GimpToolAction         action,
84                                                      GimpDisplay           *display);
85 static void       gimp_cage_tool_button_press       (GimpTool              *tool,
86                                                      const GimpCoords      *coords,
87                                                      guint32                time,
88                                                      GdkModifierType        state,
89                                                      GimpButtonPressType    press_type,
90                                                      GimpDisplay           *display);
91 static void       gimp_cage_tool_button_release     (GimpTool              *tool,
92                                                      const GimpCoords      *coords,
93                                                      guint32                time,
94                                                      GdkModifierType        state,
95                                                      GimpButtonReleaseType  release_type,
96                                                      GimpDisplay           *display);
97 static void       gimp_cage_tool_motion             (GimpTool              *tool,
98                                                      const GimpCoords      *coords,
99                                                      guint32                time,
100                                                      GdkModifierType        state,
101                                                      GimpDisplay           *display);
102 static gboolean   gimp_cage_tool_key_press          (GimpTool              *tool,
103                                                      GdkEventKey           *kevent,
104                                                      GimpDisplay           *display);
105 static void       gimp_cage_tool_cursor_update      (GimpTool              *tool,
106                                                      const GimpCoords      *coords,
107                                                      GdkModifierType        state,
108                                                      GimpDisplay           *display);
109 static void       gimp_cage_tool_oper_update        (GimpTool              *tool,
110                                                      const GimpCoords      *coords,
111                                                      GdkModifierType        state,
112                                                      gboolean               proximity,
113                                                      GimpDisplay           *display);
114 static void       gimp_cage_tool_options_notify     (GimpTool              *tool,
115                                                      GimpToolOptions       *options,
116                                                      const GParamSpec      *pspec);
117 
118 static void       gimp_cage_tool_draw               (GimpDrawTool          *draw_tool);
119 
120 static void       gimp_cage_tool_start              (GimpCageTool          *ct,
121                                                      GimpDisplay           *display);
122 static void       gimp_cage_tool_halt               (GimpCageTool          *ct);
123 static void       gimp_cage_tool_commit             (GimpCageTool          *ct);
124 
125 static gint       gimp_cage_tool_is_on_handle       (GimpCageTool          *ct,
126                                                      GimpDrawTool          *draw_tool,
127                                                      GimpDisplay           *display,
128                                                      gdouble                x,
129                                                      gdouble                y,
130                                                      gint                   handle_size);
131 static gint       gimp_cage_tool_is_on_edge         (GimpCageTool          *ct,
132                                                      gdouble                x,
133                                                      gdouble                y,
134                                                      gint                   handle_size);
135 
136 static gboolean   gimp_cage_tool_is_complete        (GimpCageTool          *ct);
137 static void       gimp_cage_tool_remove_last_handle (GimpCageTool          *ct);
138 static void       gimp_cage_tool_compute_coef       (GimpCageTool          *ct);
139 static void       gimp_cage_tool_create_filter      (GimpCageTool          *ct);
140 static void       gimp_cage_tool_filter_flush       (GimpDrawableFilter    *filter,
141                                                      GimpTool              *tool);
142 static void       gimp_cage_tool_filter_update      (GimpCageTool          *ct);
143 
144 static void       gimp_cage_tool_create_render_node (GimpCageTool          *ct);
145 static void       gimp_cage_tool_render_node_update (GimpCageTool          *ct);
146 
147 
G_DEFINE_TYPE(GimpCageTool,gimp_cage_tool,GIMP_TYPE_DRAW_TOOL)148 G_DEFINE_TYPE (GimpCageTool, gimp_cage_tool, GIMP_TYPE_DRAW_TOOL)
149 
150 #define parent_class gimp_cage_tool_parent_class
151 
152 
153 void
154 gimp_cage_tool_register (GimpToolRegisterCallback  callback,
155                          gpointer                  data)
156 {
157   (* callback) (GIMP_TYPE_CAGE_TOOL,
158                 GIMP_TYPE_CAGE_OPTIONS,
159                 gimp_cage_options_gui,
160                 0,
161                 "gimp-cage-tool",
162                 _("Cage Transform"),
163                 _("Cage Transform: Deform a selection with a cage"),
164                 N_("_Cage Transform"), "<shift>G",
165                 NULL, GIMP_HELP_TOOL_CAGE,
166                 GIMP_ICON_TOOL_CAGE,
167                 data);
168 }
169 
170 static void
gimp_cage_tool_class_init(GimpCageToolClass * klass)171 gimp_cage_tool_class_init (GimpCageToolClass *klass)
172 {
173   GimpToolClass     *tool_class      = GIMP_TOOL_CLASS (klass);
174   GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
175 
176   tool_class->initialize     = gimp_cage_tool_initialize;
177   tool_class->control        = gimp_cage_tool_control;
178   tool_class->button_press   = gimp_cage_tool_button_press;
179   tool_class->button_release = gimp_cage_tool_button_release;
180   tool_class->key_press      = gimp_cage_tool_key_press;
181   tool_class->motion         = gimp_cage_tool_motion;
182   tool_class->cursor_update  = gimp_cage_tool_cursor_update;
183   tool_class->oper_update    = gimp_cage_tool_oper_update;
184   tool_class->options_notify = gimp_cage_tool_options_notify;
185 
186   draw_tool_class->draw      = gimp_cage_tool_draw;
187 }
188 
189 static void
gimp_cage_tool_init(GimpCageTool * self)190 gimp_cage_tool_init (GimpCageTool *self)
191 {
192   GimpTool *tool = GIMP_TOOL (self);
193 
194   gimp_tool_control_set_preserve    (tool->control, FALSE);
195   gimp_tool_control_set_dirty_mask  (tool->control,
196                                      GIMP_DIRTY_IMAGE           |
197                                      GIMP_DIRTY_IMAGE_STRUCTURE |
198                                      GIMP_DIRTY_DRAWABLE        |
199                                      GIMP_DIRTY_SELECTION       |
200                                      GIMP_DIRTY_ACTIVE_DRAWABLE);
201   gimp_tool_control_set_wants_click (tool->control, TRUE);
202   gimp_tool_control_set_precision   (tool->control,
203                                      GIMP_CURSOR_PRECISION_SUBPIXEL);
204   gimp_tool_control_set_tool_cursor (tool->control,
205                                      GIMP_TOOL_CURSOR_PERSPECTIVE);
206 
207   self->config          = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
208   self->hovering_handle = -1;
209   self->tool_state      = CAGE_STATE_INIT;
210 }
211 
212 static gboolean
gimp_cage_tool_initialize(GimpTool * tool,GimpDisplay * display,GError ** error)213 gimp_cage_tool_initialize (GimpTool     *tool,
214                            GimpDisplay  *display,
215                            GError      **error)
216 {
217   GimpGuiConfig *config   = GIMP_GUI_CONFIG (display->gimp->config);
218   GimpImage     *image    = gimp_display_get_image (display);
219   GimpDrawable  *drawable = gimp_image_get_active_drawable (image);
220 
221   if (! drawable)
222     return FALSE;
223 
224   if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
225     {
226       g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
227                            _("Cannot modify the pixels of layer groups."));
228       return FALSE;
229     }
230 
231   if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
232     {
233       g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
234                            _("The active layer's pixels are locked."));
235       if (error)
236         gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
237       return FALSE;
238     }
239 
240   if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
241       ! config->edit_non_visible)
242     {
243       g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
244                            _("The active layer is not visible."));
245       return FALSE;
246     }
247 
248   gimp_cage_tool_start (GIMP_CAGE_TOOL (tool), display);
249 
250   return TRUE;
251 }
252 
253 static void
gimp_cage_tool_control(GimpTool * tool,GimpToolAction action,GimpDisplay * display)254 gimp_cage_tool_control (GimpTool       *tool,
255                         GimpToolAction  action,
256                         GimpDisplay    *display)
257 {
258   GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
259 
260   switch (action)
261     {
262     case GIMP_TOOL_ACTION_PAUSE:
263     case GIMP_TOOL_ACTION_RESUME:
264       break;
265 
266     case GIMP_TOOL_ACTION_HALT:
267       gimp_cage_tool_halt (ct);
268       break;
269 
270     case GIMP_TOOL_ACTION_COMMIT:
271       gimp_cage_tool_commit (ct);
272       break;
273     }
274 
275   GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
276 }
277 
278 static void
gimp_cage_tool_button_press(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type,GimpDisplay * display)279 gimp_cage_tool_button_press (GimpTool            *tool,
280                              const GimpCoords    *coords,
281                              guint32              time,
282                              GdkModifierType      state,
283                              GimpButtonPressType  press_type,
284                              GimpDisplay         *display)
285 {
286   GimpCageTool *ct        = GIMP_CAGE_TOOL (tool);
287   GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
288   gint          handle    = -1;
289   gint          edge      = -1;
290 
291   gimp_tool_control_activate (tool->control);
292 
293   if (ct->config)
294     {
295       handle = gimp_cage_tool_is_on_handle (ct,
296                                             draw_tool,
297                                             display,
298                                             coords->x,
299                                             coords->y,
300                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE);
301       edge = gimp_cage_tool_is_on_edge (ct,
302                                         coords->x,
303                                         coords->y,
304                                         GIMP_TOOL_HANDLE_SIZE_CIRCLE);
305     }
306 
307   ct->movement_start_x = coords->x;
308   ct->movement_start_y = coords->y;
309 
310   switch (ct->tool_state)
311     {
312     case CAGE_STATE_INIT:
313       /* No handle yet, we add the first one and switch the tool to
314        * moving handle state.
315        */
316       gimp_cage_config_add_cage_point (ct->config,
317                                        coords->x - ct->offset_x,
318                                        coords->y - ct->offset_y);
319       gimp_cage_config_select_point (ct->config, 0);
320       ct->tool_state = CAGE_STATE_MOVE_HANDLE;
321       break;
322 
323     case CAGE_STATE_WAIT:
324       if (handle == -1 && edge <= 0)
325         {
326           /* User clicked on the background, we add a new handle
327            * and move it
328            */
329           gimp_cage_config_add_cage_point (ct->config,
330                                            coords->x - ct->offset_x,
331                                            coords->y - ct->offset_y);
332           gimp_cage_config_select_point (ct->config,
333                                          gimp_cage_config_get_n_points (ct->config) - 1);
334           ct->tool_state = CAGE_STATE_MOVE_HANDLE;
335         }
336       else if (handle == 0 && gimp_cage_config_get_n_points (ct->config) > 2)
337         {
338           /* User clicked on the first handle, we wait for
339            * release for closing the cage and switching to
340            * deform if possible
341            */
342           gimp_cage_config_select_point (ct->config, 0);
343           ct->tool_state = CAGE_STATE_CLOSING;
344         }
345       else if (handle >= 0)
346         {
347           /* User clicked on a handle, so we move it */
348 
349           if (state & gimp_get_extend_selection_mask ())
350             {
351               /* Multiple selection */
352 
353               gimp_cage_config_toggle_point_selection (ct->config, handle);
354             }
355           else
356             {
357               /* New selection */
358 
359               if (! gimp_cage_config_point_is_selected (ct->config, handle))
360                 {
361                   gimp_cage_config_select_point (ct->config, handle);
362                 }
363             }
364 
365           ct->tool_state = CAGE_STATE_MOVE_HANDLE;
366         }
367       else if (edge > 0)
368         {
369           /* User clicked on an edge, we add a new handle here and select it */
370 
371           gimp_cage_config_insert_cage_point (ct->config, edge,
372                                               coords->x, coords->y);
373           gimp_cage_config_select_point (ct->config, edge);
374           ct->tool_state = CAGE_STATE_MOVE_HANDLE;
375         }
376       break;
377 
378     case DEFORM_STATE_WAIT:
379       if (handle == -1)
380         {
381           /* User clicked on the background, we start a rubber band
382            * selection
383            */
384           ct->selection_start_x = coords->x;
385           ct->selection_start_y = coords->y;
386           ct->tool_state = DEFORM_STATE_SELECTING;
387         }
388 
389       if (handle >= 0)
390         {
391           /* User clicked on a handle, so we move it */
392 
393           if (state & gimp_get_extend_selection_mask ())
394             {
395               /* Multiple selection */
396 
397               gimp_cage_config_toggle_point_selection (ct->config, handle);
398             }
399           else
400             {
401               /* New selection */
402 
403               if (! gimp_cage_config_point_is_selected (ct->config, handle))
404                 {
405                   gimp_cage_config_select_point (ct->config, handle);
406                 }
407             }
408 
409           ct->tool_state = DEFORM_STATE_MOVE_HANDLE;
410         }
411       break;
412     }
413 }
414 
415 void
gimp_cage_tool_button_release(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type,GimpDisplay * display)416 gimp_cage_tool_button_release (GimpTool              *tool,
417                                const GimpCoords      *coords,
418                                guint32                time,
419                                GdkModifierType        state,
420                                GimpButtonReleaseType  release_type,
421                                GimpDisplay           *display)
422 {
423   GimpCageTool    *ct      = GIMP_CAGE_TOOL (tool);
424   GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
425 
426   gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
427 
428   gimp_tool_control_halt (tool->control);
429 
430   if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
431     {
432       /* Cancelling */
433 
434       switch (ct->tool_state)
435         {
436         case CAGE_STATE_CLOSING:
437           ct->tool_state = CAGE_STATE_WAIT;
438           break;
439 
440         case CAGE_STATE_MOVE_HANDLE:
441           gimp_cage_config_remove_last_cage_point (ct->config);
442           ct->tool_state = CAGE_STATE_WAIT;
443           break;
444 
445         case CAGE_STATE_SELECTING:
446           ct->tool_state = CAGE_STATE_WAIT;
447           break;
448 
449         case DEFORM_STATE_MOVE_HANDLE:
450           gimp_cage_tool_filter_update (ct);
451           ct->tool_state = DEFORM_STATE_WAIT;
452           break;
453 
454         case DEFORM_STATE_SELECTING:
455           ct->tool_state = DEFORM_STATE_WAIT;
456           break;
457         }
458 
459       gimp_cage_config_reset_displacement (ct->config);
460     }
461   else
462     {
463       /* Normal release */
464 
465       switch (ct->tool_state)
466         {
467         case CAGE_STATE_CLOSING:
468           ct->dirty_coef = TRUE;
469           gimp_cage_config_commit_displacement (ct->config);
470 
471           if (release_type == GIMP_BUTTON_RELEASE_CLICK)
472             g_object_set (options, "cage-mode", GIMP_CAGE_MODE_DEFORM, NULL);
473           break;
474 
475         case CAGE_STATE_MOVE_HANDLE:
476           ct->dirty_coef = TRUE;
477           ct->tool_state = CAGE_STATE_WAIT;
478           gimp_cage_config_commit_displacement (ct->config);
479           break;
480 
481         case CAGE_STATE_SELECTING:
482           {
483             GeglRectangle area =
484               { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
485                 MIN (ct->selection_start_y, coords->y) - ct->offset_y,
486                 ABS (ct->selection_start_x - coords->x),
487                 ABS (ct->selection_start_y - coords->y) };
488 
489             if (state & gimp_get_extend_selection_mask ())
490               {
491                 gimp_cage_config_select_add_area (ct->config,
492                                                   GIMP_CAGE_MODE_CAGE_CHANGE,
493                                                   area);
494               }
495             else
496               {
497                 gimp_cage_config_select_area (ct->config,
498                                               GIMP_CAGE_MODE_CAGE_CHANGE,
499                                               area);
500               }
501 
502             ct->tool_state = CAGE_STATE_WAIT;
503           }
504           break;
505 
506         case DEFORM_STATE_MOVE_HANDLE:
507           ct->tool_state = DEFORM_STATE_WAIT;
508           gimp_cage_config_commit_displacement (ct->config);
509           gegl_node_set (ct->cage_node,
510                          "config", ct->config,
511                          NULL);
512           gimp_cage_tool_filter_update (ct);
513           break;
514 
515         case DEFORM_STATE_SELECTING:
516           {
517             GeglRectangle area =
518               { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
519                 MIN (ct->selection_start_y, coords->y) - ct->offset_y,
520                 ABS (ct->selection_start_x - coords->x),
521                 ABS (ct->selection_start_y - coords->y) };
522 
523             if (state & gimp_get_extend_selection_mask ())
524               {
525                 gimp_cage_config_select_add_area (ct->config,
526                                                   GIMP_CAGE_MODE_DEFORM, area);
527               }
528             else
529               {
530                 gimp_cage_config_select_area (ct->config,
531                                               GIMP_CAGE_MODE_DEFORM, area);
532               }
533 
534             ct->tool_state = DEFORM_STATE_WAIT;
535           }
536           break;
537         }
538     }
539 
540   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
541 }
542 
543 static void
gimp_cage_tool_motion(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpDisplay * display)544 gimp_cage_tool_motion (GimpTool         *tool,
545                        const GimpCoords *coords,
546                        guint32           time,
547                        GdkModifierType   state,
548                        GimpDisplay      *display)
549 {
550   GimpCageTool    *ct       = GIMP_CAGE_TOOL (tool);
551   GimpCageOptions *options  = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
552 
553   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
554 
555   ct->cursor_x = coords->x;
556   ct->cursor_y = coords->y;
557 
558   switch (ct->tool_state)
559     {
560     case CAGE_STATE_MOVE_HANDLE:
561     case CAGE_STATE_CLOSING:
562     case DEFORM_STATE_MOVE_HANDLE:
563       gimp_cage_config_add_displacement (ct->config,
564                                          options->cage_mode,
565                                          ct->cursor_x - ct->movement_start_x,
566                                          ct->cursor_y - ct->movement_start_y);
567       break;
568     }
569 
570   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
571 }
572 
573 static gboolean
gimp_cage_tool_key_press(GimpTool * tool,GdkEventKey * kevent,GimpDisplay * display)574 gimp_cage_tool_key_press (GimpTool    *tool,
575                           GdkEventKey *kevent,
576                           GimpDisplay *display)
577 {
578   GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
579 
580   if (! ct->config)
581     return FALSE;
582 
583   switch (kevent->keyval)
584     {
585     case GDK_KEY_BackSpace:
586       if (ct->tool_state == CAGE_STATE_WAIT)
587         {
588           if (gimp_cage_config_get_n_points (ct->config) != 0)
589             gimp_cage_tool_remove_last_handle (ct);
590         }
591       else if (ct->tool_state == DEFORM_STATE_WAIT)
592         {
593           gimp_cage_config_remove_selected_points (ct->config);
594 
595           /* if the cage have less than 3 handles, we reopen it */
596           if (gimp_cage_config_get_n_points (ct->config) <= 2)
597             {
598               ct->tool_state = CAGE_STATE_WAIT;
599             }
600 
601           gimp_cage_tool_compute_coef (ct);
602           gimp_cage_tool_render_node_update (ct);
603         }
604       return TRUE;
605 
606     case GDK_KEY_Return:
607     case GDK_KEY_KP_Enter:
608     case GDK_KEY_ISO_Enter:
609       if (! gimp_cage_tool_is_complete (ct) &&
610           gimp_cage_config_get_n_points (ct->config) > 2)
611         {
612           g_object_set (gimp_tool_get_options (tool),
613                         "cage-mode", GIMP_CAGE_MODE_DEFORM,
614                         NULL);
615         }
616       else if (ct->tool_state == DEFORM_STATE_WAIT)
617         {
618           gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
619         }
620       return TRUE;
621 
622     case GDK_KEY_Escape:
623       gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
624       return TRUE;
625 
626     default:
627       break;
628     }
629 
630   return FALSE;
631 }
632 
633 static void
gimp_cage_tool_oper_update(GimpTool * tool,const GimpCoords * coords,GdkModifierType state,gboolean proximity,GimpDisplay * display)634 gimp_cage_tool_oper_update (GimpTool         *tool,
635                             const GimpCoords *coords,
636                             GdkModifierType   state,
637                             gboolean          proximity,
638                             GimpDisplay      *display)
639 {
640   GimpCageTool *ct        = GIMP_CAGE_TOOL (tool);
641   GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
642 
643   if (ct->config)
644     {
645       ct->hovering_handle = gimp_cage_tool_is_on_handle (ct,
646                                                          draw_tool,
647                                                          display,
648                                                          coords->x,
649                                                          coords->y,
650                                                          GIMP_TOOL_HANDLE_SIZE_CIRCLE);
651 
652       ct->hovering_edge = gimp_cage_tool_is_on_edge (ct,
653                                                      coords->x,
654                                                      coords->y,
655                                                      GIMP_TOOL_HANDLE_SIZE_CIRCLE);
656     }
657 
658   gimp_draw_tool_pause (draw_tool);
659 
660   ct->cursor_x = coords->x;
661   ct->cursor_y = coords->y;
662 
663   gimp_draw_tool_resume (draw_tool);
664 }
665 
666 static void
gimp_cage_tool_cursor_update(GimpTool * tool,const GimpCoords * coords,GdkModifierType state,GimpDisplay * display)667 gimp_cage_tool_cursor_update (GimpTool         *tool,
668                               const GimpCoords *coords,
669                               GdkModifierType   state,
670                               GimpDisplay      *display)
671 {
672   GimpCageTool       *ct       = GIMP_CAGE_TOOL (tool);
673   GimpCageOptions    *options  = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
674   GimpGuiConfig      *config   = GIMP_GUI_CONFIG (display->gimp->config);
675   GimpCursorModifier  modifier = GIMP_CURSOR_MODIFIER_PLUS;
676 
677   if (tool->display)
678     {
679       if (ct->hovering_handle != -1)
680         {
681           modifier = GIMP_CURSOR_MODIFIER_MOVE;
682         }
683       else if (ct->hovering_edge  != -1 &&
684                options->cage_mode == GIMP_CAGE_MODE_CAGE_CHANGE)
685         {
686           modifier = GIMP_CURSOR_MODIFIER_PLUS;
687         }
688       else
689         {
690           if (gimp_cage_tool_is_complete (ct))
691             modifier = GIMP_CURSOR_MODIFIER_BAD;
692         }
693     }
694   else
695     {
696       GimpImage    *image    = gimp_display_get_image (display);
697       GimpDrawable *drawable = gimp_image_get_active_drawable (image);
698 
699       if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
700           gimp_item_is_content_locked (GIMP_ITEM (drawable))    ||
701           ! (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
702              config->edit_non_visible))
703         {
704           modifier = GIMP_CURSOR_MODIFIER_BAD;
705         }
706     }
707 
708   gimp_tool_control_set_cursor_modifier (tool->control, modifier);
709 
710   GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
711 }
712 
713 static void
gimp_cage_tool_options_notify(GimpTool * tool,GimpToolOptions * options,const GParamSpec * pspec)714 gimp_cage_tool_options_notify (GimpTool         *tool,
715                                GimpToolOptions  *options,
716                                const GParamSpec *pspec)
717 {
718   GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
719 
720   GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
721 
722   if (! tool->display)
723     return;
724 
725   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
726 
727   if (strcmp (pspec->name, "cage-mode") == 0)
728     {
729       GimpCageMode mode;
730 
731       g_object_get (options,
732                     "cage-mode", &mode,
733                     NULL);
734 
735       if (mode == GIMP_CAGE_MODE_DEFORM)
736         {
737           /* switch to deform mode */
738 
739           if (gimp_cage_config_get_n_points (ct->config) > 2)
740             {
741               gimp_cage_config_reset_displacement (ct->config);
742               gimp_cage_config_reverse_cage_if_needed (ct->config);
743               gimp_tool_push_status (tool, tool->display,
744                                      _("Press ENTER to commit the transform"));
745               ct->tool_state = DEFORM_STATE_WAIT;
746 
747               if (! ct->render_node)
748                 {
749                   gimp_cage_tool_create_render_node (ct);
750                 }
751 
752               if (ct->dirty_coef)
753                 {
754                   gimp_cage_tool_compute_coef (ct);
755                   gimp_cage_tool_render_node_update (ct);
756                 }
757 
758               if (! ct->filter)
759                 gimp_cage_tool_create_filter (ct);
760 
761               gimp_cage_tool_filter_update (ct);
762             }
763           else
764             {
765               g_object_set (options,
766                             "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
767                             NULL);
768             }
769         }
770       else
771         {
772           /* switch to edit mode */
773           if (ct->filter)
774             {
775               gimp_drawable_filter_abort (ct->filter);
776 
777               gimp_tool_pop_status (tool, tool->display);
778               ct->tool_state = CAGE_STATE_WAIT;
779             }
780         }
781     }
782   else if (strcmp  (pspec->name, "fill-plain-color") == 0)
783     {
784       if (ct->tool_state == DEFORM_STATE_WAIT)
785         {
786           gimp_cage_tool_render_node_update (ct);
787           gimp_cage_tool_filter_update (ct);
788         }
789     }
790 
791   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
792 }
793 
794 static void
gimp_cage_tool_draw(GimpDrawTool * draw_tool)795 gimp_cage_tool_draw (GimpDrawTool *draw_tool)
796 {
797   GimpCageTool    *ct        = GIMP_CAGE_TOOL (draw_tool);
798   GimpCageOptions *options   = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
799   GimpCageConfig  *config    = ct->config;
800   GimpCanvasGroup *stroke_group;
801   gint             n_vertices;
802   gint             i;
803   GimpHandleType   handle;
804 
805   n_vertices = gimp_cage_config_get_n_points (config);
806 
807   if (n_vertices == 0)
808     return;
809 
810   if (ct->tool_state == CAGE_STATE_INIT)
811     return;
812 
813   stroke_group = gimp_draw_tool_add_stroke_group (draw_tool);
814 
815   gimp_draw_tool_push_group (draw_tool, stroke_group);
816 
817   /* If needed, draw line to the cursor. */
818   if (! gimp_cage_tool_is_complete (ct))
819     {
820       GimpVector2 last_point;
821 
822       last_point = gimp_cage_config_get_point_coordinate (ct->config,
823                                                           options->cage_mode,
824                                                           n_vertices - 1);
825 
826       gimp_draw_tool_add_line (draw_tool,
827                                last_point.x + ct->offset_x,
828                                last_point.y + ct->offset_y,
829                                ct->cursor_x,
830                                ct->cursor_y);
831     }
832 
833   gimp_draw_tool_pop_group (draw_tool);
834 
835   /* Draw the cage with handles. */
836   for (i = 0; i < n_vertices; i++)
837     {
838       GimpCanvasItem *item;
839       GimpVector2     point1, point2;
840 
841       point1 = gimp_cage_config_get_point_coordinate (ct->config,
842                                                       options->cage_mode,
843                                                       i);
844       point1.x += ct->offset_x;
845       point1.y += ct->offset_y;
846 
847       if (i > 0 || gimp_cage_tool_is_complete (ct))
848         {
849           gint index_point2;
850 
851           if (i == 0)
852             index_point2 = n_vertices - 1;
853           else
854             index_point2 = i - 1;
855 
856           point2 = gimp_cage_config_get_point_coordinate (ct->config,
857                                                           options->cage_mode,
858                                                           index_point2);
859           point2.x += ct->offset_x;
860           point2.y += ct->offset_y;
861 
862           if (i != ct->hovering_edge ||
863               gimp_cage_tool_is_complete (ct))
864             {
865               gimp_draw_tool_push_group (draw_tool, stroke_group);
866             }
867 
868           item = gimp_draw_tool_add_line (draw_tool,
869                                           point1.x,
870                                           point1.y,
871                                           point2.x,
872                                           point2.y);
873 
874           if (i == ct->hovering_edge &&
875               ! gimp_cage_tool_is_complete (ct))
876             {
877               gimp_canvas_item_set_highlight (item, TRUE);
878             }
879           else
880             {
881               gimp_draw_tool_pop_group (draw_tool);
882             }
883         }
884 
885       if (gimp_cage_config_point_is_selected (ct->config, i))
886         {
887           if (i == ct->hovering_handle)
888             handle = GIMP_HANDLE_FILLED_SQUARE;
889           else
890             handle = GIMP_HANDLE_SQUARE;
891         }
892       else
893         {
894           if (i == ct->hovering_handle)
895             handle = GIMP_HANDLE_FILLED_CIRCLE;
896           else
897             handle = GIMP_HANDLE_CIRCLE;
898         }
899 
900       item = gimp_draw_tool_add_handle (draw_tool,
901                                         handle,
902                                         point1.x,
903                                         point1.y,
904                                         GIMP_TOOL_HANDLE_SIZE_CIRCLE,
905                                         GIMP_TOOL_HANDLE_SIZE_CIRCLE,
906                                         GIMP_HANDLE_ANCHOR_CENTER);
907 
908       if (i == ct->hovering_handle)
909         gimp_canvas_item_set_highlight (item, TRUE);
910     }
911 
912   if (ct->tool_state == DEFORM_STATE_SELECTING ||
913       ct->tool_state == CAGE_STATE_SELECTING)
914     {
915       gimp_draw_tool_add_rectangle (draw_tool,
916                                     FALSE,
917                                     MIN (ct->selection_start_x, ct->cursor_x),
918                                     MIN (ct->selection_start_y, ct->cursor_y),
919                                     ABS (ct->selection_start_x - ct->cursor_x),
920                                     ABS (ct->selection_start_y - ct->cursor_y));
921     }
922 }
923 
924 static void
gimp_cage_tool_start(GimpCageTool * ct,GimpDisplay * display)925 gimp_cage_tool_start (GimpCageTool *ct,
926                       GimpDisplay  *display)
927 {
928   GimpTool     *tool     = GIMP_TOOL (ct);
929   GimpImage    *image    = gimp_display_get_image (display);
930   GimpDrawable *drawable = gimp_image_get_active_drawable (image);
931 
932   tool->display  = display;
933   tool->drawable = drawable;
934 
935   g_clear_object (&ct->config);
936 
937   g_clear_object (&ct->coef);
938   ct->dirty_coef = TRUE;
939 
940   if (ct->filter)
941     {
942       gimp_drawable_filter_abort (ct->filter);
943       g_clear_object (&ct->filter);
944     }
945 
946   if (ct->render_node)
947     {
948       g_clear_object (&ct->render_node);
949       ct->coef_node = NULL;
950       ct->cage_node = NULL;
951     }
952 
953   ct->config          = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
954   ct->hovering_handle = -1;
955   ct->hovering_edge   = -1;
956   ct->tool_state      = CAGE_STATE_INIT;
957 
958   /* Setting up cage offset to convert the cage point coords to
959    * drawable coords
960    */
961   gimp_item_get_offset (GIMP_ITEM (tool->drawable),
962                         &ct->offset_x, &ct->offset_y);
963 
964   gimp_draw_tool_start (GIMP_DRAW_TOOL (ct), display);
965 }
966 
967 static void
gimp_cage_tool_halt(GimpCageTool * ct)968 gimp_cage_tool_halt (GimpCageTool *ct)
969 {
970   GimpTool *tool = GIMP_TOOL (ct);
971 
972   g_clear_object (&ct->config);
973   g_clear_object (&ct->coef);
974   g_clear_object (&ct->render_node);
975   ct->coef_node = NULL;
976   ct->cage_node = NULL;
977 
978   if (ct->filter)
979     {
980       gimp_tool_control_push_preserve (tool->control, TRUE);
981 
982       gimp_drawable_filter_abort (ct->filter);
983       g_clear_object (&ct->filter);
984 
985       gimp_tool_control_pop_preserve (tool->control);
986 
987       gimp_image_flush (gimp_display_get_image (tool->display));
988     }
989 
990   tool->display  = NULL;
991   tool->drawable = NULL;
992   ct->tool_state = CAGE_STATE_INIT;
993 
994   g_object_set (gimp_tool_get_options (tool),
995                 "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
996                 NULL);
997 }
998 
999 static void
gimp_cage_tool_commit(GimpCageTool * ct)1000 gimp_cage_tool_commit (GimpCageTool *ct)
1001 {
1002   if (ct->filter)
1003     {
1004       GimpTool *tool = GIMP_TOOL (ct);
1005 
1006       gimp_tool_control_push_preserve (tool->control, TRUE);
1007 
1008       gimp_drawable_filter_commit (ct->filter, GIMP_PROGRESS (tool), FALSE);
1009       g_clear_object (&ct->filter);
1010 
1011       gimp_tool_control_pop_preserve (tool->control);
1012 
1013       gimp_image_flush (gimp_display_get_image (tool->display));
1014     }
1015 }
1016 
1017 static gint
gimp_cage_tool_is_on_handle(GimpCageTool * ct,GimpDrawTool * draw_tool,GimpDisplay * display,gdouble x,gdouble y,gint handle_size)1018 gimp_cage_tool_is_on_handle (GimpCageTool *ct,
1019                              GimpDrawTool *draw_tool,
1020                              GimpDisplay  *display,
1021                              gdouble       x,
1022                              gdouble       y,
1023                              gint          handle_size)
1024 {
1025   GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
1026   GimpCageConfig  *config  = ct->config;
1027   gdouble          dist    = G_MAXDOUBLE;
1028   gint             i;
1029   GimpVector2      cage_point;
1030   guint            n_cage_vertices;
1031 
1032   g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
1033 
1034   n_cage_vertices = gimp_cage_config_get_n_points (config);
1035 
1036   if (n_cage_vertices == 0)
1037     return -1;
1038 
1039   for (i = 0; i < n_cage_vertices; i++)
1040     {
1041       cage_point = gimp_cage_config_get_point_coordinate (config,
1042                                                           options->cage_mode,
1043                                                           i);
1044       cage_point.x += ct->offset_x;
1045       cage_point.y += ct->offset_y;
1046 
1047       dist = gimp_draw_tool_calc_distance_square (GIMP_DRAW_TOOL (draw_tool),
1048                                                   display,
1049                                                   x, y,
1050                                                   cage_point.x,
1051                                                   cage_point.y);
1052 
1053       if (dist <= SQR (handle_size / 2))
1054         return i;
1055     }
1056 
1057   return -1;
1058 }
1059 
1060 static gint
gimp_cage_tool_is_on_edge(GimpCageTool * ct,gdouble x,gdouble y,gint handle_size)1061 gimp_cage_tool_is_on_edge (GimpCageTool *ct,
1062                            gdouble       x,
1063                            gdouble       y,
1064                            gint          handle_size)
1065 {
1066   GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
1067   GimpCageConfig  *config  = ct->config;
1068   gint             i;
1069   guint            n_cage_vertices;
1070   GimpVector2      A, B, C, AB, BC, AC;
1071   gdouble          lAB, lBC, lAC, lEB, lEC;
1072 
1073   g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
1074 
1075   n_cage_vertices = gimp_cage_config_get_n_points (config);
1076 
1077   if (n_cage_vertices < 2)
1078     return -1;
1079 
1080   A = gimp_cage_config_get_point_coordinate (config,
1081                                              options->cage_mode,
1082                                              n_cage_vertices-1);
1083   B = gimp_cage_config_get_point_coordinate (config,
1084                                              options->cage_mode,
1085                                              0);
1086   C.x = x;
1087   C.y = y;
1088 
1089   for (i = 0; i < n_cage_vertices; i++)
1090     {
1091       gimp_vector2_sub (&AB, &A, &B);
1092       gimp_vector2_sub (&BC, &B, &C);
1093       gimp_vector2_sub (&AC, &A, &C);
1094 
1095       lAB = gimp_vector2_length (&AB);
1096       lBC = gimp_vector2_length (&BC);
1097       lAC = gimp_vector2_length (&AC);
1098       lEB = lAB / 2 + (SQR (lBC) - SQR (lAC)) / (2 * lAB);
1099       lEC = sqrt (SQR (lBC) - SQR (lEB));
1100 
1101       if ((lEC < handle_size / 2) && (ABS (SQR (lBC) - SQR (lAC)) <= SQR (lAB)))
1102         return i;
1103 
1104       A = B;
1105       B = gimp_cage_config_get_point_coordinate (config,
1106                                                  options->cage_mode,
1107                                                  (i+1) % n_cage_vertices);
1108     }
1109 
1110   return -1;
1111 }
1112 
1113 static gboolean
gimp_cage_tool_is_complete(GimpCageTool * ct)1114 gimp_cage_tool_is_complete (GimpCageTool *ct)
1115 {
1116   return (ct->tool_state > CAGE_STATE_CLOSING);
1117 }
1118 
1119 static void
gimp_cage_tool_remove_last_handle(GimpCageTool * ct)1120 gimp_cage_tool_remove_last_handle (GimpCageTool *ct)
1121 {
1122   GimpCageConfig *config = ct->config;
1123 
1124   gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
1125 
1126   gimp_cage_config_remove_last_cage_point (config);
1127 
1128   gimp_draw_tool_resume (GIMP_DRAW_TOOL (ct));
1129 }
1130 
1131 static void
gimp_cage_tool_compute_coef(GimpCageTool * ct)1132 gimp_cage_tool_compute_coef (GimpCageTool *ct)
1133 {
1134   GimpCageConfig *config = ct->config;
1135   GimpProgress   *progress;
1136   const Babl     *format;
1137   GeglNode       *gegl;
1138   GeglNode       *input;
1139   GeglNode       *output;
1140   GeglProcessor  *processor;
1141   GeglBuffer     *buffer;
1142   gdouble         value;
1143 
1144   progress = gimp_progress_start (GIMP_PROGRESS (ct), FALSE,
1145                                   _("Computing Cage Coefficients"));
1146 
1147   g_clear_object (&ct->coef);
1148 
1149   format = babl_format_n (babl_type ("float"),
1150                           gimp_cage_config_get_n_points (config) * 2);
1151 
1152 
1153   gegl = gegl_node_new ();
1154 
1155   input = gegl_node_new_child (gegl,
1156                                "operation", "gimp:cage-coef-calc",
1157                                "config",    ct->config,
1158                                NULL);
1159 
1160   output = gegl_node_new_child (gegl,
1161                                 "operation", "gegl:buffer-sink",
1162                                 "buffer",    &buffer,
1163                                 "format",    format,
1164                                 NULL);
1165 
1166   gegl_node_connect_to (input, "output",
1167                         output, "input");
1168 
1169   processor = gegl_node_new_processor (output, NULL);
1170 
1171   while (gegl_processor_work (processor, &value))
1172     {
1173       if (progress)
1174         gimp_progress_set_value (progress, value);
1175     }
1176 
1177   if (progress)
1178     gimp_progress_end (progress);
1179 
1180   g_object_unref (processor);
1181 
1182   ct->coef = buffer;
1183   g_object_unref (gegl);
1184 
1185   ct->dirty_coef = FALSE;
1186 }
1187 
1188 static void
gimp_cage_tool_create_render_node(GimpCageTool * ct)1189 gimp_cage_tool_create_render_node (GimpCageTool *ct)
1190 {
1191   GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
1192   GeglNode        *render;
1193   GeglNode        *input;
1194   GeglNode        *output;
1195 
1196   g_return_if_fail (ct->render_node == NULL);
1197   /* render_node is not supposed to be recreated */
1198 
1199   ct->render_node = gegl_node_new ();
1200 
1201   input  = gegl_node_get_input_proxy  (ct->render_node, "input");
1202   output = gegl_node_get_output_proxy (ct->render_node, "output");
1203 
1204   ct->coef_node = gegl_node_new_child (ct->render_node,
1205                                        "operation", "gegl:buffer-source",
1206                                        "buffer",    ct->coef,
1207                                        NULL);
1208 
1209   ct->cage_node = gegl_node_new_child (ct->render_node,
1210                                        "operation",        "gimp:cage-transform",
1211                                        "config",           ct->config,
1212                                        "fill-plain-color", options->fill_plain_color,
1213                                        NULL);
1214 
1215   render = gegl_node_new_child (ct->render_node,
1216                                 "operation", "gegl:map-absolute",
1217                                 NULL);
1218 
1219   gegl_node_connect_to (input,         "output",
1220                         ct->cage_node, "input");
1221 
1222   gegl_node_connect_to (ct->coef_node, "output",
1223                         ct->cage_node, "aux");
1224 
1225   gegl_node_connect_to (input,  "output",
1226                         render, "input");
1227 
1228   gegl_node_connect_to (ct->cage_node, "output",
1229                         render,        "aux");
1230 
1231   gegl_node_connect_to (render, "output",
1232                         output, "input");
1233 
1234   gimp_gegl_progress_connect (ct->cage_node, GIMP_PROGRESS (ct),
1235                               _("Cage Transform"));
1236 }
1237 
1238 static void
gimp_cage_tool_render_node_update(GimpCageTool * ct)1239 gimp_cage_tool_render_node_update (GimpCageTool *ct)
1240 {
1241   GimpCageOptions *options  = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
1242   gboolean         fill;
1243   GeglBuffer      *buffer;
1244 
1245   gegl_node_get (ct->cage_node,
1246                  "fill-plain-color", &fill,
1247                  NULL);
1248 
1249   if (fill != options->fill_plain_color)
1250     {
1251       gegl_node_set (ct->cage_node,
1252                      "fill-plain-color", options->fill_plain_color,
1253                      NULL);
1254     }
1255 
1256   gegl_node_get (ct->coef_node,
1257                  "buffer", &buffer,
1258                  NULL);
1259 
1260   if (buffer != ct->coef)
1261     {
1262       gegl_node_set (ct->coef_node,
1263                      "buffer", ct->coef,
1264                      NULL);
1265     }
1266 
1267   if (buffer)
1268     g_object_unref (buffer);
1269 }
1270 
1271 static void
gimp_cage_tool_create_filter(GimpCageTool * ct)1272 gimp_cage_tool_create_filter (GimpCageTool *ct)
1273 {
1274   if (! ct->render_node)
1275     gimp_cage_tool_create_render_node (ct);
1276 
1277   ct->filter = gimp_drawable_filter_new (GIMP_TOOL (ct)->drawable,
1278                                          _("Cage transform"),
1279                                          ct->render_node,
1280                                          GIMP_ICON_TOOL_CAGE);
1281   gimp_drawable_filter_set_region (ct->filter, GIMP_FILTER_REGION_DRAWABLE);
1282 
1283   g_signal_connect (ct->filter, "flush",
1284                     G_CALLBACK (gimp_cage_tool_filter_flush),
1285                     ct);
1286 }
1287 
1288 static void
gimp_cage_tool_filter_flush(GimpDrawableFilter * filter,GimpTool * tool)1289 gimp_cage_tool_filter_flush (GimpDrawableFilter *filter,
1290                              GimpTool           *tool)
1291 {
1292   GimpImage *image = gimp_display_get_image (tool->display);
1293 
1294   gimp_projection_flush (gimp_image_get_projection (image));
1295 }
1296 
1297 static void
gimp_cage_tool_filter_update(GimpCageTool * ct)1298 gimp_cage_tool_filter_update (GimpCageTool *ct)
1299 {
1300   gimp_drawable_filter_apply (ct->filter, NULL);
1301 }
1302