mirror of https://github.com/GNOME/gimp.git
1914 lines
54 KiB
C
1914 lines
54 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. */
|
|
|
|
/* Personal note: Dr. Barrett, one of the authors of the paper written above
|
|
* is not only one of the most brilliant people I have ever met, he is an
|
|
* incredible professor who genuinely cares about his students and wants them
|
|
* to learn as much as they can about the topic.
|
|
*
|
|
* I didn't even notice I was taking a class from the person who wrote the
|
|
* paper until halfway through the semester.
|
|
* -- Rockwalrus
|
|
*/
|
|
|
|
/* The history of this implementation is lonog and varied. It was
|
|
* orignally done by Spencer and Peter, and worked fine in the 0.54
|
|
* (motif only) release of the gimp. Later revisions (0.99.something
|
|
* until about 1.1.4) completely changed the algorithm used, until it
|
|
* bore little resemblance to the one described in the paper above.
|
|
* The 0.54 version of the algorithm was then forwards ported to 1.1.4
|
|
* by Austin Donnelly.
|
|
*/
|
|
|
|
/* Livewire boundary implementation done by Laramie Leavitt */
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
#include "libgimpwidgets/gimpwidgets.h"
|
|
|
|
#include "tools-types.h"
|
|
|
|
#include "base/pixel-region.h"
|
|
#include "base/temp-buf.h"
|
|
#include "base/tile-manager.h"
|
|
#include "base/tile.h"
|
|
|
|
#include "paint-funcs/paint-funcs.h"
|
|
|
|
#include "core/gimpchannel.h"
|
|
#include "core/gimpchannel-select.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimppickable.h"
|
|
#include "core/gimpprojection.h"
|
|
#include "core/gimpscanconvert.h"
|
|
#include "core/gimptoolinfo.h"
|
|
|
|
#include "widgets/gimphelp-ids.h"
|
|
|
|
#include "display/gimpdisplay.h"
|
|
|
|
#include "gimpiscissorstool.h"
|
|
#include "gimpselectionoptions.h"
|
|
#include "gimptoolcontrol.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
/* 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 9 /* size (in pixels) of seed handles */
|
|
#define POINT_HALFWIDTH (POINT_WIDTH / 2)
|
|
#define EXTEND_BY 0.2 /* proportion to expand cost map by */
|
|
#define FIXED 5 /* additional fixed size to expand cost map */
|
|
#define MIN_GRADIENT 63 /* gradients < this are directionless */
|
|
|
|
#define MAX_POINTS 2048
|
|
|
|
#define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
|
|
#define BLOCK_WIDTH 64
|
|
#define BLOCK_HEIGHT 64
|
|
#define CONV_WIDTH (BLOCK_WIDTH + 2)
|
|
#define CONV_HEIGHT (BLOCK_HEIGHT + 2)
|
|
|
|
/* weight to give between gradient (_G) and direction (_D) */
|
|
#define OMEGA_D 0.2
|
|
#define OMEGA_G 0.8
|
|
|
|
/* sentinel to mark seed point in ?cost? map */
|
|
#define SEED_POINT 9
|
|
|
|
/* Functional defines */
|
|
#define PIXEL_COST(x) ((x) >> 8)
|
|
#define PIXEL_DIR(x) ((x) & 0x000000ff)
|
|
|
|
|
|
struct _ICurve
|
|
{
|
|
gint x1, y1;
|
|
gint x2, y2;
|
|
GPtrArray *points;
|
|
};
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void gimp_iscissors_tool_class_init (GimpIscissorsToolClass *klass);
|
|
static void gimp_iscissors_tool_init (GimpIscissorsTool *iscissors);
|
|
|
|
static void gimp_iscissors_tool_finalize (GObject *object);
|
|
|
|
static void gimp_iscissors_tool_control (GimpTool *tool,
|
|
GimpToolAction action,
|
|
GimpDisplay *gdisp);
|
|
static void gimp_iscissors_tool_button_press (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp);
|
|
static void gimp_iscissors_tool_button_release (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp);
|
|
static void gimp_iscissors_tool_motion (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp);
|
|
static void gimp_iscissors_tool_oper_update (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp);
|
|
static void gimp_iscissors_tool_cursor_update (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp);
|
|
|
|
static void gimp_iscissors_tool_reset (GimpIscissorsTool *iscissors);
|
|
static void gimp_iscissors_tool_draw (GimpDrawTool *draw_tool);
|
|
|
|
|
|
static void iscissors_convert (GimpIscissorsTool *iscissors,
|
|
GimpDisplay *gdisp);
|
|
static TileManager * gradient_map_new (GimpImage *gimage);
|
|
|
|
static void find_optimal_path (TileManager *gradient_map,
|
|
TempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gint xs,
|
|
gint ys);
|
|
static void find_max_gradient (GimpIscissorsTool *iscissors,
|
|
GimpImage *gimage,
|
|
gint *x,
|
|
gint *y);
|
|
static void calculate_curve (GimpTool *tool,
|
|
ICurve *curve);
|
|
static void iscissors_draw_curve (GimpDrawTool *draw_tool,
|
|
ICurve *curve);
|
|
static void iscissors_free_icurves (GSList *list);
|
|
static void iscissors_free_buffers (GimpIscissorsTool *iscissors);
|
|
|
|
static gint mouse_over_vertex (GimpIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
static gboolean clicked_on_vertex (GimpTool *tool);
|
|
static GSList * mouse_over_curve (GimpIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
static gboolean clicked_on_curve (GimpTool *tool);
|
|
|
|
static void precalculate_arrays (void);
|
|
static GPtrArray * plot_pixels (GimpIscissorsTool *iscissors,
|
|
TempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint xs,
|
|
gint ys,
|
|
gint xe,
|
|
gint ye);
|
|
|
|
|
|
/* static variables */
|
|
|
|
/* where to move on a given link direction */
|
|
static gint move[8][2] =
|
|
{
|
|
{ 1, 0 },
|
|
{ 0, 1 },
|
|
{ -1, 1 },
|
|
{ 1, 1 },
|
|
{ -1, 0 },
|
|
{ 0, -1 },
|
|
{ 1, -1 },
|
|
{ -1, -1 },
|
|
};
|
|
|
|
/* IE:
|
|
* '---+---+---`
|
|
* | 7 | 5 | 6 |
|
|
* +---+---+---+
|
|
* | 4 | | 0 |
|
|
* +---+---+---+
|
|
* | 2 | 1 | 3 |
|
|
* `---+---+---'
|
|
*/
|
|
|
|
|
|
/* temporary convolution buffers -- */
|
|
static guchar maxgrad_conv0[TILE_WIDTH * TILE_HEIGHT * 4] = "";
|
|
static guchar maxgrad_conv1[TILE_WIDTH * TILE_HEIGHT * 4] = "";
|
|
static guchar maxgrad_conv2[TILE_WIDTH * TILE_HEIGHT * 4] = "";
|
|
|
|
|
|
static gfloat horz_deriv[9] =
|
|
{
|
|
1, 0, -1,
|
|
2, 0, -2,
|
|
1, 0, -1,
|
|
};
|
|
|
|
static gfloat vert_deriv[9] =
|
|
{
|
|
1, 2, 1,
|
|
0, 0, 0,
|
|
-1, -2, -1,
|
|
};
|
|
|
|
static gfloat blur_32[9] =
|
|
{
|
|
1, 1, 1,
|
|
1, 24, 1,
|
|
1, 1, 1,
|
|
};
|
|
|
|
static gfloat distance_weights[GRADIENT_SEARCH * GRADIENT_SEARCH];
|
|
|
|
static gint diagonal_weight[256];
|
|
static gint direction_value[256][4];
|
|
static gboolean initialized = FALSE;
|
|
static Tile *cur_tile = NULL;
|
|
|
|
|
|
static GimpSelectionToolClass *parent_class = NULL;
|
|
|
|
|
|
void
|
|
gimp_iscissors_tool_register (GimpToolRegisterCallback callback,
|
|
gpointer data)
|
|
{
|
|
(* callback) (GIMP_TYPE_ISCISSORS_TOOL,
|
|
GIMP_TYPE_SELECTION_OPTIONS,
|
|
gimp_selection_options_gui,
|
|
0,
|
|
"gimp-iscissors-tool",
|
|
_("Scissors"),
|
|
_("Select shapes from image"),
|
|
N_("Intelligent _Scissors"),
|
|
"I",
|
|
NULL, GIMP_HELP_TOOL_ISCISSORS,
|
|
GIMP_STOCK_TOOL_ISCISSORS,
|
|
data);
|
|
}
|
|
|
|
GType
|
|
gimp_iscissors_tool_get_type (void)
|
|
{
|
|
static GType tool_type = 0;
|
|
|
|
if (! tool_type)
|
|
{
|
|
static const GTypeInfo tool_info =
|
|
{
|
|
sizeof (GimpIscissorsToolClass),
|
|
(GBaseInitFunc) NULL,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) gimp_iscissors_tool_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (GimpIscissorsTool),
|
|
0, /* n_preallocs */
|
|
(GInstanceInitFunc) gimp_iscissors_tool_init,
|
|
};
|
|
|
|
tool_type = g_type_register_static (GIMP_TYPE_SELECTION_TOOL,
|
|
"GimpIscissorsTool",
|
|
&tool_info, 0);
|
|
}
|
|
|
|
return tool_type;
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_class_init (GimpIscissorsToolClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass);
|
|
GimpDrawToolClass *draw_tool_class = GIMP_DRAW_TOOL_CLASS (klass);
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
object_class->finalize = gimp_iscissors_tool_finalize;
|
|
|
|
tool_class->control = gimp_iscissors_tool_control;
|
|
tool_class->button_press = gimp_iscissors_tool_button_press;
|
|
tool_class->button_release = gimp_iscissors_tool_button_release;
|
|
tool_class->motion = gimp_iscissors_tool_motion;
|
|
tool_class->oper_update = gimp_iscissors_tool_oper_update;
|
|
tool_class->cursor_update = gimp_iscissors_tool_cursor_update;
|
|
|
|
draw_tool_class->draw = gimp_iscissors_tool_draw;
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_init (GimpIscissorsTool *iscissors)
|
|
{
|
|
GimpTool *tool = GIMP_TOOL (iscissors);
|
|
|
|
iscissors->op = ISCISSORS_OP_NONE;
|
|
iscissors->dp_buf = NULL;
|
|
iscissors->curves = NULL;
|
|
iscissors->draw = DRAW_NOTHING;
|
|
iscissors->state = NO_ACTION;
|
|
iscissors->mask = NULL;
|
|
iscissors->gradient_map = NULL;
|
|
iscissors->livewire = NULL;
|
|
|
|
gimp_tool_control_set_scroll_lock (tool->control, TRUE);
|
|
gimp_tool_control_set_snap_to (tool->control, FALSE);
|
|
gimp_tool_control_set_preserve (tool->control, FALSE);
|
|
gimp_tool_control_set_dirty_mask (tool->control, GIMP_DIRTY_IMAGE_SIZE);
|
|
gimp_tool_control_set_tool_cursor (tool->control, GIMP_TOOL_CURSOR_ISCISSORS);
|
|
|
|
gimp_iscissors_tool_reset (iscissors);
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_finalize (GObject *object)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (object);
|
|
|
|
gimp_iscissors_tool_reset (iscissors);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_control (GimpTool *tool,
|
|
GimpToolAction action,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
IscissorsDraw draw;
|
|
|
|
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;
|
|
|
|
GIMP_TOOL_CLASS (parent_class)->control (tool, action, gdisp);
|
|
|
|
switch (action)
|
|
{
|
|
case PAUSE:
|
|
break;
|
|
|
|
case RESUME:
|
|
break;
|
|
|
|
case HALT:
|
|
gimp_iscissors_tool_reset (iscissors);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_button_press (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
GimpSelectionOptions *options;
|
|
|
|
options = GIMP_SELECTION_OPTIONS (tool->tool_info->tool_options);
|
|
|
|
iscissors->x = coords->x;
|
|
iscissors->y = coords->y;
|
|
|
|
/* If the tool was being used in another image...reset it */
|
|
|
|
if (gimp_tool_control_is_active (tool->control))
|
|
{
|
|
if (gdisp != tool->gdisp)
|
|
{
|
|
gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
|
|
gimp_iscissors_tool_reset (iscissors);
|
|
gimp_tool_control_activate (tool->control);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gimp_tool_control_activate (tool->control);
|
|
}
|
|
|
|
tool->gdisp = gdisp;
|
|
|
|
switch (iscissors->state)
|
|
{
|
|
case NO_ACTION:
|
|
iscissors->state = SEED_PLACEMENT;
|
|
iscissors->draw = DRAW_CURRENT_SEED;
|
|
|
|
if (! (state & GDK_SHIFT_MASK))
|
|
find_max_gradient (iscissors,
|
|
gdisp->gimage,
|
|
&iscissors->x,
|
|
&iscissors->y);
|
|
|
|
iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1);
|
|
iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1);
|
|
|
|
iscissors->ix = iscissors->x;
|
|
iscissors->iy = iscissors->y;
|
|
|
|
/* Initialize the selection core only on starting the tool */
|
|
gimp_draw_tool_start (GIMP_DRAW_TOOL (tool), gdisp);
|
|
break;
|
|
|
|
default:
|
|
/* Check if the mouse click occurred 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_CURVE | DRAW_ACTIVE_CURVE;
|
|
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
|
|
}
|
|
/* If the iscissors is connected, check if the click was inside */
|
|
else if (iscissors->connected && iscissors->mask &&
|
|
gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
|
|
iscissors->x,
|
|
iscissors->y))
|
|
{
|
|
/* Undraw the curve */
|
|
gimp_tool_control_halt (tool->control);
|
|
|
|
iscissors->draw = DRAW_CURVE;
|
|
gimp_draw_tool_stop (GIMP_DRAW_TOOL (tool));
|
|
|
|
gimp_channel_select_channel (gimp_image_get_mask (gdisp->gimage),
|
|
tool->tool_info->blurb,
|
|
iscissors->mask,
|
|
0, 0,
|
|
GIMP_SELECTION_TOOL (tool)->op,
|
|
options->feather,
|
|
options->feather_radius,
|
|
options->feather_radius);
|
|
|
|
gimp_iscissors_tool_reset (iscissors);
|
|
|
|
gimp_image_flush (gdisp->gimage);
|
|
}
|
|
else if (! iscissors->connected)
|
|
{
|
|
/* if we're not connected, we're adding a new point */
|
|
|
|
/* pause the tool, but undraw nothing */
|
|
iscissors->draw = DRAW_NOTHING;
|
|
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
|
|
|
|
iscissors->state = SEED_PLACEMENT;
|
|
iscissors->draw = DRAW_CURRENT_SEED;
|
|
|
|
if (options->interactive)
|
|
iscissors->draw |= DRAW_LIVEWIRE;
|
|
|
|
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iscissors_convert (GimpIscissorsTool *iscissors,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpSelectionOptions *options;
|
|
GimpScanConvert *sc;
|
|
GimpVector2 *points;
|
|
guint n_points;
|
|
GSList *list;
|
|
ICurve *icurve;
|
|
guint packed;
|
|
gint i;
|
|
gint index;
|
|
|
|
options =
|
|
GIMP_SELECTION_OPTIONS (GIMP_TOOL (iscissors)->tool_info->tool_options);
|
|
|
|
sc = gimp_scan_convert_new ();
|
|
|
|
/* 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);
|
|
|
|
n_points = icurve->points->len;
|
|
points = g_new (GimpVector2, n_points);
|
|
|
|
for (i = 0; i < n_points; i ++)
|
|
{
|
|
packed = GPOINTER_TO_INT (g_ptr_array_index (icurve->points, i));
|
|
points[i].x = packed & 0x0000ffff;
|
|
points[i].y = packed >> 16;
|
|
}
|
|
|
|
gimp_scan_convert_add_points (sc, n_points, points, FALSE);
|
|
g_free (points);
|
|
}
|
|
|
|
if (iscissors->mask)
|
|
g_object_unref (iscissors->mask);
|
|
|
|
iscissors->mask = gimp_channel_new_mask (gdisp->gimage,
|
|
gdisp->gimage->width,
|
|
gdisp->gimage->height);
|
|
gimp_scan_convert_render (sc, GIMP_DRAWABLE (iscissors->mask)->tiles,
|
|
0, 0, options->antialias);
|
|
gimp_scan_convert_free (sc);
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_button_release (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
GimpSelectionOptions *options;
|
|
ICurve *curve;
|
|
|
|
options = GIMP_SELECTION_OPTIONS (tool->tool_info->tool_options);
|
|
|
|
/* Make sure X didn't skip the button release event -- as it's known
|
|
* to do
|
|
*/
|
|
if (iscissors->state == WAITING)
|
|
return;
|
|
|
|
/* Undraw everything */
|
|
switch (iscissors->state)
|
|
{
|
|
case SEED_PLACEMENT:
|
|
iscissors->draw = DRAW_CURVE | DRAW_CURRENT_SEED;
|
|
if (options->interactive)
|
|
iscissors->draw |= DRAW_LIVEWIRE;
|
|
break;
|
|
case SEED_ADJUSTMENT:
|
|
iscissors->draw = DRAW_CURVE | DRAW_ACTIVE_CURVE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
|
|
|
|
/* First take care of the case where the user "cancels" the action */
|
|
if (! (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 (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (tool), gdisp,
|
|
iscissors->x, iscissors->y,
|
|
GIMP_HANDLE_CIRCLE,
|
|
curve->x1, curve->y1,
|
|
POINT_WIDTH, POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE))
|
|
{
|
|
iscissors->x = curve->x1;
|
|
iscissors->y = curve->y1;
|
|
iscissors->connected = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Create the new curve segment */
|
|
if (iscissors->ix != iscissors->x ||
|
|
iscissors->iy != iscissors->y)
|
|
{
|
|
curve = g_new (ICurve, 1);
|
|
|
|
curve->x1 = iscissors->ix;
|
|
curve->y1 = iscissors->iy;
|
|
iscissors->ix = curve->x2 = iscissors->x;
|
|
iscissors->iy = curve->y2 = iscissors->y;
|
|
curve->points = NULL;
|
|
iscissors->curves = g_slist_append (iscissors->curves, curve);
|
|
calculate_curve (tool, curve);
|
|
}
|
|
}
|
|
else /* this was our first point */
|
|
{
|
|
iscissors->first_point = FALSE;
|
|
}
|
|
break;
|
|
|
|
case SEED_ADJUSTMENT:
|
|
/* recalculate both curves */
|
|
if (iscissors->curve1)
|
|
{
|
|
iscissors->curve1->x1 = iscissors->nx;
|
|
iscissors->curve1->y1 = iscissors->ny;
|
|
calculate_curve (tool, iscissors->curve1);
|
|
}
|
|
if (iscissors->curve2)
|
|
{
|
|
iscissors->curve2->x2 = iscissors->nx;
|
|
iscissors->curve2->y2 = iscissors->ny;
|
|
calculate_curve (tool, iscissors->curve2);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
iscissors->state = WAITING;
|
|
|
|
/* Draw only the boundary */
|
|
iscissors->draw = DRAW_CURVE;
|
|
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
|
|
|
|
/* convert the curves into a region */
|
|
if (iscissors->connected)
|
|
iscissors_convert (iscissors, gdisp);
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_motion (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
GimpSelectionOptions *options;
|
|
|
|
options = GIMP_SELECTION_OPTIONS (tool->tool_info->tool_options);
|
|
|
|
if (iscissors->state == NO_ACTION)
|
|
return;
|
|
|
|
if (iscissors->state == SEED_PLACEMENT)
|
|
{
|
|
iscissors->draw = DRAW_CURRENT_SEED;
|
|
|
|
if (options->interactive)
|
|
iscissors->draw |= DRAW_LIVEWIRE;
|
|
}
|
|
else if (iscissors->state == SEED_ADJUSTMENT)
|
|
{
|
|
iscissors->draw = DRAW_ACTIVE_CURVE;
|
|
}
|
|
|
|
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
|
|
|
|
iscissors->x = coords->x;
|
|
iscissors->y = coords->y;
|
|
|
|
switch (iscissors->state)
|
|
{
|
|
case SEED_PLACEMENT:
|
|
/* Hold the shift key down to disable the auto-edge snap feature */
|
|
if (! (state & GDK_SHIFT_MASK))
|
|
find_max_gradient (iscissors, gdisp->gimage,
|
|
&iscissors->x, &iscissors->y);
|
|
|
|
iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1);
|
|
iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1);
|
|
|
|
if (iscissors->first_point)
|
|
{
|
|
iscissors->ix = iscissors->x;
|
|
iscissors->iy = iscissors->y;
|
|
}
|
|
break;
|
|
|
|
case SEED_ADJUSTMENT:
|
|
/* Move the current seed to the location of the cursor */
|
|
if (! (state & GDK_SHIFT_MASK))
|
|
find_max_gradient (iscissors, gdisp->gimage,
|
|
&iscissors->x, &iscissors->y);
|
|
|
|
iscissors->x = CLAMP (iscissors->x, 0, gdisp->gimage->width - 1);
|
|
iscissors->y = CLAMP (iscissors->y, 0, gdisp->gimage->height - 1);
|
|
|
|
iscissors->nx = iscissors->x;
|
|
iscissors->ny = iscissors->y;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_draw (GimpDrawTool *draw_tool)
|
|
{
|
|
GimpTool *tool = GIMP_TOOL (draw_tool);
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (draw_tool);
|
|
GimpDisplay *gdisp;
|
|
ICurve *curve;
|
|
GSList *list;
|
|
|
|
gdisp = tool->gdisp;
|
|
|
|
/* Draw the crosshairs target if we're placing a seed */
|
|
if (iscissors->draw & DRAW_CURRENT_SEED)
|
|
{
|
|
gimp_draw_tool_draw_handle (draw_tool,
|
|
GIMP_HANDLE_CROSS,
|
|
iscissors->x,
|
|
iscissors->y,
|
|
TARGET_WIDTH,
|
|
TARGET_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE);
|
|
|
|
/* Draw a line boundary */
|
|
if (! iscissors->first_point && ! (iscissors->draw & DRAW_LIVEWIRE))
|
|
{
|
|
gimp_draw_tool_draw_line (draw_tool,
|
|
iscissors->ix, iscissors->iy,
|
|
iscissors->x, iscissors->y,
|
|
FALSE);
|
|
}
|
|
}
|
|
|
|
/* Draw the livewire boundary */
|
|
if ((iscissors->draw & DRAW_LIVEWIRE) && ! iscissors->first_point)
|
|
{
|
|
/* See if the mouse has moved. If so, create a new segment... */
|
|
if (! iscissors->livewire ||
|
|
(iscissors->livewire &&
|
|
(iscissors->ix != iscissors->livewire->x1 ||
|
|
iscissors->x != iscissors->livewire->x2 ||
|
|
iscissors->iy != iscissors->livewire->y1 ||
|
|
iscissors->y != iscissors->livewire->y2)))
|
|
{
|
|
curve = g_new (ICurve, 1);
|
|
|
|
curve->x1 = iscissors->ix;
|
|
curve->y1 = iscissors->iy;
|
|
curve->x2 = iscissors->x;
|
|
curve->y2 = iscissors->y;
|
|
curve->points = NULL;
|
|
|
|
if (iscissors->livewire)
|
|
{
|
|
if (iscissors->livewire->points)
|
|
g_ptr_array_free (iscissors->livewire->points, TRUE);
|
|
|
|
g_free (iscissors->livewire);
|
|
|
|
iscissors->livewire = NULL;
|
|
}
|
|
|
|
iscissors->livewire = curve;
|
|
calculate_curve (tool, curve);
|
|
curve = NULL;
|
|
}
|
|
|
|
/* plot the curve */
|
|
iscissors_draw_curve (draw_tool, iscissors->livewire);
|
|
}
|
|
|
|
if ((iscissors->draw & DRAW_CURVE) && ! iscissors->first_point)
|
|
{
|
|
/* Draw a point at the init point coordinates */
|
|
if (! iscissors->connected)
|
|
{
|
|
gimp_draw_tool_draw_handle (draw_tool,
|
|
GIMP_HANDLE_FILLED_CIRCLE,
|
|
iscissors->ix,
|
|
iscissors->iy,
|
|
POINT_WIDTH,
|
|
POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE);
|
|
}
|
|
|
|
/* Go through the list of icurves, and render each one... */
|
|
for (list = iscissors->curves; list; list = g_slist_next (list))
|
|
{
|
|
curve = (ICurve *) list->data;
|
|
|
|
if (iscissors->draw & DRAW_ACTIVE_CURVE)
|
|
{
|
|
/* don't draw curve1 at all */
|
|
if (curve == iscissors->curve1)
|
|
continue;
|
|
}
|
|
|
|
gimp_draw_tool_draw_handle (draw_tool,
|
|
GIMP_HANDLE_FILLED_CIRCLE,
|
|
curve->x1,
|
|
curve->y1,
|
|
POINT_WIDTH,
|
|
POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE);
|
|
|
|
if (iscissors->draw & DRAW_ACTIVE_CURVE)
|
|
{
|
|
/* draw only the start handle of curve2 */
|
|
if (curve == iscissors->curve2)
|
|
continue;
|
|
}
|
|
|
|
/* plot the curve */
|
|
iscissors_draw_curve (draw_tool, curve);
|
|
}
|
|
}
|
|
|
|
if (iscissors->draw & DRAW_ACTIVE_CURVE)
|
|
{
|
|
/* plot both curves, and the control point between them */
|
|
if (iscissors->curve1)
|
|
{
|
|
gimp_draw_tool_draw_line (draw_tool,
|
|
iscissors->curve1->x2,
|
|
iscissors->curve1->y2,
|
|
iscissors->nx,
|
|
iscissors->ny,
|
|
FALSE);
|
|
}
|
|
if (iscissors->curve2)
|
|
{
|
|
gimp_draw_tool_draw_line (draw_tool,
|
|
iscissors->curve2->x1,
|
|
iscissors->curve2->y1,
|
|
iscissors->nx,
|
|
iscissors->ny,
|
|
FALSE);
|
|
}
|
|
|
|
gimp_draw_tool_draw_handle (draw_tool,
|
|
GIMP_HANDLE_FILLED_CIRCLE,
|
|
iscissors->nx,
|
|
iscissors->ny,
|
|
POINT_WIDTH,
|
|
POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iscissors_draw_curve (GimpDrawTool *draw_tool,
|
|
ICurve *curve)
|
|
{
|
|
gpointer *point;
|
|
guint len;
|
|
gint npts = 0;
|
|
guint32 coords;
|
|
guint32 coords_2;
|
|
|
|
/* Uh, this shouldn't happen, but it does. So we ignore it.
|
|
* Quality code, baby.
|
|
*/
|
|
if (! curve->points)
|
|
return;
|
|
|
|
point = curve->points->pdata + 1;
|
|
len = curve->points->len - 1;
|
|
|
|
while (len--)
|
|
{
|
|
coords = GPOINTER_TO_INT (*point);
|
|
coords_2 = GPOINTER_TO_INT (*(point - 1));
|
|
point++;
|
|
|
|
if (npts < MAX_POINTS)
|
|
{
|
|
gimp_draw_tool_draw_line (draw_tool,
|
|
(coords & 0x0000ffff),
|
|
(coords >> 16),
|
|
(coords_2 & 0x0000ffff),
|
|
(coords_2 >> 16),
|
|
FALSE);
|
|
npts++;
|
|
}
|
|
else
|
|
{
|
|
g_warning ("too many points in ICurve segment!");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_oper_update (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
|
|
GIMP_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, gdisp);
|
|
|
|
if (mouse_over_vertex (iscissors, coords->x, coords->y))
|
|
{
|
|
iscissors->op = ISCISSORS_OP_MOVE_POINT;
|
|
}
|
|
else if (mouse_over_curve (iscissors, coords->x, coords->y))
|
|
{
|
|
iscissors->op = ISCISSORS_OP_ADD_POINT;
|
|
}
|
|
else if (iscissors->connected && iscissors->mask)
|
|
{
|
|
if (gimp_pickable_get_opacity_at (GIMP_PICKABLE (iscissors->mask),
|
|
coords->x, coords->y))
|
|
{
|
|
iscissors->op = ISCISSORS_OP_SELECT;
|
|
}
|
|
else
|
|
{
|
|
iscissors->op = ISCISSORS_OP_IMPOSSIBLE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (iscissors->state)
|
|
{
|
|
case WAITING:
|
|
iscissors->op = ISCISSORS_OP_ADD_POINT;
|
|
break;
|
|
|
|
case SEED_PLACEMENT:
|
|
case SEED_ADJUSTMENT:
|
|
default:
|
|
iscissors->op = ISCISSORS_OP_NONE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_iscissors_tool_cursor_update (GimpTool *tool,
|
|
GimpCoords *coords,
|
|
GdkModifierType state,
|
|
GimpDisplay *gdisp)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
GimpCursorType cursor = GIMP_CURSOR_MOUSE;
|
|
GimpCursorModifier modifier = GIMP_CURSOR_MODIFIER_NONE;
|
|
|
|
switch (iscissors->op)
|
|
{
|
|
case ISCISSORS_OP_SELECT:
|
|
GIMP_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, gdisp);
|
|
return;
|
|
case ISCISSORS_OP_MOVE_POINT:
|
|
modifier = GIMP_CURSOR_MODIFIER_MOVE;
|
|
break;
|
|
case ISCISSORS_OP_ADD_POINT:
|
|
modifier = GIMP_CURSOR_MODIFIER_PLUS;
|
|
break;
|
|
case ISCISSORS_OP_IMPOSSIBLE:
|
|
cursor = GIMP_CURSOR_BAD;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
gimp_tool_set_cursor (tool, gdisp,
|
|
cursor, GIMP_TOOL_CURSOR_ISCISSORS, modifier);
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_iscissors_tool_reset (GimpIscissorsTool *iscissors)
|
|
{
|
|
/* Free and reset the curve list */
|
|
if (iscissors->curves)
|
|
{
|
|
iscissors_free_icurves (iscissors->curves);
|
|
g_slist_free (iscissors->curves);
|
|
iscissors->curves = NULL;
|
|
}
|
|
|
|
/* free mask */
|
|
if (iscissors->mask)
|
|
{
|
|
g_object_unref (iscissors->mask);
|
|
iscissors->mask = NULL;
|
|
}
|
|
|
|
/* free the gradient map */
|
|
if (iscissors->gradient_map)
|
|
{
|
|
/* release any tile we were using */
|
|
if (cur_tile)
|
|
{
|
|
tile_release (cur_tile, FALSE);
|
|
cur_tile = NULL;
|
|
}
|
|
|
|
tile_manager_unref (iscissors->gradient_map);
|
|
iscissors->gradient_map = NULL;
|
|
}
|
|
|
|
iscissors->curve1 = NULL;
|
|
iscissors->curve2 = NULL;
|
|
iscissors->first_point = TRUE;
|
|
iscissors->connected = FALSE;
|
|
iscissors->draw = DRAW_NOTHING;
|
|
iscissors->state = NO_ACTION;
|
|
|
|
/* Reset the dp buffers */
|
|
iscissors_free_buffers (iscissors);
|
|
|
|
/* If they haven't already been initialized, precalculate the diagonal
|
|
* weight and direction value arrays
|
|
*/
|
|
if (!initialized)
|
|
{
|
|
precalculate_arrays ();
|
|
initialized = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iscissors_free_icurves (GSList *list)
|
|
{
|
|
ICurve * curve;
|
|
|
|
while (list)
|
|
{
|
|
curve = (ICurve *) list->data;
|
|
if (curve->points)
|
|
g_ptr_array_free (curve->points, TRUE);
|
|
|
|
g_free (curve);
|
|
list = g_slist_next (list);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iscissors_free_buffers (GimpIscissorsTool *iscissors)
|
|
{
|
|
if (iscissors->dp_buf)
|
|
temp_buf_free (iscissors->dp_buf);
|
|
|
|
iscissors->dp_buf = NULL;
|
|
}
|
|
|
|
|
|
/* XXX need some scan-conversion routines from somewhere. maybe. ? */
|
|
|
|
static gint
|
|
mouse_over_vertex (GimpIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GSList *list;
|
|
ICurve *curve;
|
|
gint curves_found = 0;
|
|
|
|
/* traverse through the list, returning non-zero if the current cursor
|
|
* position is on an existing curve vertex. Set the curve1 and curve2
|
|
* variables to the two curves containing the vertex in question
|
|
*/
|
|
|
|
iscissors->curve1 = iscissors->curve2 = NULL;
|
|
|
|
list = iscissors->curves;
|
|
|
|
while (list && curves_found < 2)
|
|
{
|
|
curve = (ICurve *) list->data;
|
|
|
|
if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
|
|
GIMP_TOOL (iscissors)->gdisp,
|
|
x, y,
|
|
GIMP_HANDLE_CIRCLE,
|
|
curve->x1, curve->y1,
|
|
POINT_WIDTH, POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE))
|
|
{
|
|
iscissors->curve1 = curve;
|
|
|
|
if (curves_found++)
|
|
return curves_found;
|
|
}
|
|
else if (gimp_draw_tool_on_handle (GIMP_DRAW_TOOL (iscissors),
|
|
GIMP_TOOL (iscissors)->gdisp,
|
|
x, y,
|
|
GIMP_HANDLE_CIRCLE,
|
|
curve->x2, curve->y2,
|
|
POINT_WIDTH, POINT_WIDTH,
|
|
GTK_ANCHOR_CENTER,
|
|
FALSE))
|
|
{
|
|
iscissors->curve2 = curve;
|
|
|
|
if (curves_found++)
|
|
return curves_found;
|
|
}
|
|
|
|
list = g_slist_next (list);
|
|
}
|
|
|
|
return curves_found;
|
|
}
|
|
|
|
static gboolean
|
|
clicked_on_vertex (GimpTool *tool)
|
|
{
|
|
GimpIscissorsTool *iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
gint curves_found = 0;
|
|
|
|
curves_found = mouse_over_vertex (iscissors, iscissors->x, iscissors->y);
|
|
|
|
if (curves_found > 1)
|
|
{
|
|
/* undraw the curve */
|
|
iscissors->draw = DRAW_CURVE;
|
|
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* if only one curve was found, the curves are unconnected, and
|
|
* the user only wants to move either the first or last point
|
|
* disallow this for now.
|
|
*/
|
|
if (curves_found == 1)
|
|
return FALSE;
|
|
|
|
return clicked_on_curve (tool);
|
|
}
|
|
|
|
|
|
static GSList *
|
|
mouse_over_curve (GimpIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GSList *list;
|
|
gpointer *pt;
|
|
gint len;
|
|
ICurve *curve;
|
|
guint32 coords;
|
|
gint tx, ty;
|
|
|
|
/* traverse through the list, returning the curve segment's list element
|
|
* if the current cursor position is on a curve...
|
|
*/
|
|
|
|
for (list = iscissors->curves; list; list = g_slist_next (list))
|
|
{
|
|
curve = (ICurve *) list->data;
|
|
|
|
pt = curve->points->pdata;
|
|
len = curve->points->len;
|
|
while (len--)
|
|
{
|
|
coords = GPOINTER_TO_INT (*pt);
|
|
pt++;
|
|
tx = coords & 0x0000ffff;
|
|
ty = coords >> 16;
|
|
|
|
/* Is the specified point close enough to the curve? */
|
|
if (gimp_draw_tool_in_radius (GIMP_DRAW_TOOL (iscissors),
|
|
GIMP_TOOL (iscissors)->gdisp,
|
|
tx, ty,
|
|
x, y,
|
|
POINT_HALFWIDTH))
|
|
{
|
|
return list;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
clicked_on_curve (GimpTool *tool)
|
|
{
|
|
GimpIscissorsTool *iscissors;
|
|
GimpSelectionOptions *options;
|
|
GSList *list, *new_link;
|
|
ICurve *curve, *new_curve;
|
|
|
|
iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
options = GIMP_SELECTION_OPTIONS (tool->tool_info->tool_options);
|
|
|
|
/* traverse through the list, getting back the curve segment's list
|
|
* element if the current cursor position is on a curve...
|
|
* If this occurs, replace the curve with two new curves,
|
|
* separated by a new vertex.
|
|
*/
|
|
list = mouse_over_curve (iscissors, iscissors->x, iscissors->y);
|
|
|
|
if (list)
|
|
{
|
|
curve = (ICurve *) list->data;
|
|
|
|
/* undraw the curve */
|
|
iscissors->draw = DRAW_CURVE;
|
|
gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));
|
|
|
|
/* Create the new curve */
|
|
new_curve = g_new (ICurve, 1);
|
|
|
|
new_curve->x2 = curve->x2;
|
|
new_curve->y2 = curve->y2;
|
|
new_curve->x1 = curve->x2 = iscissors->x;
|
|
new_curve->y1 = curve->y2 = iscissors->y;
|
|
new_curve->points = NULL;
|
|
|
|
/* Create the new link and supply the new curve as data */
|
|
new_link = g_slist_alloc ();
|
|
new_link->data = 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;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
precalculate_arrays (void)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
/* The diagonal weight array */
|
|
diagonal_weight[i] = (int) (i * G_SQRT2);
|
|
|
|
/* The direction value array */
|
|
direction_value[i][0] = (127 - abs (127 - i)) * 2;
|
|
direction_value[i][1] = abs (127 - i) * 2;
|
|
direction_value[i][2] = abs (191 - i) * 2;
|
|
direction_value[i][3] = abs (63 - i) * 2;
|
|
}
|
|
|
|
/* set the 256th index of the direction_values to the hightest cost */
|
|
direction_value[255][0] = 255;
|
|
direction_value[255][1] = 255;
|
|
direction_value[255][2] = 255;
|
|
direction_value[255][3] = 255;
|
|
}
|
|
|
|
|
|
static void
|
|
calculate_curve (GimpTool *tool,
|
|
ICurve *curve)
|
|
{
|
|
GimpIscissorsTool *iscissors;
|
|
GimpDisplay *gdisp;
|
|
gint x, y, dir;
|
|
gint xs, ys, xe, ye;
|
|
gint x1, y1, x2, y2;
|
|
gint width, height;
|
|
gint ewidth, eheight;
|
|
|
|
/* Calculate the lowest cost path from one vertex to the next as specified
|
|
* by the parameter "curve".
|
|
* Here are the steps:
|
|
* 1) Calculate the appropriate working area for this operation
|
|
* 2) Allocate a temp buf for the dynamic programming array
|
|
* 3) Run the dynamic programming algorithm to find the optimal path
|
|
* 4) Translate the optimal path into pixels in the icurve data
|
|
* structure.
|
|
*/
|
|
|
|
iscissors = GIMP_ISCISSORS_TOOL (tool);
|
|
|
|
gdisp = tool->gdisp;
|
|
|
|
/* Get the bounding box */
|
|
xs = CLAMP (curve->x1, 0, gdisp->gimage->width - 1);
|
|
ys = CLAMP (curve->y1, 0, gdisp->gimage->height - 1);
|
|
xe = CLAMP (curve->x2, 0, gdisp->gimage->width - 1);
|
|
ye = CLAMP (curve->y2, 0, gdisp->gimage->height - 1);
|
|
x1 = MIN (xs, xe);
|
|
y1 = MIN (ys, ye);
|
|
x2 = MAX (xs, xe) + 1; /* +1 because if xe = 199 & xs = 0, x2 - x1, width = 200 */
|
|
y2 = MAX (ys, ye) + 1;
|
|
|
|
/* expand the boundaries past the ending points by
|
|
* some percentage of width and height. This serves the following purpose:
|
|
* It gives the algorithm more area to search so better solutions
|
|
* are found. This is particularly helpful in finding "bumps" which
|
|
* fall outside the bounding box represented by the start and end
|
|
* coordinates of the "curve".
|
|
*/
|
|
ewidth = (x2 - x1) * EXTEND_BY + FIXED;
|
|
eheight = (y2 - y1) * EXTEND_BY + FIXED;
|
|
|
|
if (xe >= xs)
|
|
x2 += CLAMP (ewidth, 0, gdisp->gimage->width - x2);
|
|
else
|
|
x1 -= CLAMP (ewidth, 0, x1);
|
|
if (ye >= ys)
|
|
y2 += CLAMP (eheight, 0, gdisp->gimage->height - y2);
|
|
else
|
|
y1 -= CLAMP (eheight, 0, y1);
|
|
|
|
/* blow away any previous points list we might have */
|
|
if (curve->points)
|
|
{
|
|
g_ptr_array_free (curve->points, TRUE);
|
|
curve->points = NULL;
|
|
}
|
|
|
|
/* If the bounding box has width and height... */
|
|
if ((x2 - x1) && (y2 - y1))
|
|
{
|
|
width = (x2 - x1);
|
|
height = (y2 - y1);
|
|
|
|
/* Initialise the gradient map tile manager for this image if we
|
|
* don't already have one. */
|
|
if (!iscissors->gradient_map)
|
|
iscissors->gradient_map = gradient_map_new (gdisp->gimage);
|
|
|
|
/* allocate the dynamic programming array */
|
|
iscissors->dp_buf =
|
|
temp_buf_resize (iscissors->dp_buf, 4, x1, y1, width, height);
|
|
|
|
/* find the optimal path of pixels from (x1, y1) to (x2, y2) */
|
|
find_optimal_path (iscissors->gradient_map, iscissors->dp_buf,
|
|
x1, y1, x2, y2, xs, ys);
|
|
|
|
/* get a list of the pixels in the optimal path */
|
|
curve->points = plot_pixels (iscissors, iscissors->dp_buf,
|
|
x1, y1, xs, ys, xe, ye);
|
|
}
|
|
/* If the bounding box has no width */
|
|
else if ((x2 - x1) == 0)
|
|
{
|
|
/* plot a vertical line */
|
|
y = ys;
|
|
dir = (ys > ye) ? -1 : 1;
|
|
curve->points = g_ptr_array_new ();
|
|
while (y != ye)
|
|
{
|
|
g_ptr_array_add (curve->points, GINT_TO_POINTER ((y << 16) + xs));
|
|
y += dir;
|
|
}
|
|
}
|
|
/* If the bounding box has no height */
|
|
else if ((y2 - y1) == 0)
|
|
{
|
|
/* plot a horizontal line */
|
|
x = xs;
|
|
dir = (xs > xe) ? -1 : 1;
|
|
curve->points = g_ptr_array_new ();
|
|
while (x != xe)
|
|
{
|
|
g_ptr_array_add (curve->points, GINT_TO_POINTER ((ys << 16) + x));
|
|
x += dir;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* badly need to get a replacement - this is _way_ too expensive */
|
|
static gboolean
|
|
gradient_map_value (TileManager *map,
|
|
gint x,
|
|
gint y,
|
|
guint8 *grad,
|
|
guint8 *dir)
|
|
{
|
|
static gint cur_tilex;
|
|
static gint cur_tiley;
|
|
guint8 *p;
|
|
|
|
if (!cur_tile ||
|
|
x / TILE_WIDTH != cur_tilex ||
|
|
y / TILE_HEIGHT != cur_tiley)
|
|
{
|
|
if (cur_tile)
|
|
tile_release (cur_tile, FALSE);
|
|
cur_tile = tile_manager_get_tile (map, x, y, TRUE, FALSE);
|
|
if (!cur_tile)
|
|
return FALSE;
|
|
cur_tilex = x / TILE_WIDTH;
|
|
cur_tiley = y / TILE_HEIGHT;
|
|
}
|
|
|
|
p = tile_data_pointer (cur_tile, x % TILE_WIDTH, y % TILE_HEIGHT);
|
|
*grad = p[0];
|
|
*dir = p[1];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
calculate_link (TileManager *gradient_map,
|
|
gint x,
|
|
gint y,
|
|
guint32 pixel,
|
|
gint link)
|
|
{
|
|
gint value = 0;
|
|
guint8 grad1, dir1, grad2, dir2;
|
|
|
|
if (!gradient_map_value (gradient_map, x, y, &grad1, &dir1))
|
|
{
|
|
grad1 = 0;
|
|
dir1 = 255;
|
|
}
|
|
|
|
/* Convert the gradient into a cost: large gradients are good, and
|
|
* so have low cost. */
|
|
grad1 = 255 - grad1;
|
|
|
|
/* calculate the contribution of the gradient magnitude */
|
|
if (link > 1)
|
|
value += diagonal_weight[grad1] * OMEGA_G;
|
|
else
|
|
value += grad1 * OMEGA_G;
|
|
|
|
/* calculate the contribution of the gradient direction */
|
|
x += (gint8)(pixel & 0xff);
|
|
y += (gint8)((pixel & 0xff00) >> 8);
|
|
if (!gradient_map_value (gradient_map, x, y, &grad2, &dir2))
|
|
{
|
|
grad2 = 0;
|
|
dir2 = 255;
|
|
}
|
|
value += (direction_value[dir1][link] + direction_value[dir2][link]) *
|
|
OMEGA_D;
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
static GPtrArray *
|
|
plot_pixels (GimpIscissorsTool *iscissors,
|
|
TempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint xs,
|
|
gint ys,
|
|
gint xe,
|
|
gint ye)
|
|
{
|
|
gint x, y;
|
|
guint32 coords;
|
|
gint link;
|
|
gint width;
|
|
guint *data;
|
|
GPtrArray *list;
|
|
|
|
width = dp_buf->width;
|
|
|
|
/* Start the data pointer at the correct location */
|
|
data = (guint *) temp_buf_data (dp_buf) + (ye - y1) * width + (xe - x1);
|
|
|
|
x = xe;
|
|
y = ye;
|
|
|
|
list = g_ptr_array_new ();
|
|
|
|
while (1)
|
|
{
|
|
coords = (y << 16) + x;
|
|
g_ptr_array_add (list, GINT_TO_POINTER (coords));
|
|
|
|
link = PIXEL_DIR (*data);
|
|
if (link == SEED_POINT)
|
|
return list;
|
|
|
|
x += move[link][0];
|
|
y += move[link][1];
|
|
data += move[link][1] * width + move[link][0];
|
|
}
|
|
|
|
/* won't get here */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#define PACK(x, y) ((((y) & 0xff) << 8) | ((x) & 0xff))
|
|
#define OFFSET(pixel) ((gint8)((pixel) & 0xff) + \
|
|
((gint8)(((pixel) & 0xff00) >> 8)) * dp_buf->width)
|
|
|
|
|
|
static void
|
|
find_optimal_path (TileManager *gradient_map,
|
|
TempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gint xs,
|
|
gint ys)
|
|
{
|
|
gint i, j, k;
|
|
gint x, y;
|
|
gint link;
|
|
gint linkdir;
|
|
gint dirx, diry;
|
|
gint min_cost;
|
|
gint new_cost;
|
|
gint offset;
|
|
gint cum_cost[8];
|
|
gint link_cost[8];
|
|
gint pixel_cost[8];
|
|
guint32 pixel[8];
|
|
guint32 *data;
|
|
guint32 *d;
|
|
|
|
/* initialize the dynamic programming buffer */
|
|
data = (guint32 *) temp_buf_data_clear (dp_buf);
|
|
|
|
/* what directions are we filling the array in according to? */
|
|
dirx = (xs - x1 == 0) ? 1 : -1;
|
|
diry = (ys - y1 == 0) ? 1 : -1;
|
|
linkdir = (dirx * diry);
|
|
|
|
y = ys;
|
|
|
|
for (i = 0; i < dp_buf->height; i++)
|
|
{
|
|
x = xs;
|
|
|
|
d = data + (y-y1) * dp_buf->width + (x-x1);
|
|
|
|
for (j = 0; j < dp_buf->width; j++)
|
|
{
|
|
min_cost = G_MAXINT;
|
|
|
|
/* pixel[] array encodes how to get to a neigbour, if possible.
|
|
* 0 means no connection (eg edge).
|
|
* Rest packed as bottom two bytes: y offset then x offset.
|
|
* Initially, we assume we can't get anywhere. */
|
|
for (k = 0; k < 8; k++)
|
|
pixel[k] = 0;
|
|
|
|
/* Find the valid neighboring pixels */
|
|
/* the previous pixel */
|
|
if (j)
|
|
pixel[((dirx == 1) ? 4 : 0)] = PACK (-dirx, 0);
|
|
|
|
/* the previous row of pixels */
|
|
if (i)
|
|
{
|
|
pixel[((diry == 1) ? 5 : 1)] = PACK (0, -diry);
|
|
|
|
link = (linkdir == 1) ? 3 : 2;
|
|
if (j)
|
|
pixel[((diry == 1) ? (link + 4) : link)] = PACK(-dirx, -diry);
|
|
|
|
link = (linkdir == 1) ? 2 : 3;
|
|
if (j != dp_buf->width - 1)
|
|
pixel[((diry == 1) ? (link + 4) : link)] = PACK (dirx, -diry);
|
|
}
|
|
|
|
/* find the minimum cost of going through each neighbor to reach the
|
|
* seed point...
|
|
*/
|
|
link = -1;
|
|
for (k = 0; k < 8; k ++)
|
|
if (pixel[k])
|
|
{
|
|
link_cost[k] = calculate_link (gradient_map,
|
|
xs + j*dirx, ys + i*diry,
|
|
pixel [k],
|
|
((k > 3) ? k - 4 : k));
|
|
offset = OFFSET (pixel [k]);
|
|
pixel_cost[k] = PIXEL_COST (d[offset]);
|
|
cum_cost[k] = pixel_cost[k] + link_cost[k];
|
|
if (cum_cost[k] < min_cost)
|
|
{
|
|
min_cost = cum_cost[k];
|
|
link = k;
|
|
}
|
|
}
|
|
|
|
/* If anything can be done... */
|
|
if (link >= 0)
|
|
{
|
|
/* set the cumulative cost of this pixel and the new direction */
|
|
*d = (cum_cost[link] << 8) + link;
|
|
|
|
/* possibly change the links from the other pixels to this pixel...
|
|
* these changes occur if a neighboring pixel will receive a lower
|
|
* cumulative cost by going through this pixel.
|
|
*/
|
|
for (k = 0; k < 8; k ++)
|
|
if (pixel[k] && k != link)
|
|
{
|
|
/* if the cumulative cost at the neighbor is greater than
|
|
* the cost through the link to the current pixel, change the
|
|
* neighbor's link to point to the current pixel.
|
|
*/
|
|
new_cost = link_cost[k] + cum_cost[link];
|
|
if (pixel_cost[k] > new_cost)
|
|
{
|
|
/* reverse the link direction /-----------------------\ */
|
|
offset = OFFSET (pixel[k]);
|
|
d[offset] = (new_cost << 8) + ((k > 3) ? k - 4 : k + 4);
|
|
}
|
|
}
|
|
}
|
|
/* Set the seed point */
|
|
else if (!i && !j)
|
|
*d = SEED_POINT;
|
|
|
|
/* increment the data pointer and the x counter */
|
|
d += dirx;
|
|
x += dirx;
|
|
}
|
|
|
|
/* increment the y counter */
|
|
y += diry;
|
|
}
|
|
}
|
|
|
|
|
|
/* Called to fill in a newly referenced tile in the gradient map */
|
|
static void
|
|
gradmap_tile_validate (TileManager *tm,
|
|
Tile *tile)
|
|
{
|
|
static gboolean first_gradient = TRUE;
|
|
|
|
gint x, y;
|
|
gint dw, dh;
|
|
gint sw, sh;
|
|
gint i, j;
|
|
gint b;
|
|
gfloat gradient;
|
|
guint8 *gradmap;
|
|
guint8 *tiledata;
|
|
guint8 *datah, *datav;
|
|
gint8 hmax, vmax;
|
|
Tile *srctile;
|
|
PixelRegion srcPR, destPR;
|
|
GimpImage *gimage;
|
|
|
|
gimage = (GimpImage *) tile_manager_get_user_data (tm);
|
|
|
|
if (first_gradient)
|
|
{
|
|
gint radius = GRADIENT_SEARCH >> 1;
|
|
|
|
/* compute the distance weights */
|
|
for (i = 0; i < GRADIENT_SEARCH; i++)
|
|
for (j = 0; j < GRADIENT_SEARCH; j++)
|
|
distance_weights[i * GRADIENT_SEARCH + j] =
|
|
1.0 / (1 + sqrt (SQR(i - radius) + SQR(j - radius)));
|
|
|
|
first_gradient = FALSE;
|
|
}
|
|
|
|
tile_manager_get_tile_coordinates (tm, tile, &x, &y);
|
|
dw = tile_ewidth (tile);
|
|
dh = tile_eheight (tile);
|
|
|
|
/* get corresponding tile in the gimage */
|
|
srctile = tile_manager_get_tile (gimp_projection_get_tiles (gimage->projection),
|
|
x, y, TRUE, FALSE);
|
|
if (!srctile)
|
|
return;
|
|
|
|
sw = tile_ewidth (srctile);
|
|
sh = tile_eheight (srctile);
|
|
|
|
pixel_region_init_data (&srcPR, tile_data_pointer (srctile, 0, 0),
|
|
gimp_projection_get_bytes (gimage->projection),
|
|
gimp_projection_get_bytes (gimage->projection) *
|
|
MIN (dw, sw),
|
|
0, 0, MIN (dw, sw), MIN (dh, sh));
|
|
|
|
/* 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, GIMP_NORMAL_CONVOL, FALSE);
|
|
|
|
/* 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, GIMP_NEGATIVE_CONVOL,
|
|
FALSE);
|
|
|
|
/* Get the vertical derivative */
|
|
destPR.data = maxgrad_conv2;
|
|
convolve_region (&srcPR, &destPR, vert_deriv, 3, 1, GIMP_NEGATIVE_CONVOL,
|
|
FALSE);
|
|
|
|
/* calculate overall gradient */
|
|
tiledata = tile_data_pointer (tile, 0, 0);
|
|
for (i = 0; i < srcPR.h; i++)
|
|
{
|
|
datah = maxgrad_conv1 + srcPR.rowstride*i;
|
|
datav = maxgrad_conv2 + srcPR.rowstride*i;
|
|
gradmap = tiledata + tile_ewidth (tile) * COST_WIDTH * i;
|
|
|
|
for (j = 0; j < srcPR.w; j++)
|
|
{
|
|
hmax = datah[0] - 128;
|
|
vmax = datav[0] - 128;
|
|
for (b = 1; b < srcPR.bytes; b++)
|
|
{
|
|
if (abs (datah[b] - 128) > abs (hmax)) hmax = datah[b] - 128;
|
|
if (abs (datav[b] - 128) > abs (vmax)) vmax = datav[b] - 128;
|
|
}
|
|
|
|
if (i == 0 || j == 0 || i == srcPR.h-1 || j == srcPR.w-1)
|
|
{
|
|
gradmap[j*COST_WIDTH] = 0;
|
|
gradmap[j*COST_WIDTH + 1] = 255;
|
|
goto contin;
|
|
}
|
|
|
|
/* 1 byte absolute magitude first */
|
|
gradient = sqrt(SQR(hmax) + SQR(vmax));
|
|
gradmap[j*COST_WIDTH] = gradient * 255 / MAX_GRADIENT;
|
|
|
|
/* then 1 byte direction */
|
|
if (gradient > MIN_GRADIENT)
|
|
{
|
|
gfloat direction;
|
|
|
|
if (!hmax)
|
|
direction = (vmax > 0) ? G_PI_2 : -G_PI_2;
|
|
else
|
|
direction = atan ((double) vmax / (double) hmax);
|
|
|
|
/* Scale the direction from between 0 and 254,
|
|
* corresponding to -PI/2, PI/2 255 is reserved for
|
|
* directionless pixels */
|
|
gradmap[j*COST_WIDTH + 1] =
|
|
(guint8) (254 * (direction + G_PI_2) / G_PI);
|
|
}
|
|
else
|
|
gradmap[j*COST_WIDTH + 1] = 255; /* reserved for weak gradient */
|
|
|
|
contin:
|
|
datah += srcPR.bytes;
|
|
datav += srcPR.bytes;
|
|
}
|
|
}
|
|
|
|
tile_release (srctile, FALSE);
|
|
}
|
|
|
|
static TileManager *
|
|
gradient_map_new (GimpImage *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 (GimpIscissorsTool *iscissors,
|
|
GimpImage *gimage,
|
|
gint *x,
|
|
gint *y)
|
|
{
|
|
PixelRegion srcPR;
|
|
gint radius;
|
|
gint i, j;
|
|
gint endx, endy;
|
|
gint sx, sy, cx, cy;
|
|
gint x1, y1, x2, y2;
|
|
void *pr;
|
|
guint8 *gradient;
|
|
gfloat g, max_gradient;
|
|
|
|
/* Initialise the gradient map tile manager for this image if we
|
|
* don't already have one. */
|
|
if (!iscissors->gradient_map)
|
|
iscissors->gradient_map = gradient_map_new (gimage);
|
|
|
|
radius = GRADIENT_SEARCH >> 1;
|
|
|
|
/* calculate the extent of the search */
|
|
cx = CLAMP (*x, 0, gimage->width);
|
|
cy = CLAMP (*y, 0, gimage->height);
|
|
sx = cx - radius;
|
|
sy = cy - radius;
|
|
x1 = CLAMP (cx - radius, 0, gimage->width);
|
|
y1 = CLAMP (cy - radius, 0, gimage->height);
|
|
x2 = CLAMP (cx + radius, 0, gimage->width);
|
|
y2 = CLAMP (cy + radius, 0, gimage->height);
|
|
/* calculate the factor to multiply the distance from the cursor by */
|
|
|
|
max_gradient = 0;
|
|
*x = cx;
|
|
*y = cy;
|
|
|
|
/* Find the point of max gradient */
|
|
pixel_region_init (&srcPR, iscissors->gradient_map,
|
|
x1, y1, x2 - x1, y2 - y1, FALSE);
|
|
|
|
/* this iterates over 1, 2 or 4 tiles only */
|
|
for (pr = pixel_regions_register (1, &srcPR);
|
|
pr != NULL;
|
|
pr = pixel_regions_process (pr))
|
|
{
|
|
endx = srcPR.x + srcPR.w;
|
|
endy = srcPR.y + srcPR.h;
|
|
for (i = srcPR.y; i < endy; i++)
|
|
{
|
|
gradient = srcPR.data + srcPR.rowstride * (i - srcPR.y);
|
|
for (j = srcPR.x; j < endx; j++)
|
|
{
|
|
g = *gradient;
|
|
gradient += COST_WIDTH;
|
|
g *= distance_weights [(i-y1) * GRADIENT_SEARCH + (j-x1)];
|
|
if (g > max_gradient)
|
|
{
|
|
max_gradient = g;
|
|
*x = j;
|
|
*y = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|