1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <gdk-pixbuf/gdk-pixbuf.h>
21 #include <gegl.h>
22
23 #include "libgimpmath/gimpmath.h"
24
25 #include "core-types.h"
26
27 #include "gimp.h"
28 #include "gimpgrid.h"
29 #include "gimpguide.h"
30 #include "gimpimage.h"
31 #include "gimpimage-grid.h"
32 #include "gimpimage-guides.h"
33 #include "gimpimage-snap.h"
34
35 #include "vectors/gimpstroke.h"
36 #include "vectors/gimpvectors.h"
37
38 #include "gimp-intl.h"
39
40
41 static gboolean gimp_image_snap_distance (const gdouble unsnapped,
42 const gdouble nearest,
43 const gdouble epsilon,
44 gdouble *mindist,
45 gdouble *target);
46
47
48
49 /* public functions */
50
51 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)52 gimp_image_snap_x (GimpImage *image,
53 gdouble x,
54 gdouble *tx,
55 gdouble epsilon_x,
56 gboolean snap_to_guides,
57 gboolean snap_to_grid,
58 gboolean snap_to_canvas)
59 {
60 gdouble mindist = G_MAXDOUBLE;
61 gboolean snapped = FALSE;
62
63 g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
64 g_return_val_if_fail (tx != NULL, FALSE);
65
66 *tx = x;
67
68 if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
69 if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
70
71 if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
72 return FALSE;
73
74 if (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x))
75 return FALSE;
76
77 if (snap_to_guides)
78 {
79 GList *list;
80
81 for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
82 {
83 GimpGuide *guide = list->data;
84 gint position = gimp_guide_get_position (guide);
85
86 if (gimp_guide_is_custom (guide))
87 continue;
88
89 if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_VERTICAL)
90 {
91 snapped |= gimp_image_snap_distance (x, position,
92 epsilon_x,
93 &mindist, tx);
94 }
95 }
96 }
97
98 if (snap_to_grid)
99 {
100 GimpGrid *grid = gimp_image_get_grid (image);
101 gdouble xspacing;
102 gdouble xoffset;
103
104 gimp_grid_get_spacing (grid, &xspacing, NULL);
105 gimp_grid_get_offset (grid, &xoffset, NULL);
106
107 if (xspacing > 0.0)
108 {
109 gdouble nearest;
110
111 nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
112
113 snapped |= gimp_image_snap_distance (x, nearest,
114 epsilon_x,
115 &mindist, tx);
116 }
117 }
118
119 if (snap_to_canvas)
120 {
121 snapped |= gimp_image_snap_distance (x, 0,
122 epsilon_x,
123 &mindist, tx);
124 snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
125 epsilon_x,
126 &mindist, tx);
127 }
128
129 return snapped;
130 }
131
132 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)133 gimp_image_snap_y (GimpImage *image,
134 gdouble y,
135 gdouble *ty,
136 gdouble epsilon_y,
137 gboolean snap_to_guides,
138 gboolean snap_to_grid,
139 gboolean snap_to_canvas)
140 {
141 gdouble mindist = G_MAXDOUBLE;
142 gboolean snapped = FALSE;
143
144 g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
145 g_return_val_if_fail (ty != NULL, FALSE);
146
147 *ty = y;
148
149 if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
150 if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
151
152 if (! (snap_to_guides || snap_to_grid || snap_to_canvas))
153 return FALSE;
154
155 if (y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y))
156 return FALSE;
157
158 if (snap_to_guides)
159 {
160 GList *list;
161
162 for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
163 {
164 GimpGuide *guide = list->data;
165 gint position = gimp_guide_get_position (guide);
166
167 if (gimp_guide_is_custom (guide))
168 continue;
169
170 if (gimp_guide_get_orientation (guide) == GIMP_ORIENTATION_HORIZONTAL)
171 {
172 snapped |= gimp_image_snap_distance (y, position,
173 epsilon_y,
174 &mindist, ty);
175 }
176 }
177 }
178
179 if (snap_to_grid)
180 {
181 GimpGrid *grid = gimp_image_get_grid (image);
182 gdouble yspacing;
183 gdouble yoffset;
184
185 gimp_grid_get_spacing (grid, NULL, &yspacing);
186 gimp_grid_get_offset (grid, NULL, &yoffset);
187
188 if (yspacing > 0.0)
189 {
190 gdouble nearest;
191
192 nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
193
194 snapped |= gimp_image_snap_distance (y, nearest,
195 epsilon_y,
196 &mindist, ty);
197 }
198 }
199
200 if (snap_to_canvas)
201 {
202 snapped |= gimp_image_snap_distance (y, 0,
203 epsilon_y,
204 &mindist, ty);
205 snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
206 epsilon_y,
207 &mindist, ty);
208 }
209
210 return snapped;
211 }
212
213 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)214 gimp_image_snap_point (GimpImage *image,
215 gdouble x,
216 gdouble y,
217 gdouble *tx,
218 gdouble *ty,
219 gdouble epsilon_x,
220 gdouble epsilon_y,
221 gboolean snap_to_guides,
222 gboolean snap_to_grid,
223 gboolean snap_to_canvas,
224 gboolean snap_to_vectors,
225 gboolean show_all)
226 {
227 gdouble mindist_x = G_MAXDOUBLE;
228 gdouble mindist_y = G_MAXDOUBLE;
229 gboolean snapped = FALSE;
230
231 g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
232 g_return_val_if_fail (tx != NULL, FALSE);
233 g_return_val_if_fail (ty != NULL, FALSE);
234
235 *tx = x;
236 *ty = y;
237
238 if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
239 if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
240 if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
241
242 if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
243 return FALSE;
244
245 if (! show_all &&
246 (x < -epsilon_x || x >= (gimp_image_get_width (image) + epsilon_x) ||
247 y < -epsilon_y || y >= (gimp_image_get_height (image) + epsilon_y)))
248 {
249 /* Off-canvas grid is invisible unless "show all" option is
250 * enabled. So let's not snap to the invisible grid.
251 */
252 snap_to_grid = FALSE;
253 snap_to_canvas = FALSE;
254 }
255
256 if (snap_to_guides)
257 {
258 GList *list;
259
260 for (list = gimp_image_get_guides (image); list; list = g_list_next (list))
261 {
262 GimpGuide *guide = list->data;
263 gint position = gimp_guide_get_position (guide);
264
265 if (gimp_guide_is_custom (guide))
266 continue;
267
268 switch (gimp_guide_get_orientation (guide))
269 {
270 case GIMP_ORIENTATION_HORIZONTAL:
271 snapped |= gimp_image_snap_distance (y, position,
272 epsilon_y,
273 &mindist_y, ty);
274 break;
275
276 case GIMP_ORIENTATION_VERTICAL:
277 snapped |= gimp_image_snap_distance (x, position,
278 epsilon_x,
279 &mindist_x, tx);
280 break;
281
282 default:
283 break;
284 }
285 }
286 }
287
288 if (snap_to_grid)
289 {
290 GimpGrid *grid = gimp_image_get_grid (image);
291 gdouble xspacing, yspacing;
292 gdouble xoffset, yoffset;
293
294 gimp_grid_get_spacing (grid, &xspacing, &yspacing);
295 gimp_grid_get_offset (grid, &xoffset, &yoffset);
296
297 if (xspacing > 0.0)
298 {
299 gdouble nearest;
300
301 nearest = xoffset + RINT ((x - xoffset) / xspacing) * xspacing;
302
303 snapped |= gimp_image_snap_distance (x, nearest,
304 epsilon_x,
305 &mindist_x, tx);
306 }
307
308 if (yspacing > 0.0)
309 {
310 gdouble nearest;
311
312 nearest = yoffset + RINT ((y - yoffset) / yspacing) * yspacing;
313
314 snapped |= gimp_image_snap_distance (y, nearest,
315 epsilon_y,
316 &mindist_y, ty);
317 }
318 }
319
320 if (snap_to_canvas)
321 {
322 snapped |= gimp_image_snap_distance (x, 0,
323 epsilon_x,
324 &mindist_x, tx);
325 snapped |= gimp_image_snap_distance (x, gimp_image_get_width (image),
326 epsilon_x,
327 &mindist_x, tx);
328
329 snapped |= gimp_image_snap_distance (y, 0,
330 epsilon_y,
331 &mindist_y, ty);
332 snapped |= gimp_image_snap_distance (y, gimp_image_get_height (image),
333 epsilon_y,
334 &mindist_y, ty);
335 }
336
337 if (snap_to_vectors)
338 {
339 GimpVectors *vectors = gimp_image_get_active_vectors (image);
340 GimpStroke *stroke = NULL;
341 GimpCoords coords = { 0, 0, 0, 0, 0 };
342
343 coords.x = x;
344 coords.y = y;
345
346 while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
347 {
348 GimpCoords nearest;
349
350 if (gimp_stroke_nearest_point_get (stroke, &coords, 1.0,
351 &nearest,
352 NULL, NULL, NULL) >= 0)
353 {
354 snapped |= gimp_image_snap_distance (x, nearest.x,
355 epsilon_x,
356 &mindist_x, tx);
357 snapped |= gimp_image_snap_distance (y, nearest.y,
358 epsilon_y,
359 &mindist_y, ty);
360 }
361 }
362 }
363
364 return snapped;
365 }
366
367 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)368 gimp_image_snap_rectangle (GimpImage *image,
369 gdouble x1,
370 gdouble y1,
371 gdouble x2,
372 gdouble y2,
373 gdouble *tx1,
374 gdouble *ty1,
375 gdouble epsilon_x,
376 gdouble epsilon_y,
377 gboolean snap_to_guides,
378 gboolean snap_to_grid,
379 gboolean snap_to_canvas,
380 gboolean snap_to_vectors)
381 {
382 gdouble nx, ny;
383 gdouble mindist_x = G_MAXDOUBLE;
384 gdouble mindist_y = G_MAXDOUBLE;
385 gdouble x_center = (x1 + x2) / 2.0;
386 gdouble y_center = (y1 + y2) / 2.0;
387 gboolean snapped = FALSE;
388
389 g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
390 g_return_val_if_fail (tx1 != NULL, FALSE);
391 g_return_val_if_fail (ty1 != NULL, FALSE);
392
393 *tx1 = x1;
394 *ty1 = y1;
395
396 if (! gimp_image_get_guides (image)) snap_to_guides = FALSE;
397 if (! gimp_image_get_grid (image)) snap_to_grid = FALSE;
398 if (! gimp_image_get_active_vectors (image)) snap_to_vectors = FALSE;
399
400 if (! (snap_to_guides || snap_to_grid || snap_to_canvas || snap_to_vectors))
401 return FALSE;
402
403 /* left edge */
404 if (gimp_image_snap_x (image, x1, &nx,
405 MIN (epsilon_x, mindist_x),
406 snap_to_guides,
407 snap_to_grid,
408 snap_to_canvas))
409 {
410 mindist_x = ABS (nx - x1);
411 *tx1 = nx;
412 snapped = TRUE;
413 }
414
415 /* right edge */
416 if (gimp_image_snap_x (image, x2, &nx,
417 MIN (epsilon_x, mindist_x),
418 snap_to_guides,
419 snap_to_grid,
420 snap_to_canvas))
421 {
422 mindist_x = ABS (nx - x2);
423 *tx1 = RINT (x1 + (nx - x2));
424 snapped = TRUE;
425 }
426
427 /* center, vertical */
428 if (gimp_image_snap_x (image, x_center, &nx,
429 MIN (epsilon_x, mindist_x),
430 snap_to_guides,
431 snap_to_grid,
432 snap_to_canvas))
433 {
434 mindist_x = ABS (nx - x_center);
435 *tx1 = RINT (x1 + (nx - x_center));
436 snapped = TRUE;
437 }
438
439 /* top edge */
440 if (gimp_image_snap_y (image, y1, &ny,
441 MIN (epsilon_y, mindist_y),
442 snap_to_guides,
443 snap_to_grid,
444 snap_to_canvas))
445 {
446 mindist_y = ABS (ny - y1);
447 *ty1 = ny;
448 snapped = TRUE;
449 }
450
451 /* bottom edge */
452 if (gimp_image_snap_y (image, y2, &ny,
453 MIN (epsilon_y, mindist_y),
454 snap_to_guides,
455 snap_to_grid,
456 snap_to_canvas))
457 {
458 mindist_y = ABS (ny - y2);
459 *ty1 = RINT (y1 + (ny - y2));
460 snapped = TRUE;
461 }
462
463 /* center, horizontal */
464 if (gimp_image_snap_y (image, y_center, &ny,
465 MIN (epsilon_y, mindist_y),
466 snap_to_guides,
467 snap_to_grid,
468 snap_to_canvas))
469 {
470 mindist_y = ABS (ny - y_center);
471 *ty1 = RINT (y1 + (ny - y_center));
472 snapped = TRUE;
473 }
474
475 if (snap_to_vectors)
476 {
477 GimpVectors *vectors = gimp_image_get_active_vectors (image);
478 GimpStroke *stroke = NULL;
479 GimpCoords coords1 = GIMP_COORDS_DEFAULT_VALUES;
480 GimpCoords coords2 = GIMP_COORDS_DEFAULT_VALUES;
481
482 while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
483 {
484 GimpCoords nearest;
485 gdouble dist;
486
487 /* top edge */
488
489 coords1.x = x1;
490 coords1.y = y1;
491 coords2.x = x2;
492 coords2.y = y1;
493
494 if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
495 1.0, &nearest,
496 NULL, NULL, NULL) >= 0)
497 {
498 snapped |= gimp_image_snap_distance (y1, nearest.y,
499 epsilon_y,
500 &mindist_y, ty1);
501 }
502
503 if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
504 1.0, &nearest,
505 NULL, NULL, NULL) >= 0)
506 {
507 snapped |= gimp_image_snap_distance (x1, nearest.x,
508 epsilon_x,
509 &mindist_x, tx1);
510 }
511
512 if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
513 1.0, &nearest,
514 NULL, NULL, NULL) >= 0)
515 {
516 dist = ABS (nearest.x - x2);
517
518 if (dist < MIN (epsilon_x, mindist_x))
519 {
520 mindist_x = dist;
521 *tx1 = RINT (x1 + (nearest.x - x2));
522 snapped = TRUE;
523 }
524 }
525
526 /* bottom edge */
527
528 coords1.x = x1;
529 coords1.y = y2;
530 coords2.x = x2;
531 coords2.y = y2;
532
533 if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
534 1.0, &nearest,
535 NULL, NULL, NULL) >= 0)
536 {
537 dist = ABS (nearest.y - y2);
538
539 if (dist < MIN (epsilon_y, mindist_y))
540 {
541 mindist_y = dist;
542 *ty1 = RINT (y1 + (nearest.y - y2));
543 snapped = TRUE;
544 }
545 }
546
547 if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
548 1.0, &nearest,
549 NULL, NULL, NULL) >= 0)
550 {
551 snapped |= gimp_image_snap_distance (x1, nearest.x,
552 epsilon_x,
553 &mindist_x, tx1);
554 }
555
556 if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
557 1.0, &nearest,
558 NULL, NULL, NULL) >= 0)
559 {
560 dist = ABS (nearest.x - x2);
561
562 if (dist < MIN (epsilon_x, mindist_x))
563 {
564 mindist_x = dist;
565 *tx1 = RINT (x1 + (nearest.x - x2));
566 snapped = TRUE;
567 }
568 }
569
570 /* left edge */
571
572 coords1.x = x1;
573 coords1.y = y1;
574 coords2.x = x1;
575 coords2.y = y2;
576
577 if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
578 1.0, &nearest,
579 NULL, NULL, NULL) >= 0)
580 {
581 snapped |= gimp_image_snap_distance (x1, nearest.x,
582 epsilon_x,
583 &mindist_x, tx1);
584 }
585
586 if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
587 1.0, &nearest,
588 NULL, NULL, NULL) >= 0)
589 {
590 snapped |= gimp_image_snap_distance (y1, nearest.y,
591 epsilon_y,
592 &mindist_y, ty1);
593 }
594
595 if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
596 1.0, &nearest,
597 NULL, NULL, NULL) >= 0)
598 {
599 dist = ABS (nearest.y - y2);
600
601 if (dist < MIN (epsilon_y, mindist_y))
602 {
603 mindist_y = dist;
604 *ty1 = RINT (y1 + (nearest.y - y2));
605 snapped = TRUE;
606 }
607 }
608
609 /* right edge */
610
611 coords1.x = x2;
612 coords1.y = y1;
613 coords2.x = x2;
614 coords2.y = y2;
615
616 if (gimp_stroke_nearest_tangent_get (stroke, &coords1, &coords2,
617 1.0, &nearest,
618 NULL, NULL, NULL) >= 0)
619 {
620 dist = ABS (nearest.x - x2);
621
622 if (dist < MIN (epsilon_x, mindist_x))
623 {
624 mindist_x = dist;
625 *tx1 = RINT (x1 + (nearest.x - x2));
626 snapped = TRUE;
627 }
628 }
629
630 if (gimp_stroke_nearest_intersection_get (stroke, &coords1, &coords2,
631 1.0, &nearest,
632 NULL, NULL, NULL) >= 0)
633 {
634 snapped |= gimp_image_snap_distance (y1, nearest.y,
635 epsilon_y,
636 &mindist_y, ty1);
637 }
638
639 if (gimp_stroke_nearest_intersection_get (stroke, &coords2, &coords1,
640 1.0, &nearest,
641 NULL, NULL, NULL) >= 0)
642 {
643 dist = ABS (nearest.y - y2);
644
645 if (dist < MIN (epsilon_y, mindist_y))
646 {
647 mindist_y = dist;
648 *ty1 = RINT (y1 + (nearest.y - y2));
649 snapped = TRUE;
650 }
651 }
652
653 /* center */
654
655 coords1.x = x_center;
656 coords1.y = y_center;
657
658 if (gimp_stroke_nearest_point_get (stroke, &coords1, 1.0,
659 &nearest,
660 NULL, NULL, NULL) >= 0)
661 {
662 if (gimp_image_snap_distance (x_center, nearest.x,
663 epsilon_x,
664 &mindist_x, &nx))
665 {
666 mindist_x = ABS (nx - x_center);
667 *tx1 = RINT (x1 + (nx - x_center));
668 snapped = TRUE;
669 }
670
671 if (gimp_image_snap_distance (y_center, nearest.y,
672 epsilon_y,
673 &mindist_y, &ny))
674 {
675 mindist_y = ABS (ny - y_center);
676 *ty1 = RINT (y1 + (ny - y_center));
677 snapped = TRUE;
678 }
679 }
680 }
681 }
682
683 return snapped;
684 }
685
686 /* private functions */
687
688 /**
689 * gimp_image_snap_distance:
690 * @unsnapped: One coordinate of the unsnapped position
691 * @nearest: One coordinate of a snapping position candidate
692 * @epsilon: The snapping threshold
693 * @mindist: The distance to the currently closest snapping target
694 * @target: The currently closest snapping target
695 *
696 * Finds out if snapping occurs from position to a snapping candidate
697 * and sets the target accordingly.
698 *
699 * Return value: %TRUE if snapping occurred, %FALSE otherwise
700 */
701 static gboolean
gimp_image_snap_distance(const gdouble unsnapped,const gdouble nearest,const gdouble epsilon,gdouble * mindist,gdouble * target)702 gimp_image_snap_distance (const gdouble unsnapped,
703 const gdouble nearest,
704 const gdouble epsilon,
705 gdouble *mindist,
706 gdouble *target)
707 {
708 const gdouble dist = ABS (nearest - unsnapped);
709
710 if (dist < MIN (epsilon, *mindist))
711 {
712 *mindist = dist;
713 *target = nearest;
714
715 return TRUE;
716 }
717
718 return FALSE;
719 }
720