diff options
Diffstat (limited to 'app/core/gimpimage-snap.c')
-rw-r--r-- | app/core/gimpimage-snap.c | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/app/core/gimpimage-snap.c b/app/core/gimpimage-snap.c new file mode 100644 index 0000000..e35b646 --- /dev/null +++ b/app/core/gimpimage-snap.c @@ -0,0 +1,719 @@ +/* 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; +} |