gimp/app/iscissors.c
BST 1999 Austin Donnelly cf6260af60 long overdue fix for problem with overrunning buffers in a couple of
Sun Oct 17 21:28:58 BST 1999  Austin Donnelly  <austin@gimp.org>

	* app/iscissors.c: long overdue fix for problem with overrunning
	    buffers in a couple of places.  Should now work with image
	    that are not an exact multiple of the tile size, and cope with
	    moving and adding control point before the curve is closed.
	    This may well fix a number of the bugs people have reported
	    on iscissors.  As of now, I know of no bugs in iscissors - if
	    you find one, I'm interested.
1999-10-17 20:28:56 +00:00

1774 lines
45 KiB
C

/* 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. */
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "appenv.h"
#include "draw_core.h"
#include "channel_pvt.h"
#include "drawable.h"
#include "errors.h"
#include "gdisplay.h"
#include "gimage_mask.h"
#include "interface.h"
#include "iscissors.h"
#include "edit_selection.h"
#include "paint_funcs.h"
#include "rect_select.h"
#include "selection_options.h"
#include "temp_buf.h"
#include "tools.h"
#include "bezier_selectP.h"
#include "scan_convert.h"
#include "libgimp/gimpintl.h"
#include "libgimp/gimpmath.h"
#ifdef DEBUG
#define TRC(x) printf x
#define D(x) x
#else
#define TRC(x)
#define D(x)
#endif
/* 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
} Iscissors_draw;
#define DRAW_ALL (DRAW_CURRENT_SEED | DRAW_CURVE)
typedef struct _iscissors Iscissors;
struct _iscissors
{
DrawCore * core; /* Core select object */
int x, y; /* upper left hand coordinate */
int ix, iy; /* initial coordinates */
int nx, ny; /* new coordinates */
TempBuf * dp_buf; /* dynamic programming buffer */
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 */
Channel * 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
#ifdef USE_LAPLACIAN
# define COST_WIDTH 3 /* number of bytes for each pixel in cost map */
#else
# define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
#endif
#define BLOCK_WIDTH 64
#define BLOCK_HEIGHT 64
#define CONV_WIDTH (BLOCK_WIDTH + 2)
#define CONV_HEIGHT (BLOCK_HEIGHT + 2)
/* weight to give between laplacian (_Z), gradient (_G) and direction (_D) */
#ifdef USE_LAPLACIAN
# define OMEGA_Z 0.16
# define OMEGA_D 0.42
# define OMEGA_G 0.42
#else
# define OMEGA_D 0.2
# define OMEGA_G 0.8
#endif
/* 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 int 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 -- */
D(static unsigned int sent0 = 0xd0d0d0d0);
static unsigned char maxgrad_conv0 [TILE_WIDTH * TILE_HEIGHT * 4] = "";
D(static unsigned int sent1 = 0xd1d1d1d1);
static unsigned char maxgrad_conv1 [TILE_WIDTH * TILE_HEIGHT * 4] = "";
D(static unsigned int sent2 = 0xd2d2d2d2);
static unsigned char maxgrad_conv2 [TILE_WIDTH * TILE_HEIGHT * 4] = "";
D(static unsigned int sent3 = 0xd3d3d3d3);
static int horz_deriv [9] =
{
1, 0, -1,
2, 0, -2,
1, 0, -1,
};
static int vert_deriv [9] =
{
1, 2, 1,
0, 0, 0,
-1, -2, -1,
};
#ifdef USE_LAPLACIAN
static int laplacian [9] =
{
-1, -1, -1,
-1, 8, -1,
-1, -1, -1,
};
#endif
static int blur_32 [9] =
{
1, 1, 1,
1, 24, 1,
1, 1, 1,
};
static float distance_weights [GRADIENT_SEARCH * GRADIENT_SEARCH];
static int diagonal_weight [256];
static int 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 *, GdkEventButton *, gpointer);
static void iscissors_button_release (Tool *, GdkEventButton *, gpointer);
static void iscissors_motion (Tool *, GdkEventMotion *, gpointer);
static void iscissors_control (Tool *, ToolAction, gpointer);
static void iscissors_reset (Iscissors *);
static void iscissors_draw (Tool *);
static TileManager *gradient_map_new (GImage *gimage);
static void find_optimal_path (TileManager *, TempBuf *, int, int,
int, int, int, int);
static void find_max_gradient (Iscissors *, GImage *, int *, int *);
static void calculate_curve (Tool *, ICurve *);
static void iscissors_draw_curve (GDisplay *, Iscissors *, ICurve *);
static void iscissors_free_icurves (GSList *);
static void iscissors_free_buffers (Iscissors *);
static int clicked_on_vertex (Tool *);
static int clicked_on_curve (Tool *);
static void precalculate_arrays (void);
static GPtrArray *plot_pixels (Iscissors *, TempBuf *,
int, int, int, int, int, int);
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_new (Iscissors, 1);
private->core = draw_core_new (iscissors_draw);
private->curves = NULL;
private->dp_buf = NULL;
private->state = NO_ACTION;
private->mask = NULL;
private->gradient_map = 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->cursor_update_func = rect_select_cursor_update;
tool->control_func = iscissors_control;
iscissors_reset (private);
return tool;
}
void
tools_free_iscissors (Tool *tool)
{
Iscissors * iscissors;
iscissors = (Iscissors *) tool->private;
TRC (("tools_free_iscissors\n"));
/* XXX? Undraw curve */
/*iscissors->draw = DRAW_CURVE;*/
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,
gpointer gdisp_ptr)
{
GDisplay *gdisp;
GimpDrawable *drawable;
Iscissors *iscissors;
int grab_pointer = 0;
int replace, op;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
drawable = gimage_active_drawable (gdisp->gimage);
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y,
&iscissors->x, &iscissors->y, FALSE, TRUE);
/* If the tool was being used in another image...reset it */
if (tool->state == ACTIVE && gdisp_ptr != tool->gdisp_ptr)
{
/*iscissors->draw = DRAW_CURVE; XXX? */
draw_core_stop (iscissors->core, tool);
iscissors_reset (iscissors);
}
tool->state = ACTIVE;
tool->gdisp_ptr = gdisp_ptr;
switch (iscissors->state)
{
case NO_ACTION :
#if 0
/* XXX what's this supposed to do? */
if (!(bevent->state & GDK_SHIFT_MASK) &&
!(bevent->state & GDK_CONTROL_MASK))
if (selection_point_inside (gdisp->select, gdisp_ptr, bevent->x, bevent->y))
{
init_edit_selection (tool, gdisp->select, gdisp_ptr, bevent->x, bevent->y);
return;
}
#endif
iscissors->state = SEED_PLACEMENT;
iscissors->draw = DRAW_CURRENT_SEED;
grab_pointer = 1;
if (! (bevent->state & GDK_SHIFT_MASK))
find_max_gradient (iscissors, gdisp->gimage,
&iscissors->x, &iscissors->y);
iscissors->x = BOUNDS (iscissors->x, 0, gdisp->gimage->width - 1);
iscissors->y = BOUNDS (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;
draw_core_resume (iscissors->core, tool);
grab_pointer = 1;
}
/* If the iscissors is connected, check if the click was inside */
else if (iscissors->connected && iscissors->mask &&
channel_value (iscissors->mask, iscissors->x, iscissors->y))
{
/* Undraw the curve */
tool->state = INACTIVE;
iscissors->draw = DRAW_CURVE;
draw_core_stop (iscissors->core, tool);
replace = 0;
if (bevent->state & GDK_SHIFT_MASK)
op = ADD;
else if (bevent->state & GDK_CONTROL_MASK)
op = SUB;
else
{
op = ADD;
replace = 1;
}
if (replace)
gimage_mask_clear (gdisp->gimage);
else
gimage_mask_undo (gdisp->gimage);
if (((SelectionOptions *) iscissors_options)->feather)
channel_feather (iscissors->mask,
gimage_get_mask (gdisp->gimage),
((SelectionOptions *) iscissors_options)->feather_radius,
((SelectionOptions *) iscissors_options)->feather_radius,
op, 0, 0);
else
channel_combine_mask (gimage_get_mask (gdisp->gimage),
iscissors->mask, op, 0, 0);
iscissors_reset (iscissors);
selection_start (gdisp->select, TRUE);
}
/* if we're not connected, we're adding a new point */
else if (!iscissors->connected)
{
iscissors->state = SEED_PLACEMENT;
iscissors->draw = DRAW_CURRENT_SEED;
grab_pointer = 1;
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, gpointer gdisp_ptr)
{
GDisplay *gdisp = (GDisplay *) gdisp_ptr;
ScanConverter *sc;
ScanConvertPoint *pts;
guint npts;
GSList *list;
ICurve *icurve;
guint packed;
int i;
int 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)
channel_delete (iscissors->mask);
iscissors->mask = scan_converter_to_channel (sc, gdisp->gimage);
scan_converter_free (sc);
channel_invalidate_bounds (iscissors->mask);
}
static void
iscissors_button_release (Tool *tool,
GdkEventButton *bevent,
gpointer gdisp_ptr)
{
Iscissors *iscissors;
GDisplay *gdisp;
ICurve *curve;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
TRC (("iscissors_button_release\n"));
/* 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;
break;
case SEED_ADJUSTMENT :
iscissors->draw = DRAW_CURVE | DRAW_ACTIVE_CURVE;
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_malloc (sizeof (ICurve));
curve->x1 = iscissors->ix;
curve->y1 = iscissors->iy;
iscissors->ix = curve->x2 = iscissors->x;
iscissors->iy = curve->y2 = iscissors->y;
curve->points = NULL;
TRC (("create new curve segment\n"));
iscissors->curves = g_slist_append (iscissors->curves,
(void *) curve);
TRC (("calculate curve\n"));
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;
}
}
TRC (("button_release: draw core resume\n"));
/* 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_ptr);
}
static void
iscissors_motion (Tool *tool,
GdkEventMotion *mevent,
gpointer gdisp_ptr)
{
Iscissors *iscissors;
GDisplay *gdisp;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
if (tool->state != ACTIVE || iscissors->state == NO_ACTION)
return;
if (iscissors->state == SEED_PLACEMENT)
iscissors->draw = DRAW_CURRENT_SEED;
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, TRUE);
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 = BOUNDS (iscissors->x, 0, gdisp->gimage->width - 1);
iscissors->y = BOUNDS (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 = BOUNDS (iscissors->x, 0, gdisp->gimage->width - 1);
iscissors->y = BOUNDS (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;
int tx1, ty1, tx2, ty2;
int txn, tyn;
TRC (("iscissors_draw\n"));
gdisp = (GDisplay *) tool->gdisp_ptr;
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));
if (!iscissors->first_point)
gdk_draw_line (iscissors->core->win, iscissors->core->gc,
tx1, ty1, tx2, ty2);
}
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... */
list = iscissors->curves;
while (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);
list = g_slist_next (list);
}
}
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;
int tx, ty;
int npts;
guint32 coords;
/* Uh, this shouldn't happen, but it does. So we ignore it.
* Quality code, baby. */
if (!curve->points)
return;
npts = 0;
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_control (Tool *tool,
ToolAction action,
gpointer gdisp_ptr)
{
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)
{
TRC( ("iscissors_reset\n"));
/* Free and reset the curve list */
if (iscissors->curves)
{
iscissors_free_icurves (iscissors->curves);
TRC (("g_slist_free (iscissors->curves);\n"));
g_slist_free (iscissors->curves);
iscissors->curves = NULL;
}
/* free mask */
if (iscissors->mask)
channel_delete (iscissors->mask);
iscissors->mask = NULL;
/* free the gradient map */
if (iscissors->gradient_map)
{
/* release any tile we were using */
if (cur_tile)
{
TRC (("tile_release\n"));
tile_release (cur_tile, FALSE);
}
cur_tile = NULL;
TRC (("tile_manager_destroy (iscissors->gradient_map);\n"));
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;
TRC (("iscissors_free_icurves\n"));
while (list)
{
curve = (ICurve *) list->data;
if (curve->points)
{
TRC (("g_ptr_array_free (curve->points);\n"));
g_ptr_array_free (curve->points, TRUE);
}
TRC (("g_free (curve);\n"));
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 int
clicked_on_vertex (Tool *tool)
{
Iscissors * iscissors;
GSList *list;
ICurve * curve;
int curves_found = 0;
iscissors = (Iscissors *) tool->private;
/* 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 - iscissors->x) < POINT_HALFWIDTH &&
abs (curve->y1 - iscissors->y) < POINT_HALFWIDTH)
{
iscissors->curve1 = curve;
if (curves_found++)
return 1;
}
else if (abs (curve->x2 - iscissors->x) < POINT_HALFWIDTH &&
abs (curve->y2 - iscissors->y) < POINT_HALFWIDTH)
{
iscissors->curve2 = curve;
if (curves_found++)
return 1;
}
list = g_slist_next (list);
}
/* 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 0;
}
/* 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 int
clicked_on_curve (Tool *tool)
{
Iscissors * iscissors;
GSList *list, *new_link;
gpointer *pt;
int len;
ICurve * curve, * new_curve;
guint32 coords;
int tx, ty;
iscissors = (Iscissors *) tool->private;
/* traverse through the list, returning non-zero if the current cursor
* position is on a curve... If this occurs, replace the curve with two
* new curves, separated by the new vertex.
*/
list = iscissors->curves;
while (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 - iscissors->x) < POINT_HALFWIDTH &&
abs (ty - iscissors->y) < POINT_HALFWIDTH)
{
/* 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_malloc (sizeof (ICurve));
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 1;
}
}
list = g_slist_next (list);
}
return 0;
}
static void
precalculate_arrays (void)
{
int 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;
TRC (("i: %d, v0: %d, v1: %d, v2: %d, v3: %d\n", i,
direction_value [i][0],
direction_value [i][1],
direction_value [i][2],
direction_value [i][3]));
}
/* 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;
int x, y, dir;
int xs, ys, xe, ye;
int x1, y1, x2, y2;
int width, height;
int ewidth, eheight;
TRC (("calculate_curve(%p, %p)\n", tool, curve));
/* 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 = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
/* Get the bounding box */
xs = BOUNDS (curve->x1, 0, gdisp->gimage->width - 1);
ys = BOUNDS (curve->y1, 0, gdisp->gimage->height - 1);
xe = BOUNDS (curve->x2, 0, gdisp->gimage->width - 1);
ye = BOUNDS (curve->y2, 0, gdisp->gimage->height - 1);
x1 = MINIMUM (xs, xe);
y1 = MINIMUM (ys, ye);
x2 = MAXIMUM (xs, xe) + 1; /* +1 because if xe = 199 & xs = 0, x2 - x1, width = 200 */
y2 = MAXIMUM (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 += BOUNDS (ewidth, 0, gdisp->gimage->width - x2);
else
x1 -= BOUNDS (ewidth, 0, x1);
if (ye >= ys)
y2 += BOUNDS (eheight, 0, gdisp->gimage->height - y2);
else
y1 -= BOUNDS (eheight, 0, y1);
/* blow away any previous points list we might have */
if (curve->points)
{
TRC (("1229: g_ptr_array_free (curve->points);\n"));
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);
TRC (("dp buf resize\n"));
/* allocate the dynamic programming array */
iscissors->dp_buf =
temp_buf_resize (iscissors->dp_buf, 4, x1, y1, width, height);
TRC (("find_optimal_path\n"));
/* 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 */
TRC (("plot_pixels\n"));
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 int
gradient_map_value (TileManager *map, int x, int 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 0;
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 1;
}
static int
calculate_link (TileManager *gradient_map,
int x, int y, guint32 pixel, int link)
{
int 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,
int x1, int y1,
int xs, int ys,
int xe, int ye)
{
int x, y;
guint32 coords;
int link;
int width;
unsigned int * data;
GPtrArray *list;
width = dp_buf->width;
/* Start the data pointer at the correct location */
data = (unsigned int *)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,
int x1, int y1, int x2, int y2,
int xs, int ys)
{
int i, j, k;
int x, y;
int link;
int linkdir;
int dirx, diry;
int min_cost;
int new_cost;
int offset;
int cum_cost [8];
int link_cost [8];
int pixel_cost [8];
guint32 pixel [8];
guint32 * data, *d;
TRC (("find_optimal_path (%p, %p, [%d,%d-%d,%d] %d, %d)\n",
gradient_map, dp_buf, x1, y1, x2, y2, xs, ys));
/* initialize the dynamic programming buffer */
data = (guint32 *) temp_buf_data (dp_buf);
for (i = 0; i < dp_buf->height; i++)
for (j = 0; j < dp_buf->width; j++)
*data++ = 0; /* 0 cumulative cost, 0 direction */
/* 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;
/* Start the data pointer at the correct location */
data = (guint32 *) temp_buf_data (dp_buf);
TRC (("find_optimal_path: mainloop\n"));
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;
}
TRC (("done: find_optimal_path\n"));
}
/* 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;
int x, y;
int dw, dh;
int sw, sh;
int i, j;
int b;
float 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)
{
int 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);
TRC (("fill req for tile %p @ (%d, %d)\n", tile, x, y));
/* get corresponding tile in the gimage */
srctile = tile_manager_get_tile (gimp_image_composite (gimage),
x, y, TRUE, FALSE);
if (!srctile)
{
g_warning ("bad tile coords?");
return;
}
sw = tile_ewidth (srctile);
sh = tile_eheight (srctile);
if (dw != sw || dh != sh)
g_warning ("dw:%d sw:%d dh:%d sh:%d\n", dw, sw, dh, sh);
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)
{
float 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:
{
#ifdef DEBUG
int g = gradmap[j*COST_WIDTH];
int d = gradmap[j*COST_WIDTH + 1];
TRC (("%c%c", 'a' + (g * 25 / 255), '0' + (d / 25)));
#endif /* DEBUG */
}
datah += srcPR.bytes;
datav += srcPR.bytes;
}
TRC (("\n"));
}
TRC (("\n"));
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, int *x, int *y)
{
PixelRegion srcPR;
int radius;
int i, j;
int endx, endy;
int sx, sy, cx, cy;
int x1, y1, x2, y2;
void *pr;
guint8 *gradient;
float g, max_gradient;
TRC (("find_max_gradient(%d, %d)\n", *x, *y));
/* 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 = BOUNDS (*x, 0, gimage->width);
cy = BOUNDS (*y, 0, gimage->height);
sx = cx - radius;
sy = cy - radius;
x1 = BOUNDS (cx - radius, 0, gimage->width);
y1 = BOUNDS (cy - radius, 0, gimage->height);
x2 = BOUNDS (cx + radius, 0, gimage->width);
y2 = BOUNDS (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;
}
}
}
}
TRC (("done: find_max_gradient(%d, %d)\n", *x, *y));
}
/* End of iscissors.c */