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