Commit b3a9a6a3 authored by Michael Natterer's avatar Michael Natterer 😴

Bug 55367 - Rotated view of the canvas (view is rotated, not image contents)

First version of display rotation, inspired by gimp-painter.
The rotation always happens around the image's center.

The only "UI" for rotating is currently shift+middle-drag and
shift+space-drag. Control constrains the angle to 15 degrees
and is currently the only way to go back to "no rotation".
parent f45e7c26
......@@ -113,6 +113,8 @@ libappdisplay_a_sources = \
gimpdisplayshell-progress.h \
gimpdisplayshell-render.c \
gimpdisplayshell-render.h \
gimpdisplayshell-rotate.c \
gimpdisplayshell-rotate.h \
gimpdisplayshell-scale.c \
gimpdisplayshell-scale.h \
gimpdisplayshell-scale-dialog.c \
......
......@@ -629,12 +629,18 @@ gimp_canvas_item_transform_xy (GimpCanvasItem *item,
gint *ty)
{
GimpCanvasItemPrivate *private;
gint64 nx;
gint64 ny;
g_return_if_fail (GIMP_IS_CANVAS_ITEM (item));
private = GET_PRIVATE (item);
gimp_display_shell_transform_xy (private->shell, x, y, tx, ty);
nx = x * private->shell->scale_x - private->shell->offset_x;
ny = y * private->shell->scale_y - private->shell->offset_y;
*tx = CLAMP (nx, G_MININT, G_MAXINT);
*ty = CLAMP (ny, G_MININT, G_MAXINT);
}
void
......@@ -650,7 +656,8 @@ gimp_canvas_item_transform_xy_f (GimpCanvasItem *item,
private = GET_PRIVATE (item);
gimp_display_shell_transform_xy_f (private->shell, x, y, tx, ty);
*tx = SCALEX (private->shell, x) - private->shell->offset_x;
*ty = SCALEY (private->shell, y) - private->shell->offset_y;
}
......
......@@ -886,8 +886,9 @@ gimp_display_paint_area (GimpDisplay *display,
h = (y2 - y1);
/* display the area */
gimp_display_shell_transform_xy_f (shell, x, y, &x1_f, &y1_f);
gimp_display_shell_transform_xy_f (shell, x + w, y + h, &x2_f, &y2_f);
gimp_display_shell_transform_bounds (shell,
x, y, x + w, y + h,
&x1_f, &y1_f, &x2_f, &y2_f);
/* make sure to expose a superset of the transformed sub-pixel expose
* area, not a subset. bug #126942. --mitch
......
......@@ -454,7 +454,8 @@ static void
gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
cairo_t *cr)
{
cairo_rectangle_int_t image_rect;
cairo_rectangle_list_t *clip_rectangles;
cairo_rectangle_int_t image_rect;
image_rect.x = - shell->offset_x;
image_rect.y = - shell->offset_y;
......@@ -469,6 +470,9 @@ gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
cairo_save (cr);
if (shell->rotate_transform)
cairo_transform (cr, shell->rotate_transform);
cairo_rectangle (cr,
image_rect.x,
image_rect.y,
......@@ -489,6 +493,10 @@ gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
*/
cairo_save (cr);
clip_rectangles = cairo_copy_clip_rectangle_list (cr);
if (shell->rotate_transform)
cairo_transform (cr, shell->rotate_transform);
cairo_rectangle (cr,
image_rect.x,
......@@ -499,15 +507,12 @@ gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
if (gdk_cairo_get_clip_rectangle (cr, NULL))
{
cairo_rectangle_list_t *clip_rectangles;
gint i;
gint i;
cairo_save (cr);
gimp_display_shell_draw_checkerboard (shell, cr);
cairo_restore (cr);
clip_rectangles = cairo_copy_clip_rectangle_list (cr);
for (i = 0; i < clip_rectangles->num_rectangles; i++)
{
cairo_rectangle_t rect = clip_rectangles->rectangles[i];
......@@ -518,10 +523,9 @@ gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
ceil (rect.width),
ceil (rect.height));
}
cairo_rectangle_list_destroy (clip_rectangles);
}
cairo_rectangle_list_destroy (clip_rectangles);
cairo_restore (cr);
......@@ -529,6 +533,9 @@ gimp_display_shell_canvas_draw_image (GimpDisplayShell *shell,
*/
/* draw canvas items */
if (shell->rotate_transform)
cairo_transform (cr, shell->rotate_transform);
gimp_canvas_item_draw (shell->canvas_item, cr);
/* restart (and recalculate) the selection boundaries */
......
......@@ -28,6 +28,7 @@
#include "display-types.h"
#include "core/gimp-cairo.h"
#include "core/gimp-utils.h"
#include "gimpcanvas.h"
#include "gimpcanvas-style.h"
......@@ -36,6 +37,8 @@
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-draw.h"
#include "gimpdisplayshell-render.h"
#include "gimpdisplayshell-rotate.h"
#include "gimpdisplayshell-scale.h"
#include "gimpdisplayxfer.h"
......@@ -132,22 +135,54 @@ gimp_display_shell_draw_image (GimpDisplayShell *shell,
gint w,
gint h)
{
gint x2, y2;
gint x1, y1, x2, y2;
gint i, j;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
g_return_if_fail (gimp_display_get_image (shell->display));
g_return_if_fail (cr != NULL);
x2 = x + w;
y2 = y + h;
if (shell->rotate_untransform)
{
gdouble tx1, ty1;
gdouble tx2, ty2;
gint image_width;
gint image_height;
gimp_display_shell_rotate_untransform_bounds (shell,
x, y, x + w, y + h,
&tx1, &ty1, &tx2, &ty2);
x1 = floor (tx1 - 0.5);
y1 = floor (ty1 - 0.5);
x2 = ceil (tx2 + 0.5);
y2 = ceil (ty2 + 0.5);
gimp_display_shell_scale_get_image_size (shell,
&image_width, &image_height);
x1 = CLAMP (x1, -shell->offset_x, -shell->offset_x + image_width);
y1 = CLAMP (y1, -shell->offset_y, -shell->offset_y + image_height);
x2 = CLAMP (x2, -shell->offset_x, -shell->offset_x + image_width);
y2 = CLAMP (y2, -shell->offset_y, -shell->offset_y + image_height);
if (!(x2 > x1) || !(y2 > y1))
return;
}
else
{
x1 = x;
y1 = y;
x2 = x + w;
y2 = y + h;
}
/* display the image in RENDER_BUF_WIDTH x RENDER_BUF_HEIGHT
* sized chunks
*/
for (i = y; i < y2; i += GIMP_DISPLAY_RENDER_BUF_HEIGHT)
for (i = y1; i < y2; i += GIMP_DISPLAY_RENDER_BUF_HEIGHT)
{
for (j = x; j < x2; j += GIMP_DISPLAY_RENDER_BUF_WIDTH)
for (j = x1; j < x2; j += GIMP_DISPLAY_RENDER_BUF_WIDTH)
{
gint dx, dy;
......
......@@ -22,6 +22,8 @@
#include <gtk/gtk.h>
#include <libgimpmath/gimpmath.h>
#include "display-types.h"
#include "gimpcanvascursor.h"
......@@ -32,6 +34,7 @@
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-expose.h"
#include "gimpdisplayshell-items.h"
#include "gimpdisplayshell-rotate.h"
/* local function prototypes */
......@@ -190,5 +193,38 @@ gimp_display_shell_item_update (GimpCanvasItem *item,
cairo_region_t *region,
GimpDisplayShell *shell)
{
gimp_display_shell_expose_region (shell, region);
if (shell->rotate_transform)
{
gint n_rects;
gint i;
n_rects = cairo_region_num_rectangles (region);
for (i = 0; i < n_rects; i++)
{
cairo_rectangle_int_t rect;
gdouble tx1, ty1;
gdouble tx2, ty2;
gint x1, y1, x2, y2;
cairo_region_get_rectangle (region, i, &rect);
gimp_display_shell_rotate_transform_bounds (shell,
rect.x, rect.y,
rect.x + rect.width,
rect.y + rect.height,
&tx1, &ty1, &tx2, &ty2);
x1 = floor (tx1 - 0.5);
y1 = floor (ty1 - 0.5);
x2 = ceil (tx2 + 0.5);
y2 = ceil (ty2 + 0.5);
gimp_display_shell_expose_area (shell, x1, y1, x2 - x1, y2 - y1);
}
}
else
{
gimp_display_shell_expose_region (shell, region);
}
}
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpmath/gimpmath.h"
#include "display-types.h"
#include "core/gimp-utils.h"
#include "gimpdisplay.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-expose.h"
#include "gimpdisplayshell-rotate.h"
#include "gimpdisplayshell-scale.h"
/* public functions */
void
gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell)
{
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
g_free (shell->rotate_transform);
g_free (shell->rotate_untransform);
if (shell->rotate_angle != 0.0 && gimp_display_get_image (shell->display))
{
gint image_width, image_height;
gdouble cx, cy;
shell->rotate_transform = g_new (cairo_matrix_t, 1);
shell->rotate_untransform = g_new (cairo_matrix_t, 1);
gimp_display_shell_scale_get_image_size (shell,
&image_width, &image_height);
cx = -shell->offset_x + image_width / 2;
cy = -shell->offset_y + image_height / 2;
cairo_matrix_init_translate (shell->rotate_transform, cx, cy);
cairo_matrix_rotate (shell->rotate_transform,
shell->rotate_angle / 180.0 * G_PI);
cairo_matrix_translate (shell->rotate_transform, -cx, -cy);
*shell->rotate_untransform = *shell->rotate_transform;
cairo_matrix_invert (shell->rotate_untransform);
}
else
{
shell->rotate_transform = NULL;
shell->rotate_untransform = NULL;
}
}
void
gimp_display_shell_rotate_transform_bounds (GimpDisplayShell *shell,
gdouble x1,
gdouble y1,
gdouble x2,
gdouble y2,
gdouble *nx1,
gdouble *ny1,
gdouble *nx2,
gdouble *ny2)
{
gdouble tx1, ty1;
gdouble tx2, ty2;
gdouble tx3, ty3;
gdouble tx4, ty4;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
tx1 = x1;
ty1 = y1;
tx2 = x1;
ty2 = y2;
tx3 = x2;
ty3 = y1;
tx4 = x2;
ty4 = y2;
cairo_matrix_transform_point (shell->rotate_transform, &tx1, &ty1);
cairo_matrix_transform_point (shell->rotate_transform, &tx2, &ty2);
cairo_matrix_transform_point (shell->rotate_transform, &tx3, &ty3);
cairo_matrix_transform_point (shell->rotate_transform, &tx4, &ty4);
*nx1 = MIN4 (tx1, tx2, tx3, tx4);
*ny1 = MIN4 (ty1, ty2, ty3, ty4);
*nx2 = MAX4 (tx1, tx2, tx3, tx4);
*ny2 = MAX4 (ty1, ty2, ty3, ty4);
}
void
gimp_display_shell_rotate_untransform_bounds (GimpDisplayShell *shell,
gdouble x1,
gdouble y1,
gdouble x2,
gdouble y2,
gdouble *nx1,
gdouble *ny1,
gdouble *nx2,
gdouble *ny2)
{
gdouble tx1, ty1;
gdouble tx2, ty2;
gdouble tx3, ty3;
gdouble tx4, ty4;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
tx1 = x1;
ty1 = y1;
tx2 = x1;
ty2 = y2;
tx3 = x2;
ty3 = y1;
tx4 = x2;
ty4 = y2;
cairo_matrix_transform_point (shell->rotate_untransform, &tx1, &ty1);
cairo_matrix_transform_point (shell->rotate_untransform, &tx2, &ty2);
cairo_matrix_transform_point (shell->rotate_untransform, &tx3, &ty3);
cairo_matrix_transform_point (shell->rotate_untransform, &tx4, &ty4);
*nx1 = MIN4 (tx1, tx2, tx3, tx4);
*ny1 = MIN4 (ty1, ty2, ty3, ty4);
*nx2 = MAX4 (tx1, tx2, tx3, tx4);
*ny2 = MAX4 (ty1, ty2, ty3, ty4);
}
void
gimp_display_shell_rotate_drag (GimpDisplayShell *shell,
gdouble last_x,
gdouble last_y,
gdouble cur_x,
gdouble cur_y,
gboolean constrain)
{
gint image_width, image_height;
gdouble px, py;
gdouble x1, y1, x2, y2;
gdouble angle1, angle2, angle;
g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
gimp_display_shell_scale_get_image_size (shell,
&image_width, &image_height);
px = -shell->offset_x + image_width / 2;
py = -shell->offset_y + image_height / 2;
x1 = cur_x - px;
x2 = last_x - px;
y1 = py - cur_y;
y2 = py - 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);
shell->rotate_drag_angle += (angle * 180.0 / G_PI);
if (constrain)
{
shell->rotate_angle = (gint) (((gint) shell->rotate_drag_angle / 15) * 15);
}
else
{
shell->rotate_angle = shell->rotate_drag_angle;
}
gimp_display_shell_rotate_update_transform (shell);
gimp_display_shell_expose_full (shell);
}
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GIMP_DISPLAY_SHELL_ROTATE_H__
#define __GIMP_DISPLAY_SHELL_ROTATE_H__
void gimp_display_shell_rotate_update_transform (GimpDisplayShell *shell);
void gimp_display_shell_rotate_transform_bounds (GimpDisplayShell *shell,
gdouble x1,
gdouble y1,
gdouble x2,
gdouble y2,
gdouble *nx1,
gdouble *ny1,
gdouble *nx2,
gdouble *ny2);
void gimp_display_shell_rotate_untransform_bounds (GimpDisplayShell *shell,
gdouble x1,
gdouble y1,
gdouble x2,
gdouble y2,
gdouble *nx1,
gdouble *ny1,
gdouble *nx2,
gdouble *ny2);
void gimp_display_shell_rotate_drag (GimpDisplayShell *shell,
gdouble last_x,
gdouble last_y,
gdouble cur_x,
gdouble cur_y,
gboolean constrain);
#endif /* __GIMP_DISPLAY_SHELL_ROTATE_H__ */
......@@ -36,6 +36,7 @@
#include "gimpdisplay-foreach.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-expose.h"
#include "gimpdisplayshell-rotate.h"
#include "gimpdisplayshell-scale.h"
#include "gimpdisplayshell-scroll.h"
......@@ -119,6 +120,8 @@ gimp_display_shell_scroll (GimpDisplayShell *shell,
shell->offset_x += x_offset;
shell->offset_y += y_offset;
gimp_display_shell_rotate_update_transform (shell);
gimp_overlay_box_scroll (GIMP_OVERLAY_BOX (shell->canvas),
-x_offset, -y_offset);
......
......@@ -292,6 +292,9 @@ selection_render_mask (Selection *selection)
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_width (cr, 1.0);
if (selection->shell->rotate_transform)
cairo_transform (cr, selection->shell->rotate_transform);
gimp_cairo_add_segments (cr,
selection->segs_in,
selection->n_segs_in);
......@@ -438,6 +441,9 @@ selection_start_timeout (Selection *selection)
cr = gdk_cairo_create (gtk_widget_get_window (selection->shell->canvas));
if (selection->shell->rotate_transform)
cairo_transform (cr, selection->shell->rotate_transform);
gimp_display_shell_draw_selection_out (selection->shell, cr,
selection->segs_out,
selection->n_segs_out);
......
......@@ -55,6 +55,7 @@
#include "gimpdisplayshell-cursor.h"
#include "gimpdisplayshell-grab.h"
#include "gimpdisplayshell-layer-select.h"
#include "gimpdisplayshell-rotate.h"
#include "gimpdisplayshell-scale.h"
#include "gimpdisplayshell-scroll.h"
#include "gimpdisplayshell-tool-events.h"
......@@ -79,6 +80,7 @@ static void gimp_display_shell_check_device_cursor (GimpDisplayShell
static void gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
const GdkEvent *event,
GdkModifierType state,
gint x,
gint y);
static void gimp_display_shell_stop_scrolling (GimpDisplayShell *shell,
......@@ -561,7 +563,7 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
}
else if (bevent->button == 2)
{
gimp_display_shell_start_scrolling (shell, NULL,
gimp_display_shell_start_scrolling (shell, NULL, state,
bevent->x, bevent->y);
}
......@@ -841,9 +843,24 @@ gimp_display_shell_canvas_tool_events (GtkWidget *canvas,
? ((GdkEventMotion *) compressed_motion)->y
: mevent->y);
gimp_display_shell_scroll (shell,
shell->scroll_last_x - x,
shell->scroll_last_y - y);
if (shell->rotating)
{
gboolean constrain = (state & GDK_CONTROL_MASK) ? TRUE : FALSE;
gimp_display_shell_rotate_drag (shell,
shell->scroll_last_x,
shell->scroll_last_y,
x,
y,
constrain);
}
else
{
gimp_display_shell_scroll (shell,
shell->scroll_last_x - x,
shell->scroll_last_y - y);
}
shell->scroll_last_x = x;
shell->scroll_last_y = y;
......@@ -1406,6 +1423,7 @@ gimp_display_shell_check_device_cursor (GimpDisplayShell *shell)
static void
gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
const GdkEvent *event,
GdkModifierType state,
gint x,
gint y)
{
......@@ -1413,11 +1431,16 @@ gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
gimp_display_shell_pointer_grab (shell, event, GDK_POINTER_MOTION_MASK);
shell->scrolling = TRUE;
shell->scroll_last_x = x;
shell->scroll_last_y = y;
shell->scrolling = TRUE;
shell->scroll_last_x = x;
shell->scroll_last_y = y;
shell->rotating = (state & GDK_SHIFT_MASK) ? TRUE : FALSE;
shell->rotate_drag_angle = shell->rotate_angle;
gimp_display_shell_set_override_cursor (shell, GDK_FLEUR);
if (shell->rotating)
gimp_display_shell_set_override_cursor (shell, GDK_EXCHANGE);
else
gimp_display_shell_set_override_cursor (shell, GDK_FLEUR);
}
static void
......@@ -1428,9 +1451,11 @@ gimp_display_shell_stop_scrolling (GimpDisplayShell *shell,
gimp_display_shell_unset_override_cursor (shell);
shell->scrolling = FALSE;
shell->scroll_last_x = 0;
shell->scroll_last_y = 0;
shell->scrolling = FALSE;
shell->scroll_last_x = 0;
shell->scroll_last_y = 0;
shell->rotating = FALSE;
shell->rotate_drag_angle = 0.0;
gimp_display_shell_pointer_ungrab (shell, event);
}
......@@ -1457,6 +1482,7 @@ gimp_display_shell_space_pressed (GimpDisplayShell *shell,
GimpDeviceManager *manager;
GimpDeviceInfo *current_device;
GimpCoords coords;
GdkModifierType state = 0;
manager = gimp_devices_get_manager (gimp);
current_device = gimp_device_manager_get_current_device (manager);
......@@ -1464,8 +1490,9 @@ gimp_display_shell_space_pressed (GimpDisplayShell *shell,
gimp_device_info_get_device_coords (current_device,
gtk_widget_get_window (shell->canvas),
&coords);
gdk_event_get_state (event, &state);
gimp_display_shell_start_scrolling (shell, event,
gimp_display_shell_start_scrolling (shell, event, state,
coords.x, coords.y);
}
break;
......
......@@ -27,6 +27,7 @@
#include "core/gimpboundary.h"
#include "core/gimpdrawable.h"
#include "core/gimpimage.h"
#include "core/gimp-utils.h"
#include "gimpdisplay.h"
#include "gimpdisplayshell.h"
......@@ -59,6 +60,11 @@ gimp_display_shell_transform_coords (const GimpDisplayShell *shell,
display_coords->x -= shell->offset_x;
display_coords->y -= shell->offset_y;
if (shell->rotate_transform)
cairo_matrix_transform_point (shell->rotate_transform,
&display_coords->x,
&display_coords->y);
}
/**
......@@ -81,8 +87,13 @@ gimp_display_shell_untransform_coords (const GimpDisplayShell *shell,
*image_coords = *display_coords;
image_coords->x = display_coords->x + shell->offset_x;
image_coords->y = display_coords->y + shell->offset_y;
if (shell->rotate_untransform)
cairo_matrix_transform_point (shell->rotate_untransform,
&image_coords->x,
&image_coords->y);
image_coords->x += shell->offset_x;
image_coords->y += shell->offset_y;
image_coords->x /= shell->scale_x;
image_coords->y /= shell->scale_y;
......@@ -115,6 +126,17 @@ gimp_display_shell_transform_xy (const GimpDisplayShell *shell,
tx = x * shell->scale_x - shell->offset_x;
ty = y * shell->scale_y - shell->offset_y;
if (shell->rotate_transform)
{
gdouble fx = tx;
gdouble fy = ty;
cairo_matrix_transform_point (shell->rotate_transform, &fy, &fy);
tx = fx;
ty = fy;
}
/* The projected coordinates might overflow a gint in the case of big
images at high zoom levels, so we clamp them here to avoid problems. */
*nx = CLAMP (tx, G_MININT, G_MAXINT);
......@@ -150,6 +172,17 @@ gimp_display_shell_untransform_xy (const GimpDisplayShell *shell,
g_return_if_fail (nx != NULL);
g_return_if_fail (ny != NULL);
if (shell->rotate_untransform)
{
gdouble fx = x;
gdouble fy = y;
cairo_matrix_transform_point (shell->rotate_untransform, &fy, &fy);
x = fx;
y = fy;
}
if (round)
{