mirror of https://github.com/GNOME/gimp.git
353 lines
7.9 KiB
C
353 lines
7.9 KiB
C
/* The GIMP -- an image manipulation program
|
|
* Copyright (C) 1995-1999 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.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/pixel-region.h"
|
|
|
|
#include "gimpchannel.h"
|
|
#include "gimpimage.h"
|
|
#include "gimpscanconvert.h"
|
|
|
|
|
|
#ifdef DEBUG
|
|
#define TRC(x) printf x
|
|
#else
|
|
#define TRC(x)
|
|
#endif
|
|
|
|
|
|
struct _GimpScanConvert
|
|
{
|
|
guint width;
|
|
guint height;
|
|
GSList **scanlines; /* array of height*antialias scanlines */
|
|
|
|
guint antialias; /* how much to oversample by */
|
|
|
|
/* record the first and last points so we can close the curve */
|
|
gboolean got_first;
|
|
GimpVector2 first;
|
|
gboolean got_last;
|
|
GimpVector2 last;
|
|
};
|
|
|
|
|
|
/* Local helper routines to scan convert the polygon */
|
|
|
|
static GSList *
|
|
insert_into_sorted_list (GSList *list,
|
|
gint x)
|
|
{
|
|
GSList *orig = list;
|
|
GSList *rest;
|
|
|
|
if (!list)
|
|
return g_slist_prepend (list, GINT_TO_POINTER (x));
|
|
|
|
while (list)
|
|
{
|
|
rest = g_slist_next (list);
|
|
if (x < GPOINTER_TO_INT (list->data))
|
|
{
|
|
rest = g_slist_prepend (rest, list->data);
|
|
list->next = rest;
|
|
list->data = GINT_TO_POINTER (x);
|
|
return orig;
|
|
}
|
|
else if (!rest)
|
|
{
|
|
g_slist_append (list, GINT_TO_POINTER (x));
|
|
return orig;
|
|
}
|
|
list = g_slist_next (list);
|
|
}
|
|
|
|
return orig;
|
|
}
|
|
|
|
|
|
static void
|
|
convert_segment (GimpScanConvert *sc,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
{
|
|
gint ydiff, y, tmp;
|
|
gint width;
|
|
gint height;
|
|
GSList **scanlines;
|
|
gfloat xinc, xstart;
|
|
|
|
/* pre-calculate invariant commonly used values */
|
|
width = sc->width * sc->antialias;
|
|
height = sc->height * sc->antialias;
|
|
scanlines = sc->scanlines;
|
|
|
|
x1 = CLAMP (x1, 0, width - 1);
|
|
y1 = CLAMP (y1, 0, height - 1);
|
|
x2 = CLAMP (x2, 0, width - 1);
|
|
y2 = CLAMP (y2, 0, height - 1);
|
|
|
|
if (y1 > y2)
|
|
{
|
|
tmp = y2; y2 = y1; y1 = tmp;
|
|
tmp = x2; x2 = x1; x1 = tmp;
|
|
}
|
|
|
|
ydiff = (y2 - y1);
|
|
|
|
if (ydiff)
|
|
{
|
|
xinc = (float) (x2 - x1) / (float) ydiff;
|
|
xstart = x1 + 0.5 * xinc;
|
|
for (y = y1 ; y < y2; y++)
|
|
{
|
|
scanlines[y] = insert_into_sorted_list (scanlines[y], ROUND (xstart));
|
|
xstart += xinc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* horizontal line */
|
|
scanlines[y1] = insert_into_sorted_list (scanlines[y1], ROUND (x1));
|
|
scanlines[y1] = insert_into_sorted_list (scanlines[y1], ROUND (x2));
|
|
}
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
GimpScanConvert *
|
|
gimp_scan_convert_new (guint width,
|
|
guint height,
|
|
guint antialias)
|
|
{
|
|
GimpScanConvert *sc;
|
|
|
|
g_return_val_if_fail (width > 0, NULL);
|
|
g_return_val_if_fail (height > 0, NULL);
|
|
g_return_val_if_fail (antialias > 0, NULL);
|
|
|
|
sc = g_new0 (GimpScanConvert, 1);
|
|
|
|
sc->antialias = antialias;
|
|
sc->width = width;
|
|
sc->height = height;
|
|
sc->scanlines = g_new0 (GSList *, height * antialias);
|
|
|
|
return sc;
|
|
}
|
|
|
|
void
|
|
gimp_scan_convert_free (GimpScanConvert *sc)
|
|
{
|
|
g_free (sc->scanlines);
|
|
g_free (sc);
|
|
}
|
|
|
|
/* Add "n_points" from "points" to the polygon currently being
|
|
* described by "scan_converter".
|
|
*/
|
|
void
|
|
gimp_scan_convert_add_points (GimpScanConvert *sc,
|
|
guint n_points,
|
|
GimpVector2 *points)
|
|
{
|
|
gint i;
|
|
guint antialias;
|
|
|
|
g_return_if_fail (sc != NULL);
|
|
g_return_if_fail (points != NULL);
|
|
|
|
antialias = sc->antialias;
|
|
|
|
if (!sc->got_first && n_points > 0)
|
|
{
|
|
sc->got_first = TRUE;
|
|
sc->first = points[0];
|
|
}
|
|
|
|
/* link from previous point */
|
|
if (sc->got_last && n_points > 0)
|
|
{
|
|
TRC (("|| %g,%g -> %g,%g\n",
|
|
sc->last.x, sc->last.y,
|
|
points[0].x, points[0].y));
|
|
convert_segment (sc,
|
|
(gint) sc->last.x * antialias,
|
|
(gint) sc->last.y * antialias,
|
|
(gint) points[0].x * antialias,
|
|
(gint) points[0].y * antialias);
|
|
}
|
|
|
|
for (i = 0; i < (n_points - 1); i++)
|
|
{
|
|
convert_segment (sc,
|
|
(gint) points[i].x * antialias,
|
|
(gint) points[i].y * antialias,
|
|
(gint) points[i + 1].x * antialias,
|
|
(gint) points[i + 1].y * antialias);
|
|
}
|
|
|
|
TRC (("[] %g,%g -> %g,%g\n",
|
|
points[0].x, points[0].y,
|
|
points[n_points-1].x, points[n_points-1].y));
|
|
|
|
if (n_points > 0)
|
|
{
|
|
sc->got_last = TRUE;
|
|
sc->last = points[n_points - 1];
|
|
}
|
|
}
|
|
|
|
|
|
/* Scan convert the polygon described by the list of points passed to
|
|
* scan_convert_add_points, and return a channel with a bits set if
|
|
* they fall within the polygon defined. The polygon is filled
|
|
* according to the even-odd rule. The polygon is closed by
|
|
* joining the final point to the initial point.
|
|
*/
|
|
GimpChannel *
|
|
gimp_scan_convert_to_channel (GimpScanConvert *sc,
|
|
GimpImage *gimage)
|
|
{
|
|
GimpChannel *mask;
|
|
GSList *list;
|
|
PixelRegion maskPR;
|
|
guint widtha;
|
|
guint heighta;
|
|
guint antialias;
|
|
guint antialias2;
|
|
guchar *buf;
|
|
guchar *b;
|
|
gint *vals;
|
|
gint val;
|
|
gint x, x2, w;
|
|
gint i, j;
|
|
|
|
antialias = sc->antialias;
|
|
antialias2 = antialias * antialias;
|
|
|
|
/* do we need to close the polygon? */
|
|
if (sc->got_first && sc->got_last &&
|
|
(sc->first.x != sc->last.x || sc->first.y != sc->last.y))
|
|
{
|
|
convert_segment (sc,
|
|
(gint) sc->last.x * antialias,
|
|
(gint) sc->last.y * antialias,
|
|
(gint) sc->first.x * antialias,
|
|
(gint) sc->first.y * antialias);
|
|
}
|
|
|
|
mask = gimp_channel_new_mask (gimage, sc->width, sc->height);
|
|
|
|
buf = g_new0 (guchar, sc->width);
|
|
widtha = sc->width * antialias;
|
|
heighta = sc->height * antialias;
|
|
/* allocate value array */
|
|
vals = g_new (gint, widtha);
|
|
|
|
/* dump scanlines */
|
|
for (i = 0; i < heighta; i++)
|
|
{
|
|
list = sc->scanlines[i];
|
|
TRC (("%03d: ", i));
|
|
while (list)
|
|
{
|
|
TRC (("%3d ", GPOINTER_TO_INT (list->data)));
|
|
list = g_slist_next (list);
|
|
}
|
|
TRC (("\n"));
|
|
}
|
|
|
|
pixel_region_init (&maskPR, gimp_drawable_data (GIMP_DRAWABLE (mask)), 0, 0,
|
|
gimp_drawable_width (GIMP_DRAWABLE (mask)),
|
|
gimp_drawable_height (GIMP_DRAWABLE (mask)), TRUE);
|
|
|
|
for (i = 0; i < heighta; i++)
|
|
{
|
|
list = sc->scanlines[i];
|
|
|
|
/* zero the vals array */
|
|
if (!(i % antialias))
|
|
memset (vals, 0, widtha * sizeof (int));
|
|
|
|
while (list)
|
|
{
|
|
x = GPOINTER_TO_INT (list->data);
|
|
list = g_slist_next (list);
|
|
if (!list)
|
|
{
|
|
g_message ("Cannot properly scanline convert polygon!\n");
|
|
}
|
|
else
|
|
{
|
|
/* bounds checking */
|
|
x = CLAMP (x, 0, widtha);
|
|
x2 = CLAMP (GPOINTER_TO_INT (list->data), 0, widtha);
|
|
|
|
w = x2 - x;
|
|
|
|
if (w > 0)
|
|
{
|
|
if (antialias == 1)
|
|
{
|
|
gimp_channel_add_segment (mask, x, i, w, 255);
|
|
}
|
|
else
|
|
{
|
|
for (j = 0; j < w; j++)
|
|
vals[j + x] += 255;
|
|
}
|
|
}
|
|
list = g_slist_next (list);
|
|
}
|
|
}
|
|
|
|
if (antialias != 1 && !((i+1) % antialias))
|
|
{
|
|
b = buf;
|
|
for (j = 0; j < widtha; j += antialias)
|
|
{
|
|
val = 0;
|
|
for (x = 0; x < antialias; x++)
|
|
val += vals[j + x];
|
|
|
|
*b++ = (guchar) (val / antialias2);
|
|
}
|
|
|
|
pixel_region_set_row (&maskPR, 0, (i / antialias), sc->width, buf);
|
|
}
|
|
}
|
|
|
|
g_free (vals);
|
|
g_free (buf);
|
|
|
|
return mask;
|
|
}
|