From 418ef05b5d0b74e6f19200ffa40bcc3afccb5029 Mon Sep 17 00:00:00 2001 From: kramm Date: Tue, 22 Feb 2005 18:53:50 +0000 Subject: [PATCH] libart 2.3.16 --- lib/art/Makefile.in | 22 + lib/art/art_affine.c | 458 ++++++++++ lib/art/art_alphagamma.c | 85 ++ lib/art/art_bpath.c | 92 ++ lib/art/art_gray_svp.c | 123 +++ lib/art/art_misc.c | 79 ++ lib/art/art_pixbuf.c | 285 ++++++ lib/art/art_rect.c | 215 +++++ lib/art/art_rect_svp.c | 82 ++ lib/art/art_rect_uta.c | 134 +++ lib/art/art_render.c | 1383 +++++++++++++++++++++++++++++ lib/art/art_render_gradient.c | 716 +++++++++++++++ lib/art/art_render_mask.c | 168 ++++ lib/art/art_render_svp.c | 421 +++++++++ lib/art/art_rgb.c | 175 ++++ lib/art/art_rgb_a_affine.c | 149 ++++ lib/art/art_rgb_affine.c | 106 +++ lib/art/art_rgb_affine_private.c | 127 +++ lib/art/art_rgb_bitmap_affine.c | 198 +++++ lib/art/art_rgb_pixbuf_affine.c | 104 +++ lib/art/art_rgb_rgba_affine.c | 142 +++ lib/art/art_rgb_svp.c | 457 ++++++++++ lib/art/art_rgba.c | 258 ++++++ lib/art/art_svp.c | 152 ++++ lib/art/art_svp_intersect.c | 1803 ++++++++++++++++++++++++++++++++++++++ lib/art/art_svp_ops.c | 401 +++++++++ lib/art/art_svp_point.c | 144 +++ lib/art/art_svp_render_aa.c | 463 ++++++++++ lib/art/art_svp_vpath.c | 215 +++++ lib/art/art_svp_vpath_stroke.c | 741 ++++++++++++++++ lib/art/art_svp_wind.c | 1545 ++++++++++++++++++++++++++++++++ lib/art/art_uta.c | 88 ++ lib/art/art_uta_ops.c | 112 +++ lib/art/art_uta_rect.c | 111 +++ lib/art/art_uta_svp.c | 54 ++ lib/art/art_uta_vpath.c | 382 ++++++++ lib/art/art_vpath.c | 241 +++++ lib/art/art_vpath_bpath.c | 317 +++++++ lib/art/art_vpath_dash.c | 200 +++++ lib/art/art_vpath_svp.c | 196 +++++ lib/art/gen_art_config.c | 52 ++ 41 files changed, 13196 insertions(+) create mode 100644 lib/art/Makefile.in create mode 100644 lib/art/art_affine.c create mode 100644 lib/art/art_alphagamma.c create mode 100644 lib/art/art_bpath.c create mode 100644 lib/art/art_gray_svp.c create mode 100644 lib/art/art_misc.c create mode 100644 lib/art/art_pixbuf.c create mode 100644 lib/art/art_rect.c create mode 100644 lib/art/art_rect_svp.c create mode 100644 lib/art/art_rect_uta.c create mode 100644 lib/art/art_render.c create mode 100644 lib/art/art_render_gradient.c create mode 100644 lib/art/art_render_mask.c create mode 100644 lib/art/art_render_svp.c create mode 100644 lib/art/art_rgb.c create mode 100644 lib/art/art_rgb_a_affine.c create mode 100644 lib/art/art_rgb_affine.c create mode 100644 lib/art/art_rgb_affine_private.c create mode 100644 lib/art/art_rgb_bitmap_affine.c create mode 100644 lib/art/art_rgb_pixbuf_affine.c create mode 100644 lib/art/art_rgb_rgba_affine.c create mode 100644 lib/art/art_rgb_svp.c create mode 100644 lib/art/art_rgba.c create mode 100644 lib/art/art_svp.c create mode 100644 lib/art/art_svp_intersect.c create mode 100644 lib/art/art_svp_ops.c create mode 100644 lib/art/art_svp_point.c create mode 100644 lib/art/art_svp_render_aa.c create mode 100644 lib/art/art_svp_vpath.c create mode 100644 lib/art/art_svp_vpath_stroke.c create mode 100644 lib/art/art_svp_wind.c create mode 100644 lib/art/art_uta.c create mode 100644 lib/art/art_uta_ops.c create mode 100644 lib/art/art_uta_rect.c create mode 100644 lib/art/art_uta_svp.c create mode 100644 lib/art/art_uta_vpath.c create mode 100644 lib/art/art_vpath.c create mode 100644 lib/art/art_vpath_bpath.c create mode 100644 lib/art/art_vpath_dash.c create mode 100644 lib/art/art_vpath_svp.c create mode 100644 lib/art/gen_art_config.c diff --git a/lib/art/Makefile.in b/lib/art/Makefile.in new file mode 100644 index 0000000..460a408 --- /dev/null +++ b/lib/art/Makefile.in @@ -0,0 +1,22 @@ +top_builddir = ../.. +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +include ../../Makefile.common + +libart_objects = art_affine.$(O) art_alphagamma.$(O) art_bpath.$(O) art_gray_svp.$(O) art_misc.$(O) art_pixbuf.$(O) art_rect.$(O) art_rect_svp.$(O) art_rect_uta.$(O) art_render.$(O) art_render_gradient.$(O) art_render_mask.$(O) art_render_svp.$(O) art_rgb.$(O) art_rgb_a_affine.$(O) art_rgb_affine.$(O) art_rgb_affine_private.$(O) art_rgb_bitmap_affine.$(O) art_rgb_pixbuf_affine.$(O) art_rgb_rgba_affine.$(O) art_rgb_svp.$(O) art_rgba.$(O) art_svp.$(O) art_svp_intersect.$(O) art_svp_ops.$(O) art_svp_point.$(O) art_svp_render_aa.$(O) art_svp_vpath.$(O) art_svp_vpath_stroke.$(O) art_svp_wind.$(O) art_uta.$(O) art_uta_ops.$(O) art_uta_rect.$(O) art_uta_svp.$(O) art_uta_vpath.$(O) art_vpath.$(O) art_vpath_bpath.$(O) art_vpath_dash.$(O) art_vpath_svp.$(O) + +all: libart$(A) + +libart$(A): $(libart_objects) + $(AR) r libart$(A) $(libart_objects) + $(RANLIB) libart$(A) + +%.$(O): %.c + $(C) $< -o $@ + +install: +uninstall: +clean: + rm -f *.o *.obj *.lo *.a *.lib *.la gmon.out + + diff --git a/lib/art/art_affine.c b/lib/art/art_affine.c new file mode 100644 index 0000000..9f332a3 --- /dev/null +++ b/lib/art/art_affine.c @@ -0,0 +1,458 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Simple manipulations with affine transformations */ + +#include "config.h" +#include "art_affine.h" +#include "art_misc.h" /* for M_PI */ + +#include +#include /* for sprintf */ +#include /* for strcpy */ + + +/* According to a strict interpretation of the libart structure, this + routine should go into its own module, art_point_affine. However, + it's only two lines of code, and it can be argued that it is one of + the natural basic functions of an affine transformation. +*/ + +/** + * art_affine_point: Do an affine transformation of a point. + * @dst: Where the result point is stored. + * @src: The original point. + @ @affine: The affine transformation. + **/ +void +art_affine_point (ArtPoint *dst, const ArtPoint *src, + const double affine[6]) +{ + double x, y; + + x = src->x; + y = src->y; + dst->x = x * affine[0] + y * affine[2] + affine[4]; + dst->y = x * affine[1] + y * affine[3] + affine[5]; +} + +/** + * art_affine_invert: Find the inverse of an affine transformation. + * @dst: Where the resulting affine is stored. + * @src: The original affine transformation. + * + * All non-degenerate affine transforms are invertible. If the original + * affine is degenerate or nearly so, expect numerical instability and + * very likely core dumps on Alpha and other fp-picky architectures. + * Otherwise, @dst multiplied with @src, or @src multiplied with @dst + * will be (to within roundoff error) the identity affine. + **/ +void +art_affine_invert (double dst[6], const double src[6]) +{ + double r_det; + + r_det = 1.0 / (src[0] * src[3] - src[1] * src[2]); + dst[0] = src[3] * r_det; + dst[1] = -src[1] * r_det; + dst[2] = -src[2] * r_det; + dst[3] = src[0] * r_det; + dst[4] = -src[4] * dst[0] - src[5] * dst[2]; + dst[5] = -src[4] * dst[1] - src[5] * dst[3]; +} + +/** + * art_affine_flip: Flip an affine transformation horizontally and/or vertically. + * @dst_affine: Where the resulting affine is stored. + * @src_affine: The original affine transformation. + * @horiz: Whether or not to flip horizontally. + * @vert: Whether or not to flip horizontally. + * + * Flips the affine transform. FALSE for both @horiz and @vert implements + * a simple copy operation. TRUE for both @horiz and @vert is a + * 180 degree rotation. It is ok for @src_affine and @dst_affine to + * be equal pointers. + **/ +void +art_affine_flip (double dst_affine[6], const double src_affine[6], int horz, int vert) +{ + dst_affine[0] = horz ? - src_affine[0] : src_affine[0]; + dst_affine[1] = horz ? - src_affine[1] : src_affine[1]; + dst_affine[2] = vert ? - src_affine[2] : src_affine[2]; + dst_affine[3] = vert ? - src_affine[3] : src_affine[3]; + dst_affine[4] = horz ? - src_affine[4] : src_affine[4]; + dst_affine[5] = vert ? - src_affine[5] : src_affine[5]; +} + +#define EPSILON 1e-6 + +/* It's ridiculous I have to write this myself. This is hardcoded to + six digits of precision, which is good enough for PostScript. + + The return value is the number of characters (i.e. strlen (str)). + It is no more than 12. */ +static int +art_ftoa (char str[80], double x) +{ + char *p = str; + int i, j; + + p = str; + if (fabs (x) < EPSILON / 2) + { + strcpy (str, "0"); + return 1; + } + if (x < 0) + { + *p++ = '-'; + x = -x; + } + if ((int)floor ((x + EPSILON / 2) < 1)) + { + *p++ = '0'; + *p++ = '.'; + i = sprintf (p, "%06d", (int)floor ((x + EPSILON / 2) * 1e6)); + while (i && p[i - 1] == '0') + i--; + if (i == 0) + i--; + p += i; + } + else if (x < 1e6) + { + i = sprintf (p, "%d", (int)floor (x + EPSILON / 2)); + p += i; + if (i < 6) + { + int ix; + + *p++ = '.'; + x -= floor (x + EPSILON / 2); + for (j = i; j < 6; j++) + x *= 10; + ix = floor (x + 0.5); + + for (j = 0; j < i; j++) + ix *= 10; + + /* A cheap hack, this routine can round wrong for fractions + near one. */ + if (ix == 1000000) + ix = 999999; + + sprintf (p, "%06d", ix); + i = 6 - i; + while (i && p[i - 1] == '0') + i--; + if (i == 0) + i--; + p += i; + } + } + else + p += sprintf (p, "%g", x); + + *p = '\0'; + return p - str; +} + + + +#include +/** + * art_affine_to_string: Convert affine transformation to concise PostScript string representation. + * @str: Where to store the resulting string. + * @src: The affine transform. + * + * Converts an affine transform into a bit of PostScript code that + * implements the transform. Special cases of scaling, rotation, and + * translation are detected, and the corresponding PostScript + * operators used (this greatly aids understanding the output + * generated). The identity transform is mapped to the null string. + **/ +void +art_affine_to_string (char str[128], const double src[6]) +{ + char tmp[80]; + int i, ix; + +#if 0 + for (i = 0; i < 1000; i++) + { + double d = rand () * .1 / RAND_MAX; + art_ftoa (tmp, d); + printf ("%g %f %s\n", d, d, tmp); + } +#endif + if (fabs (src[4]) < EPSILON && fabs (src[5]) < EPSILON) + { + /* could be scale or rotate */ + if (fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON) + { + /* scale */ + if (fabs (src[0] - 1) < EPSILON && fabs (src[3] - 1) < EPSILON) + { + /* identity transform */ + str[0] = '\0'; + return; + } + else + { + ix = 0; + ix += art_ftoa (str + ix, src[0]); + str[ix++] = ' '; + ix += art_ftoa (str + ix, src[3]); + strcpy (str + ix, " scale"); + return; + } + } + else + { + /* could be rotate */ + if (fabs (src[0] - src[3]) < EPSILON && + fabs (src[1] + src[2]) < EPSILON && + fabs (src[0] * src[0] + src[1] * src[1] - 1) < 2 * EPSILON) + { + double theta; + + theta = (180 / M_PI) * atan2 (src[1], src[0]); + art_ftoa (tmp, theta); + sprintf (str, "%s rotate", tmp); + return; + } + } + } + else + { + /* could be translate */ + if (fabs (src[0] - 1) < EPSILON && fabs (src[1]) < EPSILON && + fabs (src[2]) < EPSILON && fabs (src[3] - 1) < EPSILON) + { + ix = 0; + ix += art_ftoa (str + ix, src[4]); + str[ix++] = ' '; + ix += art_ftoa (str + ix, src[5]); + strcpy (str + ix, " translate"); + return; + } + } + + ix = 0; + str[ix++] = '['; + str[ix++] = ' '; + for (i = 0; i < 6; i++) + { + ix += art_ftoa (str + ix, src[i]); + str[ix++] = ' '; + } + strcpy (str + ix, "] concat"); +} + +/** + * art_affine_multiply: Multiply two affine transformation matrices. + * @dst: Where to store the result. + * @src1: The first affine transform to multiply. + * @src2: The second affine transform to multiply. + * + * Multiplies two affine transforms together, i.e. the resulting @dst + * is equivalent to doing first @src1 then @src2. Note that the + * PostScript concat operator multiplies on the left, i.e. "M concat" + * is equivalent to "CTM = multiply (M, CTM)"; + * + * It is safe to call this function with @dst equal to @src1 or @src2. + **/ +void +art_affine_multiply (double dst[6], const double src1[6], const double src2[6]) +{ + double d0, d1, d2, d3, d4, d5; + + d0 = src1[0] * src2[0] + src1[1] * src2[2]; + d1 = src1[0] * src2[1] + src1[1] * src2[3]; + d2 = src1[2] * src2[0] + src1[3] * src2[2]; + d3 = src1[2] * src2[1] + src1[3] * src2[3]; + d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4]; + d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5]; + dst[0] = d0; + dst[1] = d1; + dst[2] = d2; + dst[3] = d3; + dst[4] = d4; + dst[5] = d5; +} + +/** + * art_affine_identity: Set up the identity matrix. + * @dst: Where to store the resulting affine transform. + * + * Sets up an identity matrix. + **/ +void +art_affine_identity (double dst[6]) +{ + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + dst[3] = 1; + dst[4] = 0; + dst[5] = 0; +} + + +/** + * art_affine_scale: Set up a scaling matrix. + * @dst: Where to store the resulting affine transform. + * @sx: X scale factor. + * @sy: Y scale factor. + * + * Sets up a scaling matrix. + **/ +void +art_affine_scale (double dst[6], double sx, double sy) +{ + dst[0] = sx; + dst[1] = 0; + dst[2] = 0; + dst[3] = sy; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_rotate: Set up a rotation affine transform. + * @dst: Where to store the resulting affine transform. + * @theta: Rotation angle in degrees. + * + * Sets up a rotation matrix. In the standard libart coordinate + * system, in which increasing y moves downward, this is a + * counterclockwise rotation. In the standard PostScript coordinate + * system, which is reversed in the y direction, it is a clockwise + * rotation. + **/ +void +art_affine_rotate (double dst[6], double theta) +{ + double s, c; + + s = sin (theta * M_PI / 180.0); + c = cos (theta * M_PI / 180.0); + dst[0] = c; + dst[1] = s; + dst[2] = -s; + dst[3] = c; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_shear: Set up a shearing matrix. + * @dst: Where to store the resulting affine transform. + * @theta: Shear angle in degrees. + * + * Sets up a shearing matrix. In the standard libart coordinate system + * and a small value for theta, || becomes \\. Horizontal lines remain + * unchanged. + **/ +void +art_affine_shear (double dst[6], double theta) +{ + double t; + + t = tan (theta * M_PI / 180.0); + dst[0] = 1; + dst[1] = 0; + dst[2] = t; + dst[3] = 1; + dst[4] = 0; + dst[5] = 0; +} + +/** + * art_affine_translate: Set up a translation matrix. + * @dst: Where to store the resulting affine transform. + * @tx: X translation amount. + * @tx: Y translation amount. + * + * Sets up a translation matrix. + **/ +void +art_affine_translate (double dst[6], double tx, double ty) +{ + dst[0] = 1; + dst[1] = 0; + dst[2] = 0; + dst[3] = 1; + dst[4] = tx; + dst[5] = ty; +} + +/** + * art_affine_expansion: Find the affine's expansion factor. + * @src: The affine transformation. + * + * Finds the expansion factor, i.e. the square root of the factor + * by which the affine transform affects area. In an affine transform + * composed of scaling, rotation, shearing, and translation, returns + * the amount of scaling. + * + * Return value: the expansion factor. + **/ +double +art_affine_expansion (const double src[6]) +{ + return sqrt (fabs (src[0] * src[3] - src[1] * src[2])); +} + +/** + * art_affine_rectilinear: Determine whether the affine transformation is rectilinear. + * @src: The original affine transformation. + * + * Determines whether @src is rectilinear, i.e. grid-aligned + * rectangles are transformed to other grid-aligned rectangles. The + * implementation has epsilon-tolerance for roundoff errors. + * + * Return value: TRUE if @src is rectilinear. + **/ +int +art_affine_rectilinear (const double src[6]) +{ + return ((fabs (src[1]) < EPSILON && fabs (src[2]) < EPSILON) || + (fabs (src[0]) < EPSILON && fabs (src[3]) < EPSILON)); +} + +/** + * art_affine_equal: Determine whether two affine transformations are equal. + * @matrix1: An affine transformation. + * @matrix2: Another affine transformation. + * + * Determines whether @matrix1 and @matrix2 are equal, with + * epsilon-tolerance for roundoff errors. + * + * Return value: TRUE if @matrix1 and @matrix2 are equal. + **/ +int +art_affine_equal (double matrix1[6], double matrix2[6]) +{ + return (fabs (matrix1[0] - matrix2[0]) < EPSILON && + fabs (matrix1[1] - matrix2[1]) < EPSILON && + fabs (matrix1[2] - matrix2[2]) < EPSILON && + fabs (matrix1[3] - matrix2[3]) < EPSILON && + fabs (matrix1[4] - matrix2[4]) < EPSILON && + fabs (matrix1[5] - matrix2[5]) < EPSILON); +} diff --git a/lib/art/art_alphagamma.c b/lib/art/art_alphagamma.c new file mode 100644 index 0000000..4651883 --- /dev/null +++ b/lib/art/art_alphagamma.c @@ -0,0 +1,85 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Some functions to build alphagamma tables */ + +#include "config.h" +#include "art_alphagamma.h" + +#include + +/** + * art_alphagamma_new: Create a new #ArtAlphaGamma. + * @gamma: Gamma value. + * + * Create a new #ArtAlphaGamma for a specific value of @gamma. When + * correctly implemented (which is generally not the case in libart), + * alpha compositing with an alphagamma parameter is equivalent to + * applying the gamma transformation to source images, doing the alpha + * compositing (in linear intensity space), then applying the inverse + * gamma transformation, bringing it back to a gamma-adjusted + * intensity space. + * + * Return value: The newly created #ArtAlphaGamma. + **/ +ArtAlphaGamma * +art_alphagamma_new (double gamma) +{ + int tablesize; + ArtAlphaGamma *alphagamma; + int i; + int *table; + art_u8 *invtable; + double s, r_gamma; + + tablesize = ceil (gamma * 8); + if (tablesize < 10) + tablesize = 10; + + alphagamma = (ArtAlphaGamma *)art_alloc (sizeof(ArtAlphaGamma) + + ((1 << tablesize) - 1) * + sizeof(art_u8)); + alphagamma->gamma = gamma; + alphagamma->invtable_size = tablesize; + + table = alphagamma->table; + for (i = 0; i < 256; i++) + table[i] = (int)floor (((1 << tablesize) - 1) * + pow (i * (1.0 / 255), gamma) + 0.5); + + invtable = alphagamma->invtable; + s = 1.0 / ((1 << tablesize) - 1); + r_gamma = 1.0 / gamma; + for (i = 0; i < 1 << tablesize; i++) + invtable[i] = (int)floor (255 * pow (i * s, r_gamma) + 0.5); + + return alphagamma; +} + +/** + * art_alphagamma_free: Free an #ArtAlphaGamma. + * @alphagamma: An #ArtAlphaGamma. + * + * Frees the #ArtAlphaGamma. + **/ +void +art_alphagamma_free (ArtAlphaGamma *alphagamma) +{ + art_free (alphagamma); +} diff --git a/lib/art/art_bpath.c b/lib/art/art_bpath.c new file mode 100644 index 0000000..a25acbf --- /dev/null +++ b/lib/art/art_bpath.c @@ -0,0 +1,92 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for bezier paths */ + +#include "config.h" +#include "art_bpath.h" + +#include + + +/** + * art_bpath_affine_transform: Affine transform an #ArtBpath. + * @src: The source #ArtBpath. + * @matrix: The affine transform. + * + * Affine transform the bezpath, returning a newly allocated #ArtBpath + * (allocated using art_alloc()). + * + * Result (x', y') = (matrix[0] * x + matrix[2] * y + matrix[4], + * matrix[1] * x + matrix[3] * y + matrix[5]) + * + * Return value: the transformed #ArtBpath. + **/ +ArtBpath * +art_bpath_affine_transform (const ArtBpath *src, const double matrix[6]) +{ + int i; + int size; + ArtBpath *new; + ArtPathcode code; + double x, y; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtBpath, size + 1); + + for (i = 0; i < size; i++) + { + code = src[i].code; + new[i].code = code; + if (code == ART_CURVETO) + { + x = src[i].x1; + y = src[i].y1; + new[i].x1 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y1 = matrix[1] * x + matrix[3] * y + matrix[5]; + x = src[i].x2; + y = src[i].y2; + new[i].x2 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y2 = matrix[1] * x + matrix[3] * y + matrix[5]; + } + else + { + new[i].x1 = 0; + new[i].y1 = 0; + new[i].x2 = 0; + new[i].y2 = 0; + } + x = src[i].x3; + y = src[i].y3; + new[i].x3 = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y3 = matrix[1] * x + matrix[3] * y + matrix[5]; + } + new[i].code = ART_END; + new[i].x1 = 0; + new[i].y1 = 0; + new[i].x2 = 0; + new[i].y2 = 0; + new[i].x3 = 0; + new[i].y3 = 0; + + return new; +} + diff --git a/lib/art/art_gray_svp.c b/lib/art/art_gray_svp.c new file mode 100644 index 0000000..e5d5df9 --- /dev/null +++ b/lib/art/art_gray_svp.c @@ -0,0 +1,123 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Render a sorted vector path into a graymap. */ + +#include "config.h" +#include "art_gray_svp.h" + +#include /* for memset */ +#include "art_misc.h" + +#include "art_svp.h" +#include "art_svp_render_aa.h" + +typedef struct _ArtGraySVPData ArtGraySVPData; + +struct _ArtGraySVPData { + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_gray_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtGraySVPData *data = (ArtGraySVPData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + int running_sum = start; + int x0, x1; + int k; + +#if 0 + printf ("start = %d", start); + running_sum = start; + for (k = 0; k < n_steps; k++) + { + running_sum += steps[k].delta; + printf (" %d:%d", steps[k].x, running_sum >> 16); + } + printf ("\n"); +#endif + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + memset (linebuf, running_sum >> 16, run_x1 - x0); + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + memset (linebuf + run_x0 - x0, running_sum >> 16, run_x1 - run_x0); + } + running_sum += steps[k].delta; + if (x1 > run_x1) + memset (linebuf + run_x1 - x0, running_sum >> 16, x1 - run_x1); + } + else + { + memset (linebuf, running_sum >> 16, x1 - x0); + } + + data->buf += data->rowstride; +} + +/** + * art_gray_svp_aa: Render the vector path into the bytemap. + * @svp: The SVP to render. + * @x0: The view window's left coord. + * @y0: The view window's top coord. + * @x1: The view window's right coord. + * @y1: The view window's bottom coord. + * @buf: The buffer where the bytemap is stored. + * @rowstride: the rowstride for @buf. + * + * Each pixel gets a value proportional to the area within the pixel + * overlapping the (filled) SVP. Pixel (x, y) is stored at: + * + * @buf[(y - * @y0) * @rowstride + (x - @x0)] + * + * All pixels @x0 <= x < @x1, @y0 <= y < @y1 are generated. A + * stored value of zero is no coverage, and a value of 255 is full + * coverage. The area within the pixel (x, y) is the region covered + * by [x..x+1] and [y..y+1]. + **/ +void +art_gray_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u8 *buf, int rowstride) +{ + ArtGraySVPData data; + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + art_svp_render_aa (svp, x0, y0, x1, y1, art_gray_svp_callback, &data); +} diff --git a/lib/art/art_misc.c b/lib/art/art_misc.c new file mode 100644 index 0000000..1927062 --- /dev/null +++ b/lib/art/art_misc.c @@ -0,0 +1,79 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Various utility functions RLL finds useful. */ + +#include "config.h" +#include "art_misc.h" + +#ifdef HAVE_UINSTD_H +#include +#endif +#include +#include + +/** + * art_die: Print the error message to stderr and exit with a return code of 1. + * @fmt: The printf-style format for the error message. + * + * Used for dealing with severe errors. + **/ +void +art_die (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + exit (1); +} + +/** + * art_warn: Print the warning message to stderr. + * @fmt: The printf-style format for the warning message. + * + * Used for generating warnings. + **/ +void +art_warn (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} + +/** + * art_dprint: Print the debug message to stderr. + * @fmt: The printf-style format for the debug message. + * + * Used for generating debug output. + **/ +void +art_dprint (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); +} + diff --git a/lib/art/art_pixbuf.c b/lib/art/art_pixbuf.c new file mode 100644 index 0000000..e993753 --- /dev/null +++ b/lib/art/art_pixbuf.c @@ -0,0 +1,285 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_pixbuf.h" + +#include "art_misc.h" +#include + +/** + * art_pixbuf_new_rgb_dnotify: Create a new RGB #ArtPixBuf with explicit destroy notification. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * @dfunc_data: The private data passed to @dfunc. + * @dfunc: The destroy notification function. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * @dfunc is called with @dfunc_data and @pixels as arguments when the + * #ArtPixBuf is destroyed. Using a destroy notification function + * allows a wide range of memory management disciplines for the pixel + * memory. A NULL value for @dfunc is also allowed and means that no + * special action will be taken on destruction. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgb_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc) +{ + ArtPixBuf *pixbuf; + + pixbuf = art_new (ArtPixBuf, 1); + + pixbuf->format = ART_PIX_RGB; + pixbuf->n_channels = 3; + pixbuf->has_alpha = 0; + pixbuf->bits_per_sample = 8; + + pixbuf->pixels = (art_u8 *) pixels; + pixbuf->width = width; + pixbuf->height = height; + pixbuf->rowstride = rowstride; + pixbuf->destroy_data = dfunc_data; + pixbuf->destroy = dfunc; + + return pixbuf; +} + +/** + * art_pixbuf_new_rgba_dnotify: Create a new RGBA #ArtPixBuf with explicit destroy notification. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * @dfunc_data: The private data passed to @dfunc. + * @dfunc: The destroy notification function. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * @dfunc is called with @dfunc_data and @pixels as arguments when the + * #ArtPixBuf is destroyed. Using a destroy notification function + * allows a wide range of memory management disciplines for the pixel + * memory. A NULL value for @dfunc is also allowed and means that no + * special action will be taken on destruction. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgba_dnotify (art_u8 *pixels, int width, int height, int rowstride, + void *dfunc_data, ArtDestroyNotify dfunc) +{ + ArtPixBuf *pixbuf; + + pixbuf = art_new (ArtPixBuf, 1); + + pixbuf->format = ART_PIX_RGB; + pixbuf->n_channels = 4; + pixbuf->has_alpha = 1; + pixbuf->bits_per_sample = 8; + + pixbuf->pixels = (art_u8 *) pixels; + pixbuf->width = width; + pixbuf->height = height; + pixbuf->rowstride = rowstride; + pixbuf->destroy_data = dfunc_data; + pixbuf->destroy = dfunc; + + return pixbuf; +} + +/** + * art_pixbuf_new_const_rgb: Create a new RGB #ArtPixBuf with constant pixel data. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * No action is taken when the #ArtPixBuf is destroyed. Thus, this + * function is useful when the pixel data is constant or statically + * allocated. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_const_rgb (const art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgb_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL); +} + +/** + * art_pixbuf_new_const_rgba: Create a new RGBA #ArtPixBuf with constant pixel data. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * No action is taken when the #ArtPixBuf is destroyed. Thus, this + * function is suitable when the pixel data is constant or statically + * allocated. + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_const_rgba (const art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgba_dnotify ((art_u8 *) pixels, width, height, rowstride, NULL, NULL); +} + +static void +art_pixel_destroy (void *func_data, void *data) +{ + art_free (data); +} + +/** + * art_pixbuf_new_rgb: Create a new RGB #ArtPixBuf. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGB + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * The @pixels buffer is freed with art_free() when the #ArtPixBuf is + * destroyed. Thus, this function is suitable when the pixel data is + * allocated with art_alloc(). + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgb (art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgb_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy); +} + +/** + * art_pixbuf_new_rgba: Create a new RGBA #ArtPixBuf. + * @pixels: A buffer containing the actual pixel data. + * @width: The width of the pixbuf. + * @height: The height of the pixbuf. + * @rowstride: The rowstride of the pixbuf. + * + * Creates a generic data structure for holding a buffer of RGBA + * pixels. It is possible to think of an #ArtPixBuf as a + * virtualization over specific pixel buffer formats. + * + * The @pixels buffer is freed with art_free() when the #ArtPixBuf is + * destroyed. Thus, this function is suitable when the pixel data is + * allocated with art_alloc(). + * + * Return value: The newly created #ArtPixBuf. + **/ +ArtPixBuf * +art_pixbuf_new_rgba (art_u8 *pixels, int width, int height, int rowstride) +{ + return art_pixbuf_new_rgba_dnotify (pixels, width, height, rowstride, NULL, art_pixel_destroy); +} + +/** + * art_pixbuf_free: Destroy an #ArtPixBuf. + * @pixbuf: The #ArtPixBuf to be destroyed. + * + * Destroys the #ArtPixBuf, calling the destroy notification function + * (if non-NULL) so that the memory for the pixel buffer can be + * properly reclaimed. + **/ +void +art_pixbuf_free (ArtPixBuf *pixbuf) +{ + ArtDestroyNotify destroy = pixbuf->destroy; + void *destroy_data = pixbuf->destroy_data; + art_u8 *pixels = pixbuf->pixels; + + pixbuf->pixels = NULL; + pixbuf->destroy = NULL; + pixbuf->destroy_data = NULL; + + if (destroy) + destroy (destroy_data, pixels); + + art_free (pixbuf); +} + +/** + * art_pixbuf_free_shallow: + * @pixbuf: The #ArtPixBuf to be destroyed. + * + * Destroys the #ArtPixBuf without calling the destroy notification function. + * + * This function is deprecated. Use the _dnotify variants for + * allocation instead. + **/ +void +art_pixbuf_free_shallow (ArtPixBuf *pixbuf) +{ + art_free (pixbuf); +} + +/** + * art_pixbuf_duplicate: Duplicate a pixbuf. + * @pixbuf: The #ArtPixBuf to duplicate. + * + * Duplicates a pixbuf, including duplicating the buffer. + * + * Return value: The duplicated pixbuf. + **/ +ArtPixBuf * +art_pixbuf_duplicate (const ArtPixBuf *pixbuf) +{ + ArtPixBuf *result; + int size; + + result = art_new (ArtPixBuf, 1); + + result->format = pixbuf->format; + result->n_channels = pixbuf->n_channels; + result->has_alpha = pixbuf->has_alpha; + result->bits_per_sample = pixbuf->bits_per_sample; + + size = (pixbuf->height - 1) * pixbuf->rowstride + + pixbuf->width * ((pixbuf->n_channels * pixbuf->bits_per_sample + 7) >> 3); + result->pixels = art_alloc (size); + memcpy (result->pixels, pixbuf->pixels, size); + + result->width = pixbuf->width; + result->height = pixbuf->height; + result->rowstride = pixbuf->rowstride; + result->destroy_data = NULL; + result->destroy = art_pixel_destroy; + + return result; +} diff --git a/lib/art/art_rect.c b/lib/art/art_rect.c new file mode 100644 index 0000000..c9dd5b3 --- /dev/null +++ b/lib/art/art_rect.c @@ -0,0 +1,215 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect.h" + +#include + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/* rectangle primitives stolen from gzilla */ + +/** + * art_irect_copy: Make a copy of an integer rectangle. + * @dest: Where the copy is stored. + * @src: The source rectangle. + * + * Copies the rectangle. + **/ +void +art_irect_copy (ArtIRect *dest, const ArtIRect *src) { + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +/** + * art_irect_union: Find union of two integer rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the smallest rectangle that includes @src1 and @src2. + **/ +void +art_irect_union (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) { + if (art_irect_empty (src1)) { + art_irect_copy (dest, src2); + } else if (art_irect_empty (src2)) { + art_irect_copy (dest, src1); + } else { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +/** + * art_irect_intersection: Find intersection of two integer rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the intersection of @src1 and @src2. + **/ +void +art_irect_intersect (ArtIRect *dest, const ArtIRect *src1, const ArtIRect *src2) { + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * art_irect_empty: Determine whether integer rectangle is empty. + * @src: The source rectangle. + * + * Return value: TRUE if @src is an empty rectangle, FALSE otherwise. + **/ +int +art_irect_empty (const ArtIRect *src) { + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +#if 0 +gboolean irect_point_inside (ArtIRect *rect, GzwPoint *point) { + return (point->x >= rect->x0 && point->y >= rect->y0 && + point->x < rect->x1 && point->y < rect->y1); +} +#endif + +/** + * art_drect_copy: Make a copy of a rectangle. + * @dest: Where the copy is stored. + * @src: The source rectangle. + * + * Copies the rectangle. + **/ +void +art_drect_copy (ArtDRect *dest, const ArtDRect *src) { + dest->x0 = src->x0; + dest->y0 = src->y0; + dest->x1 = src->x1; + dest->y1 = src->y1; +} + +/** + * art_drect_union: Find union of two rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the smallest rectangle that includes @src1 and @src2. + **/ +void +art_drect_union (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) { + if (art_drect_empty (src1)) { + art_drect_copy (dest, src2); + } else if (art_drect_empty (src2)) { + art_drect_copy (dest, src1); + } else { + dest->x0 = MIN (src1->x0, src2->x0); + dest->y0 = MIN (src1->y0, src2->y0); + dest->x1 = MAX (src1->x1, src2->x1); + dest->y1 = MAX (src1->y1, src2->y1); + } +} + +/** + * art_drect_intersection: Find intersection of two rectangles. + * @dest: Where the result is stored. + * @src1: A source rectangle. + * @src2: Another source rectangle. + * + * Finds the intersection of @src1 and @src2. + **/ +void +art_drect_intersect (ArtDRect *dest, const ArtDRect *src1, const ArtDRect *src2) { + dest->x0 = MAX (src1->x0, src2->x0); + dest->y0 = MAX (src1->y0, src2->y0); + dest->x1 = MIN (src1->x1, src2->x1); + dest->y1 = MIN (src1->y1, src2->y1); +} + +/** + * art_irect_empty: Determine whether rectangle is empty. + * @src: The source rectangle. + * + * Return value: TRUE if @src is an empty rectangle, FALSE otherwise. + **/ +int +art_drect_empty (const ArtDRect *src) { + return (src->x1 <= src->x0 || src->y1 <= src->y0); +} + +/** + * art_drect_affine_transform: Affine transform rectangle. + * @dst: Where to store the result. + * @src: The source rectangle. + * @matrix: The affine transformation. + * + * Find the smallest rectangle enclosing the affine transformed @src. + * The result is exactly the affine transformation of @src when + * @matrix specifies a rectilinear affine transformation, otherwise it + * is a conservative approximation. + **/ +void +art_drect_affine_transform (ArtDRect *dst, const ArtDRect *src, const double matrix[6]) +{ + double x00, y00, x10, y10; + double x01, y01, x11, y11; + + x00 = src->x0 * matrix[0] + src->y0 * matrix[2] + matrix[4]; + y00 = src->x0 * matrix[1] + src->y0 * matrix[3] + matrix[5]; + x10 = src->x1 * matrix[0] + src->y0 * matrix[2] + matrix[4]; + y10 = src->x1 * matrix[1] + src->y0 * matrix[3] + matrix[5]; + x01 = src->x0 * matrix[0] + src->y1 * matrix[2] + matrix[4]; + y01 = src->x0 * matrix[1] + src->y1 * matrix[3] + matrix[5]; + x11 = src->x1 * matrix[0] + src->y1 * matrix[2] + matrix[4]; + y11 = src->x1 * matrix[1] + src->y1 * matrix[3] + matrix[5]; + dst->x0 = MIN (MIN (x00, x10), MIN (x01, x11)); + dst->y0 = MIN (MIN (y00, y10), MIN (y01, y11)); + dst->x1 = MAX (MAX (x00, x10), MAX (x01, x11)); + dst->y1 = MAX (MAX (y00, y10), MAX (y01, y11)); +} + +/** + * art_drect_to_irect: Convert rectangle to integer rectangle. + * @dst: Where to store resulting integer rectangle. + * @src: The source rectangle. + * + * Find the smallest integer rectangle that encloses @src. + **/ +void +art_drect_to_irect (ArtIRect *dst, ArtDRect *src) +{ + dst->x0 = floor (src->x0); + dst->y0 = floor (src->y0); + dst->x1 = ceil (src->x1); + dst->y1 = ceil (src->y1); +} diff --git a/lib/art/art_rect_svp.c b/lib/art/art_rect_svp.c new file mode 100644 index 0000000..d981996 --- /dev/null +++ b/lib/art/art_rect_svp.c @@ -0,0 +1,82 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect_svp.h" + +#include "art_misc.h" +#include "art_svp.h" +#include "art_rect.h" + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/** + * art_drect_svp: Find the bounding box of a sorted vector path. + * @bbox: Where to store the bounding box. + * @svp: The SVP. + * + * Finds the bounding box of the SVP. + **/ +void +art_drect_svp (ArtDRect *bbox, const ArtSVP *svp) +{ + int i; + + if (svp->n_segs == 0) + { + bbox->x0 = 0; + bbox->y0 = 0; + bbox->x1 = 0; + bbox->y1 = 0; + return; + } + + art_drect_copy (bbox, &svp->segs[0].bbox); + + for (i = 1; i < svp->n_segs; i++) + { + bbox->x0 = MIN (bbox->x0, svp->segs[i].bbox.x0); + bbox->y0 = MIN (bbox->y0, svp->segs[i].bbox.y0); + bbox->x1 = MAX (bbox->x1, svp->segs[i].bbox.x1); + bbox->y1 = MAX (bbox->y1, svp->segs[i].bbox.y1); + } +} + +/** + * art_drect_svp_union: Compute the bounding box of the svp and union it in to the existing bounding box. + * @bbox: Initial boundin box and where to store the bounding box. + * @svp: The SVP. + * + * Finds the bounding box of the SVP, computing its union with an + * existing bbox. + **/ +void +art_drect_svp_union (ArtDRect *bbox, const ArtSVP *svp) +{ + ArtDRect svp_bbox; + + art_drect_svp (&svp_bbox, svp); + art_drect_union (bbox, bbox, &svp_bbox); +} diff --git a/lib/art/art_rect_uta.c b/lib/art/art_rect_uta.c new file mode 100644 index 0000000..cd002f8 --- /dev/null +++ b/lib/art/art_rect_uta.c @@ -0,0 +1,134 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rect_uta.h" + +/* Functions to decompose a microtile array into a list of rectangles. */ + +/** + * art_rect_list_from_uta: Decompose uta into list of rectangles. + * @uta: The source uta. + * @max_width: The maximum width of the resulting rectangles. + * @max_height: The maximum height of the resulting rectangles. + * @p_nrects: Where to store the number of returned rectangles. + * + * Allocates a new list of rectangles, sets *@p_nrects to the number + * in the list. This list should be freed with art_free(). + * + * Each rectangle bounded in size by (@max_width, @max_height). + * However, these bounds must be at least the size of one tile. + * + * This routine provides a precise implementation, i.e. the rectangles + * cover exactly the same area as the uta. It is thus appropriate in + * cases where the overhead per rectangle is small compared with the + * cost of filling in extra pixels. + * + * Return value: An array containing the resulting rectangles. + **/ +ArtIRect * +art_rect_list_from_uta (ArtUta *uta, int max_width, int max_height, + int *p_nrects) +{ + ArtIRect *rects; + int n_rects, n_rects_max; + int x, y; + int width, height; + int ix; + int left_ix; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int x0, y0, x1, y1; + int *glom; + int glom_rect; + + n_rects = 0; + n_rects_max = 1; + rects = art_new (ArtIRect, n_rects_max); + + width = uta->width; + height = uta->height; + utiles = uta->utiles; + + glom = art_new (int, width * height); + for (ix = 0; ix < width * height; ix++) + glom[ix] = -1; + + ix = 0; + for (y = 0; y < height; y++) + for (x = 0; x < width; x++) + { + bb = utiles[ix]; + if (bb) + { + x0 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X0(bb); + y0 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y0(bb); + y1 = ((uta->y0 + y) << ART_UTILE_SHIFT) + ART_UTA_BBOX_Y1(bb); + + left_ix = ix; + /* now try to extend to the right */ + while (x != width - 1 && + ART_UTA_BBOX_X1(bb) == ART_UTILE_SIZE && + (((bb & 0xffffff) ^ utiles[ix + 1]) & 0xffff00ff) == 0 && + (((uta->x0 + x + 1) << ART_UTILE_SHIFT) + + ART_UTA_BBOX_X1(utiles[ix + 1]) - + x0) <= max_width) + { + bb = utiles[ix + 1]; + ix++; + x++; + } + x1 = ((uta->x0 + x) << ART_UTILE_SHIFT) + ART_UTA_BBOX_X1(bb); + + + /* if rectangle nonempty */ + if ((x1 ^ x0) | (y1 ^ y0)) + { + /* try to glom onto an existing rectangle */ + glom_rect = glom[left_ix]; + if (glom_rect != -1 && + x0 == rects[glom_rect].x0 && + x1 == rects[glom_rect].x1 && + y0 == rects[glom_rect].y1 && + y1 - rects[glom_rect].y0 <= max_height) + { + rects[glom_rect].y1 = y1; + } + else + { + if (n_rects == n_rects_max) + art_expand (rects, ArtIRect, n_rects_max); + rects[n_rects].x0 = x0; + rects[n_rects].y0 = y0; + rects[n_rects].x1 = x1; + rects[n_rects].y1 = y1; + glom_rect = n_rects; + n_rects++; + } + if (y != height - 1) + glom[left_ix + width] = glom_rect; + } + } + ix++; + } + + art_free (glom); + *p_nrects = n_rects; + return rects; +} diff --git a/lib/art/art_render.c b/lib/art/art_render.c new file mode 100644 index 0000000..65b344c --- /dev/null +++ b/lib/art/art_render.c @@ -0,0 +1,1383 @@ +/* + * art_render.c: Modular rendering architecture. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_render.h" + +#include "art_rgb.h" + +typedef struct _ArtRenderPriv ArtRenderPriv; + +struct _ArtRenderPriv { + ArtRender super; + + ArtImageSource *image_source; + + int n_mask_source; + ArtMaskSource **mask_source; + + int n_callbacks; + ArtRenderCallback **callbacks; +}; + +ArtRender * +art_render_new (int x0, int y0, int x1, int y1, + art_u8 *pixels, int rowstride, + int n_chan, int depth, ArtAlphaType alpha_type, + ArtAlphaGamma *alphagamma) +{ + ArtRenderPriv *priv; + ArtRender *result; + + priv = art_new (ArtRenderPriv, 1); + result = &priv->super; + + if (n_chan > ART_MAX_CHAN) + { + art_warn ("art_render_new: n_chan = %d, exceeds %d max\n", + n_chan, ART_MAX_CHAN); + return NULL; + } + if (depth > ART_MAX_DEPTH) + { + art_warn ("art_render_new: depth = %d, exceeds %d max\n", + depth, ART_MAX_DEPTH); + return NULL; + } + if (x0 >= x1) + { + art_warn ("art_render_new: x0 >= x1 (x0 = %d, x1 = %d)\n", x0, x1); + return NULL; + } + result->x0 = x0; + result->y0 = y0; + result->x1 = x1; + result->y1 = y1; + result->pixels = pixels; + result->rowstride = rowstride; + result->n_chan = n_chan; + result->depth = depth; + result->alpha_type = alpha_type; + + result->clear = ART_FALSE; + result->opacity = 0x10000; + result->compositing_mode = ART_COMPOSITE_NORMAL; + result->alphagamma = alphagamma; + + result->alpha_buf = NULL; + result->image_buf = NULL; + + result->run = NULL; + result->span_x = NULL; + + result->need_span = ART_FALSE; + + priv->image_source = NULL; + + priv->n_mask_source = 0; + priv->mask_source = NULL; + + return result; +} + +/* todo on clear routines: I haven't really figured out what to do + with clearing the alpha channel. It _should_ be possible to clear + to an arbitrary RGBA color. */ + +/** + * art_render_clear: Set clear color. + * @clear_color: Color with which to clear dest. + * + * Sets clear color, equivalent to actually clearing the destination + * buffer before rendering. This is the most general form. + **/ +void +art_render_clear (ArtRender *render, const ArtPixMaxDepth *clear_color) +{ + int i; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + + render->clear = ART_TRUE; + for (i = 0; i < n_ch; i++) + render->clear_color[i] = clear_color[i]; +} + +/** + * art_render_clear_rgb: Set clear color, given in RGB format. + * @clear_rgb: Clear color, in 0xRRGGBB format. + * + * Sets clear color, equivalent to actually clearing the destination + * buffer before rendering. + **/ +void +art_render_clear_rgb (ArtRender *render, art_u32 clear_rgb) +{ + if (render->n_chan != 3) + art_warn ("art_render_clear_rgb: called on render with %d channels, only works with 3\n", + render->n_chan); + else + { + int r, g, b; + + render->clear = ART_TRUE; + r = clear_rgb >> 16; + g = (clear_rgb >> 8) & 0xff; + b = clear_rgb & 0xff; + render->clear_color[0] = ART_PIX_MAX_FROM_8(r); + render->clear_color[1] = ART_PIX_MAX_FROM_8(g); + render->clear_color[2] = ART_PIX_MAX_FROM_8(b); + } +} + +static void +art_render_nop_done (ArtRenderCallback *self, ArtRender *render) +{ +} + +static void +art_render_clear_render_rgb8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + art_u8 r, g, b; + ArtPixMaxDepth color_max; + + color_max = render->clear_color[0]; + r = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[1]; + g = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[2]; + b = ART_PIX_8_FROM_MAX (color_max); + + art_rgb_fill_run (dest, r, g, b, width); +} + +static void +art_render_clear_render_8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + int i, j; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + int ix; + art_u8 color[ART_MAX_CHAN + 1]; + + for (j = 0; j < n_ch; j++) + { + ArtPixMaxDepth color_max = render->clear_color[j]; + color[j] = ART_PIX_8_FROM_MAX (color_max); + } + + ix = 0; + for (i = 0; i < width; i++) + for (j = 0; j < n_ch; j++) + dest[ix++] = color[j]; +} + +const ArtRenderCallback art_render_clear_rgb8_obj = +{ + art_render_clear_render_rgb8, + art_render_nop_done +}; + +const ArtRenderCallback art_render_clear_8_obj = +{ + art_render_clear_render_8, + art_render_nop_done +}; + +#if ART_MAX_DEPTH >= 16 + +static void +art_render_clear_render_16 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + int i, j; + int n_ch = render->n_chan + (render->alpha_type != ART_ALPHA_NONE); + int ix; + art_u16 *dest_16 = (art_u16 *)dest; + art_u8 color[ART_MAX_CHAN + 1]; + + for (j = 0; j < n_ch; j++) + { + int color_16 = render->clear_color[j]; + color[j] = color_16; + } + + ix = 0; + for (i = 0; i < width; i++) + for (j = 0; j < n_ch; j++) + dest_16[ix++] = color[j]; +} + +const ArtRenderCallback art_render_clear_16_obj = +{ + art_render_clear_render_16, + art_render_nop_done +}; + +#endif /* ART_MAX_DEPTH >= 16 */ + +/* todo: inline */ +static ArtRenderCallback * +art_render_choose_clear_callback (ArtRender *render) +{ + ArtRenderCallback *clear_callback; + + if (render->depth == 8) + { + if (render->n_chan == 3 && + render->alpha_type == ART_ALPHA_NONE) + clear_callback = (ArtRenderCallback *)&art_render_clear_rgb8_obj; + else + clear_callback = (ArtRenderCallback *)&art_render_clear_8_obj; + } +#if ART_MAX_DEPTH >= 16 + else if (render->depth == 16) + clear_callback = (ArtRenderCallback *)&art_render_clear_16_obj; +#endif + else + { + art_die ("art_render_choose_clear_callback: inconsistent render->depth = %d\n", + render->depth); + } + return clear_callback; +} + +#if 0 +/* todo: get around to writing this */ +static void +art_render_composite_render_noa_8_norm (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + int width = render->x1 - render->x0; + +} +#endif + +/* This is the most general form of the function. It is slow but + (hopefully) correct. Actually, I'm still worried about roundoff + errors in the premul case - it seems to me that an off-by-one could + lead to overflow. */ +static void +art_render_composite (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + art_u32 depth = render->depth; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *alpha_buf = render->alpha_buf; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + art_u32 alpha; + int image_ix; + art_u16 src[ART_MAX_CHAN + 1]; + art_u16 dst[ART_MAX_CHAN + 1]; + int n_chan = render->n_chan; + ArtAlphaType alpha_type = render->alpha_type; + int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE); + int dst_pixstride = n_ch * (depth >> 3); + int buf_depth = render->buf_depth; + ArtAlphaType buf_alpha = render->buf_alpha; + int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE); + int buf_pixstride = buf_n_ch * (buf_depth >> 3); + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x8100) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * buf_pixstride; + dstptr = dest + (run_x0 - x0) * dst_pixstride; + for (x = run_x0; x < run_x1; x++) + { + if (alpha_buf) + { + if (depth == 8) + { + tmp = run_alpha * alpha_buf[x - x0] + 0x80; + /* range 0x80 .. 0xff0080 */ + alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)alpha_buf)[x - x0]; + tmp = (run_alpha * tmp + 0x8000) >> 8; + /* range 0x80 .. 0xffff80 */ + alpha = (tmp + (tmp >> 16)) >> 8; + } + } + else + alpha = run_alpha; + /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */ + + /* convert (src pixel * alpha) to premul alpha form, + store in src as 0..0xffff range */ + if (buf_alpha == ART_ALPHA_NONE) + { + src_alpha = alpha; + src_mul = src_alpha; + } + else + { + if (buf_depth == 8) + { + tmp = alpha * bufptr[n_chan] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)bufptr)[n_chan]; + tmp = (alpha * tmp + 0x8000) >> 8; + /* range 0x80 .. 0xffff80 */ + src_alpha = (tmp + (tmp >> 16)) >> 8; + } + if (buf_alpha == ART_ALPHA_SEPARATE) + src_mul = src_alpha; + else /* buf_alpha == (ART_ALPHA_PREMUL) */ + src_mul = alpha; + } + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + if (buf_depth == 8) + { + src_mul *= 0x101; + for (j = 0; j < n_chan; j++) + src[j] = (bufptr[j] * src_mul + 0x8000) >> 16; + } + else if (buf_depth == 16) + { + for (j = 0; j < n_chan; j++) + src[j] = (((art_u16 *)bufptr)[j] * src_mul + 0x8000) >> 16; + } + bufptr += buf_pixstride; + + /* src[0..n_chan - 1] (range 0..0xffff) and src_alpha (range + 0..0x10000) now contain the source pixel with + premultiplied alpha */ + + /* convert dst pixel to premul alpha form, + store in dst as 0..0xffff range */ + if (alpha_type == ART_ALPHA_NONE) + { + dst_alpha = 0x10000; + dst_mul = dst_alpha; + } + else + { + if (depth == 8) + { + tmp = dstptr[n_chan]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + } + else /* (depth == 16) */ + { + tmp = ((art_u16 *)dstptr)[n_chan]; + dst_alpha = (tmp + (tmp >> 15)); + } + if (alpha_type == ART_ALPHA_SEPARATE) + dst_mul = dst_alpha; + else /* (alpha_type == ART_ALPHA_PREMUL) */ + dst_mul = 0x10000; + } + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + if (depth == 8) + { + dst_mul *= 0x101; + for (j = 0; j < n_chan; j++) + dst[j] = (dstptr[j] * dst_mul + 0x8000) >> 16; + } + else if (buf_depth == 16) + { + for (j = 0; j < n_chan; j++) + dst[j] = (((art_u16 *)dstptr)[j] * dst_mul + 0x8000) >> 16; + } + + /* do the compositing, dst = (src over dst) */ + for (j = 0; j < n_chan; j++) + { + art_u32 srcv, dstv; + art_u32 tmp; + + srcv = src[j]; + dstv = dst[j]; + tmp = ((dstv * (0x10000 - src_alpha) + 0x8000) >> 16) + srcv; + tmp -= tmp >> 16; + dst[j] = tmp; + } + + if (alpha_type == ART_ALPHA_NONE) + { + if (depth == 8) + dst_mul = 0xff; + else /* (depth == 16) */ + dst_mul = 0xffff; + } + else + { + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0) + { + if (depth == 8) + dst_mul = 0xff; + else /* (depth == 16) */ + dst_mul = 0xffff; + } + else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */ + { + if (depth == 8) + dst_mul = 0xff0000 / dst_alpha; + else /* (depth == 16) */ + dst_mul = 0xffff0000 / dst_alpha; + } + } + if (depth == 8) + { + for (j = 0; j < n_chan; j++) + dstptr[j] = (dst[j] * dst_mul + 0x8000) >> 16; + if (alpha_type != ART_ALPHA_NONE) + dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16; + } + else if (depth == 16) + { + for (j = 0; j < n_chan; j++) + ((art_u16 *)dstptr)[j] = (dst[j] * dst_mul + 0x8000) >> 16; + if (alpha_type != ART_ALPHA_NONE) + ((art_u16 *)dstptr)[n_chan] = (dst_alpha * 0xffff + 0x8000) >> 16; + } + dstptr += dst_pixstride; + } + } +} + +const ArtRenderCallback art_render_composite_obj = +{ + art_render_composite, + art_render_nop_done +}; + +static void +art_render_composite_8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *alpha_buf = render->alpha_buf; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + art_u32 alpha; + int image_ix; + int n_chan = render->n_chan; + ArtAlphaType alpha_type = render->alpha_type; + int n_ch = n_chan + (alpha_type != ART_ALPHA_NONE); + int dst_pixstride = n_ch; + ArtAlphaType buf_alpha = render->buf_alpha; + int buf_n_ch = n_chan + (buf_alpha != ART_ALPHA_NONE); + int buf_pixstride = buf_n_ch; + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * buf_pixstride; + dstptr = dest + (run_x0 - x0) * dst_pixstride; + for (x = run_x0; x < run_x1; x++) + { + if (alpha_buf) + { + tmp = run_alpha * alpha_buf[x - x0] + 0x80; + /* range 0x80 .. 0xff0080 */ + alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + } + else + alpha = run_alpha; + /* alpha is run_alpha * alpha_buf[x], range 0 .. 0x10000 */ + + /* convert (src pixel * alpha) to premul alpha form, + store in src as 0..0xffff range */ + if (buf_alpha == ART_ALPHA_NONE) + { + src_alpha = alpha; + src_mul = src_alpha; + } + else + { + tmp = alpha * bufptr[n_chan] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + + if (buf_alpha == ART_ALPHA_SEPARATE) + src_mul = src_alpha; + else /* buf_alpha == (ART_ALPHA_PREMUL) */ + src_mul = alpha; + } + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + src_mul *= 0x101; + + if (alpha_type == ART_ALPHA_NONE) + { + dst_alpha = 0x10000; + dst_mul = dst_alpha; + } + else + { + tmp = dstptr[n_chan]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + if (alpha_type == ART_ALPHA_SEPARATE) + dst_mul = dst_alpha; + else /* (alpha_type == ART_ALPHA_PREMUL) */ + dst_mul = 0x10000; + } + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + if (alpha_type == ART_ALPHA_NONE) + { + dst_save_mul = 0xff; + } + else + { + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + if (alpha_type == ART_ALPHA_PREMUL || dst_alpha == 0) + { + dst_save_mul = 0xff; + } + else /* (ALPHA_TYPE == ART_ALPHA_SEPARATE && dst_alpha != 0) */ + { + dst_save_mul = 0xff0000 / dst_alpha; + } + } + + for (j = 0; j < n_chan; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + if (alpha_type != ART_ALPHA_NONE) + dstptr[n_chan] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += buf_pixstride; + dstptr += dst_pixstride; + } + } +} + +const ArtRenderCallback art_render_composite_8_obj = +{ + art_render_composite_8, + art_render_nop_done +}; + + +/* Assumes: + * alpha_buf is NULL + * buf_alpha = ART_ALPHA_NONE (source) + * alpha_type = ART_ALPHA_SEPARATE (dest) + * n_chan = 3; + */ +static void +art_render_composite_8_opt1 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + int image_ix; + art_u8 *bufptr; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * 3; + dstptr = dest + (run_x0 - x0) * 4; + if (run_alpha == 0x10000) + { + for (x = run_x0; x < run_x1; x++) + { + *dstptr++ = *bufptr++; + *dstptr++ = *bufptr++; + *dstptr++ = *bufptr++; + *dstptr++ = 0xff; + } + } + else + { + for (x = run_x0; x < run_x1; x++) + { + src_mul = run_alpha * 0x101; + + tmp = dstptr[3]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + dst_mul = dst_alpha; + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + dst_alpha += ((((0x10000 - dst_alpha) * run_alpha) >> 8) + 0x80) >> 8; + if (dst_alpha == 0) + dst_save_mul = 0xff; + else /* (dst_alpha != 0) */ + dst_save_mul = 0xff0000 / dst_alpha; + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - run_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 3; + dstptr += 4; + } + } + } +} + + +const ArtRenderCallback art_render_composite_8_opt1_obj = +{ + art_render_composite_8_opt1, + art_render_nop_done +}; + +/* Assumes: + * alpha_buf is NULL + * buf_alpha = ART_ALPHA_PREMUL (source) + * alpha_type = ART_ALPHA_SEPARATE (dest) + * n_chan = 3; + */ +static void +art_render_composite_8_opt2 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + int x0 = render->x0; + int x; + int run_x0, run_x1; + art_u8 *image_buf = render->image_buf; + int i, j; + art_u32 tmp; + art_u32 run_alpha; + int image_ix; + art_u8 *bufptr; + art_u32 src_alpha; + art_u32 src_mul; + art_u8 *dstptr; + art_u32 dst_alpha; + art_u32 dst_mul, dst_save_mul; + + image_ix = 0; + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run[i].x; + run_x1 = run[i + 1].x; + tmp = run[i].alpha; + if (tmp < 0x10000) + continue; + + run_alpha = (tmp + (tmp >> 8) + (tmp >> 16) - 0x8000) >> 8; /* range [0 .. 0x10000] */ + bufptr = image_buf + (run_x0 - x0) * 4; + dstptr = dest + (run_x0 - x0) * 4; + if (run_alpha == 0x10000) + { + for (x = run_x0; x < run_x1; x++) + { + src_alpha = (bufptr[3] << 8) + bufptr[3] + (bufptr[3] >> 7); + /* src_alpha is the (alpha of the source pixel), + range 0..0x10000 */ + + dst_alpha = (dstptr[3] << 8) + dstptr[3] + (dstptr[3] >> 7); + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul = dst_alpha*0x101; + + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + + if (dst_alpha == 0) + dst_save_mul = 0xff; + else /* dst_alpha != 0) */ + dst_save_mul = 0xff0000 / dst_alpha; + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] << 8) | bufptr[j]; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 4; + dstptr += 4; + } + } + else + { + for (x = run_x0; x < run_x1; x++) + { + tmp = run_alpha * bufptr[3] + 0x80; + /* range 0x80 .. 0xff0080 */ + src_alpha = (tmp + (tmp >> 8) + (tmp >> 16)) >> 8; + /* src_alpha is the (alpha of the source pixel * alpha), + range 0..0x10000 */ + + src_mul = run_alpha * 0x101; + + tmp = dstptr[3]; + /* range 0..0xff */ + dst_alpha = (tmp << 8) + tmp + (tmp >> 7); + dst_mul = dst_alpha; + /* dst_alpha is the alpha of the dest pixel, + range 0..0x10000 */ + + dst_mul *= 0x101; + + if (src_alpha >= 0x10000) + dst_alpha = 0x10000; + else + dst_alpha += ((((0x10000 - dst_alpha) * src_alpha) >> 8) + 0x80) >> 8; + + if (dst_alpha == 0) + { + dst_save_mul = 0xff; + } + else /* dst_alpha != 0) */ + { + dst_save_mul = 0xff0000 / dst_alpha; + } + + for (j = 0; j < 3; j++) + { + art_u32 src, dst; + art_u32 tmp; + + src = (bufptr[j] * src_mul + 0x8000) >> 16; + dst = (dstptr[j] * dst_mul + 0x8000) >> 16; + tmp = ((dst * (0x10000 - src_alpha) + 0x8000) >> 16) + src; + tmp -= tmp >> 16; + dstptr[j] = (tmp * dst_save_mul + 0x8000) >> 16; + } + dstptr[3] = (dst_alpha * 0xff + 0x8000) >> 16; + + bufptr += 4; + dstptr += 4; + } + } + } +} + +const ArtRenderCallback art_render_composite_8_opt2_obj = +{ + art_render_composite_8_opt2, + art_render_nop_done +}; + + +/* todo: inline */ +static ArtRenderCallback * +art_render_choose_compositing_callback (ArtRender *render) +{ + if (render->depth == 8 && render->buf_depth == 8) + { + if (render->n_chan == 3 && + render->alpha_buf == NULL && + render->alpha_type == ART_ALPHA_SEPARATE) + { + if (render->buf_alpha == ART_ALPHA_NONE) + return (ArtRenderCallback *)&art_render_composite_8_opt1_obj; + else if (render->buf_alpha == ART_ALPHA_PREMUL) + return (ArtRenderCallback *)&art_render_composite_8_opt2_obj; + } + + return (ArtRenderCallback *)&art_render_composite_8_obj; + } + return (ArtRenderCallback *)&art_render_composite_obj; +} + +/** + * art_render_invoke_callbacks: Invoke the callbacks in the render object. + * @render: The render object. + * @y: The current Y coordinate value. + * + * Invokes the callbacks of the render object in the appropriate + * order. Drivers should call this routine once per scanline. + * + * todo: should management of dest devolve to this routine? very + * plausibly yes. + **/ +void +art_render_invoke_callbacks (ArtRender *render, art_u8 *dest, int y) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int i; + + for (i = 0; i < priv->n_callbacks; i++) + { + ArtRenderCallback *callback; + + callback = priv->callbacks[i]; + callback->render (callback, render, dest, y); + } +} + +/** + * art_render_invoke: Perform the requested rendering task. + * @render: The render object. + * + * Invokes the renderer and all sources associated with it, to perform + * the requested rendering task. + **/ +void +art_render_invoke (ArtRender *render) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int width; + int best_driver, best_score; + int i; + int n_callbacks, n_callbacks_max; + ArtImageSource *image_source; + ArtImageSourceFlags image_flags; + int buf_depth; + ArtAlphaType buf_alpha; + art_boolean first = ART_TRUE; + + if (render == NULL) + { + art_warn ("art_render_invoke: called with render == NULL\n"); + return; + } + if (priv->image_source == NULL) + { + art_warn ("art_render_invoke: no image source given\n"); + return; + } + + width = render->x1 - render->x0; + + render->run = art_new (ArtRenderMaskRun, width + 1); + + /* Elect a mask source as driver. */ + best_driver = -1; + best_score = 0; + for (i = 0; i < priv->n_mask_source; i++) + { + int score; + ArtMaskSource *mask_source; + + mask_source = priv->mask_source[i]; + score = mask_source->can_drive (mask_source, render); + if (score > best_score) + { + best_score = score; + best_driver = i; + } + } + + /* Allocate alpha buffer if needed. */ + if (priv->n_mask_source > 1 || + (priv->n_mask_source == 1 && best_driver < 0)) + { + render->alpha_buf = art_new (art_u8, (width * render->depth) >> 3); + } + + /* Negotiate image rendering and compositing. */ + image_source = priv->image_source; + image_source->negotiate (image_source, render, &image_flags, &buf_depth, + &buf_alpha); + + /* Build callback list. */ + n_callbacks_max = priv->n_mask_source + 3; + priv->callbacks = art_new (ArtRenderCallback *, n_callbacks_max); + n_callbacks = 0; + for (i = 0; i < priv->n_mask_source; i++) + if (i != best_driver) + { + ArtMaskSource *mask_source = priv->mask_source[i]; + + mask_source->prepare (mask_source, render, first); + first = ART_FALSE; + priv->callbacks[n_callbacks++] = &mask_source->super; + } + + if (render->clear && !(image_flags & ART_IMAGE_SOURCE_CAN_CLEAR)) + priv->callbacks[n_callbacks++] = + art_render_choose_clear_callback (render); + + priv->callbacks[n_callbacks++] = &image_source->super; + + /* Allocate image buffer and add compositing callback if needed. */ + if (!(image_flags & ART_IMAGE_SOURCE_CAN_COMPOSITE)) + { + int bytespp = ((render->n_chan + (buf_alpha != ART_ALPHA_NONE)) * + buf_depth) >> 3; + render->buf_depth = buf_depth; + render->buf_alpha = buf_alpha; + render->image_buf = art_new (art_u8, width * bytespp); + priv->callbacks[n_callbacks++] = + art_render_choose_compositing_callback (render); + } + + priv->n_callbacks = n_callbacks; + + if (render->need_span) + render->span_x = art_new (int, width + 1); + + /* Invoke the driver */ + if (best_driver >= 0) + { + ArtMaskSource *driver; + + driver = priv->mask_source[best_driver]; + driver->invoke_driver (driver, render); + } + else + { + art_u8 *dest_ptr = render->pixels; + int y; + + /* Dummy driver */ + render->n_run = 2; + render->run[0].x = render->x0; + render->run[0].alpha = 0x8000 + 0xff * render->opacity; + render->run[1].x = render->x1; + render->run[1].alpha = 0x8000; + if (render->need_span) + { + render->n_span = 2; + render->span_x[0] = render->x0; + render->span_x[1] = render->x1; + } + for (y = render->y0; y < render->y1; y++) + { + art_render_invoke_callbacks (render, dest_ptr, y); + dest_ptr += render->rowstride; + } + } + + if (priv->mask_source != NULL) + art_free (priv->mask_source); + + /* clean up callbacks */ + for (i = 0; i < priv->n_callbacks; i++) + { + ArtRenderCallback *callback; + + callback = priv->callbacks[i]; + callback->done (callback, render); + } + + /* Tear down object */ + if (render->alpha_buf != NULL) + art_free (render->alpha_buf); + if (render->image_buf != NULL) + art_free (render->image_buf); + art_free (render->run); + if (render->span_x != NULL) + art_free (render->span_x); + art_free (priv->callbacks); + art_free (render); +} + +/** + * art_render_mask_solid: Add a solid translucent mask. + * @render: The render object. + * @opacity: Opacity in [0..0x10000] form. + * + * Adds a translucent mask to the rendering object. + **/ +void +art_render_mask_solid (ArtRender *render, int opacity) +{ + art_u32 old_opacity = render->opacity; + art_u32 new_opacity_tmp; + + if (opacity == 0x10000) + /* avoid potential overflow */ + return; + new_opacity_tmp = old_opacity * (art_u32)opacity + 0x8000; + render->opacity = new_opacity_tmp >> 16; +} + +/** + * art_render_add_mask_source: Add a mask source to the render object. + * @render: Render object. + * @mask_source: Mask source to add. + * + * This routine adds a mask source to the render object. In general, + * client api's for adding mask sources should just take a render object, + * then the mask source creation function should call this function. + * Clients should never have to call this function directly, unless of + * course they're creating custom mask sources. + **/ +void +art_render_add_mask_source (ArtRender *render, ArtMaskSource *mask_source) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + int n_mask_source = priv->n_mask_source++; + + if (n_mask_source == 0) + priv->mask_source = art_new (ArtMaskSource *, 1); + /* This predicate is true iff n_mask_source is a power of two */ + else if (!(n_mask_source & (n_mask_source - 1))) + priv->mask_source = art_renew (priv->mask_source, ArtMaskSource *, + n_mask_source << 1); + + priv->mask_source[n_mask_source] = mask_source; +} + +/** + * art_render_add_image_source: Add a mask source to the render object. + * @render: Render object. + * @image_source: Image source to add. + * + * This routine adds an image source to the render object. In general, + * client api's for adding image sources should just take a render + * object, then the mask source creation function should call this + * function. Clients should never have to call this function + * directly, unless of course they're creating custom image sources. + **/ +void +art_render_add_image_source (ArtRender *render, ArtImageSource *image_source) +{ + ArtRenderPriv *priv = (ArtRenderPriv *)render; + + if (priv->image_source != NULL) + { + art_warn ("art_render_add_image_source: image source already present.\n"); + return; + } + priv->image_source = image_source; +} + +/* Solid image source object and methods. Perhaps this should go into a + separate file. */ + +typedef struct _ArtImageSourceSolid ArtImageSourceSolid; + +struct _ArtImageSourceSolid { + ArtImageSource super; + ArtPixMaxDepth color[ART_MAX_CHAN]; + art_u32 *rgbtab; + art_boolean init; +}; + +static void +art_render_image_solid_done (ArtRenderCallback *self, ArtRender *render) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + + if (z->rgbtab != NULL) + art_free (z->rgbtab); + art_free (self); +} + +static void +art_render_image_solid_rgb8_opaq_init (ArtImageSourceSolid *self, ArtRender *render) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtPixMaxDepth color_max; + int r_fg, g_fg, b_fg; + int r_bg, g_bg, b_bg; + int r, g, b; + int dr, dg, db; + int i; + int tmp; + art_u32 *rgbtab; + + rgbtab = art_new (art_u32, 256); + z->rgbtab = rgbtab; + + color_max = self->color[0]; + r_fg = ART_PIX_8_FROM_MAX (color_max); + color_max = self->color[1]; + g_fg = ART_PIX_8_FROM_MAX (color_max); + color_max = self->color[2]; + b_fg = ART_PIX_8_FROM_MAX (color_max); + + color_max = render->clear_color[0]; + r_bg = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[1]; + g_bg = ART_PIX_8_FROM_MAX (color_max); + color_max = render->clear_color[2]; + b_bg = ART_PIX_8_FROM_MAX (color_max); + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + tmp = ((r_fg - r_bg) << 16) + 0x80; + dr = (tmp + (tmp >> 8)) >> 8; + tmp = ((g_fg - g_bg) << 16) + 0x80; + dg = (tmp + (tmp >> 8)) >> 8; + tmp = ((b_fg - b_bg) << 16) + 0x80; + db = (tmp + (tmp >> 8)) >> 8; + + for (i = 0; i < 256; i++) + { + rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16); + r += dr; + g += dg; + b += db; + } +} + +static void +art_render_image_solid_rgb8_opaq (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtRenderMaskRun *run = render->run; + int n_run = render->n_run; + art_u32 *rgbtab = z->rgbtab; + art_u32 rgb; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + int i; + int ix; + + if (n_run > 0) + { + run_x1 = run[0].x; + if (run_x1 > x0) + { + rgb = rgbtab[0]; + art_rgb_fill_run (dest, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - x0); + } + for (i = 0; i < n_run - 1; i++) + { + run_x0 = run_x1; + run_x1 = run[i + 1].x; + rgb = rgbtab[(run[i].alpha >> 16) & 0xff]; + ix = (run_x0 - x0) * 3; +#define OPTIMIZE_LEN_1 +#ifdef OPTIMIZE_LEN_1 + if (run_x1 - run_x0 == 1) + { + dest[ix] = rgb >> 16; + dest[ix + 1] = (rgb >> 8) & 0xff; + dest[ix + 2] = rgb & 0xff; + } + else + { + art_rgb_fill_run (dest + ix, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); + } +#else + art_rgb_fill_run (dest + ix, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); +#endif + } + } + else + { + run_x1 = x0; + } + if (run_x1 < x1) + { + rgb = rgbtab[0]; + art_rgb_fill_run (dest + (run_x1 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - run_x1); + } +} + +static void +art_render_image_solid_rgb8 (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + int width = render->x1 - render->x0; + art_u8 r, g, b; + ArtPixMaxDepth color_max; + + /* todo: replace this simple test with real sparseness */ + if (z->init) + return; + z->init = ART_TRUE; + + color_max = z->color[0]; + r = ART_PIX_8_FROM_MAX (color_max); + color_max = z->color[1]; + g = ART_PIX_8_FROM_MAX (color_max); + color_max = z->color[2]; + b = ART_PIX_8_FROM_MAX (color_max); + + art_rgb_fill_run (render->image_buf, r, g, b, width); +} + +static void +art_render_image_solid_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + ArtImageSourceSolid *z = (ArtImageSourceSolid *)self; + ArtImageSourceFlags flags = 0; + static void (*render_cbk) (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y); + + render_cbk = NULL; + + if (render->depth == 8 && render->n_chan == 3 && + render->alpha_type == ART_ALPHA_NONE) + { + if (render->clear) + { + render_cbk = art_render_image_solid_rgb8_opaq; + flags |= ART_IMAGE_SOURCE_CAN_CLEAR | ART_IMAGE_SOURCE_CAN_COMPOSITE; + art_render_image_solid_rgb8_opaq_init (z, render); + } + } + if (render_cbk == NULL) + { + if (render->depth == 8) + { + render_cbk = art_render_image_solid_rgb8; + *p_buf_depth = 8; + *p_alpha = ART_ALPHA_NONE; /* todo */ + } + } + /* todo: general case */ + self->super.render = render_cbk; + *p_flags = flags; +} + +/** + * art_render_image_solid: Add a solid color image source. + * @render: The render object. + * @color: Color. + * + * Adds an image source with the solid color given by @color. The + * color need not be retained in memory after this call. + **/ +void +art_render_image_solid (ArtRender *render, ArtPixMaxDepth *color) +{ + ArtImageSourceSolid *image_source; + int i; + + image_source = art_new (ArtImageSourceSolid, 1); + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_image_solid_done; + image_source->super.negotiate = art_render_image_solid_negotiate; + + for (i = 0; i < render->n_chan; i++) + image_source->color[i] = color[i]; + + image_source->rgbtab = NULL; + image_source->init = ART_FALSE; + + art_render_add_image_source (render, &image_source->super); +} diff --git a/lib/art/art_render_gradient.c b/lib/art/art_render_gradient.c new file mode 100644 index 0000000..e58fff0 --- /dev/null +++ b/lib/art/art_render_gradient.c @@ -0,0 +1,716 @@ +/* + * art_render_gradient.c: Gradient image source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien + * Alexander Larsson + */ + +#include "config.h" +#include "art_render_gradient.h" + +#include +#include +#include +#include +#include + +/* Hack to find out how to define alloca on different platforms. + * Modified version of glib/galloca.h. + */ + +#ifdef __GNUC__ +/* GCC does the right thing */ +# undef alloca +# define alloca(size) __builtin_alloca (size) +#elif defined (HAVE_ALLOCA_H) +/* a native and working alloca.h is there */ +# include +#else /* !__GNUC__ && !HAVE_ALLOCA_H */ +# ifdef _MSC_VER +# include +# define alloca _alloca +# else /* !_MSC_VER */ +# ifdef _AIX + #pragma alloca +# else /* !_AIX */ +# ifndef alloca /* predefined by HP cc +Olibcalls */ +char *alloca (); +# endif /* !alloca */ +# endif /* !_AIX */ +# endif /* !_MSC_VER */ +#endif /* !__GNUC__ && !HAVE_ALLOCA_H */ + +#undef DEBUG_SPEW + +typedef struct _ArtImageSourceGradLin ArtImageSourceGradLin; +typedef struct _ArtImageSourceGradRad ArtImageSourceGradRad; + +/* The stops will be copied right after this structure */ +struct _ArtImageSourceGradLin { + ArtImageSource super; + ArtGradientLinear gradient; + ArtGradientStop stops[1]; +}; + +/* The stops will be copied right after this structure */ +struct _ArtImageSourceGradRad { + ArtImageSource super; + ArtGradientRadial gradient; + double a; + ArtGradientStop stops[1]; +}; + +#define EPSILON 1e-6 + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +static void +art_rgba_gradient_run (art_u8 *buf, + art_u8 *color1, + art_u8 *color2, + int len) +{ + int i; + int r, g, b, a; + int dr, dg, db, da; + +#ifdef DEBUG_SPEW + printf ("gradient run from %3d %3d %3d %3d to %3d %3d %3d %3d in %d pixels\n", + color1[0], color1[1], color1[2], color1[3], + color2[0], color2[1], color2[2], color2[3], + len); +#endif + + r = (color1[0] << 16) + 0x8000; + g = (color1[1] << 16) + 0x8000; + b = (color1[2] << 16) + 0x8000; + a = (color1[3] << 16) + 0x8000; + dr = ((color2[0] - color1[0]) << 16) / len; + dg = ((color2[1] - color1[1]) << 16) / len; + db = ((color2[2] - color1[2]) << 16) / len; + da = ((color2[3] - color1[3]) << 16) / len; + + for (i = 0; i < len; i++) + { + *buf++ = (r>>16); + *buf++ = (g>>16); + *buf++ = (b>>16); + *buf++ = (a>>16); + + r += dr; + g += dg; + b += db; + a += da; + } +} + +static void +calc_color_at (ArtGradientStop *stops, + int n_stops, + ArtGradientSpread spread, + double offset, + double offset_fraction, + int favor_start, + int ix, + art_u8 *color) +{ + double off0, off1; + int j; + + if (spread == ART_GRADIENT_PAD) + { + if (offset < 0.0) + { + color[0] = ART_PIX_8_FROM_MAX (stops[0].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[0].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[0].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[0].color[3]); + return; + } + if (offset >= 1.0) + { + color[0] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[n_stops-1].color[3]); + return; + } + } + + if (ix > 0 && ix < n_stops) + { + off0 = stops[ix - 1].offset; + off1 = stops[ix].offset; + if (fabs (off1 - off0) > EPSILON) + { + double interp; + double o; + o = offset_fraction; + + if ((fabs (o) < EPSILON) && (!favor_start)) + o = 1.0; + else if ((fabs (o-1.0) < EPSILON) && (favor_start)) + o = 0.0; + + /* + if (offset_fraction == 0.0 && !favor_start) + offset_fraction = 1.0; + */ + + interp = (o - off0) / (off1 - off0); + for (j = 0; j < 4; j++) + { + int z0, z1; + int z; + z0 = stops[ix - 1].color[j]; + z1 = stops[ix].color[j]; + z = floor (z0 + (z1 - z0) * interp + 0.5); + color[j] = ART_PIX_8_FROM_MAX (z); + } + return; + } + /* If offsets are too close to safely do the division, just + pick the ix color. */ + color[0] = ART_PIX_8_FROM_MAX (stops[ix].color[0]); + color[1] = ART_PIX_8_FROM_MAX (stops[ix].color[1]); + color[2] = ART_PIX_8_FROM_MAX (stops[ix].color[2]); + color[3] = ART_PIX_8_FROM_MAX (stops[ix].color[3]); + return; + } + + printf ("WARNING! bad ix %d in calc_color_at() [internal error]\n", ix); + assert (0); +} + +static void +art_render_gradient_linear_render_8 (ArtRenderCallback *self, + ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self; + const ArtGradientLinear *gradient = &(z->gradient); + int i; + int width = render->x1 - render->x0; + int len; + double offset, d_offset; + double offset_fraction; + int next_stop; + int ix; + art_u8 color1[4], color2[4]; + int n_stops = gradient->n_stops; + int extra_stops; + ArtGradientStop *stops = gradient->stops; + ArtGradientStop *tmp_stops; + art_u8 *bufp = render->image_buf; + ArtGradientSpread spread = gradient->spread; + +#ifdef DEBUG_SPEW + printf ("x1: %d, x2: %d, y: %d\n", render->x0, render->x1, y); + printf ("spread: %d, stops:", gradient->spread); + for (i=0;istops[i].offset); + } + printf ("\n"); + printf ("a: %f, b: %f, c: %f\n", gradient->a, gradient->b, gradient->c); +#endif + + offset = render->x0 * gradient->a + y * gradient->b + gradient->c; + d_offset = gradient->a; + + /* We need to force the gradient to extend the whole 0..1 segment, + because the rest of the code doesn't handle partial gradients + correctly */ + if ((gradient->stops[0].offset > EPSILON /* == 0.0 */) || + (gradient->stops[n_stops-1].offset < (1.0 - EPSILON))) + { + extra_stops = 0; + tmp_stops = stops = alloca (sizeof (ArtGradientStop) * (n_stops + 2)); + if (gradient->stops[0].offset > EPSILON /* 0.0 */) + { + memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop)); + tmp_stops[0].offset = 0.0; + tmp_stops += 1; + extra_stops++; + } + memcpy (tmp_stops, gradient->stops, sizeof (ArtGradientStop) * n_stops); + if (gradient->stops[n_stops-1].offset < (1.0 - EPSILON)) + { + tmp_stops += n_stops; + memcpy (tmp_stops, &gradient->stops[n_stops-1], sizeof (ArtGradientStop)); + tmp_stops[0].offset = 1.0; + extra_stops++; + } + n_stops += extra_stops; + + +#ifdef DEBUG_SPEW + printf ("start/stop modified stops:"); + for (i=0;i offset_fraction || + (d_offset < 0.0 && fabs (stops[ix].offset - offset_fraction) < EPSILON)) + break; + if (ix == 0) + ix = n_stops - 1; + else if (ix == n_stops) + ix = n_stops - 1; + +#ifdef DEBUG_SPEW + printf ("Initial ix: %d\n", ix); +#endif + + assert (ix > 0); + assert (ix < n_stops); + assert ((stops[ix-1].offset <= offset_fraction + EPSILON) || + ((stops[ix].offset > (1.0 - EPSILON)) && (offset_fraction < EPSILON /* == 0.0*/))); + assert (offset_fraction <= stops[ix].offset); + /* FIXME: These asserts may be broken, it is for now + safer to not use them. Should be fixed! + See bug #121850 + assert ((offset_fraction != stops[ix-1].offset) || + (d_offset >= 0.0)); + assert ((offset_fraction != stops[ix].offset) || + (d_offset <= 0.0)); + */ + + while (width > 0) + { +#ifdef DEBUG_SPEW + printf ("ix: %d\n", ix); + printf ("start offset: %f\n", offset); +#endif + calc_color_at (stops, n_stops, + spread, + offset, + offset_fraction, + (d_offset > -EPSILON), + ix, + color1); + + if (d_offset > 0) + next_stop = ix; + else + next_stop = ix-1; + +#ifdef DEBUG_SPEW + printf ("next_stop: %d\n", next_stop); +#endif + if (fabs (d_offset) > EPSILON) + { + double o; + o = offset_fraction; + + if ((fabs (o) <= EPSILON) && (ix == n_stops - 1)) + o = 1.0; + else if ((fabs (o-1.0) <= EPSILON) && (ix == 1)) + o = 0.0; + +#ifdef DEBUG_SPEW + printf ("o: %f\n", o); +#endif + len = (int)floor (fabs ((stops[next_stop].offset - o) / d_offset)) + 1; + len = MAX (len, 0); + len = MIN (len, width); + } + else + { + len = width; + } +#ifdef DEBUG_SPEW + printf ("len: %d\n", len); +#endif + if (len > 0) + { + offset = offset + (len-1) * d_offset; + offset_fraction = offset - floor (offset); +#ifdef DEBUG_SPEW + printf ("end offset: %f, fraction: %f\n", offset, offset_fraction); +#endif + calc_color_at (stops, n_stops, + spread, + offset, + offset_fraction, + (d_offset < EPSILON), + ix, + color2); + + art_rgba_gradient_run (bufp, + color1, + color2, + len); + offset += d_offset; + offset_fraction = offset - floor (offset); + } + + if (d_offset > 0) + { + do + { + ix++; + if (ix == n_stops) + ix = 1; + /* Note: offset_fraction can actually be one here on x86 machines that + does calculations with extended precision, but later rounds to 64bit. + This happens if the 80bit offset_fraction is larger than the + largest 64bit double that is less than one. + */ + } + while (!((stops[ix-1].offset <= offset_fraction && + offset_fraction < stops[ix].offset) || + (ix == 1 && offset_fraction > (1.0 - EPSILON)))); + } + else + { + do + { + ix--; + if (ix == 0) + ix = n_stops - 1; + } + while (!((stops[ix-1].offset < offset_fraction && + offset_fraction <= stops[ix].offset) || + (ix == n_stops - 1 && offset_fraction < EPSILON /* == 0.0*/))); + } + + bufp += 4*len; + width -= len; + } +} + + +/** + * art_render_gradient_setpix: Set a gradient pixel. + * @render: The render object. + * @dst: Pointer to destination (where to store pixel). + * @n_stops: Number of stops in @stops. + * @stops: The stops for the gradient. + * @offset: The offset. + * + * @n_stops must be > 0. + * + * Sets a gradient pixel, storing it at @dst. + **/ +static void +art_render_gradient_setpix (ArtRender *render, + art_u8 *dst, + int n_stops, ArtGradientStop *stops, + double offset) +{ + int ix; + int j; + double off0, off1; + int n_ch = render->n_chan + 1; + + for (ix = 0; ix < n_stops; ix++) + if (stops[ix].offset > offset) + break; + /* stops[ix - 1].offset < offset < stops[ix].offset */ + if (ix > 0 && ix < n_stops) + { + off0 = stops[ix - 1].offset; + off1 = stops[ix].offset; + if (fabs (off1 - off0) > EPSILON) + { + double interp; + + interp = (offset - off0) / (off1 - off0); + for (j = 0; j < n_ch; j++) + { + int z0, z1; + int z; + z0 = stops[ix - 1].color[j]; + z1 = stops[ix].color[j]; + z = floor (z0 + (z1 - z0) * interp + 0.5); + if (render->buf_depth == 8) + dst[j] = ART_PIX_8_FROM_MAX (z); + else /* (render->buf_depth == 16) */ + ((art_u16 *)dst)[j] = z; + } + return; + } + } + else if (ix == n_stops) + ix--; + + for (j = 0; j < n_ch; j++) + { + int z; + z = stops[ix].color[j]; + if (render->buf_depth == 8) + dst[j] = ART_PIX_8_FROM_MAX (z); + else /* (render->buf_depth == 16) */ + ((art_u16 *)dst)[j] = z; + } +} + +static void +art_render_gradient_linear_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static void +art_render_gradient_linear_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradLin *z = (ArtImageSourceGradLin *)self; + const ArtGradientLinear *gradient = &(z->gradient); + int pixstride = (render->n_chan + 1) * (render->depth >> 3); + int x; + int width = render->x1 - render->x0; + double offset, d_offset; + double actual_offset; + int n_stops = gradient->n_stops; + ArtGradientStop *stops = gradient->stops; + art_u8 *bufp = render->image_buf; + ArtGradientSpread spread = gradient->spread; + + offset = render->x0 * gradient->a + y * gradient->b + gradient->c; + d_offset = gradient->a; + + for (x = 0; x < width; x++) + { + if (spread == ART_GRADIENT_PAD) + actual_offset = offset; + else if (spread == ART_GRADIENT_REPEAT) + actual_offset = offset - floor (offset); + else /* (spread == ART_GRADIENT_REFLECT) */ + { + double tmp; + + tmp = offset - 2 * floor (0.5 * offset); + actual_offset = tmp > 1 ? 2 - tmp : tmp; + } + art_render_gradient_setpix (render, bufp, n_stops, stops, actual_offset); + offset += d_offset; + bufp += pixstride; + } +} + +static void +art_render_gradient_linear_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + if (render->depth == 8 && + render->n_chan == 3) + { + self->super.render = art_render_gradient_linear_render_8; + *p_flags = 0; + *p_buf_depth = 8; + *p_alpha = ART_ALPHA_PREMUL; + return; + } + + self->super.render = art_render_gradient_linear_render; + *p_flags = 0; + *p_buf_depth = render->depth; + *p_alpha = ART_ALPHA_PREMUL; +} + +/** + * art_render_gradient_linear: Add a linear gradient image source. + * @render: The render object. + * @gradient: The linear gradient. + * + * Adds the linear gradient @gradient as the image source for rendering + * in the render object @render. + **/ +void +art_render_gradient_linear (ArtRender *render, + const ArtGradientLinear *gradient, + ArtFilterLevel level) +{ + ArtImageSourceGradLin *image_source = art_alloc (sizeof (ArtImageSourceGradLin) + + sizeof (ArtGradientStop) * (gradient->n_stops - 1)); + + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_gradient_linear_done; + image_source->super.negotiate = art_render_gradient_linear_negotiate; + + /* copy the gradient into the structure */ + image_source->gradient = *gradient; + image_source->gradient.stops = image_source->stops; + memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops); + + art_render_add_image_source (render, &image_source->super); +} + +static void +art_render_gradient_radial_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static void +art_render_gradient_radial_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtImageSourceGradRad *z = (ArtImageSourceGradRad *)self; + const ArtGradientRadial *gradient = &(z->gradient); + int pixstride = (render->n_chan + 1) * (render->depth >> 3); + int x; + int x0 = render->x0; + int width = render->x1 - x0; + int n_stops = gradient->n_stops; + ArtGradientStop *stops = gradient->stops; + art_u8 *bufp = render->image_buf; + double fx = gradient->fx; + double fy = gradient->fy; + double dx, dy; + double *affine = gradient->affine; + double aff0 = affine[0]; + double aff1 = affine[1]; + const double a = z->a; + const double arecip = 1.0 / a; + double b, db; + double c, dc, ddc; + double b_a, db_a; + double rad, drad, ddrad; + + dx = x0 * aff0 + y * affine[2] + affine[4] - fx; + dy = x0 * aff1 + y * affine[3] + affine[5] - fy; + b = dx * fx + dy * fy; + db = aff0 * fx + aff1 * fy; + c = dx * dx + dy * dy; + dc = 2 * aff0 * dx + aff0 * aff0 + 2 * aff1 * dy + aff1 * aff1; + ddc = 2 * aff0 * aff0 + 2 * aff1 * aff1; + + b_a = b * arecip; + db_a = db * arecip; + + rad = b_a * b_a + c * arecip; + drad = 2 * b_a * db_a + db_a * db_a + dc * arecip; + ddrad = 2 * db_a * db_a + ddc * arecip; + + for (x = 0; x < width; x++) + { + double z; + + if (rad > 0) + z = b_a + sqrt (rad); + else + z = b_a; + art_render_gradient_setpix (render, bufp, n_stops, stops, z); + bufp += pixstride; + b_a += db_a; + rad += drad; + drad += ddrad; + } +} + +static void +art_render_gradient_radial_negotiate (ArtImageSource *self, ArtRender *render, + ArtImageSourceFlags *p_flags, + int *p_buf_depth, ArtAlphaType *p_alpha) +{ + self->super.render = art_render_gradient_radial_render; + *p_flags = 0; + *p_buf_depth = render->depth; + *p_alpha = ART_ALPHA_PREMUL; +} + +/** + * art_render_gradient_radial: Add a radial gradient image source. + * @render: The render object. + * @gradient: The radial gradient. + * + * Adds the radial gradient @gradient as the image source for rendering + * in the render object @render. + **/ +void +art_render_gradient_radial (ArtRender *render, + const ArtGradientRadial *gradient, + ArtFilterLevel level) +{ + ArtImageSourceGradRad *image_source = art_alloc (sizeof (ArtImageSourceGradRad) + + sizeof (ArtGradientStop) * (gradient->n_stops - 1)); + double fx = gradient->fx; + double fy = gradient->fy; + + image_source->super.super.render = NULL; + image_source->super.super.done = art_render_gradient_radial_done; + image_source->super.negotiate = art_render_gradient_radial_negotiate; + + /* copy the gradient into the structure */ + image_source->gradient = *gradient; + image_source->gradient.stops = image_source->stops; + memcpy (image_source->gradient.stops, gradient->stops, sizeof (ArtGradientStop) * gradient->n_stops); + + /* todo: sanitycheck fx, fy? */ + image_source->a = 1 - fx * fx - fy * fy; + + art_render_add_image_source (render, &image_source->super); +} diff --git a/lib/art/art_render_mask.c b/lib/art/art_render_mask.c new file mode 100644 index 0000000..ce82608 --- /dev/null +++ b/lib/art/art_render_mask.c @@ -0,0 +1,168 @@ +/* + * art_render_mask.c: Alpha mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien + */ + +#include "config.h" +#include "art_render_mask.h" + +#include + + +typedef struct _ArtMaskSourceMask ArtMaskSourceMask; + +struct _ArtMaskSourceMask { + ArtMaskSource super; + ArtRender *render; + art_boolean first; + int x0; + int y0; + int x1; + int y1; + const art_u8 *mask_buf; + int rowstride; +}; + +static void +art_render_mask_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static int +art_render_mask_can_drive (ArtMaskSource *self, ArtRender *render) +{ + return 0; +} + +static void +art_render_mask_render (ArtRenderCallback *self, ArtRender *render, + art_u8 *dest, int y) +{ + ArtMaskSourceMask *z = (ArtMaskSourceMask *)self; + int x0 = render->x0, x1 = render->x1; + int z_x0 = z->x0, z_x1 = z->x1; + int width = x1 - x0; + int z_width = z_x1 - z_x0; + art_u8 *alpha_buf = render->alpha_buf; + + if (y < z->y0 || y >= z->y1 || z_width <= 0) + memset (alpha_buf, 0, width); + else + { + const art_u8 *src_line = z->mask_buf + (y - z->y0) * z->rowstride; + art_u8 *dst_line = alpha_buf + z_x0 - x0; + + if (z_x0 > x0) + memset (alpha_buf, 0, z_x0 - x0); + + if (z->first) + memcpy (dst_line, src_line, z_width); + else + { + int x; + + for (x = 0; x < z_width; x++) + { + int v; + v = src_line[x]; + if (v) + { + v = v * dst_line[x] + 0x80; + v = (v + (v >> 8)) >> 8; + dst_line[x] = v; + } + else + { + dst_line[x] = 0; + } + } + } + + if (z_x1 < x1) + memset (alpha_buf + z_x1 - x0, 0, x1 - z_x1); + } +} + +static void +art_render_mask_prepare (ArtMaskSource *self, ArtRender *render, + art_boolean first) +{ + ArtMaskSourceMask *z = (ArtMaskSourceMask *)self; + self->super.render = art_render_mask_render; + z->first = first; +} + +/** + * art_render_mask: Use an alpha buffer as a render mask source. + * @render: Render object. + * @x0: Left coordinate of mask rect. + * @y0: Top coordinate of mask rect. + * @x1: Right coordinate of mask rect. + * @y1: Bottom coordinate of mask rect. + * @mask_buf: Buffer containing 8bpp alpha mask data. + * @rowstride: Rowstride of @mask_buf. + * + * Adds @mask_buf to the render object as a mask. Note: @mask_buf must + * remain allocated until art_render_invoke() is called on @render. + **/ +void +art_render_mask (ArtRender *render, + int x0, int y0, int x1, int y1, + const art_u8 *mask_buf, int rowstride) +{ + ArtMaskSourceMask *mask_source; + + if (x0 < render->x0) + { + mask_buf += render->x0 - x0; + x0 = render->x0; + } + if (x1 > render->x1) + x1 = render->x1; + + if (y0 < render->y0) + { + mask_buf += (render->y0 - y0) * rowstride; + y0 = render->y0; + } + if (y1 > render->y1) + y1 = render->y1; + + mask_source = art_new (ArtMaskSourceMask, 1); + + mask_source->super.super.render = NULL; + mask_source->super.super.done = art_render_mask_done; + mask_source->super.can_drive = art_render_mask_can_drive; + mask_source->super.invoke_driver = NULL; + mask_source->super.prepare = art_render_mask_prepare; + mask_source->render = render; + mask_source->x0 = x0; + mask_source->y0 = y0; + mask_source->x1 = x1; + mask_source->y1 = y1; + mask_source->mask_buf = mask_buf; + mask_source->rowstride = rowstride; + + art_render_add_mask_source (render, &mask_source->super); + +} diff --git a/lib/art/art_render_svp.c b/lib/art/art_render_svp.c new file mode 100644 index 0000000..895e3cc --- /dev/null +++ b/lib/art/art_render_svp.c @@ -0,0 +1,421 @@ +/* + * art_render_gradient.c: SVP mask source for modular rendering. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Raph Levien + */ + +#include "art_render_svp.h" +#include "art_svp_render_aa.h" + +typedef struct _ArtMaskSourceSVP ArtMaskSourceSVP; + +struct _ArtMaskSourceSVP { + ArtMaskSource super; + ArtRender *render; + const ArtSVP *svp; + art_u8 *dest_ptr; +}; + +static void +art_render_svp_done (ArtRenderCallback *self, ArtRender *render) +{ + art_free (self); +} + +static int +art_render_svp_can_drive (ArtMaskSource *self, ArtRender *render) +{ + return 10; +} + +/* The basic art_render_svp_callback function is repeated four times, + for all combinations of non-unit opacity and generating spans. In + general, I'd consider this bad style, but in this case I plead + a measurable performance improvement. */ + +static void +art_render_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int i; + int running_sum = start; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0 && running_sum > 0x80ff) + { + run[0].x = x0; + run[0].alpha = running_sum; + n_run++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + run[n_run].alpha = running_sum; + n_run++; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + run[n_run].alpha = running_sum; + n_run++; + } + if (running_sum > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + } + + render->n_run = n_run; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_span (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int n_span = 0; + int i; + int running_sum = start; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + int *span_x = render->span_x; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0 && running_sum > 0x80ff) + { + run[0].x = x0; + run[0].alpha = running_sum; + n_run++; + span_x[0] = x0; + n_span++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + run[n_run].alpha = running_sum; + n_run++; + if ((n_span & 1) != (running_sum > 0x80ff)) + span_x[n_span++] = run_x0; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + run[n_run].alpha = running_sum; + n_run++; + if ((n_span & 1) != (running_sum > 0x80ff)) + span_x[n_span++] = run_x1; + } + if (running_sum > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + span_x[n_span++] = x1; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + span_x[0] = x0; + span_x[1] = x1; + n_span = 2; + } + + render->n_run = n_run; + render->n_span = n_span; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_opacity (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int i; + art_u32 running_sum; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + art_u32 opacity = render->opacity; + art_u32 alpha; + + running_sum = start - 0x7f80; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + if (run_x1 > x0 && alpha > 0x80ff) + { + run[0].x = x0; + run[0].alpha = alpha; + n_run++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + run[n_run].alpha = alpha; + n_run++; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + alpha = ((running_sum >> 8) * opacity + 0x80080) >> 8; + run[n_run].alpha = alpha; + n_run++; + } + if (alpha > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + } + + render->n_run = n_run; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_callback_opacity_span (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)callback_data; + ArtRender *render = z->render; + int n_run = 0; + int n_span = 0; + int i; + art_u32 running_sum; + int x0 = render->x0; + int x1 = render->x1; + int run_x0, run_x1; + ArtRenderMaskRun *run = render->run; + int *span_x = render->span_x; + art_u32 opacity = render->opacity; + art_u32 alpha; + + running_sum = start - 0x7f80; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + if (run_x1 > x0 && alpha > 0x80ff) + { + run[0].x = x0; + run[0].alpha = alpha; + n_run++; + span_x[0] = x0; + n_span++; + } + + for (i = 0; i < n_steps - 1; i++) + { + running_sum += steps[i].delta; + run_x0 = run_x1; + run_x1 = steps[i + 1].x; + if (run_x1 > run_x0) + { + run[n_run].x = run_x0; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + run[n_run].alpha = alpha; + n_run++; + if ((n_span & 1) != (alpha > 0x80ff)) + span_x[n_span++] = run_x0; + } + } + if (x1 > run_x1) + { + running_sum += steps[n_steps - 1].delta; + run[n_run].x = run_x1; + alpha = ((running_sum >> 8) * opacity + 0x800080) >> 8; + run[n_run].alpha = alpha; + n_run++; + if ((n_span & 1) != (alpha > 0x80ff)) + span_x[n_span++] = run_x1; + } + if (alpha > 0x80ff) + { + run[n_run].x = x1; + run[n_run].alpha = 0x8000; + n_run++; + span_x[n_span++] = x1; + } + } + else if ((running_sum >> 16) > 0) + { + run[0].x = x0; + run[0].alpha = running_sum; + run[1].x = x1; + run[1].alpha = running_sum; + n_run = 2; + span_x[0] = x0; + span_x[1] = x1; + n_span = 2; + } + + render->n_run = n_run; + render->n_span = n_span; + + art_render_invoke_callbacks (render, z->dest_ptr, y); + + z->dest_ptr += render->rowstride; +} + +static void +art_render_svp_invoke_driver (ArtMaskSource *self, ArtRender *render) +{ + ArtMaskSourceSVP *z = (ArtMaskSourceSVP *)self; + void (*callback) (void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps); + + z->dest_ptr = render->pixels; + if (render->opacity == 0x10000) + { + if (render->need_span) + callback = art_render_svp_callback_span; + else + callback = art_render_svp_callback; + } + else + { + if (render->need_span) + callback = art_render_svp_callback_opacity_span; + else + callback = art_render_svp_callback_opacity; + } + + art_svp_render_aa (z->svp, + render->x0, render->y0, + render->x1, render->y1, callback, + self); + art_render_svp_done (&self->super, render); +} + +static void +art_render_svp_prepare (ArtMaskSource *self, ArtRender *render, + art_boolean first) +{ + /* todo */ + art_die ("art_render_svp non-driver mode not yet implemented.\n"); +} + +/** + * art_render_svp: Use an SVP as a render mask source. + * @render: Render object. + * @svp: SVP. + * + * Adds @svp to the render object as a mask. Note: @svp must remain + * allocated until art_render_invoke() is called on @render. + **/ +void +art_render_svp (ArtRender *render, const ArtSVP *svp) +{ + ArtMaskSourceSVP *mask_source; + mask_source = art_new (ArtMaskSourceSVP, 1); + + mask_source->super.super.render = NULL; + mask_source->super.super.done = art_render_svp_done; + mask_source->super.can_drive = art_render_svp_can_drive; + mask_source->super.invoke_driver = art_render_svp_invoke_driver; + mask_source->super.prepare = art_render_svp_prepare; + mask_source->render = render; + mask_source->svp = svp; + + art_render_add_mask_source (render, &mask_source->super); +} diff --git a/lib/art/art_rgb.c b/lib/art/art_rgb.c new file mode 100644 index 0000000..05bfa02 --- /dev/null +++ b/lib/art/art_rgb.c @@ -0,0 +1,175 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb.h" + +#include /* for memset */ + +/* Basic operators for manipulating 24-bit packed RGB buffers. */ + +#define COLOR_RUN_COMPLEX + +#ifdef COLOR_RUN_SIMPLE +/* This is really slow. Is there any way we might speed it up? + Two ideas: + + First, maybe we should be working at 32-bit alignment. Then, + this can be a simple loop over word stores. + + Second, we can keep working at 24-bit alignment, but have some + intelligence about storing. For example, we can iterate over + 4-pixel chunks (aligned at 4 pixels), with an inner loop + something like: + + *buf++ = v1; + *buf++ = v2; + *buf++ = v3; + + One source of extra complexity is the need to make sure linebuf is + aligned to a 32-bit boundary. + + This second alternative has some complexity to it, but is + appealing because it really minimizes the memory bandwidth. */ +void +art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, gint n) +{ + int i; + + if (r == g && g == b) + { + memset (buf, g, n + n + n); + } + else + { + for (i = 0; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } +} +#endif + +#ifdef COLOR_RUN_COMPLEX +/* This implements the second of the two ideas above. The test results + are _very_ encouraging - it seems the speed is within 10% of + memset, which is quite good! */ +/** + * art_rgb_fill_run: fill a buffer a solid RGB color. + * @buf: Buffer to fill. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @n: Number of RGB triples to fill. + * + * Fills a buffer with @n copies of the (@r, @g, @b) triple. Thus, + * locations @buf (inclusive) through @buf + 3 * @n (exclusive) are + * written. + * + * The implementation of this routine is very highly optimized. + **/ +void +art_rgb_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) +{ + int i; + unsigned int v1, v2, v3; + + if (r == g && g == b) + { + memset (buf, g, n + n + n); + } + else + { + if (n < 8) + { + for (i = 0; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } else { + /* handle prefix up to byte alignment */ + /* I'm worried about this cast on sizeof(long) != sizeof(uchar *) + architectures, but it _should_ work. */ + for (i = 0; ((unsigned long)buf) & 3; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } +#ifndef WORDS_BIGENDIAN + v1 = r | (g << 8) | (b << 16) | (r << 24); + v3 = (v1 << 8) | b; + v2 = (v3 << 8) | g; +#else + v1 = (r << 24) | (g << 16) | (b << 8) | r; + v2 = (v1 << 8) | g; + v3 = (v2 << 8) | b; +#endif + for (; i < n - 3; i += 4) + { + ((art_u32 *)buf)[0] = v1; + ((art_u32 *)buf)[1] = v2; + ((art_u32 *)buf)[2] = v3; + buf += 12; + } + /* handle postfix */ + for (; i < n; i++) + { + *buf++ = r; + *buf++ = g; + *buf++ = b; + } + } + } +} +#endif + +/** + * art_rgb_run_alpha: Render semitransparent color over RGB buffer. + * @buf: Buffer for rendering. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @alpha: Alpha, range 0..256. + * @n: Number of RGB triples to render. + * + * Renders a sequential run of solid (@r, @g, @b) color over @buf with + * opacity @alpha. + **/ +void +art_rgb_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) +{ + int i; + int v; + + for (i = 0; i < n; i++) + { + v = *buf; + *buf++ = v + (((r - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((g - v) * alpha + 0x80) >> 8); + v = *buf; + *buf++ = v + (((b - v) * alpha + 0x80) >> 8); + } +} + diff --git a/lib/art/art_rgb_a_affine.c b/lib/art/art_rgb_a_affine.c new file mode 100644 index 0000000..ff38e2a --- /dev/null +++ b/lib/art/art_rgb_a_affine.c @@ -0,0 +1,149 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_a_affine.h" + +#include + +#include "art_affine.h" +#include "art_point.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed alpha only images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ + +/** + * art_rgb_a_affine: Affine transform source Alpha image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image alpha buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @rgb: RGB foreground color, in 0xRRGGBB. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the solid color rgb with alpha specified by the + * source image stored in @src, compositing over the area of destination + * image @dst specified by the rectangle (@x0, @y0) - (@x1, @y1). + * As usual in libart, the left and top edges of this rectangle are + * included, and the right and bottom edges are excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_a_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgb, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + int tmp; + int run_x0, run_x1; + art_u8 r, g, b; + + r = (rgb>>16)&0xff; + g = (rgb>>8)&0xff; + b = (rgb)&0xff; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x; + if (src_x >= 0 && src_x < src_width && + src_y >= 0 && src_y < src_height) + { + + alpha = *src_p; + if (alpha) + { + if (alpha == 255) + { + dst_p[0] = r; + dst_p[1] = g; + dst_p[2] = b; + } + else + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + tmp = (r - bg_r) * alpha; + fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (g - bg_g) * alpha; + fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (b - bg_b) * alpha; + fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + } + } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/lib/art/art_rgb_affine.c b/lib/art/art_rgb_affine.c new file mode 100644 index 0000000..1d82667 --- /dev/null +++ b/lib/art/art_rgb_affine.c @@ -0,0 +1,106 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_affine.h" + +#include +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed rgb images + over rgb pixel buffers. */ + +/** + * art_rgb_affine: Affine transform source RGB image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image RGB buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be done + * in a gamma-corrected color space. Since the source image is opaque RGB, + * this argument only affects the edges. In the current implementation, + * it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_affine (art_u8 *dst, int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int run_x0, run_x1; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x * 3; + dst_p[0] = src_p[0]; + dst_p[1] = src_p[1]; + dst_p[2] = src_p[2]; + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/lib/art/art_rgb_affine_private.c b/lib/art/art_rgb_affine_private.c new file mode 100644 index 0000000..679e114 --- /dev/null +++ b/lib/art/art_rgb_affine_private.c @@ -0,0 +1,127 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_affine_private.h" + +#include +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" + +/* Private functions for the rgb affine image compositors - primarily, + the determination of runs, eliminating the need for source image + bbox calculation in the inner loop. */ + +/* Determine a "run", such that the inverse affine of all pixels from + (x0, y) inclusive to (x1, y) exclusive fit within the bounds + of the source image. + + Initial values of x0, x1, and result values stored in first two + pointer arguments. +*/ + +#define EPSILON 1e-6 + +void +art_rgb_affine_run (int *p_x0, int *p_x1, int y, + int src_width, int src_height, + const double affine[6]) +{ + int x0, x1; + double z; + double x_intercept; + int xi; + + x0 = *p_x0; + x1 = *p_x1; + + /* do left and right edges */ + if (affine[0] > EPSILON) + { + z = affine[2] * (y + 0.5) + affine[4]; + x_intercept = -z / affine[0]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = (-z + src_width) / affine[0]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else if (affine[0] < -EPSILON) + { + z = affine[2] * (y + 0.5) + affine[4]; + x_intercept = (-z + src_width) / affine[0]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = -z / affine[0]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else + { + z = affine[2] * (y + 0.5) + affine[4]; + if (z < 0 || z >= src_width) + { + *p_x1 = *p_x0; + return; + } + } + + /* do top and bottom edges */ + if (affine[1] > EPSILON) + { + z = affine[3] * (y + 0.5) + affine[5]; + x_intercept = -z / affine[1]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = (-z + src_height) / affine[1]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else if (affine[1] < -EPSILON) + { + z = affine[3] * (y + 0.5) + affine[5]; + x_intercept = (-z + src_height) / affine[1]; + xi = ceil (x_intercept + EPSILON - 0.5); + if (xi > x0) + x0 = xi; + x_intercept = -z / affine[1]; + xi = ceil (x_intercept - EPSILON - 0.5); + if (xi < x1) + x1 = xi; + } + else + { + z = affine[3] * (y + 0.5) + affine[5]; + if (z < 0 || z >= src_height) + { + *p_x1 = *p_x0; + return; + } + } + + *p_x0 = x0; + *p_x1 = x1; +} diff --git a/lib/art/art_rgb_bitmap_affine.c b/lib/art/art_rgb_bitmap_affine.c new file mode 100644 index 0000000..825f8b5 --- /dev/null +++ b/lib/art/art_rgb_bitmap_affine.c @@ -0,0 +1,198 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_bitmap_affine.h" + +#include +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed bitmap images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. Foreground color is given and assumed to be + opaque, background color is assumed to be fully transparent. */ + +static void +art_rgb_bitmap_affine_opaque (art_u8 *dst, + int x0, int y0, int x1, int y1, + int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgb, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + art_u8 r, g, b; + int run_x0, run_x1; + + r = rgb >> 16; + g = (rgb >> 8) & 0xff; + b = rgb & 0xff; + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + (src_x >> 3); + if (*src_p & (128 >> (src_x & 7))) + { + dst_p[0] = r; + dst_p[1] = g; + dst_p[2] = b; + } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} +/* Composite the source image over the destination image, applying the + affine transform. Foreground color is given, background color is + assumed to be fully transparent. */ + +/** + * art_rgb_bitmap_affine: Affine transform source bitmap image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image bitmap buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @rgba: RGBA foreground color, in 0xRRGGBBAA. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). + * + * The source bitmap stored with MSB as the leftmost pixel. Source 1 + * bits correspond to the semitransparent color @rgba, while source 0 + * bits are transparent. + * + * See art_rgb_affine() for a description of additional parameters. + **/ +void +art_rgb_bitmap_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + art_u32 rgba, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + art_u8 r, g, b; + int run_x0, run_x1; + + alpha = rgba & 0xff; + if (alpha == 0xff) + { + art_rgb_bitmap_affine_opaque (dst, x0, y0, x1, y1, dst_rowstride, + src, + src_width, src_height, src_rowstride, + rgba >> 8, + affine, + level, + alphagamma); + return; + } + /* alpha = (65536 * alpha) / 255; */ + alpha = (alpha << 8) + alpha + (alpha >> 7); + r = rgba >> 24; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + (src_x >> 3); + if (*src_p & (128 >> (src_x & 7))) + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + fg_r = bg_r + (((r - bg_r) * alpha + 0x8000) >> 16); + fg_g = bg_g + (((g - bg_g) * alpha + 0x8000) >> 16); + fg_b = bg_b + (((b - bg_b) * alpha + 0x8000) >> 16); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/lib/art/art_rgb_pixbuf_affine.c b/lib/art/art_rgb_pixbuf_affine.c new file mode 100644 index 0000000..0a25b57 --- /dev/null +++ b/lib/art/art_rgb_pixbuf_affine.c @@ -0,0 +1,104 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_pixbuf_affine.h" + +#include +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_pixbuf.h" +#include "art_rgb_affine.h" +#include "art_rgb_affine.h" +#include "art_rgb_rgba_affine.h" + +/* This module handles compositing of affine-transformed generic + pixbuf images over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ +/** + * art_rgb_pixbuf_affine: Affine transform source RGB pixbuf and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @pixbuf: source image pixbuf. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_pixbuf_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const ArtPixBuf *pixbuf, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + if (pixbuf->format != ART_PIX_RGB) + { + art_warn ("art_rgb_pixbuf_affine: need RGB format image\n"); + return; + } + + if (pixbuf->bits_per_sample != 8) + { + art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n"); + return; + } + + if (pixbuf->n_channels != 3 + (pixbuf->has_alpha != 0)) + { + art_warn ("art_rgb_pixbuf_affine: need 8-bit sample data\n"); + return; + } + + if (pixbuf->has_alpha) + art_rgb_rgba_affine (dst, x0, y0, x1, y1, dst_rowstride, + pixbuf->pixels, + pixbuf->width, pixbuf->height, pixbuf->rowstride, + affine, + level, + alphagamma); + else + art_rgb_affine (dst, x0, y0, x1, y1, dst_rowstride, + pixbuf->pixels, + pixbuf->width, pixbuf->height, pixbuf->rowstride, + affine, + level, + alphagamma); +} diff --git a/lib/art/art_rgb_rgba_affine.c b/lib/art/art_rgb_rgba_affine.c new file mode 100644 index 0000000..41c7397 --- /dev/null +++ b/lib/art/art_rgb_rgba_affine.c @@ -0,0 +1,142 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgb_rgba_affine.h" + +#include +#include "art_misc.h" +#include "art_point.h" +#include "art_affine.h" +#include "art_rgb_affine_private.h" + +/* This module handles compositing of affine-transformed rgba images + over rgb pixel buffers. */ + +/* Composite the source image over the destination image, applying the + affine transform. */ + +/** + * art_rgb_rgba_affine: Affine transform source RGBA image and composite. + * @dst: Destination image RGB buffer. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @dst_rowstride: Rowstride of @dst buffer. + * @src: Source image RGBA buffer. + * @src_width: Width of source image. + * @src_height: Height of source image. + * @src_rowstride: Rowstride of @src buffer. + * @affine: Affine transform. + * @level: Filter level. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Affine transform the source image stored in @src, compositing over + * the area of destination image @dst specified by the rectangle + * (@x0, @y0) - (@x1, @y1). As usual in libart, the left and top edges + * of this rectangle are included, and the right and bottom edges are + * excluded. + * + * The @alphagamma parameter specifies that the alpha compositing be + * done in a gamma-corrected color space. In the current + * implementation, it is ignored. + * + * The @level parameter specifies the speed/quality tradeoff of the + * image interpolation. Currently, only ART_FILTER_NEAREST is + * implemented. + **/ +void +art_rgb_rgba_affine (art_u8 *dst, + int x0, int y0, int x1, int y1, int dst_rowstride, + const art_u8 *src, + int src_width, int src_height, int src_rowstride, + const double affine[6], + ArtFilterLevel level, + ArtAlphaGamma *alphagamma) +{ + /* Note: this is a slow implementation, and is missing all filter + levels other than NEAREST. It is here for clarity of presentation + and to establish the interface. */ + int x, y; + double inv[6]; + art_u8 *dst_p, *dst_linestart; + const art_u8 *src_p; + ArtPoint pt, src_pt; + int src_x, src_y; + int alpha; + art_u8 bg_r, bg_g, bg_b; + art_u8 fg_r, fg_g, fg_b; + int tmp; + int run_x0, run_x1; + + dst_linestart = dst; + art_affine_invert (inv, affine); + for (y = y0; y < y1; y++) + { + pt.y = y + 0.5; + run_x0 = x0; + run_x1 = x1; + art_rgb_affine_run (&run_x0, &run_x1, y, src_width, src_height, + inv); + dst_p = dst_linestart + (run_x0 - x0) * 3; + for (x = run_x0; x < run_x1; x++) + { + pt.x = x + 0.5; + art_affine_point (&src_pt, &pt, inv); + src_x = floor (src_pt.x); + src_y = floor (src_pt.y); + src_p = src + (src_y * src_rowstride) + src_x * 4; + if (src_x >= 0 && src_x < src_width && + src_y >= 0 && src_y < src_height) + { + + alpha = src_p[3]; + if (alpha) + { + if (alpha == 255) + { + dst_p[0] = src_p[0]; + dst_p[1] = src_p[1]; + dst_p[2] = src_p[2]; + } + else + { + bg_r = dst_p[0]; + bg_g = dst_p[1]; + bg_b = dst_p[2]; + + tmp = (src_p[0] - bg_r) * alpha; + fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (src_p[1] - bg_g) * alpha; + fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8); + tmp = (src_p[2] - bg_b) * alpha; + fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8); + + dst_p[0] = fg_r; + dst_p[1] = fg_g; + dst_p[2] = fg_b; + } + } + } else { dst_p[0] = 255; dst_p[1] = 0; dst_p[2] = 0; } + dst_p += 3; + } + dst_linestart += dst_rowstride; + } +} diff --git a/lib/art/art_rgb_svp.c b/lib/art/art_rgb_svp.c new file mode 100644 index 0000000..d031016 --- /dev/null +++ b/lib/art/art_rgb_svp.c @@ -0,0 +1,457 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Render a sorted vector path into an RGB buffer. */ + +#include "config.h" +#include "art_rgb_svp.h" + +#include "art_svp.h" +#include "art_svp_render_aa.h" +#include "art_rgb.h" + +typedef struct _ArtRgbSVPData ArtRgbSVPData; +typedef struct _ArtRgbSVPAlphaData ArtRgbSVPAlphaData; + +struct _ArtRgbSVPData { + art_u32 rgbtab[256]; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +struct _ArtRgbSVPAlphaData { + int alphatab[256]; + art_u8 r, g, b, alpha; + art_u8 *buf; + int rowstride; + int x0, x1; +}; + +static void +art_rgb_svp_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPData *data = (ArtRgbSVPData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + art_u32 rgb; + int x0, x1; + int k; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf + (run_x0 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf + (run_x1 - x0) * 3, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - run_x1); + } + } + else + { + rgb = data->rgbtab[(running_sum >> 16) & 0xff]; + art_rgb_fill_run (linebuf, + rgb >> 16, (rgb >> 8) & 0xff, rgb & 0xff, + x1 - x0); + } + + data->buf += data->rowstride; +} + +/* Render the vector path into the RGB buffer. */ + +/** + * art_rgb_svp_aa: Render sorted vector path into RGB buffer. + * @svp: The source sorted vector path. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @fg_color: Foreground color in 0xRRGGBB format. + * @bg_color: Background color in 0xRRGGBB format. + * @buf: Destination RGB buffer. + * @rowstride: Rowstride of @buf buffer. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the rendering. + * + * Renders the shape specified with @svp into the @buf RGB buffer. + * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height, + * of the rectangle rendered. The new pixels are stored starting at + * the first byte of @buf. Thus, the @x0 and @y0 parameters specify + * an offset within @svp, and may be tweaked as a way of doing + * integer-pixel translations without fiddling with @svp itself. + * + * The @fg_color and @bg_color arguments specify the opaque colors to + * be used for rendering. For pixels of entirely 0 winding-number, + * @bg_color is used. For pixels of entirely 1 winding number, + * @fg_color is used. In between, the color is interpolated based on + * the fraction of the pixel with a winding number of 1. If + * @alphagamma is NULL, then linear interpolation (in pixel counts) is + * the default. Otherwise, the interpolation is as specified by + * @alphagamma. + **/ +void +art_rgb_svp_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 fg_color, art_u32 bg_color, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) +{ + ArtRgbSVPData data; + + int r_fg, g_fg, b_fg; + int r_bg, g_bg, b_bg; + int r, g, b; + int dr, dg, db; + int i; + + if (alphagamma == NULL) + { + r_fg = fg_color >> 16; + g_fg = (fg_color >> 8) & 0xff; + b_fg = fg_color & 0xff; + + r_bg = bg_color >> 16; + g_bg = (bg_color >> 8) & 0xff; + b_bg = bg_color & 0xff; + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + dr = ((r_fg - r_bg) << 16) / 255; + dg = ((g_fg - g_bg) << 16) / 255; + db = ((b_fg - b_bg) << 16) / 255; + + for (i = 0; i < 256; i++) + { + data.rgbtab[i] = (r & 0xff0000) | ((g & 0xff0000) >> 8) | (b >> 16); + r += dr; + g += dg; + b += db; + } + } + else + { + int *table; + art_u8 *invtab; + + table = alphagamma->table; + + r_fg = table[fg_color >> 16]; + g_fg = table[(fg_color >> 8) & 0xff]; + b_fg = table[fg_color & 0xff]; + + r_bg = table[bg_color >> 16]; + g_bg = table[(bg_color >> 8) & 0xff]; + b_bg = table[bg_color & 0xff]; + + r = (r_bg << 16) + 0x8000; + g = (g_bg << 16) + 0x8000; + b = (b_bg << 16) + 0x8000; + dr = ((r_fg - r_bg) << 16) / 255; + dg = ((g_fg - g_bg) << 16) / 255; + db = ((b_fg - b_bg) << 16) / 255; + + invtab = alphagamma->invtable; + for (i = 0; i < 256; i++) + { + data.rgbtab[i] = (invtab[r >> 16] << 16) | + (invtab[g >> 16] << 8) | + invtab[b >> 16]; + r += dr; + g += dg; + b += db; + } + } + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_callback, &data); +} + +static void +art_rgb_svp_alpha_callback (void *callback_data, int y, + int start, ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + else + { + alpha = (running_sum >> 16) & 0xff; + if (alpha) + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + + data->buf += data->rowstride; +} + +static void +art_rgb_svp_alpha_opaque_callback (void *callback_data, int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps) +{ + ArtRgbSVPAlphaData *data = (ArtRgbSVPAlphaData *)callback_data; + art_u8 *linebuf; + int run_x0, run_x1; + art_u32 running_sum = start; + int x0, x1; + int k; + art_u8 r, g, b; + int *alphatab; + int alpha; + + linebuf = data->buf; + x0 = data->x0; + x1 = data->x1; + + r = data->r; + g = data->g; + b = data->b; + alphatab = data->alphatab; + + if (n_steps > 0) + { + run_x1 = steps[0].x; + if (run_x1 > x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf, + r, g, b, + run_x1 - x0); + else + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + run_x1 - x0); + } + } + + for (k = 0; k < n_steps - 1; k++) + { + running_sum += steps[k].delta; + run_x0 = run_x1; + run_x1 = steps[k + 1].x; + if (run_x1 > run_x0) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf + (run_x0 - x0) * 3, + r, g, b, + run_x1 - run_x0); + else + art_rgb_run_alpha (linebuf + (run_x0 - x0) * 3, + r, g, b, alphatab[alpha], + run_x1 - run_x0); + } + } + } + running_sum += steps[k].delta; + if (x1 > run_x1) + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf + (run_x1 - x0) * 3, + r, g, b, + x1 - run_x1); + else + art_rgb_run_alpha (linebuf + (run_x1 - x0) * 3, + r, g, b, alphatab[alpha], + x1 - run_x1); + } + } + } + else + { + alpha = running_sum >> 16; + if (alpha) + { + if (alpha >= 255) + art_rgb_fill_run (linebuf, + r, g, b, + x1 - x0); + else + art_rgb_run_alpha (linebuf, + r, g, b, alphatab[alpha], + x1 - x0); + } + } + + data->buf += data->rowstride; +} + +/** + * art_rgb_svp_alpha: Alpha-composite sorted vector path over RGB buffer. + * @svp: The source sorted vector path. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @rgba: Color in 0xRRGGBBAA format. + * @buf: Destination RGB buffer. + * @rowstride: Rowstride of @buf buffer. + * @alphagamma: #ArtAlphaGamma for gamma-correcting the compositing. + * + * Renders the shape specified with @svp over the @buf RGB buffer. + * @x1 - @x0 specifies the width, and @y1 - @y0 specifies the height, + * of the rectangle rendered. The new pixels are stored starting at + * the first byte of @buf. Thus, the @x0 and @y0 parameters specify + * an offset within @svp, and may be tweaked as a way of doing + * integer-pixel translations without fiddling with @svp itself. + * + * The @rgba argument specifies the color for the rendering. Pixels of + * entirely 0 winding number are left untouched. Pixels of entirely + * 1 winding number have the color @rgba composited over them (ie, + * are replaced by the red, green, blue components of @rgba if the alpha + * component is 0xff). Pixels of intermediate coverage are interpolated + * according to the rule in @alphagamma, or default to linear if + * @alphagamma is NULL. + **/ +void +art_rgb_svp_alpha (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + art_u32 rgba, + art_u8 *buf, int rowstride, + ArtAlphaGamma *alphagamma) +{ + ArtRgbSVPAlphaData data; + int r, g, b, alpha; + int i; + int a, da; + + r = rgba >> 24; + g = (rgba >> 16) & 0xff; + b = (rgba >> 8) & 0xff; + alpha = rgba & 0xff; + + data.r = r; + data.g = g; + data.b = b; + data.alpha = alpha; + + a = 0x8000; + da = (alpha * 66051 + 0x80) >> 8; /* 66051 equals 2 ^ 32 / (255 * 255) */ + + for (i = 0; i < 256; i++) + { + data.alphatab[i] = a >> 16; + a += da; + } + + data.buf = buf; + data.rowstride = rowstride; + data.x0 = x0; + data.x1 = x1; + if (alpha == 255) + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_opaque_callback, + &data); + else + art_svp_render_aa (svp, x0, y0, x1, y1, art_rgb_svp_alpha_callback, &data); +} + diff --git a/lib/art/art_rgba.c b/lib/art/art_rgba.c new file mode 100644 index 0000000..cde183c --- /dev/null +++ b/lib/art/art_rgba.c @@ -0,0 +1,258 @@ +/* + * art_rgba.c: Functions for manipulating RGBA pixel data. + * + * Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_rgba.h" + +#define ART_OPTIMIZE_SPACE + +#ifndef ART_OPTIMIZE_SPACE +#include "art_rgba_table.c" +#endif + +/** + * art_rgba_rgba_composite: Composite RGBA image over RGBA buffer. + * @dst: Destination RGBA buffer. + * @src: Source RGBA buffer. + * @n: Number of RGBA pixels to composite. + * + * Composites the RGBA pixels in @dst over the @src buffer. + **/ +void +art_rgba_rgba_composite (art_u8 *dst, const art_u8 *src, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba, dst_rgba; +#else + art_u32 src_abgr, dst_abgr; +#endif + art_u8 src_alpha, dst_alpha; + + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + src_rgba = ((art_u32 *)src)[i]; + src_alpha = src_rgba & 0xff; +#else + src_abgr = ((art_u32 *)src)[i]; + src_alpha = (src_abgr >> 24) & 0xff; +#endif + if (src_alpha) + { + if (src_alpha == 0xff || + ( +#ifdef WORDS_BIGENDIAN + dst_rgba = ((art_u32 *)dst)[i], + dst_alpha = dst_rgba & 0xff, +#else + dst_abgr = ((art_u32 *)dst)[i], + dst_alpha = (dst_abgr >> 24), +#endif + dst_alpha == 0)) +#ifdef WORDS_BIGENDIAN + ((art_u32 *)dst)[i] = src_rgba; +#else + ((art_u32 *)dst)[i] = src_abgr; +#endif + else + { + int r, g, b, a; + int src_r, src_g, src_b; + int dst_r, dst_g, dst_b; + int tmp; + int c; + +#ifdef ART_OPTIMIZE_SPACE + tmp = (255 - src_alpha) * (255 - dst_alpha) + 0x80; + a = 255 - ((tmp + (tmp >> 8)) >> 8); + c = ((src_alpha << 16) + (a >> 1)) / a; +#else + tmp = art_rgba_composite_table[(src_alpha << 8) + dst_alpha]; + c = tmp & 0x1ffff; + a = tmp >> 24; +#endif +#ifdef WORDS_BIGENDIAN + src_r = (src_rgba >> 24) & 0xff; + src_g = (src_rgba >> 16) & 0xff; + src_b = (src_rgba >> 8) & 0xff; + dst_r = (dst_rgba >> 24) & 0xff; + dst_g = (dst_rgba >> 16) & 0xff; + dst_b = (dst_rgba >> 8) & 0xff; +#else + src_r = src_abgr & 0xff; + src_g = (src_abgr >> 8) & 0xff; + src_b = (src_abgr >> 16) & 0xff; + dst_r = dst_abgr & 0xff; + dst_g = (dst_abgr >> 8) & 0xff; + dst_b = (dst_abgr >> 16) & 0xff; +#endif + r = dst_r + (((src_r - dst_r) * c + 0x8000) >> 16); + g = dst_g + (((src_g - dst_g) * c + 0x8000) >> 16); + b = dst_b + (((src_b - dst_b) * c + 0x8000) >> 16); +#ifdef WORDS_BIGENDIAN + ((art_u32 *)dst)[i] = (r << 24) | (g << 16) | (b << 8) | a; +#else + ((art_u32 *)dst)[i] = (a << 24) | (b << 16) | (g << 8) | r; +#endif + } + } +#if 0 + /* it's not clear to me this optimization really wins */ + else + { + /* skip over run of transparent pixels */ + for (; i < n - 1; i++) + { +#ifdef WORDS_BIGENDIAN + src_rgba = ((art_u32 *)src)[i + 1]; + if (src_rgba & 0xff) + break; +#else + src_abgr = ((art_u32 *)src)[i + 1]; + if (src_abgr & 0xff000000) + break; +#endif + } + } +#endif + } +} + +/** + * art_rgba_fill_run: fill an RGBA buffer a solid RGB color. + * @buf: Buffer to fill. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @n: Number of RGB triples to fill. + * + * Fills a buffer with @n copies of the (@r, @g, @b) triple, solid + * alpha. Thus, locations @buf (inclusive) through @buf + 4 * @n + * (exclusive) are written. + **/ +void +art_rgba_fill_run (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba; +#else + art_u32 src_abgr; +#endif + +#ifdef WORDS_BIGENDIAN + src_rgba = (r << 24) | (g << 16) | (b << 8) | 255; +#else + src_abgr = (255 << 24) | (b << 16) | (g << 8) | r; +#endif + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = src_rgba; +#else + ((art_u32 *)buf)[i] = src_abgr; +#endif + } +} + +/** + * art_rgba_run_alpha: Render semitransparent color over RGBA buffer. + * @buf: Buffer for rendering. + * @r: Red, range 0..255. + * @g: Green, range 0..255. + * @b: Blue, range 0..255. + * @alpha: Alpha, range 0..255. + * @n: Number of RGB triples to render. + * + * Renders a sequential run of solid (@r, @g, @b) color over @buf with + * opacity @alpha. Note that the range of @alpha is 0..255, in contrast + * to art_rgb_run_alpha, which has a range of 0..256. + **/ +void +art_rgba_run_alpha (art_u8 *buf, art_u8 r, art_u8 g, art_u8 b, int alpha, int n) +{ + int i; +#ifdef WORDS_BIGENDIAN + art_u32 src_rgba, dst_rgba; +#else + art_u32 src_abgr, dst_abgr; +#endif + art_u8 dst_alpha; + int a; + int dst_r, dst_g, dst_b; + int tmp; + int c; + +#ifdef WORDS_BIGENDIAN + src_rgba = (r << 24) | (g << 16) | (b << 8) | alpha; +#else + src_abgr = (alpha << 24) | (b << 16) | (g << 8) | r; +#endif + for (i = 0; i < n; i++) + { +#ifdef WORDS_BIGENDIAN + dst_rgba = ((art_u32 *)buf)[i]; + dst_alpha = dst_rgba & 0xff; +#else + dst_abgr = ((art_u32 *)buf)[i]; + dst_alpha = (dst_abgr >> 24) & 0xff; +#endif + if (dst_alpha) + { +#ifdef ART_OPTIMIZE_SPACE + tmp = (255 - alpha) * (255 - dst_alpha) + 0x80; + a = 255 - ((tmp + (tmp >> 8)) >> 8); + c = ((alpha << 16) + (a >> 1)) / a; +#else + tmp = art_rgba_composite_table[(alpha << 8) + dst_alpha]; + c = tmp & 0x1ffff; + a = tmp >> 24; +#endif +#ifdef WORDS_BIGENDIAN + dst_r = (dst_rgba >> 24) & 0xff; + dst_g = (dst_rgba >> 16) & 0xff; + dst_b = (dst_rgba >> 8) & 0xff; +#else + dst_r = dst_abgr & 0xff; + dst_g = (dst_abgr >> 8) & 0xff; + dst_b = (dst_abgr >> 16) & 0xff; +#endif + dst_r += (((r - dst_r) * c + 0x8000) >> 16); + dst_g += (((g - dst_g) * c + 0x8000) >> 16); + dst_b += (((b - dst_b) * c + 0x8000) >> 16); +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = (dst_r << 24) | (dst_g << 16) | (dst_b << 8) | a; +#else + ((art_u32 *)buf)[i] = (a << 24) | (dst_b << 16) | (dst_g << 8) | dst_r; +#endif + } + else + { +#ifdef WORDS_BIGENDIAN + ((art_u32 *)buf)[i] = src_rgba; +#else + ((art_u32 *)buf)[i] = src_abgr; +#endif + } + } +} diff --git a/lib/art/art_svp.c b/lib/art/art_svp.c new file mode 100644 index 0000000..8d7f7d1 --- /dev/null +++ b/lib/art/art_svp.c @@ -0,0 +1,152 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for sorted vector paths */ + +#include "config.h" +#include "art_svp.h" + +#include "art_misc.h" + +/* Add a new segment. The arguments can be zero and NULL if the caller + would rather fill them in later. + + We also realloc one auxiliary array of ints of size n_segs if + desired. +*/ +/** + * art_svp_add_segment: Add a segment to an #ArtSVP structure. + * @p_vp: Pointer to where the #ArtSVP structure is stored. + * @pn_segs_max: Pointer to the allocated size of *@p_vp. + * @pn_points_max: Pointer to where auxiliary array is stored. + * @n_points: Number of points for new segment. + * @dir: Direction for new segment; 0 is up, 1 is down. + * @points: Points for new segment. + * @bbox: Bounding box for new segment. + * + * Adds a new segment to an ArtSVP structure. This routine reallocates + * the structure if necessary, updating *@p_vp and *@pn_segs_max as + * necessary. + * + * The new segment is simply added after all other segments. Thus, + * this routine should be called in order consistent with the #ArtSVP + * sorting rules. + * + * If the @bbox argument is given, it is simply stored in the new + * segment. Otherwise (if it is NULL), the bounding box is computed + * from the @points given. + **/ +int +art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max, + int **pn_points_max, + int n_points, int dir, ArtPoint *points, + ArtDRect *bbox) +{ + int seg_num; + ArtSVP *svp; + ArtSVPSeg *seg; + + svp = *p_vp; + seg_num = svp->n_segs++; + if (*pn_segs_max == seg_num) + { + *pn_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (*pn_segs_max - 1) * sizeof(ArtSVPSeg)); + *p_vp = svp; + if (pn_points_max != NULL) + *pn_points_max = art_renew (*pn_points_max, int, *pn_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = n_points; + seg->dir = dir; + seg->points = points; + if (bbox) + seg->bbox = *bbox; + else if (points) + { + double x_min, x_max; + int i; + + x_min = x_max = points[0].x; + for (i = 1; i < n_points; i++) + { + if (x_min > points[i].x) + x_min = points[i].x; + if (x_max < points[i].x) + x_max = points[i].x; + } + seg->bbox.x0 = x_min; + seg->bbox.y0 = points[0].y; + + seg->bbox.x1 = x_max; + seg->bbox.y1 = points[n_points - 1].y; + } + return seg_num; +} + + +/** + * art_svp_free: Free an #ArtSVP structure. + * @svp: #ArtSVP to free. + * + * Frees an #ArtSVP structure and all the segments in it. + **/ +void +art_svp_free (ArtSVP *svp) +{ + int n_segs = svp->n_segs; + int i; + + for (i = 0; i < n_segs; i++) + art_free (svp->segs[i].points); + art_free (svp); +} + +#ifdef ART_USE_NEW_INTERSECTOR +#define EPSILON 0 +#else +#define EPSILON 1e-6 +#endif + +/** + * art_svp_seg_compare: Compare two segments of an svp. + * @seg1: First segment to compare. + * @seg2: Second segment to compare. + * + * Compares two segments of an svp. Return 1 if @seg2 is below or to the + * right of @seg1, -1 otherwise. + **/ +int +art_svp_seg_compare (const void *s1, const void *s2) +{ + const ArtSVPSeg *seg1 = s1; + const ArtSVPSeg *seg2 = s2; + + if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1; + else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1; + else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1; + else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1; + else if ((seg1->points[1].x - seg1->points[0].x) * + (seg2->points[1].y - seg2->points[0].y) - + (seg1->points[1].y - seg1->points[0].y) * + (seg2->points[1].x - seg2->points[0].x) > 0) return 1; + else return -1; +} + diff --git a/lib/art/art_svp_intersect.c b/lib/art/art_svp_intersect.c new file mode 100644 index 0000000..4ece5f4 --- /dev/null +++ b/lib/art/art_svp_intersect.c @@ -0,0 +1,1803 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 2001 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This file contains a testbed implementation of the new intersection + code. +*/ + +#include "config.h" +#include "art_svp_intersect.h" + +#include /* for sqrt */ + +/* Sanitychecking verifies the main invariant on every priority queue + point. Do not use in production, as it slows things down way too + much. */ +#define noSANITYCHECK + +/* This can be used in production, to prevent hangs. Eventually, it + should not be necessary. */ +#define CHEAP_SANITYCHECK + +#define noVERBOSE + +#include "art_misc.h" + +/* A priority queue - perhaps move to a separate file if it becomes + needed somewhere else */ + +#define ART_PRIQ_USE_HEAP + +typedef struct _ArtPriQ ArtPriQ; +typedef struct _ArtPriPoint ArtPriPoint; + +struct _ArtPriQ { + int n_items; + int n_items_max; + ArtPriPoint **items; +}; + +struct _ArtPriPoint { + double x; + double y; + void *user_data; +}; + +static ArtPriQ * +art_pri_new (void) +{ + ArtPriQ *result = art_new (ArtPriQ, 1); + + result->n_items = 0; + result->n_items_max = 16; + result->items = art_new (ArtPriPoint *, result->n_items_max); + return result; +} + +static void +art_pri_free (ArtPriQ *pq) +{ + art_free (pq->items); + art_free (pq); +} + +static art_boolean +art_pri_empty (ArtPriQ *pq) +{ + return pq->n_items == 0; +} + +#ifdef ART_PRIQ_USE_HEAP + +/* This heap implementation is based on Vasek Chvatal's course notes: + http://www.cs.rutgers.edu/~chvatal/notes/pq.html#heap */ + +static void +art_pri_bubble_up (ArtPriQ *pq, int vacant, ArtPriPoint *missing) +{ + ArtPriPoint **items = pq->items; + int parent; + + parent = (vacant - 1) >> 1; + while (vacant > 0 && (missing->y < items[parent]->y || + (missing->y == items[parent]->y && + missing->x < items[parent]->x))) + { + items[vacant] = items[parent]; + vacant = parent; + parent = (vacant - 1) >> 1; + } + + items[vacant] = missing; +} + +static void +art_pri_insert (ArtPriQ *pq, ArtPriPoint *point) +{ + if (pq->n_items == pq->n_items_max) + art_expand (pq->items, ArtPriPoint *, pq->n_items_max); + + art_pri_bubble_up (pq, pq->n_items++, point); +} + +static void +art_pri_sift_down_from_root (ArtPriQ *pq, ArtPriPoint *missing) +{ + ArtPriPoint **items = pq->items; + int vacant = 0, child = 2; + int n = pq->n_items; + + while (child < n) + { + if (items[child - 1]->y < items[child]->y || + (items[child - 1]->y == items[child]->y && + items[child - 1]->x < items[child]->x)) + child--; + items[vacant] = items[child]; + vacant = child; + child = (vacant + 1) << 1; + } + if (child == n) + { + items[vacant] = items[n - 1]; + vacant = n - 1; + } + + art_pri_bubble_up (pq, vacant, missing); +} + +static ArtPriPoint * +art_pri_choose (ArtPriQ *pq) +{ + ArtPriPoint *result = pq->items[0]; + + art_pri_sift_down_from_root (pq, pq->items[--pq->n_items]); + return result; +} + +#else + +/* Choose least point in queue */ +static ArtPriPoint * +art_pri_choose (ArtPriQ *pq) +{ + int i; + int best = 0; + double best_x, best_y; + double y; + ArtPriPoint *result; + + if (pq->n_items == 0) + return NULL; + + best_x = pq->items[best]->x; + best_y = pq->items[best]->y; + + for (i = 1; i < pq->n_items; i++) + { + y = pq->items[i]->y; + if (y < best_y || (y == best_y && pq->items[i]->x < best_x)) + { + best = i; + best_x = pq->items[best]->x; + best_y = y; + } + } + result = pq->items[best]; + pq->items[best] = pq->items[--pq->n_items]; + return result; +} + +static void +art_pri_insert (ArtPriQ *pq, ArtPriPoint *point) +{ + if (pq->n_items == pq->n_items_max) + art_expand (pq->items, ArtPriPoint *, pq->n_items_max); + + pq->items[pq->n_items++] = point; +} + +#endif + +#ifdef TEST_PRIQ + +#include /* for rand() */ +#include + +static double +double_rand (double lo, double hi, int quant) +{ + int tmp = rand () / (RAND_MAX * (1.0 / quant)) + 0.5; + return lo + tmp * ((hi - lo) / quant); +} + +/* + * This custom allocator for priority queue points is here so I can + * test speed. It doesn't look like it will be that significant, but + * if I want a small improvement later, it's something. + */ + +typedef ArtPriPoint *ArtPriPtPool; + +static ArtPriPtPool * +art_pri_pt_pool_new (void) +{ + ArtPriPtPool *result = art_new (ArtPriPtPool, 1); + *result = NULL; + return result; +} + +static ArtPriPoint * +art_pri_pt_alloc (ArtPriPtPool *pool) +{ + ArtPriPoint *result = *pool; + if (result == NULL) + return art_new (ArtPriPoint, 1); + else + { + *pool = result->user_data; + return result; + } +} + +static void +art_pri_pt_free (ArtPriPtPool *pool, ArtPriPoint *pt) +{ + pt->user_data = *pool; + *pool = pt; +} + +static void +art_pri_pt_pool_free (ArtPriPtPool *pool) +{ + ArtPriPoint *pt = *pool; + while (pt != NULL) + { + ArtPriPoint *next = pt->user_data; + art_free (pt); + pt = next; + } + art_free (pool); +} + +int +main (int argc, char **argv) +{ + ArtPriPtPool *pool = art_pri_pt_pool_new (); + ArtPriQ *pq; + int i, j; + const int n_iter = 1; + const int pq_size = 100; + + for (j = 0; j < n_iter; j++) + { + pq = art_pri_new (); + + for (i = 0; i < pq_size; i++) + { + ArtPriPoint *pt = art_pri_pt_alloc (pool); + pt->x = double_rand (0, 1, 100); + pt->y = double_rand (0, 1, 100); + pt->user_data = (void *)i; + art_pri_insert (pq, pt); + } + + while (!art_pri_empty (pq)) + { + ArtPriPoint *pt = art_pri_choose (pq); + if (n_iter == 1) + printf ("(%g, %g), %d\n", pt->x, pt->y, (int)pt->user_data); + art_pri_pt_free (pool, pt); + } + + art_pri_free (pq); + } + art_pri_pt_pool_free (pool); + return 0; +} + +#else /* TEST_PRIQ */ + +/* A virtual class for an "svp writer". A client of this object creates an + SVP by repeatedly calling "add segment" and "add point" methods on it. +*/ + +typedef struct _ArtSvpWriterRewind ArtSvpWriterRewind; + +/* An implementation of the svp writer virtual class that applies the + winding rule. */ + +struct _ArtSvpWriterRewind { + ArtSvpWriter super; + ArtWindRule rule; + ArtSVP *svp; + int n_segs_max; + int *n_points_max; +}; + +static int +art_svp_writer_rewind_add_segment (ArtSvpWriter *self, int wind_left, + int delta_wind, double x, double y) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *svp; + ArtSVPSeg *seg; + art_boolean left_filled, right_filled; + int wind_right = wind_left + delta_wind; + int seg_num; + const int init_n_points_max = 4; + + switch (swr->rule) + { + case ART_WIND_RULE_NONZERO: + left_filled = (wind_left != 0); + right_filled = (wind_right != 0); + break; + case ART_WIND_RULE_INTERSECT: + left_filled = (wind_left > 1); + right_filled = (wind_right > 1); + break; + case ART_WIND_RULE_ODDEVEN: + left_filled = (wind_left & 1); + right_filled = (wind_right & 1); + break; + case ART_WIND_RULE_POSITIVE: + left_filled = (wind_left > 0); + right_filled = (wind_right > 0); + break; + default: + art_die ("Unknown wind rule %d\n", swr->rule); + } + if (left_filled == right_filled) + { + /* discard segment now */ +#ifdef VERBOSE + art_dprint ("swr add_segment: %d += %d (%g, %g) --> -1\n", + wind_left, delta_wind, x, y); +#endif + return -1; + } + + svp = swr->svp; + seg_num = svp->n_segs++; + if (swr->n_segs_max == seg_num) + { + swr->n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (swr->n_segs_max - 1) * + sizeof(ArtSVPSeg)); + swr->svp = svp; + swr->n_points_max = art_renew (swr->n_points_max, int, + swr->n_segs_max); + } + seg = &svp->segs[seg_num]; + seg->n_points = 1; + seg->dir = right_filled; + swr->n_points_max[seg_num] = init_n_points_max; + seg->bbox.x0 = x; + seg->bbox.y0 = y; + seg->bbox.x1 = x; + seg->bbox.y1 = y; + seg->points = art_new (ArtPoint, init_n_points_max); + seg->points[0].x = x; + seg->points[0].y = y; +#ifdef VERBOSE + art_dprint ("swr add_segment: %d += %d (%g, %g) --> %d(%s)\n", + wind_left, delta_wind, x, y, seg_num, + seg->dir ? "v" : "^"); +#endif + return seg_num; +} + +static void +art_svp_writer_rewind_add_point (ArtSvpWriter *self, int seg_id, + double x, double y) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + int n_points; + +#ifdef VERBOSE + art_dprint ("swr add_point: %d (%g, %g)\n", seg_id, x, y); +#endif + if (seg_id < 0) + /* omitted segment */ + return; + + seg = &swr->svp->segs[seg_id]; + n_points = seg->n_points++; + if (swr->n_points_max[seg_id] == n_points) + art_expand (seg->points, ArtPoint, swr->n_points_max[seg_id]); + seg->points[n_points].x = x; + seg->points[n_points].y = y; + if (x < seg->bbox.x0) + seg->bbox.x0 = x; + if (x > seg->bbox.x1) + seg->bbox.x1 = x; + seg->bbox.y1 = y; +} + +static void +art_svp_writer_rewind_close_segment (ArtSvpWriter *self, int seg_id) +{ + /* Not needed for this simple implementation. A potential future + optimization is to merge segments that can be merged safely. */ +#ifdef SANITYCHECK + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVPSeg *seg; + + if (seg_id >= 0) + { + seg = &swr->svp->segs[seg_id]; + if (seg->n_points < 2) + art_warn ("*** closing segment %d with only %d point%s\n", + seg_id, seg->n_points, seg->n_points == 1 ? "" : "s"); + } +#endif + +#ifdef VERBOSE + art_dprint ("swr close_segment: %d\n", seg_id); +#endif +} + +ArtSVP * +art_svp_writer_rewind_reap (ArtSvpWriter *self) +{ + ArtSvpWriterRewind *swr = (ArtSvpWriterRewind *)self; + ArtSVP *result = swr->svp; + + art_free (swr->n_points_max); + art_free (swr); + return result; +} + +ArtSvpWriter * +art_svp_writer_rewind_new (ArtWindRule rule) +{ + ArtSvpWriterRewind *result = art_new (ArtSvpWriterRewind, 1); + + result->super.add_segment = art_svp_writer_rewind_add_segment; + result->super.add_point = art_svp_writer_rewind_add_point; + result->super.close_segment = art_svp_writer_rewind_close_segment; + + result->rule = rule; + result->n_segs_max = 16; + result->svp = art_alloc (sizeof(ArtSVP) + + (result->n_segs_max - 1) * sizeof(ArtSVPSeg)); + result->svp->n_segs = 0; + result->n_points_max = art_new (int, result->n_segs_max); + + return &result->super; +} + +/* Now, data structures for the active list */ + +typedef struct _ArtActiveSeg ArtActiveSeg; + +/* Note: BNEG is 1 for \ lines, and 0 for /. Thus, + x[(flags & BNEG) ^ 1] <= x[flags & BNEG] */ +#define ART_ACTIVE_FLAGS_BNEG 1 + +/* This flag is set if the segment has been inserted into the active + list. */ +#define ART_ACTIVE_FLAGS_IN_ACTIVE 2 + +/* This flag is set when the segment is to be deleted in the + horiz commit process. */ +#define ART_ACTIVE_FLAGS_DEL 4 + +/* This flag is set if the seg_id is a valid output segment. */ +#define ART_ACTIVE_FLAGS_OUT 8 + +/* This flag is set if the segment is in the horiz list. */ +#define ART_ACTIVE_FLAGS_IN_HORIZ 16 + +struct _ArtActiveSeg { + int flags; + int wind_left, delta_wind; + ArtActiveSeg *left, *right; /* doubly linked list structure */ + + const ArtSVPSeg *in_seg; + int in_curs; + + double x[2]; + double y0, y1; + double a, b, c; /* line equation; ax+by+c = 0 for the line, a^2 + b^2 = 1, + and a>0 */ + + /* bottom point and intersection point stack */ + int n_stack; + int n_stack_max; + ArtPoint *stack; + + /* horiz commit list */ + ArtActiveSeg *horiz_left, *horiz_right; + double horiz_x; + int horiz_delta_wind; + int seg_id; +}; + +typedef struct _ArtIntersectCtx ArtIntersectCtx; + +struct _ArtIntersectCtx { + const ArtSVP *in; + ArtSvpWriter *out; + + ArtPriQ *pq; + + ArtActiveSeg *active_head; + + double y; + ArtActiveSeg *horiz_first; + ArtActiveSeg *horiz_last; + + /* segment index of next input segment to be added to pri q */ + int in_curs; +}; + +#define EPSILON_A 1e-5 /* Threshold for breaking lines at point insertions */ + +/** + * art_svp_intersect_setup_seg: Set up an active segment from input segment. + * @seg: Active segment. + * @pri_pt: Priority queue point to initialize. + * + * Sets the x[], a, b, c, flags, and stack fields according to the + * line from the current cursor value. Sets the priority queue point + * to the bottom point of this line. Also advances the input segment + * cursor. + **/ +static void +art_svp_intersect_setup_seg (ArtActiveSeg *seg, ArtPriPoint *pri_pt) +{ + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs++; + double x0, y0, x1, y1; + double dx, dy, s; + double a, b, r2; + + x0 = in_seg->points[in_curs].x; + y0 = in_seg->points[in_curs].y; + x1 = in_seg->points[in_curs + 1].x; + y1 = in_seg->points[in_curs + 1].y; + pri_pt->x = x1; + pri_pt->y = y1; + dx = x1 - x0; + dy = y1 - y0; + r2 = dx * dx + dy * dy; + s = r2 == 0 ? 1 : 1 / sqrt (r2); + seg->a = a = dy * s; + seg->b = b = -dx * s; + seg->c = -(a * x0 + b * y0); + seg->flags = (seg->flags & ~ART_ACTIVE_FLAGS_BNEG) | (dx > 0); + seg->x[0] = x0; + seg->x[1] = x1; + seg->y0 = y0; + seg->y1 = y1; + seg->n_stack = 1; + seg->stack[0].x = x1; + seg->stack[0].y = y1; +} + +/** + * art_svp_intersect_add_horiz: Add point to horizontal list. + * @ctx: Intersector context. + * @seg: Segment with point to insert into horizontal list. + * + * Inserts @seg into horizontal list, keeping it in ascending horiz_x + * order. + * + * Note: the horiz_commit routine processes "clusters" of segs in the + * horiz list, all sharing the same horiz_x value. The cluster is + * processed in active list order, rather than horiz list order. Thus, + * the order of segs in the horiz list sharing the same horiz_x + * _should_ be irrelevant. Even so, we use b as a secondary sorting key, + * as a "belt and suspenders" defensive coding tactic. + **/ +static void +art_svp_intersect_add_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + ArtActiveSeg **pp = &ctx->horiz_last; + ArtActiveSeg *place; + ArtActiveSeg *place_right = NULL; + + +#ifdef CHEAP_SANITYCHECK + if (seg->flags & ART_ACTIVE_FLAGS_IN_HORIZ) + { + art_warn ("*** attempt to put segment in horiz list twice\n"); + return; + } + seg->flags |= ART_ACTIVE_FLAGS_IN_HORIZ; +#endif + +#ifdef VERBOSE + art_dprint ("add_horiz %lx, x = %g\n", (unsigned long) seg, seg->horiz_x); +#endif + for (place = *pp; place != NULL && (place->horiz_x > seg->horiz_x || + (place->horiz_x == seg->horiz_x && + place->b < seg->b)); + place = *pp) + { + place_right = place; + pp = &place->horiz_left; + } + *pp = seg; + seg->horiz_left = place; + seg->horiz_right = place_right; + if (place == NULL) + ctx->horiz_first = seg; + else + place->horiz_right = seg; +} + +static void +art_svp_intersect_push_pt (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x, double y) +{ + ArtPriPoint *pri_pt; + int n_stack = seg->n_stack; + + if (n_stack == seg->n_stack_max) + art_expand (seg->stack, ArtPoint, seg->n_stack_max); + seg->stack[n_stack].x = x; + seg->stack[n_stack].y = y; + seg->n_stack++; + + seg->x[1] = x; + seg->y1 = y; + + pri_pt = art_new (ArtPriPoint, 1); + pri_pt->x = x; + pri_pt->y = y; + pri_pt->user_data = seg; + art_pri_insert (ctx->pq, pri_pt); +} + +typedef enum { + ART_BREAK_LEFT = 1, + ART_BREAK_RIGHT = 2 +} ArtBreakFlags; + +/** + * art_svp_intersect_break: Break an active segment. + * + * Note: y must be greater than the top point's y, and less than + * the bottom's. + * + * Return value: x coordinate of break point. + */ +static double +art_svp_intersect_break (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x_ref, double y, ArtBreakFlags break_flags) +{ + double x0, y0, x1, y1; + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + double x; + + x0 = in_seg->points[in_curs - 1].x; + y0 = in_seg->points[in_curs - 1].y; + x1 = in_seg->points[in_curs].x; + y1 = in_seg->points[in_curs].y; + x = x0 + (x1 - x0) * ((y - y0) / (y1 - y0)); + if ((break_flags == ART_BREAK_LEFT && x > x_ref) || + (break_flags == ART_BREAK_RIGHT && x < x_ref)) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_break: limiting x to %f, was %f, %s\n", + x_ref, x, break_flags == ART_BREAK_LEFT ? "left" : "right"); + x = x_ref; +#endif + } + + /* I think we can count on min(x0, x1) <= x <= max(x0, x1) with sane + arithmetic, but it might be worthwhile to check just in case. */ + + if (y > ctx->y) + art_svp_intersect_push_pt (ctx, seg, x, y); + else + { + seg->x[0] = x; + seg->y0 = y; + seg->horiz_x = x; + art_svp_intersect_add_horiz (ctx, seg); + } + + return x; +} + +/** + * art_svp_intersect_add_point: Add a point, breaking nearby neighbors. + * @ctx: Intersector context. + * @x: X coordinate of point to add. + * @y: Y coordinate of point to add. + * @seg: "nearby" segment, or NULL if leftmost. + * + * Return value: Segment immediately to the left of the new point, or + * NULL if the new point is leftmost. + **/ +static ArtActiveSeg * +art_svp_intersect_add_point (ArtIntersectCtx *ctx, double x, double y, + ArtActiveSeg *seg, ArtBreakFlags break_flags) +{ + ArtActiveSeg *left, *right; + double x_min = x, x_max = x; + art_boolean left_live, right_live; + double d; + double new_x; + ArtActiveSeg *test, *result = NULL; + double x_test; + + left = seg; + if (left == NULL) + right = ctx->active_head; + else + right = left->right; + left_live = (break_flags & ART_BREAK_LEFT) && (left != NULL); + right_live = (break_flags & ART_BREAK_RIGHT) && (right != NULL); + while (left_live || right_live) + { + if (left_live) + { + if (x <= left->x[left->flags & ART_ACTIVE_FLAGS_BNEG] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != left->y0 && y < left->y1) + { + d = x_min * left->a + y * left->b + left->c; + if (d < EPSILON_A) + { + new_x = art_svp_intersect_break (ctx, left, x_min, y, + ART_BREAK_LEFT); + if (new_x > x_max) + { + x_max = new_x; + right_live = (right != NULL); + } + else if (new_x < x_min) + x_min = new_x; + left = left->left; + left_live = (left != NULL); + } + else + left_live = ART_FALSE; + } + else + left_live = ART_FALSE; + } + else if (right_live) + { + if (x >= right->x[(right->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] && + /* It may be that one of these conjuncts turns out to be always + true. We test both anyway, to be defensive. */ + y != right->y0 && y < right->y1) + { + d = x_max * right->a + y * right->b + right->c; + if (d > -EPSILON_A) + { + new_x = art_svp_intersect_break (ctx, right, x_max, y, + ART_BREAK_RIGHT); + if (new_x < x_min) + { + x_min = new_x; + left_live = (left != NULL); + } + else if (new_x >= x_max) + x_max = new_x; + right = right->right; + right_live = (right != NULL); + } + else + right_live = ART_FALSE; + } + else + right_live = ART_FALSE; + } + } + + /* Ascending order is guaranteed by break_flags. Thus, we don't need + to actually fix up non-ascending pairs. */ + + /* Now, (left, right) defines an interval of segments broken. Sort + into ascending x order. */ + test = left == NULL ? ctx->active_head : left->right; + result = left; + if (test != NULL && test != right) + { + if (y == test->y0) + x_test = test->x[0]; + else /* assert y == test->y1, I think */ + x_test = test->x[1]; + for (;;) + { + if (x_test <= x) + result = test; + test = test->right; + if (test == right) + break; + new_x = x_test; + if (new_x < x_test) + { + art_warn ("art_svp_intersect_add_point: non-ascending x\n"); + } + x_test = new_x; + } + } + return result; +} + +static void +art_svp_intersect_swap_active (ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg) +{ + right_seg->left = left_seg->left; + if (right_seg->left != NULL) + right_seg->left->right = right_seg; + else + ctx->active_head = right_seg; + left_seg->right = right_seg->right; + if (left_seg->right != NULL) + left_seg->right->left = left_seg; + left_seg->left = right_seg; + right_seg->right = left_seg; +} + +/** + * art_svp_intersect_test_cross: Test crossing of a pair of active segments. + * @ctx: Intersector context. + * @left_seg: Left segment of the pair. + * @right_seg: Right segment of the pair. + * @break_flags: Flags indicating whether to break neighbors. + * + * Tests crossing of @left_seg and @right_seg. If there is a crossing, + * inserts the intersection point into both segments. + * + * Return value: True if the intersection took place at the current + * scan line, indicating further iteration is needed. + **/ +static art_boolean +art_svp_intersect_test_cross (ArtIntersectCtx *ctx, + ArtActiveSeg *left_seg, ArtActiveSeg *right_seg, + ArtBreakFlags break_flags) +{ + double left_x0, left_y0, left_x1; + double left_y1 = left_seg->y1; + double right_y1 = right_seg->y1; + double d; + + const ArtSVPSeg *in_seg; + int in_curs; + double d0, d1, t; + double x, y; /* intersection point */ + +#ifdef VERBOSE + static int count = 0; + + art_dprint ("art_svp_intersect_test_cross %lx <-> %lx: count=%d\n", + (unsigned long)left_seg, (unsigned long)right_seg, count++); +#endif + + if (left_seg->y0 == right_seg->y0 && left_seg->x[0] == right_seg->x[0]) + { + /* Top points of left and right segments coincide. This case + feels like a bit of duplication - we may want to merge it + with the cases below. However, this way, we're sure that this + logic makes only localized changes. */ + + if (left_y1 < right_y1) + { + /* Test left (x1, y1) against right segment */ + double left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) + { + /* I'm unsure about the break flags here. */ + double right_x1 = art_svp_intersect_break (ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else if (left_y1 > right_y1) + { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) + { + /* See above regarding break flags. */ + double left_x1 = art_svp_intersect_break (ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else /* left_y1 == right_y1 */ + { + double left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + art_svp_intersect_swap_active (ctx, left_seg, right_seg); + return ART_TRUE; + } + + if (left_y1 < right_y1) + { + /* Test left (x1, y1) against right segment */ + double left_x1 = left_seg->x[1]; + + if (left_x1 < + right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1] || + left_y1 == right_seg->y0) + return ART_FALSE; + d = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d < -EPSILON_A) + return ART_FALSE; + else if (d < EPSILON_A) + { + double right_x1 = art_svp_intersect_break (ctx, right_seg, + left_x1, left_y1, + ART_BREAK_RIGHT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else if (left_y1 > right_y1) + { + /* Test right (x1, y1) against left segment */ + double right_x1 = right_seg->x[1]; + + if (right_x1 > left_seg->x[left_seg->flags & ART_ACTIVE_FLAGS_BNEG] || + right_y1 == left_seg->y0) + return ART_FALSE; + d = right_x1 * left_seg->a + right_y1 * left_seg->b + left_seg->c; + if (d > EPSILON_A) + return ART_FALSE; + else if (d > -EPSILON_A) + { + double left_x1 = art_svp_intersect_break (ctx, left_seg, + right_x1, right_y1, + ART_BREAK_LEFT); + if (left_x1 <= right_x1) + return ART_FALSE; + } + } + else /* left_y1 == right_y1 */ + { + double left_x1 = left_seg->x[1]; + double right_x1 = right_seg->x[1]; + + if (left_x1 <= right_x1) + return ART_FALSE; + } + + /* The segments cross. Find the intersection point. */ + + in_seg = left_seg->in_seg; + in_curs = left_seg->in_curs; + left_x0 = in_seg->points[in_curs - 1].x; + left_y0 = in_seg->points[in_curs - 1].y; + left_x1 = in_seg->points[in_curs].x; + left_y1 = in_seg->points[in_curs].y; + d0 = left_x0 * right_seg->a + left_y0 * right_seg->b + right_seg->c; + d1 = left_x1 * right_seg->a + left_y1 * right_seg->b + right_seg->c; + if (d0 == d1) + { + x = left_x0; + y = left_y0; + } + else + { + /* Is this division always safe? It could possibly overflow. */ + t = d0 / (d0 - d1); + if (t <= 0) + { + x = left_x0; + y = left_y0; + } + else if (t >= 1) + { + x = left_x1; + y = left_y1; + } + else + { + x = left_x0 + t * (left_x1 - left_x0); + y = left_y0 + t * (left_y1 - left_y0); + } + } + + /* Make sure intersection point is within bounds of right seg. */ + if (y < right_seg->y0) + { + x = right_seg->x[0]; + y = right_seg->y0; + } + else if (y > right_seg->y1) + { + x = right_seg->x[1]; + y = right_seg->y1; + } + else if (x < right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) + x = right_seg->x[(right_seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]; + else if (x > right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]) + x = right_seg->x[right_seg->flags & ART_ACTIVE_FLAGS_BNEG]; + + if (y == left_seg->y0) + { + if (y != right_seg->y0) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_test_cross: intersection (%g, %g) matches former y0 of %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + art_svp_intersect_push_pt (ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point (ctx, x, y, right_seg->right, + break_flags); + } + else + { + /* Intersection takes place at current scan line; process + immediately rather than queueing intersection point into + priq. */ + ArtActiveSeg *winner, *loser; + + /* Choose "most vertical" segement */ + if (left_seg->a > right_seg->a) + { + winner = left_seg; + loser = right_seg; + } + else + { + winner = right_seg; + loser = left_seg; + } + + loser->x[0] = winner->x[0]; + loser->horiz_x = loser->x[0]; + loser->horiz_delta_wind += loser->delta_wind; + winner->horiz_delta_wind -= loser->delta_wind; + + art_svp_intersect_swap_active (ctx, left_seg, right_seg); + return ART_TRUE; + } + } + else if (y == right_seg->y0) + { +#ifdef VERBOSE + art_dprint ("*** art_svp_intersect_test_cross: intersection (%g, %g) matches latter y0 of %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + art_svp_intersect_push_pt (ctx, left_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point (ctx, x, y, left_seg->left, + break_flags); + } + else + { +#ifdef VERBOSE + art_dprint ("Inserting (%g, %g) into %lx, %lx\n", + x, y, (unsigned long)left_seg, (unsigned long)right_seg); +#endif + /* Insert the intersection point into both segments. */ + art_svp_intersect_push_pt (ctx, left_seg, x, y); + art_svp_intersect_push_pt (ctx, right_seg, x, y); + if ((break_flags & ART_BREAK_LEFT) && left_seg->left != NULL) + art_svp_intersect_add_point (ctx, x, y, left_seg->left, break_flags); + if ((break_flags & ART_BREAK_RIGHT) && right_seg->right != NULL) + art_svp_intersect_add_point (ctx, x, y, right_seg->right, break_flags); + } + return ART_FALSE; +} + +/** + * art_svp_intersect_active_delete: Delete segment from active list. + * @ctx: Intersection context. + * @seg: Segment to delete. + * + * Deletes @seg from the active list. + **/ +static /* todo inline */ void +art_svp_intersect_active_delete (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + ArtActiveSeg *left = seg->left, *right = seg->right; + + if (left != NULL) + left->right = right; + else + ctx->active_head = right; + if (right != NULL) + right->left = left; +} + +/** + * art_svp_intersect_active_free: Free an active segment. + * @seg: Segment to delete. + * + * Frees @seg. + **/ +static /* todo inline */ void +art_svp_intersect_active_free (ArtActiveSeg *seg) +{ + art_free (seg->stack); +#ifdef VERBOSE + art_dprint ("Freeing %lx\n", (unsigned long) seg); +#endif + art_free (seg); +} + +/** + * art_svp_intersect_insert_cross: Test crossings of newly inserted line. + * + * Tests @seg against its left and right neighbors for intersections. + * Precondition: the line in @seg is not purely horizontal. + **/ +static void +art_svp_intersect_insert_cross (ArtIntersectCtx *ctx, + ArtActiveSeg *seg) +{ + ArtActiveSeg *left = seg, *right = seg; + + for (;;) + { + if (left != NULL) + { + ArtActiveSeg *leftc; + + for (leftc = left->left; leftc != NULL; leftc = leftc->left) + if (!(leftc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (leftc != NULL && + art_svp_intersect_test_cross (ctx, leftc, left, + ART_BREAK_LEFT)) + { + if (left == right || right == NULL) + right = left->right; + } + else + { + left = NULL; + } + } + else if (right != NULL && right->right != NULL) + { + ArtActiveSeg *rightc; + + for (rightc = right->right; rightc != NULL; rightc = rightc->right) + if (!(rightc->flags & ART_ACTIVE_FLAGS_DEL)) + break; + if (rightc != NULL && + art_svp_intersect_test_cross (ctx, right, rightc, + ART_BREAK_RIGHT)) + { + if (left == right || left == NULL) + left = right->left; + } + else + { + right = NULL; + } + } + else + break; + } +} + +/** + * art_svp_intersect_horiz: Add horizontal line segment. + * @ctx: Intersector context. + * @seg: Segment on which to add horizontal line. + * @x0: Old x position. + * @x1: New x position. + * + * Adds a horizontal line from @x0 to @x1, and updates the current + * location of @seg to @x1. + **/ +static void +art_svp_intersect_horiz (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + double x0, double x1) +{ + ArtActiveSeg *hs; + + if (x0 == x1) + return; + + hs = art_new (ArtActiveSeg, 1); + + hs->flags = ART_ACTIVE_FLAGS_DEL | (seg->flags & ART_ACTIVE_FLAGS_OUT); + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + ArtSvpWriter *swr = ctx->out; + + swr->add_point (swr, seg->seg_id, x0, ctx->y); + } + hs->seg_id = seg->seg_id; + hs->horiz_x = x0; + hs->horiz_delta_wind = seg->delta_wind; + hs->stack = NULL; + + /* Ideally, the (a, b, c) values will never be read. However, there + are probably some tests remaining that don't check for _DEL + before evaluating the line equation. For those, these + initializations will at least prevent a UMR of the values, which + can crash on some platforms. */ + hs->a = 0.0; + hs->b = 0.0; + hs->c = 0.0; + + seg->horiz_delta_wind -= seg->delta_wind; + + art_svp_intersect_add_horiz (ctx, hs); + + if (x0 > x1) + { + ArtActiveSeg *left; + art_boolean first = ART_TRUE; + + for (left = seg->left; left != NULL; left = seg->left) + { + int left_bneg = left->flags & ART_ACTIVE_FLAGS_BNEG; + + if (left->x[left_bneg] <= x1) + break; + if (left->x[left_bneg ^ 1] <= x1 && + x1 * left->a + ctx->y * left->b + left->c >= 0) + break; + if (left->y0 != ctx->y && left->y1 != ctx->y) + { + art_svp_intersect_break (ctx, left, x1, ctx->y, ART_BREAK_LEFT); + } +#ifdef VERBOSE + art_dprint ("x0=%g > x1=%g, swapping %lx, %lx\n", + x0, x1, (unsigned long)left, (unsigned long)seg); +#endif + art_svp_intersect_swap_active (ctx, left, seg); + if (first && left->right != NULL) + { + art_svp_intersect_test_cross (ctx, left, left->right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + else + { + ArtActiveSeg *right; + art_boolean first = ART_TRUE; + + for (right = seg->right; right != NULL; right = seg->right) + { + int right_bneg = right->flags & ART_ACTIVE_FLAGS_BNEG; + + if (right->x[right_bneg ^ 1] >= x1) + break; + if (right->x[right_bneg] >= x1 && + x1 * right->a + ctx->y * right->b + right->c <= 0) + break; + if (right->y0 != ctx->y && right->y1 != ctx->y) + { + art_svp_intersect_break (ctx, right, x1, ctx->y, + ART_BREAK_LEFT); + } +#ifdef VERBOSE + art_dprint ("[right]x0=%g < x1=%g, swapping %lx, %lx\n", + x0, x1, (unsigned long)seg, (unsigned long)right); +#endif + art_svp_intersect_swap_active (ctx, seg, right); + if (first && right->left != NULL) + { + art_svp_intersect_test_cross (ctx, right->left, right, + ART_BREAK_RIGHT); + first = ART_FALSE; + } + } + } + + seg->x[0] = x1; + seg->x[1] = x1; + seg->horiz_x = x1; + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +} + +/** + * art_svp_intersect_insert_line: Insert a line into the active list. + * @ctx: Intersector context. + * @seg: Segment containing line to insert. + * + * Inserts the line into the intersector context, taking care of any + * intersections, and adding the appropriate horizontal points to the + * active list. + **/ +static void +art_svp_intersect_insert_line (ArtIntersectCtx *ctx, ArtActiveSeg *seg) +{ + if (seg->y1 == seg->y0) + { +#ifdef VERBOSE + art_dprint ("art_svp_intersect_insert_line: %lx is horizontal\n", + (unsigned long)seg); +#endif + art_svp_intersect_horiz (ctx, seg, seg->x[0], seg->x[1]); + } + else + { + art_svp_intersect_insert_cross (ctx, seg); + art_svp_intersect_add_horiz (ctx, seg); + } +} + +static void +art_svp_intersect_process_intersection (ArtIntersectCtx *ctx, + ArtActiveSeg *seg) +{ + int n_stack = --seg->n_stack; + seg->x[1] = seg->stack[n_stack - 1].x; + seg->y1 = seg->stack[n_stack - 1].y; + seg->x[0] = seg->stack[n_stack].x; + seg->y0 = seg->stack[n_stack].y; + seg->horiz_x = seg->x[0]; + art_svp_intersect_insert_line (ctx, seg); +} + +static void +art_svp_intersect_advance_cursor (ArtIntersectCtx *ctx, ArtActiveSeg *seg, + ArtPriPoint *pri_pt) +{ + const ArtSVPSeg *in_seg = seg->in_seg; + int in_curs = seg->in_curs; + ArtSvpWriter *swr = seg->flags & ART_ACTIVE_FLAGS_OUT ? ctx->out : NULL; + + if (swr != NULL) + swr->add_point (swr, seg->seg_id, seg->x[1], seg->y1); + if (in_curs + 1 == in_seg->n_points) + { + ArtActiveSeg *left = seg->left, *right = seg->right; + +#if 0 + if (swr != NULL) + swr->close_segment (swr, seg->seg_id); + seg->flags &= ~ART_ACTIVE_FLAGS_OUT; +#endif + seg->flags |= ART_ACTIVE_FLAGS_DEL; + art_svp_intersect_add_horiz (ctx, seg); + art_svp_intersect_active_delete (ctx, seg); + if (left != NULL && right != NULL) + art_svp_intersect_test_cross (ctx, left, right, + ART_BREAK_LEFT | ART_BREAK_RIGHT); + art_free (pri_pt); + } + else + { + seg->horiz_x = seg->x[1]; + + art_svp_intersect_setup_seg (seg, pri_pt); + art_pri_insert (ctx->pq, pri_pt); + art_svp_intersect_insert_line (ctx, seg); + } +} + +static void +art_svp_intersect_add_seg (ArtIntersectCtx *ctx, const ArtSVPSeg *in_seg) +{ + ArtActiveSeg *seg = art_new (ArtActiveSeg, 1); + ArtActiveSeg *test; + double x0, y0; + ArtActiveSeg *beg_range; + ArtActiveSeg *last = NULL; + ArtActiveSeg *left, *right; + ArtPriPoint *pri_pt = art_new (ArtPriPoint, 1); + + seg->flags = 0; + seg->in_seg = in_seg; + seg->in_curs = 0; + + seg->n_stack_max = 4; + seg->stack = art_new (ArtPoint, seg->n_stack_max); + + seg->horiz_delta_wind = 0; + + seg->wind_left = 0; + + pri_pt->user_data = seg; + art_svp_intersect_setup_seg (seg, pri_pt); + art_pri_insert (ctx->pq, pri_pt); + + /* Find insertion place for new segment */ + /* This is currently a left-to-right scan, but should be replaced + with a binary search as soon as it's validated. */ + + x0 = in_seg->points[0].x; + y0 = in_seg->points[0].y; + beg_range = NULL; + for (test = ctx->active_head; test != NULL; test = test->right) + { + double d; + int test_bneg = test->flags & ART_ACTIVE_FLAGS_BNEG; + + if (x0 < test->x[test_bneg]) + { + if (x0 < test->x[test_bneg ^ 1]) + break; + d = x0 * test->a + y0 * test->b + test->c; + if (d < 0) + break; + } + last = test; + } + + left = art_svp_intersect_add_point (ctx, x0, y0, last, ART_BREAK_LEFT | ART_BREAK_RIGHT); + seg->left = left; + if (left == NULL) + { + right = ctx->active_head; + ctx->active_head = seg; + } + else + { + right = left->right; + left->right = seg; + } + seg->right = right; + if (right != NULL) + right->left = seg; + + seg->delta_wind = in_seg->dir ? 1 : -1; + seg->horiz_x = x0; + + art_svp_intersect_insert_line (ctx, seg); +} + +#ifdef SANITYCHECK +static void +art_svp_intersect_sanitycheck_winding (ArtIntersectCtx *ctx) +{ +#if 0 + /* At this point, we seem to be getting false positives, so it's + turned off for now. */ + + ArtActiveSeg *seg; + int winding_number = 0; + + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + /* Check winding number consistency. */ + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + if (winding_number != seg->wind_left) + art_warn ("*** art_svp_intersect_sanitycheck_winding: seg %lx has wind_left of %d, expected %d\n", + (unsigned long) seg, seg->wind_left, winding_number); + winding_number = seg->wind_left + seg->delta_wind; + } + } + if (winding_number != 0) + art_warn ("*** art_svp_intersect_sanitycheck_winding: non-balanced winding number %d\n", + winding_number); +#endif +} +#endif + +/** + * art_svp_intersect_horiz_commit: Commit points in horiz list to output. + * @ctx: Intersection context. + * + * The main function of the horizontal commit is to output new + * points to the output writer. + * + * This "commit" pass is also where winding numbers are assigned, + * because doing it here provides much greater tolerance for inputs + * which are not in strict SVP order. + * + * Each cluster in the horiz_list contains both segments that are in + * the active list (ART_ACTIVE_FLAGS_DEL is false) and that are not, + * and are scheduled to be deleted (ART_ACTIVE_FLAGS_DEL is true). We + * need to deal with both. + **/ +static void +art_svp_intersect_horiz_commit (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + int winding_number = 0; /* initialization just to avoid warning */ + int horiz_wind = 0; + double last_x = 0; /* initialization just to avoid warning */ + +#ifdef VERBOSE + art_dprint ("art_svp_intersect_horiz_commit: y=%g\n", ctx->y); + for (seg = ctx->horiz_first; seg != NULL; seg = seg->horiz_right) + art_dprint (" %lx: %g %+d\n", + (unsigned long)seg, seg->horiz_x, seg->horiz_delta_wind); +#endif + + /* Output points to svp writer. */ + for (seg = ctx->horiz_first; seg != NULL;) + { + /* Find a cluster with common horiz_x, */ + ArtActiveSeg *curs; + double x = seg->horiz_x; + + /* Generate any horizontal segments. */ + if (horiz_wind != 0) + { + ArtSvpWriter *swr = ctx->out; + int seg_id; + + seg_id = swr->add_segment (swr, winding_number, horiz_wind, + last_x, ctx->y); + swr->add_point (swr, seg_id, x, ctx->y); + swr->close_segment (swr, seg_id); + } + + /* Find first active segment in cluster. */ + + for (curs = seg; curs != NULL && curs->horiz_x == x; + curs = curs->horiz_right) + if (!(curs->flags & ART_ACTIVE_FLAGS_DEL)) + break; + + if (curs != NULL && curs->horiz_x == x) + { + /* There exists at least one active segment in this cluster. */ + + /* Find beginning of cluster. */ + for (; curs->left != NULL; curs = curs->left) + if (curs->left->horiz_x != x) + break; + + if (curs->left != NULL) + winding_number = curs->left->wind_left + curs->left->delta_wind; + else + winding_number = 0; + + do + { +#ifdef VERBOSE + art_dprint (" curs %lx: winding_number = %d += %d\n", + (unsigned long)curs, winding_number, curs->delta_wind); +#endif + if (!(curs->flags & ART_ACTIVE_FLAGS_OUT) || + curs->wind_left != winding_number) + { + ArtSvpWriter *swr = ctx->out; + + if (curs->flags & ART_ACTIVE_FLAGS_OUT) + { + swr->add_point (swr, curs->seg_id, + curs->horiz_x, ctx->y); + swr->close_segment (swr, curs->seg_id); + } + + curs->seg_id = swr->add_segment (swr, winding_number, + curs->delta_wind, + x, ctx->y); + curs->flags |= ART_ACTIVE_FLAGS_OUT; + } + curs->wind_left = winding_number; + winding_number += curs->delta_wind; + curs = curs->right; + } + while (curs != NULL && curs->horiz_x == x); + } + + /* Skip past cluster. */ + do + { + ArtActiveSeg *next = seg->horiz_right; + + seg->flags &= ~ART_ACTIVE_FLAGS_IN_HORIZ; + horiz_wind += seg->horiz_delta_wind; + seg->horiz_delta_wind = 0; + if (seg->flags & ART_ACTIVE_FLAGS_DEL) + { + if (seg->flags & ART_ACTIVE_FLAGS_OUT) + { + ArtSvpWriter *swr = ctx->out; + swr->close_segment (swr, seg->seg_id); + } + art_svp_intersect_active_free (seg); + } + seg = next; + } + while (seg != NULL && seg->horiz_x == x); + + last_x = x; + } + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; +#ifdef SANITYCHECK + art_svp_intersect_sanitycheck_winding (ctx); +#endif +} + +#ifdef VERBOSE +static void +art_svp_intersect_print_active (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + + art_dprint ("Active list (y = %g):\n", ctx->y); + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + art_dprint (" %lx: (%g, %g)-(%g, %g), (a, b, c) = (%g, %g, %g)\n", + (unsigned long)seg, + seg->x[0], seg->y0, seg->x[1], seg->y1, + seg->a, seg->b, seg->c); + } +} +#endif + +#ifdef SANITYCHECK +static void +art_svp_intersect_sanitycheck (ArtIntersectCtx *ctx) +{ + ArtActiveSeg *seg; + ArtActiveSeg *last = NULL; + double d; + + for (seg = ctx->active_head; seg != NULL; seg = seg->right) + { + if (seg->left != last) + { + art_warn ("*** art_svp_intersect_sanitycheck: last=%lx, seg->left=%lx\n", + (unsigned long)last, (unsigned long)seg->left); + } + if (last != NULL) + { + /* pairwise compare with previous seg */ + + /* First the top. */ + if (last->y0 < seg->y0) + { + } + else + { + } + + /* Then the bottom. */ + if (last->y1 < seg->y1) + { + if (!((last->x[1] < + seg->x[(seg->flags & ART_ACTIVE_FLAGS_BNEG) ^ 1]) || + last->y1 == seg->y0)) + { + d = last->x[1] * seg->a + last->y1 * seg->b + seg->c; + if (d >= -EPSILON_A) + art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to right (d = %g)\n", + last->x[1], last->y1, (unsigned long) last, + (unsigned long) seg, d); + } + } + else if (last->y1 > seg->y1) + + { + if (!((seg->x[1] > + last->x[last->flags & ART_ACTIVE_FLAGS_BNEG]) || + seg->y1 == last->y0)) + { + d = seg->x[1] * last->a + seg->y1 * last->b + last->c; + if (d <= EPSILON_A) + art_warn ("*** bottom (%g, %g) of %lx is not clear of %lx to left (d = %g)\n", + seg->x[1], seg->y1, (unsigned long) seg, + (unsigned long) last, d); + } + } + else + { + if (last->x[1] > seg->x[1]) + art_warn ("*** bottoms (%g, %g) of %lx and (%g, %g) of %lx out of order\n", + last->x[1], last->y1, (unsigned long)last, + seg->x[1], seg->y1, (unsigned long)seg); + } + } + last = seg; + } +} +#endif + +void +art_svp_intersector (const ArtSVP *in, ArtSvpWriter *out) +{ + ArtIntersectCtx *ctx; + ArtPriQ *pq; + ArtPriPoint *first_point; +#ifdef VERBOSE + int count = 0; +#endif + + if (in->n_segs == 0) + return; + + ctx = art_new (ArtIntersectCtx, 1); + ctx->in = in; + ctx->out = out; + pq = art_pri_new (); + ctx->pq = pq; + + ctx->active_head = NULL; + + ctx->horiz_first = NULL; + ctx->horiz_last = NULL; + + ctx->in_curs = 0; + first_point = art_new (ArtPriPoint, 1); + first_point->x = in->segs[0].points[0].x; + first_point->y = in->segs[0].points[0].y; + first_point->user_data = NULL; + ctx->y = first_point->y; + art_pri_insert (pq, first_point); + + while (!art_pri_empty (pq)) + { + ArtPriPoint *pri_point = art_pri_choose (pq); + ArtActiveSeg *seg = (ArtActiveSeg *)pri_point->user_data; + +#ifdef VERBOSE + art_dprint ("\nIntersector step %d\n", count++); + art_svp_intersect_print_active (ctx); + art_dprint ("priq choose (%g, %g) %lx\n", pri_point->x, pri_point->y, + (unsigned long)pri_point->user_data); +#endif +#ifdef SANITYCHECK + art_svp_intersect_sanitycheck(ctx); +#endif + + if (ctx->y != pri_point->y) + { + art_svp_intersect_horiz_commit (ctx); + ctx->y = pri_point->y; + } + + if (seg == NULL) + { + /* Insert new segment from input */ + const ArtSVPSeg *in_seg = &in->segs[ctx->in_curs++]; + art_svp_intersect_add_seg (ctx, in_seg); + if (ctx->in_curs < in->n_segs) + { + const ArtSVPSeg *next_seg = &in->segs[ctx->in_curs]; + pri_point->x = next_seg->points[0].x; + pri_point->y = next_seg->points[0].y; + /* user_data is already NULL */ + art_pri_insert (pq, pri_point); + } + else + art_free (pri_point); + } + else + { + int n_stack = seg->n_stack; + + if (n_stack > 1) + { + art_svp_intersect_process_intersection (ctx, seg); + art_free (pri_point); + } + else + { + art_svp_intersect_advance_cursor (ctx, seg, pri_point); + } + } + } + + art_svp_intersect_horiz_commit (ctx); + + art_pri_free (pq); + art_free (ctx); +} + +#endif /* not TEST_PRIQ */ diff --git a/lib/art/art_svp_ops.c b/lib/art/art_svp_ops.c new file mode 100644 index 0000000..5bb837c --- /dev/null +++ b/lib/art/art_svp_ops.c @@ -0,0 +1,401 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#define noVERBOSE + +/* Vector path set operations, over sorted vpaths. */ + +#include "config.h" +#include "art_svp_ops.h" + +#include "art_misc.h" + +#include "art_svp.h" +#include "art_vpath.h" +#include "art_svp_vpath.h" +#include "art_svp.h" +#ifdef ART_USE_NEW_INTERSECTOR +#include "art_svp_intersect.h" +#else +#include "art_svp_wind.h" +#endif +#include "art_vpath_svp.h" + +/* Merge the segments of the two svp's. The resulting svp will share + segments with args passed in, so be super-careful with the + allocation. */ +/** + * art_svp_merge: Merge the segments of two svp's. + * @svp1: One svp to merge. + * @svp2: The other svp to merge. + * + * Merges the segments of two SVP's into a new one. The resulting + * #ArtSVP data structure will share the segments of the argument + * svp's, so it is probably a good idea to free it shallowly, + * especially if the arguments will be freed with art_svp_free(). + * + * Return value: The merged #ArtSVP. + **/ +static ArtSVP * +art_svp_merge (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtSVP *svp_new; + int ix; + int ix1, ix2; + + svp_new = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (svp1->n_segs + svp2->n_segs - 1) * + sizeof(ArtSVPSeg)); + ix1 = 0; + ix2 = 0; + for (ix = 0; ix < svp1->n_segs + svp2->n_segs; ix++) + { + if (ix1 < svp1->n_segs && + (ix2 == svp2->n_segs || + art_svp_seg_compare (&svp1->segs[ix1], &svp2->segs[ix2]) < 1)) + svp_new->segs[ix] = svp1->segs[ix1++]; + else + svp_new->segs[ix] = svp2->segs[ix2++]; + } + + svp_new->n_segs = ix; + return svp_new; +} + +#ifdef VERBOSE + +#define XOFF 50 +#define YOFF 700 + +static void +print_ps_vpath (ArtVpath *vpath) +{ + int i; + + printf ("gsave %d %d translate 1 -1 scale\n", XOFF, YOFF); + for (i = 0; vpath[i].code != ART_END; i++) + { + switch (vpath[i].code) + { + case ART_MOVETO: + printf ("%g %g moveto\n", vpath[i].x, vpath[i].y); + break; + case ART_LINETO: + printf ("%g %g lineto\n", vpath[i].x, vpath[i].y); + break; + default: + break; + } + } + printf ("stroke grestore showpage\n"); +} + +#define DELT 4 + +static void +print_ps_svp (ArtSVP *vpath) +{ + int i, j; + + printf ("%% begin\n"); + for (i = 0; i < vpath->n_segs; i++) + { + printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0); + for (j = 0; j < vpath->segs[i].n_points; j++) + { + printf ("%g %g %s\n", + XOFF + vpath->segs[i].points[j].x, + YOFF - vpath->segs[i].points[j].y, + j ? "lineto" : "moveto"); + } + printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n", + XOFF + vpath->segs[i].points[0].x - DELT, + YOFF - DELT - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x - DELT, + YOFF - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x + DELT, + YOFF - vpath->segs[i].points[0].y, + XOFF + vpath->segs[i].points[0].x + DELT, + YOFF - DELT - vpath->segs[i].points[0].y); + printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n", + XOFF + vpath->segs[i].points[j - 1].x - DELT, + YOFF + DELT - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x - DELT, + YOFF - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x + DELT, + YOFF - vpath->segs[i].points[j - 1].y, + XOFF + vpath->segs[i].points[j - 1].x + DELT, + YOFF + DELT - vpath->segs[i].points[j - 1].y); + printf ("stroke\n"); + } + + printf ("showpage\n"); +} +#endif + +#ifndef ART_USE_NEW_INTERSECTOR +static ArtSVP * +art_svp_merge_perturbed (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtVpath *vpath1, *vpath2; + ArtVpath *vpath1_p, *vpath2_p; + ArtSVP *svp1_p, *svp2_p; + ArtSVP *svp_new; + + vpath1 = art_vpath_from_svp (svp1); + vpath1_p = art_vpath_perturb (vpath1); + art_free (vpath1); + svp1_p = art_svp_from_vpath (vpath1_p); + art_free (vpath1_p); + + vpath2 = art_vpath_from_svp (svp2); + vpath2_p = art_vpath_perturb (vpath2); + art_free (vpath2); + svp2_p = art_svp_from_vpath (vpath2_p); + art_free (vpath2_p); + + svp_new = art_svp_merge (svp1_p, svp2_p); +#ifdef VERBOSE + print_ps_svp (svp1_p); + print_ps_svp (svp2_p); + print_ps_svp (svp_new); +#endif + art_free (svp1_p); + art_free (svp2_p); + + return svp_new; +} +#endif + +/* Compute the union of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Would be better if we didn't go to unsorted vector path + and back to add the perturbation. + + Precision: The perturbation fuzzes the coordinates slightly. In + cases of butting segments, razor thin long holes may appear. + +*/ +/** + * art_svp_union: Compute the union of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the union of the two argument svp's. Given two svp's with + * winding numbers of 0 and 1 everywhere, the resulting winding number + * will be 1 where either (or both) of the argument svp's has a + * winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The union of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_POSITIVE); +#ifdef VERBOSE + print_ps_svp (svp4); + print_ps_svp (svp_new); +#endif + art_svp_free (svp4); + return svp_new; +#endif +} + +/* Compute the intersection of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Would be better if we didn't go to unsorted vector path + and back to add the perturbation. + + Precision: The perturbation fuzzes the coordinates slightly. In + cases of butting segments, razor thin long isolated segments may + appear. + +*/ + +/** + * art_svp_intersect: Compute the intersection of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the intersection of the two argument svp's. Given two + * svp's with winding numbers of 0 and 1 everywhere, the resulting + * winding number will be 1 where both of the argument svp's has a + * winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The intersection of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_INTERSECT); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_INTERSECT); + art_svp_free (svp4); + return svp_new; +#endif +} + +/* Compute the symmetric difference of two vector paths. + + Status of this routine: + + Basic correctness: Seems to work. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: We could do a lot better by scanning through the svp + representations and culling out any segments that are exactly + identical. It would also be better if we didn't go to unsorted + vector path and back to add the perturbation. + + Precision: Awful. In the case of inputs which are similar (the + common case for canvas display), the entire outline is "hairy." In + addition, the perturbation fuzzes the coordinates slightly. It can + be used as a conservative approximation. + +*/ + +/** + * art_svp_diff: Compute the symmetric difference of two sorted vector paths. + * @svp1: One sorted vector path. + * @svp2: The other sorted vector path. + * + * Computes the symmetric of the two argument svp's. Given two svp's + * with winding numbers of 0 and 1 everywhere, the resulting winding + * number will be 1 where either, but not both, of the argument svp's + * has a winding number 1, 0 otherwise. The result is newly allocated. + * + * Currently, this routine has accuracy problems pending the + * implementation of the new intersector. + * + * Return value: The symmetric difference of @svp1 and @svp2. + **/ +ArtSVP * +art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + + svp3 = art_svp_merge (svp1, svp2); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_ODDEVEN); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + return svp_new; +#else + ArtSVP *svp3, *svp4, *svp_new; + + svp3 = art_svp_merge_perturbed (svp1, svp2); + svp4 = art_svp_uncross (svp3); + art_svp_free (svp3); + + svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_ODDEVEN); + art_svp_free (svp4); + return svp_new; +#endif +} + +#ifdef ART_USE_NEW_INTERSECTOR +ArtSVP * +art_svp_minus (const ArtSVP *svp1, const ArtSVP *svp2) +{ + ArtSVP *svp2_mod; + ArtSVP *svp3, *svp_new; + ArtSvpWriter *swr; + int i; + + svp2_mod = (ArtSVP *) svp2; /* get rid of the const for a while */ + + /* First invert svp2 to "turn it inside out" */ + for (i = 0; i < svp2_mod->n_segs; i++) + svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir; + + svp3 = art_svp_merge (svp1, svp2_mod); + swr = art_svp_writer_rewind_new (ART_WIND_RULE_POSITIVE); + art_svp_intersector (svp3, swr); + svp_new = art_svp_writer_rewind_reap (swr); + art_free (svp3); /* shallow free because svp3 contains shared segments */ + + /* Flip svp2 back to its original state */ + for (i = 0; i < svp2_mod->n_segs; i++) + svp2_mod->segs[i].dir = !svp2_mod->segs[i].dir; + + return svp_new; +} +#endif /* ART_USE_NEW_INTERSECTOR */ diff --git a/lib/art/art_svp_point.c b/lib/art/art_svp_point.c new file mode 100644 index 0000000..4b41e8c --- /dev/null +++ b/lib/art/art_svp_point.c @@ -0,0 +1,144 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_svp_point.h" + +#include +#include "art_misc.h" + +#include "art_svp.h" + +/* Determine whether a point is inside, or near, an svp. */ + +/* return winding number of point wrt svp */ +/** + * art_svp_point_wind: Determine winding number of a point with respect to svp. + * @svp: The svp. + * @x: The X coordinate of the point. + * @y: The Y coordinate of the point. + * + * Determine the winding number of the point @x, @y with respect to @svp. + * + * Return value: the winding number. + **/ +int +art_svp_point_wind (ArtSVP *svp, double x, double y) +{ + int i, j; + int wind = 0; + + for (i = 0; i < svp->n_segs; i++) + { + ArtSVPSeg *seg = &svp->segs[i]; + + if (seg->bbox.y0 > y) + break; + + if (seg->bbox.y1 > y) + { + if (seg->bbox.x1 < x) + wind += seg->dir ? 1 : -1; + else if (seg->bbox.x0 <= x) + { + double x0, y0, x1, y1, dx, dy; + + for (j = 0; j < seg->n_points - 1; j++) + { + if (seg->points[j + 1].y > y) + break; + } + x0 = seg->points[j].x; + y0 = seg->points[j].y; + x1 = seg->points[j + 1].x; + y1 = seg->points[j + 1].y; + + dx = x1 - x0; + dy = y1 - y0; + if ((x - x0) * dy > (y - y0) * dx) + wind += seg->dir ? 1 : -1; + } + } + } + + return wind; +} + +/** + * art_svp_point_dist: Determine distance between point and svp. + * @svp: The svp. + * @x: The X coordinate of the point. + * @y: The Y coordinate of the point. + * + * Determines the distance of the point @x, @y to the closest edge in + * @svp. A large number is returned if @svp is empty. + * + * Return value: the distance. + **/ +double +art_svp_point_dist (ArtSVP *svp, double x, double y) +{ + int i, j; + double dist_sq; + double best_sq = -1; + + for (i = 0; i < svp->n_segs; i++) + { + ArtSVPSeg *seg = &svp->segs[i]; + for (j = 0; j < seg->n_points - 1; j++) + { + double x0 = seg->points[j].x; + double y0 = seg->points[j].y; + double x1 = seg->points[j + 1].x; + double y1 = seg->points[j + 1].y; + + double dx = x1 - x0; + double dy = y1 - y0; + + double dxx0 = x - x0; + double dyy0 = y - y0; + + double dot = dxx0 * dx + dyy0 * dy; + + if (dot < 0) + dist_sq = dxx0 * dxx0 + dyy0 * dyy0; + else + { + double rr = dx * dx + dy * dy; + + if (dot > rr) + dist_sq = (x - x1) * (x - x1) + (y - y1) * (y - y1); + else + { + double perp = (y - y0) * dx - (x - x0) * dy; + + dist_sq = perp * perp / rr; + } + } + if (best_sq < 0 || dist_sq < best_sq) + best_sq = dist_sq; + } + } + + if (best_sq >= 0) + return sqrt (best_sq); + else + return 1e12; +} + diff --git a/lib/art/art_svp_render_aa.c b/lib/art/art_svp_render_aa.c new file mode 100644 index 0000000..d696a51 --- /dev/null +++ b/lib/art/art_svp_render_aa.c @@ -0,0 +1,463 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* The spiffy antialiased renderer for sorted vector paths. */ + +#include "config.h" +#include "art_svp_render_aa.h" + +#include +#include /* for memmove */ +#include "art_misc.h" + +#include "art_rect.h" +#include "art_svp.h" + +#include + +typedef double artfloat; + +struct _ArtSVPRenderAAIter { + const ArtSVP *svp; + int x0, x1; + int y; + int seg_ix; + + int *active_segs; + int n_active_segs; + int *cursor; + artfloat *seg_x; + artfloat *seg_dx; + + ArtSVPRenderAAStep *steps; +}; + +static void +art_svp_render_insert_active (int i, int *active_segs, int n_active_segs, + artfloat *seg_x, artfloat *seg_dx) +{ + int j; + artfloat x; + int tmp1, tmp2; + + /* this is a cheap hack to get ^'s sorted correctly */ + x = seg_x[i] + 0.001 * seg_dx[i]; + for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++); + + tmp1 = i; + while (j < n_active_segs) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + j++; + } + active_segs[j] = tmp1; +} + +static void +art_svp_render_delete_active (int *active_segs, int j, int n_active_segs) +{ + int k; + + for (k = j; k < n_active_segs; k++) + active_segs[k] = active_segs[k + 1]; +} + +#define EPSILON 1e-6 + +/* Render the sorted vector path in the given rectangle, antialiased. + + This interface uses a callback for the actual pixel rendering. The + callback is called y1 - y0 times (once for each scan line). The y + coordinate is given as an argument for convenience (it could be + stored in the callback's private data and incremented on each + call). + + The rendered polygon is represented in a semi-runlength format: a + start value and a sequence of "steps". Each step has an x + coordinate and a value delta. The resulting value at position x is + equal to the sum of the start value and all step delta values for + which the step x coordinate is less than or equal to x. An + efficient algorithm will traverse the steps left to right, keeping + a running sum. + + All x coordinates in the steps are guaranteed to be x0 <= x < x1. + (This guarantee is a change from the gfonted vpaar renderer, and is + designed to simplify the callback). + + There is now a further guarantee that no two steps will have the + same x value. This may allow for further speedup and simplification + of renderers. + + The value 0x8000 represents 0% coverage by the polygon, while + 0xff8000 represents 100% coverage. This format is designed so that + >> 16 results in a standard 0x00..0xff value range, with nice + rounding. + + Status of this routine: + + Basic correctness: OK + + Numerical stability: pretty good, although probably not + bulletproof. + + Speed: Needs more aggressive culling of bounding boxes. Can + probably speed up the [x0,x1) clipping of step values. Can do more + of the step calculation in fixed point. + + Precision: No known problems, although it should be tested + thoroughly, especially for symmetry. + +*/ + +ArtSVPRenderAAIter * +art_svp_render_aa_iter (const ArtSVP *svp, + int x0, int y0, int x1, int y1) +{ + ArtSVPRenderAAIter *iter = art_new (ArtSVPRenderAAIter, 1); + + iter->svp = svp; + iter->y = y0; + iter->x0 = x0; + iter->x1 = x1; + iter->seg_ix = 0; + + iter->active_segs = art_new (int, svp->n_segs); + iter->cursor = art_new (int, svp->n_segs); + iter->seg_x = art_new (artfloat, svp->n_segs); + iter->seg_dx = art_new (artfloat, svp->n_segs); + iter->steps = art_new (ArtSVPRenderAAStep, x1 - x0); + iter->n_active_segs = 0; + + return iter; +} + +#define ADD_STEP(xpos, xdelta) \ + /* stereotype code fragment for adding a step */ \ + if (n_steps == 0 || steps[n_steps - 1].x < xpos) \ + { \ + sx = n_steps; \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + else \ + { \ + for (sx = n_steps; sx > 0; sx--) \ + { \ + if (steps[sx - 1].x == xpos) \ + { \ + steps[sx - 1].delta += xdelta; \ + sx = n_steps; \ + break; \ + } \ + else if (steps[sx - 1].x < xpos) \ + { \ + break; \ + } \ + } \ + if (sx < n_steps) \ + { \ + memmove (&steps[sx + 1], &steps[sx], \ + (n_steps - sx) * sizeof(steps[0])); \ + steps[sx].x = xpos; \ + steps[sx].delta = xdelta; \ + n_steps++; \ + } \ + } + +void +art_svp_render_aa_iter_step (ArtSVPRenderAAIter *iter, int *p_start, + ArtSVPRenderAAStep **p_steps, int *p_n_steps) +{ + const ArtSVP *svp = iter->svp; + int *active_segs = iter->active_segs; + int n_active_segs = iter->n_active_segs; + int *cursor = iter->cursor; + artfloat *seg_x = iter->seg_x; + artfloat *seg_dx = iter->seg_dx; + int i = iter->seg_ix; + int j; + int x0 = iter->x0; + int x1 = iter->x1; + int y = iter->y; + int seg_index; + + int x; + ArtSVPRenderAAStep *steps = iter->steps; + int n_steps; + artfloat y_top, y_bot; + artfloat x_top, x_bot; + artfloat x_min, x_max; + int ix_min, ix_max; + artfloat delta; /* delta should be int too? */ + int last, this; + int xdelta; + artfloat rslope, drslope; + int start; + const ArtSVPSeg *seg; + int curs; + artfloat dy; + + int sx; + + /* insert new active segments */ + for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++) + { + if (svp->segs[i].bbox.y1 > y && + svp->segs[i].bbox.x0 < x1) + { + seg = &svp->segs[i]; + /* move cursor to topmost vector which overlaps [y,y+1) */ + for (curs = 0; seg->points[curs + 1].y < y; curs++); + cursor[i] = curs; + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs (dy) >= EPSILON) + seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) / + dy; + else + seg_dx[i] = 1e12; + seg_x[i] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[i]; + art_svp_render_insert_active (i, active_segs, n_active_segs++, + seg_x, seg_dx); + } + } + + n_steps = 0; + + /* render the runlengths, advancing and deleting as we go */ + start = 0x8000; + + for (j = 0; j < n_active_segs; j++) + { + seg_index = active_segs[j]; + seg = &svp->segs[seg_index]; + curs = cursor[seg_index]; + while (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) + { + y_top = y; + if (y_top < seg->points[curs].y) + y_top = seg->points[curs].y; + y_bot = y + 1; + if (y_bot > seg->points[curs + 1].y) + y_bot = seg->points[curs + 1].y; + if (y_top != y_bot) { + delta = (seg->dir ? 16711680.0 : -16711680.0) * + (y_bot - y_top); + x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index]; + x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index]; + if (x_top < x_bot) + { + x_min = x_top; + x_max = x_bot; + } + else + { + x_min = x_bot; + x_max = x_top; + } + ix_min = floor (x_min); + ix_max = floor (x_max); + if (ix_min >= x1) + { + /* skip; it starts to the right of the render region */ + } + else if (ix_max < x0) + /* it ends to the left of the render region */ + start += delta; + else if (ix_min == ix_max) + { + /* case 1, antialias a single pixel */ + xdelta = (ix_min + 1 - (x_min + x_max) * 0.5) * delta; + + ADD_STEP(ix_min, xdelta) + + if (ix_min + 1 < x1) + { + xdelta = delta - xdelta; + + ADD_STEP(ix_min + 1, xdelta) + } + } + else + { + /* case 2, antialias a run */ + rslope = 1.0 / fabs (seg_dx[seg_index]); + drslope = delta * rslope; + last = + drslope * 0.5 * + (ix_min + 1 - x_min) * (ix_min + 1 - x_min); + xdelta = last; + if (ix_min >= x0) + { + ADD_STEP(ix_min, xdelta) + + x = ix_min + 1; + } + else + { + start += last; + x = x0; + } + if (ix_max > x1) + ix_max = x1; + for (; x < ix_max; x++) + { + this = (seg->dir ? 16711680.0 : -16711680.0) * rslope * + (x + 0.5 - x_min); + xdelta = this - last; + last = this; + + ADD_STEP(x, xdelta) + } + if (x < x1) + { + this = + delta * (1 - 0.5 * + (x_max - ix_max) * (x_max - ix_max) * + rslope); + xdelta = this - last; + last = this; + + ADD_STEP(x, xdelta) + + if (x + 1 < x1) + { + xdelta = delta - last; + + ADD_STEP(x + 1, xdelta) + } + } + } + } + curs++; + if (curs != seg->n_points - 1 && + seg->points[curs].y < y + 1) + { + dy = seg->points[curs + 1].y - seg->points[curs].y; + if (fabs (dy) >= EPSILON) + seg_dx[seg_index] = (seg->points[curs + 1].x - + seg->points[curs].x) / dy; + else + seg_dx[seg_index] = 1e12; + seg_x[seg_index] = seg->points[curs].x + + (y - seg->points[curs].y) * seg_dx[seg_index]; + } + /* break here, instead of duplicating predicate in while? */ + } + if (seg->points[curs].y >= y + 1) + { + curs--; + cursor[seg_index] = curs; + seg_x[seg_index] += seg_dx[seg_index]; + } + else + { + art_svp_render_delete_active (active_segs, j--, + --n_active_segs); + } + } + + *p_start = start; + *p_steps = steps; + *p_n_steps = n_steps; + + iter->seg_ix = i; + iter->n_active_segs = n_active_segs; + iter->y++; +} + +void +art_svp_render_aa_iter_done (ArtSVPRenderAAIter *iter) +{ + art_free (iter->steps); + + art_free (iter->seg_dx); + art_free (iter->seg_x); + art_free (iter->cursor); + art_free (iter->active_segs); + art_free (iter); +} + +/** + * art_svp_render_aa: Render SVP antialiased. + * @svp: The #ArtSVP to render. + * @x0: Left coordinate of destination rectangle. + * @y0: Top coordinate of destination rectangle. + * @x1: Right coordinate of destination rectangle. + * @y1: Bottom coordinate of destination rectangle. + * @callback: The callback which actually paints the pixels. + * @callback_data: Private data for @callback. + * + * Renders the sorted vector path in the given rectangle, antialiased. + * + * This interface uses a callback for the actual pixel rendering. The + * callback is called @y1 - @y0 times (once for each scan line). The y + * coordinate is given as an argument for convenience (it could be + * stored in the callback's private data and incremented on each + * call). + * + * The rendered polygon is represented in a semi-runlength format: a + * start value and a sequence of "steps". Each step has an x + * coordinate and a value delta. The resulting value at position x is + * equal to the sum of the start value and all step delta values for + * which the step x coordinate is less than or equal to x. An + * efficient algorithm will traverse the steps left to right, keeping + * a running sum. + * + * All x coordinates in the steps are guaranteed to be @x0 <= x < @x1. + * (This guarantee is a change from the gfonted vpaar renderer from + * which this routine is derived, and is designed to simplify the + * callback). + * + * The value 0x8000 represents 0% coverage by the polygon, while + * 0xff8000 represents 100% coverage. This format is designed so that + * >> 16 results in a standard 0x00..0xff value range, with nice + * rounding. + * + **/ +void +art_svp_render_aa (const ArtSVP *svp, + int x0, int y0, int x1, int y1, + void (*callback) (void *callback_data, + int y, + int start, + ArtSVPRenderAAStep *steps, int n_steps), + void *callback_data) +{ + ArtSVPRenderAAIter *iter; + int y; + int start; + ArtSVPRenderAAStep *steps; + int n_steps; + + iter = art_svp_render_aa_iter (svp, x0, y0, x1, y1); + + + for (y = y0; y < y1; y++) + { + art_svp_render_aa_iter_step (iter, &start, &steps, &n_steps); + (*callback) (callback_data, y, start, steps, n_steps); + } + + art_svp_render_aa_iter_done (iter); +} diff --git a/lib/art/art_svp_vpath.c b/lib/art/art_svp_vpath.c new file mode 100644 index 0000000..196711a --- /dev/null +++ b/lib/art/art_svp_vpath.c @@ -0,0 +1,215 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Sort vector paths into sorted vector paths */ + +#include "config.h" +#include "art_svp_vpath.h" + +#include +#include + +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" + + +/* reverse a list of points in place */ +static void +reverse_points (ArtPoint *points, int n_points) +{ + int i; + ArtPoint tmp_p; + + for (i = 0; i < (n_points >> 1); i++) + { + tmp_p = points[i]; + points[i] = points[n_points - (i + 1)]; + points[n_points - (i + 1)] = tmp_p; + } +} + +/** + * art_svp_from_vpath: Convert a vpath to a sorted vector path. + * @vpath: #ArtVPath to convert. + * + * Converts a vector path into sorted vector path form. The svp form is + * more efficient for rendering and other vector operations. + * + * Basically, the implementation is to traverse the vector path, + * generating a new segment for each "run" of points in the vector + * path with monotonically increasing Y values. All the resulting + * values are then sorted. + * + * Note: I'm not sure that the sorting rule is correct with respect + * to numerical stability issues. + * + * Return value: Resulting sorted vector path. + **/ +ArtSVP * +art_svp_from_vpath (ArtVpath *vpath) +{ + int n_segs, n_segs_max; + ArtSVP *svp; + int dir; + int new_dir; + int i; + ArtPoint *points; + int n_points, n_points_max; + double x, y; + double x_min, x_max; + + n_segs = 0; + n_segs_max = 16; + svp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + + dir = 0; + n_points = 0; + n_points_max = 0; + points = NULL; + i = 0; + + x = y = 0; /* unnecessary, given "first code must not be LINETO" invariant, + but it makes gcc -Wall -ansi -pedantic happier */ + x_min = x_max = 0; /* same */ + + while (vpath[i].code != ART_END) { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) + { + if (points != NULL && n_points >= 2) + { + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + points = NULL; + } + + if (points == NULL) + { + n_points_max = 4; + points = art_new (ArtPoint, n_points_max); + } + + n_points = 1; + points[0].x = x = vpath[i].x; + points[0].y = y = vpath[i].y; + x_min = x; + x_max = x; + dir = 0; + } + else /* must be LINETO */ + { + new_dir = (vpath[i].y > y || + (vpath[i].y == y && vpath[i].x > x)) ? 1 : -1; + if (dir && dir != new_dir) + { + /* new segment */ + x = points[n_points - 1].x; + y = points[n_points - 1].y; + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + + n_points = 1; + n_points_max = 4; + points = art_new (ArtPoint, n_points_max); + points[0].x = x; + points[0].y = y; + x_min = x; + x_max = x; + } + + if (points != NULL) + { + if (n_points == n_points_max) + art_expand (points, ArtPoint, n_points_max); + points[n_points].x = x = vpath[i].x; + points[n_points].y = y = vpath[i].y; + if (x < x_min) x_min = x; + else if (x > x_max) x_max = x; + n_points++; + } + dir = new_dir; + } + i++; + } + + if (points != NULL) + { + if (n_points >= 2) + { + if (n_segs == n_segs_max) + { + n_segs_max <<= 1; + svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) + + (n_segs_max - 1) * + sizeof(ArtSVPSeg)); + } + svp->segs[n_segs].n_points = n_points; + svp->segs[n_segs].dir = (dir > 0); + if (dir < 0) + reverse_points (points, n_points); + svp->segs[n_segs].points = points; + svp->segs[n_segs].bbox.x0 = x_min; + svp->segs[n_segs].bbox.x1 = x_max; + svp->segs[n_segs].bbox.y0 = points[0].y; + svp->segs[n_segs].bbox.y1 = points[n_points - 1].y; + n_segs++; + } + else + art_free (points); + } + + svp->n_segs = n_segs; + + qsort (&svp->segs, n_segs, sizeof (ArtSVPSeg), art_svp_seg_compare); + + return svp; +} + diff --git a/lib/art/art_svp_vpath_stroke.c b/lib/art/art_svp_vpath_stroke.c new file mode 100644 index 0000000..bf3111b --- /dev/null +++ b/lib/art/art_svp_vpath_stroke.c @@ -0,0 +1,741 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#include "config.h" +#include "art_svp_vpath_stroke.h" + +#include +#include + +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" +#ifdef ART_USE_NEW_INTERSECTOR +#include "art_svp_intersect.h" +#else +#include "art_svp_wind.h" +#endif +#include "art_svp_vpath.h" + +#define EPSILON 1e-6 +#define EPSILON_2 1e-12 + +#define yes_OPTIMIZE_INNER + +/* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1, + yc + y1), centered at (xc, yc), and with given radius. Both x0^2 + + y0^2 and x1^2 + y1^2 should be equal to radius^2. + + A positive value of radius means curve to the left, negative means + curve to the right. +*/ +static void +art_svp_vpath_stroke_arc (ArtVpath **p_vpath, int *pn, int *pn_max, + double xc, double yc, + double x0, double y0, + double x1, double y1, + double radius, + double flatness) +{ + double theta; + double th_0, th_1; + int n_pts; + int i; + double aradius; + + aradius = fabs (radius); + theta = 2 * M_SQRT2 * sqrt (flatness / aradius); + th_0 = atan2 (y0, x0); + th_1 = atan2 (y1, x1); + if (radius > 0) + { + /* curve to the left */ + if (th_0 < th_1) th_0 += M_PI * 2; + n_pts = ceil ((th_0 - th_1) / theta); + } + else + { + /* curve to the right */ + if (th_1 < th_0) th_1 += M_PI * 2; + n_pts = ceil ((th_1 - th_0) / theta); + } +#ifdef VERBOSE + printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta); +#endif + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + x0, yc + y0); + for (i = 1; i < n_pts; i++) + { + theta = th_0 + (th_1 - th_0) * i / n_pts; + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + cos (theta) * aradius, + yc + sin (theta) * aradius); +#ifdef VERBOSE + printf ("mid %f %f\n", cos (theta) * radius, sin (theta) * radius); +#endif + } + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, xc + x1, yc + y1); +#ifdef VERBOSE + printf ("end %f %f\n", x1, y1); +#endif +} + +/* Assume that forw and rev are at point i0. Bring them to i1, + joining with the vector i1 - i2. + + This used to be true, but isn't now that the stroke_raw code is + filtering out (near)zero length vectors: {It so happens that all + invocations of this function maintain the precondition i1 = i0 + 1, + so we could decrease the number of arguments by one. We haven't + done that here, though.} + + forw is to the line's right and rev is to its left. + + Precondition: no zero-length vectors, otherwise a divide by + zero will happen. */ +static void +render_seg (ArtVpath **p_forw, int *pn_forw, int *pn_forw_max, + ArtVpath **p_rev, int *pn_rev, int *pn_rev_max, + ArtVpath *vpath, int i0, int i1, int i2, + ArtPathStrokeJoinType join, + double line_width, double miter_limit, double flatness) +{ + double dx0, dy0; + double dx1, dy1; + double dlx0, dly0; + double dlx1, dly1; + double dmx, dmy; + double dmr2; + double scale; + double cross; + +#ifdef VERBOSE + printf ("join style = %d\n", join); +#endif + + /* The vectors of the lines from i0 to i1 and i1 to i2. */ + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + dx1 = vpath[i2].x - vpath[i1].x; + dy1 = vpath[i2].y - vpath[i1].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + + /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx1 * dx1 + dy1 * dy1); + dlx1 = dy1 * scale; + dly1 = -dx1 * scale; + +#ifdef VERBOSE + printf ("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n", + vpath[i0].x, vpath[i0].y, + vpath[i1].x, vpath[i1].y, + vpath[i2].x, vpath[i2].y); + + printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n", + dx0, dy0, dlx0, dly0); + + printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n", + dx1, dy1, dlx1, dly1); +#endif + + /* now, forw's last point is expected to be colinear along d[xy]0 + to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */ + + /* positive for positive area (i.e. left turn) */ + cross = dx1 * dy0 - dx0 * dy1; + + dmx = (dlx0 + dlx1) * 0.5; + dmy = (dly0 + dly1) * 0.5; + dmr2 = dmx * dmx + dmy * dmy; + + if (join == ART_PATH_STROKE_JOIN_MITER && + dmr2 * miter_limit * miter_limit < line_width * line_width) + join = ART_PATH_STROKE_JOIN_BEVEL; + + /* the case when dmr2 is zero or very small bothers me + (i.e. near a 180 degree angle) + ALEX: So, we avoid the optimization when dmr2 is very small. This should + be safe since dmx/y is only used in optimization and in MITER case, and MITER + should be converted to BEVEL when dmr2 is very small. */ + if (dmr2 > EPSILON_2) + { + scale = line_width * line_width / dmr2; + dmx *= scale; + dmy *= scale; + } + + if (cross * cross < EPSILON_2 && dx0 * dx1 + dy0 * dy1 >= 0) + { + /* going straight */ +#ifdef VERBOSE + printf ("%% render_seg: straight\n"); +#endif + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + } + else if (cross > 0) + { + /* left turn, forw is outside and rev is inside */ + +#ifdef VERBOSE + printf ("%% render_seg: left\n"); +#endif + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 + dm[xy] is inside i0-i1 rectangle */ + (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 && + /* and that i1 + dm[xy] is inside i1-i2 rectangle */ + ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */ + (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 && + /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0) +#endif + ) + { + /* can safely add single intersection point */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } + else + { + /* need to loop-de-loop the inside */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) + { + /* bevel */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + else if (join == ART_PATH_STROKE_JOIN_MITER) + { + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } + else if (join == ART_PATH_STROKE_JOIN_ROUND) { + art_svp_vpath_stroke_arc (p_forw, pn_forw, pn_forw_max, + vpath[i1].x, vpath[i1].y, + -dlx0, -dly0, + -dlx1, -dly1, + line_width, + flatness); + } + } + else + { + /* right turn, rev is outside and forw is inside */ +#ifdef VERBOSE + printf ("%% render_seg: right\n"); +#endif + + if ( +#ifdef NO_OPTIMIZE_INNER + 0 && +#endif + (dmr2 > EPSILON_2) && + /* check that i1 - dm[xy] is inside i0-i1 rectangle */ + (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 && + /* and that i1 - dm[xy] is inside i1-i2 rectangle */ + ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0) +#ifdef PEDANTIC_INNER + && + /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */ + (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 && + /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */ + ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0) +#endif + ) + { + /* can safely add single intersection point */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy); + } + else + { + /* need to loop-de-loop the inside */ + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x, vpath[i1].y); + art_vpath_add_point (p_forw, pn_forw, pn_forw_max, + ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1); + } + + if (join == ART_PATH_STROKE_JOIN_BEVEL) + { + /* bevel */ + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1); + } + else if (join == ART_PATH_STROKE_JOIN_MITER) + { + art_vpath_add_point (p_rev, pn_rev, pn_rev_max, + ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy); + } + else if (join == ART_PATH_STROKE_JOIN_ROUND) { + art_svp_vpath_stroke_arc (p_rev, pn_rev, pn_rev_max, + vpath[i1].x, vpath[i1].y, + dlx0, dly0, + dlx1, dly1, + -line_width, + flatness); + } + + } +} + +/* caps i1, under the assumption of a vector from i0 */ +static void +render_cap (ArtVpath **p_result, int *pn_result, int *pn_result_max, + ArtVpath *vpath, int i0, int i1, + ArtPathStrokeCapType cap, double line_width, double flatness) +{ + double dx0, dy0; + double dlx0, dly0; + double scale; + int n_pts; + int i; + + dx0 = vpath[i1].x - vpath[i0].x; + dy0 = vpath[i1].y - vpath[i0].y; + + /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise + 90 degrees, and scaled to the length of line_width. */ + scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0); + dlx0 = dy0 * scale; + dly0 = -dx0 * scale; + +#ifdef VERBOSE + printf ("cap style = %d\n", cap); +#endif + + switch (cap) + { + case ART_PATH_STROKE_CAP_BUTT: + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_ROUND: + n_pts = ceil (M_PI / (2.0 * M_SQRT2 * sqrt (flatness / line_width))); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0); + for (i = 1; i < n_pts; i++) + { + double theta, c_th, s_th; + + theta = M_PI * i / n_pts; + c_th = cos (theta); + s_th = sin (theta); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 * c_th - dly0 * s_th, + vpath[i1].y - dly0 * c_th + dlx0 * s_th); + } + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0); + break; + case ART_PATH_STROKE_CAP_SQUARE: + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x - dlx0 - dly0, + vpath[i1].y - dly0 + dlx0); + art_vpath_add_point (p_result, pn_result, pn_result_max, + ART_LINETO, + vpath[i1].x + dlx0 - dly0, + vpath[i1].y + dly0 + dlx0); + break; + } +} + +/** + * art_svp_from_vpath_raw: Stroke a vector path, raw version + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Exactly the same as art_svp_vpath_stroke(), except that the resulting + * stroke outline may self-intersect and have regions of winding number + * greater than 1. + * + * Return value: Resulting raw stroked outline in svp format. + **/ +ArtVpath * +art_svp_vpath_stroke_raw (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) +{ + int begin_idx, end_idx; + int i; + ArtVpath *forw, *rev; + int n_forw, n_rev; + int n_forw_max, n_rev_max; + ArtVpath *result; + int n_result, n_result_max; + double half_lw = 0.5 * line_width; + int closed; + int last, this, next, second; + double dx, dy; + + n_forw_max = 16; + forw = art_new (ArtVpath, n_forw_max); + + n_rev_max = 16; + rev = art_new (ArtVpath, n_rev_max); + + n_result = 0; + n_result_max = 16; + result = art_new (ArtVpath, n_result_max); + + for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx) + { + n_forw = 0; + n_rev = 0; + + closed = (vpath[begin_idx].code == ART_MOVETO); + + /* we don't know what the first point joins with until we get to the + last point and see if it's closed. So we start with the second + line in the path. + + Note: this is not strictly true (we now know it's closed from + the opening pathcode), but why fix code that isn't broken? + */ + + this = begin_idx; + /* skip over identical points at the beginning of the subpath */ + for (i = this + 1; vpath[i].code == ART_LINETO; i++) + { + dx = vpath[i].x - vpath[this].x; + dy = vpath[i].y - vpath[this].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + second = next; + + /* invariant: this doesn't coincide with next */ + while (vpath[next].code == ART_LINETO) + { + last = this; + this = next; + /* skip over identical points after the beginning of the subpath */ + for (i = this + 1; vpath[i].code == ART_LINETO; i++) + { + dx = vpath[i].x - vpath[this].x; + dy = vpath[i].y - vpath[this].y; + if (dx * dx + dy * dy > EPSILON_2) + break; + } + next = i; + if (vpath[next].code != ART_LINETO) + { + /* reached end of path */ + /* make "closed" detection conform to PostScript + semantics (i.e. explicit closepath code rather than + just the fact that end of the path is the beginning) */ + if (closed && + vpath[this].x == vpath[begin_idx].x && + vpath[this].y == vpath[begin_idx].y) + { + int j; + + /* path is closed, render join to beginning */ + render_seg (&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this, second, + join, half_lw, miter_limit, flatness); + +#ifdef VERBOSE + printf ("%% forw %d, rev %d\n", n_forw, n_rev); +#endif + /* do forward path */ + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, forw[n_forw - 1].x, + forw[n_forw - 1].y); + for (j = 0; j < n_forw; j++) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + + /* do reverse path, reversed */ + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, rev[0].x, + rev[0].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + } + else + { + /* path is open */ + int j; + + /* add to forw rather than result to ensure that + forw has at least one point. */ + render_cap (&forw, &n_forw, &n_forw_max, + vpath, last, this, + cap, half_lw, flatness); + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO, forw[0].x, + forw[0].y); + for (j = 1; j < n_forw; j++) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[j].x, + forw[j].y); + for (j = n_rev - 1; j >= 0; j--) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, rev[j].x, + rev[j].y); + render_cap (&result, &n_result, &n_result_max, + vpath, second, begin_idx, + cap, half_lw, flatness); + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, forw[0].x, + forw[0].y); + } + } + else + render_seg (&forw, &n_forw, &n_forw_max, + &rev, &n_rev, &n_rev_max, + vpath, last, this, next, + join, half_lw, miter_limit, flatness); + } + end_idx = next; + } + + art_free (forw); + art_free (rev); +#ifdef VERBOSE + printf ("%% n_result = %d\n", n_result); +#endif + art_vpath_add_point (&result, &n_result, &n_result_max, ART_END, 0, 0); + return result; +} + +#define noVERBOSE + +#ifdef VERBOSE + +#define XOFF 50 +#define YOFF 700 + +static void +print_ps_vpath (ArtVpath *vpath) +{ + int i; + + for (i = 0; vpath[i].code != ART_END; i++) + { + switch (vpath[i].code) + { + case ART_MOVETO: + printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + case ART_LINETO: + printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y); + break; + default: + break; + } + } + printf ("stroke showpage\n"); +} + +static void +print_ps_svp (ArtSVP *vpath) +{ + int i, j; + + printf ("%% begin\n"); + for (i = 0; i < vpath->n_segs; i++) + { + printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0); + for (j = 0; j < vpath->segs[i].n_points; j++) + { + printf ("%g %g %s\n", + XOFF + vpath->segs[i].points[j].x, + YOFF - vpath->segs[i].points[j].y, + j ? "lineto" : "moveto"); + } + printf ("stroke\n"); + } + + printf ("showpage\n"); +} +#endif + +/* Render a vector path into a stroked outline. + + Status of this routine: + + Basic correctness: Only miter and bevel line joins are implemented, + and only butt line caps. Otherwise, seems to be fine. + + Numerical stability: We cheat (adding random perturbation). Thus, + it seems very likely that no numerical stability problems will be + seen in practice. + + Speed: Should be pretty good. + + Precision: The perturbation fuzzes the coordinates slightly, + but not enough to be visible. */ +/** + * art_svp_vpath_stroke: Stroke a vector path. + * @vpath: #ArtVPath to stroke. + * @join: Join style. + * @cap: Cap style. + * @line_width: Width of stroke. + * @miter_limit: Miter limit. + * @flatness: Flatness. + * + * Computes an svp representing the stroked outline of @vpath. The + * width of the stroked line is @line_width. + * + * Lines are joined according to the @join rule. Possible values are + * ART_PATH_STROKE_JOIN_MITER (for mitered joins), + * ART_PATH_STROKE_JOIN_ROUND (for round joins), and + * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join + * is converted to a bevelled join if the miter would extend to a + * distance of more than @miter_limit * @line_width from the actual + * join point. + * + * If there are open subpaths, the ends of these subpaths are capped + * according to the @cap rule. Possible values are + * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end + * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at + * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap, + * extending half @line_width past the end point). + * + * The @flatness parameter controls the accuracy of the rendering. It + * is most important for determining the number of points to use to + * approximate circular arcs for round lines and joins. In general, the + * resulting vector path will be within @flatness pixels of the "ideal" + * path containing actual circular arcs. I reserve the right to use + * the @flatness parameter to convert bevelled joins to miters for very + * small turn angles, as this would reduce the number of points in the + * resulting outline path. + * + * The resulting path is "clean" with respect to self-intersections, i.e. + * the winding number is 0 or 1 at each point. + * + * Return value: Resulting stroked outline in svp format. + **/ +ArtSVP * +art_svp_vpath_stroke (ArtVpath *vpath, + ArtPathStrokeJoinType join, + ArtPathStrokeCapType cap, + double line_width, + double miter_limit, + double flatness) +{ +#ifdef ART_USE_NEW_INTERSECTOR + ArtVpath *vpath_stroke; + ArtSVP *svp, *svp2; + ArtSvpWriter *swr; + + vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap, + line_width, miter_limit, flatness); +#ifdef VERBOSE + //print_ps_vpath (vpath_stroke); +#endif + svp = art_svp_from_vpath (vpath_stroke); +#ifdef VERBOSE + //print_ps_svp (svp); +#endif + art_free (vpath_stroke); + + swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO); + art_svp_intersector (svp, swr); + + svp2 = art_svp_writer_rewind_reap (swr); +#ifdef VERBOSE + //print_ps_svp (svp2); +#endif + art_svp_free (svp); + return svp2; +#else + ArtVpath *vpath_stroke, *vpath2; + ArtSVP *svp, *svp2, *svp3; + + vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap, + line_width, miter_limit, flatness); +#ifdef VERBOSE + //print_ps_vpath (vpath_stroke); +#endif + vpath2 = art_vpath_perturb (vpath_stroke); +#ifdef VERBOSE + //print_ps_vpath (vpath2); +#endif + art_free (vpath_stroke); + svp = art_svp_from_vpath (vpath2); +#ifdef VERBOSE + //print_ps_svp (svp); +#endif + art_free (vpath2); + svp2 = art_svp_uncross (svp); +#ifdef VERBOSE + //print_ps_svp (svp2); +#endif + art_svp_free (svp); + svp3 = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_NONZERO); +#ifdef VERBOSE + //print_ps_svp (svp3); +#endif + art_svp_free (svp2); + + return svp3; +#endif +} diff --git a/lib/art/art_svp_wind.c b/lib/art/art_svp_wind.c new file mode 100644 index 0000000..a12b1c7 --- /dev/null +++ b/lib/art/art_svp_wind.c @@ -0,0 +1,1545 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Primitive intersection and winding number operations on sorted + vector paths. + + These routines are internal to libart, used to construct operations + like intersection, union, and difference. */ + +#include "config.h" +#include "art_svp_wind.h" + +#include /* for printf of debugging info */ +#include /* for memcpy */ +#include +#include "art_misc.h" + +#include "art_rect.h" +#include "art_svp.h" + +#define noVERBOSE + +#define PT_EQ(p1,p2) ((p1).x == (p2).x && (p1).y == (p2).y) + +#define PT_CLOSE(p1,p2) (fabs ((p1).x - (p2).x) < 1e-6 && fabs ((p1).y - (p2).y) < 1e-6) + +/* return nonzero and set *p to the intersection point if the lines + z0-z1 and z2-z3 intersect each other. */ +static int +intersect_lines (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3, + ArtPoint *p) +{ + double a01, b01, c01; + double a23, b23, c23; + double d0, d1, d2, d3; + double det; + + /* if the vectors share an endpoint, they don't intersect */ + if (PT_EQ (z0, z2) || PT_EQ (z0, z3) || PT_EQ (z1, z2) || PT_EQ (z1, z3)) + return 0; + +#if 0 + if (PT_CLOSE (z0, z2) || PT_CLOSE (z0, z3) || PT_CLOSE (z1, z2) || PT_CLOSE (z1, z3)) + return 0; +#endif + + /* find line equations ax + by + c = 0 */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = (z1.x * z0.y - z1.y * z0.x) */ + + d2 = a01 * z2.x + b01 * z2.y + c01; + d3 = a01 * z3.x + b01 * z3.y + c01; + if ((d2 > 0) == (d3 > 0)) + return 0; + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + d0 = a23 * z0.x + b23 * z0.y + c23; + d1 = a23 * z1.x + b23 * z1.y + c23; + if ((d0 > 0) == (d1 > 0)) + return 0; + + /* now we definitely know that the lines intersect */ + /* solve the two linear equations ax + by + c = 0 */ + det = 1.0 / (a01 * b23 - a23 * b01); + p->x = det * (c23 * b01 - c01 * b23); + p->y = det * (c01 * a23 - c23 * a01); + + return 1; +} + +#define EPSILON 1e-6 + +static double +trap_epsilon (double v) +{ + const double epsilon = EPSILON; + + if (v < epsilon && v > -epsilon) return 0; + else return v; +} + +/* Determine the order of line segments z0-z1 and z2-z3. + Return +1 if z2-z3 lies entirely to the right of z0-z1, + -1 if entirely to the left, + or 0 if overlap. + + The case analysis in this function is quite ugly. The fact that it's + almost 200 lines long is ridiculous. + + Ok, so here's the plan to cut it down: + + First, do a bounding line comparison on the x coordinates. This is pretty + much the common case, and should go quickly. It also takes care of the + case where both lines are horizontal. + + Then, do d0 and d1 computation, but only if a23 is nonzero. + + Finally, do d2 and d3 computation, but only if a01 is nonzero. + + Fall through to returning 0 (this will happen when both lines are + horizontal and they overlap). + */ +static int +x_order (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3) +{ + double a01, b01, c01; + double a23, b23, c23; + double d0, d1, d2, d3; + + if (z0.y == z1.y) + { + if (z2.y == z3.y) + { + double x01min, x01max; + double x23min, x23max; + + if (z0.x > z1.x) + { + x01min = z1.x; + x01max = z0.x; + } + else + { + x01min = z0.x; + x01max = z1.x; + } + + if (z2.x > z3.x) + { + x23min = z3.x; + x23max = z2.x; + } + else + { + x23min = z2.x; + x23max = z3.x; + } + + if (x23min >= x01max) return 1; + else if (x01min >= x23max) return -1; + else return 0; + } + else + { + /* z0-z1 is horizontal, z2-z3 isn't */ + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (z3.y < z2.y) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + + d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23); + d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23); + + if (d0 > 0) + { + if (d1 >= 0) return 1; + else return 0; + } + else if (d0 == 0) + { + if (d1 > 0) return 1; + else if (d1 < 0) return -1; + else printf ("case 1 degenerate\n"); + return 0; + } + else /* d0 < 0 */ + { + if (d1 <= 0) return -1; + else return 0; + } + } + } + else if (z2.y == z3.y) + { + /* z2-z3 is horizontal, z0-z1 isn't */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = (z1.x * z0.y - z1.y * z0.x) */ + + if (z1.y < z0.y) + { + a01 = -a01; + b01 = -b01; + c01 = -c01; + } + + d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01); + d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01); + + if (d2 > 0) + { + if (d3 >= 0) return -1; + else return 0; + } + else if (d2 == 0) + { + if (d3 > 0) return -1; + else if (d3 < 0) return 1; + else printf ("case 2 degenerate\n"); + return 0; + } + else /* d2 < 0 */ + { + if (d3 <= 0) return 1; + else return 0; + } + } + + /* find line equations ax + by + c = 0 */ + a01 = z0.y - z1.y; + b01 = z1.x - z0.x; + c01 = -(z0.x * a01 + z0.y * b01); + /* = -((z0.y - z1.y) * z0.x + (z1.x - z0.x) * z0.y) + = -(z1.x * z0.y - z1.y * z0.x) */ + + if (a01 > 0) + { + a01 = -a01; + b01 = -b01; + c01 = -c01; + } + /* so now, (a01, b01) points to the left, thus a01 * x + b01 * y + c01 + is negative if the point lies to the right of the line */ + + d2 = trap_epsilon (a01 * z2.x + b01 * z2.y + c01); + d3 = trap_epsilon (a01 * z3.x + b01 * z3.y + c01); + if (d2 > 0) + { + if (d3 >= 0) return -1; + } + else if (d2 == 0) + { + if (d3 > 0) return -1; + else if (d3 < 0) return 1; + else + fprintf (stderr, "colinear!\n"); + } + else /* d2 < 0 */ + { + if (d3 <= 0) return 1; + } + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (a23 > 0) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + d0 = trap_epsilon (a23 * z0.x + b23 * z0.y + c23); + d1 = trap_epsilon (a23 * z1.x + b23 * z1.y + c23); + if (d0 > 0) + { + if (d1 >= 0) return 1; + } + else if (d0 == 0) + { + if (d1 > 0) return 1; + else if (d1 < 0) return -1; + else + fprintf (stderr, "colinear!\n"); + } + else /* d0 < 0 */ + { + if (d1 <= 0) return -1; + } + + return 0; +} + +/* similar to x_order, but to determine whether point z0 + epsilon lies to + the left of the line z2-z3 or to the right */ +static int +x_order_2 (ArtPoint z0, ArtPoint z1, ArtPoint z2, ArtPoint z3) +{ + double a23, b23, c23; + double d0, d1; + + a23 = z2.y - z3.y; + b23 = z3.x - z2.x; + c23 = -(z2.x * a23 + z2.y * b23); + + if (a23 > 0) + { + a23 = -a23; + b23 = -b23; + c23 = -c23; + } + + d0 = a23 * z0.x + b23 * z0.y + c23; + + if (d0 > EPSILON) + return -1; + else if (d0 < -EPSILON) + return 1; + + d1 = a23 * z1.x + b23 * z1.y + c23; + if (d1 > EPSILON) + return -1; + else if (d1 < -EPSILON) + return 1; + + if (z0.x == z1.x && z1.x == z2.x && z2.x == z3.x) + { + art_dprint ("x_order_2: colinear and horizontally aligned!\n"); + return 0; + } + + if (z0.x <= z2.x && z1.x <= z2.x && z0.x <= z3.x && z1.x <= z3.x) + return -1; + if (z0.x >= z2.x && z1.x >= z2.x && z0.x >= z3.x && z1.x >= z3.x) + return 1; + + fprintf (stderr, "x_order_2: colinear!\n"); + return 0; +} + +#ifdef DEAD_CODE +/* Traverse the vector path, keeping it in x-sorted order. + + This routine doesn't actually do anything - it's just here for + explanatory purposes. */ +void +traverse (ArtSVP *vp) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { + printf ("y = %g\n", y); + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { + printf ("deleting %d\n", asi); + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { + cursor[seg_idx] = 0; + printf ("inserting %d\n", seg_idx); + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order (vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1], + vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1]) == -1) + break; + } + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + seg_idx++; + } + + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d (%g, %g) - (%g, %g) %s\n", asi, + vp->segs[asi].points[cursor[asi]].x, + vp->segs[asi].points[cursor[asi]].y, + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^"); + } + + /* advance y to the next event */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + y = vp->segs[asi].points[cursor[asi] + 1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (y > vp->segs[asi].points[cursor[asi] + 1].y) + y = vp->segs[asi].points[cursor[asi] + 1].y; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + while (cursor[asi] < vp->segs[asi].n_points - 1 && + y >= vp->segs[asi].points[cursor[asi] + 1].y) + cursor[asi]++; + } + printf ("\n"); + } + art_free (cursor); + art_free (active_segs); +} +#endif + +/* I believe that the loop will always break with i=1. + + I think I'll want to change this from a simple sorted list to a + modified stack. ips[*][0] will get its own data structure, and + ips[*] will in general only be allocated if there is an intersection. + Finally, the segment can be traced through the initial point + (formerly ips[*][0]), backwards through the stack, and finally + to cursor + 1. + + This change should cut down on allocation bandwidth, and also + eliminate the iteration through n_ipl below. + +*/ +static void +insert_ip (int seg_i, int *n_ips, int *n_ips_max, ArtPoint **ips, ArtPoint ip) +{ + int i; + ArtPoint tmp1, tmp2; + int n_ipl; + ArtPoint *ipl; + + n_ipl = n_ips[seg_i]++; + if (n_ipl == n_ips_max[seg_i]) + art_expand (ips[seg_i], ArtPoint, n_ips_max[seg_i]); + ipl = ips[seg_i]; + for (i = 1; i < n_ipl; i++) + if (ipl[i].y > ip.y) + break; + tmp1 = ip; + for (; i <= n_ipl; i++) + { + tmp2 = ipl[i]; + ipl[i] = tmp1; + tmp1 = tmp2; + } +} + +/* test active segment (i - 1) against i for intersection, if + so, add intersection point to both ips lists. */ +static void +intersect_neighbors (int i, int *active_segs, + int *n_ips, int *n_ips_max, ArtPoint **ips, + int *cursor, ArtSVP *vp) +{ + ArtPoint z0, z1, z2, z3; + int asi01, asi23; + ArtPoint ip; + + asi01 = active_segs[i - 1]; + + z0 = ips[asi01][0]; + if (n_ips[asi01] == 1) + z1 = vp->segs[asi01].points[cursor[asi01] + 1]; + else + z1 = ips[asi01][1]; + + asi23 = active_segs[i]; + + z2 = ips[asi23][0]; + if (n_ips[asi23] == 1) + z3 = vp->segs[asi23].points[cursor[asi23] + 1]; + else + z3 = ips[asi23][1]; + + if (intersect_lines (z0, z1, z2, z3, &ip)) + { +#ifdef VERBOSE + printf ("new intersection point: (%g, %g)\n", ip.x, ip.y); +#endif + insert_ip (asi01, n_ips, n_ips_max, ips, ip); + insert_ip (asi23, n_ips, n_ips_max, ips, ip); + } +} + +/* Add a new point to a segment in the svp. + + Here, we also check to make sure that the segments satisfy nocross. + However, this is only valuable for debugging, and could possibly be + removed. +*/ +static void +svp_add_point (ArtSVP *svp, int *n_points_max, + ArtPoint p, int *seg_map, int *active_segs, int n_active_segs, + int i) +{ + int asi, asi_left, asi_right; + int n_points, n_points_left, n_points_right; + ArtSVPSeg *seg; + + asi = seg_map[active_segs[i]]; + seg = &svp->segs[asi]; + n_points = seg->n_points; + /* find out whether neighboring segments share a point */ + if (i > 0) + { + asi_left = seg_map[active_segs[i - 1]]; + n_points_left = svp->segs[asi_left].n_points; + if (n_points_left > 1 && + PT_EQ (svp->segs[asi_left].points[n_points_left - 2], + svp->segs[asi].points[n_points - 1])) + { + /* ok, new vector shares a top point with segment to the left - + now, check that it satisfies ordering invariant */ + if (x_order (svp->segs[asi_left].points[n_points_left - 2], + svp->segs[asi_left].points[n_points_left - 1], + svp->segs[asi].points[n_points - 1], + p) < 1) + + { +#ifdef VERBOSE + printf ("svp_add_point: cross on left!\n"); +#endif + } + } + } + + if (i + 1 < n_active_segs) + { + asi_right = seg_map[active_segs[i + 1]]; + n_points_right = svp->segs[asi_right].n_points; + if (n_points_right > 1 && + PT_EQ (svp->segs[asi_right].points[n_points_right - 2], + svp->segs[asi].points[n_points - 1])) + { + /* ok, new vector shares a top point with segment to the right - + now, check that it satisfies ordering invariant */ + if (x_order (svp->segs[asi_right].points[n_points_right - 2], + svp->segs[asi_right].points[n_points_right - 1], + svp->segs[asi].points[n_points - 1], + p) > -1) + { +#ifdef VERBOSE + printf ("svp_add_point: cross on right!\n"); +#endif + } + } + } + if (n_points_max[asi] == n_points) + art_expand (seg->points, ArtPoint, n_points_max[asi]); + seg->points[n_points] = p; + if (p.x < seg->bbox.x0) + seg->bbox.x0 = p.x; + else if (p.x > seg->bbox.x1) + seg->bbox.x1 = p.x; + seg->bbox.y1 = p.y; + seg->n_points++; +} + +#if 0 +/* find where the segment (currently at i) is supposed to go, and return + the target index - if equal to i, then there is no crossing problem. + + "Where it is supposed to go" is defined as following: + + Delete element i, re-insert at position target (bumping everything + target and greater to the right). + */ +static int +find_crossing (int i, int *active_segs, int n_active_segs, + int *cursor, ArtPoint **ips, int *n_ips, ArtSVP *vp) +{ + int asi, asi_left, asi_right; + ArtPoint p0, p1; + ArtPoint p0l, p1l; + ArtPoint p0r, p1r; + int target; + + asi = active_segs[i]; + p0 = ips[asi][0]; + if (n_ips[asi] == 1) + p1 = vp->segs[asi].points[cursor[asi] + 1]; + else + p1 = ips[asi][1]; + + for (target = i; target > 0; target--) + { + asi_left = active_segs[target - 1]; + p0l = ips[asi_left][0]; + if (n_ips[asi_left] == 1) + p1l = vp->segs[asi_left].points[cursor[asi_left] + 1]; + else + p1l = ips[asi_left][1]; + if (!PT_EQ (p0, p0l)) + break; + +#ifdef VERBOSE + printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n", + p0l.x, p0l.y, p1l.x, p1l.y, p0.x, p0.y, p1.x, p1.y); +#endif + if (x_order (p0l, p1l, p0, p1) == 1) + break; + +#ifdef VERBOSE + printf ("scanning to the left (i=%d, target=%d)\n", i, target); +#endif + } + + if (target < i) return target; + + for (; target < n_active_segs - 1; target++) + { + asi_right = active_segs[target + 1]; + p0r = ips[asi_right][0]; + if (n_ips[asi_right] == 1) + p1r = vp->segs[asi_right].points[cursor[asi_right] + 1]; + else + p1r = ips[asi_right][1]; + if (!PT_EQ (p0, p0r)) + break; + +#ifdef VERBOSE + printf ("point matches on left (%g, %g) - (%g, %g) x (%g, %g) - (%g, %g)!\n", + p0.x, p0.y, p1.x, p1.y, p0r.x, p0r.y, p1r.x, p1r.y); +#endif + if (x_order (p0r, p1r, p0, p1) == 1) + break; + +#ifdef VERBOSE + printf ("scanning to the right (i=%d, target=%d)\n", i, target); +#endif + } + + return target; +} +#endif + +/* This routine handles the case where the segment changes its position + in the active segment list. Generally, this will happen when the + segment (defined by i and cursor) shares a top point with a neighbor, + but breaks the ordering invariant. + + Essentially, this routine sorts the lines [start..end), all of which + share a top point. This is implemented as your basic insertion sort. + + This routine takes care of intersecting the appropriate neighbors, + as well. + + A first argument of -1 immediately returns, which helps reduce special + casing in the main unwind routine. +*/ +static void +fix_crossing (int start, int end, int *active_segs, int n_active_segs, + int *cursor, ArtPoint **ips, int *n_ips, int *n_ips_max, + ArtSVP *vp, int *seg_map, + ArtSVP **p_new_vp, int *pn_segs_max, + int **pn_points_max) +{ + int i, j; + int target; + int asi, asj; + ArtPoint p0i, p1i; + ArtPoint p0j, p1j; + int swap = 0; +#ifdef VERBOSE + int k; +#endif + ArtPoint *pts; + +#ifdef VERBOSE + printf ("fix_crossing: [%d..%d)", start, end); + for (k = 0; k < n_active_segs; k++) + printf (" %d", active_segs[k]); + printf ("\n"); +#endif + + if (start == -1) + return; + + for (i = start + 1; i < end; i++) + { + + asi = active_segs[i]; + if (cursor[asi] < vp->segs[asi].n_points - 1) { + p0i = ips[asi][0]; + if (n_ips[asi] == 1) + p1i = vp->segs[asi].points[cursor[asi] + 1]; + else + p1i = ips[asi][1]; + + for (j = i - 1; j >= start; j--) + { + asj = active_segs[j]; + if (cursor[asj] < vp->segs[asj].n_points - 1) + { + p0j = ips[asj][0]; + if (n_ips[asj] == 1) + p1j = vp->segs[asj].points[cursor[asj] + 1]; + else + p1j = ips[asj][1]; + + /* we _hope_ p0i = p0j */ + if (x_order_2 (p0j, p1j, p0i, p1i) == -1) + break; + } + } + + target = j + 1; + /* target is where active_seg[i] _should_ be in active_segs */ + + if (target != i) + { + swap = 1; + +#ifdef VERBOSE + printf ("fix_crossing: at %i should be %i\n", i, target); +#endif + + /* let's close off all relevant segments */ + for (j = i; j >= target; j--) + { + asi = active_segs[j]; + /* First conjunct: this isn't the last point in the original + segment. + + Second conjunct: this isn't the first point in the new + segment (i.e. already broken). + */ + if (cursor[asi] < vp->segs[asi].n_points - 1 && + (*p_new_vp)->segs[seg_map[asi]].n_points != 1) + { + int seg_num; + /* so break here */ +#ifdef VERBOSE + printf ("closing off %d\n", j); +#endif + + pts = art_new (ArtPoint, 16); + pts[0] = ips[asi][0]; + seg_num = art_svp_add_segment (p_new_vp, pn_segs_max, + pn_points_max, + 1, vp->segs[asi].dir, + pts, + NULL); + (*pn_points_max)[seg_num] = 16; + seg_map[asi] = seg_num; + } + } + + /* now fix the ordering in active_segs */ + asi = active_segs[i]; + for (j = i; j > target; j--) + active_segs[j] = active_segs[j - 1]; + active_segs[j] = asi; + } + } + } + if (swap && start > 0) + { + int as_start; + + as_start = active_segs[start]; + if (cursor[as_start] < vp->segs[as_start].n_points) + { +#ifdef VERBOSE + printf ("checking intersection of %d, %d\n", start - 1, start); +#endif + intersect_neighbors (start, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + + if (swap && end < n_active_segs) + { + int as_end; + + as_end = active_segs[end - 1]; + if (cursor[as_end] < vp->segs[as_end].n_points) + { +#ifdef VERBOSE + printf ("checking intersection of %d, %d\n", end - 1, end); +#endif + intersect_neighbors (end, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + if (swap) + { +#ifdef VERBOSE + printf ("fix_crossing return: [%d..%d)", start, end); + for (k = 0; k < n_active_segs; k++) + printf (" %d", active_segs[k]); + printf ("\n"); +#endif + } +} + +/* Return a new sorted vector that covers the same area as the + argument, but which satisfies the nocross invariant. + + Basically, this routine works by finding the intersection points, + and cutting the segments at those points. + + Status of this routine: + + Basic correctness: Seems ok. + + Numerical stability: known problems in the case of points falling + on lines, and colinear lines. For actual use, randomly perturbing + the vertices is currently recommended. + + Speed: pretty good, although a more efficient priority queue, as + well as bbox culling of potential intersections, are two + optimizations that could help. + + Precision: pretty good, although the numerical stability problems + make this routine unsuitable for precise calculations of + differences. + +*/ + +/* Here is a more detailed description of the algorithm. It follows + roughly the structure of traverse (above), but is obviously quite + a bit more complex. + + Here are a few important data structures: + + A new sorted vector path (new_svp). + + For each (active) segment in the original, a list of intersection + points. + + Of course, the original being traversed. + + The following invariants hold (in addition to the invariants + of the traverse procedure). + + The new sorted vector path lies entirely above the y scan line. + + The new sorted vector path keeps the nocross invariant. + + For each active segment, the y scan line crosses the line from the + first to the second of the intersection points (where the second + point is cursor + 1 if there is only one intersection point). + + The list of intersection points + the (cursor + 1) point is kept + in nondecreasing y order. + + Of the active segments, none of the lines from first to second + intersection point cross the 1st ip..2nd ip line of the left or + right neighbor. (However, such a line may cross further + intersection points of the neighbors, or segments past the + immediate neighbors). + + Of the active segments, all lines from 1st ip..2nd ip are in + strictly increasing x_order (this is very similar to the invariant + of the traverse procedure, but is explicitly stated here in terms + of ips). (this basically says that nocross holds on the active + segments) + + The combination of the new sorted vector path, the path through all + the intersection points to cursor + 1, and [cursor + 1, n_points) + covers the same area as the argument. + + Another important data structure is mapping from original segment + number to new segment number. + + The algorithm is perhaps best understood as advancing the cursors + while maintaining these invariants. Here's roughly how it's done. + + When deleting segments from the active list, those segments are added + to the new sorted vector path. In addition, the neighbors may intersect + each other, so they are intersection tested (see below). + + When inserting new segments, they are intersection tested against + their neighbors. The top point of the segment becomes the first + intersection point. + + Advancing the cursor is just a bit different from the traverse + routine, as the cursor may advance through the intersection points + as well. Only when there is a single intersection point in the list + does the cursor advance in the original segment. In either case, + the new vector is intersection tested against both neighbors. It + also causes the vector over which the cursor is advancing to be + added to the new svp. + + Two steps need further clarification: + + Intersection testing: the 1st ip..2nd ip lines of the neighbors + are tested to see if they cross (using intersect_lines). If so, + then the intersection point is added to the ip list of both + segments, maintaining the invariant that the list of intersection + points is nondecreasing in y). + + Adding vector to new svp: if the new vector shares a top x + coordinate with another vector, then it is checked to see whether + it is in order. If not, then both segments are "broken," and then + restarted. Note: in the case when both segments are in the same + order, they may simply be swapped without breaking. + + For the time being, I'm going to put some of these operations into + subroutines. If it turns out to be a performance problem, I could + try to reorganize the traverse procedure so that each is only + called once, and inline them. But if it's not a performance + problem, I'll just keep it this way, because it will probably help + to make the code clearer, and I believe this code could use all the + clarity it can get. */ +/** + * art_svp_uncross: Resolve self-intersections of an svp. + * @vp: The original svp. + * + * Finds all the intersections within @vp, and constructs a new svp + * with new points added at these intersections. + * + * This routine needs to be redone from scratch with numerical robustness + * in mind. I'm working on it. + * + * Return value: The new svp. + **/ +ArtSVP * +art_svp_uncross (ArtSVP *vp) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + /* new data structures */ + /* intersection points; invariant: *ips[i] is only allocated if + i is active */ + int *n_ips, *n_ips_max; + ArtPoint **ips; + /* new sorted vector path */ + int n_segs_max, seg_num; + ArtSVP *new_vp; + int *n_points_max; + /* mapping from argument to new segment numbers - again, only valid + if active */ + int *seg_map; + double y_curs; + ArtPoint p_curs; + int first_share; + double share_x; + ArtPoint *pts; + + n_segs_max = 16; + new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + new_vp->n_segs = 0; + + if (vp->n_segs == 0) + return new_vp; + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + seg_map = art_new (int, vp->n_segs); + n_ips = art_new (int, vp->n_segs); + n_ips_max = art_new (int, vp->n_segs); + ips = art_new (ArtPoint *, vp->n_segs); + + n_points_max = art_new (int, n_segs_max); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { +#ifdef VERBOSE + printf ("y = %g\n", y); +#endif + + /* maybe move deletions to end of loop (to avoid so much special + casing on the end of a segment)? */ + + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { + do + { +#ifdef VERBOSE + printf ("deleting %d\n", asi); +#endif + art_free (ips[asi]); + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + if (i < n_active_segs) + asi = active_segs[i]; + else + break; + } + while (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y); + + /* test intersection of neighbors */ + if (i > 0 && i < n_active_segs) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { +#ifdef VERBOSE + printf ("inserting %d\n", seg_idx); +#endif + cursor[seg_idx] = 0; + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order_2 (vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1], + vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1]) == -1) + break; + } + + /* Create and initialize the intersection points data structure */ + n_ips[seg_idx] = 1; + n_ips_max[seg_idx] = 2; + ips[seg_idx] = art_new (ArtPoint, n_ips_max[seg_idx]); + ips[seg_idx][0] = vp->segs[seg_idx].points[0]; + + /* Start a new segment in the new vector path */ + pts = art_new (ArtPoint, 16); + pts[0] = vp->segs[seg_idx].points[0]; + seg_num = art_svp_add_segment (&new_vp, &n_segs_max, + &n_points_max, + 1, vp->segs[seg_idx].dir, + pts, + NULL); + n_points_max[seg_num] = 16; + seg_map[seg_idx] = seg_num; + + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + + if (i > 0) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + if (i + 1 < n_active_segs) + intersect_neighbors (i + 1, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + seg_idx++; + } + + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ +#ifdef VERBOSE + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d ", asi); + for (j = 0; j < n_ips[asi]; j++) + printf ("(%g, %g) - ", + ips[asi][j].x, + ips[asi][j].y); + printf ("(%g, %g) %s\n", + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^"); + } +#endif + + /* advance y to the next event + Note: this is quadratic. We'd probably get decent constant + factor speed improvement by caching the y_curs values. */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + if (n_ips[asi] == 1) + y = vp->segs[asi].points[cursor[asi] + 1].y; + else + y = ips[asi][1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (n_ips[asi] == 1) + y_curs = vp->segs[asi].points[cursor[asi] + 1].y; + else + y_curs = ips[asi][1].y; + if (y > y_curs) + y = y_curs; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + first_share = -1; + share_x = 0; /* to avoid gcc warning, although share_x is never + used when first_share is -1 */ + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (n_ips[asi] == 1) + p_curs = vp->segs[asi].points[cursor[asi] + 1]; + else + p_curs = ips[asi][1]; + if (p_curs.y == y) + { + svp_add_point (new_vp, n_points_max, + p_curs, seg_map, active_segs, n_active_segs, i); + + n_ips[asi]--; + for (j = 0; j < n_ips[asi]; j++) + ips[asi][j] = ips[asi][j + 1]; + + if (n_ips[asi] == 0) + { + ips[asi][0] = p_curs; + n_ips[asi] = 1; + cursor[asi]++; + } + + if (first_share < 0 || p_curs.x != share_x) + { + /* this is where crossings are detected, and if + found, the active segments switched around. */ + + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + + first_share = i; + share_x = p_curs.x; + } + + if (cursor[asi] < vp->segs[asi].n_points - 1) + { + + if (i > 0) + intersect_neighbors (i, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + + if (i + 1 < n_active_segs) + intersect_neighbors (i + 1, active_segs, + n_ips, n_ips_max, ips, + cursor, vp); + } + } + else + { + /* not on a cursor point */ + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + first_share = -1; + } + } + + /* fix crossing on last shared group */ + fix_crossing (first_share, i, + active_segs, n_active_segs, + cursor, ips, n_ips, n_ips_max, vp, seg_map, + &new_vp, + &n_segs_max, &n_points_max); + +#ifdef VERBOSE + printf ("\n"); +#endif + } + + /* not necessary to sort, new segments only get added at y, which + increases monotonically */ +#if 0 + qsort (&new_vp->segs, new_vp->n_segs, sizeof (svp_seg), svp_seg_compare); + { + int k; + for (k = 0; k < new_vp->n_segs - 1; k++) + { + printf ("(%g, %g) - (%g, %g) %s (%g, %g) - (%g, %g)\n", + new_vp->segs[k].points[0].x, + new_vp->segs[k].points[0].y, + new_vp->segs[k].points[1].x, + new_vp->segs[k].points[1].y, + svp_seg_compare (&new_vp->segs[k], &new_vp->segs[k + 1]) > 1 ? ">": "<", + new_vp->segs[k + 1].points[0].x, + new_vp->segs[k + 1].points[0].y, + new_vp->segs[k + 1].points[1].x, + new_vp->segs[k + 1].points[1].y); + } + } +#endif + + art_free (n_points_max); + art_free (seg_map); + art_free (n_ips_max); + art_free (n_ips); + art_free (ips); + art_free (cursor); + art_free (active_segs); + + return new_vp; +} + +#define noVERBOSE + +/* Rewind a svp satisfying the nocross invariant. + + The winding number of a segment is defined as the winding number of + the points to the left while travelling in the direction of the + segment. Therefore it preincrements and postdecrements as a scan + line is traversed from left to right. + + Status of this routine: + + Basic correctness: Was ok in gfonted. However, this code does not + yet compute bboxes for the resulting svp segs. + + Numerical stability: known problems in the case of horizontal + segments in polygons with any complexity. For actual use, randomly + perturbing the vertices is recommended. + + Speed: good. + + Precision: good, except that no attempt is made to remove "hair". + Doing random perturbation just makes matters worse. + +*/ +/** + * art_svp_rewind_uncrossed: Rewind an svp satisfying the nocross invariant. + * @vp: The original svp. + * @rule: The winding rule. + * + * Creates a new svp with winding number of 0 or 1 everywhere. The @rule + * argument specifies a rule for how winding numbers in the original + * @vp map to the winding numbers in the result. + * + * With @rule == ART_WIND_RULE_NONZERO, the resulting svp has a + * winding number of 1 where @vp has a nonzero winding number. + * + * With @rule == ART_WIND_RULE_INTERSECT, the resulting svp has a + * winding number of 1 where @vp has a winding number greater than + * 1. It is useful for computing intersections. + * + * With @rule == ART_WIND_RULE_ODDEVEN, the resulting svp has a + * winding number of 1 where @vp has an odd winding number. It is + * useful for implementing the even-odd winding rule of the + * PostScript imaging model. + * + * With @rule == ART_WIND_RULE_POSITIVE, the resulting svp has a + * winding number of 1 where @vp has a positive winding number. It is + * useful for implementing asymmetric difference. + * + * This routine needs to be redone from scratch with numerical robustness + * in mind. I'm working on it. + * + * Return value: The new svp. + **/ +ArtSVP * +art_svp_rewind_uncrossed (ArtSVP *vp, ArtWindRule rule) +{ + int *active_segs; + int n_active_segs; + int *cursor; + int seg_idx; + double y; + int tmp1, tmp2; + int asi; + int i, j; + + ArtSVP *new_vp; + int n_segs_max; + int *winding; + int left_wind; + int wind; + int keep, invert; + +#ifdef VERBOSE + print_svp (vp); +#endif + n_segs_max = 16; + new_vp = (ArtSVP *)art_alloc (sizeof(ArtSVP) + + (n_segs_max - 1) * sizeof(ArtSVPSeg)); + new_vp->n_segs = 0; + + if (vp->n_segs == 0) + return new_vp; + + winding = art_new (int, vp->n_segs); + + active_segs = art_new (int, vp->n_segs); + cursor = art_new (int, vp->n_segs); + + n_active_segs = 0; + seg_idx = 0; + y = vp->segs[0].points[0].y; + while (seg_idx < vp->n_segs || n_active_segs > 0) + { +#ifdef VERBOSE + printf ("y = %g\n", y); +#endif + /* delete segments ending at y from active list */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (vp->segs[asi].n_points - 1 == cursor[asi] && + vp->segs[asi].points[cursor[asi]].y == y) + { +#ifdef VERBOSE + printf ("deleting %d\n", asi); +#endif + n_active_segs--; + for (j = i; j < n_active_segs; j++) + active_segs[j] = active_segs[j + 1]; + i--; + } + } + + /* insert new segments into the active list */ + while (seg_idx < vp->n_segs && y == vp->segs[seg_idx].points[0].y) + { +#ifdef VERBOSE + printf ("inserting %d\n", seg_idx); +#endif + cursor[seg_idx] = 0; + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (x_order_2 (vp->segs[seg_idx].points[0], + vp->segs[seg_idx].points[1], + vp->segs[asi].points[cursor[asi]], + vp->segs[asi].points[cursor[asi] + 1]) == -1) + break; + } + + /* Determine winding number for this segment */ + if (i == 0) + left_wind = 0; + else if (vp->segs[active_segs[i - 1]].dir) + left_wind = winding[active_segs[i - 1]]; + else + left_wind = winding[active_segs[i - 1]] - 1; + + if (vp->segs[seg_idx].dir) + wind = left_wind + 1; + else + wind = left_wind; + + winding[seg_idx] = wind; + + switch (rule) + { + case ART_WIND_RULE_NONZERO: + keep = (wind == 1 || wind == 0); + invert = (wind == 0); + break; + case ART_WIND_RULE_INTERSECT: + keep = (wind == 2); + invert = 0; + break; + case ART_WIND_RULE_ODDEVEN: + keep = 1; + invert = !(wind & 1); + break; + case ART_WIND_RULE_POSITIVE: + keep = (wind == 1); + invert = 0; + break; + default: + keep = 0; + invert = 0; + break; + } + + if (keep) + { + ArtPoint *points, *new_points; + int n_points; + int new_dir; + +#ifdef VERBOSE + printf ("keeping segment %d\n", seg_idx); +#endif + n_points = vp->segs[seg_idx].n_points; + points = vp->segs[seg_idx].points; + new_points = art_new (ArtPoint, n_points); + memcpy (new_points, points, n_points * sizeof (ArtPoint)); + new_dir = vp->segs[seg_idx].dir ^ invert; + art_svp_add_segment (&new_vp, &n_segs_max, + NULL, + n_points, new_dir, new_points, + &vp->segs[seg_idx].bbox); + } + + tmp1 = seg_idx; + for (j = i; j < n_active_segs; j++) + { + tmp2 = active_segs[j]; + active_segs[j] = tmp1; + tmp1 = tmp2; + } + active_segs[n_active_segs] = tmp1; + n_active_segs++; + seg_idx++; + } + +#ifdef VERBOSE + /* all active segs cross the y scanline (considering segs to be + closed on top and open on bottom) */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + printf ("%d:%d (%g, %g) - (%g, %g) %s %d\n", asi, + cursor[asi], + vp->segs[asi].points[cursor[asi]].x, + vp->segs[asi].points[cursor[asi]].y, + vp->segs[asi].points[cursor[asi] + 1].x, + vp->segs[asi].points[cursor[asi] + 1].y, + vp->segs[asi].dir ? "v" : "^", + winding[asi]); + } +#endif + + /* advance y to the next event */ + if (n_active_segs == 0) + { + if (seg_idx < vp->n_segs) + y = vp->segs[seg_idx].points[0].y; + /* else we're done */ + } + else + { + asi = active_segs[0]; + y = vp->segs[asi].points[cursor[asi] + 1].y; + for (i = 1; i < n_active_segs; i++) + { + asi = active_segs[i]; + if (y > vp->segs[asi].points[cursor[asi] + 1].y) + y = vp->segs[asi].points[cursor[asi] + 1].y; + } + if (seg_idx < vp->n_segs && y > vp->segs[seg_idx].points[0].y) + y = vp->segs[seg_idx].points[0].y; + } + + /* advance cursors to reach new y */ + for (i = 0; i < n_active_segs; i++) + { + asi = active_segs[i]; + while (cursor[asi] < vp->segs[asi].n_points - 1 && + y >= vp->segs[asi].points[cursor[asi] + 1].y) + cursor[asi]++; + } +#ifdef VERBOSE + printf ("\n"); +#endif + } + art_free (cursor); + art_free (active_segs); + art_free (winding); + + return new_vp; +} diff --git a/lib/art/art_uta.c b/lib/art/art_uta.c new file mode 100644 index 0000000..10bd6ee --- /dev/null +++ b/lib/art/art_uta.c @@ -0,0 +1,88 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta.h" + +#include +#include "art_misc.h" + +/** + * art_uta_new: Allocate a new uta. + * @x0: Left coordinate of uta. + * @y0: Top coordinate of uta. + * @x1: Right coordinate of uta. + * @y1: Bottom coordinate of uta. + * + * Allocates a new microtile array. The arguments are in units of + * tiles, not pixels. + * + * Returns: the newly allocated #ArtUta. + **/ +ArtUta * +art_uta_new (int x0, int y0, int x1, int y1) +{ + ArtUta *uta; + + uta = art_new (ArtUta, 1); + uta->x0 = x0; + uta->y0 = y0; + uta->width = x1 - x0; + uta->height = y1 - y0; + + uta->utiles = art_new (ArtUtaBbox, uta->width * uta->height); + + memset (uta->utiles, 0, uta->width * uta->height * sizeof(ArtUtaBbox)); + return uta; + } + +/** + * art_uta_new_coords: Allocate a new uta, based on pixel coordinates. + * @x0: Left coordinate of uta. + * @y0: Top coordinate of uta. + * @x1: Right coordinate of uta. + * @y1: Bottom coordinate of uta. + * + * Allocates a new microtile array. The arguments are in pixels + * + * Returns: the newly allocated #ArtUta. + **/ +ArtUta * +art_uta_new_coords (int x0, int y0, int x1, int y1) +{ + return art_uta_new (x0 >> ART_UTILE_SHIFT, y0 >> ART_UTILE_SHIFT, + 1 + (x1 >> ART_UTILE_SHIFT), + 1 + (y1 >> ART_UTILE_SHIFT)); +} + +/** + * art_uta_free: Free a uta. + * @uta: The uta to free. + * + * Frees the microtile array structure, including the actual microtile + * data. + **/ +void +art_uta_free (ArtUta *uta) +{ + art_free (uta->utiles); + art_free (uta); +} + +/* User to Aardvark! */ diff --git a/lib/art/art_uta_ops.c b/lib/art/art_uta_ops.c new file mode 100644 index 0000000..1b42516 --- /dev/null +++ b/lib/art/art_uta_ops.c @@ -0,0 +1,112 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_ops.h" + +#include +#include "art_misc.h" +#include "art_uta.h" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** + * art_uta_union: Compute union of two uta's. + * @uta1: One uta. + * @uta2: The other uta. + * + * Computes the union of @uta1 and @uta2. The union is approximate, + * but coverage is guaranteed over all pixels included in either of + * the arguments, ie more pixels may be covered than the "exact" + * union. + * + * Note: this routine is used in the Gnome Canvas to accumulate the + * region that needs to be repainted. However, since it copies over + * the entire uta (which might be largish) even when the update may be + * small, it can be a performance bottleneck. There are two approaches + * to this problem, both of which are probably worthwhile. First, the + * generated uta's should always be limited to the visible window, + * thus guaranteeing that uta's never become large. Second, there + * should be a new, destructive union operation that only touches a + * small part of the uta when the update is small. + * + * Return value: The new union uta. + **/ +ArtUta * +art_uta_union (ArtUta *uta1, ArtUta *uta2) +{ + ArtUta *uta; + int x0, y0, x1, y1; + int x, y; + int ix, ix1, ix2; + ArtUtaBbox bb, bb1, bb2; + + x0 = MIN(uta1->x0, uta2->x0); + y0 = MIN(uta1->y0, uta2->y0); + x1 = MAX(uta1->x0 + uta1->width, uta2->x0 + uta2->width); + y1 = MAX(uta1->y0 + uta1->height, uta2->y0 + uta2->height); + uta = art_uta_new (x0, y0, x1, y1); + + /* could move the first two if/else statements out of the loop */ + ix = 0; + for (y = y0; y < y1; y++) + { + ix1 = (y - uta1->y0) * uta1->width + x0 - uta1->x0; + ix2 = (y - uta2->y0) * uta2->width + x0 - uta2->x0; + for (x = x0; x < x1; x++) + { + if (x < uta1->x0 || y < uta1->y0 || + x >= uta1->x0 + uta1->width || y >= uta1->y0 + uta1->height) + bb1 = 0; + else + bb1 = uta1->utiles[ix1]; + + if (x < uta2->x0 || y < uta2->y0 || + x >= uta2->x0 + uta2->width || y >= uta2->y0 + uta2->height) + bb2 = 0; + else + bb2 = uta2->utiles[ix2]; + + if (bb1 == 0) + bb = bb2; + else if (bb2 == 0) + bb = bb1; + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb1), + ART_UTA_BBOX_X0(bb2)), + MIN(ART_UTA_BBOX_Y0(bb1), + ART_UTA_BBOX_Y0(bb2)), + MAX(ART_UTA_BBOX_X1(bb1), + ART_UTA_BBOX_X1(bb2)), + MAX(ART_UTA_BBOX_Y1(bb1), + ART_UTA_BBOX_Y1(bb2))); + uta->utiles[ix] = bb; + ix++; + ix1++; + ix2++; + } + } + return uta; +} diff --git a/lib/art/art_uta_rect.c b/lib/art/art_uta_rect.c new file mode 100644 index 0000000..68a6053 --- /dev/null +++ b/lib/art/art_uta_rect.c @@ -0,0 +1,111 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_rect.h" + +#include "art_misc.h" +#include "art_uta.h" +#include "art_rect.h" + +/** + * art_uta_from_irect: Generate uta covering a rectangle. + * @bbox: The source rectangle. + * + * Generates a uta exactly covering @bbox. Please do not call this + * function with a @bbox with zero height or width. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_irect (ArtIRect *bbox) +{ + ArtUta *uta; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int width, height; + int x, y; + int xf0, yf0, xf1, yf1; + int ix; + + uta = art_new (ArtUta, 1); + uta->x0 = bbox->x0 >> ART_UTILE_SHIFT; + uta->y0 = bbox->y0 >> ART_UTILE_SHIFT; + width = ((bbox->x1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->x0; + height = ((bbox->y1 + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT) - uta->y0; + utiles = art_new (ArtUtaBbox, width * height); + + uta->width = width; + uta->height = height; + uta->utiles = utiles; + + xf0 = bbox->x0 & (ART_UTILE_SIZE - 1); + yf0 = bbox->y0 & (ART_UTILE_SIZE - 1); + xf1 = ((bbox->x1 - 1) & (ART_UTILE_SIZE - 1)) + 1; + yf1 = ((bbox->y1 - 1) & (ART_UTILE_SIZE - 1)) + 1; + if (height == 1) + { + if (width == 1) + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, yf1); + else + { + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, yf1); + bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, yf1); + } + } + else + { + if (width == 1) + { + utiles[0] = ART_UTA_BBOX_CONS (xf0, yf0, xf1, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (xf0, 0, xf1, ART_UTILE_SIZE); + for (y = 1; y < height - 1; y++) + utiles[y] = bb; + utiles[y] = ART_UTA_BBOX_CONS (xf0, 0, xf1, yf1); + } + else + { + utiles[0] = + ART_UTA_BBOX_CONS (xf0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (0, yf0, ART_UTILE_SIZE, ART_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[x] = bb; + utiles[x] = ART_UTA_BBOX_CONS (0, yf0, xf1, ART_UTILE_SIZE); + ix = width; + for (y = 1; y < height - 1; y++) + { + utiles[ix++] = + ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE); + bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, ART_UTILE_SIZE); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, ART_UTILE_SIZE); + } + utiles[ix++] = ART_UTA_BBOX_CONS (xf0, 0, ART_UTILE_SIZE, yf1); + bb = ART_UTA_BBOX_CONS (0, 0, ART_UTILE_SIZE, yf1); + for (x = 1; x < width - 1; x++) + utiles[ix++] = bb; + utiles[ix++] = ART_UTA_BBOX_CONS (0, 0, xf1, yf1); + } + } + return uta; +} diff --git a/lib/art/art_uta_svp.c b/lib/art/art_uta_svp.c new file mode 100644 index 0000000..2a0f372 --- /dev/null +++ b/lib/art/art_uta_svp.c @@ -0,0 +1,54 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* LGPL Copyright 1998 Raph Levien */ + +#include "config.h" +#include "art_uta_svp.h" + +#include "art_misc.h" +#include "art_vpath.h" +#include "art_uta.h" +#include "art_uta_vpath.h" +#include "art_svp.h" +#include "art_vpath_svp.h" + +/** + * art_uta_from_svp: Generate uta covering an svp. + * @svp: The source svp. + * + * Generates a uta covering @svp. The resulting uta is of course + * approximate, ie it may cover more pixels than covered by @svp. + * + * Note: I will want to replace this with a more direct + * implementation. But this gets the api in place. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_svp (const ArtSVP *svp) +{ + ArtVpath *vpath; + ArtUta *uta; + + vpath = art_vpath_from_svp (svp); + uta = art_uta_from_vpath (vpath); + art_free (vpath); + return uta; +} diff --git a/lib/art/art_uta_vpath.c b/lib/art/art_uta_vpath.c new file mode 100644 index 0000000..d7df5ed --- /dev/null +++ b/lib/art/art_uta_vpath.c @@ -0,0 +1,382 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" +#include "art_uta_vpath.h" + +#include + +#include "art_misc.h" +#include "art_vpath.h" +#include "art_uta.h" + +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* MAX */ + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif /* MIN */ + +/** + * art_uta_add_line: Add a line to the uta. + * @uta: The uta to modify. + * @x0: X coordinate of line start point. + * @y0: Y coordinate of line start point. + * @x1: X coordinate of line end point. + * @y1: Y coordinate of line end point. + * @rbuf: Buffer containing first difference of winding number. + * @rbuf_rowstride: Rowstride of @rbuf. + * + * Add the line (@x0, @y0) - (@x1, @y1) to @uta, and also update the + * winding number buffer used for rendering the interior. @rbuf + * contains the first partial difference (in the X direction) of the + * winding number, measured in grid cells. Thus, each time that a line + * crosses a horizontal uta grid line, an entry of @rbuf is + * incremented if @y1 > @y0, decremented otherwise. + * + * Note that edge handling is fairly delicate. Please rtfs for + * details. + **/ +void +art_uta_add_line (ArtUta *uta, double x0, double y0, double x1, double y1, + int *rbuf, int rbuf_rowstride) +{ + int xmin, ymin; + double xmax, ymax; + int xmaxf, ymaxf; + int xmaxc, ymaxc; + int xt0, yt0; + int xt1, yt1; + int xf0, yf0; + int xf1, yf1; + int ix, ix1; + ArtUtaBbox bb; + + xmin = floor (MIN(x0, x1)); + xmax = MAX(x0, x1); + xmaxf = floor (xmax); + xmaxc = ceil (xmax); + ymin = floor (MIN(y0, y1)); + ymax = MAX(y0, y1); + ymaxf = floor (ymax); + ymaxc = ceil (ymax); + xt0 = (xmin >> ART_UTILE_SHIFT) - uta->x0; + yt0 = (ymin >> ART_UTILE_SHIFT) - uta->y0; + xt1 = (xmaxf >> ART_UTILE_SHIFT) - uta->x0; + yt1 = (ymaxf >> ART_UTILE_SHIFT) - uta->y0; + if (xt0 == xt1 && yt0 == yt1) + { + /* entirely inside a microtile, this is easy! */ + xf0 = xmin & (ART_UTILE_SIZE - 1); + yf0 = ymin & (ART_UTILE_SIZE - 1); + xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf; + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + + ix = yt0 * uta->width + xt0; + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + else + { + double dx, dy; + int sx, sy; + + dx = x1 - x0; + dy = y1 - y0; + sx = dx > 0 ? 1 : dx < 0 ? -1 : 0; + sy = dy > 0 ? 1 : dy < 0 ? -1 : 0; + if (ymin == ymaxf) + { + /* special case horizontal (dx/dy slope would be infinite) */ + xf0 = xmin & (ART_UTILE_SIZE - 1); + yf0 = ymin & (ART_UTILE_SIZE - 1); + xf1 = (xmaxf & (ART_UTILE_SIZE - 1)) + xmaxc - xmaxf; + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + + ix = yt0 * uta->width + xt0; + ix1 = yt0 * uta->width + xt1; + while (ix != ix1) + { + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, ART_UTILE_SIZE, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + ART_UTILE_SIZE, + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + xf0 = 0; + ix++; + } + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(0, + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + else + { + /* Do a Bresenham-style traversal of the line */ + double dx_dy; + double x, y; + double xn, yn; + + /* normalize coordinates to uta origin */ + x0 -= uta->x0 << ART_UTILE_SHIFT; + y0 -= uta->y0 << ART_UTILE_SHIFT; + x1 -= uta->x0 << ART_UTILE_SHIFT; + y1 -= uta->y0 << ART_UTILE_SHIFT; + if (dy < 0) + { + double tmp; + + tmp = x0; + x0 = x1; + x1 = tmp; + + tmp = y0; + y0 = y1; + y1 = tmp; + + dx = -dx; + sx = -sx; + dy = -dy; + /* we leave sy alone, because it would always be 1, + and we need it for the rbuf stuff. */ + } + xt0 = ((int)floor (x0) >> ART_UTILE_SHIFT); + xt1 = ((int)floor (x1) >> ART_UTILE_SHIFT); + /* now [xy]0 is above [xy]1 */ + + ix = yt0 * uta->width + xt0; + ix1 = yt1 * uta->width + xt1; +#ifdef VERBOSE + printf ("%% ix = %d,%d; ix1 = %d,%d\n", xt0, yt0, xt1, yt1); +#endif + + dx_dy = dx / dy; + x = x0; + y = y0; + while (ix != ix1) + { + int dix; + + /* figure out whether next crossing is horizontal or vertical */ +#ifdef VERBOSE + printf ("%% %d,%d\n", xt0, yt0); +#endif + yn = (yt0 + 1) << ART_UTILE_SHIFT; + + /* xn is the intercept with bottom edge of this tile. The + following expression is careful to result in exactly + x1 when yn = y1. */ + xn = x1 + dx_dy * (yn - y1); + + if (xt0 != (int)floor (xn) >> ART_UTILE_SHIFT) + { + /* horizontal crossing */ + xt0 += sx; + dix = sx; + if (dx > 0) + { + xn = xt0 << ART_UTILE_SHIFT; + yn = y0 + (xn - x0) / dx_dy; + + xf0 = (int)floor (x) & (ART_UTILE_SIZE - 1); + xf1 = ART_UTILE_SIZE; + } + else + { + xn = (xt0 + 1) << ART_UTILE_SHIFT; + yn = y0 + (xn - x0) / dx_dy; + + xf0 = 0; + xmaxc = (int)ceil (x); + xf1 = xmaxc - ((xt0 + 1) << ART_UTILE_SHIFT); + } + ymaxf = (int)floor (yn); + ymaxc = (int)ceil (yn); + yf1 = (ymaxf & (ART_UTILE_SIZE - 1)) + ymaxc - ymaxf; + } + else + { + /* vertical crossing */ + dix = uta->width; + xf0 = (int)floor (MIN(x, xn)) & (ART_UTILE_SIZE - 1); + xmax = MAX(x, xn); + xmaxc = (int)ceil (xmax); + xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT); + yf1 = ART_UTILE_SIZE; + + if (rbuf != NULL) + rbuf[yt0 * rbuf_rowstride + xt0] += sy; + + yt0++; + } + yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1); + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + + x = xn; + y = yn; + ix += dix; + } + xmax = MAX(x, x1); + xmaxc = ceil (xmax); + ymaxc = ceil (y1); + xf0 = (int)floor (MIN(x1, x)) & (ART_UTILE_SIZE - 1); + yf0 = (int)floor (y) & (ART_UTILE_SIZE - 1); + xf1 = xmaxc - (xt0 << ART_UTILE_SHIFT); + yf1 = ymaxc - (yt0 << ART_UTILE_SHIFT); + bb = uta->utiles[ix]; + if (bb == 0) + bb = ART_UTA_BBOX_CONS(xf0, yf0, xf1, yf1); + else + bb = ART_UTA_BBOX_CONS(MIN(ART_UTA_BBOX_X0(bb), xf0), + MIN(ART_UTA_BBOX_Y0(bb), yf0), + MAX(ART_UTA_BBOX_X1(bb), xf1), + MAX(ART_UTA_BBOX_Y1(bb), yf1)); + uta->utiles[ix] = bb; + } + } +} + +/** + * art_uta_from_vpath: Generate uta covering a vpath. + * @vec: The source vpath. + * + * Generates a uta covering @vec. The resulting uta is of course + * approximate, ie it may cover more pixels than covered by @vec. + * + * Return value: the new uta. + **/ +ArtUta * +art_uta_from_vpath (const ArtVpath *vec) +{ + ArtUta *uta; + ArtIRect bbox; + int *rbuf; + int i; + double x, y; + int sum; + int xt, yt; + ArtUtaBbox *utiles; + ArtUtaBbox bb; + int width; + int height; + int ix; + + art_vpath_bbox_irect (vec, &bbox); + + uta = art_uta_new_coords (bbox.x0, bbox.y0, bbox.x1, bbox.y1); + + width = uta->width; + height = uta->height; + utiles = uta->utiles; + + rbuf = art_new (int, width * height); + for (i = 0; i < width * height; i++) + rbuf[i] = 0; + + x = 0; + y = 0; + for (i = 0; vec[i].code != ART_END; i++) + { + switch (vec[i].code) + { + case ART_MOVETO: + x = vec[i].x; + y = vec[i].y; + break; + case ART_LINETO: + art_uta_add_line (uta, vec[i].x, vec[i].y, x, y, rbuf, width); + x = vec[i].x; + y = vec[i].y; + break; + default: + /* this shouldn't happen */ + art_free (rbuf); + art_free (uta); + return NULL; + } + } + + /* now add in the filling from rbuf */ + ix = 0; + for (yt = 0; yt < height; yt++) + { + sum = 0; + for (xt = 0; xt < width; xt++) + { + sum += rbuf[ix]; + /* Nonzero winding rule - others are possible, but hardly + worth it. */ + if (sum != 0) + { + bb = utiles[ix]; + bb &= 0xffff0000; + bb |= (ART_UTILE_SIZE << 8) | ART_UTILE_SIZE; + utiles[ix] = bb; + if (xt != width - 1) + { + bb = utiles[ix + 1]; + bb &= 0xffff00; + bb |= ART_UTILE_SIZE; + utiles[ix + 1] = bb; + } + if (yt != height - 1) + { + bb = utiles[ix + width]; + bb &= 0xff0000ff; + bb |= ART_UTILE_SIZE << 8; + utiles[ix + width] = bb; + if (xt != width - 1) + { + utiles[ix + width + 1] &= 0xffff; + } + } + } + ix++; + } + } + + art_free (rbuf); + + return uta; +} diff --git a/lib/art/art_vpath.c b/lib/art/art_vpath.c new file mode 100644 index 0000000..fa7b903 --- /dev/null +++ b/lib/art/art_vpath.c @@ -0,0 +1,241 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for vector paths */ + +#include "config.h" +#include "art_vpath.h" + +#include +#include + +#include "art_misc.h" + +#include "art_rect.h" + +/** + * art_vpath_add_point: Add point to vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @code: The pathcode for the new point. + * @x: The X coordinate of the new point. + * @y: The Y coordinate of the new point. + * + * Adds a new point to *@p_vpath, reallocating and updating *@p_vpath + * and *@pn_points_max as necessary. *@pn_points is incremented. + * + * This routine always adds the point after all points already in the + * vpath. Thus, it should be called in the order the points are + * desired. + **/ +void +art_vpath_add_point (ArtVpath **p_vpath, int *pn_points, int *pn_points_max, + ArtPathcode code, double x, double y) +{ + int i; + + i = (*pn_points)++; + if (i == *pn_points_max) + art_expand (*p_vpath, ArtVpath, *pn_points_max); + (*p_vpath)[i].code = code; + (*p_vpath)[i].x = x; + (*p_vpath)[i].y = y; +} + +/* number of steps should really depend on radius. */ +#define CIRCLE_STEPS 128 + +/** + * art_vpath_new_circle: Create a new circle. + * @x: X coordinate of center. + * @y: Y coordinate of center. + * @r: radius. + * + * Creates a new polygon closely approximating a circle with center + * (@x, @y) and radius @r. Currently, the number of points used in the + * approximation is fixed, but that will probably change. + * + * Return value: The newly created #ArtVpath. + **/ +ArtVpath * +art_vpath_new_circle (double x, double y, double r) +{ + int i; + ArtVpath *vec; + double theta; + + vec = art_new (ArtVpath, CIRCLE_STEPS + 2); + + for (i = 0; i < CIRCLE_STEPS + 1; i++) + { + vec[i].code = i ? ART_LINETO : ART_MOVETO; + theta = (i & (CIRCLE_STEPS - 1)) * (M_PI * 2.0 / CIRCLE_STEPS); + vec[i].x = x + r * cos (theta); + vec[i].y = y - r * sin (theta); + } + vec[i].code = ART_END; + + return vec; +} + +/** + * art_vpath_affine_transform: Affine transform a vpath. + * @src: Source vpath to transform. + * @matrix: Affine transform. + * + * Computes the affine transform of the vpath, using @matrix as the + * transform. @matrix is stored in the same format as PostScript, ie. + * x' = @matrix[0] * x + @matrix[2] * y + @matrix[4] + * y' = @matrix[1] * x + @matrix[3] * y + @matrix[5] + * + * Return value: the newly allocated vpath resulting from the transform. +**/ +ArtVpath * +art_vpath_affine_transform (const ArtVpath *src, const double matrix[6]) +{ + int i; + int size; + ArtVpath *new; + double x, y; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtVpath, size + 1); + + for (i = 0; i < size; i++) + { + new[i].code = src[i].code; + x = src[i].x; + y = src[i].y; + new[i].x = matrix[0] * x + matrix[2] * y + matrix[4]; + new[i].y = matrix[1] * x + matrix[3] * y + matrix[5]; + } + new[i].code = ART_END; + + return new; +} + +/** + * art_vpath_bbox_drect: Determine bounding box of vpath. + * @vec: Source vpath. + * @drect: Where to store bounding box. + * + * Determines bounding box of @vec, and stores it in @drect. + **/ +void +art_vpath_bbox_drect (const ArtVpath *vec, ArtDRect *drect) +{ + int i; + double x0, y0, x1, y1; + + if (vec[0].code == ART_END) + { + x0 = y0 = x1 = y1 = 0; + } + else + { + x0 = x1 = vec[0].x; + y0 = y1 = vec[0].y; + for (i = 1; vec[i].code != ART_END; i++) + { + if (vec[i].x < x0) x0 = vec[i].x; + if (vec[i].x > x1) x1 = vec[i].x; + if (vec[i].y < y0) y0 = vec[i].y; + if (vec[i].y > y1) y1 = vec[i].y; + } + } + drect->x0 = x0; + drect->y0 = y0; + drect->x1 = x1; + drect->y1 = y1; +} + +/** + * art_vpath_bbox_irect: Determine integer bounding box of vpath. + * @vec: Source vpath. + * idrect: Where to store bounding box. + * + * Determines integer bounding box of @vec, and stores it in @irect. + **/ +void +art_vpath_bbox_irect (const ArtVpath *vec, ArtIRect *irect) +{ + ArtDRect drect; + + art_vpath_bbox_drect (vec, &drect); + art_drect_to_irect (irect, &drect); +} + +#define PERTURBATION 2e-3 + +/** + * art_vpath_perturb: Perturb each point in vpath by small random amount. + * @src: Source vpath. + * + * Perturbs each of the points by a small random amount. This is + * helpful for cheating in cases when algorithms haven't attained + * numerical stability yet. + * + * Return value: Newly allocated vpath containing perturbed @src. + **/ +ArtVpath * +art_vpath_perturb (ArtVpath *src) +{ + int i; + int size; + ArtVpath *new; + double x, y; + double x_start, y_start; + int open; + + for (i = 0; src[i].code != ART_END; i++); + size = i; + + new = art_new (ArtVpath, size + 1); + + x_start = 0; + y_start = 0; + open = 0; + for (i = 0; i < size; i++) + { + new[i].code = src[i].code; + x = src[i].x + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5; + y = src[i].y + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5; + if (src[i].code == ART_MOVETO) + { + x_start = x; + y_start = y; + open = 0; + } + else if (src[i].code == ART_MOVETO_OPEN) + open = 1; + if (!open && (i + 1 == size || src[i + 1].code != ART_LINETO)) + { + x = x_start; + y = y_start; + } + new[i].x = x; + new[i].y = y; + } + new[i].code = ART_END; + + return new; +} diff --git a/lib/art/art_vpath_bpath.c b/lib/art/art_vpath_bpath.c new file mode 100644 index 0000000..55a46e6 --- /dev/null +++ b/lib/art/art_vpath_bpath.c @@ -0,0 +1,317 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Basic constructors and operations for bezier paths */ + +#include "config.h" +#include "art_vpath_bpath.h" + +#include + +#include "art_misc.h" + +#include "art_bpath.h" +#include "art_vpath.h" + +/* p must be allocated 2^level points. */ + +/* level must be >= 1 */ +ArtPoint * +art_bezier_to_vec (double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + ArtPoint *p, + int level) +{ + double x_m, y_m; + +#ifdef VERBOSE + printf ("bezier_to_vec: %g,%g %g,%g %g,%g %g,%g %d\n", + x0, y0, x1, y1, x2, y2, x3, y3, level); +#endif + if (level == 1) { + x_m = (x0 + 3 * (x1 + x2) + x3) * 0.125; + y_m = (y0 + 3 * (y1 + y2) + y3) * 0.125; + p->x = x_m; + p->y = y_m; + p++; + p->x = x3; + p->y = y3; + p++; +#ifdef VERBOSE + printf ("-> (%g, %g) -> (%g, %g)\n", x_m, y_m, x3, y3); +#endif + } else { + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2, + xb1, yb1, xb2, yb2); +#endif + p = art_bezier_to_vec (x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, p, level - 1); + p = art_bezier_to_vec (x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, p, level - 1); + } + return p; +} + +#define RENDER_LEVEL 4 +#define RENDER_SIZE (1 << (RENDER_LEVEL)) + +/** + * art_vpath_render_bez: Render a bezier segment into the vpath. + * @p_vpath: Where the pointer to the #ArtVpath structure is stored. + * @pn_points: Pointer to the number of points in *@p_vpath. + * @pn_points_max: Pointer to the number of points allocated. + * @x0: X coordinate of starting bezier point. + * @y0: Y coordinate of starting bezier point. + * @x1: X coordinate of first bezier control point. + * @y1: Y coordinate of first bezier control point. + * @x2: X coordinate of second bezier control point. + * @y2: Y coordinate of second bezier control point. + * @x3: X coordinate of ending bezier point. + * @y3: Y coordinate of ending bezier point. + * @flatness: Flatness control. + * + * Renders a bezier segment into the vector path, reallocating and + * updating *@p_vpath and *@pn_vpath_max as necessary. *@pn_vpath is + * incremented by the number of vector points added. + * + * This step includes (@x0, @y0) but not (@x3, @y3). + * + * The @flatness argument guides the amount of subdivision. The Adobe + * PostScript reference manual defines flatness as the maximum + * deviation between the any point on the vpath approximation and the + * corresponding point on the "true" curve, and we follow this + * definition here. A value of 0.25 should ensure high quality for aa + * rendering. +**/ +static void +art_vpath_render_bez (ArtVpath **p_vpath, int *pn, int *pn_max, + double x0, double y0, + double x1, double y1, + double x2, double y2, + double x3, double y3, + double flatness) +{ + double x3_0, y3_0; + double z3_0_dot; + double z1_dot, z2_dot; + double z1_perp, z2_perp; + double max_perp_sq; + + double x_m, y_m; + double xa1, ya1; + double xa2, ya2; + double xb1, yb1; + double xb2, yb2; + + /* It's possible to optimize this routine a fair amount. + + First, once the _dot conditions are met, they will also be met in + all further subdivisions. So we might recurse to a different + routine that only checks the _perp conditions. + + Second, the distance _should_ decrease according to fairly + predictable rules (a factor of 4 with each subdivision). So it might + be possible to note that the distance is within a factor of 4 of + acceptable, and subdivide once. But proving this might be hard. + + Third, at the last subdivision, x_m and y_m can be computed more + expeditiously (as in the routine above). + + Finally, if we were able to subdivide by, say 2 or 3, this would + allow considerably finer-grain control, i.e. fewer points for the + same flatness tolerance. This would speed things up downstream. + + In any case, this routine is unlikely to be the bottleneck. It's + just that I have this undying quest for more speed... + + */ + + x3_0 = x3 - x0; + y3_0 = y3 - y0; + + /* z3_0_dot is dist z0-z3 squared */ + z3_0_dot = x3_0 * x3_0 + y3_0 * y3_0; + + /* todo: this test is far from satisfactory. */ + if (z3_0_dot < 0.001) + goto nosubdivide; + + /* we can avoid subdivision if: + + z1 has distance no more than flatness from the z0-z3 line + + z1 is no more z0'ward than flatness past z0-z3 + + z1 is more z0'ward than z3'ward on the line traversing z0-z3 + + and correspondingly for z2 */ + + /* perp is distance from line, multiplied by dist z0-z3 */ + max_perp_sq = flatness * flatness * z3_0_dot; + + z1_perp = (y1 - y0) * x3_0 - (x1 - x0) * y3_0; + if (z1_perp * z1_perp > max_perp_sq) + goto subdivide; + + z2_perp = (y3 - y2) * x3_0 - (x3 - x2) * y3_0; + if (z2_perp * z2_perp > max_perp_sq) + goto subdivide; + + z1_dot = (x1 - x0) * x3_0 + (y1 - y0) * y3_0; + if (z1_dot < 0 && z1_dot * z1_dot > max_perp_sq) + goto subdivide; + + z2_dot = (x3 - x2) * x3_0 + (y3 - y2) * y3_0; + if (z2_dot < 0 && z2_dot * z2_dot > max_perp_sq) + goto subdivide; + + if (z1_dot + z1_dot > z3_0_dot) + goto subdivide; + + if (z2_dot + z2_dot > z3_0_dot) + goto subdivide; + + nosubdivide: + /* don't subdivide */ + art_vpath_add_point (p_vpath, pn, pn_max, + ART_LINETO, x3, y3); + return; + + subdivide: + + xa1 = (x0 + x1) * 0.5; + ya1 = (y0 + y1) * 0.5; + xa2 = (x0 + 2 * x1 + x2) * 0.25; + ya2 = (y0 + 2 * y1 + y2) * 0.25; + xb1 = (x1 + 2 * x2 + x3) * 0.25; + yb1 = (y1 + 2 * y2 + y3) * 0.25; + xb2 = (x2 + x3) * 0.5; + yb2 = (y2 + y3) * 0.5; + x_m = (xa2 + xb1) * 0.5; + y_m = (ya2 + yb1) * 0.5; +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", xa1, ya1, xa2, ya2, + xb1, yb1, xb2, yb2); +#endif + art_vpath_render_bez (p_vpath, pn, pn_max, + x0, y0, xa1, ya1, xa2, ya2, x_m, y_m, flatness); + art_vpath_render_bez (p_vpath, pn, pn_max, + x_m, y_m, xb1, yb1, xb2, yb2, x3, y3, flatness); +} + +/** + * art_bez_path_to_vec: Create vpath from bezier path. + * @bez: Bezier path. + * @flatness: Flatness control. + * + * Creates a vector path closely approximating the bezier path defined by + * @bez. The @flatness argument controls the amount of subdivision. In + * general, the resulting vpath deviates by at most @flatness pixels + * from the "ideal" path described by @bez. + * + * Return value: Newly allocated vpath. + **/ +ArtVpath * +art_bez_path_to_vec (const ArtBpath *bez, double flatness) +{ + ArtVpath *vec; + int vec_n, vec_n_max; + int bez_index; + double x, y; + + vec_n = 0; + vec_n_max = RENDER_SIZE; + vec = art_new (ArtVpath, vec_n_max); + + /* Initialization is unnecessary because of the precondition that the + bezier path does not begin with LINETO or CURVETO, but is here + to make the code warning-free. */ + x = 0; + y = 0; + + bez_index = 0; + do + { +#ifdef VERBOSE + printf ("%s %g %g\n", + bez[bez_index].code == ART_CURVETO ? "curveto" : + bez[bez_index].code == ART_LINETO ? "lineto" : + bez[bez_index].code == ART_MOVETO ? "moveto" : + bez[bez_index].code == ART_MOVETO_OPEN ? "moveto-open" : + "end", bez[bez_index].x3, bez[bez_index].y3); +#endif + /* make sure space for at least one more code */ + if (vec_n >= vec_n_max) + art_expand (vec, ArtVpath, vec_n_max); + switch (bez[bez_index].code) + { + case ART_MOVETO_OPEN: + case ART_MOVETO: + case ART_LINETO: + x = bez[bez_index].x3; + y = bez[bez_index].y3; + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = x; + vec[vec_n].y = y; + vec_n++; + break; + case ART_END: + vec[vec_n].code = bez[bez_index].code; + vec[vec_n].x = 0; + vec[vec_n].y = 0; + vec_n++; + break; + case ART_CURVETO: +#ifdef VERBOSE + printf ("%g,%g %g,%g %g,%g %g,%g\n", x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3); +#endif + art_vpath_render_bez (&vec, &vec_n, &vec_n_max, + x, y, + bez[bez_index].x1, bez[bez_index].y1, + bez[bez_index].x2, bez[bez_index].y2, + bez[bez_index].x3, bez[bez_index].y3, + flatness); + x = bez[bez_index].x3; + y = bez[bez_index].y3; + break; + } + } + while (bez[bez_index++].code != ART_END); + return vec; +} + diff --git a/lib/art/art_vpath_dash.c b/lib/art/art_vpath_dash.c new file mode 100644 index 0000000..3c98a96 --- /dev/null +++ b/lib/art/art_vpath_dash.c @@ -0,0 +1,200 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1999-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* Apply a dash style to a vector path. */ + +#include "config.h" +#include "art_vpath_dash.h" + +#include +#include + +#include "art_misc.h" + +#include "art_vpath.h" + + +/* Return the length of the largest subpath within vpath */ +static int +art_vpath_dash_max_subpath (const ArtVpath *vpath) +{ + int max_subpath; + int i; + int start; + + max_subpath = 0; + start = 0; + for (i = 0; vpath[i].code != ART_END; i++) + { + if (vpath[i].code == ART_MOVETO || vpath[i].code == ART_MOVETO_OPEN) + { + if (i - start > max_subpath) + max_subpath = i - start; + start = i; + } + } + if (i - start > max_subpath) + max_subpath = i - start; + + return max_subpath; +} + +/** + * art_vpath_dash: Add dash style to vpath. + * @vpath: Original vpath. + * @dash: Dash style. + * + * Creates a new vpath that is the result of applying dash style @dash + * to @vpath. + * + * This implementation has two known flaws: + * + * First, it adds a spurious break at the beginning of the vpath. The + * only way I see to resolve this flaw is to run the state forward one + * dash break at the beginning, and fix up by looping back to the + * first dash break at the end. This is doable but of course adds some + * complexity. + * + * Second, it does not suppress output points that are within epsilon + * of each other. + * + * Return value: Newly created vpath. + **/ +ArtVpath * +art_vpath_dash (const ArtVpath *vpath, const ArtVpathDash *dash) +{ + int max_subpath; + double *dists; + ArtVpath *result; + int n_result, n_result_max; + int start, end; + int i; + double total_dist; + + /* state while traversing dasharray - offset is offset of current dash + value, toggle is 0 for "off" and 1 for "on", and phase is the distance + in, >= 0, < dash->dash[offset]. */ + int offset, toggle; + double phase; + + /* initial values */ + int offset_init, toggle_init; + double phase_init; + + max_subpath = art_vpath_dash_max_subpath (vpath); + dists = art_new (double, max_subpath); + + n_result = 0; + n_result_max = 16; + result = art_new (ArtVpath, n_result_max); + + /* determine initial values of dash state */ + toggle_init = 1; + offset_init = 0; + phase_init = dash->offset; + while (phase_init >= dash->dash[offset_init]) + { + toggle_init = !toggle_init; + phase_init -= dash->dash[offset_init]; + offset_init++; + if (offset_init == dash->n_dash) + offset_init = 0; + } + + for (start = 0; vpath[start].code != ART_END; start = end) + { + for (end = start + 1; vpath[end].code == ART_LINETO; end++); + /* subpath is [start..end) */ + total_dist = 0; + for (i = start; i < end - 1; i++) + { + double dx, dy; + + dx = vpath[i + 1].x - vpath[i].x; + dy = vpath[i + 1].y - vpath[i].y; + dists[i - start] = sqrt (dx * dx + dy * dy); + total_dist += dists[i - start]; + } + if (total_dist <= dash->dash[offset_init] - phase_init) + { + /* subpath fits entirely within first dash */ + if (toggle_init) + { + for (i = start; i < end; i++) + art_vpath_add_point (&result, &n_result, &n_result_max, + vpath[i].code, vpath[i].x, vpath[i].y); + } + } + else + { + /* subpath is composed of at least one dash - thus all + generated pieces are open */ + double dist; + + phase = phase_init; + offset = offset_init; + toggle = toggle_init; + dist = 0; + i = start; + if (toggle) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_MOVETO_OPEN, vpath[i].x, vpath[i].y); + while (i != end - 1) + { + if (dists[i - start] - dist > dash->dash[offset] - phase) + { + /* dash boundary is next */ + double a; + double x, y; + + dist += dash->dash[offset] - phase; + a = dist / dists[i - start]; + x = vpath[i].x + a * (vpath[i + 1].x - vpath[i].x); + y = vpath[i].y + a * (vpath[i + 1].y - vpath[i].y); + art_vpath_add_point (&result, &n_result, &n_result_max, + toggle ? ART_LINETO : ART_MOVETO_OPEN, + x, y); + /* advance to next dash */ + toggle = !toggle; + phase = 0; + offset++; + if (offset == dash->n_dash) + offset = 0; + } + else + { + /* end of line in vpath is next */ + phase += dists[i - start] - dist; + i++; + dist = 0; + if (toggle) + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_LINETO, vpath[i].x, vpath[i].y); + } + } + } + } + + art_vpath_add_point (&result, &n_result, &n_result_max, + ART_END, 0, 0); + + art_free (dists); + + return result; +} diff --git a/lib/art/art_vpath_svp.c b/lib/art/art_vpath_svp.c new file mode 100644 index 0000000..000265c --- /dev/null +++ b/lib/art/art_vpath_svp.c @@ -0,0 +1,196 @@ +/* Libart_LGPL - library of basic graphic primitives + * Copyright (C) 1998-2000 Raph Levien + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* "Unsort" a sorted vector path into an ordinary vector path. */ + +#include "config.h" +#include "art_vpath_svp.h" + +#include /* for printf - debugging */ +#include "art_misc.h" + +#include "art_vpath.h" +#include "art_svp.h" + +typedef struct _ArtVpathSVPEnd ArtVpathSVPEnd; + +struct _ArtVpathSVPEnd { + int seg_num; + int which; /* 0 = top, 1 = bottom */ + double x, y; +}; + +#define EPSILON 1e-6 + +static int +art_vpath_svp_point_compare (double x1, double y1, double x2, double y2) +{ + if (y1 - EPSILON > y2) return 1; + if (y1 + EPSILON < y2) return -1; + if (x1 - EPSILON > x2) return 1; + if (x1 + EPSILON < x2) return -1; + return 0; +} + +static int +art_vpath_svp_compare (const void *s1, const void *s2) +{ + const ArtVpathSVPEnd *e1 = s1; + const ArtVpathSVPEnd *e2 = s2; + + return art_vpath_svp_point_compare (e1->x, e1->y, e2->x, e2->y); +} + +/* Convert from sorted vector path representation into regular + vector path representation. + + Status of this routine: + + Basic correctness: Only works with closed paths. + + Numerical stability: Not known to work when more than two segments + meet at a point. + + Speed: Should be pretty good. + + Precision: Does not degrade precision. + +*/ +/** + * art_vpath_from_svp: Convert from svp to vpath form. + * @svp: Original #ArtSVP. + * + * Converts the sorted vector path @svp into standard vpath form. + * + * Return value: the newly allocated vpath. + **/ +ArtVpath * +art_vpath_from_svp (const ArtSVP *svp) +{ + int n_segs = svp->n_segs; + ArtVpathSVPEnd *ends; + ArtVpath *new; + int *visited; + int n_new, n_new_max; + int i, k; + int j = 0; /* Quiet compiler */ + int seg_num; + int first; + double last_x, last_y; + int n_points; + int pt_num; + + last_x = 0; /* to eliminate "uninitialized" warning */ + last_y = 0; + + ends = art_new (ArtVpathSVPEnd, n_segs * 2); + for (i = 0; i < svp->n_segs; i++) + { + int lastpt; + + ends[i * 2].seg_num = i; + ends[i * 2].which = 0; + ends[i * 2].x = svp->segs[i].points[0].x; + ends[i * 2].y = svp->segs[i].points[0].y; + + lastpt = svp->segs[i].n_points - 1; + ends[i * 2 + 1].seg_num = i; + ends[i * 2 + 1].which = 1; + ends[i * 2 + 1].x = svp->segs[i].points[lastpt].x; + ends[i * 2 + 1].y = svp->segs[i].points[lastpt].y; + } + qsort (ends, n_segs * 2, sizeof (ArtVpathSVPEnd), art_vpath_svp_compare); + + n_new = 0; + n_new_max = 16; /* I suppose we _could_ estimate this from traversing + the svp, so we don't have to reallocate */ + new = art_new (ArtVpath, n_new_max); + + visited = art_new (int, n_segs); + for (i = 0; i < n_segs; i++) + visited[i] = 0; + + first = 1; + for (i = 0; i < n_segs; i++) + { + if (!first) + { + /* search for the continuation of the existing subpath */ + /* This could be a binary search (which is why we sorted, above) */ + for (j = 0; j < n_segs * 2; j++) + { + if (!visited[ends[j].seg_num] && + art_vpath_svp_point_compare (last_x, last_y, + ends[j].x, ends[j].y) == 0) + break; + } + if (j == n_segs * 2) + first = 1; + } + if (first) + { + /* start a new subpath */ + for (j = 0; j < n_segs * 2; j++) + if (!visited[ends[j].seg_num]) + break; + } + if (j == n_segs * 2) + { + printf ("failure\n"); + } + seg_num = ends[j].seg_num; + n_points = svp->segs[seg_num].n_points; + for (k = 0; k < n_points; k++) + { + pt_num = svp->segs[seg_num].dir ? k : n_points - (1 + k); + if (k == 0) + { + if (first) + { + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_MOVETO, + svp->segs[seg_num].points[pt_num].x, + svp->segs[seg_num].points[pt_num].y); + } + } + else + { + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_LINETO, + svp->segs[seg_num].points[pt_num].x, + svp->segs[seg_num].points[pt_num].y); + if (k == n_points - 1) + { + last_x = svp->segs[seg_num].points[pt_num].x; + last_y = svp->segs[seg_num].points[pt_num].y; + /* to make more robust, check for meeting first_[xy], + set first if so */ + } + } + first = 0; + } + visited[seg_num] = 1; + } + + art_vpath_add_point (&new, &n_new, &n_new_max, + ART_END, 0, 0); + art_free (visited); + art_free (ends); + return new; +} diff --git a/lib/art/gen_art_config.c b/lib/art/gen_art_config.c new file mode 100644 index 0000000..8d88815 --- /dev/null +++ b/lib/art/gen_art_config.c @@ -0,0 +1,52 @@ +#include +#include "config.h" +#include + +/** + * A little utility function to generate header info. + * + * Yes, it would be possible to do this using more "native" autoconf + * features, but I personally find this approach to be cleaner. + * + * The output of this program is generally written to art_config.h, + * which is installed in libart's include dir. + **/ + +static void +die (char *why) +{ + fprintf (stderr, "gen_art_config: %s\n", why); + exit (1); +} + +int +main (int argc, char **argv) +{ + printf ("/* Automatically generated by gen_art_config.c */\n" + "\n" + "#define ART_SIZEOF_CHAR %d\n" + "#define ART_SIZEOF_SHORT %d\n" + "#define ART_SIZEOF_INT %d\n" + "#define ART_SIZEOF_LONG %d\n" + "\n", + (int)sizeof(char), (int)sizeof(short), (int)sizeof(int), (int)sizeof(long)); + + if (sizeof(char) == 1) + printf ("typedef unsigned char art_u8;\n"); + else + die ("sizeof(char) != 1"); + + if (sizeof(short) == 2) + printf ("typedef unsigned short art_u16;\n"); + else + die ("sizeof(short) != 2"); + + if (sizeof(int) == 4) + printf ("typedef unsigned int art_u32;\n"); + else if (sizeof(long) == 4) + printf ("typedef unsigned long art_u32;\n"); + else + die ("sizeof(int) != 4 and sizeof(long) != 4"); + + return 0; +} -- 1.7.10.4