Gimp/app/core/gimpimage-snap.c
Jehan fa19271859 app: do not snap to custom guides.
I am still not sure whether custom guides should follow snapping rules.
Yet I could easily imagine you could want some normal guides with
snapping and in the same time symmetry without snapping to axis of
symmetry. So for the time being, let's disable snapping to custom guides
all the time and see if logic could be improved later.
2016-02-11 17:35:21 +01:00

725 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 <http://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;
gdouble i;
gimp_grid_get_spacing (grid, &xspacing, NULL);
gimp_grid_get_offset (grid, &xoffset, NULL);
/* the snap-to-grid part could probably be rewritten */
while (xoffset > xspacing)
xoffset -= xspacing;
for (i = xoffset; i <= gimp_image_get_width (image); i += xspacing)
{
if (i < 0)
continue;
snapped |= gimp_image_snap_distance (x, i,
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;
gdouble i;
gimp_grid_get_spacing (grid, NULL, &yspacing);
gimp_grid_get_offset (grid, NULL, &yoffset);
while (yoffset > yspacing)
yoffset -= yspacing;
for (i = yoffset; i <= gimp_image_get_height (image); i += yspacing)
{
if (i < 0)
continue;
snapped |= gimp_image_snap_distance (y, i,
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)
{
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 (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x) ||
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;
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;
gdouble i;
gimp_grid_get_spacing (grid, &xspacing, &yspacing);
gimp_grid_get_offset (grid, &xoffset, &yoffset);
while (xoffset > xspacing)
xoffset -= xspacing;
while (yoffset > yspacing)
yoffset -= yspacing;
for (i = xoffset; i <= gimp_image_get_width (image); i += xspacing)
{
if (i < 0)
continue;
snapped |= gimp_image_snap_distance (x, i,
epsilon_x,
&mindist_x, tx);
}
for (i = yoffset; i <= gimp_image_get_height (image); i += yspacing)
{
if (i < 0)
continue;
snapped |= gimp_image_snap_distance (y, i,
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;
}