PIKApp/app/display/pikadisplayshell-rotate.c

259 lines
8.1 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <math.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libpikamath/pikamath.h"
#include "display-types.h"
#include "pikadisplay.h"
#include "pikadisplayshell.h"
#include "pikadisplayshell-expose.h"
#include "pikadisplayshell-render.h"
#include "pikadisplayshell-rotate.h"
#include "pikadisplayshell-scale.h"
#include "pikadisplayshell-scroll.h"
#include "pikadisplayshell-transform.h"
#define ANGLE_EPSILON 1e-3
/* local function prototypes */
static void pika_display_shell_save_viewport_center (PikaDisplayShell *shell,
gdouble *x,
gdouble *y);
static void pika_display_shell_restore_viewport_center (PikaDisplayShell *shell,
gdouble x,
gdouble y);
/* public functions */
void
pika_display_shell_flip (PikaDisplayShell *shell,
gboolean flip_horizontally,
gboolean flip_vertically)
{
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
flip_horizontally = flip_horizontally ? TRUE : FALSE;
flip_vertically = flip_vertically ? TRUE : FALSE;
if (flip_horizontally != shell->flip_horizontally ||
flip_vertically != shell->flip_vertically)
{
gdouble cx, cy;
/* Maintain the current center of the viewport. */
pika_display_shell_save_viewport_center (shell, &cx, &cy);
/* freeze the active tool */
pika_display_shell_pause (shell);
/* Adjust the rotation angle so that the image gets reflected across the
* horizontal, and/or vertical, axes in screen space, regardless of the
* current rotation.
*/
if (flip_horizontally == shell->flip_horizontally ||
flip_vertically == shell->flip_vertically)
{
if (shell->rotate_angle != 0.0)
shell->rotate_angle = 360.0 - shell->rotate_angle;
}
shell->flip_horizontally = flip_horizontally;
shell->flip_vertically = flip_vertically;
pika_display_shell_rotated (shell);
pika_display_shell_restore_viewport_center (shell, cx, cy);
pika_display_shell_expose_full (shell);
pika_display_shell_render_invalidate_full (shell);
/* re-enable the active tool */
pika_display_shell_resume (shell);
}
}
void
pika_display_shell_rotate (PikaDisplayShell *shell,
gdouble delta)
{
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
pika_display_shell_rotate_to (shell, shell->rotate_angle + delta);
}
void
pika_display_shell_rotate_to (PikaDisplayShell *shell,
gdouble value)
{
gdouble cx, cy;
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
/* Maintain the current center of the viewport. */
pika_display_shell_save_viewport_center (shell, &cx, &cy);
/* Make sure the angle is within the range [0, 360). */
value = fmod (value, 360.0);
if (value < 0.0)
value += 360.0;
shell->rotate_angle = value;
/* freeze the active tool */
pika_display_shell_pause (shell);
pika_display_shell_scroll_clamp_and_update (shell);
pika_display_shell_rotated (shell);
pika_display_shell_restore_viewport_center (shell, cx, cy);
pika_display_shell_expose_full (shell);
pika_display_shell_render_invalidate_full (shell);
/* re-enable the active tool */
pika_display_shell_resume (shell);
}
void
pika_display_shell_rotate_drag (PikaDisplayShell *shell,
gdouble last_x,
gdouble last_y,
gdouble cur_x,
gdouble cur_y,
gboolean constrain)
{
gdouble pivot_x, pivot_y;
gdouble src_x, src_y, src_angle;
gdouble dest_x, dest_y, dest_angle;
gdouble delta_angle;
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
/* Rotate the image around the center of the viewport. */
pivot_x = shell->disp_width / 2.0;
pivot_y = shell->disp_height / 2.0;
src_x = last_x - pivot_x;
src_y = last_y - pivot_y;
src_angle = atan2 (src_y, src_x);
dest_x = cur_x - pivot_x;
dest_y = cur_y - pivot_y;
dest_angle = atan2 (dest_y, dest_x);
delta_angle = dest_angle - src_angle;
shell->rotate_drag_angle += 180.0 * delta_angle / G_PI;
pika_display_shell_rotate_to (shell,
constrain ?
RINT (shell->rotate_drag_angle / 15.0) * 15.0 :
shell->rotate_drag_angle);
}
void
pika_display_shell_rotate_update_transform (PikaDisplayShell *shell)
{
g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));
g_clear_pointer (&shell->rotate_transform, g_free);
g_clear_pointer (&shell->rotate_untransform, g_free);
if (fabs (shell->rotate_angle) < ANGLE_EPSILON ||
fabs (360.0 - shell->rotate_angle) < ANGLE_EPSILON)
shell->rotate_angle = 0.0;
if ((shell->rotate_angle != 0.0 ||
shell->flip_horizontally ||
shell->flip_vertically) &&
pika_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);
pika_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);
if (shell->rotate_angle != 0.0)
cairo_matrix_rotate (shell->rotate_transform,
shell->rotate_angle / 180.0 * G_PI);
if (shell->flip_horizontally)
cairo_matrix_scale (shell->rotate_transform, -1.0, 1.0);
if (shell->flip_vertically)
cairo_matrix_scale (shell->rotate_transform, 1.0, -1.0);
cairo_matrix_translate (shell->rotate_transform, -cx, -cy);
*shell->rotate_untransform = *shell->rotate_transform;
cairo_matrix_invert (shell->rotate_untransform);
}
}
/* private functions */
static void
pika_display_shell_save_viewport_center (PikaDisplayShell *shell,
gdouble *x,
gdouble *y)
{
pika_display_shell_unrotate_xy_f (shell,
shell->disp_width / 2,
shell->disp_height / 2,
x, y);
}
static void
pika_display_shell_restore_viewport_center (PikaDisplayShell *shell,
gdouble x,
gdouble y)
{
pika_display_shell_rotate_xy_f (shell, x, y, &x, &y);
x += shell->offset_x - shell->disp_width / 2;
y += shell->offset_y - shell->disp_height / 2;
pika_display_shell_scroll_set_offset (shell, RINT (x), RINT (y));
}