Files
gimp/app/core/gimpimage-snap.c
Jehan 22db7695c8 Issue #288: Point snapping to guides does not work outside the canvas.
This commit also makes snap to grid and snap to vectors work off-canvas.
Since we now have off-canvas viewing, it just makes sense that snapping
would work there too.

Note that I disable snap to grid when "Show All" is OFF. I am actually
unsure this is right (as "Show All" is a view action, and we usually
don't change behavior based on view actions; for instance snap to guides
are not disabled if guides are hidden). Yet I noticed we do this in
various other features when off-canvas. We kind of use this view flag as
a switch for features working off-canvas (for instance, color picking
works off-canvas only when "Show All" is ON). So let's keep the same
logics for now at least.

Snap to guide or snap to vectors will always work though, because guides
and vectors are always visible off-canvas (even when "Show All" is OFF).
They always have been (visible, not snappable off-canvas; now they are
both).

(cherry picked from commit 82438728fb)
2021-02-13 13:11:27 +01:00

720 lines
23 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "gimp.h"
#include "gimpgrid.h"
#include "gimpguide.h"
#include "gimpimage.h"
#include "gimpimage-grid.h"
#include "gimpimage-guides.h"
#include "gimpimage-snap.h"
#include "vectors/gimpstroke.h"
#include "vectors/gimpvectors.h"
#include "gimp-intl.h"
static gboolean gimp_image_snap_distance (const gdouble unsnapped,
const gdouble nearest,
const gdouble epsilon,
gdouble *mindist,
gdouble *target);
/* public functions */
gboolean
gimp_image_snap_x (GimpImage *image,
gdouble x,
gdouble *tx,
gdouble epsilon_x,
gboolean snap_to_guides,
gboolean snap_to_grid,
gboolean snap_to_canvas)
{
gdouble mindist = G_MAXDOUBLE;
gboolean snapped = FALSE;
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
g_return_val_if_fail (tx != NULL, FALSE);
*tx = x;
if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
return FALSE;
if (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x))
return FALSE;
if (snap_to_guides)
{
GList *list;
for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
{
GimpGuide *guide = list->data;
gint position = gimp_guide_get_position (guide);
if (gimp_guide_is_custom (guide))
continue;
if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_VERTICAL)
{
snapped |= gimp_image_snap_distance (x, position,
epsilon_x,
&mindist, tx);
}
}
}
if (snap_to_grid)
{
GimpGrid *grid = gimp_image_get_grid (image);
gdouble xspacing;
gdouble xoffset;
gimp_grid_get_spacing (grid, &xspacing, NULL);
gimp_grid_get_offset (grid, &xoffset, NULL);
if (xspacing > 0.0)
{
gdouble nearest;
nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
snapped |= gimp_image_snap_distance (x, nearest,
epsilon_x,
&mindist, tx);
}
}
if (snap_to_canvas)
{
snapped |= gimp_image_snap_distance (x, 0,
epsilon_x,
&mindist, tx);
snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
epsilon_x,
&mindist, tx);
}
return snapped;
}
gboolean
gimp_image_snap_y (GimpImage *image,
gdouble y,
gdouble *ty,
gdouble epsilon_y,
gboolean snap_to_guides,
gboolean snap_to_grid,
gboolean snap_to_canvas)
{
gdouble mindist = G_MAXDOUBLE;
gboolean snapped = FALSE;
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
g_return_val_if_fail (ty != NULL, FALSE);
*ty = y;
if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
return FALSE;
if (y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y))
return FALSE;
if (snap_to_guides)
{
GList *list;
for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
{
GimpGuide *guide = list->data;
gint position = gimp_guide_get_position (guide);
if (gimp_guide_is_custom (guide))
continue;
if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_HORIZONTAL)
{
snapped |= gimp_image_snap_distance (y, position,
epsilon_y,
&mindist, ty);
}
}
}
if (snap_to_grid)
{
GimpGrid *grid = gimp_image_get_grid (image);
gdouble yspacing;
gdouble yoffset;
gimp_grid_get_spacing (grid, NULL, &yspacing);
gimp_grid_get_offset (grid, NULL, &yoffset);
if (yspacing > 0.0)
{
gdouble nearest;
nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
snapped |= gimp_image_snap_distance (y, nearest,
epsilon_y,
&mindist, ty);
}
}
if (snap_to_canvas)
{
snapped |= gimp_image_snap_distance (y, 0,
epsilon_y,
&mindist, ty);
snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
epsilon_y,
&mindist, ty);
}
return snapped;
}
gboolean
gimp_image_snap_point (GimpImage *image,
gdouble x,
gdouble y,
gdouble *tx,
gdouble *ty,
gdouble epsilon_x,
gdouble epsilon_y,
gboolean snap_to_guides,
gboolean snap_to_grid,
gboolean snap_to_canvas,
gboolean snap_to_vectors,
gboolean show_all)
{
gdouble mindist_x = G_MAXDOUBLE;
gdouble mindist_y = G_MAXDOUBLE;
gboolean snapped = FALSE;
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
g_return_val_if_fail (tx != NULL, FALSE);
g_return_val_if_fail (ty != NULL, FALSE);
*tx = x;
*ty = y;
if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
return FALSE;
if (! show_all &&
(x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x) ||
y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y)))
{
/* Off-canvas grid is invisible unless "show all" option is
* enabled. So let's not snap to the invisible grid.
*/
snap_to_grid = FALSE;
snap_to_canvas = FALSE;
}
if (snap_to_guides)
{
GList *list;
for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
{
GimpGuide *guide = list->data;
gint position = gimp_guide_get_position (guide);
if (gimp_guide_is_custom (guide))
continue;
switch (gimp_guide_get_orientation (guide))
{
case GIMP_ORIENTATION_HORIZONTAL:
snapped |= gimp_image_snap_distance (y, position,
epsilon_y,
&mindist_y, ty);
break;
case GIMP_ORIENTATION_VERTICAL:
snapped |= gimp_image_snap_distance (x, position,
epsilon_x,
&mindist_x, tx);
break;
default:
break;
}
}
}
if (snap_to_grid)
{
GimpGrid *grid = gimp_image_get_grid (image);
gdouble xspacing, yspacing;
gdouble xoffset, yoffset;
gimp_grid_get_spacing (grid, &xspacing, &yspacing);
gimp_grid_get_offset (grid, &xoffset, &yoffset);
if (xspacing > 0.0)
{
gdouble nearest;
nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
snapped |= gimp_image_snap_distance (x, nearest,
epsilon_x,
&mindist_x, tx);
}
if (yspacing > 0.0)
{
gdouble nearest;
nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
snapped |= gimp_image_snap_distance (y, nearest,
epsilon_y,
&mindist_y, ty);
}
}
if (snap_to_canvas)
{
snapped |= gimp_image_snap_distance (x, 0,
epsilon_x,
&mindist_x, tx);
snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
epsilon_x,
&mindist_x, tx);
snapped |= gimp_image_snap_distance (y, 0,
epsilon_y,
&mindist_y, ty);
snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
epsilon_y,
&mindist_y, ty);
}
if (snap_to_vectors)
{
GimpVectors *vectors = gimp_image_get_active_vectors (image);
GimpStroke *stroke = NULL;
GimpCoords coords = { 0, 0, 0, 0, 0 };
coords.x = x;
coords.y = y;
while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
{
GimpCoords nearest;
if (gimp_stroke_nearest_point_get (stroke, &coords, 1.0,
&nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (x, nearest.x,
epsilon_x,
&mindist_x, tx);
snapped |= gimp_image_snap_distance (y, nearest.y,
epsilon_y,
&mindist_y, ty);
}
}
}
return snapped;
}
gboolean
gimp_image_snap_rectangle (GimpImage *image,
gdouble x1,
gdouble y1,
gdouble x2,
gdouble y2,
gdouble *tx1,
gdouble *ty1,
gdouble epsilon_x,
gdouble epsilon_y,
gboolean snap_to_guides,
gboolean snap_to_grid,
gboolean snap_to_canvas,
gboolean snap_to_vectors)
{
gdouble nx, ny;
gdouble mindist_x = G_MAXDOUBLE;
gdouble mindist_y = G_MAXDOUBLE;
gdouble x_center = (x1 + x2) / 2.0;
gdouble y_center = (y1 + y2) / 2.0;
gboolean snapped = FALSE;
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
g_return_val_if_fail (tx1 != NULL, FALSE);
g_return_val_if_fail (ty1 != NULL, FALSE);
*tx1 = x1;
*ty1 = y1;
if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
return FALSE;
/* left edge */
if (gimp_image_snap_x (image, x1, &nx,
MIN (epsilon_x, mindist_x),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_x = ABS (nx - x1);
*tx1 = nx;
snapped = TRUE;
}
/* right edge */
if (gimp_image_snap_x (image, x2, &nx,
MIN (epsilon_x, mindist_x),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_x = ABS (nx - x2);
*tx1 = RINT (x1 + (nx - x2));
snapped = TRUE;
}
/* center, vertical */
if (gimp_image_snap_x (image, x_center, &nx,
MIN (epsilon_x, mindist_x),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_x = ABS (nx - x_center);
*tx1 = RINT (x1 + (nx - x_center));
snapped = TRUE;
}
/* top edge */
if (gimp_image_snap_y (image, y1, &ny,
MIN (epsilon_y, mindist_y),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_y = ABS (ny - y1);
*ty1 = ny;
snapped = TRUE;
}
/* bottom edge */
if (gimp_image_snap_y (image, y2, &ny,
MIN (epsilon_y, mindist_y),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_y = ABS (ny - y2);
*ty1 = RINT (y1 + (ny - y2));
snapped = TRUE;
}
/* center, horizontal */
if (gimp_image_snap_y (image, y_center, &ny,
MIN (epsilon_y, mindist_y),
snap_to_guides,
snap_to_grid,
snap_to_canvas))
{
mindist_y = ABS (ny - y_center);
*ty1 = RINT (y1 + (ny - y_center));
snapped = TRUE;
}
if (snap_to_vectors)
{
GimpVectors *vectors = gimp_image_get_active_vectors (image);
GimpStroke *stroke = NULL;
GimpCoords coords1 = GIMP_COORDS_DEFAULT_VALUES;
GimpCoords coords2 = GIMP_COORDS_DEFAULT_VALUES;
while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
{
GimpCoords nearest;
gdouble dist;
/* top edge */
coords1.x = x1;
coords1.y = y1;
coords2.x = x2;
coords2.y = y1;
if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (y1, nearest.y,
epsilon_y,
&mindist_y, ty1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (x1, nearest.x,
epsilon_x,
&mindist_x, tx1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.x - x2);
if (dist < MIN (epsilon_x, mindist_x))
{
mindist_x = dist;
*tx1 = RINT (x1 + (nearest.x - x2));
snapped = TRUE;
}
}
/* bottom edge */
coords1.x = x1;
coords1.y = y2;
coords2.x = x2;
coords2.y = y2;
if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.y - y2);
if (dist < MIN (epsilon_y, mindist_y))
{
mindist_y = dist;
*ty1 = RINT (y1 + (nearest.y - y2));
snapped = TRUE;
}
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (x1, nearest.x,
epsilon_x,
&mindist_x, tx1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.x - x2);
if (dist < MIN (epsilon_x, mindist_x))
{
mindist_x = dist;
*tx1 = RINT (x1 + (nearest.x - x2));
snapped = TRUE;
}
}
/* left edge */
coords1.x = x1;
coords1.y = y1;
coords2.x = x1;
coords2.y = y2;
if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (x1, nearest.x,
epsilon_x,
&mindist_x, tx1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (y1, nearest.y,
epsilon_y,
&mindist_y, ty1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.y - y2);
if (dist < MIN (epsilon_y, mindist_y))
{
mindist_y = dist;
*ty1 = RINT (y1 + (nearest.y - y2));
snapped = TRUE;
}
}
/* right edge */
coords1.x = x2;
coords1.y = y1;
coords2.x = x2;
coords2.y = y2;
if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.x - x2);
if (dist < MIN (epsilon_x, mindist_x))
{
mindist_x = dist;
*tx1 = RINT (x1 + (nearest.x - x2));
snapped = TRUE;
}
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
snapped |= gimp_image_snap_distance (y1, nearest.y,
epsilon_y,
&mindist_y, ty1);
}
if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
1.0, &nearest,
NULL, NULL, NULL) >= 0)
{
dist = ABS (nearest.y - y2);
if (dist < MIN (epsilon_y, mindist_y))
{
mindist_y = dist;
*ty1 = RINT (y1 + (nearest.y - y2));
snapped = TRUE;
}
}
/* center */
coords1.x = x_center;
coords1.y = y_center;
if (gimp_stroke_nearest_point_get (stroke, &coords1, 1.0,
&nearest,
NULL, NULL, NULL) >= 0)
{
if (gimp_image_snap_distance (x_center, nearest.x,
epsilon_x,
&mindist_x, &nx))
{
mindist_x = ABS (nx - x_center);
*tx1 = RINT (x1 + (nx - x_center));
snapped = TRUE;
}
if (gimp_image_snap_distance (y_center, nearest.y,
epsilon_y,
&mindist_y, &ny))
{
mindist_y = ABS (ny - y_center);
*ty1 = RINT (y1 + (ny - y_center));
snapped = TRUE;
}
}
}
}
return snapped;
}
/* private functions */
/**
* gimp_image_snap_distance:
* @unsnapped: One coordinate of the unsnapped position
* @nearest: One coordinate of a snapping position candidate
* @epsilon: The snapping threshold
* @mindist: The distance to the currently closest snapping target
* @target: The currently closest snapping target
*
* Finds out if snapping occurs from position to a snapping candidate
* and sets the target accordingly.
*
* Return value: %TRUE if snapping occurred, %FALSE otherwise
*/
static gboolean
gimp_image_snap_distance (const gdouble unsnapped,
const gdouble nearest,
const gdouble epsilon,
gdouble *mindist,
gdouble *target)
{
const gdouble dist = ABS (nearest - unsnapped);
if (dist < MIN (epsilon, *mindist))
{
*mindist = dist;
*target = nearest;
return TRUE;
}
return FALSE;
}