diff --git a/app/dialogs/dialogs.c b/app/dialogs/dialogs.c
index 1624a8d84c..17eae74e54 100644
--- a/app/dialogs/dialogs.c
+++ b/app/dialogs/dialogs.c
@@ -244,6 +244,7 @@ static const GimpDialogFactoryEntry entries[] =
FOREIGN ("gimp-threshold-tool-dialog", TRUE, FALSE),
FOREIGN ("gimp-perspective-tool-dialog", TRUE, FALSE),
FOREIGN ("gimp-unified-transform-tool-dialog", TRUE, FALSE),
+ FOREIGN ("gimp-handle-transform-tool-dialog", TRUE, FALSE),
FOREIGN ("gimp-toolbox-color-dialog", TRUE, FALSE),
FOREIGN ("gimp-gradient-editor-color-dialog", TRUE, FALSE),
diff --git a/app/tools/Makefile.am b/app/tools/Makefile.am
index dcdacdeff3..08186ac43a 100644
--- a/app/tools/Makefile.am
+++ b/app/tools/Makefile.am
@@ -96,6 +96,10 @@ libapptools_a_sources = \
gimpfuzzyselecttool.h \
gimpgegltool.c \
gimpgegltool.h \
+ gimphandletransformoptions.c \
+ gimphandletransformoptions.h \
+ gimphandletransformtool.c \
+ gimphandletransformtool.h \
gimphealtool.c \
gimphealtool.h \
gimphistogramoptions.c \
diff --git a/app/tools/gimp-tools.c b/app/tools/gimp-tools.c
index 0ed25aee53..54b44fc305 100644
--- a/app/tools/gimp-tools.c
+++ b/app/tools/gimp-tools.c
@@ -61,6 +61,7 @@
#include "gimpforegroundselecttool.h"
#include "gimpfuzzyselecttool.h"
#include "gimpgegltool.h"
+#include "gimphandletransformtool.h"
#include "gimphealtool.h"
#include "gimphuesaturationtool.h"
#include "gimpinktool.h"
@@ -160,6 +161,7 @@ gimp_tools_init (Gimp *gimp)
gimp_cage_tool_register,
gimp_flip_tool_register,
gimp_perspective_tool_register,
+ gimp_handle_transform_tool_register,
gimp_shear_tool_register,
gimp_scale_tool_register,
gimp_rotate_tool_register,
diff --git a/app/tools/gimphandletransformoptions.c b/app/tools/gimphandletransformoptions.c
new file mode 100644
index 0000000000..78ce7c6e23
--- /dev/null
+++ b/app/tools/gimphandletransformoptions.c
@@ -0,0 +1,201 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libgimpconfig/gimpconfig.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "core/gimp.h"
+#include "core/gimptoolinfo.h"
+
+#include "widgets/gimppropwidgets.h"
+#include "widgets/gimpspinscale.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimphandletransformoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_HANDLE_MODE
+};
+
+
+static void gimp_handle_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_handle_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpHandleTransformOptions, gimp_handle_transform_options,
+ GIMP_TYPE_TRANSFORM_OPTIONS)
+
+#define parent_class gimp_handle_transform_options_parent_class
+
+
+static void
+gimp_handle_transform_options_class_init (GimpHandleTransformOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_handle_transform_options_set_property;
+ object_class->get_property = gimp_handle_transform_options_get_property;
+
+ GIMP_CONFIG_INSTALL_PROP_ENUM (object_class, PROP_HANDLE_MODE,
+ "handle-mode",
+ N_("Handle mode"),
+ GIMP_TYPE_TRANSFORM_HANDLE_MODE,
+ GIMP_HANDLE_MODE_TRANSFORM,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_handle_transform_options_init (GimpHandleTransformOptions *options)
+{
+}
+
+static void
+gimp_handle_transform_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHandleTransformOptions *options = GIMP_HANDLE_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_HANDLE_MODE:
+ options->handle_mode = g_value_get_enum (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_handle_transform_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpHandleTransformOptions *options = GIMP_HANDLE_TRANSFORM_OPTIONS (object);
+
+ switch (property_id)
+ {
+ case PROP_HANDLE_MODE:
+ g_value_set_enum (value, options->handle_mode);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_handle_transform_options_gui:
+ * @tool_options: a #GimpToolOptions
+ *
+ * Build the Transform Tool Options.
+ *
+ * Return value: a container holding the transform tool options
+ **/
+GtkWidget *
+gimp_handle_transform_options_gui (GimpToolOptions *tool_options)
+{
+ GObject *config = G_OBJECT (tool_options);
+ GtkWidget *vbox = gimp_transform_options_gui (tool_options);
+ GtkWidget *frame;
+ GtkWidget *button;
+ gint i;
+
+ frame = gimp_prop_enum_radio_frame_new (config, "handle-mode",
+ _("Handle mode"), 0, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ /* add modifier to name, add tooltip */
+ button = g_object_get_data (G_OBJECT (frame), "radio-button");
+
+ if (GTK_IS_RADIO_BUTTON (button))
+ {
+ GSList *list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+
+ for (i = g_slist_length (list) - 1 ; list; list = list->next, i--)
+ {
+ GdkModifierType shift = gimp_get_extend_selection_mask ();
+ GdkModifierType ctrl = gimp_get_constrain_behavior_mask ();
+ GdkModifierType modifier = 0;
+ gchar *tooltip = "";
+ gchar *tip;
+ gchar *label;
+
+ switch (i)
+ {
+ case GIMP_HANDLE_MODE_ADD_MOVE:
+ modifier = shift;
+ tooltip = "Add or move transform handles";
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ modifier = ctrl;
+ tooltip = "Remove transform handles";
+ break;
+
+ case GIMP_HANDLE_MODE_TRANSFORM:
+ modifier = 0;
+ tooltip = "Transform image by moving handles";
+ break;
+ }
+
+ if (modifier)
+ {
+ label = g_strdup_printf ("%s (%s)",
+ gtk_button_get_label (GTK_BUTTON (list->data)),
+ gimp_get_mod_string (modifier));
+ gtk_button_set_label (GTK_BUTTON (list->data), label);
+ g_free (label);
+
+ tip = g_strdup_printf ("%s (%s)",
+ tooltip, gimp_get_mod_string (modifier));
+ gimp_help_set_help_data (list->data, tip, NULL);
+ g_free (tip);
+ }
+ else
+ {
+ gimp_help_set_help_data (list->data, tooltip, NULL);
+ }
+ }
+ }
+
+ return vbox;
+}
diff --git a/app/tools/gimphandletransformoptions.h b/app/tools/gimphandletransformoptions.h
new file mode 100644
index 0000000000..34077404c9
--- /dev/null
+++ b/app/tools/gimphandletransformoptions.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#ifndef __GIMP_HANDLE_TRANSFORM_OPTIONS_H__
+#define __GIMP_HANDLE_TRANSFORM_OPTIONS_H__
+
+
+#include "gimptransformoptions.h"
+
+
+#define GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS (gimp_handle_transform_options_get_type ())
+#define GIMP_HANDLE_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptions))
+#define GIMP_HANDLE_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptionsClass))
+#define GIMP_IS_HANDLE_TRANSFORM_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS))
+#define GIMP_IS_HANDLE_TRANSFORM_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS))
+#define GIMP_HANDLE_TRANSFORM_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS, GimpHandleTransformOptionsClass))
+
+
+typedef struct _GimpHandleTransformOptions GimpHandleTransformOptions;
+typedef struct _GimpHandleTransformOptionsClass GimpHandleTransformOptionsClass;
+
+struct _GimpHandleTransformOptions
+{
+ GimpTransformOptions parent_instance;
+
+ GimpTransformHandleMode handle_mode;
+};
+
+struct _GimpHandleTransformOptionsClass
+{
+ GimpTransformOptionsClass parent_class;
+};
+
+
+GType gimp_handle_transform_options_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gimp_handle_transform_options_gui (GimpToolOptions *tool_options);
+
+
+#endif /* __GIMP_HANDLE_TRANSFORM_OPTIONS_H__ */
diff --git a/app/tools/gimphandletransformtool.c b/app/tools/gimphandletransformtool.c
new file mode 100644
index 0000000000..b214149c36
--- /dev/null
+++ b/app/tools/gimphandletransformtool.c
@@ -0,0 +1,985 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#include "config.h"
+
+#include
+
+#include
+#include
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "tools-types.h"
+
+#include "widgets/gimphelp-ids.h"
+#include "widgets/gimpwidgets-utils.h"
+
+#include "display/gimpcanvasitem.h"
+#include "display/gimpdisplay.h"
+#include "display/gimptoolgui.h"
+
+#include "gimphandletransformoptions.h"
+#include "gimphandletransformtool.h"
+#include "gimptoolcontrol.h"
+
+#include "gimp-intl.h"
+
+
+/* the transformation is defined by 8 points:
+ *
+ * 4 points on the original image and 4 corresponding points on the
+ * transformed image. The first NUM points on the transformed image
+ * are visible as handles.
+ *
+ * For these handles, the constants TRANSFORM_HANDLE_N,
+ * TRANSFORM_HANDLE_S, TRANSFORM_HANDLE_E and TRANSFORM_HANDLE_W are
+ * used. Actually, it makes no sense to name the handles with north,
+ * south, east, and west. But this way, we don't need to define even
+ * more enum constants.
+ */
+
+/* index into trans_info array */
+enum
+{
+ X0,
+ Y0,
+ X1,
+ Y1,
+ X2,
+ Y2,
+ X3,
+ Y3,
+ OX0,
+ OY0,
+ OX1,
+ OY1,
+ OX2,
+ OY2,
+ OX3,
+ OY3,
+ NUM
+};
+
+
+/* local function prototypes */
+
+static void gimp_handle_transform_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display);
+static void gimp_handle_transform_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display);
+static void gimp_handle_transform_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display);
+
+static void gimp_handle_transform_tool_dialog (GimpTransformTool *tr_tool);
+static void gimp_handle_transform_tool_dialog_update (GimpTransformTool *tr_tool);
+static void gimp_handle_transform_tool_prepare (GimpTransformTool *tr_tool);
+static void gimp_handle_transform_tool_motion (GimpTransformTool *tr_tool);
+static void gimp_handle_transform_tool_recalc_matrix (GimpTransformTool *tr_tool);
+static gchar *gimp_handle_transform_tool_get_undo_desc (GimpTransformTool *tr_tool);
+static TransformAction
+ gimp_handle_transform_tool_pick_function (GimpTransformTool *tr_tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display);
+static void gimp_handle_transform_tool_cursor_update (GimpTransformTool *tr_tool,
+ GimpCursorType *cursor,
+ GimpCursorModifier *modifier);
+static void gimp_handle_transform_tool_draw_gui (GimpTransformTool *tr_tool,
+ gint handle_w,
+ gint handle_h);
+
+static gboolean is_handle_position_valid (GimpTransformTool *tr_tool,
+ gint active_handle);
+static void handle_micro_move (GimpTransformTool *tr_tool,
+ gint active_handle);
+static inline gdouble calc_angle (gdouble ax,
+ gdouble ay,
+ gdouble bx,
+ gdouble by);
+static inline gdouble calc_len (gdouble a,
+ gdouble b);
+static inline gdouble calc_lineintersect_ratio (gdouble p1x,
+ gdouble p1y,
+ gdouble p2x,
+ gdouble p2y,
+ gdouble q1x,
+ gdouble q1y,
+ gdouble q2x,
+ gdouble q2y);
+static gboolean mod_gauss (gdouble matrix[],
+ gdouble solution[],
+ gint s);
+
+
+G_DEFINE_TYPE (GimpHandleTransformTool, gimp_handle_transform_tool,
+ GIMP_TYPE_TRANSFORM_TOOL)
+
+#define parent_class gimp_handle_transform_tool_parent_class
+
+
+void
+gimp_handle_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data)
+{
+ (* callback) (GIMP_TYPE_HANDLE_TRANSFORM_TOOL,
+ GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS,
+ gimp_handle_transform_options_gui,
+ GIMP_CONTEXT_BACKGROUND_MASK,
+ "gimp-handle-transform-tool",
+ _("Handle Transform"),
+ _("Handle Transform Tool: "
+ "Deform the layer, selection or path with handles"),
+ N_("_Handle Transform"), "H",
+ NULL, GIMP_HELP_TOOL_HANDLE_TRANSFORM,
+ GIMP_STOCK_TOOL_HANDLE_TRANSFORM,
+ data);
+}
+
+static void
+gimp_handle_transform_tool_class_init (GimpHandleTransformToolClass *klass)
+{
+ GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
+ GimpTransformToolClass *trans_class = GIMP_TRANSFORM_TOOL_CLASS (klass);
+
+ tool_class->button_press = gimp_handle_transform_tool_button_press;
+ tool_class->button_release = gimp_handle_transform_tool_button_release;
+ tool_class->modifier_key = gimp_handle_transform_tool_modifier_key;
+
+ trans_class->dialog = gimp_handle_transform_tool_dialog;
+ trans_class->dialog_update = gimp_handle_transform_tool_dialog_update;
+ trans_class->prepare = gimp_handle_transform_tool_prepare;
+ trans_class->motion = gimp_handle_transform_tool_motion;
+ trans_class->recalc_matrix = gimp_handle_transform_tool_recalc_matrix;
+ trans_class->get_undo_desc = gimp_handle_transform_tool_get_undo_desc;
+ trans_class->pick_function = gimp_handle_transform_tool_pick_function;
+ trans_class->cursor_update = gimp_handle_transform_tool_cursor_update;
+ trans_class->draw_gui = gimp_handle_transform_tool_draw_gui;
+}
+
+static void
+gimp_handle_transform_tool_init (GimpHandleTransformTool *ht_tool)
+{
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (ht_tool);
+
+ tr_tool->progress_text = _("Handle transformation");
+ tr_tool->use_grid = TRUE;
+
+ ht_tool->saved_handle_mode = GIMP_HANDLE_MODE_TRANSFORM;
+}
+
+static void
+gimp_handle_transform_tool_button_press (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type,
+ GimpDisplay *display)
+{
+ GimpHandleTransformTool *ht = GIMP_HANDLE_TRANSFORM_TOOL (tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpHandleTransformOptions *options;
+ gint num;
+ gint active_handle;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ num = (gint) tr_tool->trans_info[NUM];
+ active_handle = tr_tool->function - TRANSFORM_HANDLE_N;
+
+ /* There is nothing to be done on creation */
+ if (tr_tool->function == TRANSFORM_CREATING)
+ {
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time,
+ state, press_type, display);
+ return;
+ }
+
+ if (options->handle_mode == GIMP_HANDLE_MODE_ADD_MOVE)
+ {
+ /* add handle */
+
+ if (num < 4 && tr_tool->function == TRANSFORM_HANDLE_NONE)
+ {
+ tr_tool->trans_info[X0 + 2 * num] = coords->x;
+ tr_tool->trans_info[Y0 + 2 * num] = coords->y;
+ tr_tool->function = TRANSFORM_HANDLE_N + num;
+ tr_tool->trans_info[NUM]++;
+
+ /* check for valid position and calculating of OX0...OY3 is
+ * done on button release
+ */
+ }
+
+ /* move handles without changing the transformation matrix */
+ ht->matrix_recalculation = FALSE;
+ }
+ else if (options->handle_mode == GIMP_HANDLE_MODE_REMOVE &&
+ num > 0 &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ /* remove handle */
+
+ gdouble tempx = tr_tool->trans_info[X0 + 2 * active_handle];
+ gdouble tempy = tr_tool->trans_info[Y0 + 2 * active_handle];
+ gdouble tempox = tr_tool->trans_info[OX0 + 2 * active_handle];
+ gdouble tempoy = tr_tool->trans_info[OY0 + 2 * active_handle];
+ gint i;
+
+ num--;
+ tr_tool->trans_info[NUM]--;
+
+ for (i = active_handle; i < num; i++)
+ {
+ tr_tool->trans_info[X0 + 2 * i] = tr_tool->trans_info[X1 + 2 * i];
+ tr_tool->trans_info[Y0 + 2 * i] = tr_tool->trans_info[Y1 + 2 * i];
+ tr_tool->trans_info[OX0 + 2 * i] = tr_tool->trans_info[OX1 + 2 * i];
+ tr_tool->trans_info[OY0 + 2 * i] = tr_tool->trans_info[OY1 + 2 * i];
+ }
+
+ tr_tool->trans_info[X0 + 2 * num] = tempx;
+ tr_tool->trans_info[Y0 + 2 * num] = tempy;
+ tr_tool->trans_info[OX0 + 2 * num] = tempox;
+ tr_tool->trans_info[OY0 + 2 * num] = tempoy;
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time,
+ state, press_type, display);
+}
+
+static void
+gimp_handle_transform_tool_button_release (GimpTool *tool,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type,
+ GimpDisplay *display)
+{
+ GimpHandleTransformTool *ht = GIMP_HANDLE_TRANSFORM_TOOL (tool);
+ GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (tool);
+ GimpHandleTransformOptions *options;
+ gint active_handle;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ active_handle = tr_tool->function - TRANSFORM_HANDLE_N;
+
+ if (options->handle_mode == GIMP_HANDLE_MODE_ADD_MOVE &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ GimpMatrix3 matrix;
+
+ if (! is_handle_position_valid (tr_tool, active_handle))
+ {
+ handle_micro_move (tr_tool, active_handle);
+ }
+
+ /* handle was added or moved. calculate new original position */
+ matrix = tr_tool->transform;
+ gimp_matrix3_invert (&matrix);
+ gimp_matrix3_transform_point (&matrix,
+ tr_tool->trans_info[X0 + 2 * active_handle],
+ tr_tool->trans_info[Y0 + 2 * active_handle],
+ &tr_tool->trans_info[OX0 + 2 * active_handle],
+ &tr_tool->trans_info[OY0 + 2 * active_handle]);
+ }
+
+ if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
+ {
+ /* force redraw */
+ gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
+ gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
+ }
+
+ ht->matrix_recalculation = TRUE;
+
+ GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
+ state, release_type, display);
+}
+
+static void
+gimp_handle_transform_tool_modifier_key (GimpTool *tool,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tool);
+ GimpHandleTransformOptions *options;
+ GdkModifierType shift = gimp_get_extend_selection_mask ();
+ GdkModifierType ctrl = gimp_get_constrain_behavior_mask ();
+ GimpTransformHandleMode handle_mode;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tool);
+
+ handle_mode = options->handle_mode;
+
+ if (press)
+ {
+ if (key == (state & (shift | ctrl)))
+ {
+ /* first modifier pressed */
+ ht_tool->saved_handle_mode = options->handle_mode;
+ }
+ }
+ else
+ {
+ if (! (state & (shift | ctrl)))
+ {
+ /* last modifier released */
+ handle_mode = ht_tool->saved_handle_mode;
+ }
+ }
+
+ if (state & shift)
+ {
+ handle_mode = GIMP_HANDLE_MODE_ADD_MOVE;
+ }
+ else if (state & ctrl)
+ {
+ handle_mode = GIMP_HANDLE_MODE_REMOVE;
+ }
+
+ if (handle_mode != options->handle_mode)
+ {
+ g_object_set (options, "handle-mode", handle_mode, NULL);
+ }
+
+ GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press,
+ state, display);
+}
+
+static void
+gimp_handle_transform_tool_dialog (GimpTransformTool *tr_tool)
+{
+ GimpHandleTransformTool *handle_transform = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
+ GtkWidget *frame;
+ GtkWidget *table;
+ gint x, y;
+
+ frame = gimp_frame_new (_("Transformation Matrix"));
+ gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tr_tool->gui)), frame,
+ FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (3, 3, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 2);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ for (y = 0; y < 3; y++)
+ for (x = 0; x < 3; x++)
+ {
+ GtkWidget *label = gtk_label_new (" ");
+
+ gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.0);
+ gtk_label_set_width_chars (GTK_LABEL (label), 12);
+ gtk_table_attach (GTK_TABLE (table), label,
+ x, x + 1, y, y + 1, GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ handle_transform->label[y][x] = label;
+ }
+}
+
+static void
+gimp_handle_transform_tool_dialog_update (GimpTransformTool *tr_tool)
+{
+ GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
+ gint x, y;
+
+ for (y = 0; y < 3; y++)
+ {
+ for (x = 0; x < 3; x++)
+ {
+ gchar buf[32];
+
+ g_snprintf (buf, sizeof (buf),
+ "%10.5f", tr_tool->transform.coeff[y][x]);
+
+ gtk_label_set_text (GTK_LABEL (ht_tool->label[y][x]), buf);
+ }
+ }
+}
+
+static void
+gimp_handle_transform_tool_prepare (GimpTransformTool *tr_tool)
+{
+ GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
+
+ tr_tool->trans_info[X0] = (gdouble) tr_tool->x1;
+ tr_tool->trans_info[Y0] = (gdouble) tr_tool->y1;
+ tr_tool->trans_info[X1] = (gdouble) tr_tool->x2;
+ tr_tool->trans_info[Y1] = (gdouble) tr_tool->y1;
+ tr_tool->trans_info[X2] = (gdouble) tr_tool->x1;
+ tr_tool->trans_info[Y2] = (gdouble) tr_tool->y2;
+ tr_tool->trans_info[X3] = (gdouble) tr_tool->x2;
+ tr_tool->trans_info[Y3] = (gdouble) tr_tool->y2;
+ tr_tool->trans_info[OX0] = (gdouble) tr_tool->x1;
+ tr_tool->trans_info[OY0] = (gdouble) tr_tool->y1;
+ tr_tool->trans_info[OX1] = (gdouble) tr_tool->x2;
+ tr_tool->trans_info[OY1] = (gdouble) tr_tool->y1;
+ tr_tool->trans_info[OX2] = (gdouble) tr_tool->x1;
+ tr_tool->trans_info[OY2] = (gdouble) tr_tool->y2;
+ tr_tool->trans_info[OX3] = (gdouble) tr_tool->x2;
+ tr_tool->trans_info[OY3] = (gdouble) tr_tool->y2;
+ tr_tool->trans_info[NUM] = 0;
+
+ ht_tool->matrix_recalculation = TRUE;
+}
+
+static void
+gimp_handle_transform_tool_motion (GimpTransformTool *tr_tool)
+{
+ GimpHandleTransformOptions *options;
+ gint active_handle;
+ gint num;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ active_handle = tr_tool->function - TRANSFORM_HANDLE_N;
+ num = (gint) tr_tool->trans_info[NUM];
+
+ if (active_handle >= 0 && active_handle < 4)
+ {
+ if (options->handle_mode == GIMP_HANDLE_MODE_ADD_MOVE)
+ {
+ tr_tool->trans_info[X0 + 2*active_handle] += tr_tool->curx - tr_tool->lastx;
+ tr_tool->trans_info[Y0 + 2*active_handle] += tr_tool->cury - tr_tool->lasty;
+ /* check for valid position and calculating of OX0...OY3 is
+ * done on button release hopefully this makes the code run
+ * faster Moving could be even faster if there was caching
+ * for the image preview
+ */
+ }
+ else if (options->handle_mode == GIMP_HANDLE_MODE_TRANSFORM)
+ {
+ gdouble angle, angle_sin, angle_cos, scale;
+ gdouble fixed_handles_x[3];
+ gdouble fixed_handles_y[3];
+ gdouble oldpos_x[4], oldpos_y[4];
+ gdouble newpos_x[4], newpos_y[4];
+ gint i, j;
+
+ for (i = 0, j = 0; i < 4; i++)
+ {
+ /* Find all visible handles that are not being moved */
+ if (i < num && i != active_handle)
+ {
+ fixed_handles_x[j] = tr_tool->prev_trans_info[0][X0+i*2];
+ fixed_handles_y[j] = tr_tool->prev_trans_info[0][Y0+i*2];
+ j++;
+ }
+
+ newpos_x[i] = oldpos_x[i] = tr_tool->prev_trans_info[0][X0+i*2];
+ newpos_y[i] = oldpos_y[i] = tr_tool->prev_trans_info[0][Y0+i*2];
+ }
+
+ newpos_x[active_handle] = oldpos_x[active_handle] + tr_tool->curx - tr_tool->mousex;
+ newpos_y[active_handle] = oldpos_y[active_handle] + tr_tool->cury - tr_tool->mousey;
+
+ switch (num)
+ {
+ case 1:
+ /* move */
+ for (i = 1; i < 4; i++)
+ {
+ newpos_x[i] = oldpos_x[i] + tr_tool->curx - tr_tool->mousex;
+ newpos_y[i] = oldpos_y[i] + tr_tool->cury - tr_tool->mousey;
+ }
+ break;
+
+ case 2:
+ /* rotate and keep-aspect-scale */
+ scale = calc_len (newpos_x[active_handle] - fixed_handles_x[0],
+ newpos_y[active_handle] - fixed_handles_y[0])
+ / calc_len (oldpos_x[active_handle] - fixed_handles_x[0],
+ oldpos_y[active_handle] - fixed_handles_y[0]);
+
+ angle = calc_angle (oldpos_x[active_handle] - fixed_handles_x[0],
+ oldpos_y[active_handle] - fixed_handles_y[0],
+ newpos_x[active_handle] - fixed_handles_x[0],
+ newpos_y[active_handle] - fixed_handles_y[0]);
+
+ angle_sin = sin (angle);
+ angle_cos = cos (angle);
+
+ for (i = 2; i < 4; i++)
+ {
+ newpos_x[i] = fixed_handles_x[0]
+ + scale * (angle_cos * (oldpos_x[i]-fixed_handles_x[0])
+ + angle_sin * (oldpos_y[i]-fixed_handles_y[0]) );
+ newpos_y[i] = fixed_handles_y[0]
+ + scale * (-angle_sin * (oldpos_x[i]-fixed_handles_x[0])
+ + angle_cos * (oldpos_y[i]-fixed_handles_y[0]) );
+ }
+ break;
+
+ case 3:
+ /* shear and non-aspect-scale */
+ scale = calc_lineintersect_ratio (oldpos_x[3], oldpos_y[3],
+ oldpos_x[active_handle], oldpos_y[active_handle],
+ fixed_handles_x[0], fixed_handles_y[0],
+ fixed_handles_x[1], fixed_handles_y[1]);
+
+ newpos_x[3] = oldpos_x[3] + scale * (tr_tool->curx - tr_tool->mousex);
+ newpos_y[3] = oldpos_y[3] + scale * (tr_tool->cury - tr_tool->mousey);
+ break;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ tr_tool->trans_info[X0 + 2*i] = newpos_x[i];
+ tr_tool->trans_info[Y0 + 2*i] = newpos_y[i];
+ }
+ }
+ }
+}
+
+static void
+gimp_handle_transform_tool_recalc_matrix (GimpTransformTool *tr_tool)
+{
+ gdouble coeff[8*9];
+ gdouble sol[8];
+ int i;
+ gdouble opos_x[4], opos_y[4];
+ gdouble pos_x[4], pos_y[4];
+ GimpHandleTransformTool *handle_transform = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
+
+ if (handle_transform->matrix_recalculation)
+ {
+ for (i = 0; i < 4; i++)
+ {
+ pos_x[i] = tr_tool->trans_info[X0+i*2];
+ pos_y[i] = tr_tool->trans_info[Y0+i*2];
+ opos_x[i] = tr_tool->trans_info[OX0+i*2];
+ opos_y[i] = tr_tool->trans_info[OY0+i*2];
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ coeff[i*9+0] = opos_x[i];
+ coeff[i*9+1] = opos_y[i];
+ coeff[i*9+2] = 1;
+ coeff[i*9+3] = 0;
+ coeff[i*9+4] = 0;
+ coeff[i*9+5] = 0;
+ coeff[i*9+6] = -opos_x[i]*pos_x[i];
+ coeff[i*9+7] = -opos_y[i]*pos_x[i];
+ coeff[i*9+8] = pos_x[i];
+
+ coeff[(i+4)*9+0] = 0;
+ coeff[(i+4)*9+1] = 0;
+ coeff[(i+4)*9+2] = 0;
+ coeff[(i+4)*9+3] = opos_x[i];
+ coeff[(i+4)*9+4] = opos_y[i];
+ coeff[(i+4)*9+5] = 1;
+ coeff[(i+4)*9+6] = -opos_x[i]*pos_y[i];
+ coeff[(i+4)*9+7] = -opos_y[i]*pos_y[i];
+ coeff[(i+4)*9+8] = pos_y[i];
+ }
+
+ if (mod_gauss(coeff, sol, 8))
+ {
+ tr_tool->transform.coeff[0][0] = sol[0];
+ tr_tool->transform.coeff[0][1] = sol[1];
+ tr_tool->transform.coeff[0][2] = sol[2];
+ tr_tool->transform.coeff[1][0] = sol[3];
+ tr_tool->transform.coeff[1][1] = sol[4];
+ tr_tool->transform.coeff[1][2] = sol[5];
+ tr_tool->transform.coeff[2][0] = sol[6];
+ tr_tool->transform.coeff[2][1] = sol[7];
+ tr_tool->transform.coeff[2][2] = 1;
+ }
+ else
+ {
+ /* this should not happen
+ * reset the matrix so the user sees that something went wrong */
+ gimp_matrix3_identity (&tr_tool->transform);
+ }
+ }
+}
+
+static gchar *
+gimp_handle_transform_tool_get_undo_desc (GimpTransformTool *tr_tool)
+{
+ return g_strdup (C_("undo-type", "Handle transform"));
+}
+
+static TransformAction
+gimp_handle_transform_tool_pick_function (GimpTransformTool *tr_tool,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpDisplay *display)
+{
+ TransformAction i;
+
+ for (i = TRANSFORM_HANDLE_N; i < TRANSFORM_HANDLE_N + 4; i++)
+ {
+ if (tr_tool->handles[i] &&
+ gimp_canvas_item_hit (tr_tool->handles[i], coords->x, coords->y))
+ {
+ return i;
+ }
+ }
+
+ return TRANSFORM_HANDLE_NONE;
+}
+
+static void
+gimp_handle_transform_tool_cursor_update (GimpTransformTool *tr_tool,
+ GimpCursorType *cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpHandleTransformOptions *options;
+ GimpToolCursorType tool_cursor = GIMP_TOOL_CURSOR_NONE;
+
+ options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);
+
+ *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ *modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ /* do not show modifiers when the tool isn't active */
+ if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tr_tool)))
+ return;
+
+ if (options->handle_mode == GIMP_HANDLE_MODE_TRANSFORM &&
+ tr_tool->function > TRANSFORM_HANDLE_NONE)
+ {
+ switch ((gint) tr_tool->trans_info[NUM])
+ {
+ case 1:
+ tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+ case 2:
+ tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
+ break;
+ case 3:
+ tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
+ break;
+ case 4:
+ tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
+ break;
+ }
+ }
+ else if (options->handle_mode == GIMP_HANDLE_MODE_ADD_MOVE)
+ {
+ if (tr_tool->function > TRANSFORM_HANDLE_NONE)
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ }
+ else if (options->handle_mode == GIMP_HANDLE_MODE_REMOVE)
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ }
+
+ gimp_tool_control_set_tool_cursor (GIMP_TOOL (tr_tool)->control,
+ tool_cursor);
+}
+
+static void
+gimp_handle_transform_tool_draw_gui (GimpTransformTool *tr_tool,
+ gint handle_w,
+ gint handle_h)
+{
+ GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tr_tool);
+ gint i;
+
+#if 0
+ /* show additional points for debugging */
+ for (i = tr_tool->trans_info[NUM]; i < 4; i++)
+ {
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_FILLED_CIRCLE,
+ tr_tool->trans_info[X0+2*i],
+ tr_tool->trans_info[Y0+2*i],
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_FILLED_DIAMOND,
+ tr_tool->trans_info[OX0+2*i],
+ tr_tool->trans_info[OY0+2*i],
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+
+ for (i = 0; i < tr_tool->trans_info[NUM]; i++)
+ {
+ tr_tool->handles[TRANSFORM_HANDLE_N + i] =
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_DIAMOND,
+ tr_tool->trans_info[OX0+2*i],
+ tr_tool->trans_info[OY0+2*i],
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+#endif
+
+ for (i = 0; i < tr_tool->trans_info[NUM]; i++)
+ {
+ tr_tool->handles[TRANSFORM_HANDLE_N + i] =
+ gimp_draw_tool_add_handle (draw_tool,
+ GIMP_HANDLE_CIRCLE,
+ tr_tool->trans_info[X0 + 2 * i],
+ tr_tool->trans_info[Y0 + 2 * i],
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_TOOL_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+}
+
+/* check if a handle is not on the connection line of two other handles */
+static gboolean
+is_handle_position_valid (GimpTransformTool *tr_tool,
+ gint active_handle)
+{
+ gint i, j, k;
+
+ if (tr_tool->trans_info[NUM] < 3)
+ {
+ /* there aren't two other handles */
+ return TRUE;
+ }
+
+ if (tr_tool->trans_info[NUM] == 3)
+ {
+ return ((tr_tool->trans_info[X0] - tr_tool->trans_info[X1]) *
+ (tr_tool->trans_info[Y1] - tr_tool->trans_info[Y2]) !=
+
+ (tr_tool->trans_info[X1] - tr_tool->trans_info[X2]) *
+ (tr_tool->trans_info[Y0] - tr_tool->trans_info[Y1]));
+ }
+
+ /* tr_tool->trans_info[NUM] == 4 */
+ for (i = 0; i < 2; i++)
+ {
+ for (j = i + 1; j < 3; j++)
+ {
+ for (k = j + 1; i < 4; i++)
+ {
+ if (active_handle == i ||
+ active_handle == j ||
+ active_handle == k)
+ {
+ if ((tr_tool->trans_info[X0 + 2 * i] -
+ tr_tool->trans_info[X0 + 2 * j]) *
+ (tr_tool->trans_info[Y0 + 2 * j] -
+ tr_tool->trans_info[Y0 + 2 * k]) ==
+
+ (tr_tool->trans_info[X0 + 2 * j] -
+ tr_tool->trans_info[X0 + 2 * k]) *
+ (tr_tool->trans_info[Y0 + 2 * i] -
+ tr_tool->trans_info[Y0 + 2 * j]))
+ {
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+/* three handles on a line causes problems.
+ * Let's move the new handle around a bit to find a better position */
+static void
+handle_micro_move (GimpTransformTool *tr_tool,
+ gint active_handle)
+{
+ gdouble posx = tr_tool->trans_info[X0 + 2 * active_handle];
+ gdouble posy = tr_tool->trans_info[Y0 + 2 * active_handle];
+ gdouble dx, dy;
+
+ for (dx = -0.1; dx < 0.11; dx += 0.1)
+ {
+ tr_tool->trans_info[X0 + 2 * active_handle] = posx + dx;
+
+ for (dy = -0.1; dy < 0.11; dy += 0.1)
+ {
+ tr_tool->trans_info[Y0 + 2 * active_handle] = posy + dy;
+
+ if (is_handle_position_valid (tr_tool, active_handle))
+ {
+ return;
+ }
+ }
+ }
+}
+
+/* finds the clockwise angle between the vectors given, 0-2π */
+static inline gdouble
+calc_angle (gdouble ax,
+ gdouble ay,
+ gdouble bx,
+ gdouble by)
+{
+ gdouble angle;
+ gdouble direction;
+ gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by));
+
+ angle = acos ((ax * bx + ay * by) / length);
+ direction = ax * by - ay * bx;
+
+ return ((direction < 0) ? angle : 2 * G_PI - angle);
+}
+
+static inline gdouble
+calc_len (gdouble a,
+ gdouble b)
+{
+ return sqrt (a * a + b * b);
+}
+
+
+/* imagine two lines, one through the points p1 and p2, the other one
+ * through the points q1 and q2. Find the intersection point r.
+ * Calculate (distance p1 to r)/(distance p2 to r)
+ */
+static inline gdouble
+calc_lineintersect_ratio (gdouble p1x, gdouble p1y,
+ gdouble p2x, gdouble p2y,
+ gdouble q1x, gdouble q1y,
+ gdouble q2x, gdouble q2y)
+{
+ gdouble denom, u;
+
+ denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y);
+ if (denom == 0.0)
+ {
+ /* u is infinite, so u/(u-1) is 1 */
+ return 1.0;
+ }
+
+ u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x);
+ u /= denom;
+
+ return u / (u - 1);
+}
+
+
+/* modified gaussian algorithm
+ * solves a system of linear equations
+ *
+ * Example:
+ * 1x + 2y + 4z = 25
+ * 2x + 1y = 4
+ * 3x + 5y + 2z = 23
+ * Solution: x=1, y=2, z=5
+ *
+ * Input:
+ * matrix = { 1,2,4,25,2,1,0,4,3,5,2,23 }
+ * s = 3 (Number of variables)
+ * Output:
+ * return value == TRUE (TRUE, if there is a single unique solution)
+ * solution == { 1,2,5 } (if the return value is FALSE, the content
+ * of solution is of no use)
+ */
+static gboolean
+mod_gauss (gdouble matrix[],
+ gdouble solution[],
+ gint s)
+{
+ gint p[s]; /* row permutation */
+ gint i, j, r, temp;
+ gdouble q;
+ gint t = s + 1;
+
+ for (i = 0; i < s; i++)
+ {
+ p[i] = i;
+ }
+
+ for (r = 0; r < s; r++)
+ {
+ /* make sure that (r,r) is not 0 */
+ if (matrix[p[r] * t + r] == 0.0)
+ {
+ /* we need to permutate rows */
+ for (i = r + 1; i <= s; i++)
+ {
+ if (i == s)
+ {
+ /* if this happens, the linear system has zero or
+ * more than one solutions.
+ */
+ return FALSE;
+ }
+
+ if (matrix[p[i] * t + r] != 0.0)
+ break;
+ }
+
+ temp = p[r];
+ p[r] = p[i];
+ p[i] = temp;
+ }
+
+ /* make (r,r) == 1 */
+ q = 1.0 / matrix[p[r] * t + r];
+ matrix[p[r] * t + r] = 1.0;
+
+ for (j = r + 1; j < t; j++)
+ {
+ matrix[p[r] * t + j] *= q;
+ }
+
+ /* make that all entries in column r are 0 (except (r,r)) */
+ for (i = 0; i < s; i++)
+ {
+ if (i == r)
+ continue;
+
+ for (j = r + 1; j < t ; j++)
+ {
+ matrix[p[i] * t + j] -= matrix[p[r] * t + j] * matrix[p[i] * t + r];
+ }
+
+ /* we don't need to execute the following line
+ * since we won't access this element again:
+ *
+ * matrix[p[i] * t + r] = 0.0;
+ */
+ }
+ }
+
+ for (i = 0; i < s; i++)
+ {
+ solution[i] = matrix[p[i] * t + s];
+ }
+
+ return TRUE;
+}
diff --git a/app/tools/gimphandletransformtool.h b/app/tools/gimphandletransformtool.h
new file mode 100644
index 0000000000..df6157efbf
--- /dev/null
+++ b/app/tools/gimphandletransformtool.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 .
+ */
+
+#ifndef __GIMP_HANDLE_TRANSFORM_TOOL_H__
+#define __GIMP_HANDLE_TRANSFORM_TOOL_H__
+
+
+#include "gimptransformtool.h"
+
+
+#define GIMP_TYPE_HANDLE_TRANSFORM_TOOL (gimp_handle_transform_tool_get_type ())
+#define GIMP_HANDLE_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformTool))
+#define GIMP_HANDLE_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformToolClass))
+#define GIMP_IS_HANDLE_TRANSFORM_TOOL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL))
+#define GIMP_IS_HANDLE_TRANSFORM_TOOL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HANDLE_TRANSFORM_TOOL))
+#define GIMP_HANDLE_TRANSFORM_TOOL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HANDLE_TRANSFORM_TOOL, GimpHandleTransformToolClass))
+
+#define GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS(t) (GIMP_HANDLE_TRANSFORM_OPTIONS (gimp_tool_get_options (GIMP_TOOL (t))))
+
+
+typedef struct _GimpHandleTransformTool GimpHandleTransformTool;
+typedef struct _GimpHandleTransformToolClass GimpHandleTransformToolClass;
+
+struct _GimpHandleTransformTool
+{
+ GimpTransformTool parent_instance;
+
+ GtkWidget *label[3][3];
+ gboolean matrix_recalculation;
+
+ GimpTransformHandleMode saved_handle_mode;
+};
+
+struct _GimpHandleTransformToolClass
+{
+ GimpTransformToolClass parent_class;
+};
+
+
+void gimp_handle_transform_tool_register (GimpToolRegisterCallback callback,
+ gpointer data);
+
+GType gimp_handle_transform_tool_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_HANDLE_TRANSFORM_TOOL_H__ */
diff --git a/app/tools/gimptransformtool.c b/app/tools/gimptransformtool.c
index 885cc4d95b..34fcc1e524 100644
--- a/app/tools/gimptransformtool.c
+++ b/app/tools/gimptransformtool.c
@@ -59,6 +59,7 @@
#include "display/gimptoolgui.h"
#include "gimptoolcontrol.h"
+#include "gimphandletransformtool.h"
#include "gimpperspectivetool.h"
#include "gimpunifiedtransformtool.h"
#include "gimptransformoptions.h"
@@ -1088,10 +1089,15 @@ gimp_transform_tool_draw (GimpDrawTool *draw_tool)
if (gimp_transform_options_show_preview (options))
{
GimpMatrix3 matrix = tr_tool->transform;
+ gboolean perspective;
if (options->direction == GIMP_TRANSFORM_BACKWARD)
gimp_matrix3_invert (&matrix);
+ perspective = (GIMP_IS_PERSPECTIVE_TOOL (tr_tool) ||
+ GIMP_IS_HANDLE_TRANSFORM_TOOL (tr_tool) ||
+ GIMP_IS_UNIFIED_TRANSFORM_TOOL (tr_tool));
+
gimp_draw_tool_add_transform_preview (draw_tool,
tool->drawable,
&matrix,
@@ -1099,8 +1105,7 @@ gimp_transform_tool_draw (GimpDrawTool *draw_tool)
tr_tool->y1,
tr_tool->x2,
tr_tool->y2,
- GIMP_IS_PERSPECTIVE_TOOL (tr_tool) ||
- GIMP_IS_UNIFIED_TRANSFORM_TOOL (tr_tool),
+ perspective,
options->preview_opacity);
}
diff --git a/app/tools/gimptransformtool.h b/app/tools/gimptransformtool.h
index 2ee4634017..4ef6591fd2 100644
--- a/app/tools/gimptransformtool.h
+++ b/app/tools/gimptransformtool.h
@@ -54,7 +54,7 @@ typedef enum
* of the enums at the top of each transformation tool, stored in
* trans_info and related
*/
-#define TRANS_INFO_SIZE 10
+#define TRANS_INFO_SIZE 17
typedef gdouble TransInfo[TRANS_INFO_SIZE];
diff --git a/app/tools/tools-enums.c b/app/tools/tools-enums.c
index 61155c7227..77b5fb8087 100644
--- a/app/tools/tools-enums.c
+++ b/app/tools/tools-enums.c
@@ -73,6 +73,37 @@ gimp_button_release_type_get_type (void)
return type;
}
+GType
+gimp_transform_handle_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_HANDLE_MODE_ADD_MOVE, "GIMP_HANDLE_MODE_ADD_MOVE", "add-move" },
+ { GIMP_HANDLE_MODE_REMOVE, "GIMP_HANDLE_MODE_REMOVE", "remove" },
+ { GIMP_HANDLE_MODE_TRANSFORM, "GIMP_HANDLE_MODE_TRANSFORM", "transform" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_HANDLE_MODE_ADD_MOVE, NC_("transform-handle-mode", "Add/Move"), NULL },
+ { GIMP_HANDLE_MODE_REMOVE, NC_("transform-handle-mode", "Remove"), NULL },
+ { GIMP_HANDLE_MODE_TRANSFORM, NC_("transform-handle-mode", "Transform"), NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpTransformHandleMode", values);
+ gimp_type_set_translation_context (type, "transform-handle-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
GType
gimp_rectangle_constraint_get_type (void)
{
diff --git a/app/tools/tools-enums.h b/app/tools/tools-enums.h
index c2d2cc06cf..60246efe0d 100644
--- a/app/tools/tools-enums.h
+++ b/app/tools/tools-enums.h
@@ -47,6 +47,18 @@ typedef enum
} GimpButtonReleaseType;
+#define GIMP_TYPE_TRANSFORM_HANDLE_MODE (gimp_transform_handle_mode_get_type ())
+
+GType gimp_transform_handle_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_HANDLE_MODE_ADD_MOVE, /*< desc="Add/Move" >*/
+ GIMP_HANDLE_MODE_REMOVE, /*< desc="Remove" >*/
+ GIMP_HANDLE_MODE_TRANSFORM, /*< desc="Transform" >*/
+} GimpTransformHandleMode;
+
+
#define GIMP_TYPE_RECTANGLE_CONSTRAINT (gimp_rectangle_constraint_get_type ())
GType gimp_rectangle_constraint_get_type (void) G_GNUC_CONST;
diff --git a/app/widgets/gimphelp-ids.h b/app/widgets/gimphelp-ids.h
index 1d6754eb75..7e7076ae4a 100644
--- a/app/widgets/gimphelp-ids.h
+++ b/app/widgets/gimphelp-ids.h
@@ -281,6 +281,7 @@
#define GIMP_HELP_TOOL_FOREGROUND_SELECT "gimp-tool-foreground-select"
#define GIMP_HELP_TOOL_FUZZY_SELECT "gimp-tool-fuzzy-select"
#define GIMP_HELP_TOOL_GEGL "gimp-tool-gegl"
+#define GIMP_HELP_TOOL_HANDLE_TRANSFORM "gimp-tool-handle-transform"
#define GIMP_HELP_TOOL_HEAL "gimp-tool-heal"
#define GIMP_HELP_TOOL_HUE_SATURATION "gimp-tool-hue-saturation"
#define GIMP_HELP_TOOL_INK "gimp-tool-ink"
diff --git a/icons/16/gimp-tool-handle-transform.png b/icons/16/gimp-tool-handle-transform.png
new file mode 100644
index 0000000000..8bc2ed4f26
Binary files /dev/null and b/icons/16/gimp-tool-handle-transform.png differ
diff --git a/icons/22/gimp-tool-handle-transform.png b/icons/22/gimp-tool-handle-transform.png
new file mode 100644
index 0000000000..797b99b7d6
Binary files /dev/null and b/icons/22/gimp-tool-handle-transform.png differ
diff --git a/icons/22/gimp-tool-handle-transform.xcf b/icons/22/gimp-tool-handle-transform.xcf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/icons/Makefile.am b/icons/Makefile.am
index a6a030e95b..71b007cb2b 100644
--- a/icons/Makefile.am
+++ b/icons/Makefile.am
@@ -180,6 +180,7 @@ icons16_DATA = \
16/gimp-tool-foreground-select.png \
16/gimp-tool-free-select.png \
16/gimp-tool-fuzzy-select.png \
+ 16/gimp-tool-handle-transform.png \
16/gimp-tool-heal.png \
16/gimp-tool-hue-saturation.png \
16/gimp-tool-ink.png \
@@ -290,6 +291,7 @@ icons22_DATA = \
22/gimp-tool-foreground-select.png \
22/gimp-tool-free-select.png \
22/gimp-tool-fuzzy-select.png \
+ 22/gimp-tool-handle-transform.png \
22/gimp-tool-heal.png \
22/gimp-tool-hue-saturation.png \
22/gimp-tool-ink.png \
diff --git a/libgimpwidgets/gimpstock.c b/libgimpwidgets/gimpstock.c
index 320237180d..5a7e969072 100644
--- a/libgimpwidgets/gimpstock.c
+++ b/libgimpwidgets/gimpstock.c
@@ -256,6 +256,7 @@ static const GtkStockItem gimp_stock_items[] =
{ GIMP_STOCK_TOOL_FOREGROUND_SELECT, N_("_Select"), 0, 0, LIBGIMP_DOMAIN },
{ GIMP_STOCK_TOOL_FUZZY_SELECT, NULL, 0, 0, LIBGIMP_DOMAIN },
{ GIMP_STOCK_TOOL_HUE_SATURATION, NULL, 0, 0, LIBGIMP_DOMAIN },
+ { GIMP_STOCK_TOOL_HANDLE_TRANSFORM,N_("_Transform"),0, 0, LIBGIMP_DOMAIN },
{ GIMP_STOCK_TOOL_HEAL, NULL, 0, 0, LIBGIMP_DOMAIN },
{ GIMP_STOCK_TOOL_INK, NULL, 0, 0, LIBGIMP_DOMAIN },
{ GIMP_STOCK_TOOL_ISCISSORS, NULL, 0, 0, LIBGIMP_DOMAIN },
diff --git a/libgimpwidgets/gimpstock.h b/libgimpwidgets/gimpstock.h
index 97936146dd..ed23e2d07d 100644
--- a/libgimpwidgets/gimpstock.h
+++ b/libgimpwidgets/gimpstock.h
@@ -125,6 +125,7 @@ G_BEGIN_DECLS
#define GIMP_STOCK_TOOL_FREE_SELECT "gimp-tool-free-select"
#define GIMP_STOCK_TOOL_FOREGROUND_SELECT "gimp-tool-foreground-select"
#define GIMP_STOCK_TOOL_FUZZY_SELECT "gimp-tool-fuzzy-select"
+#define GIMP_STOCK_TOOL_HANDLE_TRANSFORM "gimp-tool-handle-transform"
#define GIMP_STOCK_TOOL_HEAL "gimp-tool-heal"
#define GIMP_STOCK_TOOL_HUE_SATURATION "gimp-tool-hue-saturation"
#define GIMP_STOCK_TOOL_INK "gimp-tool-ink"
diff --git a/menus/image-menu.xml.in b/menus/image-menu.xml.in
index c9162b581b..a1025b30eb 100644
--- a/menus/image-menu.xml.in
+++ b/menus/image-menu.xml.in
@@ -602,6 +602,7 @@
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7b5e93de12..c34dbce77e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -368,6 +368,8 @@ app/tools/gimpforegroundselecttool.c
app/tools/gimpfreeselecttool.c
app/tools/gimpfuzzyselecttool.c
app/tools/gimpgegltool.c
+app/tools/gimphandletransformoptions.c
+app/tools/gimphandletransformtool.c
app/tools/gimphealtool.c
app/tools/gimphistogramoptions.c
app/tools/gimphuesaturationtool.c