2006-12-10 05:33:38 +08:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
1997-11-25 06:05:25 +08:00
|
|
|
* 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
|
1998-04-13 13:44:11 +08:00
|
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
1997-11-25 06:05:25 +08:00
|
|
|
*/
|
2000-12-17 05:37:03 +08:00
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
#include <string.h>
|
2000-12-17 05:37:03 +08:00
|
|
|
|
2001-12-08 00:10:53 +08:00
|
|
|
#include <glib-object.h>
|
2000-12-17 05:37:03 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
|
2001-05-15 19:25:25 +08:00
|
|
|
#include "base-types.h"
|
2000-12-17 05:37:03 +08:00
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
#include "boundary.h"
|
2001-05-15 19:25:25 +08:00
|
|
|
#include "pixel-region.h"
|
new ui for the "Layer Offset" dialog.
1999-07-22 Michael Natterer <mitschel@cs.tu-berlin.de>
* app/channel_ops.[ch]: new ui for the "Layer Offset" dialog.
* app/channels_dialog.c
* app/layers_dialog.c: major code cleanup: Folded some callbacks
into common ones, "widget" instead of "w", indentation, ...
* app/commands.c
* app/interface.[ch]
* app/global_edit.c: the query boxes must be shown by the caller
now. There's no need to split up the string for the message box
manually as the Gtk 1.2 label widget handles newlines corectly.
Added the "edge_lock" toggle to the "Shrink Selection" dialog.
Nicer spacings for the query and message boxes.
* app/ink.c: tried to grab the pointer in the blob preview but
failed. Left the code there as a reminder (commented out).
* app/menus.c: reordered <Image>/Select.
I was bored and grep-ed the sources for ancient or deprecated stuff:
* app/about_dialog.[ch]
* app/actionarea.[ch]
* app/app_procs.c
* app/brush_edit.c
* app/brush_select.c
* app/color_select.c
* app/convert.c
* app/devices.c
* app/gdisplay.c
* app/gdisplay_ops.c
* app/histogram_tool.[ch]
* app/info_window.c
* app/install.c
* app/ops_buttons.c
* app/palette.c
* app/palette_select.c
* app/paths_dialog.c
* app/pattern_select.c
* app/resize.c
* app/scale_toolc.c
* app/text_tool.c:
s/container_border_width/container_set_border_width/g,
s/sprintf/g_snprintf/g, replaced some constant string lengths with
strlen(x).
* app/bezier_select.c
* app/blend.c
* app/boundary.c
* app/errors.[ch]
* app/free_select.c
* app/gimpbrushlist.c
* app/gimprc.c
* app/iscissors.c
* app/main.c
* app/patterns.[ch]
* app/text_tool.c: namespace fanaticism: prefixed all gimp error
functions with "gimp_" and formated the messages more uniformly.
* app/gradient.c
* app/gradient_select.c: same stuff as above for the ui
code. There are still some sub-dialogs which need cleanup.
Did some cleanup in most of these files: prototypes, removed tons
of #include's, i18n fixes, s/w/widget/ as above, indentation, ...
1999-07-23 00:21:10 +08:00
|
|
|
#include "tile.h"
|
2001-05-15 19:25:25 +08:00
|
|
|
#include "tile-manager.h"
|
2000-12-29 23:22:01 +08:00
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
/* BoundSeg array growth parameter */
|
|
|
|
#define MAX_SEGS_INC 2048
|
|
|
|
|
2000-12-28 10:01:16 +08:00
|
|
|
|
2005-08-20 20:35:54 +08:00
|
|
|
typedef struct _Boundary Boundary;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-20 20:35:54 +08:00
|
|
|
struct _Boundary
|
|
|
|
{
|
|
|
|
/* The array of segments */
|
2005-08-20 23:46:37 +08:00
|
|
|
BoundSeg *segs;
|
|
|
|
gint num_segs;
|
|
|
|
gint max_segs;
|
|
|
|
|
|
|
|
/* The array of vertical segments */
|
|
|
|
gint *vert_segs;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
/* The empty segment arrays */
|
2005-08-20 23:46:37 +08:00
|
|
|
gint *empty_segs_n;
|
|
|
|
gint *empty_segs_c;
|
|
|
|
gint *empty_segs_l;
|
|
|
|
gint max_empty_segs;
|
2005-08-20 20:35:54 +08:00
|
|
|
};
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
static Boundary * boundary_new (PixelRegion *PR);
|
|
|
|
static BoundSeg * boundary_free (Boundary *boundary,
|
|
|
|
gboolean free_segs);
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
static void boundary_add_seg (Boundary *bounrady,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
gboolean open);
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
static void find_empty_segs (PixelRegion *maskPR,
|
|
|
|
gint scanline,
|
|
|
|
gint empty_segs[],
|
|
|
|
gint max_empty,
|
|
|
|
gint *num_empty,
|
|
|
|
BoundaryType type,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
guchar threshold);
|
|
|
|
static void process_horiz_seg (Boundary *boundary,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
gboolean open);
|
|
|
|
static void make_horiz_segs (Boundary *boundary,
|
|
|
|
gint start,
|
|
|
|
gint end,
|
|
|
|
gint scanline,
|
|
|
|
gint empty[],
|
|
|
|
gint num_empty,
|
|
|
|
gint top);
|
|
|
|
static Boundary * generate_boundary (PixelRegion *PR,
|
|
|
|
BoundaryType type,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
guchar threshold);
|
|
|
|
|
|
|
|
static gint find_segment (const BoundSeg *segs,
|
|
|
|
gint num_segs,
|
|
|
|
gint x,
|
|
|
|
gint y);
|
|
|
|
|
|
|
|
static void simplify_subdivide (const BoundSeg *segs,
|
|
|
|
gint start_idx,
|
|
|
|
gint end_idx,
|
|
|
|
GArray **ret_points);
|
|
|
|
|
|
|
|
|
|
|
|
/* public functions */
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
/**
|
|
|
|
* boundary_find:
|
|
|
|
* @maskPR: any PixelRegion
|
|
|
|
* @type: type of bounds
|
|
|
|
* @x1: left side of bounds
|
|
|
|
* @y1: top side of bounds
|
|
|
|
* @x2: right side of bounds
|
|
|
|
* @y2: botton side of bounds
|
|
|
|
* @threshold: pixel value of boundary line
|
|
|
|
* @num_segs: number of returned #BoundSeg's
|
|
|
|
*
|
|
|
|
* This function returns an array of #BoundSeg's which describe all
|
|
|
|
* outlines along pixel value @threahold, optionally within specified
|
|
|
|
* bounds instead of the whole region.
|
|
|
|
*
|
|
|
|
* The @maskPR paramater can be any PixelRegion. If the region has
|
|
|
|
* more than 1 bytes/pixel, the last byte of each pixel is used to
|
|
|
|
* determine the boundary outline.
|
|
|
|
*
|
|
|
|
* Return value: the boundary array.
|
|
|
|
**/
|
2005-08-20 23:46:37 +08:00
|
|
|
BoundSeg *
|
|
|
|
boundary_find (PixelRegion *maskPR,
|
|
|
|
BoundaryType type,
|
|
|
|
int x1,
|
|
|
|
int y1,
|
|
|
|
int x2,
|
|
|
|
int y2,
|
|
|
|
guchar threshold,
|
|
|
|
int *num_segs)
|
|
|
|
{
|
|
|
|
Boundary *boundary;
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
g_return_val_if_fail (maskPR != NULL, NULL);
|
|
|
|
g_return_val_if_fail (num_segs != NULL, NULL);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
boundary = generate_boundary (maskPR, type, x1, y1, x2, y2, threshold);
|
|
|
|
|
|
|
|
*num_segs = boundary->num_segs;
|
|
|
|
|
|
|
|
return boundary_free (boundary, FALSE);
|
|
|
|
}
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
/**
|
|
|
|
* boundary_sort:
|
|
|
|
* @segs: unsorted input segs.
|
|
|
|
* @num_segs: number of input segs
|
|
|
|
* @num_groups: number of groups in the sorted segs
|
|
|
|
*
|
|
|
|
* This function takes an array of #BoundSeg's as returned by
|
|
|
|
* boundary_find() and sorts it by contiguous groups. The returned
|
|
|
|
* array contains markers consisting of -1 coordinates and is
|
|
|
|
* @num_groups elements longer than @segs.
|
|
|
|
*
|
|
|
|
* Return value: the sorted segs
|
|
|
|
**/
|
2005-08-20 23:46:37 +08:00
|
|
|
BoundSeg *
|
|
|
|
boundary_sort (const BoundSeg *segs,
|
2006-04-12 20:49:29 +08:00
|
|
|
gint num_segs,
|
|
|
|
gint *num_groups)
|
2005-08-20 23:46:37 +08:00
|
|
|
{
|
|
|
|
Boundary *boundary;
|
|
|
|
gint i;
|
|
|
|
gint index;
|
|
|
|
gint x, y;
|
|
|
|
gint startx, starty;
|
2005-08-21 01:25:19 +08:00
|
|
|
gboolean empty;
|
2005-08-20 23:46:37 +08:00
|
|
|
BoundSeg *new_segs;
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
g_return_val_if_fail ((segs == NULL && num_segs == 0) ||
|
|
|
|
(segs != NULL && num_segs > 0), NULL);
|
|
|
|
g_return_val_if_fail (num_groups != NULL, NULL);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
*num_groups = 0;
|
|
|
|
|
|
|
|
if (num_segs == 0)
|
|
|
|
return NULL;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
for (i = 0; i < num_segs; i++)
|
|
|
|
((BoundSeg *) segs)[i].visited = FALSE;
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
boundary = boundary_new (NULL);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
index = 0;
|
|
|
|
new_segs = NULL;
|
|
|
|
empty = FALSE;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
while (! empty)
|
|
|
|
{
|
|
|
|
empty = TRUE;
|
|
|
|
|
|
|
|
/* find the index of a non-visited segment to start a group */
|
|
|
|
for (i = 0; i < num_segs; i++)
|
2006-04-12 20:49:29 +08:00
|
|
|
if (segs[i].visited == FALSE)
|
|
|
|
{
|
|
|
|
index = i;
|
|
|
|
empty = FALSE;
|
|
|
|
i = num_segs;
|
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
if (! empty)
|
2006-04-12 20:49:29 +08:00
|
|
|
{
|
|
|
|
boundary_add_seg (boundary,
|
2005-08-21 01:25:19 +08:00
|
|
|
segs[index].x1, segs[index].y1,
|
|
|
|
segs[index].x2, segs[index].y2,
|
|
|
|
segs[index].open);
|
|
|
|
|
2006-04-12 20:49:29 +08:00
|
|
|
((BoundSeg *) segs)[index].visited = TRUE;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2006-04-12 20:49:29 +08:00
|
|
|
startx = segs[index].x1;
|
|
|
|
starty = segs[index].y1;
|
|
|
|
x = segs[index].x2;
|
|
|
|
y = segs[index].y2;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2006-04-12 20:49:29 +08:00
|
|
|
while ((index = find_segment (segs, num_segs, x, y)) != -1)
|
|
|
|
{
|
|
|
|
/* make sure ordering is correct */
|
|
|
|
if (x == segs[index].x1 && y == segs[index].y1)
|
|
|
|
{
|
|
|
|
boundary_add_seg (boundary,
|
2005-08-21 01:25:19 +08:00
|
|
|
segs[index].x1, segs[index].y1,
|
|
|
|
segs[index].x2, segs[index].y2,
|
|
|
|
segs[index].open);
|
2006-04-12 20:49:29 +08:00
|
|
|
x = segs[index].x2;
|
|
|
|
y = segs[index].y2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
boundary_add_seg (boundary,
|
2005-08-21 01:25:19 +08:00
|
|
|
segs[index].x2, segs[index].y2,
|
|
|
|
segs[index].x1, segs[index].y1,
|
|
|
|
segs[index].open);
|
2006-04-12 20:49:29 +08:00
|
|
|
x = segs[index].x1;
|
|
|
|
y = segs[index].y1;
|
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2006-04-12 20:49:29 +08:00
|
|
|
((BoundSeg *) segs)[index].visited = TRUE;
|
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2006-07-06 20:27:32 +08:00
|
|
|
if (G_UNLIKELY (x != startx || y != starty))
|
2006-04-12 20:49:29 +08:00
|
|
|
g_warning ("sort_boundary(): Unconnected boundary group!");
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2006-04-12 20:49:29 +08:00
|
|
|
/* Mark the end of a group */
|
|
|
|
*num_groups = *num_groups + 1;
|
|
|
|
boundary_add_seg (boundary, -1, -1, -1, -1, 0);
|
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return boundary_free (boundary, FALSE);
|
|
|
|
}
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
/**
|
|
|
|
* boundary_simplify:
|
|
|
|
* @sorted_segs: sorted input segs
|
|
|
|
* @num_groups: number of groups in the sorted segs
|
|
|
|
* @num_segs: number of returned segs.
|
|
|
|
*
|
|
|
|
* This function takes an array of #BoundSeg's which has been sorted
|
|
|
|
* with boundary_sort() and reduces the number of segments while
|
|
|
|
* preserving the general shape as close as possible.
|
|
|
|
*
|
|
|
|
* Return value: the simplified segs.
|
|
|
|
**/
|
2005-08-20 23:46:37 +08:00
|
|
|
BoundSeg *
|
2005-08-21 01:25:19 +08:00
|
|
|
boundary_simplify (BoundSeg *sorted_segs,
|
2005-08-20 23:46:37 +08:00
|
|
|
gint num_groups,
|
|
|
|
gint *num_segs)
|
|
|
|
{
|
2005-08-21 01:25:19 +08:00
|
|
|
GArray *new_bounds;
|
|
|
|
gint i, seg;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
g_return_val_if_fail ((sorted_segs == NULL && num_groups == 0) ||
|
|
|
|
(sorted_segs != NULL && num_groups > 0), NULL);
|
2005-08-20 23:46:37 +08:00
|
|
|
g_return_val_if_fail (num_segs != NULL, NULL);
|
|
|
|
|
|
|
|
new_bounds = g_array_new (FALSE, FALSE, sizeof (BoundSeg));
|
|
|
|
|
|
|
|
seg = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < num_groups; i++)
|
|
|
|
{
|
2005-08-21 01:25:19 +08:00
|
|
|
gint start = seg;
|
|
|
|
gint n_points = 0;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
while (sorted_segs[seg].x1 != -1 ||
|
|
|
|
sorted_segs[seg].x2 != -1 ||
|
|
|
|
sorted_segs[seg].y1 != -1 ||
|
|
|
|
sorted_segs[seg].y2 != -1)
|
2005-08-20 23:46:37 +08:00
|
|
|
{
|
|
|
|
n_points++;
|
|
|
|
seg++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n_points > 0)
|
|
|
|
{
|
2005-08-21 01:25:19 +08:00
|
|
|
GArray *tmp_points;
|
|
|
|
BoundSeg tmp_seg;
|
|
|
|
gint j;
|
|
|
|
|
|
|
|
tmp_points = g_array_new (FALSE, FALSE, sizeof (gint));
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
/* temporarily use the delimiter to close the polygon */
|
2005-08-21 01:25:19 +08:00
|
|
|
tmp_seg = sorted_segs[seg];
|
|
|
|
sorted_segs[seg] = sorted_segs[start];
|
|
|
|
simplify_subdivide (sorted_segs, start, start + n_points,
|
|
|
|
&tmp_points);
|
|
|
|
sorted_segs[seg] = tmp_seg;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
for (j = 0; j < tmp_points->len; j++)
|
2005-08-20 23:46:37 +08:00
|
|
|
g_array_append_val (new_bounds,
|
2005-08-21 01:25:19 +08:00
|
|
|
sorted_segs[g_array_index (tmp_points,
|
|
|
|
gint, j)]);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
g_array_append_val (new_bounds, sorted_segs[seg]);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
g_array_free (tmp_points, TRUE);
|
2005-08-20 23:46:37 +08:00
|
|
|
}
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
seg++;
|
2005-08-20 23:46:37 +08:00
|
|
|
}
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
*num_segs = new_bounds->len;
|
2005-08-20 23:46:37 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
return (BoundSeg *) g_array_free (new_bounds, FALSE);
|
2005-08-20 23:46:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* private functions */
|
|
|
|
|
|
|
|
static Boundary *
|
|
|
|
boundary_new (PixelRegion *PR)
|
2005-08-20 20:35:54 +08:00
|
|
|
{
|
2007-05-22 18:43:48 +08:00
|
|
|
Boundary *boundary = g_slice_new0 (Boundary);
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
if (PR)
|
|
|
|
{
|
|
|
|
gint i;
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
/* array for determining the vertical line segments
|
|
|
|
* which must be drawn
|
|
|
|
*/
|
|
|
|
boundary->vert_segs = g_new (gint, PR->w + PR->x + 1);
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
for (i = 0; i <= (PR->w + PR->x); i++)
|
|
|
|
boundary->vert_segs[i] = -1;
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
/* find the maximum possible number of empty segments
|
|
|
|
* given the current mask
|
|
|
|
*/
|
|
|
|
boundary->max_empty_segs = PR->w + 3;
|
|
|
|
|
|
|
|
boundary->empty_segs_n = g_new (gint, boundary->max_empty_segs);
|
|
|
|
boundary->empty_segs_c = g_new (gint, boundary->max_empty_segs);
|
|
|
|
boundary->empty_segs_l = g_new (gint, boundary->max_empty_segs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return boundary;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BoundSeg *
|
|
|
|
boundary_free (Boundary *boundary,
|
|
|
|
gboolean free_segs)
|
|
|
|
{
|
|
|
|
BoundSeg *segs = NULL;
|
|
|
|
|
|
|
|
if (free_segs)
|
|
|
|
g_free (boundary->segs);
|
|
|
|
else
|
|
|
|
segs = boundary->segs;
|
|
|
|
|
|
|
|
g_free (boundary->vert_segs);
|
|
|
|
g_free (boundary->empty_segs_n);
|
|
|
|
g_free (boundary->empty_segs_c);
|
|
|
|
g_free (boundary->empty_segs_l);
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2007-05-22 18:43:48 +08:00
|
|
|
g_slice_free (Boundary, boundary);
|
2005-08-20 23:46:37 +08:00
|
|
|
|
|
|
|
return segs;
|
2005-08-20 20:35:54 +08:00
|
|
|
}
|
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
static void
|
|
|
|
boundary_add_seg (Boundary *boundary,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
gboolean open)
|
|
|
|
{
|
|
|
|
if (boundary->num_segs >= boundary->max_segs)
|
|
|
|
{
|
|
|
|
boundary->max_segs += MAX_SEGS_INC;
|
|
|
|
|
|
|
|
boundary->segs = g_renew (BoundSeg, boundary->segs, boundary->max_segs);
|
|
|
|
}
|
|
|
|
|
|
|
|
boundary->segs[boundary->num_segs].x1 = x1;
|
|
|
|
boundary->segs[boundary->num_segs].y1 = y1;
|
|
|
|
boundary->segs[boundary->num_segs].x2 = x2;
|
|
|
|
boundary->segs[boundary->num_segs].y2 = y2;
|
|
|
|
boundary->segs[boundary->num_segs].open = open;
|
|
|
|
boundary->num_segs ++;
|
|
|
|
}
|
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
static void
|
|
|
|
find_empty_segs (PixelRegion *maskPR,
|
2006-04-12 20:49:29 +08:00
|
|
|
gint scanline,
|
|
|
|
gint empty_segs[],
|
|
|
|
gint max_empty,
|
|
|
|
gint *num_empty,
|
|
|
|
BoundaryType type,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
2003-07-10 19:59:38 +08:00
|
|
|
guchar threshold)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2006-07-06 19:13:26 +08:00
|
|
|
const guchar *data = NULL;
|
|
|
|
Tile *tile = NULL;
|
|
|
|
gint start = 0;
|
|
|
|
gint end = 0;
|
|
|
|
gint endx = 0;
|
|
|
|
gint dstep = 0;
|
|
|
|
gint val, last;
|
|
|
|
gint x, tilex;
|
|
|
|
gint l_num_empty;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
*num_empty = 0;
|
|
|
|
|
|
|
|
if (scanline < maskPR->y || scanline >= (maskPR->y + maskPR->h))
|
|
|
|
{
|
|
|
|
empty_segs[(*num_empty)++] = 0;
|
|
|
|
empty_segs[(*num_empty)++] = G_MAXINT;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
if (type == BOUNDARY_WITHIN_BOUNDS)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
|
|
|
if (scanline < y1 || scanline >= y2)
|
2006-04-12 20:49:29 +08:00
|
|
|
{
|
|
|
|
empty_segs[(*num_empty)++] = 0;
|
|
|
|
empty_segs[(*num_empty)++] = G_MAXINT;
|
|
|
|
return;
|
|
|
|
}
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
start = x1;
|
2003-07-10 19:59:38 +08:00
|
|
|
end = x2;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
else if (type == BOUNDARY_IGNORE_BOUNDS)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
|
|
|
start = maskPR->x;
|
2003-07-10 19:59:38 +08:00
|
|
|
end = maskPR->x + maskPR->w;
|
1997-11-25 06:05:25 +08:00
|
|
|
if (scanline < y1 || scanline >= y2)
|
2006-04-12 20:49:29 +08:00
|
|
|
x2 = -1;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
tilex = -1;
|
|
|
|
empty_segs[(*num_empty)++] = 0;
|
|
|
|
last = -1;
|
|
|
|
|
1999-04-09 14:00:11 +08:00
|
|
|
l_num_empty = *num_empty;
|
|
|
|
|
2003-07-10 19:59:38 +08:00
|
|
|
if (! maskPR->tiles)
|
|
|
|
{
|
|
|
|
data = maskPR->data + scanline * maskPR->rowstride;
|
|
|
|
dstep = maskPR->bytes;
|
|
|
|
|
|
|
|
endx = end;
|
|
|
|
}
|
|
|
|
|
1999-04-09 14:00:11 +08:00
|
|
|
for (x = start; x < end;)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
|
|
|
/* Check to see if we must advance to next tile */
|
2003-07-10 19:59:38 +08:00
|
|
|
if (maskPR->tiles)
|
|
|
|
{
|
|
|
|
if ((x / TILE_WIDTH) != tilex)
|
|
|
|
{
|
|
|
|
if (tile)
|
|
|
|
tile_release (tile, FALSE);
|
2006-07-06 19:13:26 +08:00
|
|
|
|
2003-07-11 18:00:24 +08:00
|
|
|
tile = tile_manager_get_tile (maskPR->tiles,
|
2006-04-12 20:49:29 +08:00
|
|
|
x, scanline, TRUE, FALSE);
|
2003-07-11 18:00:24 +08:00
|
|
|
data =
|
2006-07-06 19:13:26 +08:00
|
|
|
(const guchar *) tile_data_pointer (tile,
|
|
|
|
x % TILE_WIDTH,
|
|
|
|
scanline % TILE_HEIGHT) +
|
2006-04-12 20:49:29 +08:00
|
|
|
(tile_bpp(tile) - 1);
|
2003-07-10 19:59:38 +08:00
|
|
|
|
|
|
|
tilex = x / TILE_WIDTH;
|
|
|
|
dstep = tile_bpp (tile);
|
|
|
|
}
|
|
|
|
|
|
|
|
endx = x + (TILE_WIDTH - (x%TILE_WIDTH));
|
|
|
|
endx = MIN (end, endx);
|
|
|
|
}
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
if (type == BOUNDARY_IGNORE_BOUNDS && (endx > x1 || x < x2))
|
2006-04-12 20:49:29 +08:00
|
|
|
{
|
|
|
|
for (; x < endx; x++)
|
|
|
|
{
|
|
|
|
if (*data > threshold)
|
|
|
|
if (x >= x1 && x < x2)
|
|
|
|
val = -1;
|
|
|
|
else
|
|
|
|
val = 1;
|
|
|
|
else
|
|
|
|
val = -1;
|
|
|
|
|
|
|
|
data += dstep;
|
|
|
|
|
|
|
|
if (last != val)
|
|
|
|
empty_segs[l_num_empty++] = x;
|
|
|
|
|
|
|
|
last = val;
|
|
|
|
}
|
|
|
|
}
|
1999-04-09 14:00:11 +08:00
|
|
|
else
|
2006-04-12 20:49:29 +08:00
|
|
|
{
|
|
|
|
for (; x < endx; x++)
|
|
|
|
{
|
|
|
|
if (*data > threshold)
|
|
|
|
val = 1;
|
|
|
|
else
|
|
|
|
val = -1;
|
|
|
|
|
|
|
|
data += dstep;
|
|
|
|
|
|
|
|
if (last != val)
|
|
|
|
empty_segs[l_num_empty++] = x;
|
|
|
|
|
|
|
|
last = val;
|
|
|
|
}
|
|
|
|
}
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
2005-08-21 01:25:19 +08:00
|
|
|
|
1999-04-09 14:00:11 +08:00
|
|
|
*num_empty = l_num_empty;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
if (last > 0)
|
|
|
|
empty_segs[(*num_empty)++] = x;
|
|
|
|
|
|
|
|
empty_segs[(*num_empty)++] = G_MAXINT;
|
|
|
|
|
|
|
|
if (tile)
|
1998-07-10 10:43:12 +08:00
|
|
|
tile_release (tile, FALSE);
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2005-08-20 20:35:54 +08:00
|
|
|
process_horiz_seg (Boundary *boundary,
|
|
|
|
gint x1,
|
2006-04-12 20:49:29 +08:00
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
|
|
|
gboolean open)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
|
|
|
/* This procedure accounts for any vertical segments that must be
|
|
|
|
drawn to close in the horizontal segments. */
|
|
|
|
|
2005-08-20 20:35:54 +08:00
|
|
|
if (boundary->vert_segs[x1] >= 0)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2005-08-21 01:25:19 +08:00
|
|
|
boundary_add_seg (boundary, x1, boundary->vert_segs[x1], x1, y1, !open);
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->vert_segs[x1] = -1;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
else
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->vert_segs[x1] = y1;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-20 20:35:54 +08:00
|
|
|
if (boundary->vert_segs[x2] >= 0)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2005-08-21 01:25:19 +08:00
|
|
|
boundary_add_seg (boundary, x2, boundary->vert_segs[x2], x2, y2, open);
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->vert_segs[x2] = -1;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
else
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->vert_segs[x2] = y2;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-21 01:25:19 +08:00
|
|
|
boundary_add_seg (boundary, x1, y1, x2, y2, open);
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2005-08-20 20:35:54 +08:00
|
|
|
make_horiz_segs (Boundary *boundary,
|
|
|
|
gint start,
|
2006-04-12 20:49:29 +08:00
|
|
|
gint end,
|
|
|
|
gint scanline,
|
|
|
|
gint empty[],
|
|
|
|
gint num_empty,
|
|
|
|
gint top)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2000-12-28 10:01:16 +08:00
|
|
|
gint empty_index;
|
|
|
|
gint e_s, e_e; /* empty segment start and end values */
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
for (empty_index = 0; empty_index < num_empty; empty_index += 2)
|
|
|
|
{
|
|
|
|
e_s = *empty++;
|
|
|
|
e_e = *empty++;
|
2005-08-20 20:35:54 +08:00
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
if (e_s <= start && e_e >= end)
|
2006-04-12 20:49:29 +08:00
|
|
|
process_horiz_seg (boundary,
|
2005-08-20 20:35:54 +08:00
|
|
|
start, scanline, end, scanline, top);
|
1997-11-25 06:05:25 +08:00
|
|
|
else if ((e_s > start && e_s < end) ||
|
2006-04-12 20:49:29 +08:00
|
|
|
(e_e < end && e_e > start))
|
|
|
|
process_horiz_seg (boundary,
|
2005-08-20 20:35:54 +08:00
|
|
|
MAX (e_s, start), scanline,
|
2006-04-12 20:49:29 +08:00
|
|
|
MIN (e_e, end), scanline, top);
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
static Boundary *
|
|
|
|
generate_boundary (PixelRegion *PR,
|
2006-04-12 20:49:29 +08:00
|
|
|
BoundaryType type,
|
|
|
|
gint x1,
|
|
|
|
gint y1,
|
|
|
|
gint x2,
|
|
|
|
gint y2,
|
2003-07-10 19:59:38 +08:00
|
|
|
guchar threshold)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2005-08-20 23:46:37 +08:00
|
|
|
Boundary *boundary;
|
|
|
|
gint scanline;
|
|
|
|
gint i;
|
|
|
|
gint start, end;
|
|
|
|
gint *tmp_segs;
|
|
|
|
|
|
|
|
gint num_empty_n = 0;
|
|
|
|
gint num_empty_c = 0;
|
|
|
|
gint num_empty_l = 0;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
boundary = boundary_new (PR);
|
2001-07-09 05:44:52 +08:00
|
|
|
|
1997-11-25 06:05:25 +08:00
|
|
|
start = 0;
|
|
|
|
end = 0;
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
if (type == BOUNDARY_WITHIN_BOUNDS)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
|
|
|
start = y1;
|
2005-08-20 20:35:54 +08:00
|
|
|
end = y2;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
2005-08-20 23:46:37 +08:00
|
|
|
else if (type == BOUNDARY_IGNORE_BOUNDS)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2001-07-09 05:44:52 +08:00
|
|
|
start = PR->y;
|
|
|
|
end = PR->y + PR->h;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the empty segments for the previous and current scanlines */
|
2005-08-20 20:35:54 +08:00
|
|
|
find_empty_segs (PR, start - 1, boundary->empty_segs_l,
|
2006-04-12 20:49:29 +08:00
|
|
|
boundary->max_empty_segs, &num_empty_l,
|
|
|
|
type, x1, y1, x2, y2,
|
2003-07-10 19:59:38 +08:00
|
|
|
threshold);
|
2005-08-20 20:35:54 +08:00
|
|
|
find_empty_segs (PR, start, boundary->empty_segs_c,
|
2006-04-12 20:49:29 +08:00
|
|
|
boundary->max_empty_segs, &num_empty_c,
|
|
|
|
type, x1, y1, x2, y2,
|
2003-07-10 19:59:38 +08:00
|
|
|
threshold);
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
for (scanline = start; scanline < end; scanline++)
|
|
|
|
{
|
|
|
|
/* find the empty segment list for the next scanline */
|
2005-08-20 20:35:54 +08:00
|
|
|
find_empty_segs (PR, scanline + 1, boundary->empty_segs_n,
|
2006-04-12 20:49:29 +08:00
|
|
|
boundary->max_empty_segs, &num_empty_n,
|
|
|
|
type, x1, y1, x2, y2,
|
2003-07-10 19:59:38 +08:00
|
|
|
threshold);
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
/* process the segments on the current scanline */
|
|
|
|
for (i = 1; i < num_empty_c - 1; i += 2)
|
2006-04-12 20:49:29 +08:00
|
|
|
{
|
|
|
|
make_horiz_segs (boundary,
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->empty_segs_c [i],
|
|
|
|
boundary->empty_segs_c [i+1],
|
2006-04-12 20:49:29 +08:00
|
|
|
scanline, boundary->empty_segs_l, num_empty_l, 1);
|
|
|
|
make_horiz_segs (boundary,
|
2005-08-20 20:35:54 +08:00
|
|
|
boundary->empty_segs_c [i],
|
|
|
|
boundary->empty_segs_c [i+1],
|
2006-04-12 20:49:29 +08:00
|
|
|
scanline + 1, boundary->empty_segs_n, num_empty_n, 0);
|
|
|
|
}
|
1997-11-25 06:05:25 +08:00
|
|
|
|
|
|
|
/* get the next scanline of empty segments, swap others */
|
2005-08-20 20:35:54 +08:00
|
|
|
tmp_segs = boundary->empty_segs_l;
|
|
|
|
boundary->empty_segs_l = boundary->empty_segs_c;
|
|
|
|
num_empty_l = num_empty_c;
|
|
|
|
boundary->empty_segs_c = boundary->empty_segs_n;
|
|
|
|
num_empty_c = num_empty_n;
|
|
|
|
boundary->empty_segs_n = tmp_segs;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
return boundary;
|
1997-11-25 06:05:25 +08:00
|
|
|
}
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
/* sorting utility functions */
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2003-08-30 22:25:05 +08:00
|
|
|
static gint
|
|
|
|
find_segment (const BoundSeg *segs,
|
2006-04-12 20:49:29 +08:00
|
|
|
gint num_segs,
|
|
|
|
gint x,
|
|
|
|
gint y)
|
1997-11-25 06:05:25 +08:00
|
|
|
{
|
2000-12-28 10:01:16 +08:00
|
|
|
gint index;
|
1997-11-25 06:05:25 +08:00
|
|
|
|
2005-08-20 20:44:01 +08:00
|
|
|
for (index = 0; index < num_segs; index++)
|
2003-08-30 22:25:05 +08:00
|
|
|
if (((segs[index].x1 == x && segs[index].y1 == y) ||
|
|
|
|
(segs[index].x2 == x && segs[index].y2 == y)) &&
|
2006-04-12 20:49:29 +08:00
|
|
|
segs[index].visited == FALSE)
|
1997-11-25 06:05:25 +08:00
|
|
|
return index;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2005-08-20 23:46:37 +08:00
|
|
|
/* simplifying utility functions */
|
2003-10-01 08:02:48 +08:00
|
|
|
|
|
|
|
static void
|
2005-08-21 01:25:19 +08:00
|
|
|
simplify_subdivide (const BoundSeg *segs,
|
|
|
|
gint start_idx,
|
|
|
|
gint end_idx,
|
|
|
|
GArray **ret_points)
|
2003-10-01 08:02:48 +08:00
|
|
|
{
|
|
|
|
gint maxdist_idx;
|
|
|
|
gint dist, maxdist;
|
|
|
|
gint i, dx, dy;
|
|
|
|
gdouble realdist;
|
|
|
|
|
|
|
|
/* g_printerr ("subdiv %d - %d\n", start_idx, end_idx); */
|
|
|
|
|
|
|
|
if (end_idx - start_idx < 2)
|
|
|
|
{
|
|
|
|
*ret_points = g_array_append_val (*ret_points, start_idx);
|
|
|
|
/* g_printerr (" %d\n", start_idx); */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
maxdist = 0;
|
|
|
|
maxdist_idx = -1;
|
|
|
|
|
|
|
|
if (segs[start_idx].x1 == segs[end_idx].x1 &&
|
|
|
|
segs[start_idx].y1 == segs[end_idx].y1)
|
|
|
|
{
|
|
|
|
/* start and endpoint are at the same coordinates */
|
|
|
|
for (i = start_idx + 1; i < end_idx; i++)
|
|
|
|
{
|
|
|
|
/* compare the sqared distances */
|
|
|
|
dist = (SQR (segs[i].x1 - segs[start_idx].x1) +
|
|
|
|
SQR (segs[i].y1 - segs[start_idx].y1));
|
2006-07-06 19:13:26 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
if (dist > maxdist)
|
|
|
|
{
|
|
|
|
maxdist = dist;
|
|
|
|
maxdist_idx = i;
|
|
|
|
}
|
|
|
|
}
|
2006-07-06 19:13:26 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
realdist = sqrt ((gdouble) maxdist);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dx = segs[end_idx].x1 - segs[start_idx].x1;
|
|
|
|
dy = segs[end_idx].y1 - segs[start_idx].y1;
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
/* g_printerr ("dx: %d, dy: %d\n", dx, dy); */
|
|
|
|
|
|
|
|
for (i = start_idx + 1; i < end_idx; i++)
|
|
|
|
{
|
|
|
|
/* this is not really the euclidic distance, but is
|
|
|
|
* proportional for this part of the line
|
|
|
|
* (for the real distance we'd have to divide by
|
|
|
|
* (SQR(dx)+SQR(dy)))
|
|
|
|
*/
|
|
|
|
dist = (dx * (segs[start_idx].y1 - segs[i].y1) -
|
|
|
|
dy * (segs[start_idx].x1 - segs[i].x1));
|
|
|
|
|
|
|
|
if (dist < 0)
|
|
|
|
dist *= -1;
|
|
|
|
|
|
|
|
if (dist > maxdist)
|
|
|
|
{
|
|
|
|
maxdist = dist;
|
|
|
|
maxdist_idx = i;
|
|
|
|
}
|
|
|
|
}
|
2006-07-06 19:13:26 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
realdist = ((gdouble) maxdist) / sqrt ((gdouble) (SQR (dx) + SQR (dy)));
|
|
|
|
}
|
2005-08-20 20:35:54 +08:00
|
|
|
|
2003-10-01 08:02:48 +08:00
|
|
|
/* g_printerr ("Index %d, x: %d, y: %d, distance: %.4f\n", maxdist_idx,
|
|
|
|
segs[maxdist_idx].x1, segs[maxdist_idx].y1, realdist); */
|
|
|
|
|
2006-07-06 19:13:26 +08:00
|
|
|
/* threshold is chosen to catch 45 degree stairs */
|
2003-10-01 08:02:48 +08:00
|
|
|
if (realdist <= 1.0)
|
|
|
|
{
|
|
|
|
*ret_points = g_array_append_val (*ret_points, start_idx);
|
|
|
|
/* g_printerr (" %d\n", start_idx); */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Simons hack */
|
|
|
|
maxdist_idx = (start_idx + end_idx) / 2;
|
|
|
|
|
|
|
|
simplify_subdivide (segs, start_idx, maxdist_idx, ret_points);
|
|
|
|
simplify_subdivide (segs, maxdist_idx, end_idx, ret_points);
|
|
|
|
}
|