1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimpcanvasboundary.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 "libgimpbase/gimpbase.h"
27 #include "libgimpmath/gimpmath.h"
28 
29 #include "display-types.h"
30 
31 #include "core/gimp-cairo.h"
32 #include "core/gimp-transform-utils.h"
33 #include "core/gimpboundary.h"
34 #include "core/gimpparamspecs.h"
35 
36 #include "gimpcanvasboundary.h"
37 #include "gimpdisplayshell.h"
38 
39 
40 enum
41 {
42   PROP_0,
43   PROP_SEGS,
44   PROP_TRANSFORM,
45   PROP_OFFSET_X,
46   PROP_OFFSET_Y
47 };
48 
49 
50 typedef struct _GimpCanvasBoundaryPrivate GimpCanvasBoundaryPrivate;
51 
52 struct _GimpCanvasBoundaryPrivate
53 {
54   GimpBoundSeg *segs;
55   gint          n_segs;
56   GimpMatrix3  *transform;
57   gdouble       offset_x;
58   gdouble       offset_y;
59 };
60 
61 #define GET_PRIVATE(boundary) \
62         ((GimpCanvasBoundaryPrivate *) gimp_canvas_boundary_get_instance_private ((GimpCanvasBoundary *) (boundary)))
63 
64 
65 /*  local function prototypes  */
66 
67 static void             gimp_canvas_boundary_finalize     (GObject        *object);
68 static void             gimp_canvas_boundary_set_property (GObject        *object,
69                                                            guint           property_id,
70                                                            const GValue   *value,
71                                                            GParamSpec     *pspec);
72 static void             gimp_canvas_boundary_get_property (GObject        *object,
73                                                            guint           property_id,
74                                                            GValue         *value,
75                                                            GParamSpec     *pspec);
76 static void             gimp_canvas_boundary_draw         (GimpCanvasItem *item,
77                                                            cairo_t        *cr);
78 static cairo_region_t * gimp_canvas_boundary_get_extents  (GimpCanvasItem *item);
79 
80 
G_DEFINE_TYPE_WITH_PRIVATE(GimpCanvasBoundary,gimp_canvas_boundary,GIMP_TYPE_CANVAS_ITEM)81 G_DEFINE_TYPE_WITH_PRIVATE (GimpCanvasBoundary, gimp_canvas_boundary,
82                             GIMP_TYPE_CANVAS_ITEM)
83 
84 #define parent_class gimp_canvas_boundary_parent_class
85 
86 
87 static void
88 gimp_canvas_boundary_class_init (GimpCanvasBoundaryClass *klass)
89 {
90   GObjectClass        *object_class = G_OBJECT_CLASS (klass);
91   GimpCanvasItemClass *item_class   = GIMP_CANVAS_ITEM_CLASS (klass);
92 
93   object_class->finalize     = gimp_canvas_boundary_finalize;
94   object_class->set_property = gimp_canvas_boundary_set_property;
95   object_class->get_property = gimp_canvas_boundary_get_property;
96 
97   item_class->draw           = gimp_canvas_boundary_draw;
98   item_class->get_extents    = gimp_canvas_boundary_get_extents;
99 
100   g_object_class_install_property (object_class, PROP_SEGS,
101                                    gimp_param_spec_array ("segs", NULL, NULL,
102                                                           GIMP_PARAM_READWRITE));
103 
104   g_object_class_install_property (object_class, PROP_TRANSFORM,
105                                    g_param_spec_pointer ("transform", NULL, NULL,
106                                                          GIMP_PARAM_READWRITE));
107 
108   g_object_class_install_property (object_class, PROP_OFFSET_X,
109                                    g_param_spec_double ("offset-x", NULL, NULL,
110                                                         -GIMP_MAX_IMAGE_SIZE,
111                                                         GIMP_MAX_IMAGE_SIZE, 0,
112                                                         GIMP_PARAM_READWRITE));
113 
114   g_object_class_install_property (object_class, PROP_OFFSET_Y,
115                                    g_param_spec_double ("offset-y", NULL, NULL,
116                                                         -GIMP_MAX_IMAGE_SIZE,
117                                                         GIMP_MAX_IMAGE_SIZE, 0,
118                                                         GIMP_PARAM_READWRITE));
119 }
120 
121 static void
gimp_canvas_boundary_init(GimpCanvasBoundary * boundary)122 gimp_canvas_boundary_init (GimpCanvasBoundary *boundary)
123 {
124   gimp_canvas_item_set_line_cap (GIMP_CANVAS_ITEM (boundary),
125                                  CAIRO_LINE_CAP_SQUARE);
126 }
127 
128 static void
gimp_canvas_boundary_finalize(GObject * object)129 gimp_canvas_boundary_finalize (GObject *object)
130 {
131   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
132 
133   g_clear_pointer (&private->segs, g_free);
134   private->n_segs = 0;
135 
136   g_clear_pointer (&private->transform, g_free);
137 
138   G_OBJECT_CLASS (parent_class)->finalize (object);
139 }
140 
141 static void
gimp_canvas_boundary_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)142 gimp_canvas_boundary_set_property (GObject      *object,
143                                    guint         property_id,
144                                    const GValue *value,
145                                    GParamSpec   *pspec)
146 {
147   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
148 
149   switch (property_id)
150     {
151     case PROP_SEGS:
152       break;
153 
154     case PROP_TRANSFORM:
155       {
156         GimpMatrix3 *transform = g_value_get_pointer (value);
157         if (private->transform)
158           g_free (private->transform);
159         if (transform)
160           private->transform = g_memdup (transform, sizeof (GimpMatrix3));
161         else
162           private->transform = NULL;
163       }
164       break;
165 
166     case PROP_OFFSET_X:
167       private->offset_x = g_value_get_double (value);
168       break;
169     case PROP_OFFSET_Y:
170       private->offset_y = g_value_get_double (value);
171       break;
172 
173     default:
174       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
175       break;
176     }
177 }
178 
179 static void
gimp_canvas_boundary_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)180 gimp_canvas_boundary_get_property (GObject    *object,
181                                    guint       property_id,
182                                    GValue     *value,
183                                    GParamSpec *pspec)
184 {
185   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (object);
186 
187   switch (property_id)
188     {
189     case PROP_SEGS:
190       break;
191 
192     case PROP_TRANSFORM:
193       g_value_set_pointer (value, private->transform);
194       break;
195 
196     case PROP_OFFSET_X:
197       g_value_set_double (value, private->offset_x);
198       break;
199     case PROP_OFFSET_Y:
200       g_value_set_double (value, private->offset_y);
201       break;
202 
203     default:
204       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
205       break;
206     }
207 }
208 
209 static void
gimp_canvas_boundary_transform(GimpCanvasItem * item,GimpSegment * segs,gint * n_segs)210 gimp_canvas_boundary_transform (GimpCanvasItem *item,
211                                 GimpSegment    *segs,
212                                 gint           *n_segs)
213 {
214   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
215   gint                       i;
216 
217   if (private->transform)
218     {
219       gint n = 0;
220 
221       for (i = 0; i < private->n_segs; i++)
222         {
223           GimpVector2 vertices[2];
224           GimpVector2 t_vertices[2];
225           gint        n_t_vertices;
226 
227           vertices[0] = (GimpVector2) { private->segs[i].x1, private->segs[i].y1 };
228           vertices[1] = (GimpVector2) { private->segs[i].x2, private->segs[i].y2 };
229 
230           gimp_transform_polygon (private->transform, vertices, 2, FALSE,
231                                   t_vertices, &n_t_vertices);
232 
233           if (n_t_vertices == 2)
234             {
235               gimp_canvas_item_transform_xy (item,
236                                              t_vertices[0].x + private->offset_x,
237                                              t_vertices[0].y + private->offset_y,
238                                              &segs[n].x1, &segs[n].y1);
239               gimp_canvas_item_transform_xy (item,
240                                              t_vertices[1].x + private->offset_x,
241                                              t_vertices[1].y + private->offset_y,
242                                              &segs[n].x2, &segs[n].y2);
243 
244               n++;
245             }
246         }
247 
248       *n_segs = n;
249     }
250   else
251     {
252       for (i = 0; i < private->n_segs; i++)
253         {
254           gimp_canvas_item_transform_xy (item,
255                                          private->segs[i].x1 + private->offset_x,
256                                          private->segs[i].y1 + private->offset_y,
257                                          &segs[i].x1,
258                                          &segs[i].y1);
259           gimp_canvas_item_transform_xy (item,
260                                          private->segs[i].x2 + private->offset_x,
261                                          private->segs[i].y2 + private->offset_y,
262                                          &segs[i].x2,
263                                          &segs[i].y2);
264 
265           /*  If this segment is a closing segment && the segments lie inside
266            *  the region, OR if this is an opening segment and the segments
267            *  lie outside the region...
268            *  we need to transform it by one display pixel
269            */
270           if (! private->segs[i].open)
271             {
272               /*  If it is vertical  */
273               if (segs[i].x1 == segs[i].x2)
274                 {
275                   segs[i].x1 -= 1;
276                   segs[i].x2 -= 1;
277                 }
278               else
279                 {
280                   segs[i].y1 -= 1;
281                   segs[i].y2 -= 1;
282                 }
283             }
284         }
285 
286       *n_segs = private->n_segs;
287     }
288 }
289 
290 static void
gimp_canvas_boundary_draw(GimpCanvasItem * item,cairo_t * cr)291 gimp_canvas_boundary_draw (GimpCanvasItem *item,
292                            cairo_t        *cr)
293 {
294   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
295   GimpSegment               *segs;
296   gint                       n_segs;
297 
298   segs = g_new0 (GimpSegment, private->n_segs);
299 
300   gimp_canvas_boundary_transform (item, segs, &n_segs);
301 
302   gimp_cairo_segments (cr, segs, n_segs);
303 
304   _gimp_canvas_item_stroke (item, cr);
305 
306   g_free (segs);
307 }
308 
309 static cairo_region_t *
gimp_canvas_boundary_get_extents(GimpCanvasItem * item)310 gimp_canvas_boundary_get_extents (GimpCanvasItem *item)
311 {
312   GimpCanvasBoundaryPrivate *private = GET_PRIVATE (item);
313   cairo_rectangle_int_t      rectangle;
314   GimpSegment               *segs;
315   gint                       n_segs;
316   gint                       x1, y1, x2, y2;
317   gint                       i;
318 
319   segs = g_new0 (GimpSegment, private->n_segs);
320 
321   gimp_canvas_boundary_transform (item, segs, &n_segs);
322 
323   if (n_segs == 0)
324     {
325       g_free (segs);
326 
327       return cairo_region_create ();
328     }
329 
330   x1 = MIN (segs[0].x1, segs[0].x2);
331   y1 = MIN (segs[0].y1, segs[0].y2);
332   x2 = MAX (segs[0].x1, segs[0].x2);
333   y2 = MAX (segs[0].y1, segs[0].y2);
334 
335   for (i = 1; i < n_segs; i++)
336     {
337       gint x3 = MIN (segs[i].x1, segs[i].x2);
338       gint y3 = MIN (segs[i].y1, segs[i].y2);
339       gint x4 = MAX (segs[i].x1, segs[i].x2);
340       gint y4 = MAX (segs[i].y1, segs[i].y2);
341 
342       x1 = MIN (x1, x3);
343       y1 = MIN (y1, y3);
344       x2 = MAX (x2, x4);
345       y2 = MAX (y2, y4);
346     }
347 
348   g_free (segs);
349 
350   rectangle.x      = x1 - 2;
351   rectangle.y      = y1 - 2;
352   rectangle.width  = x2 - x1 + 4;
353   rectangle.height = y2 - y1 + 4;
354 
355   return cairo_region_create_rectangle (&rectangle);
356 }
357 
358 GimpCanvasItem *
gimp_canvas_boundary_new(GimpDisplayShell * shell,const GimpBoundSeg * segs,gint n_segs,GimpMatrix3 * transform,gdouble offset_x,gdouble offset_y)359 gimp_canvas_boundary_new (GimpDisplayShell   *shell,
360                           const GimpBoundSeg *segs,
361                           gint                n_segs,
362                           GimpMatrix3        *transform,
363                           gdouble             offset_x,
364                           gdouble             offset_y)
365 {
366   GimpCanvasItem            *item;
367   GimpCanvasBoundaryPrivate *private;
368 
369   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
370 
371   item = g_object_new (GIMP_TYPE_CANVAS_BOUNDARY,
372                        "shell",     shell,
373                        "transform", transform,
374                        "offset-x",  offset_x,
375                        "offset-y",  offset_y,
376                        NULL);
377   private = GET_PRIVATE (item);
378 
379   /* puke */
380   private->segs   = g_memdup (segs, n_segs * sizeof (GimpBoundSeg));
381   private->n_segs = n_segs;
382 
383   return item;
384 }
385