/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimptoolgyroscope.c * Copyright (C) 2018 Ell * * 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 "display-types.h" #include "widgets/gimpwidgets-utils.h" #include "gimpdisplayshell.h" #include "gimpdisplayshell-transform.h" #include "gimptoolgyroscope.h" #include "gimp-intl.h" #define EPSILON 1e-6 #define DEG_TO_RAD (G_PI / 180.0) typedef enum { MODE_NONE, MODE_PAN, MODE_ROTATE, MODE_ZOOM } Mode; typedef enum { CONSTRAINT_NONE, CONSTRAINT_UNKNOWN, CONSTRAINT_HORIZONTAL, CONSTRAINT_VERTICAL } Constraint; enum { PROP_0, PROP_YAW, PROP_PITCH, PROP_ROLL, PROP_ZOOM, PROP_INVERT, PROP_SPEED, PROP_PIVOT_X, PROP_PIVOT_Y }; struct _GimpToolGyroscopePrivate { gdouble yaw; gdouble pitch; gdouble roll; gdouble zoom; gdouble orig_yaw; gdouble orig_pitch; gdouble orig_roll; gdouble orig_zoom; gboolean invert; gdouble speed; gdouble pivot_x; gdouble pivot_y; Mode mode; Constraint constraint; gdouble last_x; gdouble last_y; gdouble last_angle; gdouble curr_angle; gdouble last_zoom; }; /* local function prototypes */ static void gimp_tool_gyroscope_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_tool_gyroscope_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint gimp_tool_gyroscope_button_press (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonPressType press_type); static void gimp_tool_gyroscope_button_release (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonReleaseType release_type); static void gimp_tool_gyroscope_motion (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state); static GimpHit gimp_tool_gyroscope_hit (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, gboolean proximity); static void gimp_tool_gyroscope_hover (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, gboolean proximity); static gboolean gimp_tool_gyroscope_key_press (GimpToolWidget *widget, GdkEventKey *kevent); static void gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget, GdkModifierType key, gboolean press, GdkModifierType state); static gboolean gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, GimpCursorType *cursor, GimpToolCursorType *tool_cursor, GimpCursorModifier *modifier); static void gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope, GdkModifierType state); static void gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope); static void gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope); static void gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope, const GimpVector3 *axis); static void gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector, const GimpVector3 *axis); G_DEFINE_TYPE_WITH_PRIVATE (GimpToolGyroscope, gimp_tool_gyroscope, GIMP_TYPE_TOOL_WIDGET) #define parent_class gimp_tool_gyroscope_parent_class static void gimp_tool_gyroscope_class_init (GimpToolGyroscopeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass); object_class->set_property = gimp_tool_gyroscope_set_property; object_class->get_property = gimp_tool_gyroscope_get_property; widget_class->button_press = gimp_tool_gyroscope_button_press; widget_class->button_release = gimp_tool_gyroscope_button_release; widget_class->motion = gimp_tool_gyroscope_motion; widget_class->hit = gimp_tool_gyroscope_hit; widget_class->hover = gimp_tool_gyroscope_hover; widget_class->key_press = gimp_tool_gyroscope_key_press; widget_class->motion_modifier = gimp_tool_gyroscope_motion_modifier; widget_class->get_cursor = gimp_tool_gyroscope_get_cursor; g_object_class_install_property (object_class, PROP_YAW, g_param_spec_double ("yaw", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_PITCH, g_param_spec_double ("pitch", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_ROLL, g_param_spec_double ("roll", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_ZOOM, g_param_spec_double ("zoom", NULL, NULL, 0.0, +G_MAXDOUBLE, 1.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_INVERT, g_param_spec_boolean ("invert", NULL, NULL, FALSE, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_SPEED, g_param_spec_double ("speed", NULL, NULL, -G_MAXDOUBLE, +G_MAXDOUBLE, 1.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, -G_MAXDOUBLE, +G_MAXDOUBLE, 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, -G_MAXDOUBLE, +G_MAXDOUBLE, 0.0, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void gimp_tool_gyroscope_init (GimpToolGyroscope *gyroscope) { gyroscope->private = gimp_tool_gyroscope_get_instance_private (gyroscope); } static void gimp_tool_gyroscope_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object); GimpToolGyroscopePrivate *private = gyroscope->private; switch (property_id) { case PROP_YAW: private->yaw = g_value_get_double (value); break; case PROP_PITCH: private->pitch = g_value_get_double (value); break; case PROP_ROLL: private->roll = g_value_get_double (value); break; case PROP_ZOOM: private->zoom = g_value_get_double (value); break; case PROP_INVERT: private->invert = g_value_get_boolean (value); break; case PROP_SPEED: private->speed = g_value_get_double (value); 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_tool_gyroscope_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (object); GimpToolGyroscopePrivate *private = gyroscope->private; switch (property_id) { case PROP_YAW: g_value_set_double (value, private->yaw); break; case PROP_PITCH: g_value_set_double (value, private->pitch); break; case PROP_ROLL: g_value_set_double (value, private->roll); break; case PROP_ZOOM: g_value_set_double (value, private->zoom); break; case PROP_INVERT: g_value_set_boolean (value, private->invert); break; case PROP_SPEED: g_value_set_double (value, private->speed); 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; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint gimp_tool_gyroscope_button_press (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonPressType press_type) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); GimpToolGyroscopePrivate *private = gyroscope->private; gimp_tool_gyroscope_save (gyroscope); if (state & GDK_MOD1_MASK) { private->mode = MODE_ZOOM; private->last_zoom = private->zoom; } else if (state & gimp_get_extend_selection_mask ()) { private->mode = MODE_ROTATE; private->last_angle = atan2 (coords->y - private->pivot_y, coords->x - private->pivot_x); private->curr_angle = private->last_angle; } else { private->mode = MODE_PAN; if (state & gimp_get_constrain_behavior_mask ()) private->constraint = CONSTRAINT_UNKNOWN; else private->constraint = CONSTRAINT_NONE; } private->last_x = coords->x; private->last_y = coords->y; gimp_tool_gyroscope_update_status (gyroscope, state); return 1; } static void gimp_tool_gyroscope_button_release (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state, GimpButtonReleaseType release_type) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); GimpToolGyroscopePrivate *private = gyroscope->private; if (release_type == GIMP_BUTTON_RELEASE_CANCEL) gimp_tool_gyroscope_restore (gyroscope); private->mode = MODE_NONE; gimp_tool_gyroscope_update_status (gyroscope, state); } static void gimp_tool_gyroscope_motion (GimpToolWidget *widget, const GimpCoords *coords, guint32 time, GdkModifierType state) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); GimpToolGyroscopePrivate *private = gyroscope->private; GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); GimpVector3 axis = {}; switch (private->mode) { case MODE_PAN: { gdouble x1 = private->last_x; gdouble y1 = private->last_y; gdouble x2 = coords->x; gdouble y2 = coords->y; gdouble factor = 1.0 / private->zoom; if (private->constraint != CONSTRAINT_NONE) { gimp_display_shell_rotate_xy_f (shell, x1, y1, &x1, &y1); gimp_display_shell_rotate_xy_f (shell, x2, y2, &x2, &y2); if (private->constraint == CONSTRAINT_UNKNOWN) { if (fabs (x2 - x1) > fabs (y2 - y1)) private->constraint = CONSTRAINT_HORIZONTAL; else if (fabs (y2 - y1) > fabs (x2 - x1)) private->constraint = CONSTRAINT_VERTICAL; } if (private->constraint == CONSTRAINT_HORIZONTAL) y2 = y1; else if (private->constraint == CONSTRAINT_VERTICAL) x2 = x1; gimp_display_shell_unrotate_xy_f (shell, x1, y1, &x1, &y1); gimp_display_shell_unrotate_xy_f (shell, x2, y2, &x2, &y2); } if (private->invert) factor = 1.0 / factor; gimp_vector3_set (&axis, y2 - y1, x2 - x1, 0.0); gimp_vector3_mul (&axis, factor * private->speed); } break; case MODE_ROTATE: { gdouble angle; angle = atan2 (coords->y - private->pivot_y, coords->x - private->pivot_x); private->curr_angle = angle; angle -= private->last_angle; if (state & gimp_get_constrain_behavior_mask ()) angle = RINT (angle / (G_PI / 6.0)) * (G_PI / 6.0); gimp_vector3_set (&axis, 0.0, 0.0, angle); private->last_angle += angle; } break; case MODE_ZOOM: { gdouble x1, y1; gdouble x2, y2; gdouble zoom; gimp_display_shell_transform_xy_f (shell, private->last_x, private->last_y, &x1, &y1); gimp_display_shell_transform_xy_f (shell, coords->x, coords->y, &x2, &y2); zoom = (y1 - y2) * shell->scale_y / 128.0; if (private->invert) zoom = -zoom; private->last_zoom *= pow (2.0, zoom); zoom = log (private->last_zoom / private->zoom) / G_LN2; if (state & gimp_get_constrain_behavior_mask ()) zoom = RINT (zoom * 2.0) / 2.0; g_object_set (gyroscope, "zoom", private->zoom * pow (2.0, zoom), NULL); } break; case MODE_NONE: g_return_if_reached (); } private->last_x = coords->x; private->last_y = coords->y; gimp_tool_gyroscope_rotate (gyroscope, &axis); } static GimpHit gimp_tool_gyroscope_hit (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, gboolean proximity) { return GIMP_HIT_INDIRECT; } static void gimp_tool_gyroscope_hover (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, gboolean proximity) { gimp_tool_gyroscope_update_status (GIMP_TOOL_GYROSCOPE (widget), state); } static gboolean gimp_tool_gyroscope_key_press (GimpToolWidget *widget, GdkEventKey *kevent) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); GimpToolGyroscopePrivate *private = gyroscope->private; GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget); GimpVector3 axis = {}; gboolean fast; gboolean result = FALSE; fast = (kevent->state & gimp_get_constrain_behavior_mask ()); if (kevent->state & GDK_MOD1_MASK) { /* zoom */ gdouble zoom = 0.0; switch (kevent->keyval) { case GDK_KEY_Up: zoom = fast ? +1.0 : +1.0 / 8.0; result = TRUE; break; case GDK_KEY_Down: zoom = fast ? -1.0 : -1.0 / 8.0; result = TRUE; break; } if (private->invert) zoom = -zoom; if (zoom) { g_object_set (gyroscope, "zoom", private->zoom * pow (2.0, zoom), NULL); } } else if (kevent->state & gimp_get_extend_selection_mask ()) { /* rotate */ gdouble angle = 0.0; switch (kevent->keyval) { case GDK_KEY_Left: angle = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Right: angle = fast ? -15.0 : -1.0; result = TRUE; break; } if (shell->flip_horizontally ^ shell->flip_vertically) angle = -angle; gimp_vector3_set (&axis, 0.0, 0.0, angle * DEG_TO_RAD); } else { /* pan */ gdouble x0 = 0.0; gdouble y0 = 0.0; gdouble x = 0.0; gdouble y = 0.0; gdouble factor = 1.0 / private->zoom; if (private->invert) factor = 1.0 / factor; switch (kevent->keyval) { case GDK_KEY_Left: x = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Right: x = fast ? -15.0 : -1.0; result = TRUE; break; case GDK_KEY_Up: y = fast ? +15.0 : +1.0; result = TRUE; break; case GDK_KEY_Down: y = fast ? -15.0 : -1.0; result = TRUE; break; } gimp_display_shell_unrotate_xy_f (shell, x0, y0, &x0, &y0); gimp_display_shell_unrotate_xy_f (shell, x, y, &x, &y); gimp_vector3_set (&axis, (y - y0) * DEG_TO_RAD, (x - x0) * DEG_TO_RAD, 0.0); gimp_vector3_mul (&axis, factor); } gimp_tool_gyroscope_rotate (gyroscope, &axis); return result; } static void gimp_tool_gyroscope_motion_modifier (GimpToolWidget *widget, GdkModifierType key, gboolean press, GdkModifierType state) { GimpToolGyroscope *gyroscope = GIMP_TOOL_GYROSCOPE (widget); GimpToolGyroscopePrivate *private = gyroscope->private; gimp_tool_gyroscope_update_status (gyroscope, state); if (key == gimp_get_constrain_behavior_mask ()) { switch (private->mode) { case MODE_PAN: if (state & gimp_get_constrain_behavior_mask ()) private->constraint = CONSTRAINT_UNKNOWN; else private->constraint = CONSTRAINT_NONE; break; case MODE_ROTATE: if (! (state & gimp_get_constrain_behavior_mask ())) private->last_angle = private->curr_angle; break; case MODE_ZOOM: if (! (state & gimp_get_constrain_behavior_mask ())) private->last_zoom = private->zoom; break; case MODE_NONE: break; } } } static gboolean gimp_tool_gyroscope_get_cursor (GimpToolWidget *widget, const GimpCoords *coords, GdkModifierType state, GimpCursorType *cursor, GimpToolCursorType *tool_cursor, GimpCursorModifier *modifier) { if (state & GDK_MOD1_MASK) *modifier = GIMP_CURSOR_MODIFIER_ZOOM; else if (state & gimp_get_extend_selection_mask ()) *modifier = GIMP_CURSOR_MODIFIER_ROTATE; else *modifier = GIMP_CURSOR_MODIFIER_MOVE; return TRUE; } static void gimp_tool_gyroscope_update_status (GimpToolGyroscope *gyroscope, GdkModifierType state) { GimpToolGyroscopePrivate *private = gyroscope->private; gchar *status; if (private->mode == MODE_ZOOM || (private->mode == MODE_NONE && state & GDK_MOD1_MASK)) { status = gimp_suggest_modifiers (_("Click-Drag to zoom"), gimp_get_toggle_behavior_mask () & ~state, NULL, _("%s for constrained steps"), NULL); } else if (private->mode == MODE_ROTATE || (private->mode == MODE_NONE && state & gimp_get_extend_selection_mask ())) { status = gimp_suggest_modifiers (_("Click-Drag to rotate"), gimp_get_toggle_behavior_mask () & ~state, NULL, _("%s for constrained angles"), NULL); } else { status = gimp_suggest_modifiers (_("Click-Drag to pan"), (((gimp_get_extend_selection_mask () | GDK_MOD1_MASK) * (private->mode == MODE_NONE)) | gimp_get_toggle_behavior_mask ()) & ~state, _("%s to rotate"), _("%s for a constrained axis"), _("%s to zoom")); } gimp_tool_widget_set_status (GIMP_TOOL_WIDGET (gyroscope), status); g_free (status); } static void gimp_tool_gyroscope_save (GimpToolGyroscope *gyroscope) { GimpToolGyroscopePrivate *private = gyroscope->private; private->orig_yaw = private->yaw; private->orig_pitch = private->pitch; private->orig_roll = private->roll; private->orig_zoom = private->zoom; } static void gimp_tool_gyroscope_restore (GimpToolGyroscope *gyroscope) { GimpToolGyroscopePrivate *private = gyroscope->private; g_object_set (gyroscope, "yaw", private->orig_yaw, "pitch", private->orig_pitch, "roll", private->orig_roll, "zoom", private->orig_zoom, NULL); } static void gimp_tool_gyroscope_rotate (GimpToolGyroscope *gyroscope, const GimpVector3 *axis) { GimpToolGyroscopePrivate *private = gyroscope->private; GimpVector3 real_axis; GimpVector3 basis[2]; gdouble yaw; gdouble pitch; gdouble roll; gint i; if (gimp_vector3_length (axis) < EPSILON) return; real_axis = *axis; if (private->invert) gimp_vector3_neg (&real_axis); for (i = 0; i < 2; i++) { gimp_vector3_set (&basis[i], i == 0, i == 1, 0.0); if (private->invert) gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis); gimp_tool_gyroscope_rotate_vector ( &basis[i], &(GimpVector3) {0.0, private->yaw * DEG_TO_RAD, 0.0}); gimp_tool_gyroscope_rotate_vector ( &basis[i], &(GimpVector3) {private->pitch * DEG_TO_RAD, 0.0, 0.0}); gimp_tool_gyroscope_rotate_vector ( &basis[i], &(GimpVector3) {0.0, 0.0, private->roll * DEG_TO_RAD}); if (! private->invert) gimp_tool_gyroscope_rotate_vector (&basis[i], &real_axis); } roll = atan2 (basis[1].x, basis[1].y); for (i = 0; i < 2; i++) { gimp_tool_gyroscope_rotate_vector ( &basis[i], &(GimpVector3) {0.0, 0.0, -roll}); } pitch = atan2 (-basis[1].z, basis[1].y); for (i = 0; i < 1; i++) { gimp_tool_gyroscope_rotate_vector ( &basis[i], &(GimpVector3) {-pitch, 0.0, 0.0}); } yaw = atan2 (basis[0].z, basis[0].x); g_object_set (gyroscope, "yaw", yaw / DEG_TO_RAD, "pitch", pitch / DEG_TO_RAD, "roll", roll / DEG_TO_RAD, NULL); } static void gimp_tool_gyroscope_rotate_vector (GimpVector3 *vector, const GimpVector3 *axis) { GimpVector3 normalized_axis; GimpVector3 projection; GimpVector3 u; GimpVector3 v; gdouble angle; angle = gimp_vector3_length (axis); if (angle < EPSILON) return; normalized_axis = gimp_vector3_mul_val (*axis, 1.0 / angle); projection = gimp_vector3_mul_val ( normalized_axis, gimp_vector3_inner_product (vector, &normalized_axis)); u = gimp_vector3_sub_val (*vector, projection); v = gimp_vector3_cross_product (&u, &normalized_axis); gimp_vector3_mul (&u, cos (angle)); gimp_vector3_mul (&v, sin (angle)); gimp_vector3_add (vector, &u, &v); gimp_vector3_add (vector, vector, &projection); } /* public functions */ GimpToolWidget * gimp_tool_gyroscope_new (GimpDisplayShell *shell) { g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL); return g_object_new (GIMP_TYPE_TOOL_GYROSCOPE, "shell", shell, NULL); }