gimp/app/iscissors.c

2657 lines
62 KiB
C
Raw Normal View History

1997-11-25 06:05:25 +08:00
/* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* This tool is based on a paper from SIGGRAPH '95
* thanks to Professor D. Forsyth for prompting us to implement this tool
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "appenv.h"
#include "bezier_selectP.h"
#include "draw_core.h"
#include "channel_pvt.h"
#include "drawable.h"
1997-11-25 06:05:25 +08:00
#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 "temp_buf.h"
/* local structures */
typedef struct _IScissorsOptions IScissorsOptions;
struct _IScissorsOptions
{
int antialias;
int feather;
double feather_radius;
double resolution;
double threshold;
};
typedef struct _kink Kink;
struct _kink
{
int x, y; /* coordinates */
int is_a_kink; /* is this a kink? */
double normal[2]; /* normal vector to kink */
double kinkiness; /* kinkiness measure */
};
typedef struct _point Point;
struct _point
{
int x, y; /* coordinates */
int dir; /* direction */
int kink; /* is it a kink? */
int stable; /* is the point in a stable locale? */
double dx, dy; /* moving coordinates */
double normal[2]; /* normal vector to kink */
};
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 */
int state; /* state of iscissors */
int num_segs; /* number of points in the polygon */
int num_pts; /* number of kinks in list */
int num_kinks; /* number of kinks in list */
Channel * mask; /* selection mask */
Kink * kinks; /* kinks in the object outline */
TempBuf * edge_buf; /* edge map buffer */
};
typedef double BezierMatrix[4][4];
typedef double CRMatrix[4][4];
/**********************************************/
/* Intelligent scissors selection apparatus */
#define IMAGE_COORDS 1
#define AA_IMAGE_COORDS 2
#define SCREEN_COORDS 3
#define SUPERSAMPLE 3
#define SUPERSAMPLE2 9
#define FREE_SELECT_MODE 0
#define BOUNDARY_MODE 1
#define POINT_WIDTH 8
#define POINT_HALFWIDTH 4
#define DEFAULT_MAX_INC 1024
#define EDGE_WIDTH 1
#define LOCALIZE_RADIUS 24
#define BLOCK_WIDTH 64
#define BLOCK_HEIGHT 64
#define CONV_WIDTH BLOCK_WIDTH
#define CONV_HEIGHT BLOCK_HEIGHT
#define HORIZONTAL 0
#define VERTICAL 1
#define SUBDIVIDE 1000
#define EDGE_STRENGTH 255
#define EPSILON 0.00001
#define NO 0
#define YES 1
/* functional defines */
#define SQR(x) ((x) * (x))
#define BILINEAR(jk,j1k,jk1,j1k1,dx,dy) \
((1-dy) * ((1-dx)*jk + dx*j1k) + \
dy * ((1-dx)*jk1 + dx*j1k1))
/* static variables */
/* The global array of XSegments for drawing the polygon... */
static GdkSegment *segs = NULL;
static int max_segs = 0;
static Point * pts = NULL;
static int max_pts = 0;
/* boundary resolution variables */
static int resolution = 20; /* in pixels */
static int threshold = 15; /* in intensity */
static double elasticity = 0.25; /* between 0.0 -> 1.0 */
static double kink_thres = 0.33; /* between 0.0 -> 1.0 */
static double std_dev = 1.0; /* in pixels */
static int miss_thres = 4; /* in intensity */
/* edge map blocks variables */
static TempBuf ** edge_map_blocks = NULL;
static int horz_blocks;
static int vert_blocks;
/* convolution and basis matrixes */
static unsigned char conv1 [CONV_WIDTH * CONV_HEIGHT * MAX_CHANNELS];
static unsigned char conv2 [CONV_WIDTH * CONV_HEIGHT * MAX_CHANNELS];
static unsigned char grad [(CONV_WIDTH + 2) * (CONV_HEIGHT + 2)];
static CRMatrix CR_basis =
{
{ -0.5, 1.5, -1.5, 0.5 },
{ 1.0, -2.5, 2.0, -0.5 },
{ -0.5, 0.0, 0.5, 0.0 },
{ 0.0, 1.0, 0.0, 0.0 },
};
static CRMatrix CR_bezier_basis =
{
{ 0.0, 1.0, 0.0, 0.0 },
{ -0.16667, 1.0, 0.16667, 0.0 },
{ 0.0, 0.16667, 1.0, -0.16667 },
{ 0.0, 0.0, 1.0, 0.0 },
};
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 *, int, gpointer);
static void iscissors_reset (Iscissors *);
static void iscissors_draw (Tool *);
static void iscissors_draw_CR (GDisplay *, Iscissors *,
Point *, int *, int);
static void CR_compose (CRMatrix, CRMatrix, CRMatrix);
static int add_segment (int *, int, int);
static int add_point (int *, int, int, int, double *);
/* boundary localization routines */
static void normalize (double *);
static double dotprod (double *, double *);
static Kink * get_kink (Kink *, int, int);
static int find_next_kink (Kink *, int, int);
static double find_distance (Kink *, int, int);
static int go_distance (Kink *, int, double, double *, double *);
static int travel_length (Kink *, int, int, int, int);
static int find_edge_xy (TempBuf *, int, double, double, double *);
static void find_boundary (Tool *);
static void shape_of_boundary (Tool *);
static void process_kinks (Tool *);
static void initial_boundary (Tool *);
static void edge_map_from_boundary (Tool *);
static void orient_boundary (Tool *);
static void reset_boundary (Tool *);
static int localize_boundary (Tool *);
static void post_process_boundary (Tool *);
static void bezierify_boundary (Tool *);
/* edge map buffer utility functions */
static TempBuf * calculate_edge_map (GImage *, int, int, int, int);
static void construct_edge_map (Tool *, TempBuf *);
/* edge map blocks utility functions */
static void set_edge_map_blocks (void *, int, int, int, int);
static void allocate_edge_map_blocks (int, int, int, int);
static void free_edge_map_blocks (void);
/* gaussian & 1st derivative */
static void gaussian_deriv (PixelRegion *, PixelRegion *, int, double);
static void make_curve (int *, int *, double, int);
static void make_curve_d (int *, int *, double, int);
/* Catmull-Rom boundary conversion */
static void CR_convert (Iscissors * , GDisplay *, int);
static void CR_convert_points (GdkPoint *, int);
static void CR_convert_line (GSList **, int, int, int, int);
static GSList * CR_insert_in_list (GSList *, int);
1997-11-25 06:05:25 +08:00
/*******************************************************/
/* Selection options dialog--for all selection tools */
/*******************************************************/
static void
selection_toggle_update (GtkWidget *w,
gpointer data)
{
int *toggle_val;
toggle_val = (int *) data;
if (GTK_TOGGLE_BUTTON (w)->active)
*toggle_val = TRUE;
else
*toggle_val = FALSE;
}
static void
selection_scale_update (GtkAdjustment *adjustment,
double *scale_val)
{
*scale_val = adjustment->value;
}
static IScissorsOptions *
iscissors_selection_options (void)
{
IScissorsOptions *options;
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *antialias_toggle;
GtkWidget *feather_toggle;
GtkWidget *feather_scale;
GtkObject *feather_scale_data;
GtkWidget *resolution_scale;
GtkObject *resolution_scale_data;
GtkWidget *threshold_scale;
GtkWidget *convert_button;
GtkObject *threshold_scale_data;
/* the new options structure */
options = (IScissorsOptions *) g_malloc (sizeof (IScissorsOptions));
options->antialias = 1;
options->feather = 0;
options->feather_radius = 10.0;
options->resolution = 40.0;
options->threshold = 15.0;
/* the main vbox */
vbox = gtk_vbox_new (FALSE, 1);
/* the main label */
label = gtk_label_new ("Intelligent Scissors Options");
gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
/* the antialias toggle button */
antialias_toggle = gtk_check_button_new_with_label ("Antialiasing");
gtk_box_pack_start (GTK_BOX (vbox), antialias_toggle, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (antialias_toggle), "toggled",
(GtkSignalFunc) selection_toggle_update,
&options->antialias);
gtk_widget_show (antialias_toggle);
/* the feather toggle button */
feather_toggle = gtk_check_button_new_with_label ("Feather");
gtk_box_pack_start (GTK_BOX (vbox), feather_toggle, FALSE, FALSE, 0);
gtk_signal_connect (GTK_OBJECT (feather_toggle), "toggled",
(GtkSignalFunc) selection_toggle_update,
&options->feather);
gtk_widget_show (feather_toggle);
/* the feather radius scale */
hbox = gtk_hbox_new (FALSE, 1);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
label = gtk_label_new ("Feather Radius");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
feather_scale_data = gtk_adjustment_new (10.0, 0.0, 100.0, 1.0, 1.0, 0.0);
feather_scale = gtk_hscale_new (GTK_ADJUSTMENT (feather_scale_data));
gtk_box_pack_start (GTK_BOX (hbox), feather_scale, TRUE, TRUE, 0);
gtk_scale_set_value_pos (GTK_SCALE (feather_scale), GTK_POS_TOP);
gtk_range_set_update_policy (GTK_RANGE (feather_scale), GTK_UPDATE_DELAYED);
gtk_signal_connect (GTK_OBJECT (feather_scale_data), "value_changed",
(GtkSignalFunc) selection_scale_update,
&options->feather_radius);
gtk_widget_show (feather_scale);
gtk_widget_show (hbox);
/* the resolution scale */
hbox = gtk_hbox_new (FALSE, 1);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
label = gtk_label_new ("Curve Resolution");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
resolution_scale_data = gtk_adjustment_new (4.0, 1.0, 200.0, 1.0, 1.0, 0.0);
resolution_scale = gtk_hscale_new (GTK_ADJUSTMENT (resolution_scale_data));
gtk_box_pack_start (GTK_BOX (hbox), resolution_scale, TRUE, TRUE, 0);
gtk_scale_set_value_pos (GTK_SCALE (resolution_scale), GTK_POS_TOP);
gtk_range_set_update_policy (GTK_RANGE (resolution_scale), GTK_UPDATE_DELAYED);
gtk_signal_connect (GTK_OBJECT (resolution_scale_data), "value_changed",
(GtkSignalFunc) selection_scale_update,
&options->resolution);
gtk_widget_show (resolution_scale);
gtk_widget_show (hbox);
/* the threshold scale */
hbox = gtk_hbox_new (FALSE, 1);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
label = gtk_label_new ("Edge Detect Thresh.");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
threshold_scale_data = gtk_adjustment_new (15.0, 1.0, 255.0, 1.0, 1.0, 0.0);
threshold_scale = gtk_hscale_new (GTK_ADJUSTMENT (threshold_scale_data));
gtk_box_pack_start (GTK_BOX (hbox), threshold_scale, TRUE, TRUE, 0);
gtk_scale_set_value_pos (GTK_SCALE (threshold_scale), GTK_POS_TOP);
gtk_range_set_update_policy (GTK_RANGE (threshold_scale), GTK_UPDATE_DELAYED);
gtk_signal_connect (GTK_OBJECT (threshold_scale_data), "value_changed",
(GtkSignalFunc) selection_scale_update,
&options->threshold);
gtk_widget_show (threshold_scale);
gtk_widget_show (hbox);
/* the convert to bezier button */
convert_button = gtk_button_new_with_label ("Convert to Bezier Curve");
gtk_box_pack_start (GTK_BOX (vbox), convert_button, TRUE, TRUE, 0);
gtk_widget_show (convert_button);
/* Register this selection options widget with the main tools options dialog */
tools_register_options (ISCISSORS, vbox);
return options;
}
Tool *
tools_new_iscissors ()
{
Tool * tool;
Iscissors * private;
if (!iscissors_options)
iscissors_options = iscissors_selection_options ();
tool = (Tool *) g_malloc (sizeof (Tool));
private = (Iscissors *) g_malloc (sizeof (Iscissors));
private->core = draw_core_new (iscissors_draw);
private->edge_buf = NULL;
private->kinks = NULL;
private->mask = NULL;
tool->type = ISCISSORS;
tool->state = INACTIVE;
tool->scroll_lock = 0; /* Allow scrolling */
tool->private = (void *) private;
tool->button_press_func = iscissors_button_press;
tool->button_release_func = iscissors_button_release;
tool->motion_func = iscissors_motion;
tool->arrow_keys_func = standard_arrow_keys_func;
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;
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;
Iscissors *iscissors;
int replace, op;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
/* message_box ("Intelligent Scissors is currently not enabled\nfor use with the tile-based GIMP",
1997-11-25 06:05:25 +08:00
NULL, NULL);
return;*/
1997-11-25 06:05:25 +08:00
gdisplay_untransform_coords (gdisp, bevent->x, bevent->y,
&iscissors->x, &iscissors->y, FALSE, TRUE);
1997-11-25 06:05:25 +08:00
/* If the tool was being used in another image...reset it */
if (tool->state == ACTIVE && gdisp_ptr != tool->gdisp_ptr)
{
draw_core_stop (iscissors->core, tool);
iscissors_reset (iscissors);
}
switch (iscissors->state)
{
case FREE_SELECT_MODE:
tool->state = ACTIVE;
tool->gdisp_ptr = gdisp_ptr;
gdk_pointer_grab (gdisp->canvas->window, FALSE,
(GDK_POINTER_MOTION_HINT_MASK |
GDK_BUTTON1_MOTION_MASK |
GDK_BUTTON_RELEASE_MASK),
NULL, NULL, bevent->time);
if (bevent->state & GDK_MOD1_MASK)
{
init_edit_selection (tool, gdisp_ptr, bevent, MaskTranslate);
return;
}
else if (!(bevent->state & GDK_SHIFT_MASK) && !(bevent->state & GDK_CONTROL_MASK))
if (! (layer_is_floating_sel (gimage_get_active_layer (gdisp->gimage))) &&
gdisplay_mask_value (gdisp, bevent->x, bevent->y) > HALF_WAY)
{
/* Have to blank out the edge blocks since they might change */
iscissors_reset (iscissors);
init_edit_selection (tool, gdisp_ptr, bevent, MaskToLayerTranslate);
return;
}
/* If the edge map blocks haven't been allocated, do so now */
if (!edge_map_blocks)
1997-11-25 06:05:25 +08:00
allocate_edge_map_blocks (BLOCK_WIDTH, BLOCK_HEIGHT,
gdisp->gimage->width,
gdisp->gimage->height);
1997-11-25 06:05:25 +08:00
iscissors->num_segs = 0;
add_segment (&(iscissors->num_segs), bevent->x, bevent->y);
draw_core_start (iscissors->core,
gdisp->canvas->window,
tool);
break;
case BOUNDARY_MODE:
if (/*channel_value (iscissors->mask, iscissors->x, iscissors->y)*/ TRUE)
1997-11-25 06:05:25 +08:00
{
replace = 0;
if (bevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
{
bezierify_boundary (tool);
return;
}
else if ((bevent->state & GDK_SHIFT_MASK) && !(bevent->state & GDK_CONTROL_MASK))
op = ADD;
else if ((bevent->state & GDK_CONTROL_MASK) && !(bevent->state & GDK_SHIFT_MASK))
op = SUB;
else if ((bevent->state & GDK_CONTROL_MASK) && (bevent->state & GDK_SHIFT_MASK))
op = INTERSECT;
else
{
op = ADD;
replace = 1;
}
tool->state = INACTIVE;
/* If we're antialiased, then recompute the
* mask...
*/
if (iscissors_options->antialias)
CR_convert (iscissors, tool->gdisp_ptr, YES);
draw_core_stop (iscissors->core, tool);
if (replace)
gimage_mask_clear (gdisp->gimage);
else
gimage_mask_undo (gdisp->gimage);
if (iscissors_options->feather)
channel_feather (iscissors->mask,
gimage_get_mask (gdisp->gimage),
iscissors_options->feather_radius, op, 0, 0);
else
channel_combine_mask (gimage_get_mask (gdisp->gimage),
iscissors->mask, op, 0, 0);
iscissors_reset (iscissors);
gdisplays_flush ();
}
break;
}
}
static void
iscissors_button_release (Tool *tool,
GdkEventButton *bevent,
gpointer gdisp_ptr)
{
Iscissors *iscissors;
GDisplay *gdisp;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
/*return;*/
1997-11-25 06:05:25 +08:00
gdk_pointer_ungrab (bevent->time);
gdk_flush ();
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 FREE_SELECT_MODE:
/* Add one additional segment */
add_segment (&(iscissors->num_segs), segs[0].x1, segs[0].y1);
if (iscissors->num_segs >= 3)
{
/* Find the boundary */
find_boundary (tool);
/* Set the new state */
iscissors->state = BOUNDARY_MODE;
/* Start the draw core up again */
draw_core_resume (iscissors->core, tool);
return;
}
break;
case BOUNDARY_MODE:
iscissors->state = FREE_SELECT_MODE;
break;
}
}
tool->state = INACTIVE;
}
static void
iscissors_motion (Tool *tool,
GdkEventMotion *mevent,
gpointer gdisp_ptr)
{
Iscissors *iscissors;
GDisplay *gdisp;
if (tool->state != ACTIVE)
return;
gdisp = (GDisplay *) gdisp_ptr;
iscissors = (Iscissors *) tool->private;
switch (iscissors->state)
{
case FREE_SELECT_MODE:
if (add_segment (&(iscissors->num_segs), mevent->x, mevent->y))
gdk_draw_segments (iscissors->core->win, iscissors->core->gc,
segs + (iscissors->num_segs - 1), 1);
break;
case BOUNDARY_MODE:
break;
}
}
static void
iscissors_draw (Tool *tool)
{
GDisplay *gdisp;
Iscissors *iscissors;
int indices[4];
int i;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
switch (iscissors->state)
{
case FREE_SELECT_MODE:
gdk_draw_segments (iscissors->core->win, iscissors->core->gc,
segs, iscissors->num_segs);
break;
case BOUNDARY_MODE:
for (i = 0; i < iscissors->num_pts; i ++)
{
indices[0] = (i < 3) ? (iscissors->num_pts + i - 3) : (i - 3);
indices[1] = (i < 2) ? (iscissors->num_pts + i - 2) : (i - 2);
indices[2] = (i < 1) ? (iscissors->num_pts + i - 1) : (i - 1);
indices[3] = i;
iscissors_draw_CR (gdisp, iscissors, pts, indices, SCREEN_COORDS);
}
break;
}
}
static void
iscissors_draw_CR (GDisplay *gdisp,
Iscissors *iscissors,
Point *pts,
int *indices,
int draw_type)
{
#define ROUND(x) ((int) ((x) + 0.5))
static GdkPoint gdk_points[256];
static int npoints = 256;
CRMatrix geometry;
CRMatrix tmp1, tmp2;
CRMatrix deltas;
double x, dx, dx2, dx3;
double y, dy, dy2, dy3;
double d, d2, d3;
int lastx, lasty;
int newx, newy;
int index;
int i;
/* construct the geometry matrix from the segment */
/* assumes that a valid segment containing 4 points is passed in */
for (i = 0; i < 4; i++)
{
switch (draw_type)
{
case IMAGE_COORDS:
geometry[i][0] = pts[indices[i]].dx;
geometry[i][1] = pts[indices[i]].dy;
break;
case AA_IMAGE_COORDS:
geometry[i][0] = pts[indices[i]].dx * SUPERSAMPLE;
geometry[i][1] = pts[indices[i]].dy * SUPERSAMPLE;
break;
case SCREEN_COORDS:
gdisplay_transform_coords_f (gdisp, (int) pts[indices[i]].dx,
(int) pts[indices[i]].dy, &x, &y, TRUE);
1997-11-25 06:05:25 +08:00
geometry[i][0] = x;
geometry[i][1] = y;
break;
}
geometry[i][2] = 0;
geometry[i][3] = 0;
}
/* subdivide the curve n times */
/* n can be adjusted to give a finer or coarser curve */
d = 1.0 / SUBDIVIDE;
d2 = d * d;
d3 = d * d * d;
/* construct a temporary matrix for determining the forward differencing deltas */
tmp2[0][0] = 0; tmp2[0][1] = 0; tmp2[0][2] = 0; tmp2[0][3] = 1;
tmp2[1][0] = d3; tmp2[1][1] = d2; tmp2[1][2] = d; tmp2[1][3] = 0;
tmp2[2][0] = 6*d3; tmp2[2][1] = 2*d2; tmp2[2][2] = 0; tmp2[2][3] = 0;
tmp2[3][0] = 6*d3; tmp2[3][1] = 0; tmp2[3][2] = 0; tmp2[3][3] = 0;
/* compose the basis and geometry matrices */
CR_compose (CR_basis, geometry, tmp1);
/* compose the above results to get the deltas matrix */
CR_compose (tmp2, tmp1, deltas);
/* extract the x deltas */
x = deltas[0][0];
dx = deltas[1][0];
dx2 = deltas[2][0];
dx3 = deltas[3][0];
/* extract the y deltas */
y = deltas[0][1];
dy = deltas[1][1];
dy2 = deltas[2][1];
dy3 = deltas[3][1];
lastx = x;
lasty = y;
gdk_points[0].x = lastx;
gdk_points[0].y = lasty;
index = 1;
/* loop over the curve */
for (i = 0; i < SUBDIVIDE; i++)
{
/* increment the x values */
x += dx;
dx += dx2;
dx2 += dx3;
/* increment the y values */
y += dy;
dy += dy2;
dy2 += dy3;
newx = ROUND (x);
newy = ROUND (y);
/* if this point is different than the last one...then draw it */
if ((lastx != newx) || (lasty != newy))
{
/* add the point to the point buffer */
gdk_points[index].x = newx;
gdk_points[index].y = newy;
index++;
/* if the point buffer is full put it to the screen and zero it out */
if (index >= npoints)
{
switch (draw_type)
{
case IMAGE_COORDS: case AA_IMAGE_COORDS:
CR_convert_points (gdk_points, index);
break;
case SCREEN_COORDS:
gdk_draw_points (iscissors->core->win, iscissors->core->gc,
gdk_points, index);
break;
}
index = 0;
}
}
lastx = newx;
lasty = newy;
}
/* if there are points in the buffer, then put them on the screen */
if (index)
switch (draw_type)
{
case IMAGE_COORDS: case AA_IMAGE_COORDS:
CR_convert_points (gdk_points, index);
break;
case SCREEN_COORDS:
gdk_draw_points (iscissors->core->win, iscissors->core->gc,
gdk_points, index);
break;
}
}
static void
iscissors_control (Tool *tool,
int action,
gpointer gdisp_ptr)
{
Iscissors * iscissors;
iscissors = (Iscissors *) tool->private;
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;
}
}
static void
iscissors_reset (Iscissors *iscissors)
{
/* Reset the edge map blocks structure */
free_edge_map_blocks ();
/* free edge buffer */
if (iscissors->edge_buf)
temp_buf_free (iscissors->edge_buf);
/* free mask */
if (iscissors->mask)
channel_delete (iscissors->mask);
/* Free kinks */
if (iscissors->kinks)
g_free (iscissors->kinks);
iscissors->state = FREE_SELECT_MODE;
iscissors->mask = NULL;
iscissors->edge_buf = NULL;
iscissors->kinks = NULL;
iscissors->num_segs = 0;
iscissors->num_pts = 0;
iscissors->num_kinks = 0;
}
static int
add_segment (int *num_segs,
int x,
int y)
{
static int first = 1;
if (*num_segs >= max_segs)
{
max_segs += DEFAULT_MAX_INC;
segs = (GdkSegment *) g_realloc ((void *) segs, sizeof (GdkSegment) * max_segs);
if (!segs)
fatal_error ("Unable to reallocate segment array in iscissors.");
}
if (*num_segs)
{
segs[*num_segs].x1 = segs[*num_segs - 1].x2;
segs[*num_segs].y1 = segs[*num_segs - 1].y2;
}
else if (first)
{
segs[0].x1 = x;
segs[0].y1 = y;
}
segs[*num_segs].x2 = x;
segs[*num_segs].y2 = y;
if (! *num_segs && first)
first = 0;
else
{
(*num_segs)++;
first = 1;
}
return 1;
}
static int
add_point (int *num_pts,
int kink,
int x,
int y,
double *normal)
{
if (*num_pts >= max_pts)
{
max_pts += DEFAULT_MAX_INC;
pts = (Point *) g_realloc ((void *) pts, sizeof (Point) * max_pts);
if (!pts)
fatal_error ("Unable to reallocate points array in iscissors.");
}
pts[*num_pts].x = x;
pts[*num_pts].y = y;
pts[*num_pts].kink = kink;
pts[*num_pts].normal[0] = normal[0];
pts[*num_pts].normal[1] = normal[1];
(*num_pts)++;
return 1;
}
static void
CR_compose (CRMatrix a,
CRMatrix b,
CRMatrix ab)
{
int i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
ab[i][j] = (a[i][0] * b[0][j] +
a[i][1] * b[1][j] +
a[i][2] * b[2][j] +
a[i][3] * b[3][j]);
}
}
}
static void
normalize (double *vec)
{
double length;
length = sqrt (SQR (vec[0]) + SQR (vec[1]));
if (length)
{
vec[0] /= length;
vec[1] /= length;
}
}
static double
dotprod (double *vec1,
double *vec2)
{
double val;
val = vec1[0] * vec2[0] + vec1[1] * vec2[1];
return val;
}
static Kink *
get_kink (Kink *kinks,
int index,
int num_kinks)
{
if (index >= 0 && index < num_kinks)
return kinks + index;
else if (index < 0)
{
while (index < 0)
index += num_kinks;
return kinks + index;
}
else
{
while (index >= num_kinks)
index -= num_kinks;
return kinks + index;
}
/* I don't think this ever gets called -- Rockwalrus */
1997-11-25 06:05:25 +08:00
return NULL;
}
static int
find_next_kink (Kink *kinks,
int num_kinks,
int this)
{
if (this >= num_kinks)
return 0;
do {
this++;
} while (! kinks[this].is_a_kink);
return this;
}
static double
find_distance (Kink *kinks,
int this,
int next)
{
double dist = 0.0;
double dx, dy;
while (this != next)
{
dx = kinks[this].x - kinks[this + 1].x;
dy = kinks[this].y - kinks[this + 1].y;
dist += sqrt (SQR (dx) + SQR (dy));
this ++;
}
return dist;
}
static int
go_distance (Kink *kinks,
int this,
double dist,
double *x,
double *y)
{
double dx, dy;
double length = 0.0;
double t = 2.0;
dx = dy = 0.0;
if (dist == 0.0)
{
*x = kinks[this].x;
*y = kinks[this].y;
return 1;
}
while (dist > 0.0)
{
dx = kinks[this + 1].x - kinks[this].x;
dy = kinks[this + 1].y - kinks[this].y;
length = sqrt (SQR (dx) + SQR (dy));
dist -= length;
if (dist > 0.0)
this++;
}
t = (length + dist) / length;
*x = kinks[this].x + t * dx;
*y = kinks[this].y + t * dy;
return this;
}
static int
travel_length (Kink *kinks,
int num_kinks,
int start,
int dir,
int dist)
{
double dx, dy;
Kink * k1, * k2;
double distance = dist;
int length = 0;
while (distance > 0)
{
k1 = get_kink (kinks, start, num_kinks);
k2 = get_kink (kinks, start+dir, num_kinks);
dx = k2->x - k1->x;
dy = k2->y - k1->y;
distance -= sqrt (SQR (dx) + SQR (dy));
start += dir;
length += dir;
}
/* backup one step and return value */
return length;
}
static int
find_edge_xy (TempBuf *edge_buf,
int dir,
double x,
double y,
double *edge)
{
double dx, dy;
int ix, iy;
int xx, yy;
int rowstride, bytes;
int d11, d12, d21, d22;
unsigned char * data;
int b;
bytes = edge_buf->bytes;
rowstride = bytes * edge_buf->width;
x -= edge_buf->x;
y -= edge_buf->y;
ix = (int) (x + 2) - 2;
iy = (int) (y + 2) - 2;
/* If we're scouting out of bounds, return 0 */
if (ix < 0 || ix >= edge_buf->width ||
iy < 0 || iy >= edge_buf->height)
{
edge[0] = EDGE_STRENGTH;
return 1;
}
if (dir > 0)
{
dx = x - ix;
dy = y - iy;
}
else
{
dx = 1 - (x - ix);
dy = 1 - (y - iy);
}
data = temp_buf_data (edge_buf) + iy * rowstride + ix * bytes;
for (b = 0; b < bytes; b++)
{
if (dir > 0)
{
xx = ((ix + 1) >= edge_buf->width) ? 0 : bytes;
yy = ((iy + 1) >= edge_buf->height) ? 0 : rowstride;
}
else
{
xx = ((ix - 1) < 0) ? 0 : -bytes;
yy = ((iy - 1) < 0) ? 0 : -rowstride;
}
d11 = (data[0] > threshold) ? data[0] : 0;
d12 = (data[xx] > threshold) ? data[xx] : 0;
d21 = (data[yy] > threshold) ? data[yy] : 0;
d22 = (data[xx + yy] > threshold) ? data[xx + yy] : 0;
edge[b] = BILINEAR (d11, d12, d21, d22, dx, dy);
data++;
}
if (edge[0] > 0.0)
return 1;
else
return 0;
}
static void
find_boundary (Tool *tool)
{
/* Find directional changes */
shape_of_boundary (tool);
/* Process the kinks */
process_kinks (tool);
/* Determine the initial boundary */
initial_boundary (tool);
/* Get the edge map from the boundary extents */
edge_map_from_boundary (tool);
/* Orient the boundary based on edge detection */
orient_boundary (tool);
/* Setup the points array for localization */
reset_boundary (tool);
/* Localize the boundary based on edge detection
* and inter-segment elasticity
*/
while (localize_boundary (tool)) ;
/* Post process the points array to fit non-edge-seeking
* boundary points into the scheme of things
*/
post_process_boundary (tool);
/* convert the boundary into a mask */
CR_convert ((Iscissors *) tool->private,
(GDisplay *) tool->gdisp_ptr, NO);
}
static void
shape_of_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
Kink * kinks, * k;
double vec1[2], vec2[2], vec[2];
double std_dev;
double weight;
int left, right;
int i, j;
/* This function determines the kinkiness at each point in the
* original free-hand curve by finding the dotproduct between
* the two vectors formed at each point from that point to its
* immediate neighbors. A smoothing function is applied to
* determine the vectors to ameliorate the otherwise excessive
* jitter associated with original selection.
*/
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
iscissors->num_kinks = iscissors->num_segs;
if (iscissors->kinks)
g_free (iscissors->kinks);
kinks = iscissors->kinks = (Kink *) g_malloc (sizeof (Kink) *
(iscissors->num_kinks + 1));
for (i = 0; i < iscissors->num_kinks; i++)
{
kinks[i].x = segs[i].x1;
kinks[i].y = segs[i].y1;
}
for (i = 0; i < iscissors->num_kinks; i++)
{
left = travel_length (kinks, iscissors->num_kinks, i, -1, resolution);
right = travel_length (kinks, iscissors->num_kinks, i, 1, resolution);
std_dev = sqrt (-(SQR (left)) / (2 * log (EPSILON)));
if (fabs (std_dev) < EPSILON) std_dev = 1.0;
vec1[0] = 0.0;
vec1[1] = 0.0;
for (j = left; j < 0; j++)
{
k = get_kink (kinks, i+j, iscissors->num_kinks);
vec[0] = k->x - kinks[i].x;
vec[1] = k->y - kinks[i].y;
normalize (vec);
weight = exp (-SQR(j+1) / (2 * SQR (std_dev)));
vec1[0] += weight * vec[0];
vec1[1] += weight * vec[1];
}
normalize (vec1);
std_dev = sqrt (-(SQR (right)) / (2 * log (EPSILON)));
if (fabs (std_dev) < EPSILON) std_dev = 1.0;
vec2[0] = 0.0;
vec2[1] = 0.0;
for (j = 1; j <= right; j++)
{
k = get_kink (kinks, i+j, iscissors->num_kinks);
vec[0] = k->x - kinks[i].x;
vec[1] = k->y - kinks[i].y;
normalize (vec);
weight = exp (-SQR(j-1) / (2 * SQR (std_dev)));
vec2[0] += weight * vec[0];
vec2[1] += weight * vec[1];
}
normalize (vec2);
/* determine the kinkiness based on the two vectors */
kinks[i].kinkiness = (M_PI - acos (dotprod (vec1, vec2)))/ M_PI;
kinks[i].normal[0] = (vec1[0] + vec2[0]) / 2.0;
kinks[i].normal[1] = (vec1[1] + vec2[1]) / 2.0;
/* if the average vector is zero length... */
if (kinks[i].normal[0] < EPSILON && kinks[i].normal[1] < EPSILON)
{
/* normal = 90 degree rotation of vec1 */
kinks[i].normal[0] = -vec1[1];
kinks[i].normal[1] = vec1[0];
}
normalize (kinks[i].normal);
}
}
static void
process_kinks (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
Kink * kinks, * k_left, * k_right;
int x, y;
int i;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
kinks = iscissors->kinks;
for (i = 0; i < iscissors->num_kinks; i++)
{
/* transform from screen to image coordinates */
gdisplay_untransform_coords (gdisp, kinks[i].x, kinks[i].y,
&x, &y, FALSE, TRUE);
kinks[i].x = BOUNDS (x, 0, (gdisp->gimage->width - 1));
kinks[i].y = BOUNDS (y, 0, (gdisp->gimage->height - 1));
1997-11-25 06:05:25 +08:00
/* get local maximums */
k_left = get_kink (kinks, i-1, iscissors->num_kinks);
k_right = get_kink (kinks, i+1, iscissors->num_kinks);
if ((kinks[i].kinkiness > k_left->kinkiness) &&
(kinks[i].kinkiness >= k_right->kinkiness) &&
(kinks[i].kinkiness > kink_thres))
kinks[i].is_a_kink = 1;
else
kinks[i].is_a_kink = 0;
}
}
static void
initial_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
Kink * kinks;
double x, y;
double dist;
double res;
int i, n, this, next, k;
int num_pts = 0;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
kinks = iscissors->kinks;
/* for a connected boundary, set up the last kink as the same
* x & y coordinates as the first
*/
kinks[iscissors->num_kinks].x = kinks[0].x;
kinks[iscissors->num_kinks].y = kinks[0].y;
kinks[iscissors->num_kinks].is_a_kink = 1;
this = 0;
while ((next = find_next_kink (kinks, iscissors->num_kinks, this)))
{
/* Find the distance in pixels from the current to
* the next kink
*/
dist = find_distance (kinks, this, next);
if (dist > 0.0)
{
/* Find the number of segments that should be created
* to fill the void
*/
n = (int) (dist / resolution);
res = dist / (double) (n + 1);
add_point (&num_pts, 1, kinks[this].x, kinks[this].y, kinks[this].normal);
for (i = 1; i <= n; i++)
{
k = go_distance (kinks, this, res * i, &x, &y);
add_point (&num_pts, 0, (int) x, (int) y, kinks[k].normal);
}
}
this = next;
}
iscissors->num_pts = num_pts;
}
static void
edge_map_from_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
unsigned char black[EDGE_WIDTH] = { 0 };
int x, y, w, h;
int x1, y1, x2, y2;
int i;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
x = y = w = h = x1 = y1 = x2 = y2 = 0;
x1 = gdisp->gimage->width;
y1 = gdisp->gimage->height;
1997-11-25 06:05:25 +08:00
/* Find the edge map extents */
for (i = 0; i < iscissors->num_pts; i++)
{
x = BOUNDS (pts[i].x - LOCALIZE_RADIUS, 0,
gdisp->gimage->width);
1997-11-25 06:05:25 +08:00
y = BOUNDS (pts[i].y - LOCALIZE_RADIUS, 0,
gdisp->gimage->height);
1997-11-25 06:05:25 +08:00
w = BOUNDS (pts[i].x + LOCALIZE_RADIUS, 0,
gdisp->gimage->width);
1997-11-25 06:05:25 +08:00
h = BOUNDS (pts[i].y + LOCALIZE_RADIUS, 0,
gdisp->gimage->height);
1997-11-25 06:05:25 +08:00
w -= x;
h -= y;
set_edge_map_blocks (gdisp->gimage, x, y, w, h);
if (x < x1)
x1 = x;
if (y < y1)
y1 = y;
if (x + w > x2)
x2 = x + w;
if (y + h > y2)
y2 = y + h;
}
/* construct the edge map */
iscissors->edge_buf = temp_buf_new ((x2 - x1), (y2 - y1),
EDGE_WIDTH, x1, y1, black);
construct_edge_map (tool, iscissors->edge_buf);
}
static void
orient_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
int e1, e2;
double dx1, dy1, dx2, dy2;
double edge1[EDGE_WIDTH], edge2[EDGE_WIDTH];
double max;
double angle;
int dir = 0;
1997-11-25 06:05:25 +08:00
int i, j;
int max_dir;
int max_orient;
int found;
max_dir = 0;
max_orient = 0;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
for (i = 0; i < iscissors->num_pts; i++)
{
/* Search for the closest edge */
j = 0;
max = 0.0;
found = 0;
angle = atan2 (pts[i].normal[1], pts[i].normal[0]);
dir = ((angle > -3 * M_PI_4) && (angle < M_PI_4)) ? 1 : -1;
while (j < LOCALIZE_RADIUS && !found)
{
dx1 = pts[i].x + pts[i].normal[0] * j;
dy1 = pts[i].y + pts[i].normal[1] * j;
e1 = find_edge_xy (iscissors->edge_buf, dir, dx1, dy1, edge1);
dx2 = pts[i].x - pts[i].normal[0] * j;
dy2 = pts[i].y - pts[i].normal[1] * j;
e2 = find_edge_xy (iscissors->edge_buf, -dir, dx2, dy2, edge2);
e1 = e2 = 0;
if (e1 && e2)
{
if (edge1[0] > edge2[0])
pts[i].dir = dir;
else
{
pts[i].normal[0] *= -1;
pts[i].normal[1] *= -1;
pts[i].dir = -dir;
}
found = 1;
}
else if (e1)
{
pts[i].dir = dir;
found = 1;
}
else if (e2)
{
pts[i].dir = -dir;
pts[i].normal[0] *= -1;
pts[i].normal[1] *= -1;
found = 1;
}
else
{
if (edge1[0] > max)
{
max = edge1[0];
max_orient = 1;
max_dir = dir;
}
if (edge2[0] > max)
{
max = edge2[0];
max_orient = -1;
max_dir = -dir;
}
}
j++;
}
if (!found && max > miss_thres)
{
pts[i].normal[0] *= max_orient;
pts[i].normal[1] *= max_orient;
pts[i].dir = max_dir;
}
else if (!found)
{
pts[i].normal[0] = 0.0;
pts[i].normal[1] = 0.0;
pts[i].dir = 0;
}
}
}
static void
reset_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
double edge[EDGE_WIDTH];
int i;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
for (i = 0; i < iscissors->num_pts; i++)
{
if (pts[i].dir == 0)
pts[i].stable = 1;
else if (find_edge_xy (iscissors->edge_buf,
pts[i].dir, pts[i].x, pts[i].y, edge))
pts[i].stable = 1;
else
pts[i].stable = 0;
pts[i].dx = pts[i].x;
pts[i].dy = pts[i].y;
}
}
static int
localize_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
double x, y;
double dx, dy;
double max;
double d_l, d_r;
double edge[EDGE_WIDTH];
int i, left, right;
int moved = 0;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
/* this function lets the boundary crawl in its desired
* direction, but within limits set by the elasticity
* variable. The process is incremental, so this function
* needs to be repeatedly called until there is no discernable
* movement
*/
for (i = 0; i < iscissors->num_pts; i++)
{
if (!pts[i].stable)
{
x = pts[i].dx + pts[i].normal[0];
y = pts[i].dy + pts[i].normal[1];
left = (i == 0) ? iscissors->num_pts - 1 : i - 1;
right = (i == (iscissors->num_pts - 1)) ? 0 : i + 1;
dx = x - pts[left].dx;
dy = y - pts[left].dy;
d_l = sqrt (SQR (dx) + SQR (dy));
dx = x - pts[right].dx;
dy = y - pts[right].dy;
d_r = sqrt (SQR (dx) + SQR (dy));
dx = pts[left].dx - pts[right].dx;
dy = pts[left].dy - pts[right].dy;
max = (sqrt (SQR (dx) + SQR (dy)) / 2.0) * (1.0 + elasticity);
/* If moving the point along it's directional vector
* still satisfies the elasticity constraints (OR)
* the point is a kink (in which case it can violate
* elasticity completely.
*/
if (((d_l < max) && (d_r < max)) || pts[i].kink)
{
pts[i].dx = x;
pts[i].dy = y;
if (find_edge_xy (iscissors->edge_buf, pts[i].dir, x, y, edge))
pts[i].stable = 1;
else
moved++;
}
}
}
return moved;
}
static void
post_process_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
int i;
int left, right;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
/* Relocate all points which did not manage to seek an edge
* to the average of their edge-seeking neighbors
* Also relocate points which failed to reach a stable
* edge position. These cases indicate that the point
* somehow slipped through the cracks and is headed towards
* lands unknown and most likely undesired.
*/
for (i = 0; i < iscissors->num_pts; i++)
{
if (pts[i].dir == 0 || pts[i].stable == 0)
{
left = (i == 0) ? iscissors->num_pts - 1 : i - 1;
right = (i == (iscissors->num_pts - 1)) ? 0 : i + 1;
if (pts[left].stable && pts[right].stable)
{
pts[i].dx = (pts[left].dx + pts[right].dx) / 2.0;
pts[i].dy = (pts[left].dy + pts[right].dy) / 2.0;
}
}
}
/* connect the boundary */
pts[iscissors->num_pts].dx = pts[0].dx;
pts[iscissors->num_pts].dy = pts[0].dy;
}
static void
bezierify_boundary (Tool *tool)
{
Iscissors * iscissors;
GDisplay * gdisp;
CRMatrix geometry;
CRMatrix bezier_geom;
BezierPoint * bez_pts;
BezierPoint * new_pt;
BezierPoint * last_pt;
int indices[4];
int i, j;
gdisp = (GDisplay *) tool->gdisp_ptr;
iscissors = (Iscissors *) tool->private;
if (iscissors->num_pts < 4)
{
message_box ("Boundary contains < 4 points! Cannot bezierify.", NULL, NULL);
return;
}
bez_pts = NULL;
last_pt = NULL;
for (i = 0; i < iscissors->num_pts; i ++)
{
indices[0] = (i < 3) ? (iscissors->num_pts + i - 3) : (i - 3);
indices[1] = (i < 2) ? (iscissors->num_pts + i - 2) : (i - 2);
indices[2] = (i < 1) ? (iscissors->num_pts + i - 1) : (i - 1);
indices[3] = i;
for (j = 0; j < 4; j++)
{
geometry[j][0] = pts[indices[j]].dx;
geometry[j][1] = pts[indices[j]].dy;
geometry[j][2] = 0;
geometry[j][3] = 0;
}
CR_compose (CR_bezier_basis, geometry, bezier_geom);
for (j = 0; j < 3; j++)
{
new_pt = (BezierPoint *) g_malloc (sizeof (BezierPoint));
if (last_pt) last_pt->next = new_pt;
else bez_pts = new_pt;
new_pt->type = (j == 0) ? BEZIER_ANCHOR : BEZIER_CONTROL;
new_pt->x = bezier_geom[j][0];
new_pt->y = bezier_geom[j][1];
new_pt->next = NULL;
new_pt->prev = last_pt;
last_pt = new_pt;
}
}
/* final anchor point */
last_pt->next = bez_pts;
bez_pts->prev = last_pt;
/* Load this curve into the bezier tool */
bezier_select_load (gdisp, bez_pts, iscissors->num_pts * 3, 1);
}
static TempBuf *
calculate_edge_map (GImage *gimage,
int x,
int y,
int w,
int h)
{
TempBuf * edge_map;
PixelRegion srcPR, destPR;
int width, height;
int offx, offy;
int i, j;
int x1, y1, x2, y2;
double gradient;
double dx, dy;
int xx, yy;
unsigned char *gr, * dh, * dv, * cm;
int hmax, vmax;
int b;
1997-11-25 06:05:25 +08:00
double prev, next;
GimpDrawable *drawable;
void *pr;
FILE *dump;
drawable = gimage_active_drawable (gimage);
1997-11-25 06:05:25 +08:00
x1 = y1 = x2 = y2 = 0;
/* allocate the new edge map */
edge_map = temp_buf_new (w, h, EDGE_WIDTH, x, y, NULL);
/* calculate the extent of the search make a 1 pixel border */
x1 = BOUNDS (x, 0, gimage->width);
y1 = BOUNDS (y, 0, gimage->height);
x2 = BOUNDS (x + w, 0, gimage->width);
y2 = BOUNDS (y + h, 0, gimage->height);
1997-11-25 06:05:25 +08:00
width = x2 - x1;
height = y2 - y1;
offx = (x - x1);
offy = (y - y1);
/* Set the drawable up as the source pixel region */
/*srcPR.bytes = drawable_bytes (drawable);
1997-11-25 06:05:25 +08:00
srcPR.w = width;
srcPR.h = height;
srcPR.rowstride = gimage->width * drawable_bytes (drawable);
srcPR.data = drawable_data (drawable) + y1 * srcPR.rowstride + x1 * srcPR.bytes;*/
pixel_region_init(&srcPR, drawable_data(drawable), x1, y1, width, height, 1);
1997-11-25 06:05:25 +08:00
/* Get the horizontal derivative */
destPR.data = conv1 + MAX_CHANNELS * (CONV_WIDTH * offy + offx);
destPR.rowstride = CONV_WIDTH * MAX_CHANNELS;
destPR.tiles = NULL;
for (pr =pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
gaussian_deriv (&srcPR, &destPR, HORIZONTAL, std_dev);
1997-11-25 06:05:25 +08:00
/* Get the vertical derivative */
destPR.data = conv2 + MAX_CHANNELS * (CONV_WIDTH * offy + offx);
for (pr =pixel_regions_register (2, &srcPR, &destPR); pr != NULL; pr = pixel_regions_process (pr))
gaussian_deriv (&srcPR, &destPR, VERTICAL, std_dev);
1997-11-25 06:05:25 +08:00
/* fill in the edge map */
for (i = 0; i < height; i++)
{
gr = grad + (CONV_WIDTH + 2) * (i+1) + 1;
dh = conv1 + destPR.rowstride * i;
dv = conv2 + destPR.rowstride * i;
for (j = 0; j < width; j++)
{
hmax = dh[0] - 128;
vmax = dv[0] - 128;
for (b = 1; b < drawable_bytes (drawable); b++)
1997-11-25 06:05:25 +08:00
{
if (abs (dh[b] - 128) > abs (hmax))
hmax = dh[b] - 128;
if (abs (dv[b] - 128) > abs (vmax))
vmax = dv[b] - 128;
}
1997-11-25 06:05:25 +08:00
/* store the information in the edge map */
dh[0] = hmax + 128;
dv[0] = vmax + 128;
/* Find the gradient */
gradient = sqrt (SQR (hmax) + SQR (vmax));
/* Make the edge gradient map extend one pixel further */
if (j == 0)
gr[-1] = (unsigned char) gradient;
if (j == (width - 1))
gr[+1] = (unsigned char) gradient;
*gr++ = (unsigned char) gradient;
dh += srcPR.bytes;
dv += srcPR.bytes;
}
}
/* Make the edge gradient map extend one row further */
memcpy (grad, grad + (CONV_WIDTH+2), (CONV_WIDTH+2));
1997-11-25 06:05:25 +08:00
memcpy (grad + (CONV_WIDTH+2) * (CONV_HEIGHT+1),
grad + (CONV_WIDTH+2) * (CONV_HEIGHT),
(CONV_WIDTH+2));
cm = temp_buf_data (edge_map);
for (i = 0; i < h; i++)
{
gr = grad + (CONV_WIDTH+2)*(i + offy + 1) + (offx + 1);
dh = conv1 + destPR.rowstride*(i + offy) + srcPR.bytes * offx;
dv = conv2 + destPR.rowstride*(i + offy) + srcPR.bytes * offx;
for (j = 0; j < w; j++)
{
dx = (double) (dh[0] - 128) / 128.0;
dy = (double) (dv[0] - 128) / 128.0;
xx = (dx > 0) ? 1 : -1;
yy = (dy > 0) ? (CONV_WIDTH + 2) : -(CONV_WIDTH + 2);
dx = fabs (dx);
dy = fabs (dy);
prev = BILINEAR (gr[0], gr[-xx], gr[-yy], gr[-xx -yy], dx, dy);
next = BILINEAR (gr[0], gr[xx], gr[yy], gr[xx + yy], dx, dy);
if (gr[0] >= prev && gr[0] >= next)
*cm++ = gr[0];
else
*cm++ = 0;
/*cm++ = dh[0];*/
/*cm++ = dv[0];*/
1997-11-25 06:05:25 +08:00
dh += srcPR.bytes;
dv += srcPR.bytes;
gr ++;
}
}
/* dump=fopen("/ugrad/summersn/dump", "w"); */
/* fprintf(dump, "P5\n%d %d\n255\n", edge_map->width, edge_map->height); */
/* fwrite(edge_map->data, edge_map->width * edge_map->height, sizeof (guchar), dump); */
/* fclose (dump); */
1997-11-25 06:05:25 +08:00
return edge_map;
1997-11-25 06:05:25 +08:00
}
static void
construct_edge_map (Tool *tool,
TempBuf *edge_buf)
{
TempBuf * block;
int index;
int x, y;
int endx, endy;
int row, col;
int offx, offy;
int x2, y2;
PixelRegion srcPR, destPR;
FILE *dump;
1997-11-25 06:05:25 +08:00
/* init some variables */
srcPR.bytes = edge_buf->bytes;
destPR.rowstride = edge_buf->bytes * edge_buf->width;
destPR.x = edge_buf->x;
destPR.y = edge_buf->y;
destPR.h = edge_buf->height;
destPR.w = edge_buf -> width;
destPR.bytes = edge_buf->bytes;
srcPR.tiles = destPR.tiles = NULL;
1997-11-25 06:05:25 +08:00
y = edge_buf->y;
endx = edge_buf->x + edge_buf->width;
endy = edge_buf->y + edge_buf->height;
row = (y / BLOCK_HEIGHT);
/* patch the buffer with the saved portions of the image */
while (y < endy)
{
x = edge_buf->x;
col = (x / BLOCK_WIDTH);
/* calculate y offset into this row of blocks */
offy = (y - row * BLOCK_HEIGHT);
y2 = (row + 1) * BLOCK_HEIGHT;
if (y2 > endy) y2 = endy;
srcPR.h = y2 - y;
while (x < endx)
{
index = row * horz_blocks + col;
block = edge_map_blocks [index];
/* If the block exists, patch it into buf */
if (block)
{
/* calculate x offset into the block */
1997-11-25 06:05:25 +08:00
offx = (x - col * BLOCK_WIDTH);
x2 = (col + 1) * BLOCK_WIDTH;
if (x2 > endx) x2 = endx;
srcPR.w = x2 - x;
/* Calculate the offsets into each buffer */
srcPR.rowstride = srcPR.bytes * block->width;
srcPR.data = temp_buf_data (block) + offy * srcPR.rowstride + offx * srcPR.bytes;
destPR.data = temp_buf_data (edge_buf) +
(y - edge_buf->y) * destPR.rowstride + (x - edge_buf->x) * edge_buf->bytes;
copy_region (&srcPR, &destPR);
}
col ++;
x = col * BLOCK_WIDTH;
}
row ++;
y = row * BLOCK_HEIGHT;
}
/* dump the edge buffer for debugging*/
/* dump=fopen("/ugrad/summersn/dump", "w");
fprintf(dump, "P5\n%d %d\n255\n", edge_buf->width, edge_buf->height);
fwrite(edge_buf->data, edge_buf->width * edge_buf->height, sizeof (guchar), dump);
fclose (dump); */
1997-11-25 06:05:25 +08:00
}
/* edge map blocks utility functions */
static void
set_edge_map_blocks (void *gimage_ptr,
int x,
int y,
int w,
int h)
{
GImage * gimage;
int endx, endy;
int startx;
int index;
int x1, y1;
int x2, y2;
int row, col;
int width, height;
width = height = 0;
gimage = (GImage *) gimage_ptr;
width = gimage->width;
height = gimage->height;
1997-11-25 06:05:25 +08:00
startx = x;
endx = x + w;
endy = y + h;
row = y / BLOCK_HEIGHT;
while (y < endy)
{
col = x / BLOCK_WIDTH;
while (x < endx)
{
index = row * horz_blocks + col;
/* If the block doesn't exist, create and initialize it */
if (! edge_map_blocks [index])
{
/* determine memory efficient width and height of block */
x1 = col * BLOCK_WIDTH;
x2 = BOUNDS (x1 + BLOCK_WIDTH, 0, width);
w = (x2 - x1);
y1 = row * BLOCK_HEIGHT;
y2 = BOUNDS (y1 + BLOCK_HEIGHT, 0, height);
h = (y2 - y1);
/* calculate a edge map for the specified portion of the gimage */
edge_map_blocks [index] = calculate_edge_map (gimage, x1, y1, w, h);
}
col++;
x = col * BLOCK_WIDTH;
}
row ++;
y = row * BLOCK_HEIGHT;
x = startx;
}
}
static void
allocate_edge_map_blocks (int block_width,
int block_height,
int image_width,
int image_height)
{
int num_blocks;
int i;
/* calculate the number of rows and cols in the edge map block grid */
horz_blocks = (image_width + block_width - 1) / block_width;
vert_blocks = (image_height + block_height - 1) / block_height;
/* Allocate the array */
num_blocks = horz_blocks * vert_blocks;
edge_map_blocks = (TempBuf **) g_malloc (sizeof (TempBuf *) * num_blocks);
/* Initialize the array */
for (i = 0; i < num_blocks; i++)
edge_map_blocks [i] = NULL;
}
static void
free_edge_map_blocks ()
{
int i;
int num_blocks;
if (!edge_map_blocks)
return;
num_blocks = vert_blocks * horz_blocks;
for (i = 0; i < num_blocks; i++)
if (edge_map_blocks [i])
temp_buf_free (edge_map_blocks [i]);
g_free (edge_map_blocks);
edge_map_blocks = NULL;
}
/*********************************************/
/* Functions for gaussian convolutions */
/*********************************************/
static void
gaussian_deriv (PixelRegion *input,
PixelRegion *output,
int type,
double std_dev)
{
long width, height;
unsigned char *dest, *dp;
unsigned char *src, *sp, *s;
int bytes;
int *buf, *b;
int chan;
int i, row, col;
int start, end;
int curve_array [9];
int sum_array [9];
int * curve;
int * sum;
int val;
int total;
int length;
int initial_p[MAX_CHANNELS], initial_m[MAX_CHANNELS];
length = 3; /* static for speed */
width = input->w;
height = input->h;
bytes = input->bytes;
/* initialize */
curve = curve_array + length;
sum = sum_array + length;
buf = g_malloc (sizeof (int) * MAXIMUM (width, height) * bytes);
if (type == VERTICAL)
{
make_curve_d (curve, sum, std_dev, length);
total = sum[0] * -2;
}
else
{
make_curve (curve, sum, std_dev, length);
total = sum[length] + curve[length];
}
src = input->data;
dest = output->data;
for (col = 0; col < width; col++)
{
sp = src;
dp = dest;
b = buf;
src += bytes;
dest += bytes;
for (chan = 0; chan < bytes; chan++)
{
initial_p[chan] = sp[chan];
initial_m[chan] = sp[(height-1) * input->rowstride + chan];
}
for (row = 0; row < height; row++)
{
start = (row < length) ? -row : -length;
end = (height <= (row + length)) ? (height - row - 1) : length;
for (chan = 0; chan < bytes; chan++)
{
s = sp + (start * input->rowstride) + chan ;
val = 0;
i = start;
if (start != -length)
val += initial_p[chan] * (sum[start] - sum[-length]);
while (i <= end)
{
val += *s * curve[i++];
s += input->rowstride;
}
if (end != length)
val += initial_m[chan] * (sum[length] + curve[length] - sum[end+1]);
*b++ = val / total;
}
sp += input->rowstride;
}
b = buf;
if (type == VERTICAL)
for (row = 0; row < height; row++)
{
for (chan = 0; chan < bytes; chan++)
{
b[chan] = b[chan] + 128;
if (b[chan] > 255)
dp[chan] = 255;
else if (b[chan] < 0)
dp[chan] = 0;
else
dp[chan] = b[chan];
}
b += bytes;
dp += output->rowstride;
}
else
for (row = 0; row < height; row++)
{
for (chan = 0; chan < bytes; chan++)
{
if (b[chan] > 255)
dp[chan] = 255;
else if (b[chan] < 0)
dp[chan] = 0;
else
dp[chan] = b[chan];
}
b += bytes;
dp += output->rowstride;
}
}
if (type == HORIZONTAL)
{
make_curve_d (curve, sum, std_dev, length);
total = sum[0] * -2;
}
else
{
make_curve (curve, sum, std_dev, length);
total = sum[length] + curve[length];
}
src = output->data;
dest = output->data;
for (row = 0; row < height; row++)
{
sp = src;
dp = dest;
b = buf;
src += output->rowstride;
dest += output->rowstride;
for (chan = 0; chan < bytes; chan++)
{
initial_p[chan] = sp[chan];
initial_m[chan] = sp[(width-1) * bytes + chan];
}
for (col = 0; col < width; col++)
{
start = (col < length) ? -col : -length;
end = (width <= (col + length)) ? (width - col - 1) : length;
for (chan = 0; chan < bytes; chan++)
{
s = sp + (start * bytes) + chan;
val = 0;
i = start;
if (start != -length)
val += initial_p[chan] * (sum[start] - sum[-length]);
while (i <= end)
{
val += *s * curve[i++];
s += bytes;
}
if (end != length)
val += initial_m[chan] * (sum[length] + curve[length] - sum[end+1]);
*b++ = val / total;
}
sp += bytes;
}
b = buf;
if (type == HORIZONTAL)
for (col = 0; col < width; col++)
{
for (chan = 0; chan < bytes; chan++)
{
b[chan] = b[chan] + 128;
if (b[chan] > 255)
dp[chan] = 255;
else if (b[chan] < 0)
dp[chan] = 0;
else
dp[chan] = b[chan];
}
b += bytes;
dp += bytes;
}
else
for (col = 0; col < width; col++)
{
for (chan = 0; chan < bytes; chan++)
{
if (b[chan] > 255)
dp[chan] = 255;
else if (b[chan] < 0)
dp[chan] = 0;
else
dp[chan] = b[chan];
}
b += bytes;
dp += bytes;
}
}
g_free (buf);
}
/*
* The equations: g(r) = exp (- r^2 / (2 * sigma^2))
* r = sqrt (x^2 + y ^2)
*/
static void
make_curve (int *curve,
int *sum,
double sigma,
int length)
{
double sigma2;
int i;
sigma2 = sigma * sigma;
curve[0] = 255;
for (i = 1; i <= length; i++)
{
curve[i] = (int) (exp (- (i * i) / (2 * sigma2)) * 255);
curve[-i] = curve[i];
}
sum[-length] = 0;
for (i = -length+1; i <= length; i++)
sum[i] = sum[i-1] + curve[i-1];
}
/*
* The equations: d_g(r) = -r * exp (- r^2 / (2 * sigma^2)) / sigma^2
* r = sqrt (x^2 + y ^2)
*/
static void
make_curve_d (int *curve,
int *sum,
double sigma,
int length)
{
double sigma2;
int i;
sigma2 = sigma * sigma;
curve[0] = 0;
for (i = 1; i <= length; i++)
{
curve[i] = (int) ((i * exp (- (i * i) / (2 * sigma2)) / sigma2) * 255);
curve[-i] = -curve[i];
}
sum[-length] = 0;
sum[0] = 0;
for (i = 1; i <= length; i++)
{
sum[-length + i] = sum[-length + i - 1] + curve[-length + i - 1];
sum[i] = sum[i - 1] + curve[i - 1];
}
}
/***********************************************/
/* Functions for Catmull-Rom area conversion */
/***********************************************/
static GSList **CR_scanlines;
1997-11-25 06:05:25 +08:00
static int start_convert;
static int width, height;
static int lastx;
static int lasty;
static void
CR_convert (Iscissors *iscissors,
GDisplay *gdisp,
int antialias)
{
int indices[4];
GSList *list;
1997-11-25 06:05:25 +08:00
int draw_type;
int * vals, val;
int x, w;
int i, j;
PixelRegion maskPR;
unsigned char *buf, *b;
1997-11-25 06:05:25 +08:00
vals = NULL;
/* destroy previous region */
if (iscissors->mask)
{
channel_delete (iscissors->mask);
iscissors->mask = NULL;
}
/* get the new mask's maximum extents */
if (antialias)
{
width = gdisp->gimage->width * SUPERSAMPLE;
height = gdisp->gimage->height * SUPERSAMPLE;
draw_type = AA_IMAGE_COORDS;
/* allocate value array */
vals = (int *) g_malloc (sizeof (int) * width);
buf = (unsigned char *) g_malloc (sizeof(unsigned char *) * width);
1997-11-25 06:05:25 +08:00
}
else
{
width = gdisp->gimage->width;
height = gdisp->gimage->height;
draw_type = IMAGE_COORDS;
buf = NULL;
vals = NULL;
1997-11-25 06:05:25 +08:00
}
/* create a new mask */
iscissors->mask = channel_new_mask (gdisp->gimage->ID, gdisp->gimage->width,
gdisp->gimage->height);
/* allocate room for the scanlines */
CR_scanlines = g_malloc (sizeof (GSList *) * height);
1997-11-25 06:05:25 +08:00
/* zero out the scanlines */
for (i = 0; i < height; i++)
CR_scanlines[i] = NULL;
/* scan convert the curve */
start_convert = 1;
for (i = 0; i < iscissors->num_pts; i ++)
{
indices[0] = (i < 3) ? (iscissors->num_pts + i - 3) : (i - 3);
indices[1] = (i < 2) ? (iscissors->num_pts + i - 2) : (i - 2);
indices[2] = (i < 1) ? (iscissors->num_pts + i - 1) : (i - 1);
indices[3] = i;
iscissors_draw_CR (gdisp, iscissors, pts, indices, draw_type);
}
pixel_region_init (&maskPR, iscissors->mask->drawable.tiles, 0, 0,
iscissors->mask->drawable.width,
iscissors->mask->drawable.height, TRUE);
1997-11-25 06:05:25 +08:00
for (i = 0; i < height; i++)
{
list = CR_scanlines[i];
/* zero the vals array */
if (antialias && !(i % SUPERSAMPLE))
memset (vals, 0, width * sizeof (int));
while (list)
{
x = (long) list->data;
list = list->next;
if (list)
{
w = (long) list->data - x;
if (!antialias)
channel_add_segment (iscissors->mask, x, i, w, 255);
else
for (j = 0; j < w; j++)
vals[j + x] += 255;
list = g_slist_next (list);
1997-11-25 06:05:25 +08:00
}
}
if (antialias && !((i+1) % SUPERSAMPLE))
{
b = buf;
for (j = 0; j < width; j += SUPERSAMPLE)
{
val = 0;
for (x = 0; x < SUPERSAMPLE; x++)
val += vals[j + x];
1997-11-25 06:05:25 +08:00
*b++ = (unsigned char) (val / SUPERSAMPLE2);
}
pixel_region_set_row (&maskPR, 0, (i / SUPERSAMPLE), iscissors->mask->drawable.width, buf);
}
1997-11-25 06:05:25 +08:00
g_slist_free (CR_scanlines[i]);
1997-11-25 06:05:25 +08:00
}
if (antialias)
g_free (vals);
g_free (CR_scanlines);
CR_scanlines = NULL;
}
static void
CR_convert_points (GdkPoint *points,
int npoints)
{
int i;
if (start_convert)
start_convert = 0;
else
CR_convert_line (CR_scanlines, lastx, lasty, points[0].x, points[0].y);
for (i = 0; i < (npoints - 1); i++)
{
CR_convert_line (CR_scanlines,
points[i].x, points[i].y,
points[i+1].x, points[i+1].y);
}
lastx = points[npoints-1].x;
lasty = points[npoints-1].y;
}
static void
CR_convert_line (GSList **scanlines,
int x1,
int y1,
int x2,
int y2)
1997-11-25 06:05:25 +08:00
{
int dx, dy;
int error, inc;
int tmp;
float slope;
if (y1 == y2)
return;
if (y1 > y2)
{
tmp = y2; y2 = y1; y1 = tmp;
tmp = x2; x2 = x1; x1 = tmp;
}
if (y1 < 0)
{
if (y2 < 0)
return;
if (x2 == x1)
{
y1 = 0;
}
else
{
slope = (float) (y2 - y1) / (float) (x2 - x1);
x1 = x2 + (0 - y2) / slope;
y1 = 0;
}
}
if (y2 >= height)
{
if (y1 >= height)
return;
if (x2 == x1)
{
y2 = height;
}
else
{
slope = (float) (y2 - y1) / (float) (x2 - x1);
x2 = x1 + (height - y1) / slope;
y2 = height;
}
}
if (y1 == y2)
return;
dx = x2 - x1;
dy = y2 - y1;
scanlines = &scanlines[y1];
if (((dx < 0) ? -dx : dx) > ((dy < 0) ? -dy : dy))
{
if (dx < 0)
{
inc = -1;
dx = -dx;
}
else
{
inc = 1;
}
error = -dx /2;
while (x1 != x2)
{
error += dy;
if (error > 0)
{
error -= dx;
*scanlines = CR_insert_in_list (*scanlines, x1);
scanlines++;
}
x1 += inc;
}
}
else
{
error = -dy /2;
if (dx < 0)
{
dx = -dx;
inc = -1;
}
else
{
inc = 1;
}
while (y1++ < y2)
{
*scanlines = CR_insert_in_list (*scanlines, x1);
scanlines++;
error += dx;
if (error > 0)
{
error -= dy;
x1 += inc;
}
}
}
}
static GSList *
CR_insert_in_list (GSList *list,
int x)
1997-11-25 06:05:25 +08:00
{
GSList *orig = list;
GSList *rest;
1997-11-25 06:05:25 +08:00
if (!list)
return g_slist_prepend (list, (gpointer) ((long) x));
1997-11-25 06:05:25 +08:00
while (list)
{
rest = g_slist_next (list);
1997-11-25 06:05:25 +08:00
if (x < (long) list->data)
{
rest = g_slist_prepend (rest, list->data);
1997-11-25 06:05:25 +08:00
list->next = rest;
list->data = (gpointer) ((long) x);
return orig;
}
else if (!rest)
{
g_slist_append (list, (gpointer) ((long) x));
1997-11-25 06:05:25 +08:00
return orig;
}
list = g_slist_next (list);
1997-11-25 06:05:25 +08:00
}
return orig;
}