/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This tool is based on a paper from SIGGRAPH '95: * "Intelligent Scissors for Image Composition", Eric N. Mortensen and * William A. Barrett, Brigham Young University. * * thanks to Professor D. Forsyth for prompting us to implement this tool. */ /* The history of this implementation is lonog and varied. It was * orignally done by Spencer and Peter, and worked fine in the 0.54 * (motif only) release of the gimp. Later revisions (0.99.something * until about 1.1.4) completely changed the algorithm used, until it * bore little resemblance to the one described in the paper above. * The 0.54 version of the algorithm was then forwards ported to 1.1.4 * by Austin Donnelly. */ /* Livewire boundary implementation done by Laramie Leavitt */ #include "config.h" #include #include #include #include "libgimpmath/gimpmath.h" #include "apptypes.h" #include "draw_core.h" #include "cursorutil.h" #include "drawable.h" #include "gdisplay.h" #include "gimage_mask.h" #include "gimpchannel.h" #include "gimpimage.h" #include "paint_funcs.h" #include "pixel_region.h" #include "temp_buf.h" #include "scan_convert.h" #include "tile.h" #include "tile_manager.h" #include "bezier_selectP.h" #include "iscissors.h" #include "edit_selection.h" #include "selection_options.h" #include "tools.h" /* local structures */ typedef struct _ICurve ICurve; struct _ICurve { int x1, y1; int x2, y2; GPtrArray *points; }; /* The possible states... */ typedef enum { NO_ACTION, SEED_PLACEMENT, SEED_ADJUSTMENT, WAITING } Iscissors_state; /* The possible drawing states... */ typedef enum { DRAW_NOTHING = 0x0, DRAW_CURRENT_SEED = 0x1, DRAW_CURVE = 0x2, DRAW_ACTIVE_CURVE = 0x4, DRAW_LIVEWIRE = 0x8 } Iscissors_draw; #define DRAW_ALL (DRAW_CURRENT_SEED | DRAW_CURVE) typedef struct _iscissors Iscissors; struct _iscissors { DrawCore *core; /* Core select object */ SelectOps op; gint x, y; /* upper left hand coordinate */ gint ix, iy; /* initial coordinates */ gint nx, ny; /* new coordinates */ TempBuf *dp_buf; /* dynamic programming buffer */ ICurve *livewire; /* livewire boundary curve */ ICurve *curve1; /* 1st curve connected to current point */ ICurve *curve2; /* 2nd curve connected to current point */ GSList *curves; /* the list of curves */ gboolean first_point; /* is this the first point? */ gboolean connected; /* is the region closed? */ Iscissors_state state; /* state of iscissors */ Iscissors_draw draw; /* items to draw on a draw request */ /* XXX might be useful */ GimpChannel *mask; /* selection mask */ TileManager *gradient_map; /* lazily filled gradient map */ }; typedef struct _IScissorsOptions IScissorsOptions; struct _IScissorsOptions { SelectionOptions selection_options; }; /**********************************************/ /* Intelligent scissors selection apparatus */ /* Other defines... */ #define MAX_GRADIENT 179.606 /* == sqrt(127^2 + 127^2) */ #define GRADIENT_SEARCH 32 /* how far to look when snapping to an edge */ #define TARGET_HEIGHT 25 #define TARGET_WIDTH 25 #define POINT_WIDTH 8 /* size (in pixels) of seed handles */ #define POINT_HALFWIDTH (POINT_WIDTH / 2) #define EXTEND_BY 0.2 /* proportion to expand cost map by */ #define FIXED 5 /* additional fixed size to expand cost map */ #define MIN_GRADIENT 63 /* gradients < this are directionless */ #define MAX_POINTS 2048 #define COST_WIDTH 2 /* number of bytes for each pixel in cost map */ #define BLOCK_WIDTH 64 #define BLOCK_HEIGHT 64 #define CONV_WIDTH (BLOCK_WIDTH + 2) #define CONV_HEIGHT (BLOCK_HEIGHT + 2) /* weight to give between gradient (_G) and direction (_D) */ #define OMEGA_D 0.2 #define OMEGA_G 0.8 /* sentinel to mark seed point in ?cost? map */ #define SEED_POINT 9 /* Functional defines */ #define PIXEL_COST(x) (x >> 8) #define PIXEL_DIR(x) (x & 0x000000ff) /* static variables */ /* where to move on a given link direction */ static gint move [8][2] = { { 1, 0 }, { 0, 1 }, { -1, 1 }, { 1, 1 }, { -1, 0 }, { 0, -1 }, { 1, -1 }, { -1, -1 }, }; /* IE: * '---+---+---` * | 7 | 5 | 6 | * +---+---+---+ * | 4 | | 0 | * +---+---+---+ * | 2 | 1 | 3 | * `---+---+---' */ /* points for drawing curves */ static GdkPoint curve_points [MAX_POINTS]; /* temporary convolution buffers -- */ static guchar maxgrad_conv0 [TILE_WIDTH * TILE_HEIGHT * 4] = ""; static guchar maxgrad_conv1 [TILE_WIDTH * TILE_HEIGHT * 4] = ""; static guchar maxgrad_conv2 [TILE_WIDTH * TILE_HEIGHT * 4] = ""; static gint horz_deriv [9] = { 1, 0, -1, 2, 0, -2, 1, 0, -1, }; static gint vert_deriv [9] = { 1, 2, 1, 0, 0, 0, -1, -2, -1, }; static gint blur_32 [9] = { 1, 1, 1, 1, 24, 1, 1, 1, 1, }; static gfloat distance_weights [GRADIENT_SEARCH * GRADIENT_SEARCH]; static gint diagonal_weight [256]; static gint direction_value [256][4]; static gboolean initialized = FALSE; static Tile *cur_tile = NULL; /***********************************************************************/ /* static variables */ static IScissorsOptions *iscissors_options = NULL; /***********************************************************************/ /* Local function prototypes */ static void iscissors_button_press (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp); static void iscissors_button_release (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp); static void iscissors_motion (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp); static void iscissors_oper_update (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp); static void iscissors_modifier_update (Tool *tool, GdkEventKey *kevent, GDisplay *gdisp); static void iscissors_cursor_update (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp); static void iscissors_control (Tool *tool, ToolAction tool_action, GDisplay *gdisp); static void iscissors_reset (Iscissors *iscissors); static void iscissors_draw (Tool *tool); static TileManager * gradient_map_new (GImage *gimage); static void find_optimal_path (TileManager *gradient_map, TempBuf *dp_buf, gint x1, gint y1, gint x2, gint y2, gint xs, gint ys); static void find_max_gradient (Iscissors *iscissors, GImage *gimage, gint *x, gint *y); static void calculate_curve (Tool *tool, ICurve *curve); static void iscissors_draw_curve (GDisplay *gdisp, Iscissors *iscissors, ICurve *curve); static void iscissors_free_icurves (GSList *list); static void iscissors_free_buffers (Iscissors *iscissors); static gint mouse_over_vertex (Iscissors *iscissors, gint x, gint y); static gboolean clicked_on_vertex (Tool *tool); static GSList * mouse_over_curve (Iscissors *iscissors, gint x, gint y); static gboolean clicked_on_curve (Tool *tool); static void precalculate_arrays (void); static GPtrArray * plot_pixels (Iscissors *iscissors, TempBuf *dp_buf, gint x1, gint y1, gint xs, gint ys, gint xe, gint ye); static void iscissors_options_reset (void) { IScissorsOptions *options = iscissors_options; selection_options_reset ((SelectionOptions *) options); } static IScissorsOptions * iscissors_options_new (void) { IScissorsOptions *options; /* the new intelligent scissors tool options structure */ options = g_new (IScissorsOptions, 1); selection_options_init ((SelectionOptions *) options, ISCISSORS, iscissors_options_reset); return options; } Tool * tools_new_iscissors (void) { Tool * tool; Iscissors * private; if (!iscissors_options) { iscissors_options = iscissors_options_new (); tools_register (ISCISSORS, (ToolOptions *) iscissors_options); } tool = tools_new_tool (ISCISSORS); private = g_new0 (Iscissors, 1); private->core = draw_core_new (iscissors_draw); private->op = -1; private->curves = NULL; private->dp_buf = NULL; private->state = NO_ACTION; private->mask = NULL; private->gradient_map = NULL; private->livewire = NULL; tool->auto_snap_to = FALSE; /* Don't snap to guides */ tool->private = (void *) private; tool->button_press_func = iscissors_button_press; tool->button_release_func = iscissors_button_release; tool->motion_func = iscissors_motion; tool->oper_update_func = iscissors_oper_update; tool->modifier_key_func = iscissors_modifier_update; tool->cursor_update_func = iscissors_cursor_update; tool->control_func = iscissors_control; iscissors_reset (private); return tool; } void tools_free_iscissors (Tool *tool) { Iscissors * iscissors; iscissors = (Iscissors *) tool->private; if (tool->state == ACTIVE) draw_core_stop (iscissors->core, tool); draw_core_free (iscissors->core); iscissors_reset (iscissors); g_free (iscissors); } /* Local functions */ static void iscissors_button_press (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { GimpDrawable *drawable; Iscissors *iscissors; gboolean grab_pointer = FALSE; iscissors = (Iscissors *) tool->private; drawable = gimp_image_active_drawable (gdisp->gimage); gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &iscissors->x, &iscissors->y, FALSE, FALSE); /* If the tool was being used in another image...reset it */ if (tool->state == ACTIVE && gdisp != tool->gdisp) { draw_core_stop (iscissors->core, tool); iscissors_reset (iscissors); } tool->state = ACTIVE; tool->gdisp = gdisp; switch (iscissors->state) { case NO_ACTION: iscissors->state = SEED_PLACEMENT; iscissors->draw = DRAW_CURRENT_SEED; grab_pointer = TRUE; if (! (bevent->state & GDK_SHIFT_MASK)) find_max_gradient (iscissors, gdisp->gimage, &iscissors->x, &iscissors->y); iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1); iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1); iscissors->ix = iscissors->x; iscissors->iy = iscissors->y; /* Initialize the selection core only on starting the tool */ draw_core_start (iscissors->core, gdisp->canvas->window, tool); break; default: /* Check if the mouse click occured on a vertex or the curve itself */ if (clicked_on_vertex (tool)) { iscissors->nx = iscissors->x; iscissors->ny = iscissors->y; iscissors->state = SEED_ADJUSTMENT; iscissors->draw = DRAW_ACTIVE_CURVE; if (((SelectionOptions *) iscissors_options)->interactive) iscissors->draw |= DRAW_LIVEWIRE; draw_core_resume (iscissors->core, tool); grab_pointer = TRUE; } /* If the iscissors is connected, check if the click was inside */ else if (iscissors->connected && iscissors->mask && gimp_channel_value (iscissors->mask, iscissors->x, iscissors->y)) { /* Undraw the curve */ tool->state = INACTIVE; iscissors->draw = DRAW_CURVE; draw_core_stop (iscissors->core, tool); if (iscissors->op == SELECTION_REPLACE) gimage_mask_clear (gdisp->gimage); else gimage_mask_undo (gdisp->gimage); if (((SelectionOptions *) iscissors_options)->feather) gimp_channel_feather (iscissors->mask, gimp_image_get_mask (gdisp->gimage), ((SelectionOptions *) iscissors_options)->feather_radius, ((SelectionOptions *) iscissors_options)->feather_radius, iscissors->op, 0, 0); else gimp_channel_combine_mask (gimp_image_get_mask (gdisp->gimage), iscissors->mask, iscissors->op, 0, 0); iscissors_reset (iscissors); gdisplays_flush (); } /* if we're not connected, we're adding a new point */ else if (!iscissors->connected) { iscissors->state = SEED_PLACEMENT; iscissors->draw = DRAW_CURRENT_SEED; if (((SelectionOptions *) iscissors_options)->interactive) iscissors->draw |= DRAW_LIVEWIRE; grab_pointer = TRUE; draw_core_resume (iscissors->core, tool); } break; } if (grab_pointer) gdk_pointer_grab (gdisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, bevent->time); } static void iscissors_convert (Iscissors *iscissors, GDisplay *gdisp) { ScanConverter *sc; ScanConvertPoint *pts; guint npts; GSList *list; ICurve *icurve; guint packed; gint i; gint index; sc = scan_converter_new (gdisp->gimage->width, gdisp->gimage->height, 1); /* go over the curves in reverse order, adding the points we have */ list = iscissors->curves; index = g_slist_length (list); while (index) { index--; icurve = (ICurve *) g_slist_nth_data (list, index); npts = icurve->points->len; pts = g_new (ScanConvertPoint, npts); for (i = 0; i < npts; i ++) { packed = GPOINTER_TO_INT (g_ptr_array_index (icurve->points, i)); pts[i].x = packed & 0x0000ffff; pts[i].y = packed >> 16; } scan_converter_add_points (sc, npts, pts); g_free (pts); } if (iscissors->mask) gtk_object_unref (GTK_OBJECT (iscissors->mask)); iscissors->mask = scan_converter_to_channel (sc, gdisp->gimage); scan_converter_free (sc); gimp_channel_invalidate_bounds (iscissors->mask); } static void iscissors_button_release (Tool *tool, GdkEventButton *bevent, GDisplay *gdisp) { Iscissors *iscissors; ICurve *curve; iscissors = (Iscissors *) tool->private; /* Make sure X didn't skip the button release event -- as it's known * to do */ if (iscissors->state == WAITING) return; gdk_pointer_ungrab (bevent->time); gdk_flush (); /* XXX why the flush? */ /* Undraw everything */ switch (iscissors->state) { case SEED_PLACEMENT: iscissors->draw = DRAW_CURVE | DRAW_CURRENT_SEED; if (((SelectionOptions *) iscissors_options)->interactive) iscissors->draw |= DRAW_LIVEWIRE; break; case SEED_ADJUSTMENT: iscissors->draw = DRAW_CURVE | DRAW_ACTIVE_CURVE; if (((SelectionOptions *) iscissors_options)->interactive) iscissors->draw |= DRAW_LIVEWIRE; break; default: break; } draw_core_stop (iscissors->core, tool); /* First take care of the case where the user "cancels" the action */ if (! (bevent->state & GDK_BUTTON3_MASK)) { /* Progress to the next stage of intelligent selection */ switch (iscissors->state) { case SEED_PLACEMENT: /* Add a new icurve */ if (!iscissors->first_point) { /* Determine if we're connecting to the first point */ if (iscissors->curves) { curve = (ICurve *) iscissors->curves->data; if (abs (iscissors->x - curve->x1) < POINT_HALFWIDTH && abs (iscissors->y - curve->y1) < POINT_HALFWIDTH) { iscissors->x = curve->x1; iscissors->y = curve->y1; iscissors->connected = TRUE; } } /* Create the new curve segment */ if (iscissors->ix != iscissors->x || iscissors->iy != iscissors->y) { curve = g_new (ICurve, 1); curve->x1 = iscissors->ix; curve->y1 = iscissors->iy; iscissors->ix = curve->x2 = iscissors->x; iscissors->iy = curve->y2 = iscissors->y; curve->points = NULL; iscissors->curves = g_slist_append (iscissors->curves, (void *) curve); calculate_curve (tool, curve); } } else /* this was our first point */ { iscissors->first_point = FALSE; } break; case SEED_ADJUSTMENT: /* recalculate both curves */ if (iscissors->curve1) { iscissors->curve1->x1 = iscissors->nx; iscissors->curve1->y1 = iscissors->ny; calculate_curve (tool, iscissors->curve1); } if (iscissors->curve2) { iscissors->curve2->x2 = iscissors->nx; iscissors->curve2->y2 = iscissors->ny; calculate_curve (tool, iscissors->curve2); } break; default: break; } } /* Draw only the boundary */ iscissors->state = WAITING; iscissors->draw = DRAW_CURVE; draw_core_resume (iscissors->core, tool); /* convert the curves into a region */ if (iscissors->connected) iscissors_convert (iscissors, gdisp); } static void iscissors_motion (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { Iscissors *iscissors; iscissors = (Iscissors *) tool->private; if (tool->state != ACTIVE || iscissors->state == NO_ACTION) return; if (iscissors->state == SEED_PLACEMENT) { iscissors->draw = DRAW_CURRENT_SEED; if (((SelectionOptions *) iscissors_options)->interactive) iscissors->draw = DRAW_CURRENT_SEED | DRAW_LIVEWIRE; } else if (iscissors->state == SEED_ADJUSTMENT) { iscissors->draw = DRAW_ACTIVE_CURVE; } draw_core_pause (iscissors->core, tool); gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &iscissors->x, &iscissors->y, FALSE, FALSE); switch (iscissors->state) { case SEED_PLACEMENT: /* Hold the shift key down to disable the auto-edge snap feature */ if (! (mevent->state & GDK_SHIFT_MASK)) find_max_gradient (iscissors, gdisp->gimage, &iscissors->x, &iscissors->y); iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1); iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1); if (iscissors->first_point) { iscissors->ix = iscissors->x; iscissors->iy = iscissors->y; } break; case SEED_ADJUSTMENT: /* Move the current seed to the location of the cursor */ if (! (mevent->state & GDK_SHIFT_MASK)) find_max_gradient (iscissors, gdisp->gimage, &iscissors->x, &iscissors->y); iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1); iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1); iscissors->nx = iscissors->x; iscissors->ny = iscissors->y; break; default: break; } draw_core_resume (iscissors->core, tool); } static void iscissors_draw (Tool *tool) { GDisplay *gdisp; Iscissors *iscissors; ICurve *curve; GSList *list; gint tx1, ty1, tx2, ty2; gint txn, tyn; gdisp = tool->gdisp; iscissors = (Iscissors *) tool->private; gdisplay_transform_coords (gdisp, iscissors->ix, iscissors->iy, &tx1, &ty1, FALSE); /* Draw the crosshairs target if we're placing a seed */ if (iscissors->draw & DRAW_CURRENT_SEED) { gdisplay_transform_coords (gdisp, iscissors->x, iscissors->y, &tx2, &ty2, FALSE); gdk_draw_line (iscissors->core->win, iscissors->core->gc, tx2 - (TARGET_WIDTH >> 1), ty2, tx2 + (TARGET_WIDTH >> 1), ty2); gdk_draw_line (iscissors->core->win, iscissors->core->gc, tx2, ty2 - (TARGET_HEIGHT >> 1), tx2, ty2 + (TARGET_HEIGHT >> 1)); /* Draw a line boundary */ if (!iscissors->first_point && !(iscissors->draw & DRAW_LIVEWIRE)) { gdk_draw_line (iscissors->core->win, iscissors->core->gc, tx1, ty1, tx2, ty2); } } /* Draw the livewire boundary */ if ((iscissors->draw & DRAW_LIVEWIRE) && !iscissors->first_point) { /* See if the mouse has moved. If so, create a new segment... */ if (! iscissors->livewire || (iscissors->livewire && (iscissors->ix != iscissors->livewire->x1 || iscissors->x != iscissors->livewire->x2 || iscissors->iy != iscissors->livewire->y1 || iscissors->y != iscissors->livewire->y2))) { curve = g_new (ICurve, 1); curve->x1 = iscissors->ix; curve->y1 = iscissors->iy; curve->x2 = iscissors->x; curve->y2 = iscissors->y; curve->points = NULL; if (iscissors->livewire) { if (iscissors->livewire->points) g_ptr_array_free (iscissors->livewire->points, TRUE); g_free (iscissors->livewire); iscissors->livewire = NULL; } iscissors->livewire = curve; calculate_curve (tool, curve); curve = NULL; } /* plot the curve */ iscissors_draw_curve (gdisp, iscissors, iscissors->livewire); } if ((iscissors->draw & DRAW_CURVE) && !iscissors->first_point) { /* Draw a point at the init point coordinates */ if (!iscissors->connected) gdk_draw_arc (iscissors->core->win, iscissors->core->gc, 1, tx1 - POINT_HALFWIDTH, ty1 - POINT_HALFWIDTH, POINT_WIDTH, POINT_WIDTH, 0, 23040); /* Go through the list of icurves, and render each one... */ for (list = iscissors->curves; list; list = g_slist_next (list)) { curve = (ICurve *) list->data; /* plot the curve */ iscissors_draw_curve (gdisp, iscissors, curve); gdisplay_transform_coords (gdisp, curve->x1, curve->y1, &tx1, &ty1, FALSE); gdk_draw_arc (iscissors->core->win, iscissors->core->gc, 1, tx1 - POINT_HALFWIDTH, ty1 - POINT_HALFWIDTH, POINT_WIDTH, POINT_WIDTH, 0, 23040); } } if (iscissors->draw & DRAW_ACTIVE_CURVE) { gdisplay_transform_coords (gdisp, iscissors->nx, iscissors->ny, &txn, &tyn, FALSE); /* plot both curves, and the control point between them */ if (iscissors->curve1) { gdisplay_transform_coords (gdisp, iscissors->curve1->x2, iscissors->curve1->y2, &tx1, &ty1, FALSE); gdk_draw_line (iscissors->core->win, iscissors->core->gc, tx1, ty1, txn, tyn); } if (iscissors->curve2) { gdisplay_transform_coords (gdisp, iscissors->curve2->x1, iscissors->curve2->y1, &tx2, &ty2, FALSE); gdk_draw_line (iscissors->core->win, iscissors->core->gc, tx2, ty2, txn, tyn); } gdk_draw_arc (iscissors->core->win, iscissors->core->gc, 1, txn - POINT_HALFWIDTH, tyn - POINT_HALFWIDTH, POINT_WIDTH, POINT_WIDTH, 0, 23040); } } static void iscissors_draw_curve (GDisplay *gdisp, Iscissors *iscissors, ICurve *curve) { gpointer *point; guint len; gint tx, ty; gint npts = 0; guint32 coords; /* Uh, this shouldn't happen, but it does. So we ignore it. * Quality code, baby. */ if (!curve->points) return; point = curve->points->pdata; len = curve->points->len; while (len--) { coords = GPOINTER_TO_INT (*point); point++; gdisplay_transform_coords (gdisp, (coords & 0x0000ffff), (coords >> 16), &tx, &ty, FALSE); if (npts < MAX_POINTS) { curve_points [npts].x = tx; curve_points [npts].y = ty; npts ++; } else { g_warning ("too many points in ICurve segment!"); return; } } /* draw the curve */ gdk_draw_lines (iscissors->core->win, iscissors->core->gc, curve_points, npts); } static void iscissors_oper_update (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { Iscissors *iscissors; gint x, y; iscissors = (Iscissors *) tool->private; gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &x, &y, FALSE, FALSE); if (mouse_over_vertex (iscissors, x, y)) { iscissors->op = SELECTION_MOVE_MASK; /* abused */ } else if (mouse_over_curve (iscissors, x, y)) { iscissors->op = SELECTION_MOVE; /* abused */ } else if (iscissors->connected && iscissors->mask && gimp_channel_value (iscissors->mask, x, y)) { if (mevent->state & GDK_SHIFT_MASK && mevent->state & GDK_CONTROL_MASK) { iscissors->op = SELECTION_INTERSECT; } else if (mevent->state & GDK_SHIFT_MASK) { iscissors->op = SELECTION_ADD; } else if (mevent->state & GDK_CONTROL_MASK) { iscissors->op = SELECTION_SUB; } else { iscissors->op = SELECTION_REPLACE; } } else if (iscissors->connected && iscissors->mask) { iscissors->op = -1; } else { iscissors->op = -2; } } static void iscissors_modifier_update (Tool *tool, GdkEventKey *kevent, GDisplay *gdisp) { Iscissors *iscissors; SelectOps op; iscissors = (Iscissors *) tool->private; op = iscissors->op; if (op == -2) return; switch (kevent->keyval) { case GDK_Shift_L: case GDK_Shift_R: if (op == SELECTION_REPLACE) op = SELECTION_ADD; else if (op == SELECTION_ADD) op = SELECTION_REPLACE; else if (op == SELECTION_SUB) op = SELECTION_INTERSECT; else if (op == SELECTION_INTERSECT) op = SELECTION_SUB; break; case GDK_Control_L: case GDK_Control_R: if (op == SELECTION_REPLACE) op = SELECTION_SUB; else if (op == SELECTION_ADD) op = SELECTION_INTERSECT; else if (op == SELECTION_SUB) op = SELECTION_REPLACE; else if (op == SELECTION_INTERSECT) op = SELECTION_ADD; break; } iscissors->op = op; } static void iscissors_cursor_update (Tool *tool, GdkEventMotion *mevent, GDisplay *gdisp) { Iscissors *iscissors; iscissors = (Iscissors *) tool->private; switch (iscissors->op) { case SELECTION_REPLACE: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_RECT_SELECT_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_NONE); break; case SELECTION_ADD: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_RECT_SELECT_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_PLUS); break; case SELECTION_SUB: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_RECT_SELECT_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_MINUS); break; case SELECTION_INTERSECT: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_RECT_SELECT_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_INTERSECT); break; case SELECTION_MOVE_MASK: /* abused */ gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_ISCISSORS_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_MOVE); break; case SELECTION_MOVE: /* abused */ gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_ISCISSORS_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_PLUS); break; case -1: gdisplay_install_tool_cursor (gdisp, GIMP_BAD_CURSOR, GIMP_ISCISSORS_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_NONE); break; default: switch (iscissors->state) { case WAITING: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_ISCISSORS_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_PLUS); break; case SEED_PLACEMENT: case SEED_ADJUSTMENT: default: gdisplay_install_tool_cursor (gdisp, GIMP_MOUSE_CURSOR, GIMP_ISCISSORS_TOOL_CURSOR, GIMP_CURSOR_MODIFIER_NONE); break; } } } static void iscissors_control (Tool *tool, ToolAction action, GDisplay *gdisp) { Iscissors * iscissors; Iscissors_draw draw; iscissors = (Iscissors *) tool->private; switch (iscissors->state) { case SEED_PLACEMENT: draw = DRAW_CURVE | DRAW_CURRENT_SEED; break; case SEED_ADJUSTMENT: draw = DRAW_CURVE | DRAW_ACTIVE_CURVE; break; default: draw = DRAW_CURVE; break; } iscissors->draw = draw; switch (action) { case PAUSE: draw_core_pause (iscissors->core, tool); break; case RESUME: draw_core_resume (iscissors->core, tool); break; case HALT: draw_core_stop (iscissors->core, tool); iscissors_reset (iscissors); break; default: break; } } static void iscissors_reset (Iscissors *iscissors) { /* Free and reset the curve list */ if (iscissors->curves) { iscissors_free_icurves (iscissors->curves); g_slist_free (iscissors->curves); iscissors->curves = NULL; } /* free mask */ if (iscissors->mask) { gtk_object_unref (GTK_OBJECT (iscissors->mask)); iscissors->mask = NULL; } /* free the gradient map */ if (iscissors->gradient_map) { /* release any tile we were using */ if (cur_tile) { tile_release (cur_tile, FALSE); cur_tile = NULL; } tile_manager_destroy (iscissors->gradient_map); iscissors->gradient_map = NULL; } iscissors->curve1 = NULL; iscissors->curve2 = NULL; iscissors->first_point = TRUE; iscissors->connected = FALSE; iscissors->state = NO_ACTION; /* Reset the dp buffers */ iscissors_free_buffers (iscissors); /* If they haven't already been initialized, precalculate the diagonal * weight and direction value arrays */ if (!initialized) { precalculate_arrays (); initialized = TRUE; } } static void iscissors_free_icurves (GSList *list) { ICurve * curve; while (list) { curve = (ICurve *) list->data; if (curve->points) g_ptr_array_free (curve->points, TRUE); g_free (curve); list = g_slist_next (list); } } static void iscissors_free_buffers (Iscissors *iscissors) { if (iscissors->dp_buf) temp_buf_free (iscissors->dp_buf); iscissors->dp_buf = NULL; } /* XXX need some scan-conversion routines from somewhere. maybe. ? */ static gint mouse_over_vertex (Iscissors *iscissors, gint x, gint y) { GSList *list; ICurve *curve; gint curves_found = 0; /* traverse through the list, returning non-zero if the current cursor * position is on an existing curve vertex. Set the curve1 and curve2 * variables to the two curves containing the vertex in question */ iscissors->curve1 = iscissors->curve2 = NULL; list = iscissors->curves; while (list && curves_found < 2) { curve = (ICurve *) list->data; if (abs (curve->x1 - x) < POINT_HALFWIDTH && abs (curve->y1 - y) < POINT_HALFWIDTH) { iscissors->curve1 = curve; if (curves_found++) return curves_found; } else if (abs (curve->x2 - x) < POINT_HALFWIDTH && abs (curve->y2 - y) < POINT_HALFWIDTH) { iscissors->curve2 = curve; if (curves_found++) return curves_found; } list = g_slist_next (list); } return curves_found; } static gboolean clicked_on_vertex (Tool *tool) { Iscissors *iscissors; gint curves_found = 0; iscissors = (Iscissors *) tool->private; curves_found = mouse_over_vertex (iscissors, iscissors->x, iscissors->y); if (curves_found > 1) return TRUE; /* if only one curve was found, the curves are unconnected, and * the user only wants to move either the first or last point * disallow this for now. */ if (curves_found == 1) return FALSE; /* no vertices were found at the cursor click point. Now check whether * the click occured on a curve. If so, create a new vertex there and * two curve segments to replace what used to be just one... */ return clicked_on_curve (tool); } static GSList * mouse_over_curve (Iscissors *iscissors, gint x, gint y) { GSList *list; gpointer *pt; gint len; ICurve *curve; guint32 coords; gint tx, ty; /* traverse through the list, returning the curve segment's list element * if the current cursor position is on a curve... */ for (list = iscissors->curves; list; list = g_slist_next (list)) { curve = (ICurve *) list->data; pt = curve->points->pdata; len = curve->points->len; while (len--) { coords = GPOINTER_TO_INT (*pt); pt++; tx = coords & 0x0000ffff; ty = coords >> 16; /* Is the specified point close enough to the curve? */ if (abs (tx - x) < POINT_HALFWIDTH && abs (ty - y) < POINT_HALFWIDTH) { return list; } } } return NULL; } static gboolean clicked_on_curve (Tool *tool) { Iscissors *iscissors; GSList *list, *new_link; ICurve *curve, *new_curve; iscissors = (Iscissors *) tool->private; /* traverse through the list, getting back the curve segment's list * element if the current cursor position is on a curve... * If this occurs, replace the curve with two new curves, * separated by a new vertex. */ list = mouse_over_curve (iscissors, iscissors->x, iscissors->y); if (list) { curve = (ICurve *) list->data; /* Since we're modifying the curve, undraw the existing one */ iscissors->draw = DRAW_CURVE; draw_core_pause (iscissors->core, tool); /* Create the new curve */ new_curve = g_new (ICurve, 1); new_curve->x2 = curve->x2; new_curve->y2 = curve->y2; new_curve->x1 = curve->x2 = iscissors->x; new_curve->y1 = curve->y2 = iscissors->y; new_curve->points = NULL; /* Create the new link and supply the new curve as data */ new_link = g_slist_alloc (); new_link->data = (void *) new_curve; /* Insert the new link in the list */ new_link->next = list->next; list->next = new_link; iscissors->curve1 = new_curve; iscissors->curve2 = curve; /* Redraw the curve */ draw_core_resume (iscissors->core, tool); return TRUE; } return FALSE; } static void precalculate_arrays (void) { gint i; for (i = 0; i < 256; i++) { /* The diagonal weight array */ diagonal_weight [i] = (int) (i * G_SQRT2); /* The direction value array */ direction_value [i][0] = (127 - abs (127 - i)) * 2; direction_value [i][1] = abs (127 - i) * 2; direction_value [i][2] = abs (191 - i) * 2; direction_value [i][3] = abs (63 - i) * 2; } /* set the 256th index of the direction_values to the hightest cost */ direction_value [255][0] = 255; direction_value [255][1] = 255; direction_value [255][2] = 255; direction_value [255][3] = 255; } static void calculate_curve (Tool *tool, ICurve *curve) { GDisplay *gdisp; Iscissors *iscissors; gint x, y, dir; gint xs, ys, xe, ye; gint x1, y1, x2, y2; gint width, height; gint ewidth, eheight; /* Calculate the lowest cost path from one vertex to the next as specified * by the parameter "curve". * Here are the steps: * 1) Calculate the appropriate working area for this operation * 2) Allocate a temp buf for the dynamic programming array * 3) Run the dynamic programming algorithm to find the optimal path * 4) Translate the optimal path into pixels in the icurve data * structure. */ gdisp = tool->gdisp; iscissors = (Iscissors *) tool->private; /* Get the bounding box */ xs = CLAMP (curve->x1, 0, gdisp->gimage->width - 1); ys = CLAMP (curve->y1, 0, gdisp->gimage->height - 1); xe = CLAMP (curve->x2, 0, gdisp->gimage->width - 1); ye = CLAMP (curve->y2, 0, gdisp->gimage->height - 1); x1 = MIN (xs, xe); y1 = MIN (ys, ye); x2 = MAX (xs, xe) + 1; /* +1 because if xe = 199 & xs = 0, x2 - x1, width = 200 */ y2 = MAX (ys, ye) + 1; /* expand the boundaries past the ending points by * some percentage of width and height. This serves the following purpose: * It gives the algorithm more area to search so better solutions * are found. This is particularly helpful in finding "bumps" which * fall outside the bounding box represented by the start and end * coordinates of the "curve". */ ewidth = (x2 - x1) * EXTEND_BY + FIXED; eheight = (y2 - y1) * EXTEND_BY + FIXED; if (xe >= xs) x2 += CLAMP (ewidth, 0, gdisp->gimage->width - x2); else x1 -= CLAMP (ewidth, 0, x1); if (ye >= ys) y2 += CLAMP (eheight, 0, gdisp->gimage->height - y2); else y1 -= CLAMP (eheight, 0, y1); /* blow away any previous points list we might have */ if (curve->points) { g_ptr_array_free (curve->points, TRUE); curve->points = NULL; } /* If the bounding box has width and height... */ if ((x2 - x1) && (y2 - y1)) { width = (x2 - x1); height = (y2 - y1); /* Initialise the gradient map tile manager for this image if we * don't already have one. */ if (!iscissors->gradient_map) iscissors->gradient_map = gradient_map_new (gdisp->gimage); /* allocate the dynamic programming array */ iscissors->dp_buf = temp_buf_resize (iscissors->dp_buf, 4, x1, y1, width, height); /* find the optimal path of pixels from (x1, y1) to (x2, y2) */ find_optimal_path (iscissors->gradient_map, iscissors->dp_buf, x1, y1, x2, y2, xs, ys); /* get a list of the pixels in the optimal path */ curve->points = plot_pixels (iscissors, iscissors->dp_buf, x1, y1, xs, ys, xe, ye); } /* If the bounding box has no width */ else if ((x2 - x1) == 0) { /* plot a vertical line */ y = ys; dir = (ys > ye) ? -1 : 1; curve->points = g_ptr_array_new (); while (y != ye) { g_ptr_array_add (curve->points, GINT_TO_POINTER ((y << 16) + xs)); y += dir; } } /* If the bounding box has no height */ else if ((y2 - y1) == 0) { /* plot a horizontal line */ x = xs; dir = (xs > xe) ? -1 : 1; curve->points = g_ptr_array_new (); while (x != xe) { g_ptr_array_add (curve->points, GINT_TO_POINTER ((ys << 16) + x)); x += dir; } } } /* badly need to get a replacement - this is _way_ too expensive */ static gboolean gradient_map_value (TileManager *map, gint x, gint y, guint8 *grad, guint8 *dir) { static int cur_tilex; static int cur_tiley; guint8 *p; if (!cur_tile || x / TILE_WIDTH != cur_tilex || y / TILE_HEIGHT != cur_tiley) { if (cur_tile) tile_release (cur_tile, FALSE); cur_tile = tile_manager_get_tile (map, x, y, TRUE, FALSE); if (!cur_tile) return FALSE; cur_tilex = x / TILE_WIDTH; cur_tiley = y / TILE_HEIGHT; } p = tile_data_pointer (cur_tile, x % TILE_WIDTH, y % TILE_HEIGHT); *grad = p[0]; *dir = p[1]; return TRUE; } static gint calculate_link (TileManager *gradient_map, gint x, gint y, guint32 pixel, gint link) { gint value = 0; guint8 grad1, dir1, grad2, dir2; if (!gradient_map_value (gradient_map, x, y, &grad1, &dir1)) { grad1 = 0; dir1 = 255; } /* Convert the gradient into a cost: large gradients are good, and * so have low cost. */ grad1 = 255 - grad1; /* calculate the contribution of the gradient magnitude */ if (link > 1) value += diagonal_weight [grad1] * OMEGA_G; else value += grad1 * OMEGA_G; /* calculate the contribution of the gradient direction */ x += (gint8)(pixel & 0xff); y += (gint8)((pixel & 0xff00) >> 8); if (!gradient_map_value (gradient_map, x, y, &grad2, &dir2)) { grad2 = 0; dir2 = 255; } value += (direction_value [dir1][link] + direction_value [dir2][link]) * OMEGA_D; return value; } static GPtrArray * plot_pixels (Iscissors *iscissors, TempBuf *dp_buf, gint x1, gint y1, gint xs, gint ys, gint xe, gint ye) { gint x, y; guint32 coords; gint link; gint width; guint *data; GPtrArray *list; width = dp_buf->width; /* Start the data pointer at the correct location */ data = (guint *) temp_buf_data (dp_buf) + (ye - y1) * width + (xe - x1); x = xe; y = ye; list = g_ptr_array_new (); while (1) { coords = (y << 16) + x; g_ptr_array_add (list, GINT_TO_POINTER (coords)); link = PIXEL_DIR (*data); if (link == SEED_POINT) return list; x += move [link][0]; y += move [link][1]; data += move [link][1] * width + move [link][0]; } /* won't get here */ return NULL; } #define PACK(x, y) ((((y) & 0xff) << 8) | ((x) & 0xff)) #define OFFSET(pixel) ((gint8)((pixel) & 0xff) + \ ((gint8)(((pixel) & 0xff00) >> 8)) * dp_buf->width) static void find_optimal_path (TileManager *gradient_map, TempBuf *dp_buf, gint x1, gint y1, gint x2, gint y2, gint xs, gint ys) { gint i, j, k; gint x, y; gint link; gint linkdir; gint dirx, diry; gint min_cost; gint new_cost; gint offset; gint cum_cost [8]; gint link_cost [8]; gint pixel_cost [8]; guint32 pixel [8]; guint32 * data, *d; /* initialize the dynamic programming buffer */ data = (guint32 *) temp_buf_data_clear (dp_buf); /* what directions are we filling the array in according to? */ dirx = (xs - x1 == 0) ? 1 : -1; diry = (ys - y1 == 0) ? 1 : -1; linkdir = (dirx * diry); y = ys; for (i = 0; i < dp_buf->height; i++) { x = xs; d = data + (y-y1) * dp_buf->width + (x-x1); for (j = 0; j < dp_buf->width; j++) { min_cost = G_MAXINT; /* pixel[] array encodes how to get to a neigbour, if possible. * 0 means no connection (eg edge). * Rest packed as bottom two bytes: y offset then x offset. * Initially, we assume we can't get anywhere. */ for (k = 0; k < 8; k++) pixel [k] = 0; /* Find the valid neighboring pixels */ /* the previous pixel */ if (j) pixel [((dirx == 1) ? 4 : 0)] = PACK (-dirx, 0); /* the previous row of pixels */ if (i) { pixel [((diry == 1) ? 5 : 1)] = PACK (0, -diry); link = (linkdir == 1) ? 3 : 2; if (j) pixel [((diry == 1) ? (link + 4) : link)] = PACK(-dirx, -diry); link = (linkdir == 1) ? 2 : 3; if (j != dp_buf->width - 1) pixel [((diry == 1) ? (link + 4) : link)] = PACK (dirx, -diry); } /* find the minimum cost of going through each neighbor to reach the * seed point... */ link = -1; for (k = 0; k < 8; k ++) if (pixel [k]) { link_cost [k] = calculate_link (gradient_map, xs + j*dirx, ys + i*diry, pixel [k], ((k > 3) ? k - 4 : k)); offset = OFFSET (pixel [k]); pixel_cost [k] = PIXEL_COST (d [offset]); cum_cost [k] = pixel_cost [k] + link_cost [k]; if (cum_cost [k] < min_cost) { min_cost = cum_cost [k]; link = k; } } /* If anything can be done... */ if (link >= 0) { /* set the cumulative cost of this pixel and the new direction */ *d = (cum_cost [link] << 8) + link; /* possibly change the links from the other pixels to this pixel... * these changes occur if a neighboring pixel will receive a lower * cumulative cost by going through this pixel. */ for (k = 0; k < 8; k ++) if (pixel [k] && k != link) { /* if the cumulative cost at the neighbor is greater than * the cost through the link to the current pixel, change the * neighbor's link to point to the current pixel. */ new_cost = link_cost [k] + cum_cost [link]; if (pixel_cost [k] > new_cost) { /* reverse the link direction /-----------------------\ */ offset = OFFSET (pixel [k]); d [offset] = (new_cost << 8) + ((k > 3) ? k - 4 : k + 4); } } } /* Set the seed point */ else if (!i && !j) *d = SEED_POINT; /* increment the data pointer and the x counter */ d += dirx; x += dirx; } /* increment the y counter */ y += diry; } } /* Called to fill in a newly referenced tile in the gradient map */ static void gradmap_tile_validate (TileManager *tm, Tile *tile) { static gboolean first_gradient = TRUE; gint x, y; gint dw, dh; gint sw, sh; gint i, j; gint b; gfloat gradient; guint8 *gradmap; guint8 *tiledata; guint8 *datah, *datav; gint8 hmax, vmax; Tile *srctile; PixelRegion srcPR, destPR; GImage *gimage; gimage = (GImage *) tile_manager_get_user_data (tm); if (first_gradient) { gint radius = GRADIENT_SEARCH >> 1; /* compute the distance weights */ for (i = 0; i < GRADIENT_SEARCH; i++) for (j = 0; j < GRADIENT_SEARCH; j++) distance_weights [i * GRADIENT_SEARCH + j] = 1.0 / (1 + sqrt (SQR(i - radius) + SQR(j - radius))); first_gradient = FALSE; } tile_manager_get_tile_coordinates (tm, tile, &x, &y); dw = tile_ewidth (tile); dh = tile_eheight (tile); /* get corresponding tile in the gimage */ srctile = tile_manager_get_tile (gimp_image_composite (gimage), x, y, TRUE, FALSE); if (!srctile) return; sw = tile_ewidth (srctile); sh = tile_eheight (srctile); srcPR.w = MIN (dw, sw); srcPR.h = MIN (dh, sh); srcPR.bytes = gimp_image_composite_bytes (gimage); srcPR.data = tile_data_pointer (srctile, 0, 0); srcPR.rowstride = srcPR.w * srcPR.bytes; /* XXX tile edges? */ /* Blur the source to get rid of noise */ destPR.rowstride = TILE_WIDTH * 4; destPR.data = maxgrad_conv0; convolve_region (&srcPR, &destPR, blur_32, 3, 32, NORMAL_CONVOL); /* Set the "src" temp buf up as the new source Pixel Region */ srcPR.rowstride = destPR.rowstride; srcPR.data = destPR.data; /* Get the horizontal derivative */ destPR.data = maxgrad_conv1; convolve_region (&srcPR, &destPR, horz_deriv, 3, 1, NEGATIVE_CONVOL); /* Get the vertical derivative */ destPR.data = maxgrad_conv2; convolve_region (&srcPR, &destPR, vert_deriv, 3, 1, NEGATIVE_CONVOL); /* calculate overall gradient */ tiledata = tile_data_pointer (tile, 0, 0); for (i = 0; i < srcPR.h; i++) { datah = maxgrad_conv1 + srcPR.rowstride*i; datav = maxgrad_conv2 + srcPR.rowstride*i; gradmap = tiledata + tile_ewidth (tile) * COST_WIDTH * i; for (j = 0; j < srcPR.w; j++) { hmax = datah[0] - 128; vmax = datav[0] - 128; for (b = 1; b < srcPR.bytes; b++) { if (abs (datah[b] - 128) > abs (hmax)) hmax = datah[b] - 128; if (abs (datav[b] - 128) > abs (vmax)) vmax = datav[b] - 128; } if (i == 0 || j == 0 || i == srcPR.h-1 || j == srcPR.w-1) { gradmap[j*COST_WIDTH] = 0; gradmap[j*COST_WIDTH + 1] = 255; goto contin; } /* 1 byte absolute magitude first */ gradient = sqrt(SQR(hmax) + SQR(vmax)); gradmap[j*COST_WIDTH] = gradient * 255 / MAX_GRADIENT; /* then 1 byte direction */ if (gradient > MIN_GRADIENT) { gfloat direction; if (!hmax) direction = (vmax > 0) ? G_PI_2 : -G_PI_2; else direction = atan ((double) vmax / (double) hmax); /* Scale the direction from between 0 and 254, * corresponding to -PI/2, PI/2 255 is reserved for * directionless pixels */ gradmap[j*COST_WIDTH + 1] = (guint8) (254 * (direction + G_PI_2) / G_PI); } else gradmap[j*COST_WIDTH + 1] = 255; /* reserved for weak gradient */ contin: datah += srcPR.bytes; datav += srcPR.bytes; } } tile_release (srctile, FALSE); } static TileManager * gradient_map_new (GImage *gimage) { TileManager *tm; tm = tile_manager_new (gimage->width, gimage->height, sizeof (guint8) * COST_WIDTH); tile_manager_set_user_data (tm, gimage); tile_manager_set_validate_proc (tm, gradmap_tile_validate); return tm; } static void find_max_gradient (Iscissors *iscissors, GImage *gimage, gint *x, gint *y) { PixelRegion srcPR; gint radius; gint i, j; gint endx, endy; gint sx, sy, cx, cy; gint x1, y1, x2, y2; void *pr; guint8 *gradient; gfloat g, max_gradient; /* Initialise the gradient map tile manager for this image if we * don't already have one. */ if (!iscissors->gradient_map) iscissors->gradient_map = gradient_map_new (gimage); radius = GRADIENT_SEARCH >> 1; /* calculate the extent of the search */ cx = CLAMP (*x, 0, gimage->width); cy = CLAMP (*y, 0, gimage->height); sx = cx - radius; sy = cy - radius; x1 = CLAMP (cx - radius, 0, gimage->width); y1 = CLAMP (cy - radius, 0, gimage->height); x2 = CLAMP (cx + radius, 0, gimage->width); y2 = CLAMP (cy + radius, 0, gimage->height); /* calculate the factor to multiply the distance from the cursor by */ max_gradient = 0; *x = cx; *y = cy; /* Find the point of max gradient */ pixel_region_init (&srcPR, iscissors->gradient_map, x1, y1, x2 - x1, y2 - y1, FALSE); /* this iterates over 1, 2 or 4 tiles only */ for (pr = pixel_regions_register (1, &srcPR); pr != NULL; pr = pixel_regions_process (pr)) { endx = srcPR.x + srcPR.w; endy = srcPR.y + srcPR.h; for (i = srcPR.y; i < endy; i++) { gradient = srcPR.data + srcPR.rowstride * (i - srcPR.y); for (j = srcPR.x; j < endx; j++) { g = *gradient; gradient += COST_WIDTH; g *= distance_weights [(i-y1) * GRADIENT_SEARCH + (j-x1)]; if (g > max_gradient) { max_gradient = g; *x = j; *y = i; } } } } } /* End of iscissors.c */