gimp/app/core/gimpscanconvert.c

473 lines
12 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 <glib-object.h>
#include <libart_lgpl/libart.h>
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "base/pixel-region.h"
#include "base/tile-manager.h"
#include "gimpscanconvert.h"
struct _GimpScanConvert
{
gdouble ratio_xy;
/* stuff necessary for the _add_polygons API... :-/ */
gboolean got_first;
gboolean need_closing;
GimpVector2 first;
GimpVector2 prev;
gboolean have_open;
guint num_nodes;
ArtVpath *vpath;
ArtSVP *svp; /* Sorted vector path
(extension no longer possible) */
};
/* private functions */
static void gimp_scan_convert_finish (GimpScanConvert *sc);
static void gimp_scan_convert_close_add_points (GimpScanConvert *sc);
/* public functions */
GimpScanConvert *
gimp_scan_convert_new (void)
{
GimpScanConvert *sc;
sc = g_new0 (GimpScanConvert, 1);
sc->ratio_xy = 1.0;
return sc;
}
void
gimp_scan_convert_free (GimpScanConvert *sc)
{
g_return_if_fail (sc != NULL);
if (sc->vpath)
art_free (sc->vpath);
if (sc->svp)
art_svp_free (sc->svp);
g_free (sc);
}
/* set the Pixel-Ratio (width / height) for the pixels.
*/
void
gimp_scan_convert_set_pixel_ratio (GimpScanConvert *sc,
gdouble ratio_xy)
{
g_return_if_fail (sc != NULL);
/* we only need the relative resolution */
sc->ratio_xy = ratio_xy;
}
/* Add "n_points" from "points" to the polygon currently being
* described by "scan_converter". DEPRECATED.
*/
void
gimp_scan_convert_add_points (GimpScanConvert *sc,
guint n_points,
GimpVector2 *points,
gboolean new_polygon)
{
gint i;
g_return_if_fail (sc != NULL);
g_return_if_fail (points != NULL);
g_return_if_fail (n_points > 0);
g_return_if_fail (sc->svp == NULL);
/* We need an extra nodes to end the path */
sc->vpath = art_renew (sc->vpath, ArtVpath, sc->num_nodes + n_points + 1);
if (sc->num_nodes == 0 || new_polygon)
{
if (sc->need_closing)
gimp_scan_convert_close_add_points (sc);
sc->got_first = FALSE;
}
/* We have to compress multiple identical coordinates */
for (i = 0; i < n_points; i++)
{
if (sc->got_first == FALSE ||
sc->prev.x != points[i].x || sc->prev.y != points[i].y)
{
sc->vpath[sc->num_nodes].code = ((! sc->got_first) || new_polygon) ?
ART_MOVETO : ART_LINETO;
sc->vpath[sc->num_nodes].x = points[i].x;
sc->vpath[sc->num_nodes].y = points[i].y;
sc->num_nodes++;
sc->prev = points[i];
if (!sc->got_first)
{
sc->got_first = TRUE;
sc->first = points[i];
}
}
}
sc->need_closing = TRUE;
sc->vpath[sc->num_nodes].code = ART_END;
}
static void
gimp_scan_convert_close_add_points (GimpScanConvert *sc)
{
if (sc->need_closing &&
(sc->prev.x != sc->first.x || sc->prev.y != sc->first.y))
{
sc->vpath = art_renew (sc->vpath, ArtVpath, sc->num_nodes + 2);
sc->vpath[sc->num_nodes].code = ART_LINETO;
sc->vpath[sc->num_nodes].x = sc->first.x;
sc->vpath[sc->num_nodes].y = sc->first.y;
sc->num_nodes++;
sc->vpath[sc->num_nodes].code = ART_END;
}
sc->need_closing = FALSE;
}
/* Add a polygon with "npoints" "points" that may be open or closed.
* It is not recommended to mix gimp_scan_convert_add_polyline with
* gimp_scan_convert_add_points.
*
* Please note that you should use gimp_scan_convert_stroke() if you
* specify open polygons.
*/
void
gimp_scan_convert_add_polyline (GimpScanConvert *sc,
guint n_points,
GimpVector2 *points,
gboolean closed)
{
GimpVector2 prev;
gint i;
g_return_if_fail (sc != NULL);
g_return_if_fail (points != NULL);
g_return_if_fail (n_points > 0);
g_return_if_fail (sc->svp == NULL);
if (sc->need_closing)
gimp_scan_convert_close_add_points (sc);
if (!closed)
sc->have_open = TRUE;
/* We need two extra nodes later to close the path. */
sc->vpath = art_renew (sc->vpath, ArtVpath,
sc->num_nodes + n_points + 2);
for (i = 0; i < n_points; i++)
{
/* compress multiple identical coordinates */
if (i == 0 ||
prev.x != points[i].x ||
prev.y != points[i].y)
{
sc->vpath[sc->num_nodes].code = (i == 0 ? (closed ?
ART_MOVETO :
ART_MOVETO_OPEN) :
ART_LINETO);
sc->vpath[sc->num_nodes].x = points[i].x;
sc->vpath[sc->num_nodes].y = points[i].y;
sc->num_nodes++;
prev = points[i];
}
}
/* for some reason we need to duplicate the last node?? */
sc->vpath[sc->num_nodes] = sc->vpath[sc->num_nodes - 1];
sc->num_nodes++;
sc->vpath[sc->num_nodes].code = ART_END;
/* If someone wants to mix this function with _add_points ()
* try to do something reasonable...
*/
sc->got_first = FALSE;
}
/* Stroke the content of a GimpScanConvert. The next
* gimp_scan_convert_render() will result in the outline of the polygon
* defined with the commands above.
*
* You cannot add additional polygons after this command.
*/
void
gimp_scan_convert_stroke (GimpScanConvert *sc,
gdouble width,
GimpJoinStyle join,
GimpCapStyle cap,
gdouble miter,
gdouble dash_offset,
GArray *dash_info)
{
ArtSVP *stroke;
ArtPathStrokeJoinType artjoin = 0;
ArtPathStrokeCapType artcap = 0;
g_return_if_fail (sc->svp == NULL);
if (sc->need_closing)
gimp_scan_convert_close_add_points (sc);
switch (join)
{
case GIMP_JOIN_MITER:
artjoin = ART_PATH_STROKE_JOIN_MITER;
break;
case GIMP_JOIN_ROUND:
artjoin = ART_PATH_STROKE_JOIN_ROUND;
break;
case GIMP_JOIN_BEVEL:
artjoin = ART_PATH_STROKE_JOIN_BEVEL;
break;
}
switch (cap)
{
case GIMP_CAP_BUTT:
artcap = ART_PATH_STROKE_CAP_BUTT;
break;
case GIMP_CAP_ROUND:
artcap = ART_PATH_STROKE_CAP_ROUND;
break;
case GIMP_CAP_SQUARE:
artcap = ART_PATH_STROKE_CAP_SQUARE;
break;
}
if (sc->ratio_xy != 1.0)
{
gint i;
for (i = 0; i < sc->num_nodes; i++)
sc->vpath[i].x *= sc->ratio_xy;
}
if (dash_info)
{
ArtVpath *dash_vpath;
ArtVpathDash dash;
gdouble *dashes;
gint i;
dash.offset = dash_offset * MAX (width, 1.0);
dashes = g_new (gdouble, dash_info->len);
for (i=0; i < dash_info->len ; i++)
dashes[i] = MAX (width, 1.0) * g_array_index (dash_info, gdouble, i);
dash.n_dash = dash_info->len;
dash.dash = dashes;
dash_vpath = art_vpath_dash (sc->vpath, &dash);
art_free (sc->vpath);
sc->vpath = dash_vpath;
g_free (dashes);
}
stroke = art_svp_vpath_stroke (sc->vpath, artjoin, artcap,
width, miter, 0.2);
if (sc->ratio_xy != 1.0)
{
ArtSVPSeg *segment;
ArtPoint *point;
gint i, j;
for (i = 0; i < stroke->n_segs; i++)
{
segment = stroke->segs + i;
segment->bbox.x0 /= sc->ratio_xy;
segment->bbox.x1 /= sc->ratio_xy;
for (j=0; j < segment->n_points ; j++)
{
point = segment->points + j;
point->x /= sc->ratio_xy;
}
}
}
sc->svp = stroke;
}
/* This is a more low level version. Expects a tile manager of depth 1.
*
* You cannot add additional polygons after this command.
*/
void
gimp_scan_convert_render (GimpScanConvert *sc,
TileManager *tile_manager,
gboolean antialias)
{
PixelRegion maskPR;
gpointer pr;
gint i, j, x0, y0;
guchar *dest, *d;
g_return_if_fail (sc != NULL);
g_return_if_fail (tile_manager != NULL);
gimp_scan_convert_finish (sc);
tile_manager_get_offsets (tile_manager, &x0, &y0);
pixel_region_init (&maskPR, tile_manager, 0, 0,
tile_manager_width (tile_manager),
tile_manager_height (tile_manager),
TRUE);
g_return_if_fail (maskPR.bytes == 1);
for (pr = pixel_regions_register (1, &maskPR);
pr != NULL;
pr = pixel_regions_process (pr))
{
art_gray_svp_aa (sc->svp,
x0 + maskPR.x,
y0 + maskPR.y,
x0 + maskPR.x + maskPR.w,
y0 + maskPR.y + maskPR.h,
maskPR.data, maskPR.rowstride);
if (! antialias)
{
/* Ok, the user didn't want to have antialiasing, so just
* remove the results from lots of CPU-Power...
*/
dest = maskPR.data;
for (j = 0; j < maskPR.h; j++)
{
d = dest;
for (i = 0; i < maskPR.w; i++)
{
d[0] = (d[0] >= 127) ? 255 : 0;
d += maskPR.bytes;
}
dest += maskPR.rowstride;
}
}
}
}
/* private function to convert the vpath to a svp when not using
* gimp_scan_convert_stroke
*/
static void
gimp_scan_convert_finish (GimpScanConvert *sc)
{
ArtVpath *pert_vpath;
ArtSVP *svp, *svp2;
g_return_if_fail (sc->vpath != NULL);
if (sc->need_closing)
gimp_scan_convert_close_add_points (sc);
if (sc->svp)
return; /* We already have a valid SVP */
/* Debug output of libart path */
/* {
* gint i;
* for (i=0; i < sc->num_nodes + 1; i++)
* {
* g_printerr ("X: %f, Y: %f, Type: %d\n", sc->vpath[i].x,
* sc->vpath[i].y,
* sc->vpath[i].code );
* }
* }
*/
if (sc->have_open)
{
gint i;
for (i = 0; i < sc->num_nodes; i++)
if (sc->vpath[i].code == ART_MOVETO_OPEN)
{
g_printerr ("Fixing ART_MOVETO_OPEN - result might be incorrect\n");
sc->vpath[i].code = ART_MOVETO;
}
}
/*
* Current Libart (2.3.8) recommends a slight random distorsion
* of the path, because art_svp_uncross and art_svp_rewind_uncrossed
* are not yet numerically stable. It is actually possible to construct
* worst case scenarios. The slight perturbation should not have any
* visible effect.
*/
pert_vpath = art_vpath_perturb (sc->vpath);
svp = art_svp_from_vpath (pert_vpath);
art_free (pert_vpath);
svp2 = art_svp_uncross (svp);
art_svp_free (svp);
svp = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_ODDEVEN);
art_svp_free (svp2);
sc->svp = svp;
}