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