1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpcanvasitem-utils.c
5  * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <gegl.h>
24 #include <gtk/gtk.h>
25 
26 #include "libgimpmath/gimpmath.h"
27 
28 #include "display-types.h"
29 
30 #include "core/gimpimage.h"
31 
32 #include "vectors/gimpanchor.h"
33 #include "vectors/gimpbezierstroke.h"
34 #include "vectors/gimpvectors.h"
35 
36 #include "gimpcanvasitem.h"
37 #include "gimpcanvasitem-utils.h"
38 #include "gimpdisplay.h"
39 #include "gimpdisplayshell.h"
40 #include "gimpdisplayshell-transform.h"
41 
42 
43 gboolean
gimp_canvas_item_on_handle(GimpCanvasItem * item,gdouble x,gdouble y,GimpHandleType type,gdouble handle_x,gdouble handle_y,gint width,gint height,GimpHandleAnchor anchor)44 gimp_canvas_item_on_handle (GimpCanvasItem  *item,
45                             gdouble           x,
46                             gdouble           y,
47                             GimpHandleType    type,
48                             gdouble           handle_x,
49                             gdouble           handle_y,
50                             gint              width,
51                             gint              height,
52                             GimpHandleAnchor  anchor)
53 {
54   GimpDisplayShell *shell;
55   gdouble           tx, ty;
56   gdouble           handle_tx, handle_ty;
57 
58   g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
59 
60   shell = gimp_canvas_item_get_shell (item);
61 
62   gimp_display_shell_zoom_xy_f (shell,
63                                 x, y,
64                                 &tx, &ty);
65   gimp_display_shell_zoom_xy_f (shell,
66                                 handle_x, handle_y,
67                                 &handle_tx, &handle_ty);
68 
69   switch (type)
70     {
71     case GIMP_HANDLE_SQUARE:
72     case GIMP_HANDLE_FILLED_SQUARE:
73     case GIMP_HANDLE_CROSS:
74     case GIMP_HANDLE_CROSSHAIR:
75       gimp_canvas_item_shift_to_north_west (anchor,
76                                             handle_tx, handle_ty,
77                                             width, height,
78                                             &handle_tx, &handle_ty);
79 
80       return (tx == CLAMP (tx, handle_tx, handle_tx + width) &&
81               ty == CLAMP (ty, handle_ty, handle_ty + height));
82 
83     case GIMP_HANDLE_CIRCLE:
84     case GIMP_HANDLE_FILLED_CIRCLE:
85       gimp_canvas_item_shift_to_center (anchor,
86                                         handle_tx, handle_ty,
87                                         width, height,
88                                         &handle_tx, &handle_ty);
89 
90       /* FIXME */
91       if (width != height)
92         width = (width + height) / 2;
93 
94       width /= 2;
95 
96       return ((SQR (handle_tx - tx) + SQR (handle_ty - ty)) < SQR (width));
97 
98     default:
99       g_warning ("%s: invalid handle type %d", G_STRFUNC, type);
100       break;
101     }
102 
103   return FALSE;
104 }
105 
106 gboolean
gimp_canvas_item_on_vectors_handle(GimpCanvasItem * item,GimpVectors * vectors,const GimpCoords * coord,gint width,gint height,GimpAnchorType preferred,gboolean exclusive,GimpAnchor ** ret_anchor,GimpStroke ** ret_stroke)107 gimp_canvas_item_on_vectors_handle (GimpCanvasItem    *item,
108                                     GimpVectors       *vectors,
109                                     const GimpCoords  *coord,
110                                     gint               width,
111                                     gint               height,
112                                     GimpAnchorType     preferred,
113                                     gboolean           exclusive,
114                                     GimpAnchor       **ret_anchor,
115                                     GimpStroke       **ret_stroke)
116 {
117   GimpStroke *stroke       = NULL;
118   GimpStroke *pref_stroke  = NULL;
119   GimpAnchor *anchor       = NULL;
120   GimpAnchor *pref_anchor  = NULL;
121   gdouble     dx, dy;
122   gdouble     pref_mindist = -1;
123   gdouble     mindist      = -1;
124 
125   g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
126   g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
127   g_return_val_if_fail (coord != NULL, FALSE);
128 
129   if (ret_anchor) *ret_anchor = NULL;
130   if (ret_stroke) *ret_stroke = NULL;
131 
132   while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
133     {
134       GList *anchor_list;
135       GList *list;
136 
137       anchor_list = g_list_concat (gimp_stroke_get_draw_anchors (stroke),
138                                    gimp_stroke_get_draw_controls (stroke));
139 
140       for (list = anchor_list; list; list = g_list_next (list))
141         {
142           dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
143           dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
144 
145           if (mindist < 0 || mindist > dx * dx + dy * dy)
146             {
147               mindist = dx * dx + dy * dy;
148               anchor = GIMP_ANCHOR (list->data);
149 
150               if (ret_stroke)
151                 *ret_stroke = stroke;
152             }
153 
154           if ((pref_mindist < 0 || pref_mindist > dx * dx + dy * dy) &&
155               GIMP_ANCHOR (list->data)->type == preferred)
156             {
157               pref_mindist = dx * dx + dy * dy;
158               pref_anchor = GIMP_ANCHOR (list->data);
159               pref_stroke = stroke;
160             }
161         }
162 
163       g_list_free (anchor_list);
164     }
165 
166   /* If the data passed into ret_anchor is a preferred anchor, return it. */
167   if (ret_anchor && *ret_anchor &&
168       gimp_canvas_item_on_handle (item,
169                                   coord->x,
170                                   coord->y,
171                                   GIMP_HANDLE_CIRCLE,
172                                   (*ret_anchor)->position.x,
173                                   (*ret_anchor)->position.y,
174                                   width, height,
175                                   GIMP_HANDLE_ANCHOR_CENTER) &&
176       (*ret_anchor)->type == preferred)
177     {
178       if (ret_stroke) *ret_stroke = pref_stroke;
179 
180       return TRUE;
181     }
182 
183   if (pref_anchor && gimp_canvas_item_on_handle (item,
184                                                  coord->x,
185                                                  coord->y,
186                                                  GIMP_HANDLE_CIRCLE,
187                                                  pref_anchor->position.x,
188                                                  pref_anchor->position.y,
189                                                  width, height,
190                                                  GIMP_HANDLE_ANCHOR_CENTER))
191     {
192       if (ret_anchor) *ret_anchor = pref_anchor;
193       if (ret_stroke) *ret_stroke = pref_stroke;
194 
195       return TRUE;
196     }
197   else if (!exclusive && anchor &&
198            gimp_canvas_item_on_handle (item,
199                                        coord->x,
200                                        coord->y,
201                                        GIMP_HANDLE_CIRCLE,
202                                        anchor->position.x,
203                                        anchor->position.y,
204                                        width, height,
205                                        GIMP_HANDLE_ANCHOR_CENTER))
206     {
207       if (ret_anchor)
208         *ret_anchor = anchor;
209 
210       /* *ret_stroke already set correctly. */
211       return TRUE;
212     }
213 
214   if (ret_anchor)
215     *ret_anchor = NULL;
216   if (ret_stroke)
217     *ret_stroke = NULL;
218 
219   return FALSE;
220 }
221 
222 gboolean
gimp_canvas_item_on_vectors_curve(GimpCanvasItem * item,GimpVectors * vectors,const GimpCoords * coord,gint width,gint height,GimpCoords * ret_coords,gdouble * ret_pos,GimpAnchor ** ret_segment_start,GimpAnchor ** ret_segment_end,GimpStroke ** ret_stroke)223 gimp_canvas_item_on_vectors_curve (GimpCanvasItem    *item,
224                                    GimpVectors       *vectors,
225                                    const GimpCoords  *coord,
226                                    gint               width,
227                                    gint               height,
228                                    GimpCoords        *ret_coords,
229                                    gdouble           *ret_pos,
230                                    GimpAnchor       **ret_segment_start,
231                                    GimpAnchor       **ret_segment_end,
232                                    GimpStroke       **ret_stroke)
233 {
234   GimpStroke *stroke = NULL;
235   GimpAnchor *segment_start;
236   GimpAnchor *segment_end;
237   GimpCoords  min_coords = GIMP_COORDS_DEFAULT_VALUES;
238   GimpCoords  cur_coords;
239   gdouble     min_dist, cur_dist, cur_pos;
240 
241   g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
242   g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
243   g_return_val_if_fail (coord != NULL, FALSE);
244 
245   if (ret_coords)        *ret_coords        = *coord;
246   if (ret_pos)           *ret_pos           = -1.0;
247   if (ret_segment_start) *ret_segment_start = NULL;
248   if (ret_segment_end)   *ret_segment_end   = NULL;
249   if (ret_stroke)        *ret_stroke        = NULL;
250 
251   min_dist = -1.0;
252 
253   while ((stroke = gimp_vectors_stroke_get_next (vectors, stroke)))
254     {
255       cur_dist = gimp_stroke_nearest_point_get (stroke, coord, 1.0,
256                                                 &cur_coords,
257                                                 &segment_start,
258                                                 &segment_end,
259                                                 &cur_pos);
260 
261       if (cur_dist >= 0 && (min_dist < 0 || cur_dist < min_dist))
262         {
263           min_dist   = cur_dist;
264           min_coords = cur_coords;
265 
266           if (ret_coords)        *ret_coords        = cur_coords;
267           if (ret_pos)           *ret_pos           = cur_pos;
268           if (ret_segment_start) *ret_segment_start = segment_start;
269           if (ret_segment_end)   *ret_segment_end   = segment_end;
270           if (ret_stroke)        *ret_stroke        = stroke;
271         }
272     }
273 
274   if (min_dist >= 0 &&
275       gimp_canvas_item_on_handle (item,
276                                   coord->x,
277                                   coord->y,
278                                   GIMP_HANDLE_CIRCLE,
279                                   min_coords.x,
280                                   min_coords.y,
281                                   width, height,
282                                   GIMP_HANDLE_ANCHOR_CENTER))
283     {
284       return TRUE;
285     }
286 
287   return FALSE;
288 }
289 
290 gboolean
gimp_canvas_item_on_vectors(GimpCanvasItem * item,const GimpCoords * coords,gint width,gint height,GimpCoords * ret_coords,gdouble * ret_pos,GimpAnchor ** ret_segment_start,GimpAnchor ** ret_segment_end,GimpStroke ** ret_stroke,GimpVectors ** ret_vectors)291 gimp_canvas_item_on_vectors (GimpCanvasItem    *item,
292                              const GimpCoords  *coords,
293                              gint               width,
294                              gint               height,
295                              GimpCoords        *ret_coords,
296                              gdouble           *ret_pos,
297                              GimpAnchor       **ret_segment_start,
298                              GimpAnchor       **ret_segment_end,
299                              GimpStroke       **ret_stroke,
300                              GimpVectors      **ret_vectors)
301 {
302   GimpDisplayShell *shell;
303   GimpImage        *image;
304   GList            *all_vectors;
305   GList            *list;
306 
307   g_return_val_if_fail (GIMP_IS_CANVAS_ITEM (item), FALSE);
308   g_return_val_if_fail (coords != NULL, FALSE);
309 
310   shell = gimp_canvas_item_get_shell (item);
311   image = gimp_display_get_image (shell->display);
312 
313   if (ret_coords)        *ret_coords         = *coords;
314   if (ret_pos)           *ret_pos            = -1.0;
315   if (ret_segment_start) *ret_segment_start  = NULL;
316   if (ret_segment_end)   *ret_segment_end    = NULL;
317   if (ret_stroke)        *ret_stroke         = NULL;
318   if (ret_vectors)       *ret_vectors        = NULL;
319 
320   all_vectors = gimp_image_get_vectors_list (image);
321 
322   for (list = all_vectors; list; list = g_list_next (list))
323     {
324       GimpVectors *vectors = list->data;
325 
326       if (! gimp_item_get_visible (GIMP_ITEM (vectors)))
327         continue;
328 
329       if (gimp_canvas_item_on_vectors_curve (item,
330                                              vectors, coords,
331                                              width, height,
332                                              ret_coords,
333                                              ret_pos,
334                                              ret_segment_start,
335                                              ret_segment_end,
336                                              ret_stroke))
337         {
338           if (ret_vectors)
339             *ret_vectors = vectors;
340 
341           g_list_free (all_vectors);
342 
343           return TRUE;
344         }
345     }
346 
347   g_list_free (all_vectors);
348 
349   return FALSE;
350 }
351 
352 void
gimp_canvas_item_shift_to_north_west(GimpHandleAnchor anchor,gdouble x,gdouble y,gint width,gint height,gdouble * shifted_x,gdouble * shifted_y)353 gimp_canvas_item_shift_to_north_west (GimpHandleAnchor  anchor,
354                                       gdouble           x,
355                                       gdouble           y,
356                                       gint              width,
357                                       gint              height,
358                                       gdouble          *shifted_x,
359                                       gdouble          *shifted_y)
360 {
361   switch (anchor)
362     {
363     case GIMP_HANDLE_ANCHOR_CENTER:
364       x -= width  / 2;
365       y -= height / 2;
366       break;
367 
368     case GIMP_HANDLE_ANCHOR_NORTH:
369       x -= width / 2;
370       break;
371 
372     case GIMP_HANDLE_ANCHOR_NORTH_WEST:
373       /*  nothing, this is the default  */
374       break;
375 
376     case GIMP_HANDLE_ANCHOR_NORTH_EAST:
377       x -= width;
378       break;
379 
380     case GIMP_HANDLE_ANCHOR_SOUTH:
381       x -= width / 2;
382       y -= height;
383       break;
384 
385     case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
386       y -= height;
387       break;
388 
389     case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
390       x -= width;
391       y -= height;
392       break;
393 
394     case GIMP_HANDLE_ANCHOR_WEST:
395       y -= height / 2;
396       break;
397 
398     case GIMP_HANDLE_ANCHOR_EAST:
399       x -= width;
400       y -= height / 2;
401       break;
402 
403     default:
404       break;
405     }
406 
407   if (shifted_x)
408     *shifted_x = x;
409 
410   if (shifted_y)
411     *shifted_y = y;
412 }
413 
414 void
gimp_canvas_item_shift_to_center(GimpHandleAnchor anchor,gdouble x,gdouble y,gint width,gint height,gdouble * shifted_x,gdouble * shifted_y)415 gimp_canvas_item_shift_to_center (GimpHandleAnchor  anchor,
416                                   gdouble           x,
417                                   gdouble           y,
418                                   gint              width,
419                                   gint              height,
420                                   gdouble          *shifted_x,
421                                   gdouble          *shifted_y)
422 {
423   switch (anchor)
424     {
425     case GIMP_HANDLE_ANCHOR_CENTER:
426       /*  nothing, this is the default  */
427       break;
428 
429     case GIMP_HANDLE_ANCHOR_NORTH:
430       y += height / 2;
431       break;
432 
433     case GIMP_HANDLE_ANCHOR_NORTH_WEST:
434       x += width  / 2;
435       y += height / 2;
436       break;
437 
438     case GIMP_HANDLE_ANCHOR_NORTH_EAST:
439       x -= width  / 2;
440       y += height / 2;
441       break;
442 
443     case GIMP_HANDLE_ANCHOR_SOUTH:
444       y -= height / 2;
445       break;
446 
447     case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
448       x += width  / 2;
449       y -= height / 2;
450       break;
451 
452     case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
453       x -= width  / 2;
454       y -= height / 2;
455       break;
456 
457     case GIMP_HANDLE_ANCHOR_WEST:
458       x += width / 2;
459       break;
460 
461     case GIMP_HANDLE_ANCHOR_EAST:
462       x -= width / 2;
463       break;
464 
465     default:
466       break;
467     }
468 
469   if (shifted_x)
470     *shifted_x = x;
471 
472   if (shifted_y)
473     *shifted_y = y;
474 }
475