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 /* This tool is based on a paper from SIGGRAPH '95:
19  *  "Intelligent Scissors for Image Composition", Eric N. Mortensen and
20  *   William A. Barrett, Brigham Young University.
21  *
22  * thanks to Professor D. Forsyth for prompting us to implement this tool. */
23 
24 /* Personal note: Dr. Barrett, one of the authors of the paper written above
25  * is not only one of the most brilliant people I have ever met, he is an
26  * incredible professor who genuinely cares about his students and wants them
27  * to learn as much as they can about the topic.
28  *
29  * I didn't even notice I was taking a class from the person who wrote the
30  * paper until halfway through the semester.
31  *                                                   -- Rockwalrus
32  */
33 
34 /* The history of this implementation is lonog and varied.  It was
35  * originally done by Spencer and Peter, and worked fine in the 0.54
36  * (motif only) release of GIMP.  Later revisions (0.99.something
37  * until about 1.1.4) completely changed the algorithm used, until it
38  * bore little resemblance to the one described in the paper above.
39  * The 0.54 version of the algorithm was then forwards ported to 1.1.4
40  * by Austin Donnelly.
41  */
42 
43 /* Livewire boundary implementation done by Laramie Leavitt */
44 
45 #include "config.h"
46 
47 #include <stdlib.h>
48 
49 #include <gegl.h>
50 #include <gtk/gtk.h>
51 #include <gdk/gdkkeysyms.h>
52 
53 #include "libgimpmath/gimpmath.h"
54 #include "libgimpwidgets/gimpwidgets.h"
55 
56 #include "tools-types.h"
57 
58 #include "gegl/gimp-gegl-utils.h"
59 
60 #include "core/gimpchannel.h"
61 #include "core/gimpchannel-select.h"
62 #include "core/gimpimage.h"
63 #include "core/gimppickable.h"
64 #include "core/gimpscanconvert.h"
65 #include "core/gimptempbuf.h"
66 #include "core/gimptoolinfo.h"
67 
68 #include "widgets/gimphelp-ids.h"
69 #include "widgets/gimpwidgets-utils.h"
70 
71 #include "display/gimpcanvasitem.h"
72 #include "display/gimpdisplay.h"
73 
74 #include "gimpiscissorsoptions.h"
75 #include "gimpiscissorstool.h"
76 #include "gimptilehandleriscissors.h"
77 #include "gimptoolcontrol.h"
78 
79 #include "gimp-intl.h"
80 
81 
82 /*  defines  */
83 #define  GRADIENT_SEARCH   32  /* how far to look when snapping to an edge */
84 #define  EXTEND_BY         0.2 /* proportion to expand cost map by */
85 #define  FIXED             5   /* additional fixed size to expand cost map */
86 
87 #define  COST_WIDTH        2   /* number of bytes for each pixel in cost map  */
88 
89 /* weight to give between gradient (_G) and direction (_D) */
90 #define  OMEGA_D           0.2
91 #define  OMEGA_G           0.8
92 
93 /* sentinel to mark seed point in ?cost? map */
94 #define  SEED_POINT        9
95 
96 /*  Functional defines  */
97 #define  PIXEL_COST(x)     ((x) >> 8)
98 #define  PIXEL_DIR(x)      ((x) & 0x000000ff)
99 
100 
101 struct _ISegment
102 {
103   gint       x1, y1;
104   gint       x2, y2;
105   GPtrArray *points;
106 };
107 
108 struct _ICurve
109 {
110   GQueue   *segments;
111   gboolean  first_point;
112   gboolean  closed;
113 };
114 
115 
116 /*  local function prototypes  */
117 
118 static void          gimp_iscissors_tool_finalize       (GObject               *object);
119 
120 static void          gimp_iscissors_tool_control        (GimpTool              *tool,
121                                                          GimpToolAction         action,
122                                                          GimpDisplay           *display);
123 static void          gimp_iscissors_tool_button_press   (GimpTool              *tool,
124                                                          const GimpCoords      *coords,
125                                                          guint32                time,
126                                                          GdkModifierType        state,
127                                                          GimpButtonPressType    press_type,
128                                                          GimpDisplay           *display);
129 static void          gimp_iscissors_tool_button_release (GimpTool              *tool,
130                                                          const GimpCoords      *coords,
131                                                          guint32                time,
132                                                          GdkModifierType        state,
133                                                          GimpButtonReleaseType  release_type,
134                                                          GimpDisplay           *display);
135 static void          gimp_iscissors_tool_motion         (GimpTool              *tool,
136                                                          const GimpCoords      *coords,
137                                                          guint32                time,
138                                                          GdkModifierType        state,
139                                                          GimpDisplay           *display);
140 static void          gimp_iscissors_tool_oper_update    (GimpTool              *tool,
141                                                          const GimpCoords      *coords,
142                                                          GdkModifierType        state,
143                                                          gboolean               proximity,
144                                                          GimpDisplay           *display);
145 static void          gimp_iscissors_tool_cursor_update  (GimpTool              *tool,
146                                                          const GimpCoords      *coords,
147                                                          GdkModifierType        state,
148                                                          GimpDisplay           *display);
149 static gboolean      gimp_iscissors_tool_key_press      (GimpTool              *tool,
150                                                          GdkEventKey           *kevent,
151                                                          GimpDisplay           *display);
152 static const gchar * gimp_iscissors_tool_can_undo       (GimpTool              *tool,
153                                                          GimpDisplay           *display);
154 static const gchar * gimp_iscissors_tool_can_redo       (GimpTool              *tool,
155                                                          GimpDisplay           *display);
156 static gboolean      gimp_iscissors_tool_undo           (GimpTool              *tool,
157                                                          GimpDisplay           *display);
158 static gboolean      gimp_iscissors_tool_redo           (GimpTool              *tool,
159                                                          GimpDisplay           *display);
160 
161 static void          gimp_iscissors_tool_draw           (GimpDrawTool          *draw_tool);
162 
163 static void          gimp_iscissors_tool_push_undo      (GimpIscissorsTool     *iscissors);
164 static void          gimp_iscissors_tool_pop_undo       (GimpIscissorsTool     *iscissors);
165 static void          gimp_iscissors_tool_free_redo      (GimpIscissorsTool     *iscissors);
166 
167 static void          gimp_iscissors_tool_halt           (GimpIscissorsTool     *iscissors,
168                                                          GimpDisplay           *display);
169 static void          gimp_iscissors_tool_commit         (GimpIscissorsTool     *iscissors,
170                                                          GimpDisplay           *display);
171 
172 static void          iscissors_convert         (GimpIscissorsTool *iscissors,
173                                                 GimpDisplay       *display);
174 static GeglBuffer  * gradient_map_new          (GimpPickable      *pickable);
175 
176 static void          find_optimal_path         (GeglBuffer        *gradient_map,
177                                                 GimpTempBuf       *dp_buf,
178                                                 gint               x1,
179                                                 gint               y1,
180                                                 gint               x2,
181                                                 gint               y2,
182                                                 gint               xs,
183                                                 gint               ys);
184 static void          find_max_gradient         (GimpIscissorsTool *iscissors,
185                                                 GimpPickable      *pickable,
186                                                 gint              *x,
187                                                 gint              *y);
188 static void          calculate_segment         (GimpIscissorsTool *iscissors,
189                                                 ISegment          *segment);
190 static GimpCanvasItem * iscissors_draw_segment (GimpDrawTool      *draw_tool,
191                                                 ISegment          *segment);
192 
193 static gint          mouse_over_vertex         (GimpIscissorsTool *iscissors,
194                                                 gdouble            x,
195                                                 gdouble            y);
196 static gboolean      clicked_on_vertex         (GimpIscissorsTool *iscissors,
197                                                 gdouble            x,
198                                                 gdouble            y);
199 static GList       * mouse_over_segment        (GimpIscissorsTool *iscissors,
200                                                 gdouble            x,
201                                                 gdouble            y);
202 static gboolean      clicked_on_segment        (GimpIscissorsTool *iscissors,
203                                                 gdouble            x,
204                                                 gdouble            y);
205 
206 static GPtrArray   * plot_pixels               (GimpTempBuf       *dp_buf,
207                                                 gint               x1,
208                                                 gint               y1,
209                                                 gint               xs,
210                                                 gint               ys,
211                                                 gint               xe,
212                                                 gint               ye);
213 
214 static ISegment    * isegment_new              (gint               x1,
215                                                 gint               y1,
216                                                 gint               x2,
217                                                 gint               y2);
218 static ISegment    * isegment_copy             (ISegment          *segment);
219 static void          isegment_free             (ISegment          *segment);
220 
221 static ICurve      * icurve_new                (void);
222 static ICurve      * icurve_copy               (ICurve            *curve);
223 static void          icurve_clear              (ICurve            *curve);
224 static void          icurve_free               (ICurve            *curve);
225 
226 static ISegment    * icurve_append_segment     (ICurve            *curve,
227                                                 gint               x1,
228                                                 gint               y1,
229                                                 gint               x2,
230                                                 gint               y2);
231 static ISegment    * icurve_insert_segment     (ICurve            *curve,
232                                                 GList             *sibling,
233                                                 gint               x1,
234                                                 gint               y1,
235                                                 gint               x2,
236                                                 gint               y2);
237 static void          icurve_delete_segment     (ICurve            *curve,
238                                                 ISegment          *segment);
239 
240 static void          icurve_close              (ICurve            *curve);
241 
242 static GimpScanConvert *
243                     icurve_create_scan_convert (ICurve            *curve);
244 
245 
246 /*  static variables  */
247 
248 /*  where to move on a given link direction  */
249 static const gint move[8][2] =
250 {
251   {  1,  0 },
252   {  0,  1 },
253   { -1,  1 },
254   {  1,  1 },
255   { -1,  0 },
256   {  0, -1 },
257   {  1, -1 },
258   { -1, -1 },
259 };
260 
261 /* IE:
262  * '---+---+---`
263  * | 7 | 5 | 6 |
264  * +---+---+---+
265  * | 4 |   | 0 |
266  * +---+---+---+
267  * | 2 | 1 | 3 |
268  * `---+---+---'
269  */
270 
271 static gfloat  distance_weights[GRADIENT_SEARCH * GRADIENT_SEARCH];
272 
273 static gint    diagonal_weight[256];
274 static gint    direction_value[256][4];
275 
276 
G_DEFINE_TYPE(GimpIscissorsTool,gimp_iscissors_tool,GIMP_TYPE_SELECTION_TOOL)277 G_DEFINE_TYPE (GimpIscissorsTool, gimp_iscissors_tool,
278                GIMP_TYPE_SELECTION_TOOL)
279 
280 #define parent_class gimp_iscissors_tool_parent_class
281 
282 
283 void
284 gimp_iscissors_tool_register (GimpToolRegisterCallback  callback,
285                               gpointer                  data)
286 {
287   (* callback) (GIMP_TYPE_ISCISSORS_TOOL,
288                 GIMP_TYPE_ISCISSORS_OPTIONS,
289                 gimp_iscissors_options_gui,
290                 0,
291                 "gimp-iscissors-tool",
292                 _("Scissors Select"),
293                 _("Scissors Select Tool: Select shapes using intelligent edge-fitting"),
294                 N_("Intelligent _Scissors"),
295                 "I",
296                 NULL, GIMP_HELP_TOOL_ISCISSORS,
297                 GIMP_ICON_TOOL_ISCISSORS,
298                 data);
299 }
300 
301 static void
gimp_iscissors_tool_class_init(GimpIscissorsToolClass * klass)302 gimp_iscissors_tool_class_init (GimpIscissorsToolClass *klass)
303 {
304   GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
305   GimpToolClass     *tool_class      = GIMP_TOOL_CLASS (klass);
306   GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
307   gint               i, j;
308   gint               radius;
309 
310   object_class->finalize     = gimp_iscissors_tool_finalize;
311 
312   tool_class->control        = gimp_iscissors_tool_control;
313   tool_class->button_press   = gimp_iscissors_tool_button_press;
314   tool_class->button_release = gimp_iscissors_tool_button_release;
315   tool_class->motion         = gimp_iscissors_tool_motion;
316   tool_class->key_press      = gimp_iscissors_tool_key_press;
317   tool_class->oper_update    = gimp_iscissors_tool_oper_update;
318   tool_class->cursor_update  = gimp_iscissors_tool_cursor_update;
319   tool_class->can_undo       = gimp_iscissors_tool_can_undo;
320   tool_class->can_redo       = gimp_iscissors_tool_can_redo;
321   tool_class->undo           = gimp_iscissors_tool_undo;
322   tool_class->redo           = gimp_iscissors_tool_redo;
323 
324   draw_tool_class->draw      = gimp_iscissors_tool_draw;
325 
326   for (i = 0; i < 256; i++)
327     {
328       /*  The diagonal weight array  */
329       diagonal_weight[i] = (int) (i * G_SQRT2);
330 
331       /*  The direction value array  */
332       direction_value[i][0] = (127 - abs (127 - i)) * 2;
333       direction_value[i][1] = abs (127 - i) * 2;
334       direction_value[i][2] = abs (191 - i) * 2;
335       direction_value[i][3] = abs (63 - i) * 2;
336     }
337 
338   /*  set the 256th index of the direction_values to the highest cost  */
339   direction_value[255][0] = 255;
340   direction_value[255][1] = 255;
341   direction_value[255][2] = 255;
342   direction_value[255][3] = 255;
343 
344   /*  compute the distance weights  */
345   radius = GRADIENT_SEARCH >> 1;
346 
347   for (i = 0; i < GRADIENT_SEARCH; i++)
348     for (j = 0; j < GRADIENT_SEARCH; j++)
349       distance_weights[i * GRADIENT_SEARCH + j] =
350         1.0 / (1 + sqrt (SQR (i - radius) + SQR (j - radius)));
351 }
352 
353 static void
gimp_iscissors_tool_init(GimpIscissorsTool * iscissors)354 gimp_iscissors_tool_init (GimpIscissorsTool *iscissors)
355 {
356   GimpTool *tool = GIMP_TOOL (iscissors);
357 
358   gimp_tool_control_set_scroll_lock (tool->control, TRUE);
359   gimp_tool_control_set_snap_to     (tool->control, FALSE);
360   gimp_tool_control_set_preserve    (tool->control, FALSE);
361   gimp_tool_control_set_dirty_mask  (tool->control,
362                                      GIMP_DIRTY_IMAGE_SIZE |
363                                      GIMP_DIRTY_ACTIVE_DRAWABLE);
364   gimp_tool_control_set_tool_cursor (tool->control,
365                                      GIMP_TOOL_CURSOR_ISCISSORS);
366 
367   iscissors->op    = ISCISSORS_OP_NONE;
368   iscissors->curve = icurve_new ();
369   iscissors->state = NO_ACTION;
370 }
371 
372 static void
gimp_iscissors_tool_finalize(GObject * object)373 gimp_iscissors_tool_finalize (GObject *object)
374 {
375   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (object);
376 
377   icurve_free (iscissors->curve);
378   iscissors->curve = NULL;
379 
380   G_OBJECT_CLASS (parent_class)->finalize (object);
381 }
382 
383 static void
gimp_iscissors_tool_control(GimpTool * tool,GimpToolAction action,GimpDisplay * display)384 gimp_iscissors_tool_control (GimpTool       *tool,
385                              GimpToolAction  action,
386                              GimpDisplay    *display)
387 {
388   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
389 
390   switch (action)
391     {
392     case GIMP_TOOL_ACTION_PAUSE:
393     case GIMP_TOOL_ACTION_RESUME:
394       break;
395 
396     case GIMP_TOOL_ACTION_HALT:
397       gimp_iscissors_tool_halt (iscissors, display);
398       break;
399 
400     case GIMP_TOOL_ACTION_COMMIT:
401       gimp_iscissors_tool_commit (iscissors, display);
402       break;
403     }
404 
405   GIMP_TOOL_CLASS (parent_class)->control (tool, action, display);
406 }
407 
408 static void
gimp_iscissors_tool_button_press(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonPressType press_type,GimpDisplay * display)409 gimp_iscissors_tool_button_press (GimpTool            *tool,
410                                   const GimpCoords    *coords,
411                                   guint32              time,
412                                   GdkModifierType      state,
413                                   GimpButtonPressType  press_type,
414                                   GimpDisplay         *display)
415 {
416   GimpIscissorsTool    *iscissors = GIMP_ISCISSORS_TOOL (tool);
417   GimpIscissorsOptions *options   = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
418   GimpImage            *image     = gimp_display_get_image (display);
419   ISegment             *segment;
420 
421   iscissors->x = RINT (coords->x);
422   iscissors->y = RINT (coords->y);
423 
424   /*  If the tool was being used in another image...reset it  */
425   if (display != tool->display)
426     gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
427 
428   gimp_tool_control_activate (tool->control);
429   tool->display = display;
430 
431   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
432 
433   switch (iscissors->state)
434     {
435     case NO_ACTION:
436       iscissors->state = SEED_PLACEMENT;
437 
438       if (! (state & gimp_get_extend_selection_mask ()))
439         find_max_gradient (iscissors, GIMP_PICKABLE (image),
440                            &iscissors->x, &iscissors->y);
441 
442       iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width  (image) - 1);
443       iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1);
444 
445       gimp_iscissors_tool_push_undo (iscissors);
446 
447       segment = icurve_append_segment (iscissors->curve,
448                                        iscissors->x,
449                                        iscissors->y,
450                                        iscissors->x,
451                                        iscissors->y);
452 
453       /*  Initialize the draw tool only on starting the tool  */
454       gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
455       break;
456 
457     default:
458       /*  Check if the mouse click occurred on a vertex or the curve itself  */
459       if (clicked_on_vertex (iscissors, coords->x, coords->y))
460         {
461           iscissors->state = SEED_ADJUSTMENT;
462 
463           /*  recalculate both segments  */
464           if (iscissors->segment1)
465             {
466               iscissors->segment1->x1 = iscissors->x;
467               iscissors->segment1->y1 = iscissors->y;
468 
469               if (options->interactive)
470                 calculate_segment (iscissors, iscissors->segment1);
471             }
472 
473           if (iscissors->segment2)
474             {
475               iscissors->segment2->x2 = iscissors->x;
476               iscissors->segment2->y2 = iscissors->y;
477 
478               if (options->interactive)
479                 calculate_segment (iscissors, iscissors->segment2);
480             }
481         }
482       /*  If the iscissors is closed, check if the click was inside  */
483       else if (iscissors->curve->closed && iscissors->mask &&
484                gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
485                                              iscissors->x,
486                                              iscissors->y))
487         {
488           gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
489         }
490       else if (! iscissors->curve->closed)
491         {
492           /*  if we're not closed, we're adding a new point  */
493 
494           ISegment *last = g_queue_peek_tail (iscissors->curve->segments);
495 
496           iscissors->state = SEED_PLACEMENT;
497 
498           gimp_iscissors_tool_push_undo (iscissors);
499 
500           if (last->x1 == last->x2 &&
501               last->y1 == last->y2)
502             {
503               last->x2 = iscissors->x;
504               last->y2 = iscissors->y;
505 
506               if (options->interactive)
507                 calculate_segment (iscissors, last);
508             }
509           else
510             {
511               segment = icurve_append_segment (iscissors->curve,
512                                                last->x2,
513                                                last->y2,
514                                                iscissors->x,
515                                                iscissors->y);
516 
517               if (options->interactive)
518                 calculate_segment (iscissors, segment);
519             }
520         }
521       break;
522     }
523 
524   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
525 }
526 
527 static void
iscissors_convert(GimpIscissorsTool * iscissors,GimpDisplay * display)528 iscissors_convert (GimpIscissorsTool *iscissors,
529                    GimpDisplay       *display)
530 {
531   GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (iscissors);
532   GimpImage            *image   = gimp_display_get_image (display);
533   GimpScanConvert      *sc;
534 
535   sc = icurve_create_scan_convert (iscissors->curve);
536 
537   if (iscissors->mask)
538     g_object_unref (iscissors->mask);
539 
540   iscissors->mask = gimp_channel_new_mask (image,
541                                            gimp_image_get_width  (image),
542                                            gimp_image_get_height (image));
543   gimp_scan_convert_render (sc,
544                             gimp_drawable_get_buffer (GIMP_DRAWABLE (iscissors->mask)),
545                             0, 0, options->antialias);
546 
547   gimp_scan_convert_free (sc);
548 }
549 
550 static void
gimp_iscissors_tool_button_release(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpButtonReleaseType release_type,GimpDisplay * display)551 gimp_iscissors_tool_button_release (GimpTool              *tool,
552                                     const GimpCoords      *coords,
553                                     guint32                time,
554                                     GdkModifierType        state,
555                                     GimpButtonReleaseType  release_type,
556                                     GimpDisplay           *display)
557 {
558   GimpIscissorsTool    *iscissors = GIMP_ISCISSORS_TOOL (tool);
559   GimpIscissorsOptions *options   = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
560 
561   gimp_tool_control_halt (tool->control);
562 
563   /* Make sure X didn't skip the button release event -- as it's known
564    * to do
565    */
566   if (iscissors->state == WAITING)
567     return;
568 
569   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
570 
571   if (release_type != GIMP_BUTTON_RELEASE_CANCEL)
572     {
573       /*  Progress to the next stage of intelligent selection  */
574       switch (iscissors->state)
575         {
576         case SEED_PLACEMENT:
577           /*  Add a new segment  */
578           if (! iscissors->curve->first_point)
579             {
580               /*  Determine if we're connecting to the first point  */
581 
582               ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
583 
584               if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display,
585                                             iscissors->x, iscissors->y,
586                                             GIMP_HANDLE_CIRCLE,
587                                             segment->x1, segment->y1,
588                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
589                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
590                                             GIMP_HANDLE_ANCHOR_CENTER))
591                 {
592                   iscissors->x = segment->x1;
593                   iscissors->y = segment->y1;
594 
595                   icurve_close (iscissors->curve);
596 
597                   if (! options->interactive)
598                     {
599                       segment = g_queue_peek_tail (iscissors->curve->segments);
600                       calculate_segment (iscissors, segment);
601                     }
602 
603                   gimp_iscissors_tool_free_redo (iscissors);
604                 }
605               else
606                 {
607                   segment = g_queue_peek_tail (iscissors->curve->segments);
608 
609                   if (segment->x1 != segment->x2 ||
610                       segment->y1 != segment->y2)
611                     {
612                       if (! options->interactive)
613                         calculate_segment (iscissors, segment);
614 
615                       gimp_iscissors_tool_free_redo (iscissors);
616                     }
617                   else
618                     {
619                       gimp_iscissors_tool_pop_undo (iscissors);
620                     }
621                 }
622             }
623           else /* this was our first point */
624             {
625               iscissors->curve->first_point = FALSE;
626 
627               gimp_iscissors_tool_free_redo (iscissors);
628             }
629           break;
630 
631         case SEED_ADJUSTMENT:
632           if (state & gimp_get_modify_selection_mask ())
633             {
634               if (iscissors->segment1 && iscissors->segment2)
635                 {
636                   icurve_delete_segment (iscissors->curve,
637                                          iscissors->segment2);
638 
639                   calculate_segment (iscissors, iscissors->segment1);
640                 }
641             }
642           else
643             {
644               /*  recalculate both segments  */
645 
646               if (iscissors->segment1)
647                 {
648                   if (! options->interactive)
649                     calculate_segment (iscissors, iscissors->segment1);
650                 }
651 
652               if (iscissors->segment2)
653                 {
654                   if (! options->interactive)
655                     calculate_segment (iscissors, iscissors->segment2);
656                 }
657             }
658 
659           gimp_iscissors_tool_free_redo (iscissors);
660           break;
661 
662         default:
663           break;
664         }
665     }
666   else
667     {
668       switch (iscissors->state)
669         {
670         case SEED_PLACEMENT:
671         case SEED_ADJUSTMENT:
672           gimp_iscissors_tool_pop_undo (iscissors);
673           break;
674 
675         default:
676           break;
677         }
678     }
679 
680   if (iscissors->curve->first_point)
681     iscissors->state = NO_ACTION;
682   else
683     iscissors->state = WAITING;
684 
685   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
686 
687   /*  convert the curves into a region  */
688   if (iscissors->curve->closed)
689     iscissors_convert (iscissors, display);
690 }
691 
692 static void
gimp_iscissors_tool_motion(GimpTool * tool,const GimpCoords * coords,guint32 time,GdkModifierType state,GimpDisplay * display)693 gimp_iscissors_tool_motion (GimpTool         *tool,
694                             const GimpCoords *coords,
695                             guint32           time,
696                             GdkModifierType   state,
697                             GimpDisplay      *display)
698 {
699   GimpIscissorsTool    *iscissors = GIMP_ISCISSORS_TOOL (tool);
700   GimpIscissorsOptions *options   = GIMP_ISCISSORS_TOOL_GET_OPTIONS (tool);
701   GimpImage            *image     = gimp_display_get_image (display);
702   ISegment             *segment;
703 
704   if (iscissors->state == NO_ACTION)
705     return;
706 
707   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
708 
709   iscissors->x = RINT (coords->x);
710   iscissors->y = RINT (coords->y);
711 
712   /*  Hold the shift key down to disable the auto-edge snap feature  */
713   if (! (state & gimp_get_extend_selection_mask ()))
714     find_max_gradient (iscissors, GIMP_PICKABLE (image),
715                        &iscissors->x, &iscissors->y);
716 
717   iscissors->x = CLAMP (iscissors->x, 0, gimp_image_get_width  (image) - 1);
718   iscissors->y = CLAMP (iscissors->y, 0, gimp_image_get_height (image) - 1);
719 
720   switch (iscissors->state)
721     {
722     case SEED_PLACEMENT:
723       segment = g_queue_peek_tail (iscissors->curve->segments);
724 
725       segment->x2 = iscissors->x;
726       segment->y2 = iscissors->y;
727 
728       if (iscissors->curve->first_point)
729         {
730           segment->x1 = segment->x2;
731           segment->y1 = segment->y2;
732         }
733       else
734         {
735           if (options->interactive)
736             calculate_segment (iscissors, segment);
737         }
738       break;
739 
740     case SEED_ADJUSTMENT:
741       if (iscissors->segment1)
742         {
743           iscissors->segment1->x1 = iscissors->x;
744           iscissors->segment1->y1 = iscissors->y;
745 
746           if (options->interactive)
747             calculate_segment (iscissors, iscissors->segment1);
748         }
749 
750       if (iscissors->segment2)
751         {
752           iscissors->segment2->x2 = iscissors->x;
753           iscissors->segment2->y2 = iscissors->y;
754 
755           if (options->interactive)
756             calculate_segment (iscissors, iscissors->segment2);
757         }
758       break;
759 
760     default:
761       break;
762     }
763 
764   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
765 }
766 
767 static void
gimp_iscissors_tool_draw(GimpDrawTool * draw_tool)768 gimp_iscissors_tool_draw (GimpDrawTool *draw_tool)
769 {
770   GimpIscissorsTool    *iscissors = GIMP_ISCISSORS_TOOL (draw_tool);
771   GimpIscissorsOptions *options   = GIMP_ISCISSORS_TOOL_GET_OPTIONS (draw_tool);
772   GimpCanvasItem       *item;
773   GList                *list;
774 
775   /*  First, render all segments and lines  */
776   if (! iscissors->curve->first_point)
777     {
778       for (list = g_queue_peek_head_link (iscissors->curve->segments);
779            list;
780            list = g_list_next (list))
781         {
782           ISegment *segment = list->data;
783 
784           /*  plot the segment  */
785           item = iscissors_draw_segment (draw_tool, segment);
786 
787           /*  if this segment is currently being added or adjusted  */
788           if ((iscissors->state == SEED_PLACEMENT  &&
789                ! list->next)
790               ||
791               (iscissors->state == SEED_ADJUSTMENT &&
792                (segment == iscissors->segment1 ||
793                 segment == iscissors->segment2)))
794             {
795               if (! options->interactive)
796                 item = gimp_draw_tool_add_line (draw_tool,
797                                                 segment->x1, segment->y1,
798                                                 segment->x2, segment->y2);
799 
800               if (item)
801                 gimp_canvas_item_set_highlight (item, TRUE);
802             }
803         }
804     }
805 
806   /*  Then, render the handles on top of the segments  */
807   for (list = g_queue_peek_head_link (iscissors->curve->segments);
808        list;
809        list = g_list_next (list))
810     {
811       ISegment *segment = list->data;
812 
813       if (! iscissors->curve->first_point)
814         {
815           gboolean adjustment = (iscissors->state == SEED_ADJUSTMENT &&
816                                  segment == iscissors->segment1);
817 
818           item = gimp_draw_tool_add_handle (draw_tool,
819                                             adjustment ?
820                                             GIMP_HANDLE_CROSS :
821                                             GIMP_HANDLE_FILLED_CIRCLE,
822                                             segment->x1,
823                                             segment->y1,
824                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
825                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
826                                             GIMP_HANDLE_ANCHOR_CENTER);
827 
828           if (adjustment)
829             gimp_canvas_item_set_highlight (item, TRUE);
830         }
831 
832       /*  Draw the last point if the curve is not closed  */
833       if (! list->next && ! iscissors->curve->closed)
834         {
835           gboolean placement = (iscissors->state == SEED_PLACEMENT);
836 
837           item = gimp_draw_tool_add_handle (draw_tool,
838                                             placement ?
839                                             GIMP_HANDLE_CROSS :
840                                             GIMP_HANDLE_FILLED_CIRCLE,
841                                             segment->x2,
842                                             segment->y2,
843                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
844                                             GIMP_TOOL_HANDLE_SIZE_CIRCLE,
845                                             GIMP_HANDLE_ANCHOR_CENTER);
846 
847           if (placement)
848             gimp_canvas_item_set_highlight (item, TRUE);
849         }
850     }
851 }
852 
853 static GimpCanvasItem *
iscissors_draw_segment(GimpDrawTool * draw_tool,ISegment * segment)854 iscissors_draw_segment (GimpDrawTool *draw_tool,
855                         ISegment     *segment)
856 {
857   GimpCanvasItem *item;
858   GimpVector2    *points;
859   gpointer       *point;
860   gint            i, len;
861 
862   if (! segment->points)
863     return NULL;
864 
865   len = segment->points->len;
866 
867   points = g_new (GimpVector2, len);
868 
869   for (i = 0, point = segment->points->pdata; i < len; i++, point++)
870     {
871       guint32 coords = GPOINTER_TO_INT (*point);
872 
873       points[i].x = (coords & 0x0000ffff);
874       points[i].y = (coords >> 16);
875     }
876 
877   item = gimp_draw_tool_add_lines (draw_tool, points, len, NULL, FALSE);
878 
879   g_free (points);
880 
881   return item;
882 }
883 
884 static void
gimp_iscissors_tool_oper_update(GimpTool * tool,const GimpCoords * coords,GdkModifierType state,gboolean proximity,GimpDisplay * display)885 gimp_iscissors_tool_oper_update (GimpTool         *tool,
886                                  const GimpCoords *coords,
887                                  GdkModifierType   state,
888                                  gboolean          proximity,
889                                  GimpDisplay      *display)
890 {
891   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
892 
893   GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
894                                                display);
895   /* parent sets a message in the status bar, but it will be replaced here */
896 
897   if (mouse_over_vertex (iscissors, coords->x, coords->y) > 1)
898     {
899       GdkModifierType snap_mask   = gimp_get_extend_selection_mask ();
900       GdkModifierType remove_mask = gimp_get_modify_selection_mask ();
901 
902       if (state & remove_mask)
903         {
904           gimp_tool_replace_status (tool, display,
905                                     _("Click to remove this point"));
906           iscissors->op = ISCISSORS_OP_REMOVE_POINT;
907         }
908       else
909         {
910           gchar *status =
911             gimp_suggest_modifiers (_("Click-Drag to move this point"),
912                                     (snap_mask | remove_mask) & ~state,
913                                     _("%s: disable auto-snap"),
914                                     _("%s: remove this point"),
915                                     NULL);
916           gimp_tool_replace_status (tool, display, "%s", status);
917           g_free (status);
918           iscissors->op = ISCISSORS_OP_MOVE_POINT;
919         }
920     }
921   else if (mouse_over_segment (iscissors, coords->x, coords->y))
922     {
923       ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
924 
925       if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), display,
926                                     RINT (coords->x), RINT (coords->y),
927                                     GIMP_HANDLE_CIRCLE,
928                                     segment->x1, segment->y1,
929                                     GIMP_TOOL_HANDLE_SIZE_CIRCLE,
930                                     GIMP_TOOL_HANDLE_SIZE_CIRCLE,
931                                     GIMP_HANDLE_ANCHOR_CENTER))
932         {
933           gimp_tool_replace_status (tool, display,
934                                     _("Click to close the curve"));
935           iscissors->op = ISCISSORS_OP_CONNECT;
936         }
937       else
938         {
939           gimp_tool_replace_status (tool, display,
940                                     _("Click to add a point on this segment"));
941           iscissors->op = ISCISSORS_OP_ADD_POINT;
942         }
943     }
944   else if (iscissors->curve->closed && iscissors->mask)
945     {
946       if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
947                                         RINT (coords->x),
948                                         RINT (coords->y)))
949         {
950           if (proximity)
951             {
952               gimp_tool_replace_status (tool, display,
953                                         _("Click or press Enter to convert to"
954                                           " a selection"));
955             }
956           iscissors->op = ISCISSORS_OP_SELECT;
957         }
958       else
959         {
960           if (proximity)
961             {
962               gimp_tool_replace_status (tool, display,
963                                         _("Press Enter to convert to a"
964                                           " selection"));
965             }
966           iscissors->op = ISCISSORS_OP_IMPOSSIBLE;
967         }
968     }
969   else
970     {
971       switch (iscissors->state)
972         {
973         case WAITING:
974           if (proximity)
975             {
976               GdkModifierType  snap_mask = gimp_get_extend_selection_mask ();
977               gchar           *status;
978 
979               status = gimp_suggest_modifiers (_("Click or Click-Drag to add a"
980                                                  " point"),
981                                                snap_mask & ~state,
982                                                _("%s: disable auto-snap"),
983                                                NULL, NULL);
984               gimp_tool_replace_status (tool, display, "%s", status);
985               g_free (status);
986             }
987           iscissors->op = ISCISSORS_OP_ADD_POINT;
988           break;
989 
990         default:
991           /* if NO_ACTION, keep parent's status bar message (selection tool) */
992           iscissors->op = ISCISSORS_OP_NONE;
993           break;
994         }
995     }
996 }
997 
998 static void
gimp_iscissors_tool_cursor_update(GimpTool * tool,const GimpCoords * coords,GdkModifierType state,GimpDisplay * display)999 gimp_iscissors_tool_cursor_update (GimpTool         *tool,
1000                                    const GimpCoords *coords,
1001                                    GdkModifierType   state,
1002                                    GimpDisplay      *display)
1003 {
1004   GimpIscissorsTool  *iscissors = GIMP_ISCISSORS_TOOL (tool);
1005   GimpCursorModifier  modifier  = GIMP_CURSOR_MODIFIER_NONE;
1006 
1007   switch (iscissors->op)
1008     {
1009     case ISCISSORS_OP_SELECT:
1010       {
1011         GimpSelectionOptions *options;
1012 
1013         options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
1014 
1015         /* Do not overwrite the modifiers for add, subtract, intersect */
1016         if (options->operation == GIMP_CHANNEL_OP_REPLACE)
1017           {
1018             modifier = GIMP_CURSOR_MODIFIER_SELECT;
1019           }
1020       }
1021       break;
1022 
1023     case ISCISSORS_OP_MOVE_POINT:
1024       modifier = GIMP_CURSOR_MODIFIER_MOVE;
1025       break;
1026 
1027     case ISCISSORS_OP_ADD_POINT:
1028       modifier = GIMP_CURSOR_MODIFIER_PLUS;
1029       break;
1030 
1031     case ISCISSORS_OP_REMOVE_POINT:
1032       modifier = GIMP_CURSOR_MODIFIER_MINUS;
1033       break;
1034 
1035     case ISCISSORS_OP_CONNECT:
1036       modifier = GIMP_CURSOR_MODIFIER_JOIN;
1037       break;
1038 
1039     case ISCISSORS_OP_IMPOSSIBLE:
1040       modifier = GIMP_CURSOR_MODIFIER_BAD;
1041       break;
1042 
1043     default:
1044       break;
1045     }
1046 
1047   if (modifier != GIMP_CURSOR_MODIFIER_NONE)
1048     {
1049       gimp_tool_set_cursor (tool, display,
1050                             GIMP_CURSOR_MOUSE,
1051                             GIMP_TOOL_CURSOR_ISCISSORS,
1052                             modifier);
1053     }
1054   else
1055     {
1056       GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords,
1057                                                      state, display);
1058     }
1059 }
1060 
1061 static gboolean
gimp_iscissors_tool_key_press(GimpTool * tool,GdkEventKey * kevent,GimpDisplay * display)1062 gimp_iscissors_tool_key_press (GimpTool    *tool,
1063                                GdkEventKey *kevent,
1064                                GimpDisplay *display)
1065 {
1066   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
1067 
1068   if (display != tool->display)
1069     return FALSE;
1070 
1071   switch (kevent->keyval)
1072     {
1073     case GDK_KEY_BackSpace:
1074       if (! iscissors->curve->closed &&
1075           g_queue_peek_tail (iscissors->curve->segments))
1076         {
1077           ISegment *segment = g_queue_peek_tail (iscissors->curve->segments);
1078 
1079           if (g_queue_get_length (iscissors->curve->segments) > 1)
1080             {
1081               gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
1082 
1083               gimp_iscissors_tool_push_undo (iscissors);
1084               icurve_delete_segment (iscissors->curve, segment);
1085               gimp_iscissors_tool_free_redo (iscissors);
1086 
1087               gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
1088             }
1089           else if (segment->x2 != segment->x1 || segment->y2 != segment->y1)
1090             {
1091               gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
1092 
1093               gimp_iscissors_tool_push_undo (iscissors);
1094               segment->x2 = segment->x1;
1095               segment->y2 = segment->y1;
1096               g_ptr_array_remove_range (segment->points,
1097                                         0, segment->points->len);
1098               gimp_iscissors_tool_free_redo (iscissors);
1099 
1100               gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
1101             }
1102           else
1103             {
1104               gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
1105             }
1106           return TRUE;
1107         }
1108       return FALSE;
1109 
1110     case GDK_KEY_Return:
1111     case GDK_KEY_KP_Enter:
1112     case GDK_KEY_ISO_Enter:
1113       if (iscissors->curve->closed && iscissors->mask)
1114         {
1115           gimp_tool_control (tool, GIMP_TOOL_ACTION_COMMIT, display);
1116           return TRUE;
1117         }
1118       return FALSE;
1119 
1120     case GDK_KEY_Escape:
1121       gimp_tool_control (tool, GIMP_TOOL_ACTION_HALT, display);
1122       return TRUE;
1123 
1124     default:
1125       return FALSE;
1126     }
1127 }
1128 
1129 static const gchar *
gimp_iscissors_tool_can_undo(GimpTool * tool,GimpDisplay * display)1130 gimp_iscissors_tool_can_undo (GimpTool    *tool,
1131                               GimpDisplay *display)
1132 {
1133   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
1134 
1135   if (! iscissors->undo_stack)
1136     return NULL;
1137 
1138   return _("Modify Scissors Curve");
1139 }
1140 
1141 static const gchar *
gimp_iscissors_tool_can_redo(GimpTool * tool,GimpDisplay * display)1142 gimp_iscissors_tool_can_redo (GimpTool    *tool,
1143                               GimpDisplay *display)
1144 {
1145   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
1146 
1147   if (! iscissors->redo_stack)
1148     return NULL;
1149 
1150   return _("Modify Scissors Curve");
1151 }
1152 
1153 static gboolean
gimp_iscissors_tool_undo(GimpTool * tool,GimpDisplay * display)1154 gimp_iscissors_tool_undo (GimpTool    *tool,
1155                           GimpDisplay *display)
1156 {
1157   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
1158 
1159   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
1160 
1161   iscissors->redo_stack = g_list_prepend (iscissors->redo_stack,
1162                                           iscissors->curve);
1163 
1164   iscissors->curve = iscissors->undo_stack->data;
1165 
1166   iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
1167                                          iscissors->curve);
1168 
1169   if (! iscissors->undo_stack)
1170     {
1171       iscissors->state = NO_ACTION;
1172 
1173       gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
1174     }
1175 
1176   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
1177 
1178   return TRUE;
1179 }
1180 
1181 static gboolean
gimp_iscissors_tool_redo(GimpTool * tool,GimpDisplay * display)1182 gimp_iscissors_tool_redo (GimpTool    *tool,
1183                           GimpDisplay *display)
1184 {
1185   GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
1186 
1187   gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
1188 
1189   if (! iscissors->undo_stack)
1190     {
1191       iscissors->state = WAITING;
1192 
1193       gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), display);
1194     }
1195 
1196   iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
1197                                           iscissors->curve);
1198 
1199   iscissors->curve = iscissors->redo_stack->data;
1200 
1201   iscissors->redo_stack = g_list_remove (iscissors->redo_stack,
1202                                          iscissors->curve);
1203 
1204   gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
1205 
1206   return TRUE;
1207 }
1208 
1209 static void
gimp_iscissors_tool_push_undo(GimpIscissorsTool * iscissors)1210 gimp_iscissors_tool_push_undo (GimpIscissorsTool *iscissors)
1211 {
1212   iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
1213                                           icurve_copy (iscissors->curve));
1214 }
1215 
1216 static void
gimp_iscissors_tool_pop_undo(GimpIscissorsTool * iscissors)1217 gimp_iscissors_tool_pop_undo (GimpIscissorsTool *iscissors)
1218 {
1219   icurve_free (iscissors->curve);
1220   iscissors->curve = iscissors->undo_stack->data;
1221 
1222   iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
1223                                          iscissors->curve);
1224 
1225   if (! iscissors->undo_stack)
1226     {
1227       iscissors->state = NO_ACTION;
1228 
1229       gimp_draw_tool_stop (GIMP_DRAW_TOOL (iscissors));
1230     }
1231 }
1232 
1233 static void
gimp_iscissors_tool_free_redo(GimpIscissorsTool * iscissors)1234 gimp_iscissors_tool_free_redo (GimpIscissorsTool *iscissors)
1235 {
1236   g_list_free_full (iscissors->redo_stack,
1237                     (GDestroyNotify) icurve_free);
1238   iscissors->redo_stack = NULL;
1239 
1240   /*  update the undo actions / menu items  */
1241   gimp_image_flush (gimp_display_get_image (GIMP_TOOL (iscissors)->display));
1242 }
1243 
1244 static void
gimp_iscissors_tool_halt(GimpIscissorsTool * iscissors,GimpDisplay * display)1245 gimp_iscissors_tool_halt (GimpIscissorsTool *iscissors,
1246                           GimpDisplay       *display)
1247 {
1248   icurve_clear (iscissors->curve);
1249 
1250   iscissors->segment1 = NULL;
1251   iscissors->segment2 = NULL;
1252   iscissors->state    = NO_ACTION;
1253 
1254   if (iscissors->undo_stack)
1255     {
1256       g_list_free_full (iscissors->undo_stack, (GDestroyNotify) icurve_free);
1257       iscissors->undo_stack = NULL;
1258     }
1259 
1260   if (iscissors->redo_stack)
1261     {
1262       g_list_free_full (iscissors->redo_stack, (GDestroyNotify) icurve_free);
1263       iscissors->redo_stack = NULL;
1264     }
1265 
1266   g_clear_object (&iscissors->gradient_map);
1267   g_clear_object (&iscissors->mask);
1268 }
1269 
1270 static void
gimp_iscissors_tool_commit(GimpIscissorsTool * iscissors,GimpDisplay * display)1271 gimp_iscissors_tool_commit (GimpIscissorsTool *iscissors,
1272                             GimpDisplay       *display)
1273 {
1274   GimpTool             *tool    = GIMP_TOOL (iscissors);
1275   GimpSelectionOptions *options = GIMP_SELECTION_TOOL_GET_OPTIONS (tool);
1276   GimpImage            *image   = gimp_display_get_image (display);
1277 
1278   if (! iscissors->curve->closed)
1279     {
1280       ISegment *first = g_queue_peek_head (iscissors->curve->segments);
1281       ISegment *last  = g_queue_peek_tail (iscissors->curve->segments);
1282 
1283       if (first && last && first != last)
1284         {
1285           ISegment *segment;
1286 
1287           segment = icurve_append_segment (iscissors->curve,
1288                                            last->x2,
1289                                            last->y2,
1290                                            first->x1,
1291                                            first->y1);
1292           icurve_close (iscissors->curve);
1293           calculate_segment (iscissors, segment);
1294 
1295           iscissors_convert (iscissors, display);
1296         }
1297     }
1298 
1299   if (iscissors->curve->closed && iscissors->mask)
1300     {
1301       gimp_channel_select_channel (gimp_image_get_mask (image),
1302                                    gimp_tool_get_undo_desc (tool),
1303                                    iscissors->mask,
1304                                    0, 0,
1305                                    options->operation,
1306                                    options->feather,
1307                                    options->feather_radius,
1308                                    options->feather_radius);
1309 
1310       gimp_image_flush (image);
1311     }
1312 }
1313 
1314 
1315 /* XXX need some scan-conversion routines from somewhere.  maybe. ? */
1316 
1317 static gint
mouse_over_vertex(GimpIscissorsTool * iscissors,gdouble x,gdouble y)1318 mouse_over_vertex (GimpIscissorsTool *iscissors,
1319                    gdouble            x,
1320                    gdouble            y)
1321 {
1322   GList *list;
1323   gint   segments_found = 0;
1324 
1325   /*  traverse through the list, returning non-zero if the current cursor
1326    *  position is on an existing curve vertex.  Set the segment1 and segment2
1327    *  variables to the two segments containing the vertex in question
1328    */
1329 
1330   iscissors->segment1 = iscissors->segment2 = NULL;
1331 
1332   for (list = g_queue_peek_head_link (iscissors->curve->segments);
1333        list;
1334        list = g_list_next (list))
1335     {
1336       ISegment *segment = list->data;
1337 
1338       if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
1339                                     GIMP_TOOL (iscissors)->display,
1340                                     x, y,
1341                                     GIMP_HANDLE_CIRCLE,
1342                                     segment->x1, segment->y1,
1343                                     GIMP_TOOL_HANDLE_SIZE_CIRCLE,
1344                                     GIMP_TOOL_HANDLE_SIZE_CIRCLE,
1345                                     GIMP_HANDLE_ANCHOR_CENTER))
1346         {
1347           iscissors->segment1 = segment;
1348 
1349           if (segments_found++)
1350             return segments_found;
1351         }
1352       else if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
1353                                          GIMP_TOOL (iscissors)->display,
1354                                          x, y,
1355                                          GIMP_HANDLE_CIRCLE,
1356                                          segment->x2, segment->y2,
1357                                          GIMP_TOOL_HANDLE_SIZE_CIRCLE,
1358                                          GIMP_TOOL_HANDLE_SIZE_CIRCLE,
1359                                          GIMP_HANDLE_ANCHOR_CENTER))
1360         {
1361           iscissors->segment2 = segment;
1362 
1363           if (segments_found++)
1364             return segments_found;
1365         }
1366     }
1367 
1368   return segments_found;
1369 }
1370 
1371 static gboolean
clicked_on_vertex(GimpIscissorsTool * iscissors,gdouble x,gdouble y)1372 clicked_on_vertex (GimpIscissorsTool *iscissors,
1373                    gdouble            x,
1374                    gdouble            y)
1375 {
1376   gint segments_found  = mouse_over_vertex (iscissors, x, y);
1377 
1378   if (segments_found > 1)
1379     {
1380       gimp_iscissors_tool_push_undo (iscissors);
1381 
1382       return TRUE;
1383     }
1384 
1385   /*  if only one segment was found, the segments are unconnected, and
1386    *  the user only wants to move either the first or last point
1387    *  disallow this for now.
1388    */
1389   if (segments_found == 1)
1390     return FALSE;
1391 
1392   return clicked_on_segment (iscissors, x, y);
1393 }
1394 
1395 
1396 static GList *
mouse_over_segment(GimpIscissorsTool * iscissors,gdouble x,gdouble y)1397 mouse_over_segment (GimpIscissorsTool *iscissors,
1398                     gdouble            x,
1399                     gdouble            y)
1400 {
1401   GList *list;
1402 
1403   /*  traverse through the list, returning the curve segment's list element
1404    *  if the current cursor position is on a curve...
1405    */
1406   for (list = g_queue_peek_head_link (iscissors->curve->segments);
1407        list;
1408        list = g_list_next (list))
1409     {
1410       ISegment *segment = list->data;
1411       gpointer *pt;
1412       gint      len;
1413 
1414       if (! segment->points)
1415         continue;
1416 
1417       pt  = segment->points->pdata;
1418       len = segment->points->len;
1419 
1420       while (len--)
1421         {
1422           guint32 coords = GPOINTER_TO_INT (*pt);
1423           gint    tx, ty;
1424 
1425           pt++;
1426           tx = coords & 0x0000ffff;
1427           ty = coords >> 16;
1428 
1429           /*  Is the specified point close enough to the segment?  */
1430           if (gimp_draw_tool_calc_distance_square (GIMP_DRAW_TOOL (iscissors),
1431                                                    GIMP_TOOL (iscissors)->display,
1432                                                    tx, ty,
1433                                                    x, y) < SQR (GIMP_TOOL_HANDLE_SIZE_CIRCLE / 2))
1434             {
1435               return list;
1436             }
1437         }
1438     }
1439 
1440   return NULL;
1441 }
1442 
1443 static gboolean
clicked_on_segment(GimpIscissorsTool * iscissors,gdouble x,gdouble y)1444 clicked_on_segment (GimpIscissorsTool *iscissors,
1445                     gdouble            x,
1446                     gdouble            y)
1447 {
1448   GList *list = mouse_over_segment (iscissors, x, y);
1449 
1450   /*  traverse through the list, getting back the curve segment's list
1451    *  element if the current cursor position is on a segment...
1452    *  If this occurs, replace the segment with two new segments,
1453    *  separated by a new vertex.
1454    */
1455 
1456   if (list)
1457     {
1458       ISegment *segment = list->data;
1459       ISegment *new_segment;
1460 
1461       gimp_iscissors_tool_push_undo (iscissors);
1462 
1463       new_segment = icurve_insert_segment (iscissors->curve,
1464                                            list,
1465                                            iscissors->x,
1466                                            iscissors->y,
1467                                            segment->x2,
1468                                            segment->y2);
1469 
1470       iscissors->segment1 = new_segment;
1471       iscissors->segment2 = segment;
1472 
1473       return TRUE;
1474     }
1475 
1476   return FALSE;
1477 }
1478 
1479 
1480 static void
calculate_segment(GimpIscissorsTool * iscissors,ISegment * segment)1481 calculate_segment (GimpIscissorsTool *iscissors,
1482                    ISegment          *segment)
1483 {
1484   GimpDisplay  *display  = GIMP_TOOL (iscissors)->display;
1485   GimpPickable *pickable = GIMP_PICKABLE (gimp_display_get_image (display));
1486   gint          width;
1487   gint          height;
1488   gint          xs, ys, xe, ye;
1489   gint          x1, y1, x2, y2;
1490   gint          ewidth, eheight;
1491 
1492   /* Initialise the gradient map buffer for this pickable if we don't
1493    * already have one.
1494    */
1495   if (! iscissors->gradient_map)
1496     iscissors->gradient_map = gradient_map_new (pickable);
1497 
1498   width  = gegl_buffer_get_width  (iscissors->gradient_map);
1499   height = gegl_buffer_get_height (iscissors->gradient_map);
1500 
1501   /*  Calculate the lowest cost path from one vertex to the next as specified
1502    *  by the parameter "segment".
1503    *    Here are the steps:
1504    *      1)  Calculate the appropriate working area for this operation
1505    *      2)  Allocate a temp buf for the dynamic programming array
1506    *      3)  Run the dynamic programming algorithm to find the optimal path
1507    *      4)  Translate the optimal path into pixels in the isegment data
1508    *            structure.
1509    */
1510 
1511   /*  Get the bounding box  */
1512   xs = CLAMP (segment->x1, 0, width  - 1);
1513   ys = CLAMP (segment->y1, 0, height - 1);
1514   xe = CLAMP (segment->x2, 0, width  - 1);
1515   ye = CLAMP (segment->y2, 0, height - 1);
1516   x1 = MIN (xs, xe);
1517   y1 = MIN (ys, ye);
1518   x2 = MAX (xs, xe) + 1;  /*  +1 because if xe = 199 & xs = 0, x2 - x1, width = 200  */
1519   y2 = MAX (ys, ye) + 1;
1520 
1521   /*  expand the boundaries past the ending points by
1522    *  some percentage of width and height.  This serves the following purpose:
1523    *  It gives the algorithm more area to search so better solutions
1524    *  are found.  This is particularly helpful in finding "bumps" which
1525    *  fall outside the bounding box represented by the start and end
1526    *  coordinates of the "segment".
1527    */
1528   ewidth  = (x2 - x1) * EXTEND_BY + FIXED;
1529   eheight = (y2 - y1) * EXTEND_BY + FIXED;
1530 
1531   if (xe >= xs)
1532     x2 += CLAMP (ewidth, 0, width - x2);
1533   else
1534     x1 -= CLAMP (ewidth, 0, x1);
1535 
1536   if (ye >= ys)
1537     y2 += CLAMP (eheight, 0, height - y2);
1538   else
1539     y1 -= CLAMP (eheight, 0, y1);
1540 
1541   /* blow away any previous points list we might have */
1542   if (segment->points)
1543     {
1544       g_ptr_array_free (segment->points, TRUE);
1545       segment->points = NULL;
1546     }
1547 
1548   if ((x2 - x1) && (y2 - y1))
1549     {
1550       /*  If the bounding box has width and height...  */
1551 
1552       GimpTempBuf *dp_buf; /*  dynamic programming buffer  */
1553       gint         dp_width  = (x2 - x1);
1554       gint         dp_height = (y2 - y1);
1555 
1556       dp_buf = gimp_temp_buf_new (dp_width, dp_height,
1557                                   babl_format ("Y u32"));
1558 
1559       /*  find the optimal path of pixels from (x1, y1) to (x2, y2)  */
1560       find_optimal_path (iscissors->gradient_map, dp_buf,
1561                          x1, y1, x2, y2, xs, ys);
1562 
1563       /*  get a list of the pixels in the optimal path  */
1564       segment->points = plot_pixels (dp_buf, x1, y1, xs, ys, xe, ye);
1565 
1566       gimp_temp_buf_unref (dp_buf);
1567     }
1568   else if ((x2 - x1) == 0)
1569     {
1570       /*  If the bounding box has no width  */
1571 
1572       /*  plot a vertical line  */
1573       gint y   = ys;
1574       gint dir = (ys > ye) ? -1 : 1;
1575 
1576       segment->points = g_ptr_array_new ();
1577       while (y != ye)
1578         {
1579           g_ptr_array_add (segment->points, GINT_TO_POINTER ((y << 16) + xs));
1580           y += dir;
1581         }
1582     }
1583   else if ((y2 - y1) == 0)
1584     {
1585       /*  If the bounding box has no height  */
1586 
1587       /*  plot a horizontal line  */
1588       gint x   = xs;
1589       gint dir = (xs > xe) ? -1 : 1;
1590 
1591       segment->points = g_ptr_array_new ();
1592       while (x != xe)
1593         {
1594           g_ptr_array_add (segment->points, GINT_TO_POINTER ((ys << 16) + x));
1595           x += dir;
1596         }
1597     }
1598 }
1599 
1600 
1601 /* badly need to get a replacement - this is _way_ too expensive */
1602 static gboolean
gradient_map_value(GeglSampler * map_sampler,const GeglRectangle * map_extent,gint x,gint y,guint8 * grad,guint8 * dir)1603 gradient_map_value (GeglSampler         *map_sampler,
1604                     const GeglRectangle *map_extent,
1605                     gint                 x,
1606                     gint                 y,
1607                     guint8              *grad,
1608                     guint8              *dir)
1609 {
1610   if (x >= map_extent->x     &&
1611       y >= map_extent->y     &&
1612       x <  map_extent->width &&
1613       y <  map_extent->height)
1614     {
1615       guint8 sample[2];
1616 
1617       gegl_sampler_get (map_sampler, x, y, NULL, sample, GEGL_ABYSS_NONE);
1618 
1619       *grad = sample[0];
1620       *dir  = sample[1];
1621 
1622       return TRUE;
1623     }
1624 
1625   return FALSE;
1626 }
1627 
1628 static gint
calculate_link(GeglSampler * map_sampler,const GeglRectangle * map_extent,gint x,gint y,guint32 pixel,gint link)1629 calculate_link (GeglSampler         *map_sampler,
1630                 const GeglRectangle *map_extent,
1631                 gint                 x,
1632                 gint                 y,
1633                 guint32              pixel,
1634                 gint                 link)
1635 {
1636   gint   value = 0;
1637   guint8 grad1, dir1, grad2, dir2;
1638 
1639   if (! gradient_map_value (map_sampler, map_extent, x, y, &grad1, &dir1))
1640     {
1641       grad1 = 0;
1642       dir1 = 255;
1643     }
1644 
1645   /* Convert the gradient into a cost: large gradients are good, and
1646    * so have low cost. */
1647   grad1 = 255 - grad1;
1648 
1649   /*  calculate the contribution of the gradient magnitude  */
1650   if (link > 1)
1651     value += diagonal_weight[grad1] * OMEGA_G;
1652   else
1653     value += grad1 * OMEGA_G;
1654 
1655   /*  calculate the contribution of the gradient direction  */
1656   x += (gint8)(pixel & 0xff);
1657   y += (gint8)((pixel & 0xff00) >> 8);
1658 
1659   if (! gradient_map_value (map_sampler, map_extent, x, y, &grad2, &dir2))
1660     {
1661       grad2 = 0;
1662       dir2 = 255;
1663     }
1664 
1665   value +=
1666     (direction_value[dir1][link] + direction_value[dir2][link]) * OMEGA_D;
1667 
1668   return value;
1669 }
1670 
1671 
1672 static GPtrArray *
plot_pixels(GimpTempBuf * dp_buf,gint x1,gint y1,gint xs,gint ys,gint xe,gint ye)1673 plot_pixels (GimpTempBuf *dp_buf,
1674              gint         x1,
1675              gint         y1,
1676              gint         xs,
1677              gint         ys,
1678              gint         xe,
1679              gint         ye)
1680 {
1681   gint       x, y;
1682   guint32    coords;
1683   gint       link;
1684   gint       width = gimp_temp_buf_get_width (dp_buf);
1685   guint     *data;
1686   GPtrArray *list;
1687 
1688   /*  Start the data pointer at the correct location  */
1689   data = (guint *) gimp_temp_buf_get_data (dp_buf) + (ye - y1) * width + (xe - x1);
1690 
1691   x = xe;
1692   y = ye;
1693 
1694   list = g_ptr_array_new ();
1695 
1696   while (TRUE)
1697     {
1698       coords = (y << 16) + x;
1699       g_ptr_array_add (list, GINT_TO_POINTER (coords));
1700 
1701       link = PIXEL_DIR (*data);
1702       if (link == SEED_POINT)
1703         return list;
1704 
1705       x += move[link][0];
1706       y += move[link][1];
1707       data += move[link][1] * width + move[link][0];
1708     }
1709 
1710   /*  won't get here  */
1711   return NULL;
1712 }
1713 
1714 
1715 #define PACK(x, y)    ((((y) & 0xff) << 8) | ((x) & 0xff))
1716 #define OFFSET(pixel) ((gint8)((pixel) & 0xff) + \
1717                        ((gint8)(((pixel) & 0xff00) >> 8)) * \
1718                        gimp_temp_buf_get_width (dp_buf))
1719 
1720 static void
find_optimal_path(GeglBuffer * gradient_map,GimpTempBuf * dp_buf,gint x1,gint y1,gint x2,gint y2,gint xs,gint ys)1721 find_optimal_path (GeglBuffer  *gradient_map,
1722                    GimpTempBuf *dp_buf,
1723                    gint         x1,
1724                    gint         y1,
1725                    gint         x2,
1726                    gint         y2,
1727                    gint         xs,
1728                    gint         ys)
1729 {
1730   GeglSampler         *map_sampler;
1731   const GeglRectangle *map_extent;
1732   gint                 i, j, k;
1733   gint                 x, y;
1734   gint                 link;
1735   gint                 linkdir;
1736   gint                 dirx, diry;
1737   gint                 min_cost;
1738   gint                 new_cost;
1739   gint                 offset;
1740   gint                 cum_cost[8];
1741   gint                 link_cost[8];
1742   gint                 pixel_cost[8];
1743   guint32              pixel[8];
1744   guint32             *data;
1745   guint32             *d;
1746   gint                 dp_buf_width  = gimp_temp_buf_get_width  (dp_buf);
1747   gint                 dp_buf_height = gimp_temp_buf_get_height (dp_buf);
1748 
1749   /*  initialize the gradient map sampler and extent  */
1750   map_sampler = gegl_buffer_sampler_new (gradient_map,
1751                                          gegl_buffer_get_format (gradient_map),
1752                                          GEGL_SAMPLER_NEAREST);
1753   map_extent  = gegl_buffer_get_extent (gradient_map);
1754 
1755   /*  initialize the dynamic programming buffer  */
1756   data = (guint32 *) gimp_temp_buf_data_clear (dp_buf);
1757 
1758   /*  what directions are we filling the array in according to?  */
1759   dirx = (xs - x1 == 0) ? 1 : -1;
1760   diry = (ys - y1 == 0) ? 1 : -1;
1761   linkdir = (dirx * diry);
1762 
1763   y = ys;
1764 
1765   for (i = 0; i < dp_buf_height; i++)
1766     {
1767       x = xs;
1768 
1769       d = data + (y-y1) * dp_buf_width + (x-x1);
1770 
1771       for (j = 0; j < dp_buf_width; j++)
1772         {
1773           min_cost = G_MAXINT;
1774 
1775           /* pixel[] array encodes how to get to a neighbour, if possible.
1776            * 0 means no connection (eg edge).
1777            * Rest packed as bottom two bytes: y offset then x offset.
1778            * Initially, we assume we can't get anywhere.
1779            */
1780           for (k = 0; k < 8; k++)
1781             pixel[k] = 0;
1782 
1783           /*  Find the valid neighboring pixels  */
1784           /*  the previous pixel  */
1785           if (j)
1786             pixel[((dirx == 1) ? 4 : 0)] = PACK (-dirx, 0);
1787 
1788           /*  the previous row of pixels  */
1789           if (i)
1790             {
1791               pixel[((diry == 1) ? 5 : 1)] = PACK (0, -diry);
1792 
1793               link = (linkdir == 1) ? 3 : 2;
1794               if (j)
1795                 pixel[((diry == 1) ? (link + 4) : link)] = PACK (-dirx, -diry);
1796 
1797               link = (linkdir == 1) ? 2 : 3;
1798               if (j != dp_buf_width - 1)
1799                 pixel[((diry == 1) ? (link + 4) : link)] = PACK (dirx, -diry);
1800             }
1801 
1802           /*  find the minimum cost of going through each neighbor to reach the
1803            *  seed point...
1804            */
1805           link = -1;
1806           for (k = 0; k < 8; k ++)
1807             if (pixel[k])
1808               {
1809                 link_cost[k] = calculate_link (map_sampler, map_extent,
1810                                                xs + j*dirx, ys + i*diry,
1811                                                pixel [k],
1812                                                ((k > 3) ? k - 4 : k));
1813                 offset = OFFSET (pixel [k]);
1814                 pixel_cost[k] = PIXEL_COST (d[offset]);
1815                 cum_cost[k] = pixel_cost[k] + link_cost[k];
1816                 if (cum_cost[k] < min_cost)
1817                   {
1818                     min_cost = cum_cost[k];
1819                     link = k;
1820                   }
1821               }
1822 
1823           /*  If anything can be done...  */
1824           if (link >= 0)
1825             {
1826               /*  set the cumulative cost of this pixel and the new direction
1827                */
1828               *d = (cum_cost[link] << 8) + link;
1829 
1830               /*  possibly change the links from the other pixels to this pixel...
1831                *  these changes occur if a neighboring pixel will receive a lower
1832                *  cumulative cost by going through this pixel.
1833                */
1834               for (k = 0; k < 8; k ++)
1835                 if (pixel[k] && k != link)
1836                   {
1837                     /*  if the cumulative cost at the neighbor is greater than
1838                      *  the cost through the link to the current pixel, change the
1839                      *  neighbor's link to point to the current pixel.
1840                      */
1841                     new_cost = link_cost[k] + cum_cost[link];
1842                     if (pixel_cost[k] > new_cost)
1843                     {
1844                       /*  reverse the link direction   /--------------------\ */
1845                       offset = OFFSET (pixel[k]);
1846                       d[offset] = (new_cost << 8) + ((k > 3) ? k - 4 : k + 4);
1847                     }
1848                   }
1849             }
1850           /*  Set the seed point  */
1851           else if (!i && !j)
1852             {
1853               *d = SEED_POINT;
1854             }
1855 
1856           /*  increment the data pointer and the x counter  */
1857           d += dirx;
1858           x += dirx;
1859         }
1860 
1861       /*  increment the y counter  */
1862       y += diry;
1863     }
1864 
1865   g_object_unref (map_sampler);
1866 }
1867 
1868 static GeglBuffer *
gradient_map_new(GimpPickable * pickable)1869 gradient_map_new (GimpPickable *pickable)
1870 {
1871   GeglBuffer      *buffer;
1872   GeglTileHandler *handler;
1873 
1874   buffer = gimp_pickable_get_buffer (pickable);
1875 
1876   buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
1877                                             gegl_buffer_get_width  (buffer),
1878                                             gegl_buffer_get_height (buffer)),
1879                             babl_format_n (babl_type ("u8"), 2));
1880 
1881   handler = gimp_tile_handler_iscissors_new (pickable);
1882 
1883   gimp_tile_handler_validate_assign (GIMP_TILE_HANDLER_VALIDATE (handler),
1884                                      buffer);
1885 
1886   gimp_tile_handler_validate_invalidate (GIMP_TILE_HANDLER_VALIDATE (handler),
1887                                          GEGL_RECTANGLE (0, 0,
1888                                                          gegl_buffer_get_width  (buffer),
1889                                                          gegl_buffer_get_height (buffer)));
1890 
1891   g_object_unref (handler);
1892 
1893   return buffer;
1894 }
1895 
1896 static void
find_max_gradient(GimpIscissorsTool * iscissors,GimpPickable * pickable,gint * x,gint * y)1897 find_max_gradient (GimpIscissorsTool *iscissors,
1898                    GimpPickable      *pickable,
1899                    gint              *x,
1900                    gint              *y)
1901 {
1902   GeglBufferIterator *iter;
1903   GeglRectangle      *roi;
1904   gint                width;
1905   gint                height;
1906   gint                radius;
1907   gint                cx, cy;
1908   gint                x1, y1, x2, y2;
1909   gfloat              max_gradient;
1910 
1911   /* Initialise the gradient map buffer for this pickable if we don't
1912    * already have one.
1913    */
1914   if (! iscissors->gradient_map)
1915     iscissors->gradient_map = gradient_map_new (pickable);
1916 
1917   width  = gegl_buffer_get_width  (iscissors->gradient_map);
1918   height = gegl_buffer_get_height (iscissors->gradient_map);
1919 
1920   radius = GRADIENT_SEARCH >> 1;
1921 
1922   /*  calculate the extent of the search  */
1923   cx = CLAMP (*x, 0, width);
1924   cy = CLAMP (*y, 0, height);
1925   x1 = CLAMP (cx - radius, 0, width);
1926   y1 = CLAMP (cy - radius, 0, height);
1927   x2 = CLAMP (cx + radius, 0, width);
1928   y2 = CLAMP (cy + radius, 0, height);
1929   /*  calculate the factor to multiply the distance from the cursor by  */
1930 
1931   max_gradient = 0;
1932   *x = cx;
1933   *y = cy;
1934 
1935   iter = gegl_buffer_iterator_new (iscissors->gradient_map,
1936                                    GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
1937                                    0, NULL,
1938                                    GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
1939   roi = &iter->items[0].roi;
1940 
1941   while (gegl_buffer_iterator_next (iter))
1942     {
1943       guint8 *data = iter->items[0].data;
1944       gint    endx = roi->x + roi->width;
1945       gint    endy = roi->y + roi->height;
1946       gint    i, j;
1947 
1948       for (i = roi->y; i < endy; i++)
1949         {
1950           const guint8 *gradient = data + 2 * roi->width * (i - roi->y);
1951 
1952           for (j = roi->x; j < endx; j++)
1953             {
1954               gfloat g = *gradient;
1955 
1956               gradient += COST_WIDTH;
1957 
1958               g *= distance_weights [(i - y1) * GRADIENT_SEARCH + (j - x1)];
1959 
1960               if (g > max_gradient)
1961                 {
1962                   max_gradient = g;
1963 
1964                   *x = j;
1965                   *y = i;
1966                 }
1967             }
1968         }
1969     }
1970 }
1971 
1972 static ISegment *
isegment_new(gint x1,gint y1,gint x2,gint y2)1973 isegment_new (gint x1,
1974               gint y1,
1975               gint x2,
1976               gint y2)
1977 {
1978   ISegment *segment = g_slice_new0 (ISegment);
1979 
1980   segment->x1 = x1;
1981   segment->y1 = y1;
1982   segment->x2 = x2;
1983   segment->y2 = y2;
1984 
1985   return segment;
1986 }
1987 
1988 static ISegment *
isegment_copy(ISegment * segment)1989 isegment_copy (ISegment *segment)
1990 {
1991   ISegment *copy = isegment_new (segment->x1,
1992                                  segment->y1,
1993                                  segment->x2,
1994                                  segment->y2);
1995 
1996   if (segment->points)
1997     {
1998       gint i;
1999 
2000       copy->points = g_ptr_array_sized_new (segment->points->len);
2001 
2002       for (i = 0; i < segment->points->len; i++)
2003         {
2004           gpointer value = g_ptr_array_index (segment->points, i);
2005 
2006           g_ptr_array_add (copy->points, value);
2007         }
2008     }
2009 
2010   return copy;
2011 }
2012 
2013 static void
isegment_free(ISegment * segment)2014 isegment_free (ISegment *segment)
2015 {
2016   if (segment->points)
2017     g_ptr_array_free (segment->points, TRUE);
2018 
2019   g_slice_free (ISegment, segment);
2020 }
2021 
2022 static ICurve *
icurve_new(void)2023 icurve_new (void)
2024 {
2025   ICurve *curve = g_slice_new0 (ICurve);
2026 
2027   curve->segments    = g_queue_new ();
2028   curve->first_point = TRUE;
2029 
2030   return curve;
2031 }
2032 
2033 static ICurve *
icurve_copy(ICurve * curve)2034 icurve_copy (ICurve *curve)
2035 {
2036   ICurve *copy = icurve_new ();
2037   GList  *link;
2038 
2039   for (link = g_queue_peek_head_link (curve->segments);
2040        link;
2041        link = g_list_next (link))
2042     {
2043       g_queue_push_tail (copy->segments, isegment_copy (link->data));
2044     }
2045 
2046   copy->first_point = curve->first_point;
2047   copy->closed      = curve->closed;
2048 
2049   return copy;
2050 }
2051 
2052 static void
icurve_clear(ICurve * curve)2053 icurve_clear (ICurve *curve)
2054 {
2055   while (! g_queue_is_empty (curve->segments))
2056     isegment_free (g_queue_pop_head (curve->segments));
2057 
2058   curve->first_point = TRUE;
2059   curve->closed      = FALSE;
2060 }
2061 
2062 static void
icurve_free(ICurve * curve)2063 icurve_free (ICurve *curve)
2064 {
2065   g_queue_free_full (curve->segments, (GDestroyNotify) isegment_free);
2066 
2067   g_slice_free (ICurve, curve);
2068 }
2069 
2070 static ISegment *
icurve_append_segment(ICurve * curve,gint x1,gint y1,gint x2,gint y2)2071 icurve_append_segment (ICurve *curve,
2072                        gint    x1,
2073                        gint    y1,
2074                        gint    x2,
2075                        gint    y2)
2076 {
2077   ISegment *segment = isegment_new (x1, y1, x2, y2);
2078 
2079   g_queue_push_tail (curve->segments, segment);
2080 
2081   return segment;
2082 }
2083 
2084 static ISegment *
icurve_insert_segment(ICurve * curve,GList * sibling,gint x1,gint y1,gint x2,gint y2)2085 icurve_insert_segment (ICurve *curve,
2086                        GList  *sibling,
2087                        gint    x1,
2088                        gint    y1,
2089                        gint    x2,
2090                        gint    y2)
2091 {
2092   ISegment *segment = sibling->data;
2093   ISegment *new_segment;
2094 
2095   new_segment = isegment_new (x1, y1, x2, y2);
2096 
2097   segment->x2 = x1;
2098   segment->y2 = y1;
2099 
2100   g_queue_insert_after (curve->segments, sibling, new_segment);
2101 
2102   return new_segment;
2103 }
2104 
2105 static void
icurve_delete_segment(ICurve * curve,ISegment * segment)2106 icurve_delete_segment (ICurve   *curve,
2107                        ISegment *segment)
2108 {
2109   GList    *link         = g_queue_find (curve->segments, segment);
2110   ISegment *next_segment = NULL;
2111 
2112   if (link->next)
2113     next_segment = link->next->data;
2114   else if (curve->closed)
2115     next_segment = g_queue_peek_head (curve->segments);
2116 
2117   if (next_segment)
2118     {
2119       next_segment->x1 = segment->x1;
2120       next_segment->y1 = segment->y1;
2121     }
2122 
2123   g_queue_remove (curve->segments, segment);
2124   isegment_free (segment);
2125 }
2126 
2127 static void
icurve_close(ICurve * curve)2128 icurve_close (ICurve *curve)
2129 {
2130   ISegment *first = g_queue_peek_head (curve->segments);
2131   ISegment *last  = g_queue_peek_tail (curve->segments);
2132 
2133   last->x2 = first->x1;
2134   last->y2 = first->y1;
2135 
2136   curve->closed = TRUE;
2137 }
2138 
2139 static GimpScanConvert *
icurve_create_scan_convert(ICurve * curve)2140 icurve_create_scan_convert (ICurve *curve)
2141 {
2142   GimpScanConvert *sc;
2143   GList           *list;
2144   GimpVector2     *points;
2145   guint            n_total_points = 0;
2146 
2147   sc = gimp_scan_convert_new ();
2148 
2149   for (list = g_queue_peek_tail_link (curve->segments);
2150        list;
2151        list = g_list_previous (list))
2152     {
2153       ISegment *segment = list->data;
2154 
2155       n_total_points += segment->points->len;
2156     }
2157 
2158   points = g_new (GimpVector2, n_total_points);
2159   n_total_points = 0;
2160 
2161   /* go over the segments in reverse order, adding the points we have */
2162   for (list = g_queue_peek_tail_link (curve->segments);
2163        list;
2164        list = g_list_previous (list))
2165     {
2166       ISegment *segment = list->data;
2167       guint     n_points;
2168       gint      i;
2169 
2170       n_points = segment->points->len;
2171 
2172       for (i = 0; i < n_points; i++)
2173         {
2174           guint32 packed = GPOINTER_TO_INT (g_ptr_array_index (segment->points,
2175                                                                i));
2176 
2177           points[n_total_points + i].x = packed & 0x0000ffff;
2178           points[n_total_points + i].y = packed >> 16;
2179         }
2180 
2181       n_total_points += n_points;
2182     }
2183 
2184   gimp_scan_convert_add_polyline (sc, n_total_points, points, TRUE);
2185   g_free (points);
2186 
2187   return sc;
2188 }
2189