From 457f6bf95298e2904ac89336d719f649072de942 Mon Sep 17 00:00:00 2001 From: Michael Natterer Date: Sat, 17 Jun 2017 02:34:35 +0200 Subject: [PATCH] app: add GimpToolTransformGrid, GimpToolRotateGrid and GimpToolShearGrid which do all transform tools' (except handle transform) canvas GUI and their interaction. --- app/display/Makefile.am | 6 + app/display/display-enums.c | 35 + app/display/display-enums.h | 14 + app/display/gimptoolrotategrid.c | 334 ++++ app/display/gimptoolrotategrid.h | 65 + app/display/gimptoolsheargrid.c | 364 +++++ app/display/gimptoolsheargrid.h | 65 + app/display/gimptooltransformgrid.c | 2245 +++++++++++++++++++++++++++ app/display/gimptooltransformgrid.h | 92 ++ 9 files changed, 3220 insertions(+) create mode 100644 app/display/gimptoolrotategrid.c create mode 100644 app/display/gimptoolrotategrid.h create mode 100644 app/display/gimptoolsheargrid.c create mode 100644 app/display/gimptoolsheargrid.h create mode 100644 app/display/gimptooltransformgrid.c create mode 100644 app/display/gimptooltransformgrid.h diff --git a/app/display/Makefile.am b/app/display/Makefile.am index 2069226336..ccde805c12 100644 --- a/app/display/Makefile.am +++ b/app/display/Makefile.am @@ -168,6 +168,12 @@ libappdisplay_a_sources = \ gimptoolcompass.h \ gimptoolline.c \ gimptoolline.h \ + gimptoolrotategrid.c \ + gimptoolrotategrid.h \ + gimptoolsheargrid.c \ + gimptoolsheargrid.h \ + gimptooltransformgrid.c \ + gimptooltransformgrid.h \ gimptoolwidget.c \ gimptoolwidget.h diff --git a/app/display/display-enums.c b/app/display/display-enums.c index 577e473896..e80a7ad3cd 100644 --- a/app/display/display-enums.c +++ b/app/display/display-enums.c @@ -259,6 +259,41 @@ gimp_path_style_get_type (void) return type; } +GType +gimp_transform_function_get_type (void) +{ + static const GEnumValue values[] = + { + { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", "move" }, + { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", "scale" }, + { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", "rotate" }, + { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", "shear" }, + { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", "perspective" }, + { 0, NULL, NULL } + }; + + static const GimpEnumDesc descs[] = + { + { GIMP_TRANSFORM_FUNCTION_MOVE, "GIMP_TRANSFORM_FUNCTION_MOVE", NULL }, + { GIMP_TRANSFORM_FUNCTION_SCALE, "GIMP_TRANSFORM_FUNCTION_SCALE", NULL }, + { GIMP_TRANSFORM_FUNCTION_ROTATE, "GIMP_TRANSFORM_FUNCTION_ROTATE", NULL }, + { GIMP_TRANSFORM_FUNCTION_SHEAR, "GIMP_TRANSFORM_FUNCTION_SHEAR", NULL }, + { GIMP_TRANSFORM_FUNCTION_PERSPECTIVE, "GIMP_TRANSFORM_FUNCTION_PERSPECTIVE", NULL }, + { 0, NULL, NULL } + }; + + static GType type = 0; + + if (G_UNLIKELY (! type)) + { + type = g_enum_register_static ("GimpTransformFunction", values); + gimp_type_set_translation_context (type, "transform-function"); + gimp_enum_set_value_descriptions (type, descs); + } + + return type; +} + GType gimp_zoom_focus_get_type (void) { diff --git a/app/display/display-enums.h b/app/display/display-enums.h index e0d31224a5..704856e151 100644 --- a/app/display/display-enums.h +++ b/app/display/display-enums.h @@ -120,6 +120,20 @@ typedef enum } GimpPathStyle; +#define GIMP_TYPE_TRANSFORM_FUNCTION (gimp_transform_function_get_type ()) + +GType gimp_transform_function_get_type (void) G_GNUC_CONST; + +typedef enum +{ + GIMP_TRANSFORM_FUNCTION_MOVE, + GIMP_TRANSFORM_FUNCTION_SCALE, + GIMP_TRANSFORM_FUNCTION_ROTATE, + GIMP_TRANSFORM_FUNCTION_SHEAR, + GIMP_TRANSFORM_FUNCTION_PERSPECTIVE +} GimpTransformFunction; + + #define GIMP_TYPE_ZOOM_FOCUS (gimp_zoom_focus_get_type ()) GType gimp_zoom_focus_get_type (void) G_GNUC_CONST; diff --git a/app/display/gimptoolrotategrid.c b/app/display/gimptoolrotategrid.c new file mode 100644 index 0000000000..77ffa02f1f --- /dev/null +++ b/app/display/gimptoolrotategrid.c @@ -0,0 +1,334 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrotategrid.c + * Copyright (C) 2017 Michael Natterer + * + * 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 "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpdisplayshell.h" +#include "gimptoolrotategrid.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_ANGLE +}; + + +struct _GimpToolRotateGridPrivate +{ + gdouble angle; + + gboolean rotate_grab; + gdouble real_angle; + gdouble last_x; + gdouble last_y; +}; + + +/* local function prototypes */ + +static void gimp_tool_rotate_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_rotate_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_rotate_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_rotate_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + + +G_DEFINE_TYPE (GimpToolRotateGrid, gimp_tool_rotate_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_rotate_grid_parent_class + + +static void +gimp_tool_rotate_grid_class_init (GimpToolRotateGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->set_property = gimp_tool_rotate_grid_set_property; + object_class->get_property = gimp_tool_rotate_grid_get_property; + + widget_class->button_press = gimp_tool_rotate_grid_button_press; + widget_class->motion = gimp_tool_rotate_grid_motion; + + g_object_class_install_property (object_class, PROP_ANGLE, + g_param_spec_double ("angle", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GimpToolRotateGridPrivate)); +} + +static void +gimp_tool_rotate_grid_init (GimpToolRotateGrid *grid) +{ + grid->private = G_TYPE_INSTANCE_GET_PRIVATE (grid, + GIMP_TYPE_TOOL_ROTATE_GRID, + GimpToolRotateGridPrivate); +} + +static void +gimp_tool_rotate_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object); + GimpToolRotateGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ANGLE: + private->angle = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_rotate_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (object); + GimpToolRotateGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ANGLE: + g_value_set_double (value, private->angle); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_rotate_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget); + GimpToolRotateGridPrivate *private = grid->private; + GimpTransformHandle handle; + + handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press (widget, + coords, time, + state, + press_type); + + if (handle == GIMP_TRANSFORM_HANDLE_ROTATION) + { + private->rotate_grab = TRUE; + private->real_angle = private->angle; + private->last_x = coords->x; + private->last_y = coords->y; + } + else + { + private->rotate_grab = FALSE; + } + + return handle; +} + +void +gimp_tool_rotate_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolRotateGrid *grid = GIMP_TOOL_ROTATE_GRID (widget); + GimpToolRotateGridPrivate *private = grid->private; + gdouble angle1, angle2, angle; + gdouble pivot_x, pivot_y; + gdouble x1, y1, x2, y2; + gboolean constrain; + GimpMatrix3 transform; + + if (! private->rotate_grab) + { + gdouble old_pivot_x; + gdouble old_pivot_y; + + g_object_get (widget, + "pivot-x", &old_pivot_x, + "pivot-y", &old_pivot_y, + NULL); + + g_object_freeze_notify (G_OBJECT (widget)); + + GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (widget, + coords, time, state); + + g_object_get (widget, + "pivot-x", &pivot_x, + "pivot-y", &pivot_y, + NULL); + + if (old_pivot_x != pivot_x || + old_pivot_y != pivot_y) + { + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, + pivot_x, pivot_y, + private->angle); + + g_object_set (widget, + "transform", &transform, + NULL); + } + + g_object_thaw_notify (G_OBJECT (widget)); + + return; + } + + g_object_get (widget, + "pivot-x", &pivot_x, + "pivot-y", &pivot_y, + "constrain-rotate", &constrain, + NULL); + + x1 = coords->x - pivot_x; + x2 = private->last_x - pivot_x; + y1 = pivot_y - coords->y; + y2 = pivot_y - private->last_y; + + /* find the first angle */ + angle1 = atan2 (y1, x1); + + /* find the angle */ + angle2 = atan2 (y2, x2); + + angle = angle2 - angle1; + + if (angle > G_PI || angle < -G_PI) + angle = angle2 - ((angle1 < 0) ? 2.0 * G_PI + angle1 : angle1 - 2.0 * G_PI); + + /* increment the transform tool's angle */ + private->real_angle += angle; + + /* limit the angle to between -180 and 180 degrees */ + if (private->real_angle < - G_PI) + { + private->real_angle += 2.0 * G_PI; + } + else if (private->real_angle > G_PI) + { + private->real_angle -= 2.0 * G_PI; + } + + /* constrain the angle to 15-degree multiples if ctrl is held down */ +#define FIFTEEN_DEG (G_PI / 12.0) + + if (constrain) + { + angle = FIFTEEN_DEG * (gint) ((private->real_angle + + FIFTEEN_DEG / 2.0) / FIFTEEN_DEG); + } + else + { + angle = private->real_angle; + } + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle); + + g_object_set (widget, + "transform", &transform, + "angle", angle, + NULL); + + private->last_x = coords->x; + private->last_y = coords->y; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_rotate_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble pivot_x, + gdouble pivot_y, + gdouble angle) +{ + GimpMatrix3 transform; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_rotate_center (&transform, pivot_x, pivot_y, angle); + + return g_object_new (GIMP_TYPE_TOOL_ROTATE_GRID, + "shell", shell, + "transform", &transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "pivot-x", pivot_x, + "pivot-y", pivot_y, + "angle", angle, + NULL); +} diff --git a/app/display/gimptoolrotategrid.h b/app/display/gimptoolrotategrid.h new file mode 100644 index 0000000000..43c97ccbf3 --- /dev/null +++ b/app/display/gimptoolrotategrid.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolrotategrid.h + * Copyright (C) 2017 Michael Natterer + * + * 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_TOOL_ROTATE_GRID_H__ +#define __GIMP_TOOL_ROTATE_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_ROTATE_GRID (gimp_tool_rotate_grid_get_type ()) +#define GIMP_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGrid)) +#define GIMP_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass)) +#define GIMP_IS_TOOL_ROTATE_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_ROTATE_GRID)) +#define GIMP_IS_TOOL_ROTATE_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_ROTATE_GRID)) +#define GIMP_TOOL_ROTATE_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_ROTATE_GRID, GimpToolRotateGridClass)) + + +typedef struct _GimpToolRotateGrid GimpToolRotateGrid; +typedef struct _GimpToolRotateGridPrivate GimpToolRotateGridPrivate; +typedef struct _GimpToolRotateGridClass GimpToolRotateGridClass; + +struct _GimpToolRotateGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolRotateGridPrivate *private; +}; + +struct _GimpToolRotateGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_rotate_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_rotate_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + gdouble pivot_x, + gdouble pivot_y, + gdouble angle); + + +#endif /* __GIMP_TOOL_ROTATE_GRID_H__ */ diff --git a/app/display/gimptoolsheargrid.c b/app/display/gimptoolsheargrid.c new file mode 100644 index 0000000000..0827e905f8 --- /dev/null +++ b/app/display/gimptoolsheargrid.c @@ -0,0 +1,364 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolsheargrid.c + * Copyright (C) 2017 Michael Natterer + * + * 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 "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpdisplayshell.h" +#include "gimptoolsheargrid.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_ORIENTATION, + PROP_SHEAR_X, + PROP_SHEAR_Y +}; + + +struct _GimpToolShearGridPrivate +{ + GimpOrientationType orientation; + gdouble shear_x; + gdouble shear_y; + + gdouble last_x; + gdouble last_y; +}; + + +/* local function prototypes */ + +static void gimp_tool_shear_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_shear_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint gimp_tool_shear_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_shear_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); + + +G_DEFINE_TYPE (GimpToolShearGrid, gimp_tool_shear_grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID) + +#define parent_class gimp_tool_shear_grid_parent_class + + +static void +gimp_tool_shear_grid_class_init (GimpToolShearGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->set_property = gimp_tool_shear_grid_set_property; + object_class->get_property = gimp_tool_shear_grid_get_property; + + widget_class->button_press = gimp_tool_shear_grid_button_press; + widget_class->motion = gimp_tool_shear_grid_motion; + + g_object_class_install_property (object_class, PROP_ORIENTATION, + g_param_spec_enum ("orientation", + NULL, NULL, + GIMP_TYPE_ORIENTATION_TYPE, + GIMP_ORIENTATION_UNKNOWN, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SHEAR_X, + g_param_spec_double ("shear-x", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_SHEAR_Y, + g_param_spec_double ("shear-y", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GimpToolShearGridPrivate)); +} + +static void +gimp_tool_shear_grid_init (GimpToolShearGrid *grid) +{ + grid->private = G_TYPE_INSTANCE_GET_PRIVATE (grid, + GIMP_TYPE_TOOL_SHEAR_GRID, + GimpToolShearGridPrivate); + + g_object_set (grid, + "inside-function", GIMP_TRANSFORM_FUNCTION_SHEAR, + "outside-function", GIMP_TRANSFORM_FUNCTION_SHEAR, + "use-corner-handles", FALSE, + "use-perspective-handles", FALSE, + "use-side-handles", FALSE, + "use-shear-handles", FALSE, + "use-center-handle", FALSE, + "use-pivot-handle", FALSE, + NULL); +} + +static void +gimp_tool_shear_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object); + GimpToolShearGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ORIENTATION: + private->orientation = g_value_get_enum (value); + break; + case PROP_SHEAR_X: + private->shear_x = g_value_get_double (value); + break; + case PROP_SHEAR_Y: + private->shear_y = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_tool_shear_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (object); + GimpToolShearGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_ORIENTATION: + g_value_set_enum (value, private->orientation); + break; + case PROP_SHEAR_X: + g_value_set_double (value, private->shear_x); + break; + case PROP_SHEAR_Y: + g_value_set_double (value, private->shear_y); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint +gimp_tool_shear_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget); + GimpToolShearGridPrivate *private = grid->private; + + private->last_x = coords->x; + private->last_y = coords->y; + + return 1; +} + +void +gimp_tool_shear_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolShearGrid *grid = GIMP_TOOL_SHEAR_GRID (widget); + GimpToolShearGridPrivate *private = grid->private; + gdouble diffx = coords->x - private->last_x; + gdouble diffy = coords->y - private->last_y; + gdouble amount = 0.0; + GimpMatrix3 transform; + GimpMatrix3 *t; + gdouble x1, y1; + gdouble x2, y2; + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + gdouble current_x; + gdouble current_y; + + g_object_get (widget, + "transform", &t, + "x1", &x1, + "y1", &y1, + "x2", &x2, + "y2", &y2, + NULL); + + gimp_matrix3_transform_point (t, x1, y1, &tx1, &ty1); + gimp_matrix3_transform_point (t, x2, y1, &tx2, &ty2); + gimp_matrix3_transform_point (t, x1, y2, &tx3, &ty3); + gimp_matrix3_transform_point (t, x2, y2, &tx4, &ty4); + + g_free (t); + + current_x = coords->x; + current_y = coords->y; + + diffx = current_x - private->last_x; + diffy = current_y - private->last_y; + + /* If we haven't yet decided on which way to control shearing + * decide using the maximum differential + */ + if (private->orientation == GIMP_ORIENTATION_UNKNOWN) + { +#define MIN_MOVE 5 + + if (ABS (diffx) > MIN_MOVE || ABS (diffy) > MIN_MOVE) + { + if (ABS (diffx) > ABS (diffy)) + { + private->orientation = GIMP_ORIENTATION_HORIZONTAL; + private->shear_x = 0.0; + } + else + { + private->orientation = GIMP_ORIENTATION_VERTICAL; + private->shear_y = 0.0; + } + } + /* set the current coords to the last ones */ + else + { + current_x = private->last_x; + current_y = private->last_y; + } + } + + /* if the direction is known, keep track of the magnitude */ + if (private->orientation == GIMP_ORIENTATION_HORIZONTAL) + { + if (current_y > (ty1 + ty3) / 2) + private->shear_x += diffx; + else + private->shear_x -= diffx; + + amount = private->shear_x; + } + else if (private->orientation == GIMP_ORIENTATION_VERTICAL) + { + if (current_x > (tx1 + tx2) / 2) + private->shear_y += diffy; + else + private->shear_y -= diffy; + + amount = private->shear_y; + } + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_shear (&transform, + x1, y1, x2 - x1, y2 - y1, + private->orientation, amount); + + g_object_set (widget, + "transform", &transform, + "orientation", private->orientation, + "shear-x", private->shear_x, + "shear_y", private->shear_y, + NULL); + + private->last_x = current_x; + private->last_y = current_y; +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_shear_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpOrientationType orientation, + gdouble shear_x, + gdouble shear_y) +{ + GimpMatrix3 transform; + gdouble amount; + + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + if (orientation == GIMP_ORIENTATION_HORIZONTAL) + amount = shear_x; + else + amount = shear_y; + + gimp_matrix3_identity (&transform); + gimp_transform_matrix_shear (&transform, + x1, y1, x2 - x1, y2 - y1, + orientation, amount); + + return g_object_new (GIMP_TYPE_TOOL_SHEAR_GRID, + "shell", shell, + "transform", &transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "orientation", orientation, + "shear-x", shear_x, + "shear-y", shear_y, + NULL); +} diff --git a/app/display/gimptoolsheargrid.h b/app/display/gimptoolsheargrid.h new file mode 100644 index 0000000000..09ed502d62 --- /dev/null +++ b/app/display/gimptoolsheargrid.h @@ -0,0 +1,65 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptoolsheargrid.h + * Copyright (C) 2017 Michael Natterer + * + * 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_TOOL_SHEAR_GRID_H__ +#define __GIMP_TOOL_SHEAR_GRID_H__ + + +#include "gimptooltransformgrid.h" + + +#define GIMP_TYPE_TOOL_SHEAR_GRID (gimp_tool_shear_grid_get_type ()) +#define GIMP_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGrid)) +#define GIMP_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass)) +#define GIMP_IS_TOOL_SHEAR_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_SHEAR_GRID)) +#define GIMP_IS_TOOL_SHEAR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_SHEAR_GRID)) +#define GIMP_TOOL_SHEAR_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_SHEAR_GRID, GimpToolShearGridClass)) + + +typedef struct _GimpToolShearGrid GimpToolShearGrid; +typedef struct _GimpToolShearGridPrivate GimpToolShearGridPrivate; +typedef struct _GimpToolShearGridClass GimpToolShearGridClass; + +struct _GimpToolShearGrid +{ + GimpToolTransformGrid parent_instance; + + GimpToolShearGridPrivate *private; +}; + +struct _GimpToolShearGridClass +{ + GimpToolTransformGridClass parent_class; +}; + + +GType gimp_tool_shear_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_shear_grid_new (GimpDisplayShell *shell, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpOrientationType orientation, + gdouble shear_x, + gdouble shear_y); + + +#endif /* __GIMP_TOOL_SHEAR_GRID_H__ */ diff --git a/app/display/gimptooltransformgrid.c b/app/display/gimptooltransformgrid.c new file mode 100644 index 0000000000..cf7f99cda3 --- /dev/null +++ b/app/display/gimptooltransformgrid.c @@ -0,0 +1,2245 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooltransformgrid.c + * Copyright (C) 2017 Michael Natterer + * + * Based on GimpUnifiedTransformTool + * Copyright (C) 2011 Mikael Magnusson + * + * 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 "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "display-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimp-utils.h" + +#include "gimpcanvashandle.h" +#include "gimpcanvastransformguides.h" +#include "gimpdisplayshell.h" +#include "gimptooltransformgrid.h" + +#include "gimp-intl.h" + + +#define MIN_HANDLE_SIZE 6 +#define GIMP_TOOL_HANDLE_SIZE_LARGE 25 /* FIXME */ + + +enum +{ + PROP_0, + PROP_TRANSFORM, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_PIVOT_X, + PROP_PIVOT_Y, + PROP_GUIDE_TYPE, + PROP_N_GUIDES, + PROP_INSIDE_FUNCTION, + PROP_OUTSIDE_FUNCTION, + PROP_USE_CORNER_HANDLES, + PROP_USE_PERSPECTIVE_HANDLES, + PROP_USE_SIDE_HANDLES, + PROP_USE_SHEAR_HANDLES, + PROP_USE_CENTER_HANDLE, + PROP_USE_PIVOT_HANDLE, + PROP_CONSTRAIN_MOVE, + PROP_CONSTRAIN_SCALE, + PROP_CONSTRAIN_ROTATE, + PROP_CONSTRAIN_SHEAR, + PROP_CONSTRAIN_PERSPECTIVE, + PROP_FROMPIVOT_SCALE, + PROP_FROMPIVOT_SHEAR, + PROP_FROMPIVOT_PERSPECTIVE, + PROP_CORNERSNAP, + PROP_FIXEDPIVOT +}; + + +struct _GimpToolTransformGridPrivate +{ + GimpMatrix3 transform; + gdouble x1, y1; + gdouble x2, y2; + gdouble pivot_x; + gdouble pivot_y; + GimpGuidesType guide_type; + gint n_guides; + GimpTransformFunction inside_function; + GimpTransformFunction outside_function; + gboolean use_corner_handles; + gboolean use_perspective_handles; + gboolean use_side_handles; + gboolean use_shear_handles; + gboolean use_center_handle; + gboolean use_pivot_handle; + gboolean constrain_move; + gboolean constrain_scale; + gboolean constrain_rotate; + gboolean constrain_shear; + gboolean constrain_perspective; + gboolean frompivot_scale; + gboolean frompivot_shear; + gboolean frompivot_perspective; + gboolean cornersnap; + gboolean fixedpivot; + + gdouble curx; /* current x coord */ + gdouble cury; /* current y coord */ + + gdouble mousex; /* x coord where mouse was clicked */ + gdouble mousey; /* y coord where mouse was clicked */ + + gdouble cx, cy; /* center point (for moving) */ + + /* transformed handle coords */ + gdouble tx1, ty1; + gdouble tx2, ty2; + gdouble tx3, ty3; + gdouble tx4, ty4; + gdouble tcx, tcy; + gdouble tpx, tpy; + + /* previous transformed handle coords */ + gdouble prev_tx1, prev_ty1; + gdouble prev_tx2, prev_ty2; + gdouble prev_tx3, prev_ty3; + gdouble prev_tx4, prev_ty4; + gdouble prev_tcx, prev_tcy; + gdouble prev_tpx, prev_tpy; + + GimpTransformHandle handle; /* current tool activity */ + + GimpCanvasItem *guides; + GimpCanvasItem *handles[GIMP_N_TRANSFORM_HANDLES]; + GimpCanvasItem *center_items[2]; + GimpCanvasItem *pivot_items[2]; +}; + + +/* local function prototypes */ + +static void gimp_tool_transform_grid_constructed (GObject *object); +static void gimp_tool_transform_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_tool_transform_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_tool_transform_grid_changed (GimpToolWidget *widget); +static gint gimp_tool_transform_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type); +static void gimp_tool_transform_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type); +static void gimp_tool_transform_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state); +static void gimp_tool_transform_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity); +static gboolean gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *cursor_modifier); + +static void gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid); +static void gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid, + gint *handle_w, + gint *handle_h); + + +G_DEFINE_TYPE (GimpToolTransformGrid, gimp_tool_transform_grid, + GIMP_TYPE_TOOL_WIDGET) + +#define parent_class gimp_tool_transform_grid_parent_class + + +static void +gimp_tool_transform_grid_class_init (GimpToolTransformGridClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); + + object_class->constructed = gimp_tool_transform_grid_constructed; + object_class->set_property = gimp_tool_transform_grid_set_property; + object_class->get_property = gimp_tool_transform_grid_get_property; + + widget_class->changed = gimp_tool_transform_grid_changed; + widget_class->button_press = gimp_tool_transform_grid_button_press; + widget_class->button_release = gimp_tool_transform_grid_button_release; + widget_class->motion = gimp_tool_transform_grid_motion; + widget_class->hover = gimp_tool_transform_grid_hover; + widget_class->get_cursor = gimp_tool_transform_grid_get_cursor; + + g_object_class_install_property (object_class, PROP_TRANSFORM, + gimp_param_spec_matrix3 ("transform", + NULL, NULL, + NULL, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X1, + g_param_spec_double ("x1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y1, + g_param_spec_double ("y1", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_X2, + g_param_spec_double ("x2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_Y2, + g_param_spec_double ("y2", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_X, + g_param_spec_double ("pivot-x", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_PIVOT_Y, + g_param_spec_double ("pivot-y", + NULL, NULL, + -GIMP_MAX_IMAGE_SIZE, + GIMP_MAX_IMAGE_SIZE, + 0.0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_GUIDE_TYPE, + g_param_spec_enum ("guide-type", NULL, NULL, + GIMP_TYPE_GUIDES_TYPE, + GIMP_GUIDES_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_N_GUIDES, + g_param_spec_int ("n-guides", NULL, NULL, + 1, 128, 4, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_INSIDE_FUNCTION, + g_param_spec_enum ("inside-function", + NULL, NULL, + GIMP_TYPE_TRANSFORM_FUNCTION, + GIMP_TRANSFORM_FUNCTION_MOVE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_OUTSIDE_FUNCTION, + g_param_spec_enum ("outside-function", + NULL, NULL, + GIMP_TYPE_TRANSFORM_FUNCTION, + GIMP_TRANSFORM_FUNCTION_ROTATE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_CORNER_HANDLES, + g_param_spec_boolean ("use-corner-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_PERSPECTIVE_HANDLES, + g_param_spec_boolean ("use-perspective-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_SIDE_HANDLES, + g_param_spec_boolean ("use-side-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_SHEAR_HANDLES, + g_param_spec_boolean ("use-shear-handles", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_CENTER_HANDLE, + g_param_spec_boolean ("use-center-handle", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_USE_PIVOT_HANDLE, + g_param_spec_boolean ("use-pivot-handle", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_MOVE, + g_param_spec_boolean ("constrain-move", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_SCALE, + g_param_spec_boolean ("constrain-scale", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_ROTATE, + g_param_spec_boolean ("constrain-rotate", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_SHEAR, + g_param_spec_boolean ("constrain-shear", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CONSTRAIN_PERSPECTIVE, + g_param_spec_boolean ("constrain-perspective", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_SCALE, + g_param_spec_boolean ("frompivot-scale", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_SHEAR, + g_param_spec_boolean ("frompivot-shear", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FROMPIVOT_PERSPECTIVE, + g_param_spec_boolean ("frompivot-perspective", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_CORNERSNAP, + g_param_spec_boolean ("cornersnap", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_FIXEDPIVOT, + g_param_spec_boolean ("fixedpivot", + NULL, NULL, + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_type_class_add_private (klass, sizeof (GimpToolTransformGridPrivate)); +} + +static void +gimp_tool_transform_grid_init (GimpToolTransformGrid *grid) +{ + grid->private = G_TYPE_INSTANCE_GET_PRIVATE (grid, + GIMP_TYPE_TOOL_TRANSFORM_GRID, + GimpToolTransformGridPrivate); +} + +static void +gimp_tool_transform_grid_constructed (GObject *object) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolWidget *widget = GIMP_TOOL_WIDGET (object); + GimpToolTransformGridPrivate *private = grid->private; + GimpCanvasGroup *stroke_group; + gint i; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + private->guides = gimp_tool_widget_add_transform_guides (widget, + &private->transform, + private->x1, + private->y1, + private->x2, + private->y2, + private->guide_type, + private->n_guides); + + for (i = 0; i < 4; i++) + { + /* draw the scale handles */ + private->handles[GIMP_TRANSFORM_HANDLE_NW + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the perspective handles */ + private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_DIAMOND, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the side handles */ + private->handles[GIMP_TRANSFORM_HANDLE_N + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + /* draw the shear handles */ + private->handles[GIMP_TRANSFORM_HANDLE_N_S + i] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_FILLED_DIAMOND, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + } + + /* draw the rotation center axis handle */ + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + private->handles[GIMP_TRANSFORM_HANDLE_PIVOT] = + GIMP_CANVAS_ITEM (stroke_group); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->pivot_items[0] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CIRCLE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + private->pivot_items[1] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CROSS, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_widget_pop_group (widget); + + /* draw the center handle */ + stroke_group = gimp_tool_widget_add_stroke_group (widget); + + private->handles[GIMP_TRANSFORM_HANDLE_CENTER] = + GIMP_CANVAS_ITEM (stroke_group); + + gimp_tool_widget_push_group (widget, stroke_group); + + private->center_items[0] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_SQUARE, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + private->center_items[1] = + gimp_tool_widget_add_handle (widget, + GIMP_HANDLE_CROSS, + 0, 0, 10, 10, + GIMP_HANDLE_ANCHOR_CENTER); + + gimp_tool_widget_pop_group (widget); + + gimp_tool_transform_grid_changed (widget); +} + +static void +gimp_tool_transform_grid_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolTransformGridPrivate *private = grid->private; + gboolean box = FALSE; + + switch (property_id) + { + case PROP_TRANSFORM: + { + GimpMatrix3 *transform = g_value_get_boxed (value); + + if (transform) + private->transform = *transform; + else + gimp_matrix3_identity (&private->transform); + } + break; + + case PROP_X1: + private->x1 = g_value_get_double (value); + box = TRUE; + break; + case PROP_Y1: + private->y1 = g_value_get_double (value); + box = TRUE; + break; + case PROP_X2: + private->x2 = g_value_get_double (value); + box = TRUE; + break; + case PROP_Y2: + private->y2 = g_value_get_double (value); + box = TRUE; + break; + + case PROP_PIVOT_X: + private->pivot_x = g_value_get_double (value); + break; + case PROP_PIVOT_Y: + private->pivot_y = g_value_get_double (value); + break; + + case PROP_GUIDE_TYPE: + private->guide_type = g_value_get_enum (value); + break; + case PROP_N_GUIDES: + private->n_guides = g_value_get_int (value); + break; + + case PROP_INSIDE_FUNCTION: + private->inside_function = g_value_get_enum (value); + break; + case PROP_OUTSIDE_FUNCTION: + private->outside_function = g_value_get_enum (value); + break; + + case PROP_USE_CORNER_HANDLES: + private->use_corner_handles = g_value_get_boolean (value); + break; + case PROP_USE_PERSPECTIVE_HANDLES: + private->use_perspective_handles = g_value_get_boolean (value); + break; + case PROP_USE_SIDE_HANDLES: + private->use_side_handles = g_value_get_boolean (value); + break; + case PROP_USE_SHEAR_HANDLES: + private->use_shear_handles = g_value_get_boolean (value); + break; + case PROP_USE_CENTER_HANDLE: + private->use_center_handle = g_value_get_boolean (value); + break; + case PROP_USE_PIVOT_HANDLE: + private->use_pivot_handle = g_value_get_boolean (value); + break; + + case PROP_CONSTRAIN_MOVE: + private->constrain_move = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_SCALE: + private->constrain_scale = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_ROTATE: + private->constrain_rotate = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_SHEAR: + private->constrain_shear = g_value_get_boolean (value); + break; + case PROP_CONSTRAIN_PERSPECTIVE: + private->constrain_perspective = g_value_get_boolean (value); + break; + + case PROP_FROMPIVOT_SCALE: + private->frompivot_scale = g_value_get_boolean (value); + break; + case PROP_FROMPIVOT_SHEAR: + private->frompivot_shear = g_value_get_boolean (value); + break; + case PROP_FROMPIVOT_PERSPECTIVE: + private->frompivot_perspective = g_value_get_boolean (value); + break; + + case PROP_CORNERSNAP: + private->cornersnap = g_value_get_boolean (value); + break; + case PROP_FIXEDPIVOT: + private->fixedpivot = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } + + if (box) + { + private->cx = (private->x1 + private->x2) / 2.0; + private->cy = (private->y1 + private->y2) / 2.0; + } +} + +static void +gimp_tool_transform_grid_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (object); + GimpToolTransformGridPrivate *private = grid->private; + + switch (property_id) + { + case PROP_TRANSFORM: + g_value_set_boxed (value, &private->transform); + break; + + case PROP_X1: + g_value_set_double (value, private->x1); + break; + case PROP_Y1: + g_value_set_double (value, private->y1); + break; + case PROP_X2: + g_value_set_double (value, private->x2); + break; + case PROP_Y2: + g_value_set_double (value, private->y2); + break; + + case PROP_PIVOT_X: + g_value_set_double (value, private->pivot_x); + break; + case PROP_PIVOT_Y: + g_value_set_double (value, private->pivot_y); + break; + + case PROP_GUIDE_TYPE: + g_value_set_enum (value, private->guide_type); + break; + case PROP_N_GUIDES: + g_value_set_int (value, private->n_guides); + break; + + case PROP_INSIDE_FUNCTION: + g_value_set_enum (value, private->inside_function); + break; + case PROP_OUTSIDE_FUNCTION: + g_value_set_enum (value, private->outside_function); + break; + + case PROP_USE_CORNER_HANDLES: + g_value_set_boolean (value, private->use_corner_handles); + break; + case PROP_USE_PERSPECTIVE_HANDLES: + g_value_set_boolean (value, private->use_perspective_handles); + break; + case PROP_USE_SIDE_HANDLES: + g_value_set_boolean (value, private->use_side_handles); + break; + case PROP_USE_SHEAR_HANDLES: + g_value_set_boolean (value, private->use_shear_handles); + break; + case PROP_USE_CENTER_HANDLE: + g_value_set_boolean (value, private->use_center_handle); + break; + case PROP_USE_PIVOT_HANDLE: + g_value_set_boolean (value, private->use_pivot_handle); + break; + + case PROP_CONSTRAIN_MOVE: + g_value_set_boolean (value, private->constrain_move); + break; + case PROP_CONSTRAIN_SCALE: + g_value_set_boolean (value, private->constrain_scale); + break; + case PROP_CONSTRAIN_ROTATE: + g_value_set_boolean (value, private->constrain_rotate); + break; + case PROP_CONSTRAIN_SHEAR: + g_value_set_boolean (value, private->constrain_shear); + break; + case PROP_CONSTRAIN_PERSPECTIVE: + g_value_set_boolean (value, private->constrain_perspective); + break; + + case PROP_FROMPIVOT_SCALE: + g_value_set_boolean (value, private->frompivot_scale); + break; + case PROP_FROMPIVOT_SHEAR: + g_value_set_boolean (value, private->frompivot_shear); + break; + case PROP_FROMPIVOT_PERSPECTIVE: + g_value_set_boolean (value, private->frompivot_perspective); + break; + + case PROP_CORNERSNAP: + g_value_set_boolean (value, private->cornersnap); + break; + case PROP_FIXEDPIVOT: + g_value_set_boolean (value, private->fixedpivot); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +transform_is_convex (GimpVector2 *pos) +{ + return gimp_transform_polygon_is_convex (pos[0].x, pos[0].y, + pos[1].x, pos[1].y, + pos[2].x, pos[2].y, + pos[3].x, pos[3].y); +} + +static inline gboolean +vectorisnull (GimpVector2 v) +{ + return ((v.x == 0.0) && (v.y == 0.0)); +} + +static inline gdouble +dotprod (GimpVector2 a, + GimpVector2 b) +{ + return a.x * b.x + a.y * b.y; +} + +static inline gdouble +norm (GimpVector2 a) +{ + return sqrt (dotprod (a, a)); +} + +static inline GimpVector2 +vectorsubtract (GimpVector2 a, + GimpVector2 b) +{ + GimpVector2 c; + + c.x = a.x - b.x; + c.y = a.y - b.y; + + return c; +} + +static inline GimpVector2 +vectoradd (GimpVector2 a, + GimpVector2 b) +{ + GimpVector2 c; + + c.x = a.x + b.x; + c.y = a.y + b.y; + + return c; +} + +static inline GimpVector2 +scalemult (GimpVector2 a, + gdouble b) +{ + GimpVector2 c; + + c.x = a.x * b; + c.y = a.y * b; + + return c; +} + +static inline GimpVector2 +vectorproject (GimpVector2 a, + GimpVector2 b) +{ + return scalemult (b, dotprod (a, b) / dotprod (b, b)); +} + +/* finds the clockwise angle between the vectors given, 0-2π */ +static inline gdouble +calcangle (GimpVector2 a, + GimpVector2 b) +{ + gdouble angle, angle2; + gdouble length; + + if (vectorisnull (a) || vectorisnull (b)) + return 0.0; + + length = norm (a) * norm (b); + + angle = acos (dotprod (a, b)/length); + angle2 = b.y; + b.y = -b.x; + b.x = angle2; + angle2 = acos (dotprod (a, b)/length); + + return ((angle2 > G_PI / 2.0) ? angle : 2.0 * G_PI - angle); +} + +static inline GimpVector2 +rotate2d (GimpVector2 p, + gdouble angle) +{ + GimpVector2 ret; + + ret.x = cos (angle) * p.x-sin (angle) * p.y; + ret.y = sin (angle) * p.x+cos (angle) * p.y; + + return ret; +} + +static inline GimpVector2 +lineintersect (GimpVector2 p1, GimpVector2 p2, + GimpVector2 q1, GimpVector2 q2) +{ + gdouble denom, u; + GimpVector2 p; + + denom = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y); + if (denom == 0.0) + { + p.x = (p1.x + p2.x + q1.x + q2.x) / 4; + p.y = (p1.y + p2.y + q1.y + q2.y) / 4; + } + else + { + u = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x); + u /= denom; + + p.x = p1.x + u * (p2.x - p1.x); + p.y = p1.y + u * (p2.y - p1.y); + } + + return p; +} + +static inline GimpVector2 +get_pivot_delta (GimpToolTransformGrid *grid, + GimpVector2 *oldpos, + GimpVector2 *newpos, + GimpVector2 pivot) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpMatrix3 transform_before; + GimpMatrix3 transform_after; + GimpVector2 delta; + + gimp_matrix3_identity (&transform_before); + gimp_matrix3_identity (&transform_after); + + gimp_transform_matrix_perspective (&transform_before, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + oldpos[0].x, oldpos[0].y, + oldpos[1].x, oldpos[1].y, + oldpos[2].x, oldpos[2].y, + oldpos[3].x, oldpos[3].y); + gimp_transform_matrix_perspective (&transform_after, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + newpos[0].x, newpos[0].y, + newpos[1].x, newpos[1].y, + newpos[2].x, newpos[2].y, + newpos[3].x, newpos[3].y); + gimp_matrix3_invert (&transform_before); + gimp_matrix3_mult (&transform_after, &transform_before); + gimp_matrix3_transform_point (&transform_before, + pivot.x, pivot.y, &delta.x, &delta.y); + + delta = vectorsubtract (delta, pivot); + + return delta; +} + +static gboolean +point_is_inside_polygon (gint n, + gdouble *x, + gdouble *y, + gdouble px, + gdouble py) +{ + gint i, j; + gboolean odd = FALSE; + + for (i = 0, j = n - 1; i < n; j = i++) + { + if ((y[i] < py && y[j] >= py) || + (y[j] < py && y[i] >= py)) + { + if (x[i] + (py - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < px) + odd = !odd; + } + } + + return odd; +} + +static gboolean +point_is_inside_polygon_pos (GimpVector2 *pos, + GimpVector2 point) +{ + return point_is_inside_polygon (4, + (gdouble[4]){ pos[0].x, pos[1].x, + pos[3].x, pos[2].x }, + (gdouble[4]){ pos[0].y, pos[1].y, + pos[3].y, pos[2].y }, + point.x, point.y); +} + +static void +get_handle_geometry (GimpToolTransformGrid *grid, + GimpVector2 *position, + gdouble *angle) +{ + GimpToolTransformGridPrivate *private = grid->private; + + GimpVector2 o[] = { { .x = private->tx1, .y = private->ty1 }, + { .x = private->tx2, .y = private->ty2 }, + { .x = private->tx3, .y = private->ty3 }, + { .x = private->tx4, .y = private->ty4 } }; + GimpVector2 right = { .x = 1.0, .y = 0.0 }; + GimpVector2 up = { .x = 0.0, .y = 1.0 }; + + if (position) + { + position[0] = o[0]; + position[1] = o[1]; + position[2] = o[2]; + position[3] = o[3]; + } + + angle[0] = calcangle (vectorsubtract (o[1], o[0]), right); + angle[1] = calcangle (vectorsubtract (o[3], o[2]), right); + angle[2] = calcangle (vectorsubtract (o[3], o[1]), up); + angle[3] = calcangle (vectorsubtract (o[2], o[0]), up); + + angle[4] = (angle[0] + angle[3]) / 2.0; + angle[5] = (angle[0] + angle[2]) / 2.0; + angle[6] = (angle[1] + angle[3]) / 2.0; + angle[7] = (angle[1] + angle[2]) / 2.0; + + angle[8] = (angle[0] + angle[1] + angle[2] + angle[3]) / 4.0; +} + +static void +gimp_tool_transform_grid_changed (GimpToolWidget *widget) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble angle[9]; + GimpVector2 o[4], t[4]; + gint handle_w; + gint handle_h; + gint d, i; + + gimp_tool_transform_grid_update_box (grid); + + gimp_canvas_transform_guides_set (private->guides, + &private->transform, + private->x1, + private->y1, + private->x2, + private->y2, + private->guide_type, + private->n_guides); + + get_handle_geometry (grid, o, angle); + gimp_tool_transform_grid_calc_handles (grid, &handle_w, &handle_h); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + gdouble factor; + + /* the scale handles */ + factor = 1.0; + if (private->use_perspective_handles) + factor = 1.5; + + h = private->handles[GIMP_TRANSFORM_HANDLE_NW + i]; + gimp_canvas_handle_set_position (h, o[i].x, o[i].y); + gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor); + gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0); + gimp_canvas_item_set_visible (h, private->use_corner_handles); + + /* the perspective handles */ + factor = 1.0; + if (private->use_corner_handles) + factor = 0.8; + + h = private->handles[GIMP_TRANSFORM_HANDLE_NW_P + i]; + gimp_canvas_handle_set_position (h, o[i].x, o[i].y); + gimp_canvas_handle_set_size (h, handle_w * factor, handle_h * factor); + gimp_canvas_handle_set_angles (h, angle[i + 4], 0.0); + gimp_canvas_item_set_visible (h, private->use_perspective_handles); + } + + /* draw the side handles */ + t[0] = scalemult (vectoradd (o[0], o[1]), 0.5); + t[1] = scalemult (vectoradd (o[2], o[3]), 0.5); + t[2] = scalemult (vectoradd (o[1], o[3]), 0.5); + t[3] = scalemult (vectoradd (o[2], o[0]), 0.5); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + + h = private->handles[GIMP_TRANSFORM_HANDLE_N + i]; + gimp_canvas_handle_set_position (h, t[i].x, t[i].y); + gimp_canvas_handle_set_size (h, handle_w, handle_h); + gimp_canvas_handle_set_angles (h, angle[i], 0.0); + gimp_canvas_item_set_visible (h, private->use_side_handles); + } + + /* draw the shear handles */ + t[0] = scalemult (vectoradd ( o[0] , scalemult (o[1], 3.0)), + 0.25); + t[1] = scalemult (vectoradd (scalemult (o[2], 3.0), o[3] ), + 0.25); + t[2] = scalemult (vectoradd ( o[1] , scalemult (o[3], 3.0)), + 0.25); + t[3] = scalemult (vectoradd (scalemult (o[2], 3.0), o[0] ), + 0.25); + + for (i = 0; i < 4; i++) + { + GimpCanvasItem *h; + + h = private->handles[GIMP_TRANSFORM_HANDLE_N_S + i]; + gimp_canvas_handle_set_position (h, t[i].x, t[i].y); + gimp_canvas_handle_set_size (h, handle_w, handle_h); + gimp_canvas_handle_set_angles (h, angle[i], 0.0); + gimp_canvas_item_set_visible (h, private->use_shear_handles); + } + + d = MIN (handle_w, handle_h); + if (private->use_center_handle) + d *= 2; /* so you can grab it from under the center handle */ + + gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_PIVOT], + private->use_pivot_handle); + + gimp_canvas_handle_set_position (private->pivot_items[0], + private->tpx, private->tpy); + gimp_canvas_handle_set_size (private->pivot_items[0], d, d); + + gimp_canvas_handle_set_position (private->pivot_items[1], + private->tpx, private->tpy); + gimp_canvas_handle_set_size (private->pivot_items[1], d, d); + + d = MIN (handle_w, handle_h); + + gimp_canvas_item_set_visible (private->handles[GIMP_TRANSFORM_HANDLE_CENTER], + private->use_center_handle); + + gimp_canvas_handle_set_position (private->center_items[0], + private->tcx, private->tcy); + gimp_canvas_handle_set_size (private->center_items[0], d, d); + gimp_canvas_handle_set_angles (private->center_items[0], angle[8], 0.0); + + gimp_canvas_handle_set_position (private->center_items[1], + private->tcx, private->tcy); + gimp_canvas_handle_set_size (private->center_items[1], d, d); + gimp_canvas_handle_set_angles (private->center_items[1], angle[8], 0.0); + + gimp_tool_transform_grid_update_hilight (grid); +} + +gint +gimp_tool_transform_grid_button_press (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonPressType press_type) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + + private->mousex = coords->x; + private->mousey = coords->y; + + if (private->handle != GIMP_TRANSFORM_HANDLE_NONE) + { + if (private->handles[private->handle]) + { + GimpCanvasItem *handle; + gdouble x, y; + + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_CENTER: + handle = private->center_items[0]; + break; + + case GIMP_TRANSFORM_HANDLE_PIVOT: + handle = private->pivot_items[0]; + break; + + default: + handle = private->handles[private->handle]; + break; + } + + gimp_canvas_handle_get_position (handle, &x, &y); + + gimp_tool_widget_snap_offsets (widget, + SIGNED_ROUND (x - coords->x), + SIGNED_ROUND (y - coords->y), + 0, 0); + } + else + { + gimp_tool_widget_snap_offsets (widget, 0, 0, 0, 0); + } + + private->prev_tx1 = private->tx1; + private->prev_ty1 = private->ty1; + private->prev_tx2 = private->tx2; + private->prev_ty2 = private->ty2; + private->prev_tx3 = private->tx3; + private->prev_ty3 = private->ty3; + private->prev_tx4 = private->tx4; + private->prev_ty4 = private->ty4; + private->prev_tpx = private->tpx; + private->prev_tpy = private->tpy; + private->prev_tcx = private->tcx; + private->prev_tcy = private->tcy; + + return private->handle; + } + + gimp_tool_widget_snap_offsets (widget, 0, 0, 0, 0); + + return 0; +} + +void +gimp_tool_transform_grid_button_release (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state, + GimpButtonReleaseType release_type) +{ +} + +void +gimp_tool_transform_grid_motion (GimpToolWidget *widget, + const GimpCoords *coords, + guint32 time, + GdkModifierType state) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble *x[4], *y[4]; + gdouble *newpivot_x, *newpivot_y; + + GimpVector2 oldpos[5], newpos[4]; + GimpVector2 cur = { .x = coords->x, + .y = coords->y }; + GimpVector2 mouse = { .x = private->mousex, + .y = private->mousey }; + GimpVector2 d; + GimpVector2 pivot; + + gboolean fixedpivot = private->fixedpivot; + GimpTransformHandle handle = private->handle; + gint i; + + private->curx = coords->x; + private->cury = coords->y; + + x[0] = &private->tx1; + y[0] = &private->ty1; + x[1] = &private->tx2; + y[1] = &private->ty2; + x[2] = &private->tx3; + y[2] = &private->ty3; + x[3] = &private->tx4; + y[3] = &private->ty4; + + newpos[0].x = oldpos[0].x = private->prev_tx1; + newpos[0].y = oldpos[0].y = private->prev_ty1; + newpos[1].x = oldpos[1].x = private->prev_tx2; + newpos[1].y = oldpos[1].y = private->prev_ty2; + newpos[2].x = oldpos[2].x = private->prev_tx3; + newpos[2].y = oldpos[2].y = private->prev_ty3; + newpos[3].x = oldpos[3].x = private->prev_tx4; + newpos[3].y = oldpos[3].y = private->prev_ty4; + + /* put center point in this array too */ + oldpos[4].x = (oldpos[0].x + oldpos[1].x + oldpos[2].x + oldpos[3].x) / 4.; + oldpos[4].y = (oldpos[0].y + oldpos[1].y + oldpos[2].y + oldpos[3].y) / 4.; + + d = vectorsubtract (cur, mouse); + + newpivot_x = &private->tpx; + newpivot_y = &private->tpy; + + pivot.x = private->prev_tpx; + pivot.y = private->prev_tpy; + + /* move */ + if (handle == GIMP_TRANSFORM_HANDLE_CENTER) + { + if (private->constrain_move) + { + /* snap to 45 degree vectors from starting point */ + gdouble angle = 16.0 * calcangle ((GimpVector2) { 1.0, 0.0 }, + d) / (2.0 * G_PI); + gdouble dist = norm (d) / sqrt (2); + + if (angle < 1.0 || angle >= 15.0) + d.y = 0; + else if (angle < 3.0) + d.y = -(d.x = dist); + else if (angle < 5.0) + d.x = 0; + else if (angle < 7.0) + d.x = d.y = -dist; + else if (angle < 9.0) + d.y = 0; + else if (angle < 11.0) + d.x = -(d.y = dist); + else if (angle < 13.0) + d.x = 0; + else if (angle < 15.0) + d.x = d.y = dist; + } + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (oldpos[i], d); + } + + /* rotate */ + if (handle == GIMP_TRANSFORM_HANDLE_ROTATION) + { + gdouble angle = calcangle (vectorsubtract (cur, pivot), + vectorsubtract (mouse, pivot)); + + if (private->constrain_rotate) + { + /* round to 15 degree multiple */ + angle /= 2 * G_PI / 24.0; + angle = round (angle); + angle *= 2 * G_PI / 24.0; + } + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (pivot, + rotate2d (vectorsubtract (oldpos[i], pivot), + angle)); + + fixedpivot = TRUE; + } + + /* move rotation axis */ + if (handle == GIMP_TRANSFORM_HANDLE_PIVOT) + { + pivot = vectoradd (pivot, d); + + if (private->cornersnap) + { + /* snap to corner points and center */ + gint closest = 0; + gdouble closest_dist = G_MAXDOUBLE, dist; + + for (i = 0; i < 5; i++) + { + dist = norm (vectorsubtract (pivot, oldpos[i])); + if (dist < closest_dist) + { + closest_dist = dist; + closest = i; + } + } + + if (closest_dist * + gimp_tool_widget_get_shell (widget)->scale_x < 50) + { + pivot = oldpos[closest]; + } + } + + fixedpivot = TRUE; + } + + /* scaling via corner */ + if (handle == GIMP_TRANSFORM_HANDLE_NW || + handle == GIMP_TRANSFORM_HANDLE_NE || + handle == GIMP_TRANSFORM_HANDLE_SE || + handle == GIMP_TRANSFORM_HANDLE_SW) + { + /* Scaling through scale handles means translating one corner point, + * with all sides at constant angles. + */ + + gint this, left, right, opposite; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_NW) + { + this = 0; left = 1; right = 2; opposite = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_NE) + { + this = 1; left = 3; right = 0; opposite = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SW) + { + this = 2; left = 0; right = 3; opposite = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SE) + { + this = 3; left = 2; right = 1; opposite = 0; + } + else + g_assert_not_reached (); + + /* when the keep aspect transformation constraint is enabled, + * the translation shall only be along the diagonal that runs + * trough this corner point. + */ + if (private->constrain_scale) + { + /* restrict to movement along the diagonal */ + GimpVector2 diag = vectorsubtract (oldpos[this], oldpos[opposite]); + + d = vectorproject (d, diag); + } + + /* Move the corner being interacted with */ + /* rp---------tp + * / /\ <- d, the interaction vector + * / / tp + * op----------/ + * + */ + newpos[this] = vectoradd (oldpos[this], d); + + /* Where the corner to the right and left would go, need these to form + * lines to intersect with the sides */ + /* rp----------/ + * /\ /\ + * / nr / nt + * op----------lp + * \ + * nl + */ + + newpos[right] = vectoradd (oldpos[right], d); + newpos[left] = vectoradd (oldpos[left], d); + + /* Now we just need to find the intersection of op-rp and nr-nt. + * rp----------/ + * / / + * / nr==========nt + * op----------/ + * + */ + newpos[right] = lineintersect (newpos[right], newpos[this], + oldpos[opposite], oldpos[right]); + newpos[left] = lineintersect (newpos[left], newpos[this], + oldpos[opposite], oldpos[left]); + /* /-----------/ + * / / + * rp============nt + * op----------/ + * + */ + + /* + * + * /--------------/ + * /--------------/ + * + */ + + if (private->frompivot_scale && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + /* transform the pivot point before the interaction and + * after, and move everything by this difference + */ + //TODO the handle doesn't actually end up where the mouse cursor is + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* scaling via sides */ + if (handle == GIMP_TRANSFORM_HANDLE_N || + handle == GIMP_TRANSFORM_HANDLE_E || + handle == GIMP_TRANSFORM_HANDLE_S || + handle == GIMP_TRANSFORM_HANDLE_W) + { + gint this_l, this_r, opp_l, opp_r; + GimpVector2 side_l, side_r, midline; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_N) + { + this_l = 1; this_r = 0; + } + else if (handle == GIMP_TRANSFORM_HANDLE_E) + { + this_l = 3; this_r = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_S) + { + this_l = 2; this_r = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_W) + { + this_l = 0; this_r = 2; + } + else + g_assert_not_reached (); + + opp_l = 3 - this_r; opp_r = 3 - this_l; + + side_l = vectorsubtract (oldpos[opp_l], oldpos[this_l]); + side_r = vectorsubtract (oldpos[opp_r], oldpos[this_r]); + midline = vectoradd (side_l, side_r); + + /* restrict to movement along the midline */ + d = vectorproject (d, midline); + + if (private->constrain_scale) + { + GimpVector2 before, after, effective_pivot = pivot; + gdouble distance; + + if (! private->frompivot_scale) + { + /* center of the opposite side is pivot */ + effective_pivot = scalemult (vectoradd (oldpos[opp_l], + oldpos[opp_r]), 0.5); + } + + /* get the difference between the distance from the pivot to + * where interaction started and the distance from the pivot + * to where cursor is now, and scale all corners distance + * from the pivot with this factor + */ + before = vectorsubtract (effective_pivot, mouse); + after = vectorsubtract (effective_pivot, cur); + after = vectorproject (after, before); + + distance = 0.5 * (after.x / before.x + after.y / before.y); + + for (i = 0; i < 4; i++) + newpos[i] = vectoradd (effective_pivot, + scalemult (vectorsubtract (oldpos[i], + effective_pivot), + distance)); + } + else + { + /* just move the side */ + newpos[this_l] = vectoradd (oldpos[this_l], d); + newpos[this_r] = vectoradd (oldpos[this_r], d); + } + + if (! private->constrain_scale && + private->frompivot_scale && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* shear */ + if (handle == GIMP_TRANSFORM_HANDLE_N_S || + handle == GIMP_TRANSFORM_HANDLE_E_S || + handle == GIMP_TRANSFORM_HANDLE_S_S || + handle == GIMP_TRANSFORM_HANDLE_W_S) + { + gint this_l, this_r; + + /* set up indices for this edge and the opposite edge */ + if (handle == GIMP_TRANSFORM_HANDLE_N_S) + { + this_l = 1; this_r = 0; + } + else if (handle == GIMP_TRANSFORM_HANDLE_W_S) + { + this_l = 0; this_r = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_S_S) + { + this_l = 2; this_r = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_E_S) + { + this_l = 3; this_r = 1; + } + else + g_assert_not_reached (); + + if (private->constrain_shear) + { + /* restrict to movement along the side */ + GimpVector2 side = vectorsubtract (oldpos[this_r], oldpos[this_l]); + + d = vectorproject (d, side); + } + + newpos[this_l] = vectoradd (oldpos[this_l], d); + newpos[this_r] = vectoradd (oldpos[this_r], d); + + if (private->frompivot_shear && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + /* perspective transform */ + if (handle == GIMP_TRANSFORM_HANDLE_NW_P || + handle == GIMP_TRANSFORM_HANDLE_NE_P || + handle == GIMP_TRANSFORM_HANDLE_SE_P || + handle == GIMP_TRANSFORM_HANDLE_SW_P) + { + gint this, left, right, opposite; + + /* 0: northwest, 1: northeast, 2: southwest, 3: southeast */ + if (handle == GIMP_TRANSFORM_HANDLE_NW_P) + { + this = 0; left = 1; right = 2; opposite = 3; + } + else if (handle == GIMP_TRANSFORM_HANDLE_NE_P) + { + this = 1; left = 3; right = 0; opposite = 2; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SW_P) + { + this = 2; left = 0; right = 3; opposite = 1; + } + else if (handle == GIMP_TRANSFORM_HANDLE_SE_P) + { + this = 3; left = 2; right = 1; opposite = 0; + } + else + g_assert_not_reached (); + + if (private->constrain_perspective) + { + /* when the constrain transformation constraint is enabled, + * the translation shall only be either along the side + * angles of the two sides that run to this corner point, or + * along the diagonal that runs trough this corner point. + */ + GimpVector2 proj[4]; + gdouble rej[4]; + + for (i = 0; i < 4; i++) + { + if (i == this) + continue; + + /* get the vectors along the sides and the diagonal */ + proj[i] = vectorsubtract (oldpos[this], oldpos[i]); + + /* project d on each candidate vector and see which has + * the shortest rejection + */ + proj[i] = vectorproject (d, proj[i]); + rej[i] = norm (vectorsubtract (d, proj[i])); + } + + if (rej[left] < rej[right] && rej[left] < rej[opposite]) + d = proj[left]; + else if (rej[right] < rej[opposite]) + d = proj[right]; + else + d = proj[opposite]; + } + + newpos[this] = vectoradd (oldpos[this], d); + + if (private->frompivot_perspective && + transform_is_convex (newpos) && + transform_is_convex (oldpos)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + + for (i = 0; i < 4; i++) + newpos[i] = vectorsubtract (newpos[i], delta); + + fixedpivot = TRUE; + } + } + + for (i = 0; i < 4; i++) + { + *x[i] = newpos[i].x; + *y[i] = newpos[i].y; + } + + /* this will have been set to TRUE if an operation used the pivot in + * addition to being a user option + */ + if (! fixedpivot && + transform_is_convex (newpos) && + transform_is_convex (oldpos) && + point_is_inside_polygon_pos (oldpos, pivot)) + { + GimpVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot); + pivot = vectoradd (pivot, delta); + } + + /* set unconditionally: if options get toggled during operation, we + * have to move pivot back + */ + *newpivot_x = pivot.x; + *newpivot_y = pivot.y; + + gimp_tool_transform_grid_update_matrix (grid); +} + +static const gchar * +get_friendly_operation_name (GimpTransformHandle handle) +{ + switch (handle) + { + case GIMP_TRANSFORM_HANDLE_NONE: + return ""; + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SE_P: + return _("Click-Drag to change perspective"); + case GIMP_TRANSFORM_HANDLE_NW: + case GIMP_TRANSFORM_HANDLE_NE: + case GIMP_TRANSFORM_HANDLE_SW: + case GIMP_TRANSFORM_HANDLE_SE: + return _("Click-Drag to scale"); + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_W: + return _("Click-Drag to scale"); + case GIMP_TRANSFORM_HANDLE_CENTER: + return _("Click-Drag to move"); + case GIMP_TRANSFORM_HANDLE_PIVOT: + return _("Click-Drag to move the pivot point"); + case GIMP_TRANSFORM_HANDLE_N_S: + case GIMP_TRANSFORM_HANDLE_S_S: + case GIMP_TRANSFORM_HANDLE_E_S: + case GIMP_TRANSFORM_HANDLE_W_S: + return _("Click-Drag to shear"); + case GIMP_TRANSFORM_HANDLE_ROTATION: + return _("Click-Drag to rotate"); + default: + g_assert_not_reached (); + } +} + +static GimpTransformHandle +gimp_tool_transform_get_area_handle (GimpToolTransformGrid *grid, + const GimpCoords *coords, + GimpTransformFunction function) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle = GIMP_TRANSFORM_HANDLE_NONE; + + switch (function) + { + case GIMP_TRANSFORM_FUNCTION_MOVE: + handle = GIMP_TRANSFORM_HANDLE_CENTER; + break; + + case GIMP_TRANSFORM_FUNCTION_ROTATE: + handle = GIMP_TRANSFORM_HANDLE_ROTATION; + break; + + case GIMP_TRANSFORM_FUNCTION_SCALE: + case GIMP_TRANSFORM_FUNCTION_PERSPECTIVE: + { + gdouble closest_dist; + gdouble dist; + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx1, + private->ty1); + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_NW_P; + else + handle = GIMP_TRANSFORM_HANDLE_NW; + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx2, + private->ty2); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_NE_P; + else + handle = GIMP_TRANSFORM_HANDLE_NE; + } + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx3, + private->ty3); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_SW_P; + else + handle = GIMP_TRANSFORM_HANDLE_SW; + } + + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + private->tx4, + private->ty4); + if (dist < closest_dist) + { + closest_dist = dist; + if (function == GIMP_TRANSFORM_FUNCTION_PERSPECTIVE) + handle = GIMP_TRANSFORM_HANDLE_SE_P; + else + handle = GIMP_TRANSFORM_HANDLE_SE; + } + } + break; + + case GIMP_TRANSFORM_FUNCTION_SHEAR: + { + gdouble handle_x; + gdouble handle_y; + gdouble closest_dist; + gdouble dist; + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_N], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_N_S; + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_W], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_W_S; + } + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_E], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_E_S; + } + + gimp_canvas_handle_get_position (private->handles[GIMP_TRANSFORM_HANDLE_S], + &handle_x, &handle_y); + dist = gimp_canvas_item_transform_distance_square (private->guides, + coords->x, coords->y, + handle_x, handle_y); + if (dist < closest_dist) + { + closest_dist = dist; + handle = GIMP_TRANSFORM_HANDLE_S_S; + } + } + break; + } + + return handle; +} + +void +gimp_tool_transform_grid_hover (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + gboolean proximity) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle = GIMP_TRANSFORM_HANDLE_NONE; + GimpTransformHandle i; + + for (i = GIMP_TRANSFORM_HANDLE_NONE + 1; i < GIMP_N_TRANSFORM_HANDLES; i++) + { + if (private->handles[i] && + gimp_canvas_item_hit (private->handles[i], coords->x, coords->y)) + { + handle = i; + break; + } + } + + if (handle == GIMP_TRANSFORM_HANDLE_NONE) + { + /* points passed in clockwise order */ + if (point_is_inside_polygon (4, + (gdouble[4]){ private->tx1, private->tx2, + private->tx4, private->tx3 }, + (gdouble[4]){ private->ty1, private->ty2, + private->ty4, private->ty3 }, + coords->x, coords->y)) + { + handle = gimp_tool_transform_get_area_handle (grid, coords, + private->inside_function); + } + else + { + handle = gimp_tool_transform_get_area_handle (grid, coords, + private->outside_function); + } + } + + if (handle != GIMP_TRANSFORM_HANDLE_NONE) + gimp_tool_widget_status (widget, get_friendly_operation_name (handle)); + + private->handle = handle; + + gimp_tool_transform_grid_update_hilight (grid); +} + +static gboolean +gimp_tool_transform_grid_get_cursor (GimpToolWidget *widget, + const GimpCoords *coords, + GdkModifierType state, + GimpCursorType *cursor, + GimpToolCursorType *tool_cursor, + GimpCursorModifier *cursor_modifier) +{ + GimpToolTransformGrid *grid = GIMP_TOOL_TRANSFORM_GRID (widget); + GimpToolTransformGridPrivate *private = grid->private; + gdouble angle[8]; + gint i; + GimpCursorType map[8]; + GimpVector2 pos[4], this, that; + gboolean flip = FALSE; + gboolean side = FALSE; + gboolean set_cursor = TRUE; + + map[0] = GIMP_CURSOR_CORNER_TOP_LEFT; + map[1] = GIMP_CURSOR_CORNER_TOP; + map[2] = GIMP_CURSOR_CORNER_TOP_RIGHT; + map[3] = GIMP_CURSOR_CORNER_RIGHT; + map[4] = GIMP_CURSOR_CORNER_BOTTOM_RIGHT; + map[5] = GIMP_CURSOR_CORNER_BOTTOM; + map[6] = GIMP_CURSOR_CORNER_BOTTOM_LEFT; + map[7] = GIMP_CURSOR_CORNER_LEFT; + + get_handle_geometry (grid, pos, angle); + + for (i = 0; i < 8; i++) + angle[i] = round (angle[i] * 180.0 / G_PI / 45.0); + + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NW: + i = (gint) angle[4] + 0; + this = pos[0]; + that = pos[3]; + break; + + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_NE: + i = (gint) angle[5] + 2; + this = pos[1]; + that = pos[2]; + break; + + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SW: + i = (gint) angle[6] + 6; + this = pos[2]; + that = pos[1]; + break; + + case GIMP_TRANSFORM_HANDLE_SE_P: + case GIMP_TRANSFORM_HANDLE_SE: + i = (gint) angle[7] + 4; + this = pos[3]; + that = pos[0]; + break; + + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_N_S: + i = (gint) angle[0] + 1; + this = vectoradd (pos[0], pos[1]); + that = vectoradd (pos[2], pos[3]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_S_S: + i = (gint) angle[1] + 5; + this = vectoradd (pos[2], pos[3]); + that = vectoradd (pos[0], pos[1]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_E_S: + i = (gint) angle[2] + 3; + this = vectoradd (pos[1], pos[3]); + that = vectoradd (pos[0], pos[2]); + side = TRUE; + break; + + case GIMP_TRANSFORM_HANDLE_W: + case GIMP_TRANSFORM_HANDLE_W_S: + i = (gint) angle[3] + 7; + this = vectoradd (pos[0], pos[2]); + that = vectoradd (pos[1], pos[3]); + side = TRUE; + break; + + default: + set_cursor = FALSE; + break; + } + + if (set_cursor) + { + i %= 8; + + switch (map[i]) + { + case GIMP_CURSOR_CORNER_TOP_LEFT: + if (this.x + this.y > that.x + that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_TOP: + if (this.y > that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_TOP_RIGHT: + if (this.x - this.y < that.x - that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_RIGHT: + if (this.x < that.x) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM_RIGHT: + if (this.x + this.y < that.x + that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM: + if (this.y < that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_BOTTOM_LEFT: + if (this.x - this.y > that.x - that.y) + flip = TRUE; + break; + case GIMP_CURSOR_CORNER_LEFT: + if (this.x > that.x) + flip = TRUE; + break; + default: + g_assert_not_reached (); + } + + if (flip) + *cursor = map[(i + 4) % 8]; + else + *cursor = map[i]; + + if (side) + *cursor += 8; + } + + /* parent class handles *cursor and *modifier for most handles */ + switch (private->handle) + { + case GIMP_TRANSFORM_HANDLE_NONE: + *tool_cursor = GIMP_TOOL_CURSOR_NONE; + break; + + case GIMP_TRANSFORM_HANDLE_NW_P: + case GIMP_TRANSFORM_HANDLE_NE_P: + case GIMP_TRANSFORM_HANDLE_SW_P: + case GIMP_TRANSFORM_HANDLE_SE_P: + *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE; + break; + + case GIMP_TRANSFORM_HANDLE_NW: + case GIMP_TRANSFORM_HANDLE_NE: + case GIMP_TRANSFORM_HANDLE_SW: + case GIMP_TRANSFORM_HANDLE_SE: + case GIMP_TRANSFORM_HANDLE_N: + case GIMP_TRANSFORM_HANDLE_S: + case GIMP_TRANSFORM_HANDLE_E: + case GIMP_TRANSFORM_HANDLE_W: + *tool_cursor = GIMP_TOOL_CURSOR_RESIZE; + break; + + case GIMP_TRANSFORM_HANDLE_CENTER: + *tool_cursor = GIMP_TOOL_CURSOR_MOVE; + break; + + case GIMP_TRANSFORM_HANDLE_PIVOT: + *tool_cursor = GIMP_TOOL_CURSOR_ROTATE; + *cursor_modifier = GIMP_CURSOR_MODIFIER_MOVE; + break; + + case GIMP_TRANSFORM_HANDLE_N_S: + case GIMP_TRANSFORM_HANDLE_S_S: + case GIMP_TRANSFORM_HANDLE_E_S: + case GIMP_TRANSFORM_HANDLE_W_S: + *tool_cursor = GIMP_TOOL_CURSOR_SHEAR; + break; + + case GIMP_TRANSFORM_HANDLE_ROTATION: + *tool_cursor = GIMP_TOOL_CURSOR_ROTATE; + break; + + default: + g_return_val_if_reached (FALSE); + } + + return TRUE; +} + +static void +gimp_tool_transform_grid_update_hilight (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + GimpTransformHandle handle; + + for (handle = GIMP_TRANSFORM_HANDLE_NONE; + handle < GIMP_N_TRANSFORM_HANDLES; + handle++) + { + if (private->handles[handle]) + { + gimp_canvas_item_set_highlight (private->handles[handle], + handle == private->handle); + } + } +} + +static void +gimp_tool_transform_grid_update_box (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + + gimp_matrix3_transform_point (&private->transform, + private->x1, private->y1, + &private->tx1, &private->ty1); + gimp_matrix3_transform_point (&private->transform, + private->x2, private->y1, + &private->tx2, &private->ty2); + gimp_matrix3_transform_point (&private->transform, + private->x1, private->y2, + &private->tx3, &private->ty3); + gimp_matrix3_transform_point (&private->transform, + private->x2, private->y2, + &private->tx4, &private->ty4); + + /* don't transform pivot */ + private->tpx = private->pivot_x; + private->tpy = private->pivot_y; + + private->tcx = (private->tx1 + + private->tx2 + + private->tx3 + + private->tx4) / 4.0; + private->tcy = (private->ty1 + + private->ty2 + + private->ty3 + + private->ty4) / 4.0; +} + +static void +gimp_tool_transform_grid_update_matrix (GimpToolTransformGrid *grid) +{ + GimpToolTransformGridPrivate *private = grid->private; + + gimp_matrix3_identity (&private->transform); + gimp_transform_matrix_perspective (&private->transform, + private->x1, + private->y1, + private->x2 - private->x1, + private->y2 - private->y1, + private->tx1, + private->ty1, + private->tx2, + private->ty2, + private->tx3, + private->ty3, + private->tx4, + private->ty4); + + private->pivot_x = private->tpx; + private->pivot_y = private->tpy; + + g_object_freeze_notify (G_OBJECT (grid)); + g_object_notify (G_OBJECT (grid), "transform"); + g_object_notify (G_OBJECT (grid), "pivot-x"); + g_object_notify (G_OBJECT (grid), "pivot-x"); + g_object_thaw_notify (G_OBJECT (grid)); +} + +static void +gimp_tool_transform_grid_calc_handles (GimpToolTransformGrid *grid, + gint *handle_w, + gint *handle_h) +{ + GimpToolTransformGridPrivate *private = grid->private; + gint dx1, dy1; + gint dx2, dy2; + gint dx3, dy3; + gint dx4, dy4; + gint x1, y1; + gint x2, y2; + + gimp_canvas_item_transform_xy (private->guides, + private->tx1, private->ty1, + &dx1, &dy1); + gimp_canvas_item_transform_xy (private->guides, + private->tx2, private->ty2, + &dx2, &dy2); + gimp_canvas_item_transform_xy (private->guides, + private->tx3, private->ty3, + &dx3, &dy3); + gimp_canvas_item_transform_xy (private->guides, + private->tx4, private->ty4, + &dx4, &dy4); + + x1 = MIN4 (dx1, dx2, dx3, dx4); + y1 = MIN4 (dy1, dy2, dy3, dy4); + x2 = MAX4 (dx1, dx2, dx3, dx4); + y2 = MAX4 (dy1, dy2, dy3, dy4); + + *handle_w = CLAMP ((x2 - x1) / 3, + MIN_HANDLE_SIZE, GIMP_TOOL_HANDLE_SIZE_LARGE); + *handle_h = CLAMP ((y2 - y1) / 3, + MIN_HANDLE_SIZE, GIMP_TOOL_HANDLE_SIZE_LARGE); +} + + +/* public functions */ + +GimpToolWidget * +gimp_tool_transform_grid_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType guide_type, + gint n_guides) +{ + g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); + + return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_GRID, + "shell", shell, + "transform", transform, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + "guide-type", guide_type, + "n-guides", n_guides, + NULL); +} diff --git a/app/display/gimptooltransformgrid.h b/app/display/gimptooltransformgrid.h new file mode 100644 index 0000000000..81d7f39e28 --- /dev/null +++ b/app/display/gimptooltransformgrid.h @@ -0,0 +1,92 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimptooltransformgrid.h + * Copyright (C) 2017 Michael Natterer + * + * 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_TOOL_TRANSFORM_GRID_H__ +#define __GIMP_TOOL_TRANSFORM_GRID_H__ + + +#include "gimptoolwidget.h" + + +typedef enum +{ + GIMP_TRANSFORM_HANDLE_NONE, + GIMP_TRANSFORM_HANDLE_NW_P, /* north west perspective */ + GIMP_TRANSFORM_HANDLE_NE_P, /* north east perspective */ + GIMP_TRANSFORM_HANDLE_SW_P, /* south west perspective */ + GIMP_TRANSFORM_HANDLE_SE_P, /* south east perspective */ + GIMP_TRANSFORM_HANDLE_NW, /* north west */ + GIMP_TRANSFORM_HANDLE_NE, /* north east */ + GIMP_TRANSFORM_HANDLE_SW, /* south west */ + GIMP_TRANSFORM_HANDLE_SE, /* south east */ + GIMP_TRANSFORM_HANDLE_N, /* north */ + GIMP_TRANSFORM_HANDLE_S, /* south */ + GIMP_TRANSFORM_HANDLE_E, /* east */ + GIMP_TRANSFORM_HANDLE_W, /* west */ + GIMP_TRANSFORM_HANDLE_CENTER, /* center for moving */ + GIMP_TRANSFORM_HANDLE_PIVOT, /* pivot for rotation and scaling */ + GIMP_TRANSFORM_HANDLE_N_S, /* north shearing */ + GIMP_TRANSFORM_HANDLE_S_S, /* south shearing */ + GIMP_TRANSFORM_HANDLE_E_S, /* east shearing */ + GIMP_TRANSFORM_HANDLE_W_S, /* west shearing */ + GIMP_TRANSFORM_HANDLE_ROTATION, /* rotation */ + + GIMP_N_TRANSFORM_HANDLES /* keep this last so *handles[] is the right size */ +} GimpTransformHandle; + + +#define GIMP_TYPE_TOOL_TRANSFORM_GRID (gimp_tool_transform_grid_get_type ()) +#define GIMP_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGrid)) +#define GIMP_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass)) +#define GIMP_IS_TOOL_TRANSFORM_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID)) +#define GIMP_IS_TOOL_TRANSFORM_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_TOOL_TRANSFORM_GRID)) +#define GIMP_TOOL_TRANSFORM_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_TOOL_TRANSFORM_GRID, GimpToolTransformGridClass)) + + +typedef struct _GimpToolTransformGrid GimpToolTransformGrid; +typedef struct _GimpToolTransformGridPrivate GimpToolTransformGridPrivate; +typedef struct _GimpToolTransformGridClass GimpToolTransformGridClass; + +struct _GimpToolTransformGrid +{ + GimpToolWidget parent_instance; + + GimpToolTransformGridPrivate *private; +}; + +struct _GimpToolTransformGridClass +{ + GimpToolWidgetClass parent_class; +}; + + +GType gimp_tool_transform_grid_get_type (void) G_GNUC_CONST; + +GimpToolWidget * gimp_tool_transform_grid_new (GimpDisplayShell *shell, + const GimpMatrix3 *transform, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2, + GimpGuidesType guide_type, + gint n_guides); + + +#endif /* __GIMP_TOOL_TRANSFORM_GRID_H__ */