2193 lines
70 KiB
C
2193 lines
70 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* 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
|
|
* originally done by Spencer and Peter, and worked fine in the 0.54
|
|
* (motif only) release of PIKA. 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 <gegl.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "libpikamath/pikamath.h"
|
|
#include "libpikawidgets/pikawidgets.h"
|
|
|
|
#include "tools-types.h"
|
|
|
|
#include "gegl/pika-gegl-utils.h"
|
|
|
|
#include "core/pikachannel.h"
|
|
#include "core/pikachannel-select.h"
|
|
#include "core/pikaimage.h"
|
|
#include "core/pikapickable.h"
|
|
#include "core/pikascanconvert.h"
|
|
#include "core/pikatempbuf.h"
|
|
#include "core/pikatoolinfo.h"
|
|
|
|
#include "widgets/pikahelp-ids.h"
|
|
#include "widgets/pikawidgets-utils.h"
|
|
|
|
#include "display/pikacanvasitem.h"
|
|
#include "display/pikadisplay.h"
|
|
|
|
#include "pikaiscissorsoptions.h"
|
|
#include "pikaiscissorstool.h"
|
|
#include "pikatilehandleriscissors.h"
|
|
#include "pikatoolcontrol.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
/* defines */
|
|
#define GRADIENT_SEARCH 32 /* how far to look when snapping to an edge */
|
|
#define EXTEND_BY 0.2 /* proportion to expand cost map by */
|
|
#define FIXED 5 /* additional fixed size to expand cost map */
|
|
|
|
#define COST_WIDTH 2 /* number of bytes for each pixel in cost map */
|
|
|
|
/* 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 _ISegment
|
|
{
|
|
gint x1, y1;
|
|
gint x2, y2;
|
|
GPtrArray *points;
|
|
};
|
|
|
|
struct _ICurve
|
|
{
|
|
GQueue *segments;
|
|
gboolean first_point;
|
|
gboolean closed;
|
|
};
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_iscissors_tool_finalize (GObject *object);
|
|
|
|
static void pika_iscissors_tool_control (PikaTool *tool,
|
|
PikaToolAction action,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_button_press (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_button_release (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_motion (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_oper_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_cursor_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display);
|
|
static gboolean pika_iscissors_tool_key_press (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display);
|
|
static const gchar * pika_iscissors_tool_can_undo (PikaTool *tool,
|
|
PikaDisplay *display);
|
|
static const gchar * pika_iscissors_tool_can_redo (PikaTool *tool,
|
|
PikaDisplay *display);
|
|
static gboolean pika_iscissors_tool_undo (PikaTool *tool,
|
|
PikaDisplay *display);
|
|
static gboolean pika_iscissors_tool_redo (PikaTool *tool,
|
|
PikaDisplay *display);
|
|
|
|
static void pika_iscissors_tool_draw (PikaDrawTool *draw_tool);
|
|
|
|
static void pika_iscissors_tool_push_undo (PikaIscissorsTool *iscissors);
|
|
static void pika_iscissors_tool_pop_undo (PikaIscissorsTool *iscissors);
|
|
static void pika_iscissors_tool_free_redo (PikaIscissorsTool *iscissors);
|
|
|
|
static void pika_iscissors_tool_halt (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display);
|
|
static void pika_iscissors_tool_commit (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display);
|
|
|
|
static void iscissors_convert (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display);
|
|
static GeglBuffer * gradient_map_new (PikaPickable *pickable);
|
|
|
|
static void find_optimal_path (GeglBuffer *gradient_map,
|
|
PikaTempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gint xs,
|
|
gint ys);
|
|
static void find_max_gradient (PikaIscissorsTool *iscissors,
|
|
PikaPickable *pickable,
|
|
gint *x,
|
|
gint *y);
|
|
static void calculate_segment (PikaIscissorsTool *iscissors,
|
|
ISegment *segment);
|
|
static PikaCanvasItem * iscissors_draw_segment (PikaDrawTool *draw_tool,
|
|
ISegment *segment);
|
|
|
|
static gint mouse_over_vertex (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
static gboolean clicked_on_vertex (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
static GList * mouse_over_segment (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
static gboolean clicked_on_segment (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y);
|
|
|
|
static GPtrArray * plot_pixels (PikaTempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint xs,
|
|
gint ys,
|
|
gint xe,
|
|
gint ye);
|
|
|
|
static ISegment * isegment_new (gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2);
|
|
static ISegment * isegment_copy (ISegment *segment);
|
|
static void isegment_free (ISegment *segment);
|
|
|
|
static ICurve * icurve_new (void);
|
|
static ICurve * icurve_copy (ICurve *curve);
|
|
static void icurve_clear (ICurve *curve);
|
|
static void icurve_free (ICurve *curve);
|
|
|
|
static ISegment * icurve_append_segment (ICurve *curve,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2);
|
|
static ISegment * icurve_insert_segment (ICurve *curve,
|
|
GList *sibling,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2);
|
|
static void icurve_delete_segment (ICurve *curve,
|
|
ISegment *segment);
|
|
|
|
static void icurve_close (ICurve *curve);
|
|
|
|
static PikaScanConvert *
|
|
icurve_create_scan_convert (ICurve *curve);
|
|
|
|
|
|
/* static variables */
|
|
|
|
/* where to move on a given link direction */
|
|
static const 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 |
|
|
* `---+---+---'
|
|
*/
|
|
|
|
static gfloat distance_weights[GRADIENT_SEARCH * GRADIENT_SEARCH];
|
|
|
|
static gint diagonal_weight[256];
|
|
static gint direction_value[256][4];
|
|
|
|
|
|
G_DEFINE_TYPE (PikaIscissorsTool, pika_iscissors_tool,
|
|
PIKA_TYPE_SELECTION_TOOL)
|
|
|
|
#define parent_class pika_iscissors_tool_parent_class
|
|
|
|
|
|
void
|
|
pika_iscissors_tool_register (PikaToolRegisterCallback callback,
|
|
gpointer data)
|
|
{
|
|
(* callback) (PIKA_TYPE_ISCISSORS_TOOL,
|
|
PIKA_TYPE_ISCISSORS_OPTIONS,
|
|
pika_iscissors_options_gui,
|
|
0,
|
|
"pika-iscissors-tool",
|
|
_("Scissors Select"),
|
|
_("Scissors Select Tool: Select shapes using intelligent edge-fitting"),
|
|
N_("Intelligent _Scissors"),
|
|
"I",
|
|
NULL, PIKA_HELP_TOOL_ISCISSORS,
|
|
PIKA_ICON_TOOL_ISCISSORS,
|
|
data);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_class_init (PikaIscissorsToolClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass);
|
|
PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass);
|
|
gint i, j;
|
|
gint radius;
|
|
|
|
object_class->finalize = pika_iscissors_tool_finalize;
|
|
|
|
tool_class->control = pika_iscissors_tool_control;
|
|
tool_class->button_press = pika_iscissors_tool_button_press;
|
|
tool_class->button_release = pika_iscissors_tool_button_release;
|
|
tool_class->motion = pika_iscissors_tool_motion;
|
|
tool_class->key_press = pika_iscissors_tool_key_press;
|
|
tool_class->oper_update = pika_iscissors_tool_oper_update;
|
|
tool_class->cursor_update = pika_iscissors_tool_cursor_update;
|
|
tool_class->can_undo = pika_iscissors_tool_can_undo;
|
|
tool_class->can_redo = pika_iscissors_tool_can_redo;
|
|
tool_class->undo = pika_iscissors_tool_undo;
|
|
tool_class->redo = pika_iscissors_tool_redo;
|
|
|
|
draw_tool_class->draw = pika_iscissors_tool_draw;
|
|
|
|
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 highest cost */
|
|
direction_value[255][0] = 255;
|
|
direction_value[255][1] = 255;
|
|
direction_value[255][2] = 255;
|
|
direction_value[255][3] = 255;
|
|
|
|
/* compute the distance weights */
|
|
radius = GRADIENT_SEARCH >> 1;
|
|
|
|
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)));
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_init (PikaIscissorsTool *iscissors)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (iscissors);
|
|
|
|
pika_tool_control_set_scroll_lock (tool->control, TRUE);
|
|
pika_tool_control_set_snap_to (tool->control, FALSE);
|
|
pika_tool_control_set_preserve (tool->control, FALSE);
|
|
pika_tool_control_set_dirty_mask (tool->control,
|
|
PIKA_DIRTY_IMAGE_SIZE |
|
|
PIKA_DIRTY_ACTIVE_DRAWABLE);
|
|
pika_tool_control_set_tool_cursor (tool->control,
|
|
PIKA_TOOL_CURSOR_ISCISSORS);
|
|
|
|
iscissors->op = ISCISSORS_OP_NONE;
|
|
iscissors->curve = icurve_new ();
|
|
iscissors->state = NO_ACTION;
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_finalize (GObject *object)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (object);
|
|
|
|
icurve_free (iscissors->curve);
|
|
iscissors->curve = NULL;
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_control (PikaTool *tool,
|
|
PikaToolAction action,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
switch (action)
|
|
{
|
|
case PIKA_TOOL_ACTION_PAUSE:
|
|
case PIKA_TOOL_ACTION_RESUME:
|
|
break;
|
|
|
|
case PIKA_TOOL_ACTION_HALT:
|
|
pika_iscissors_tool_halt (iscissors, display);
|
|
break;
|
|
|
|
case PIKA_TOOL_ACTION_COMMIT:
|
|
pika_iscissors_tool_commit (iscissors, display);
|
|
break;
|
|
}
|
|
|
|
PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_button_press (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
PikaIscissorsOptions *options = PIKA_ISCISSORS_TOOL_GET_OPTIONS (tool);
|
|
PikaImage *image = pika_display_get_image (display);
|
|
ISegment *segment;
|
|
|
|
iscissors->x = RINT (coords->x);
|
|
iscissors->y = RINT (coords->y);
|
|
|
|
/* If the tool was being used in another image...reset it */
|
|
if (display != tool->display)
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
|
|
|
|
pika_tool_control_activate (tool->control);
|
|
tool->display = display;
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
switch (iscissors->state)
|
|
{
|
|
case NO_ACTION:
|
|
iscissors->state = SEED_PLACEMENT;
|
|
|
|
if (! (state & pika_get_extend_selection_mask ()))
|
|
find_max_gradient (iscissors, PIKA_PICKABLE (image),
|
|
&iscissors->x, &iscissors->y);
|
|
|
|
iscissors->x = CLAMP (iscissors->x, 0, pika_image_get_width (image) - 1);
|
|
iscissors->y = CLAMP (iscissors->y, 0, pika_image_get_height (image) - 1);
|
|
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
|
|
segment = icurve_append_segment (iscissors->curve,
|
|
iscissors->x,
|
|
iscissors->y,
|
|
iscissors->x,
|
|
iscissors->y);
|
|
|
|
/* Initialize the draw tool only on starting the tool */
|
|
pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
|
|
break;
|
|
|
|
default:
|
|
/* Check if the mouse click occurred on a vertex or the curve itself */
|
|
if (clicked_on_vertex (iscissors, coords->x, coords->y))
|
|
{
|
|
iscissors->state = SEED_ADJUSTMENT;
|
|
|
|
/* recalculate both segments */
|
|
if (iscissors->segment1)
|
|
{
|
|
iscissors->segment1->x1 = iscissors->x;
|
|
iscissors->segment1->y1 = iscissors->y;
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment1);
|
|
}
|
|
|
|
if (iscissors->segment2)
|
|
{
|
|
iscissors->segment2->x2 = iscissors->x;
|
|
iscissors->segment2->y2 = iscissors->y;
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment2);
|
|
}
|
|
}
|
|
/* If the iscissors is closed, check if the click was inside */
|
|
else if (iscissors->curve->closed && iscissors->mask &&
|
|
pika_pickable_get_opacity_at (PIKA_PICKABLE (iscissors->mask),
|
|
iscissors->x,
|
|
iscissors->y))
|
|
{
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, display);
|
|
}
|
|
else if (! iscissors->curve->closed)
|
|
{
|
|
/* if we're not closed, we're adding a new point */
|
|
|
|
ISegment *last = g_queue_peek_tail (iscissors->curve->segments);
|
|
|
|
iscissors->state = SEED_PLACEMENT;
|
|
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
|
|
if (last->x1 == last->x2 &&
|
|
last->y1 == last->y2)
|
|
{
|
|
last->x2 = iscissors->x;
|
|
last->y2 = iscissors->y;
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, last);
|
|
}
|
|
else
|
|
{
|
|
segment = icurve_append_segment (iscissors->curve,
|
|
last->x2,
|
|
last->y2,
|
|
iscissors->x,
|
|
iscissors->y);
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, segment);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
|
|
static void
|
|
iscissors_convert (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaSelectionOptions *options = PIKA_SELECTION_TOOL_GET_OPTIONS (iscissors);
|
|
PikaImage *image = pika_display_get_image (display);
|
|
PikaScanConvert *sc;
|
|
|
|
sc = icurve_create_scan_convert (iscissors->curve);
|
|
|
|
if (iscissors->mask)
|
|
g_object_unref (iscissors->mask);
|
|
|
|
iscissors->mask = pika_channel_new_mask (image,
|
|
pika_image_get_width (image),
|
|
pika_image_get_height (image));
|
|
pika_scan_convert_render (sc,
|
|
pika_drawable_get_buffer (PIKA_DRAWABLE (iscissors->mask)),
|
|
0, 0, options->antialias);
|
|
|
|
pika_scan_convert_free (sc);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_button_release (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
PikaIscissorsOptions *options = PIKA_ISCISSORS_TOOL_GET_OPTIONS (tool);
|
|
|
|
pika_tool_control_halt (tool->control);
|
|
|
|
/* Make sure X didn't skip the button release event -- as it's known
|
|
* to do
|
|
*/
|
|
if (iscissors->state == WAITING)
|
|
return;
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
if (release_type != PIKA_BUTTON_RELEASE_CANCEL)
|
|
{
|
|
/* Progress to the next stage of intelligent selection */
|
|
switch (iscissors->state)
|
|
{
|
|
case SEED_PLACEMENT:
|
|
/* Add a new segment */
|
|
if (! iscissors->curve->first_point)
|
|
{
|
|
/* Determine if we're connecting to the first point */
|
|
|
|
ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
|
|
|
|
if (pika_draw_tool_on_handle (PIKA_DRAW_TOOL (tool), display,
|
|
iscissors->x, iscissors->y,
|
|
PIKA_HANDLE_CIRCLE,
|
|
segment->x1, segment->y1,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER))
|
|
{
|
|
iscissors->x = segment->x1;
|
|
iscissors->y = segment->y1;
|
|
|
|
icurve_close (iscissors->curve);
|
|
|
|
if (! options->interactive)
|
|
{
|
|
segment = g_queue_peek_tail (iscissors->curve->segments);
|
|
calculate_segment (iscissors, segment);
|
|
}
|
|
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
}
|
|
else
|
|
{
|
|
segment = g_queue_peek_tail (iscissors->curve->segments);
|
|
|
|
if (segment->x1 != segment->x2 ||
|
|
segment->y1 != segment->y2)
|
|
{
|
|
if (! options->interactive)
|
|
calculate_segment (iscissors, segment);
|
|
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
}
|
|
else
|
|
{
|
|
pika_iscissors_tool_pop_undo (iscissors);
|
|
}
|
|
}
|
|
}
|
|
else /* this was our first point */
|
|
{
|
|
iscissors->curve->first_point = FALSE;
|
|
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
}
|
|
break;
|
|
|
|
case SEED_ADJUSTMENT:
|
|
if (state & pika_get_modify_selection_mask ())
|
|
{
|
|
if (iscissors->segment1 && iscissors->segment2)
|
|
{
|
|
icurve_delete_segment (iscissors->curve,
|
|
iscissors->segment2);
|
|
|
|
calculate_segment (iscissors, iscissors->segment1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* recalculate both segments */
|
|
|
|
if (iscissors->segment1)
|
|
{
|
|
if (! options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment1);
|
|
}
|
|
|
|
if (iscissors->segment2)
|
|
{
|
|
if (! options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment2);
|
|
}
|
|
}
|
|
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (iscissors->state)
|
|
{
|
|
case SEED_PLACEMENT:
|
|
case SEED_ADJUSTMENT:
|
|
pika_iscissors_tool_pop_undo (iscissors);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (iscissors->curve->first_point)
|
|
iscissors->state = NO_ACTION;
|
|
else
|
|
iscissors->state = WAITING;
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
|
|
/* convert the curves into a region */
|
|
if (iscissors->curve->closed)
|
|
iscissors_convert (iscissors, display);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_motion (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
PikaIscissorsOptions *options = PIKA_ISCISSORS_TOOL_GET_OPTIONS (tool);
|
|
PikaImage *image = pika_display_get_image (display);
|
|
ISegment *segment;
|
|
|
|
if (iscissors->state == NO_ACTION)
|
|
return;
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
iscissors->x = RINT (coords->x);
|
|
iscissors->y = RINT (coords->y);
|
|
|
|
/* Hold the shift key down to disable the auto-edge snap feature */
|
|
if (! (state & pika_get_extend_selection_mask ()))
|
|
find_max_gradient (iscissors, PIKA_PICKABLE (image),
|
|
&iscissors->x, &iscissors->y);
|
|
|
|
iscissors->x = CLAMP (iscissors->x, 0, pika_image_get_width (image) - 1);
|
|
iscissors->y = CLAMP (iscissors->y, 0, pika_image_get_height (image) - 1);
|
|
|
|
switch (iscissors->state)
|
|
{
|
|
case SEED_PLACEMENT:
|
|
segment = g_queue_peek_tail (iscissors->curve->segments);
|
|
|
|
segment->x2 = iscissors->x;
|
|
segment->y2 = iscissors->y;
|
|
|
|
if (iscissors->curve->first_point)
|
|
{
|
|
segment->x1 = segment->x2;
|
|
segment->y1 = segment->y2;
|
|
}
|
|
else
|
|
{
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, segment);
|
|
}
|
|
break;
|
|
|
|
case SEED_ADJUSTMENT:
|
|
if (iscissors->segment1)
|
|
{
|
|
iscissors->segment1->x1 = iscissors->x;
|
|
iscissors->segment1->y1 = iscissors->y;
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment1);
|
|
}
|
|
|
|
if (iscissors->segment2)
|
|
{
|
|
iscissors->segment2->x2 = iscissors->x;
|
|
iscissors->segment2->y2 = iscissors->y;
|
|
|
|
if (options->interactive)
|
|
calculate_segment (iscissors, iscissors->segment2);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_draw (PikaDrawTool *draw_tool)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (draw_tool);
|
|
PikaIscissorsOptions *options = PIKA_ISCISSORS_TOOL_GET_OPTIONS (draw_tool);
|
|
PikaCanvasItem *item;
|
|
GList *list;
|
|
|
|
/* First, render all segments and lines */
|
|
if (! iscissors->curve->first_point)
|
|
{
|
|
for (list = g_queue_peek_head_link (iscissors->curve->segments);
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
|
|
/* plot the segment */
|
|
item = iscissors_draw_segment (draw_tool, segment);
|
|
|
|
/* if this segment is currently being added or adjusted */
|
|
if ((iscissors->state == SEED_PLACEMENT &&
|
|
! list->next)
|
|
||
|
|
(iscissors->state == SEED_ADJUSTMENT &&
|
|
(segment == iscissors->segment1 ||
|
|
segment == iscissors->segment2)))
|
|
{
|
|
if (! options->interactive)
|
|
item = pika_draw_tool_add_line (draw_tool,
|
|
segment->x1, segment->y1,
|
|
segment->x2, segment->y2);
|
|
|
|
if (item)
|
|
pika_canvas_item_set_highlight (item, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Then, render the handles on top of the segments */
|
|
for (list = g_queue_peek_head_link (iscissors->curve->segments);
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
|
|
if (! iscissors->curve->first_point)
|
|
{
|
|
gboolean adjustment = (iscissors->state == SEED_ADJUSTMENT &&
|
|
segment == iscissors->segment1);
|
|
|
|
item = pika_draw_tool_add_handle (draw_tool,
|
|
adjustment ?
|
|
PIKA_HANDLE_CROSS :
|
|
PIKA_HANDLE_FILLED_CIRCLE,
|
|
segment->x1,
|
|
segment->y1,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
if (adjustment)
|
|
pika_canvas_item_set_highlight (item, TRUE);
|
|
}
|
|
|
|
/* Draw the last point if the curve is not closed */
|
|
if (! list->next && ! iscissors->curve->closed)
|
|
{
|
|
gboolean placement = (iscissors->state == SEED_PLACEMENT);
|
|
|
|
item = pika_draw_tool_add_handle (draw_tool,
|
|
placement ?
|
|
PIKA_HANDLE_CROSS :
|
|
PIKA_HANDLE_FILLED_CIRCLE,
|
|
segment->x2,
|
|
segment->y2,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
if (placement)
|
|
pika_canvas_item_set_highlight (item, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static PikaCanvasItem *
|
|
iscissors_draw_segment (PikaDrawTool *draw_tool,
|
|
ISegment *segment)
|
|
{
|
|
PikaCanvasItem *item;
|
|
PikaVector2 *points;
|
|
gpointer *point;
|
|
gint i, len;
|
|
|
|
if (! segment->points)
|
|
return NULL;
|
|
|
|
len = segment->points->len;
|
|
|
|
points = g_new (PikaVector2, len);
|
|
|
|
for (i = 0, point = segment->points->pdata; i < len; i++, point++)
|
|
{
|
|
guint32 coords = GPOINTER_TO_INT (*point);
|
|
|
|
points[i].x = (coords & 0x0000ffff);
|
|
points[i].y = (coords >> 16);
|
|
}
|
|
|
|
item = pika_draw_tool_add_lines (draw_tool, points, len, NULL, FALSE);
|
|
|
|
g_free (points);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_oper_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
|
|
display);
|
|
/* parent sets a message in the status bar, but it will be replaced here */
|
|
|
|
if (mouse_over_vertex (iscissors, coords->x, coords->y) > 1)
|
|
{
|
|
GdkModifierType snap_mask = pika_get_extend_selection_mask ();
|
|
GdkModifierType remove_mask = pika_get_modify_selection_mask ();
|
|
|
|
if (state & remove_mask)
|
|
{
|
|
pika_tool_replace_status (tool, display,
|
|
_("Click to remove this point"));
|
|
iscissors->op = ISCISSORS_OP_REMOVE_POINT;
|
|
}
|
|
else
|
|
{
|
|
gchar *status =
|
|
pika_suggest_modifiers (_("Click-Drag to move this point"),
|
|
(snap_mask | remove_mask) & ~state,
|
|
_("%s: disable auto-snap"),
|
|
_("%s: remove this point"),
|
|
NULL);
|
|
pika_tool_replace_status (tool, display, "%s", status);
|
|
g_free (status);
|
|
iscissors->op = ISCISSORS_OP_MOVE_POINT;
|
|
}
|
|
}
|
|
else if (mouse_over_segment (iscissors, coords->x, coords->y))
|
|
{
|
|
ISegment *segment = g_queue_peek_head (iscissors->curve->segments);
|
|
|
|
if (pika_draw_tool_on_handle (PIKA_DRAW_TOOL (tool), display,
|
|
RINT (coords->x), RINT (coords->y),
|
|
PIKA_HANDLE_CIRCLE,
|
|
segment->x1, segment->y1,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER))
|
|
{
|
|
pika_tool_replace_status (tool, display,
|
|
_("Click to close the curve"));
|
|
iscissors->op = ISCISSORS_OP_CONNECT;
|
|
}
|
|
else
|
|
{
|
|
pika_tool_replace_status (tool, display,
|
|
_("Click to add a point on this segment"));
|
|
iscissors->op = ISCISSORS_OP_ADD_POINT;
|
|
}
|
|
}
|
|
else if (iscissors->curve->closed && iscissors->mask)
|
|
{
|
|
if (pika_pickable_get_opacity_at (PIKA_PICKABLE (iscissors->mask),
|
|
RINT (coords->x),
|
|
RINT (coords->y)))
|
|
{
|
|
if (proximity)
|
|
{
|
|
pika_tool_replace_status (tool, display,
|
|
_("Click or press Enter to convert to"
|
|
" a selection"));
|
|
}
|
|
iscissors->op = ISCISSORS_OP_SELECT;
|
|
}
|
|
else
|
|
{
|
|
if (proximity)
|
|
{
|
|
pika_tool_replace_status (tool, display,
|
|
_("Press Enter to convert to a"
|
|
" selection"));
|
|
}
|
|
iscissors->op = ISCISSORS_OP_IMPOSSIBLE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (iscissors->state)
|
|
{
|
|
case WAITING:
|
|
if (proximity)
|
|
{
|
|
GdkModifierType snap_mask = pika_get_extend_selection_mask ();
|
|
gchar *status;
|
|
|
|
status = pika_suggest_modifiers (_("Click or Click-Drag to add a"
|
|
" point"),
|
|
snap_mask & ~state,
|
|
_("%s: disable auto-snap"),
|
|
NULL, NULL);
|
|
pika_tool_replace_status (tool, display, "%s", status);
|
|
g_free (status);
|
|
}
|
|
iscissors->op = ISCISSORS_OP_ADD_POINT;
|
|
break;
|
|
|
|
default:
|
|
/* if NO_ACTION, keep parent's status bar message (selection tool) */
|
|
iscissors->op = ISCISSORS_OP_NONE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_cursor_update (PikaTool *tool,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_NONE;
|
|
|
|
switch (iscissors->op)
|
|
{
|
|
case ISCISSORS_OP_SELECT:
|
|
{
|
|
PikaSelectionOptions *options;
|
|
|
|
options = PIKA_SELECTION_TOOL_GET_OPTIONS (tool);
|
|
|
|
/* Do not overwrite the modifiers for add, subtract, intersect */
|
|
if (options->operation == PIKA_CHANNEL_OP_REPLACE)
|
|
{
|
|
modifier = PIKA_CURSOR_MODIFIER_SELECT;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ISCISSORS_OP_MOVE_POINT:
|
|
modifier = PIKA_CURSOR_MODIFIER_MOVE;
|
|
break;
|
|
|
|
case ISCISSORS_OP_ADD_POINT:
|
|
modifier = PIKA_CURSOR_MODIFIER_PLUS;
|
|
break;
|
|
|
|
case ISCISSORS_OP_REMOVE_POINT:
|
|
modifier = PIKA_CURSOR_MODIFIER_MINUS;
|
|
break;
|
|
|
|
case ISCISSORS_OP_CONNECT:
|
|
modifier = PIKA_CURSOR_MODIFIER_JOIN;
|
|
break;
|
|
|
|
case ISCISSORS_OP_IMPOSSIBLE:
|
|
modifier = PIKA_CURSOR_MODIFIER_BAD;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (modifier != PIKA_CURSOR_MODIFIER_NONE)
|
|
{
|
|
pika_tool_set_cursor (tool, display,
|
|
PIKA_CURSOR_MOUSE,
|
|
PIKA_TOOL_CURSOR_ISCISSORS,
|
|
modifier);
|
|
}
|
|
else
|
|
{
|
|
PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords,
|
|
state, display);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_iscissors_tool_key_press (PikaTool *tool,
|
|
GdkEventKey *kevent,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
if (display != tool->display)
|
|
return FALSE;
|
|
|
|
switch (kevent->keyval)
|
|
{
|
|
case GDK_KEY_BackSpace:
|
|
if (! iscissors->curve->closed &&
|
|
g_queue_peek_tail (iscissors->curve->segments))
|
|
{
|
|
ISegment *segment = g_queue_peek_tail (iscissors->curve->segments);
|
|
|
|
if (g_queue_get_length (iscissors->curve->segments) > 1)
|
|
{
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
icurve_delete_segment (iscissors->curve, segment);
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
else if (segment->x2 != segment->x1 || segment->y2 != segment->y1)
|
|
{
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
segment->x2 = segment->x1;
|
|
segment->y2 = segment->y1;
|
|
g_ptr_array_remove_range (segment->points,
|
|
0, segment->points->len);
|
|
pika_iscissors_tool_free_redo (iscissors);
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
else
|
|
{
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
|
|
case GDK_KEY_Return:
|
|
case GDK_KEY_KP_Enter:
|
|
case GDK_KEY_ISO_Enter:
|
|
if (iscissors->curve->closed && iscissors->mask)
|
|
{
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, display);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
|
|
case GDK_KEY_Escape:
|
|
pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display);
|
|
return TRUE;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static const gchar *
|
|
pika_iscissors_tool_can_undo (PikaTool *tool,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
if (! iscissors->undo_stack)
|
|
return NULL;
|
|
|
|
return _("Modify Scissors Curve");
|
|
}
|
|
|
|
static const gchar *
|
|
pika_iscissors_tool_can_redo (PikaTool *tool,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
if (! iscissors->redo_stack)
|
|
return NULL;
|
|
|
|
return _("Modify Scissors Curve");
|
|
}
|
|
|
|
static gboolean
|
|
pika_iscissors_tool_undo (PikaTool *tool,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
iscissors->redo_stack = g_list_prepend (iscissors->redo_stack,
|
|
iscissors->curve);
|
|
|
|
iscissors->curve = iscissors->undo_stack->data;
|
|
|
|
iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
|
|
iscissors->curve);
|
|
|
|
if (! iscissors->undo_stack)
|
|
{
|
|
iscissors->state = NO_ACTION;
|
|
|
|
pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));
|
|
}
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
pika_iscissors_tool_redo (PikaTool *tool,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaIscissorsTool *iscissors = PIKA_ISCISSORS_TOOL (tool);
|
|
|
|
pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));
|
|
|
|
if (! iscissors->undo_stack)
|
|
{
|
|
iscissors->state = WAITING;
|
|
|
|
pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
|
|
}
|
|
|
|
iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
|
|
iscissors->curve);
|
|
|
|
iscissors->curve = iscissors->redo_stack->data;
|
|
|
|
iscissors->redo_stack = g_list_remove (iscissors->redo_stack,
|
|
iscissors->curve);
|
|
|
|
pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_push_undo (PikaIscissorsTool *iscissors)
|
|
{
|
|
iscissors->undo_stack = g_list_prepend (iscissors->undo_stack,
|
|
icurve_copy (iscissors->curve));
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_pop_undo (PikaIscissorsTool *iscissors)
|
|
{
|
|
icurve_free (iscissors->curve);
|
|
iscissors->curve = iscissors->undo_stack->data;
|
|
|
|
iscissors->undo_stack = g_list_remove (iscissors->undo_stack,
|
|
iscissors->curve);
|
|
|
|
if (! iscissors->undo_stack)
|
|
{
|
|
iscissors->state = NO_ACTION;
|
|
|
|
pika_draw_tool_stop (PIKA_DRAW_TOOL (iscissors));
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_free_redo (PikaIscissorsTool *iscissors)
|
|
{
|
|
g_list_free_full (iscissors->redo_stack,
|
|
(GDestroyNotify) icurve_free);
|
|
iscissors->redo_stack = NULL;
|
|
|
|
/* update the undo actions / menu items */
|
|
pika_image_flush (pika_display_get_image (PIKA_TOOL (iscissors)->display));
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_halt (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display)
|
|
{
|
|
icurve_clear (iscissors->curve);
|
|
|
|
iscissors->segment1 = NULL;
|
|
iscissors->segment2 = NULL;
|
|
iscissors->state = NO_ACTION;
|
|
|
|
if (iscissors->undo_stack)
|
|
{
|
|
g_list_free_full (iscissors->undo_stack, (GDestroyNotify) icurve_free);
|
|
iscissors->undo_stack = NULL;
|
|
}
|
|
|
|
if (iscissors->redo_stack)
|
|
{
|
|
g_list_free_full (iscissors->redo_stack, (GDestroyNotify) icurve_free);
|
|
iscissors->redo_stack = NULL;
|
|
}
|
|
|
|
g_clear_object (&iscissors->gradient_map);
|
|
g_clear_object (&iscissors->mask);
|
|
}
|
|
|
|
static void
|
|
pika_iscissors_tool_commit (PikaIscissorsTool *iscissors,
|
|
PikaDisplay *display)
|
|
{
|
|
PikaTool *tool = PIKA_TOOL (iscissors);
|
|
PikaSelectionOptions *options = PIKA_SELECTION_TOOL_GET_OPTIONS (tool);
|
|
PikaImage *image = pika_display_get_image (display);
|
|
|
|
if (! iscissors->curve->closed)
|
|
{
|
|
ISegment *first = g_queue_peek_head (iscissors->curve->segments);
|
|
ISegment *last = g_queue_peek_tail (iscissors->curve->segments);
|
|
|
|
if (first && last && first != last)
|
|
{
|
|
ISegment *segment;
|
|
|
|
segment = icurve_append_segment (iscissors->curve,
|
|
last->x2,
|
|
last->y2,
|
|
first->x1,
|
|
first->y1);
|
|
icurve_close (iscissors->curve);
|
|
calculate_segment (iscissors, segment);
|
|
|
|
iscissors_convert (iscissors, display);
|
|
}
|
|
}
|
|
|
|
if (iscissors->curve->closed && iscissors->mask)
|
|
{
|
|
pika_channel_select_channel (pika_image_get_mask (image),
|
|
pika_tool_get_undo_desc (tool),
|
|
iscissors->mask,
|
|
0, 0,
|
|
options->operation,
|
|
options->feather,
|
|
options->feather_radius,
|
|
options->feather_radius);
|
|
|
|
pika_image_flush (image);
|
|
}
|
|
}
|
|
|
|
|
|
/* XXX need some scan-conversion routines from somewhere. maybe. ? */
|
|
|
|
static gint
|
|
mouse_over_vertex (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GList *list;
|
|
gint segments_found = 0;
|
|
|
|
/* traverse through the list, returning non-zero if the current cursor
|
|
* position is on an existing curve vertex. Set the segment1 and segment2
|
|
* variables to the two segments containing the vertex in question
|
|
*/
|
|
|
|
iscissors->segment1 = iscissors->segment2 = NULL;
|
|
|
|
for (list = g_queue_peek_head_link (iscissors->curve->segments);
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
|
|
if (pika_draw_tool_on_handle (PIKA_DRAW_TOOL (iscissors),
|
|
PIKA_TOOL (iscissors)->display,
|
|
x, y,
|
|
PIKA_HANDLE_CIRCLE,
|
|
segment->x1, segment->y1,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER))
|
|
{
|
|
iscissors->segment1 = segment;
|
|
|
|
if (segments_found++)
|
|
return segments_found;
|
|
}
|
|
else if (pika_draw_tool_on_handle (PIKA_DRAW_TOOL (iscissors),
|
|
PIKA_TOOL (iscissors)->display,
|
|
x, y,
|
|
PIKA_HANDLE_CIRCLE,
|
|
segment->x2, segment->y2,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_TOOL_HANDLE_SIZE_CIRCLE,
|
|
PIKA_HANDLE_ANCHOR_CENTER))
|
|
{
|
|
iscissors->segment2 = segment;
|
|
|
|
if (segments_found++)
|
|
return segments_found;
|
|
}
|
|
}
|
|
|
|
return segments_found;
|
|
}
|
|
|
|
static gboolean
|
|
clicked_on_vertex (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
gint segments_found = mouse_over_vertex (iscissors, x, y);
|
|
|
|
if (segments_found > 1)
|
|
{
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* if only one segment was found, the segments are unconnected, and
|
|
* the user only wants to move either the first or last point
|
|
* disallow this for now.
|
|
*/
|
|
if (segments_found == 1)
|
|
return FALSE;
|
|
|
|
return clicked_on_segment (iscissors, x, y);
|
|
}
|
|
|
|
|
|
static GList *
|
|
mouse_over_segment (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GList *list;
|
|
|
|
/* traverse through the list, returning the curve segment's list element
|
|
* if the current cursor position is on a curve...
|
|
*/
|
|
for (list = g_queue_peek_head_link (iscissors->curve->segments);
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
gpointer *pt;
|
|
gint len;
|
|
|
|
if (! segment->points)
|
|
continue;
|
|
|
|
pt = segment->points->pdata;
|
|
len = segment->points->len;
|
|
|
|
while (len--)
|
|
{
|
|
guint32 coords = GPOINTER_TO_INT (*pt);
|
|
gint tx, ty;
|
|
|
|
pt++;
|
|
tx = coords & 0x0000ffff;
|
|
ty = coords >> 16;
|
|
|
|
/* Is the specified point close enough to the segment? */
|
|
if (pika_draw_tool_calc_distance_square (PIKA_DRAW_TOOL (iscissors),
|
|
PIKA_TOOL (iscissors)->display,
|
|
tx, ty,
|
|
x, y) < SQR (PIKA_TOOL_HANDLE_SIZE_CIRCLE / 2))
|
|
{
|
|
return list;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
clicked_on_segment (PikaIscissorsTool *iscissors,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GList *list = mouse_over_segment (iscissors, x, y);
|
|
|
|
/* traverse through the list, getting back the curve segment's list
|
|
* element if the current cursor position is on a segment...
|
|
* If this occurs, replace the segment with two new segments,
|
|
* separated by a new vertex.
|
|
*/
|
|
|
|
if (list)
|
|
{
|
|
ISegment *segment = list->data;
|
|
ISegment *new_segment;
|
|
|
|
pika_iscissors_tool_push_undo (iscissors);
|
|
|
|
new_segment = icurve_insert_segment (iscissors->curve,
|
|
list,
|
|
iscissors->x,
|
|
iscissors->y,
|
|
segment->x2,
|
|
segment->y2);
|
|
|
|
iscissors->segment1 = new_segment;
|
|
iscissors->segment2 = segment;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
calculate_segment (PikaIscissorsTool *iscissors,
|
|
ISegment *segment)
|
|
{
|
|
PikaDisplay *display = PIKA_TOOL (iscissors)->display;
|
|
PikaPickable *pickable = PIKA_PICKABLE (pika_display_get_image (display));
|
|
gint width;
|
|
gint height;
|
|
gint xs, ys, xe, ye;
|
|
gint x1, y1, x2, y2;
|
|
gint ewidth, eheight;
|
|
|
|
/* Initialise the gradient map buffer for this pickable if we don't
|
|
* already have one.
|
|
*/
|
|
if (! iscissors->gradient_map)
|
|
iscissors->gradient_map = gradient_map_new (pickable);
|
|
|
|
width = gegl_buffer_get_width (iscissors->gradient_map);
|
|
height = gegl_buffer_get_height (iscissors->gradient_map);
|
|
|
|
/* Calculate the lowest cost path from one vertex to the next as specified
|
|
* by the parameter "segment".
|
|
* 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 isegment data
|
|
* structure.
|
|
*/
|
|
|
|
/* Get the bounding box */
|
|
xs = CLAMP (segment->x1, 0, width - 1);
|
|
ys = CLAMP (segment->y1, 0, height - 1);
|
|
xe = CLAMP (segment->x2, 0, width - 1);
|
|
ye = CLAMP (segment->y2, 0, 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 "segment".
|
|
*/
|
|
ewidth = (x2 - x1) * EXTEND_BY + FIXED;
|
|
eheight = (y2 - y1) * EXTEND_BY + FIXED;
|
|
|
|
if (xe >= xs)
|
|
x2 += CLAMP (ewidth, 0, width - x2);
|
|
else
|
|
x1 -= CLAMP (ewidth, 0, x1);
|
|
|
|
if (ye >= ys)
|
|
y2 += CLAMP (eheight, 0, height - y2);
|
|
else
|
|
y1 -= CLAMP (eheight, 0, y1);
|
|
|
|
/* blow away any previous points list we might have */
|
|
if (segment->points)
|
|
{
|
|
g_ptr_array_free (segment->points, TRUE);
|
|
segment->points = NULL;
|
|
}
|
|
|
|
if ((x2 - x1) && (y2 - y1))
|
|
{
|
|
/* If the bounding box has width and height... */
|
|
|
|
PikaTempBuf *dp_buf; /* dynamic programming buffer */
|
|
gint dp_width = (x2 - x1);
|
|
gint dp_height = (y2 - y1);
|
|
|
|
dp_buf = pika_temp_buf_new (dp_width, dp_height,
|
|
babl_format ("Y u32"));
|
|
|
|
/* find the optimal path of pixels from (x1, y1) to (x2, y2) */
|
|
find_optimal_path (iscissors->gradient_map, dp_buf,
|
|
x1, y1, x2, y2, xs, ys);
|
|
|
|
/* get a list of the pixels in the optimal path */
|
|
segment->points = plot_pixels (dp_buf, x1, y1, xs, ys, xe, ye);
|
|
|
|
pika_temp_buf_unref (dp_buf);
|
|
}
|
|
else if ((x2 - x1) == 0)
|
|
{
|
|
/* If the bounding box has no width */
|
|
|
|
/* plot a vertical line */
|
|
gint y = ys;
|
|
gint dir = (ys > ye) ? -1 : 1;
|
|
|
|
segment->points = g_ptr_array_new ();
|
|
while (y != ye)
|
|
{
|
|
g_ptr_array_add (segment->points, GINT_TO_POINTER ((y << 16) + xs));
|
|
y += dir;
|
|
}
|
|
}
|
|
else if ((y2 - y1) == 0)
|
|
{
|
|
/* If the bounding box has no height */
|
|
|
|
/* plot a horizontal line */
|
|
gint x = xs;
|
|
gint dir = (xs > xe) ? -1 : 1;
|
|
|
|
segment->points = g_ptr_array_new ();
|
|
while (x != xe)
|
|
{
|
|
g_ptr_array_add (segment->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 (GeglSampler *map_sampler,
|
|
const GeglRectangle *map_extent,
|
|
gint x,
|
|
gint y,
|
|
guint8 *grad,
|
|
guint8 *dir)
|
|
{
|
|
if (x >= map_extent->x &&
|
|
y >= map_extent->y &&
|
|
x < map_extent->width &&
|
|
y < map_extent->height)
|
|
{
|
|
guint8 sample[2];
|
|
|
|
gegl_sampler_get (map_sampler, x, y, NULL, sample, GEGL_ABYSS_NONE);
|
|
|
|
*grad = sample[0];
|
|
*dir = sample[1];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
calculate_link (GeglSampler *map_sampler,
|
|
const GeglRectangle *map_extent,
|
|
gint x,
|
|
gint y,
|
|
guint32 pixel,
|
|
gint link)
|
|
{
|
|
gint value = 0;
|
|
guint8 grad1, dir1, grad2, dir2;
|
|
|
|
if (! gradient_map_value (map_sampler, map_extent, 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 (map_sampler, map_extent, 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 (PikaTempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint xs,
|
|
gint ys,
|
|
gint xe,
|
|
gint ye)
|
|
{
|
|
gint x, y;
|
|
guint32 coords;
|
|
gint link;
|
|
gint width = pika_temp_buf_get_width (dp_buf);
|
|
guint *data;
|
|
GPtrArray *list;
|
|
|
|
/* Start the data pointer at the correct location */
|
|
data = (guint *) pika_temp_buf_get_data (dp_buf) + (ye - y1) * width + (xe - x1);
|
|
|
|
x = xe;
|
|
y = ye;
|
|
|
|
list = g_ptr_array_new ();
|
|
|
|
while (TRUE)
|
|
{
|
|
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)) * \
|
|
pika_temp_buf_get_width (dp_buf))
|
|
|
|
static void
|
|
find_optimal_path (GeglBuffer *gradient_map,
|
|
PikaTempBuf *dp_buf,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gint xs,
|
|
gint ys)
|
|
{
|
|
GeglSampler *map_sampler;
|
|
const GeglRectangle *map_extent;
|
|
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;
|
|
gint dp_buf_width = pika_temp_buf_get_width (dp_buf);
|
|
gint dp_buf_height = pika_temp_buf_get_height (dp_buf);
|
|
|
|
/* initialize the gradient map sampler and extent */
|
|
map_sampler = gegl_buffer_sampler_new (gradient_map,
|
|
gegl_buffer_get_format (gradient_map),
|
|
GEGL_SAMPLER_NEAREST);
|
|
map_extent = gegl_buffer_get_extent (gradient_map);
|
|
|
|
/* initialize the dynamic programming buffer */
|
|
data = (guint32 *) pika_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 neighbour, 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 (map_sampler, map_extent,
|
|
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;
|
|
}
|
|
|
|
g_object_unref (map_sampler);
|
|
}
|
|
|
|
static GeglBuffer *
|
|
gradient_map_new (PikaPickable *pickable)
|
|
{
|
|
GeglBuffer *buffer;
|
|
GeglTileHandler *handler;
|
|
|
|
buffer = pika_pickable_get_buffer (pickable);
|
|
|
|
buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
|
|
gegl_buffer_get_width (buffer),
|
|
gegl_buffer_get_height (buffer)),
|
|
babl_format_n (babl_type ("u8"), 2));
|
|
|
|
handler = pika_tile_handler_iscissors_new (pickable);
|
|
|
|
pika_tile_handler_validate_assign (PIKA_TILE_HANDLER_VALIDATE (handler),
|
|
buffer);
|
|
|
|
pika_tile_handler_validate_invalidate (PIKA_TILE_HANDLER_VALIDATE (handler),
|
|
GEGL_RECTANGLE (0, 0,
|
|
gegl_buffer_get_width (buffer),
|
|
gegl_buffer_get_height (buffer)));
|
|
|
|
g_object_unref (handler);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void
|
|
find_max_gradient (PikaIscissorsTool *iscissors,
|
|
PikaPickable *pickable,
|
|
gint *x,
|
|
gint *y)
|
|
{
|
|
GeglBufferIterator *iter;
|
|
GeglRectangle *roi;
|
|
gint width;
|
|
gint height;
|
|
gint radius;
|
|
gint cx, cy;
|
|
gint x1, y1, x2, y2;
|
|
gfloat max_gradient;
|
|
|
|
/* Initialise the gradient map buffer for this pickable if we don't
|
|
* already have one.
|
|
*/
|
|
if (! iscissors->gradient_map)
|
|
iscissors->gradient_map = gradient_map_new (pickable);
|
|
|
|
width = gegl_buffer_get_width (iscissors->gradient_map);
|
|
height = gegl_buffer_get_height (iscissors->gradient_map);
|
|
|
|
radius = GRADIENT_SEARCH >> 1;
|
|
|
|
/* calculate the extent of the search */
|
|
cx = CLAMP (*x, 0, width);
|
|
cy = CLAMP (*y, 0, height);
|
|
x1 = CLAMP (cx - radius, 0, width);
|
|
y1 = CLAMP (cy - radius, 0, height);
|
|
x2 = CLAMP (cx + radius, 0, width);
|
|
y2 = CLAMP (cy + radius, 0, height);
|
|
/* calculate the factor to multiply the distance from the cursor by */
|
|
|
|
max_gradient = 0;
|
|
*x = cx;
|
|
*y = cy;
|
|
|
|
iter = gegl_buffer_iterator_new (iscissors->gradient_map,
|
|
GEGL_RECTANGLE (x1, y1, x2 - x1, y2 - y1),
|
|
0, NULL,
|
|
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
|
|
roi = &iter->items[0].roi;
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
|
{
|
|
guint8 *data = iter->items[0].data;
|
|
gint endx = roi->x + roi->width;
|
|
gint endy = roi->y + roi->height;
|
|
gint i, j;
|
|
|
|
for (i = roi->y; i < endy; i++)
|
|
{
|
|
const guint8 *gradient = data + 2 * roi->width * (i - roi->y);
|
|
|
|
for (j = roi->x; j < endx; j++)
|
|
{
|
|
gfloat 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static ISegment *
|
|
isegment_new (gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
ISegment *segment = g_slice_new0 (ISegment);
|
|
|
|
segment->x1 = x1;
|
|
segment->y1 = y1;
|
|
segment->x2 = x2;
|
|
segment->y2 = y2;
|
|
|
|
return segment;
|
|
}
|
|
|
|
static ISegment *
|
|
isegment_copy (ISegment *segment)
|
|
{
|
|
ISegment *copy = isegment_new (segment->x1,
|
|
segment->y1,
|
|
segment->x2,
|
|
segment->y2);
|
|
|
|
if (segment->points)
|
|
{
|
|
gint i;
|
|
|
|
copy->points = g_ptr_array_sized_new (segment->points->len);
|
|
|
|
for (i = 0; i < segment->points->len; i++)
|
|
{
|
|
gpointer value = g_ptr_array_index (segment->points, i);
|
|
|
|
g_ptr_array_add (copy->points, value);
|
|
}
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
isegment_free (ISegment *segment)
|
|
{
|
|
if (segment->points)
|
|
g_ptr_array_free (segment->points, TRUE);
|
|
|
|
g_slice_free (ISegment, segment);
|
|
}
|
|
|
|
static ICurve *
|
|
icurve_new (void)
|
|
{
|
|
ICurve *curve = g_slice_new0 (ICurve);
|
|
|
|
curve->segments = g_queue_new ();
|
|
curve->first_point = TRUE;
|
|
|
|
return curve;
|
|
}
|
|
|
|
static ICurve *
|
|
icurve_copy (ICurve *curve)
|
|
{
|
|
ICurve *copy = icurve_new ();
|
|
GList *link;
|
|
|
|
for (link = g_queue_peek_head_link (curve->segments);
|
|
link;
|
|
link = g_list_next (link))
|
|
{
|
|
g_queue_push_tail (copy->segments, isegment_copy (link->data));
|
|
}
|
|
|
|
copy->first_point = curve->first_point;
|
|
copy->closed = curve->closed;
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
icurve_clear (ICurve *curve)
|
|
{
|
|
while (! g_queue_is_empty (curve->segments))
|
|
isegment_free (g_queue_pop_head (curve->segments));
|
|
|
|
curve->first_point = TRUE;
|
|
curve->closed = FALSE;
|
|
}
|
|
|
|
static void
|
|
icurve_free (ICurve *curve)
|
|
{
|
|
g_queue_free_full (curve->segments, (GDestroyNotify) isegment_free);
|
|
|
|
g_slice_free (ICurve, curve);
|
|
}
|
|
|
|
static ISegment *
|
|
icurve_append_segment (ICurve *curve,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
ISegment *segment = isegment_new (x1, y1, x2, y2);
|
|
|
|
g_queue_push_tail (curve->segments, segment);
|
|
|
|
return segment;
|
|
}
|
|
|
|
static ISegment *
|
|
icurve_insert_segment (ICurve *curve,
|
|
GList *sibling,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
ISegment *segment = sibling->data;
|
|
ISegment *new_segment;
|
|
|
|
new_segment = isegment_new (x1, y1, x2, y2);
|
|
|
|
segment->x2 = x1;
|
|
segment->y2 = y1;
|
|
|
|
g_queue_insert_after (curve->segments, sibling, new_segment);
|
|
|
|
return new_segment;
|
|
}
|
|
|
|
static void
|
|
icurve_delete_segment (ICurve *curve,
|
|
ISegment *segment)
|
|
{
|
|
GList *link = g_queue_find (curve->segments, segment);
|
|
ISegment *next_segment = NULL;
|
|
|
|
if (link->next)
|
|
next_segment = link->next->data;
|
|
else if (curve->closed)
|
|
next_segment = g_queue_peek_head (curve->segments);
|
|
|
|
if (next_segment)
|
|
{
|
|
next_segment->x1 = segment->x1;
|
|
next_segment->y1 = segment->y1;
|
|
}
|
|
|
|
g_queue_remove (curve->segments, segment);
|
|
isegment_free (segment);
|
|
}
|
|
|
|
static void
|
|
icurve_close (ICurve *curve)
|
|
{
|
|
ISegment *first = g_queue_peek_head (curve->segments);
|
|
ISegment *last = g_queue_peek_tail (curve->segments);
|
|
|
|
last->x2 = first->x1;
|
|
last->y2 = first->y1;
|
|
|
|
curve->closed = TRUE;
|
|
}
|
|
|
|
static PikaScanConvert *
|
|
icurve_create_scan_convert (ICurve *curve)
|
|
{
|
|
PikaScanConvert *sc;
|
|
GList *list;
|
|
PikaVector2 *points;
|
|
guint n_total_points = 0;
|
|
|
|
sc = pika_scan_convert_new ();
|
|
|
|
for (list = g_queue_peek_tail_link (curve->segments);
|
|
list;
|
|
list = g_list_previous (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
|
|
n_total_points += segment->points->len;
|
|
}
|
|
|
|
points = g_new (PikaVector2, n_total_points);
|
|
n_total_points = 0;
|
|
|
|
/* go over the segments in reverse order, adding the points we have */
|
|
for (list = g_queue_peek_tail_link (curve->segments);
|
|
list;
|
|
list = g_list_previous (list))
|
|
{
|
|
ISegment *segment = list->data;
|
|
guint n_points;
|
|
gint i;
|
|
|
|
n_points = segment->points->len;
|
|
|
|
for (i = 0; i < n_points; i++)
|
|
{
|
|
guint32 packed = GPOINTER_TO_INT (g_ptr_array_index (segment->points,
|
|
i));
|
|
|
|
points[n_total_points + i].x = packed & 0x0000ffff;
|
|
points[n_total_points + i].y = packed >> 16;
|
|
}
|
|
|
|
n_total_points += n_points;
|
|
}
|
|
|
|
pika_scan_convert_add_polyline (sc, n_total_points, points, TRUE);
|
|
g_free (points);
|
|
|
|
return sc;
|
|
}
|