summaryrefslogtreecommitdiffstats
path: root/app/tools/gimpcagetool.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/tools/gimpcagetool.c')
-rw-r--r--app/tools/gimpcagetool.c1301
1 files changed, 1301 insertions, 0 deletions
diff --git a/app/tools/gimpcagetool.c b/app/tools/gimpcagetool.c
new file mode 100644
index 0000000..37bb4c2
--- /dev/null
+++ b/app/tools/gimpcagetool.c
@@ -0,0 +1,1301 @@
+/* GIMP - The GNU Image Manipulation Program
+ *
+ * gimpcagetool.c
+ * Copyright (C) 2010 Michael Muré <batolettre@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "config/gimpguiconfig.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "operations/gimpcageconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawablefilter.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpitem.h"
+#include "core/gimpprogress.h"
+#include "core/gimpprojection.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+
+#include "gimpcagetool.h"
+#include "gimpcageoptions.h"
+#include "gimptoolcontrol.h"
+#include "gimptools-utils.h"
+
+#include "gimp-intl.h"
+
+
+/* XXX: if this state list is updated, in particular if for some reason,
+ a new CAGE_STATE_* was to be inserted after CAGE_STATE_CLOSING, check
+ if the function gimp_cage_tool_is_complete() has to be updated.
+ Current algorithm is that all DEFORM_* states are complete states,
+ and all CAGE_* states are incomplete states. */
+enum
+{
+ CAGE_STATE_INIT,
+ CAGE_STATE_WAIT,
+ CAGE_STATE_MOVE_HANDLE,
+ CAGE_STATE_SELECTING,
+ CAGE_STATE_CLOSING,
+ DEFORM_STATE_WAIT,
+ DEFORM_STATE_MOVE_HANDLE,
+ DEFORM_STATE_SELECTING
+};
+
+
+static gboolean gimp_cage_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error);
+static void gimp_cage_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display);
+static void gimp_cage_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_cage_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_cage_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display);
+static gboolean gimp_cage_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display);
+static void gimp_cage_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_cage_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display);
+static void gimp_cage_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec);
+
+static void gimp_cage_tool_draw (GimpDrawTool *draw_tool);
+
+static void gimp_cage_tool_start (GimpCageTool *ct,
+ GimpDisplay *display);
+static void gimp_cage_tool_halt (GimpCageTool *ct);
+static void gimp_cage_tool_commit (GimpCageTool *ct);
+
+static gint gimp_cage_tool_is_on_handle (GimpCageTool *ct,
+ GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gint handle_size);
+static gint gimp_cage_tool_is_on_edge (GimpCageTool *ct,
+ gdouble x,
+ gdouble y,
+ gint handle_size);
+
+static gboolean gimp_cage_tool_is_complete (GimpCageTool *ct);
+static void gimp_cage_tool_remove_last_handle (GimpCageTool *ct);
+static void gimp_cage_tool_compute_coef (GimpCageTool *ct);
+static void gimp_cage_tool_create_filter (GimpCageTool *ct);
+static void gimp_cage_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool);
+static void gimp_cage_tool_filter_update (GimpCageTool *ct);
+
+static void gimp_cage_tool_create_render_node (GimpCageTool *ct);
+static void gimp_cage_tool_render_node_update (GimpCageTool *ct);
+
+
+G_DEFINE_TYPE (GimpCageTool, gimp_cage_tool, GIMP_TYPE_DRAW_TOOL)
+
+#define parent_class gimp_cage_tool_parent_class
+
+
+void
+gimp_cage_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_CAGE_TOOL,
+ GIMP_TYPE_CAGE_OPTIONS,
+ gimp_cage_options_gui,
+ 0,
+ "gimp-cage-tool",
+ _("Cage Transform"),
+ _("Cage Transform: Deform a selection with a cage"),
+ N_("_Cage Transform"), "<shift>G",
+ NULL, GIMP_HELP_TOOL_CAGE,
+ GIMP_ICON_TOOL_CAGE,
+ data);
+}
+
+static void
+gimp_cage_tool_class_init (GimpCageToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
+
+ tool_class->initialize = gimp_cage_tool_initialize;
+ tool_class->control = gimp_cage_tool_control;
+ tool_class->button_press = gimp_cage_tool_button_press;
+ tool_class->button_release = gimp_cage_tool_button_release;
+ tool_class->key_press = gimp_cage_tool_key_press;
+ tool_class->motion = gimp_cage_tool_motion;
+ tool_class->cursor_update = gimp_cage_tool_cursor_update;
+ tool_class->oper_update = gimp_cage_tool_oper_update;
+ tool_class->options_notify = gimp_cage_tool_options_notify;
+
+ draw_tool_class->draw = gimp_cage_tool_draw;
+}
+
+static void
+gimp_cage_tool_init (GimpCageTool *self)
+{
+ GimpTool *tool = GIMP_TOOL (self);
+
+ gimp_tool_control_set_preserve (tool->control, FALSE);
+ gimp_tool_control_set_dirty_mask (tool->control,
+ GIMP_DIRTY_IMAGE |
+ GIMP_DIRTY_IMAGE_STRUCTURE |
+ GIMP_DIRTY_DRAWABLE |
+ GIMP_DIRTY_SELECTION |
+ GIMP_DIRTY_ACTIVE_DRAWABLE);
+ gimp_tool_control_set_wants_click (tool->control, TRUE);
+ gimp_tool_control_set_precision (tool->control,
+ GIMP_CURSOR_PRECISION_SUBPIXEL);
+ gimp_tool_control_set_tool_cursor (tool->control,
+ GIMP_TOOL_CURSOR_PERSPECTIVE);
+
+ self->config = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
+ self->hovering_handle = -1;
+ self->tool_state = CAGE_STATE_INIT;
+}
+
+static gboolean
+gimp_cage_tool_initialize (GimpTool *tool,
+ GimpDisplay *display,
+ GError **error)
+{
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (! drawable)
+ return FALSE;
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Cannot modify the pixels of layer groups."));
+ return FALSE;
+ }
+
+ if (gimp_item_is_content_locked (GIMP_ITEM (drawable)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer's pixels are locked."));
+ if (error)
+ gimp_tools_blink_lock_box (display->gimp, GIMP_ITEM (drawable));
+ return FALSE;
+ }
+
+ if (! gimp_item_is_visible (GIMP_ITEM (drawable)) &&
+ ! config->edit_non_visible)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("The active layer is not visible."));
+ return FALSE;
+ }
+
+ gimp_cage_tool_start (GIMP_CAGE_TOOL (tool), display);
+
+ return TRUE;
+}
+
+static void
+gimp_cage_tool_control (GimpTool *tool,
+ GimpToolAction action,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ switch (action)
+ {
+ case GIMP_TOOL_ACTION_PAUSE:
+ case GIMP_TOOL_ACTION_RESUME:
+ break;
+
+ case GIMP_TOOL_ACTION_HALT:
+ gimp_cage_tool_halt (ct);
+ break;
+
+ case GIMP_TOOL_ACTION_COMMIT:
+ gimp_cage_tool_commit (ct);
+ break;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
+}
+
+static void
+gimp_cage_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+ gint handle = -1;
+ gint edge = -1;
+
+ gimp_tool_control_activate (tool->control);
+
+ if (ct->config)
+ {
+ handle = gimp_cage_tool_is_on_handle (ct,
+ draw_tool,
+ display,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ edge = gimp_cage_tool_is_on_edge (ct,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ }
+
+ ct->movement_start_x = coords->x;
+ ct->movement_start_y = coords->y;
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_INIT:
+ /* No handle yet, we add the first one and switch the tool to
+ * moving handle state.
+ */
+ gimp_cage_config_add_cage_point (ct->config,
+ coords->x - ct->offset_x,
+ coords->y - ct->offset_y);
+ gimp_cage_config_select_point (ct->config, 0);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ break;
+
+ case CAGE_STATE_WAIT:
+ if (handle == -1 && edge <= 0)
+ {
+ /* User clicked on the background, we add a new handle
+ * and move it
+ */
+ gimp_cage_config_add_cage_point (ct->config,
+ coords->x - ct->offset_x,
+ coords->y - ct->offset_y);
+ gimp_cage_config_select_point (ct->config,
+ gimp_cage_config_get_n_points (ct->config) - 1);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ else if (handle == 0 && gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ /* User clicked on the first handle, we wait for
+ * release for closing the cage and switching to
+ * deform if possible
+ */
+ gimp_cage_config_select_point (ct->config, 0);
+ ct->tool_state = CAGE_STATE_CLOSING;
+ }
+ else if (handle >= 0)
+ {
+ /* User clicked on a handle, so we move it */
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ /* Multiple selection */
+
+ gimp_cage_config_toggle_point_selection (ct->config, handle);
+ }
+ else
+ {
+ /* New selection */
+
+ if (! gimp_cage_config_point_is_selected (ct->config, handle))
+ {
+ gimp_cage_config_select_point (ct->config, handle);
+ }
+ }
+
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ else if (edge > 0)
+ {
+ /* User clicked on an edge, we add a new handle here and select it */
+
+ gimp_cage_config_insert_cage_point (ct->config, edge,
+ coords->x, coords->y);
+ gimp_cage_config_select_point (ct->config, edge);
+ ct->tool_state = CAGE_STATE_MOVE_HANDLE;
+ }
+ break;
+
+ case DEFORM_STATE_WAIT:
+ if (handle == -1)
+ {
+ /* User clicked on the background, we start a rubber band
+ * selection
+ */
+ ct->selection_start_x = coords->x;
+ ct->selection_start_y = coords->y;
+ ct->tool_state = DEFORM_STATE_SELECTING;
+ }
+
+ if (handle >= 0)
+ {
+ /* User clicked on a handle, so we move it */
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ /* Multiple selection */
+
+ gimp_cage_config_toggle_point_selection (ct->config, handle);
+ }
+ else
+ {
+ /* New selection */
+
+ if (! gimp_cage_config_point_is_selected (ct->config, handle))
+ {
+ gimp_cage_config_select_point (ct->config, handle);
+ }
+ }
+
+ ct->tool_state = DEFORM_STATE_MOVE_HANDLE;
+ }
+ break;
+ }
+}
+
+void
+gimp_cage_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
+
+ gimp_tool_control_halt (tool->control);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* Cancelling */
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_CLOSING:
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case CAGE_STATE_MOVE_HANDLE:
+ gimp_cage_config_remove_last_cage_point (ct->config);
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case CAGE_STATE_SELECTING:
+ ct->tool_state = CAGE_STATE_WAIT;
+ break;
+
+ case DEFORM_STATE_MOVE_HANDLE:
+ gimp_cage_tool_filter_update (ct);
+ ct->tool_state = DEFORM_STATE_WAIT;
+ break;
+
+ case DEFORM_STATE_SELECTING:
+ ct->tool_state = DEFORM_STATE_WAIT;
+ break;
+ }
+
+ gimp_cage_config_reset_displacement (ct->config);
+ }
+ else
+ {
+ /* Normal release */
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_CLOSING:
+ ct->dirty_coef = TRUE;
+ gimp_cage_config_commit_displacement (ct->config);
+
+ if (release_type == GIMP_BUTTON_RELEASE_CLICK)
+ g_object_set (options, "cage-mode", GIMP_CAGE_MODE_DEFORM, NULL);
+ break;
+
+ case CAGE_STATE_MOVE_HANDLE:
+ ct->dirty_coef = TRUE;
+ ct->tool_state = CAGE_STATE_WAIT;
+ gimp_cage_config_commit_displacement (ct->config);
+ break;
+
+ case CAGE_STATE_SELECTING:
+ {
+ GeglRectangle area =
+ { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
+ MIN (ct->selection_start_y, coords->y) - ct->offset_y,
+ ABS (ct->selection_start_x - coords->x),
+ ABS (ct->selection_start_y - coords->y) };
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ gimp_cage_config_select_add_area (ct->config,
+ GIMP_CAGE_MODE_CAGE_CHANGE,
+ area);
+ }
+ else
+ {
+ gimp_cage_config_select_area (ct->config,
+ GIMP_CAGE_MODE_CAGE_CHANGE,
+ area);
+ }
+
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+ break;
+
+ case DEFORM_STATE_MOVE_HANDLE:
+ ct->tool_state = DEFORM_STATE_WAIT;
+ gimp_cage_config_commit_displacement (ct->config);
+ gegl_node_set (ct->cage_node,
+ "config", ct->config,
+ NULL);
+ gimp_cage_tool_filter_update (ct);
+ break;
+
+ case DEFORM_STATE_SELECTING:
+ {
+ GeglRectangle area =
+ { MIN (ct->selection_start_x, coords->x) - ct->offset_x,
+ MIN (ct->selection_start_y, coords->y) - ct->offset_y,
+ ABS (ct->selection_start_x - coords->x),
+ ABS (ct->selection_start_y - coords->y) };
+
+ if (state & gimp_get_extend_selection_mask ())
+ {
+ gimp_cage_config_select_add_area (ct->config,
+ GIMP_CAGE_MODE_DEFORM, area);
+ }
+ else
+ {
+ gimp_cage_config_select_area (ct->config,
+ GIMP_CAGE_MODE_DEFORM, area);
+ }
+
+ ct->tool_state = DEFORM_STATE_WAIT;
+ }
+ break;
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_cage_tool_motion (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ ct->cursor_x = coords->x;
+ ct->cursor_y = coords->y;
+
+ switch (ct->tool_state)
+ {
+ case CAGE_STATE_MOVE_HANDLE:
+ case CAGE_STATE_CLOSING:
+ case DEFORM_STATE_MOVE_HANDLE:
+ gimp_cage_config_add_displacement (ct->config,
+ options->cage_mode,
+ ct->cursor_x - ct->movement_start_x,
+ ct->cursor_y - ct->movement_start_y);
+ break;
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static gboolean
+gimp_cage_tool_key_press (GimpTool *tool,
+ GdkEventKey *kevent,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ if (! ct->config)
+ return FALSE;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ if (ct->tool_state == CAGE_STATE_WAIT)
+ {
+ if (gimp_cage_config_get_n_points (ct->config) != 0)
+ gimp_cage_tool_remove_last_handle (ct);
+ }
+ else if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_cage_config_remove_selected_points (ct->config);
+
+ /* if the cage have less than 3 handles, we reopen it */
+ if (gimp_cage_config_get_n_points (ct->config) <= 2)
+ {
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+
+ gimp_cage_tool_compute_coef (ct);
+ gimp_cage_tool_render_node_update (ct);
+ }
+ return TRUE;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ if (! gimp_cage_tool_is_complete (ct) &&
+ gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ g_object_set (gimp_tool_get_options (tool),
+ "cage-mode", GIMP_CAGE_MODE_DEFORM,
+ NULL);
+ }
+ else if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
+ }
+ return TRUE;
+
+ case GDK_KEY_Escape:
+ gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_cage_tool_oper_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tool);
+
+ if (ct->config)
+ {
+ ct->hovering_handle = gimp_cage_tool_is_on_handle (ct,
+ draw_tool,
+ display,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+
+ ct->hovering_edge = gimp_cage_tool_is_on_edge (ct,
+ coords->x,
+ coords->y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE);
+ }
+
+ gimp_draw_tool_pause (draw_tool);
+
+ ct->cursor_x = coords->x;
+ ct->cursor_y = coords->y;
+
+ gimp_draw_tool_resume (draw_tool);
+}
+
+static void
+gimp_cage_tool_cursor_update (GimpTool *tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpGuiConfig *config = GIMP_GUI_CONFIG (display->gimp->config);
+ GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_PLUS;
+
+ if (tool->display)
+ {
+ if (ct->hovering_handle != -1)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ }
+ else if (ct->hovering_edge != -1 &&
+ options->cage_mode == GIMP_CAGE_MODE_CAGE_CHANGE)
+ {
+ modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ }
+ else
+ {
+ if (gimp_cage_tool_is_complete (ct))
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+ else
+ {
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (drawable)) ||
+ gimp_item_is_content_locked (GIMP_ITEM (drawable)) ||
+ ! (gimp_item_is_visible (GIMP_ITEM (drawable)) ||
+ config->edit_non_visible))
+ {
+ modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ }
+
+ gimp_tool_control_set_cursor_modifier (tool->control, modifier);
+
+ GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
+}
+
+static void
+gimp_cage_tool_options_notify (GimpTool *tool,
+ GimpToolOptions *options,
+ const GParamSpec *pspec)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (tool);
+
+ GIMP_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
+
+ if (! tool->display)
+ return;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+
+ if (strcmp (pspec->name, "cage-mode") == 0)
+ {
+ GimpCageMode mode;
+
+ g_object_get (options,
+ "cage-mode", &mode,
+ NULL);
+
+ if (mode == GIMP_CAGE_MODE_DEFORM)
+ {
+ /* switch to deform mode */
+
+ if (gimp_cage_config_get_n_points (ct->config) > 2)
+ {
+ gimp_cage_config_reset_displacement (ct->config);
+ gimp_cage_config_reverse_cage_if_needed (ct->config);
+ gimp_tool_push_status (tool, tool->display,
+ _("Press ENTER to commit the transform"));
+ ct->tool_state = DEFORM_STATE_WAIT;
+
+ if (! ct->render_node)
+ {
+ gimp_cage_tool_create_render_node (ct);
+ }
+
+ if (ct->dirty_coef)
+ {
+ gimp_cage_tool_compute_coef (ct);
+ gimp_cage_tool_render_node_update (ct);
+ }
+
+ if (! ct->filter)
+ gimp_cage_tool_create_filter (ct);
+
+ gimp_cage_tool_filter_update (ct);
+ }
+ else
+ {
+ g_object_set (options,
+ "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
+ NULL);
+ }
+ }
+ else
+ {
+ /* switch to edit mode */
+ if (ct->filter)
+ {
+ gimp_drawable_filter_abort (ct->filter);
+
+ gimp_tool_pop_status (tool, tool->display);
+ ct->tool_state = CAGE_STATE_WAIT;
+ }
+ }
+ }
+ else if (strcmp (pspec->name, "fill-plain-color") == 0)
+ {
+ if (ct->tool_state == DEFORM_STATE_WAIT)
+ {
+ gimp_cage_tool_render_node_update (ct);
+ gimp_cage_tool_filter_update (ct);
+ }
+ }
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+}
+
+static void
+gimp_cage_tool_draw (GimpDrawTool *draw_tool)
+{
+ GimpCageTool *ct = GIMP_CAGE_TOOL (draw_tool);
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ GimpCanvasGroup *stroke_group;
+ gint n_vertices;
+ gint i;
+ GimpHandleType handle;
+
+ n_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_vertices == 0)
+ return;
+
+ if (ct->tool_state == CAGE_STATE_INIT)
+ return;
+
+ stroke_group = gimp_draw_tool_add_stroke_group (draw_tool);
+
+ gimp_draw_tool_push_group (draw_tool, stroke_group);
+
+ /* If needed, draw line to the cursor. */
+ if (! gimp_cage_tool_is_complete (ct))
+ {
+ GimpVector2 last_point;
+
+ last_point = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ n_vertices - 1);
+
+ gimp_draw_tool_add_line (draw_tool,
+ last_point.x + ct->offset_x,
+ last_point.y + ct->offset_y,
+ ct->cursor_x,
+ ct->cursor_y);
+ }
+
+ gimp_draw_tool_pop_group (draw_tool);
+
+ /* Draw the cage with handles. */
+ for (i = 0; i < n_vertices; i++)
+ {
+ GimpCanvasItem *item;
+ GimpVector2 point1, point2;
+
+ point1 = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ i);
+ point1.x += ct->offset_x;
+ point1.y += ct->offset_y;
+
+ if (i > 0 || gimp_cage_tool_is_complete (ct))
+ {
+ gint index_point2;
+
+ if (i == 0)
+ index_point2 = n_vertices - 1;
+ else
+ index_point2 = i - 1;
+
+ point2 = gimp_cage_config_get_point_coordinate (ct->config,
+ options->cage_mode,
+ index_point2);
+ point2.x += ct->offset_x;
+ point2.y += ct->offset_y;
+
+ if (i != ct->hovering_edge ||
+ gimp_cage_tool_is_complete (ct))
+ {
+ gimp_draw_tool_push_group (draw_tool, stroke_group);
+ }
+
+ item = gimp_draw_tool_add_line (draw_tool,
+ point1.x,
+ point1.y,
+ point2.x,
+ point2.y);
+
+ if (i == ct->hovering_edge &&
+ ! gimp_cage_tool_is_complete (ct))
+ {
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+ else
+ {
+ gimp_draw_tool_pop_group (draw_tool);
+ }
+ }
+
+ if (gimp_cage_config_point_is_selected (ct->config, i))
+ {
+ if (i == ct->hovering_handle)
+ handle = GIMP_HANDLE_FILLED_SQUARE;
+ else
+ handle = GIMP_HANDLE_SQUARE;
+ }
+ else
+ {
+ if (i == ct->hovering_handle)
+ handle = GIMP_HANDLE_FILLED_CIRCLE;
+ else
+ handle = GIMP_HANDLE_CIRCLE;
+ }
+
+ item = gimp_draw_tool_add_handle (draw_tool,
+ handle,
+ point1.x,
+ point1.y,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+
+ if (i == ct->hovering_handle)
+ gimp_canvas_item_set_highlight (item, TRUE);
+ }
+
+ if (ct->tool_state == DEFORM_STATE_SELECTING ||
+ ct->tool_state == CAGE_STATE_SELECTING)
+ {
+ gimp_draw_tool_add_rectangle (draw_tool,
+ FALSE,
+ MIN (ct->selection_start_x, ct->cursor_x),
+ MIN (ct->selection_start_y, ct->cursor_y),
+ ABS (ct->selection_start_x - ct->cursor_x),
+ ABS (ct->selection_start_y - ct->cursor_y));
+ }
+}
+
+static void
+gimp_cage_tool_start (GimpCageTool *ct,
+ GimpDisplay *display)
+{
+ GimpTool *tool = GIMP_TOOL (ct);
+ GimpImage *image = gimp_display_get_image (display);
+ GimpDrawable *drawable = gimp_image_get_active_drawable (image);
+
+ tool->display = display;
+ tool->drawable = drawable;
+
+ g_clear_object (&ct->config);
+
+ g_clear_object (&ct->coef);
+ ct->dirty_coef = TRUE;
+
+ if (ct->filter)
+ {
+ gimp_drawable_filter_abort (ct->filter);
+ g_clear_object (&ct->filter);
+ }
+
+ if (ct->render_node)
+ {
+ g_clear_object (&ct->render_node);
+ ct->coef_node = NULL;
+ ct->cage_node = NULL;
+ }
+
+ ct->config = g_object_new (GIMP_TYPE_CAGE_CONFIG, NULL);
+ ct->hovering_handle = -1;
+ ct->hovering_edge = -1;
+ ct->tool_state = CAGE_STATE_INIT;
+
+ /* Setting up cage offset to convert the cage point coords to
+ * drawable coords
+ */
+ gimp_item_get_offset (GIMP_ITEM (tool->drawable),
+ &ct->offset_x, &ct->offset_y);
+
+ gimp_draw_tool_start (GIMP_DRAW_TOOL (ct), display);
+}
+
+static void
+gimp_cage_tool_halt (GimpCageTool *ct)
+{
+ GimpTool *tool = GIMP_TOOL (ct);
+
+ g_clear_object (&ct->config);
+ g_clear_object (&ct->coef);
+ g_clear_object (&ct->render_node);
+ ct->coef_node = NULL;
+ ct->cage_node = NULL;
+
+ if (ct->filter)
+ {
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_abort (ct->filter);
+ g_clear_object (&ct->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+
+ tool->display = NULL;
+ tool->drawable = NULL;
+ ct->tool_state = CAGE_STATE_INIT;
+
+ g_object_set (gimp_tool_get_options (tool),
+ "cage-mode", GIMP_CAGE_MODE_CAGE_CHANGE,
+ NULL);
+}
+
+static void
+gimp_cage_tool_commit (GimpCageTool *ct)
+{
+ if (ct->filter)
+ {
+ GimpTool *tool = GIMP_TOOL (ct);
+
+ gimp_tool_control_push_preserve (tool->control, TRUE);
+
+ gimp_drawable_filter_commit (ct->filter, GIMP_PROGRESS (tool), FALSE);
+ g_clear_object (&ct->filter);
+
+ gimp_tool_control_pop_preserve (tool->control);
+
+ gimp_image_flush (gimp_display_get_image (tool->display));
+ }
+}
+
+static gint
+gimp_cage_tool_is_on_handle (GimpCageTool *ct,
+ GimpDrawTool *draw_tool,
+ GimpDisplay *display,
+ gdouble x,
+ gdouble y,
+ gint handle_size)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ gdouble dist = G_MAXDOUBLE;
+ gint i;
+ GimpVector2 cage_point;
+ guint n_cage_vertices;
+
+ g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
+
+ n_cage_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_cage_vertices == 0)
+ return -1;
+
+ for (i = 0; i < n_cage_vertices; i++)
+ {
+ cage_point = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ i);
+ cage_point.x += ct->offset_x;
+ cage_point.y += ct->offset_y;
+
+ dist = gimp_draw_tool_calc_distance_square (GIMP_DRAW_TOOL (draw_tool),
+ display,
+ x, y,
+ cage_point.x,
+ cage_point.y);
+
+ if (dist <= SQR (handle_size / 2))
+ return i;
+ }
+
+ return -1;
+}
+
+static gint
+gimp_cage_tool_is_on_edge (GimpCageTool *ct,
+ gdouble x,
+ gdouble y,
+ gint handle_size)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GimpCageConfig *config = ct->config;
+ gint i;
+ guint n_cage_vertices;
+ GimpVector2 A, B, C, AB, BC, AC;
+ gdouble lAB, lBC, lAC, lEB, lEC;
+
+ g_return_val_if_fail (GIMP_IS_CAGE_TOOL (ct), -1);
+
+ n_cage_vertices = gimp_cage_config_get_n_points (config);
+
+ if (n_cage_vertices < 2)
+ return -1;
+
+ A = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ n_cage_vertices-1);
+ B = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ 0);
+ C.x = x;
+ C.y = y;
+
+ for (i = 0; i < n_cage_vertices; i++)
+ {
+ gimp_vector2_sub (&AB, &A, &B);
+ gimp_vector2_sub (&BC, &B, &C);
+ gimp_vector2_sub (&AC, &A, &C);
+
+ lAB = gimp_vector2_length (&AB);
+ lBC = gimp_vector2_length (&BC);
+ lAC = gimp_vector2_length (&AC);
+ lEB = lAB / 2 + (SQR (lBC) - SQR (lAC)) / (2 * lAB);
+ lEC = sqrt (SQR (lBC) - SQR (lEB));
+
+ if ((lEC < handle_size / 2) && (ABS (SQR (lBC) - SQR (lAC)) <= SQR (lAB)))
+ return i;
+
+ A = B;
+ B = gimp_cage_config_get_point_coordinate (config,
+ options->cage_mode,
+ (i+1) % n_cage_vertices);
+ }
+
+ return -1;
+}
+
+static gboolean
+gimp_cage_tool_is_complete (GimpCageTool *ct)
+{
+ return (ct->tool_state > CAGE_STATE_CLOSING);
+}
+
+static void
+gimp_cage_tool_remove_last_handle (GimpCageTool *ct)
+{
+ GimpCageConfig *config = ct->config;
+
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (ct));
+
+ gimp_cage_config_remove_last_cage_point (config);
+
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (ct));
+}
+
+static void
+gimp_cage_tool_compute_coef (GimpCageTool *ct)
+{
+ GimpCageConfig *config = ct->config;
+ GimpProgress *progress;
+ const Babl *format;
+ GeglNode *gegl;
+ GeglNode *input;
+ GeglNode *output;
+ GeglProcessor *processor;
+ GeglBuffer *buffer;
+ gdouble value;
+
+ progress = gimp_progress_start (GIMP_PROGRESS (ct), FALSE,
+ _("Computing Cage Coefficients"));
+
+ g_clear_object (&ct->coef);
+
+ format = babl_format_n (babl_type ("float"),
+ gimp_cage_config_get_n_points (config) * 2);
+
+
+ gegl = gegl_node_new ();
+
+ input = gegl_node_new_child (gegl,
+ "operation", "gimp:cage-coef-calc",
+ "config", ct->config,
+ NULL);
+
+ output = gegl_node_new_child (gegl,
+ "operation", "gegl:buffer-sink",
+ "buffer", &buffer,
+ "format", format,
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ output, "input");
+
+ processor = gegl_node_new_processor (output, NULL);
+
+ while (gegl_processor_work (processor, &value))
+ {
+ if (progress)
+ gimp_progress_set_value (progress, value);
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ g_object_unref (processor);
+
+ ct->coef = buffer;
+ g_object_unref (gegl);
+
+ ct->dirty_coef = FALSE;
+}
+
+static void
+gimp_cage_tool_create_render_node (GimpCageTool *ct)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ GeglNode *render;
+ GeglNode *input;
+ GeglNode *output;
+
+ g_return_if_fail (ct->render_node == NULL);
+ /* render_node is not supposed to be recreated */
+
+ ct->render_node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (ct->render_node, "input");
+ output = gegl_node_get_output_proxy (ct->render_node, "output");
+
+ ct->coef_node = gegl_node_new_child (ct->render_node,
+ "operation", "gegl:buffer-source",
+ "buffer", ct->coef,
+ NULL);
+
+ ct->cage_node = gegl_node_new_child (ct->render_node,
+ "operation", "gimp:cage-transform",
+ "config", ct->config,
+ "fill-plain-color", options->fill_plain_color,
+ NULL);
+
+ render = gegl_node_new_child (ct->render_node,
+ "operation", "gegl:map-absolute",
+ NULL);
+
+ gegl_node_connect_to (input, "output",
+ ct->cage_node, "input");
+
+ gegl_node_connect_to (ct->coef_node, "output",
+ ct->cage_node, "aux");
+
+ gegl_node_connect_to (input, "output",
+ render, "input");
+
+ gegl_node_connect_to (ct->cage_node, "output",
+ render, "aux");
+
+ gegl_node_connect_to (render, "output",
+ output, "input");
+
+ gimp_gegl_progress_connect (ct->cage_node, GIMP_PROGRESS (ct),
+ _("Cage Transform"));
+}
+
+static void
+gimp_cage_tool_render_node_update (GimpCageTool *ct)
+{
+ GimpCageOptions *options = GIMP_CAGE_TOOL_GET_OPTIONS (ct);
+ gboolean fill;
+ GeglBuffer *buffer;
+
+ gegl_node_get (ct->cage_node,
+ "fill-plain-color", &fill,
+ NULL);
+
+ if (fill != options->fill_plain_color)
+ {
+ gegl_node_set (ct->cage_node,
+ "fill-plain-color", options->fill_plain_color,
+ NULL);
+ }
+
+ gegl_node_get (ct->coef_node,
+ "buffer", &buffer,
+ NULL);
+
+ if (buffer != ct->coef)
+ {
+ gegl_node_set (ct->coef_node,
+ "buffer", ct->coef,
+ NULL);
+ }
+
+ if (buffer)
+ g_object_unref (buffer);
+}
+
+static void
+gimp_cage_tool_create_filter (GimpCageTool *ct)
+{
+ if (! ct->render_node)
+ gimp_cage_tool_create_render_node (ct);
+
+ ct->filter = gimp_drawable_filter_new (GIMP_TOOL (ct)->drawable,
+ _("Cage transform"),
+ ct->render_node,
+ GIMP_ICON_TOOL_CAGE);
+ gimp_drawable_filter_set_region (ct->filter, GIMP_FILTER_REGION_DRAWABLE);
+
+ g_signal_connect (ct->filter, "flush",
+ G_CALLBACK (gimp_cage_tool_filter_flush),
+ ct);
+}
+
+static void
+gimp_cage_tool_filter_flush (GimpDrawableFilter *filter,
+ GimpTool *tool)
+{
+ GimpImage *image = gimp_display_get_image (tool->display);
+
+ gimp_projection_flush (gimp_image_get_projection (image));
+}
+
+static void
+gimp_cage_tool_filter_update (GimpCageTool *ct)
+{
+ gimp_drawable_filter_apply (ct->filter, NULL);
+}