summaryrefslogtreecommitdiff
path: root/src/cairo-traps.c
diff options
context:
space:
mode:
authorCarl Worth <cworth@cworth.org>2003-07-18 11:34:19 +0000
committerCarl Worth <cworth@cworth.org>2003-07-18 11:34:19 +0000
commitdc1e96ae3502a81729839f4bcafcbc1fd00fc1bc (patch)
tree859d5aa4a6315c8355b3c5e87826e8722c50c6c5 /src/cairo-traps.c
parent4a57fd0881b242d98ea74abb46c8c402faeb1960 (diff)
Renamed everything from Xr* to cairo_*
Diffstat (limited to 'src/cairo-traps.c')
-rw-r--r--src/cairo-traps.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/src/cairo-traps.c b/src/cairo-traps.c
new file mode 100644
index 00000000..c0afa3d3
--- /dev/null
+++ b/src/cairo-traps.c
@@ -0,0 +1,593 @@
+/*
+ * Copyright © 2002 Keith Packard, member of The XFree86 Project, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of Keith Packard not be used in
+ * advertising or publicity pertaining to distribution of the software without
+ * specific, written prior permission. Keith Packard makes no
+ * representations about the suitability of this software for any purpose. It
+ * is provided "as is" without express or implied warranty.
+ *
+ * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * 2002-07-15: Converted from XRenderCompositeDoublePoly to cairo_trap. Carl D. Worth
+ */
+
+#include "cairoint.h"
+
+#define CAIRO_TRAPS_GROWTH_INC 10
+
+/* private functions */
+
+static cairo_status_t
+cairo_traps_grow_by (cairo_traps_t *traps, int additional);
+
+cairo_status_t
+cairo_traps_add_trap (cairo_traps_t *traps, XFixed top, XFixed bottom,
+ XLineFixed left, XLineFixed right);
+
+cairo_status_t
+cairo_traps_add_trap_from_points (cairo_traps_t *traps, XFixed top, XFixed bottom,
+ XPointFixed left_p1, XPointFixed left_p2,
+ XPointFixed right_p1, XPointFixed right_p2);
+
+static int
+_compare_point_fixed_by_y (const void *av, const void *bv);
+
+static int
+_compare_cairo_edge_by_top (const void *av, const void *bv);
+
+static XFixed
+_compute_x (XLineFixed *line, XFixed y);
+
+static double
+_compute_inverse_slope (XLineFixed *l);
+
+static double
+_compute_x_intercept (XLineFixed *l, double inverse_slope);
+
+static XFixed
+_lines_intersect (XLineFixed *l1, XLineFixed *l2, XFixed *intersection);
+
+void
+cairo_traps_init (cairo_traps_t *traps)
+{
+ traps->num_xtraps = 0;
+
+ traps->xtraps_size = 0;
+ traps->xtraps = NULL;
+}
+
+void
+cairo_traps_fini (cairo_traps_t *traps)
+{
+ if (traps->xtraps_size) {
+ free (traps->xtraps);
+ traps->xtraps = NULL;
+ traps->xtraps_size = 0;
+ traps->num_xtraps = 0;
+ }
+}
+
+cairo_status_t
+cairo_traps_add_trap (cairo_traps_t *traps, XFixed top, XFixed bottom,
+ XLineFixed left, XLineFixed right)
+{
+ cairo_status_t status;
+ XTrapezoid *trap;
+
+ if (top == bottom) {
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ if (traps->num_xtraps >= traps->xtraps_size) {
+ status = cairo_traps_grow_by (traps, CAIRO_TRAPS_GROWTH_INC);
+ if (status)
+ return status;
+ }
+
+ trap = &traps->xtraps[traps->num_xtraps];
+ trap->top = top;
+ trap->bottom = bottom;
+ trap->left = left;
+ trap->right = right;
+
+ traps->num_xtraps++;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_status_t
+cairo_traps_add_trap_from_points (cairo_traps_t *traps, XFixed top, XFixed bottom,
+ XPointFixed left_p1, XPointFixed left_p2,
+ XPointFixed right_p1, XPointFixed right_p2)
+{
+ XLineFixed left;
+ XLineFixed right;
+
+ left.p1 = left_p1;
+ left.p2 = left_p2;
+
+ right.p1 = right_p1;
+ right.p2 = right_p2;
+
+ return cairo_traps_add_trap (traps, top, bottom, left, right);
+}
+
+static cairo_status_t
+cairo_traps_grow_by (cairo_traps_t *traps, int additional)
+{
+ XTrapezoid *new_xtraps;
+ int old_size = traps->xtraps_size;
+ int new_size = traps->num_xtraps + additional;
+
+ if (new_size <= traps->xtraps_size) {
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ traps->xtraps_size = new_size;
+ new_xtraps = realloc (traps->xtraps, traps->xtraps_size * sizeof (XTrapezoid));
+
+ if (new_xtraps == NULL) {
+ traps->xtraps_size = old_size;
+ return CAIRO_STATUS_NO_MEMORY;
+ }
+
+ traps->xtraps = new_xtraps;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static int
+_compare_point_fixed_by_y (const void *av, const void *bv)
+{
+ const XPointFixed *a = av, *b = bv;
+
+ int ret = a->y - b->y;
+ if (ret == 0) {
+ ret = a->x - b->x;
+ }
+ return ret;
+}
+
+cairo_status_t
+cairo_traps_tessellate_triangle (cairo_traps_t *traps, XPointFixed t[3])
+{
+ cairo_status_t status;
+ XLineFixed line;
+ double intersect;
+ XPointFixed tsort[3];
+
+ memcpy (tsort, t, 3 * sizeof (XPointFixed));
+ qsort (tsort, 3, sizeof (XPointFixed), _compare_point_fixed_by_y);
+
+ /* horizontal top edge requires special handling */
+ if (tsort[0].y == tsort[1].y) {
+ if (tsort[0].x < tsort[1].x)
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[1].y, tsort[2].y,
+ tsort[0], tsort[2],
+ tsort[1], tsort[2]);
+ else
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[1].y, tsort[2].y,
+ tsort[1], tsort[2],
+ tsort[0], tsort[2]);
+ return status;
+ }
+
+ line.p1 = tsort[0];
+ line.p2 = tsort[1];
+
+ intersect = _compute_x (&line, tsort[2].y);
+
+ if (intersect < tsort[2].x) {
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[0].y, tsort[1].y,
+ tsort[0], tsort[1],
+ tsort[0], tsort[2]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[1].y, tsort[2].y,
+ tsort[1], tsort[2],
+ tsort[0], tsort[2]);
+ if (status)
+ return status;
+ } else {
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[0].y, tsort[1].y,
+ tsort[0], tsort[2],
+ tsort[0], tsort[1]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ tsort[1].y, tsort[2].y,
+ tsort[0], tsort[2],
+ tsort[1], tsort[2]);
+ if (status)
+ return status;
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/* Warning: This function reorders the elements of the array provided. */
+cairo_status_t
+cairo_traps_tessellate_rectangle (cairo_traps_t *traps, XPointFixed q[4])
+{
+ cairo_status_t status;
+
+ qsort (q, 4, sizeof (XPointFixed), _compare_point_fixed_by_y);
+
+ if (q[1].x > q[2].x) {
+ status = cairo_traps_add_trap_from_points (traps,
+ q[0].y, q[1].y, q[0], q[2], q[0], q[1]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ q[1].y, q[2].y, q[0], q[2], q[1], q[3]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ q[2].y, q[3].y, q[2], q[3], q[1], q[3]);
+ if (status)
+ return status;
+ } else {
+ status = cairo_traps_add_trap_from_points (traps,
+ q[0].y, q[1].y, q[0], q[1], q[0], q[2]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ q[1].y, q[2].y, q[1], q[3], q[0], q[2]);
+ if (status)
+ return status;
+ status = cairo_traps_add_trap_from_points (traps,
+ q[2].y, q[3].y, q[1], q[3], q[2], q[3]);
+ if (status)
+ return status;
+ }
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static int
+_compare_cairo_edge_by_top (const void *av, const void *bv)
+{
+ const cairo_edge_t *a = av, *b = bv;
+ int ret;
+
+ ret = a->edge.p1.y - b->edge.p1.y;
+ if (ret == 0)
+ ret = a->edge.p1.x - b->edge.p1.x;
+ return ret;
+}
+
+/* Return value is:
+ > 0 if a is "clockwise" from b, (in a mathematical, not a graphical sense)
+ == 0 if slope (a) == slope (b)
+ < 0 if a is "counter-clockwise" from b
+*/
+static int
+_compare_cairo_edge_by_slope (const void *av, const void *bv)
+{
+ const cairo_edge_t *a = av, *b = bv;
+
+ double a_dx = XFixedToDouble (a->edge.p2.x - a->edge.p1.x);
+ double a_dy = XFixedToDouble (a->edge.p2.y - a->edge.p1.y);
+ double b_dx = XFixedToDouble (b->edge.p2.x - b->edge.p1.x);
+ double b_dy = XFixedToDouble (b->edge.p2.y - b->edge.p1.y);
+
+ return b_dy * a_dx - a_dy * b_dx;
+}
+
+static int
+_compare_cairo_edge_by_current_xthen_slope (const void *av, const void *bv)
+{
+ const cairo_edge_t *a = av, *b = bv;
+ int ret;
+
+ ret = a->current_x - b->current_x;
+ if (ret == 0)
+ ret = _compare_cairo_edge_by_slope (a, b);
+ return ret;
+}
+
+/* XXX: Both _compute_x and _compute_inverse_slope will divide by zero
+ for horizontal lines. Now, we "know" that when we are tessellating
+ polygons that the polygon data structure discards all horizontal
+ edges, but there's nothing here to guarantee that. I suggest the
+ following:
+
+ A) Move all of the polygon tessellation code out of xrtraps.c and
+ into xrpoly.c, (in order to be in the same module as the code
+ discarding horizontal lines).
+
+ OR
+
+ B) Re-implement the line intersection in a way that avoids all
+ division by zero. Here's one approach. The only disadvantage
+ might be that that there are not meaningful names for all of the
+ sub-computations -- just a bunch of determinants. I haven't
+ looked at complexity, (both are probably similar and it probably
+ doesn't matter much anyway).
+
+static double
+_det (double a, double b, double c, double d)
+{
+ return a * d - b * c;
+}
+
+static int
+_lines_intersect (XLineFixed *l1, XLineFixed *l2, XFixed *y_intersection)
+{
+ double dx1 = XFixedToDouble (l1->p1.x - l1->p2.x);
+ double dy1 = XFixedToDouble (l1->p1.y - l1->p2.y);
+
+ double dx2 = XFixedToDouble (l2->p1.x - l2->p2.x);
+ double dy2 = XFixedToDouble (l2->p1.y - l2->p2.y);
+
+ double l1_det, l2_det;
+
+ double den_det = _det (dx1, dy1, dx2, dy2);
+
+ if (den_det == 0)
+ return 0;
+
+ l1_det = _det (l1->p1.x, l1->p1.y,
+ l1->p2.x, l1->p2.y);
+ l2_det = _det (l2->p1.x, l2->p1.y,
+ l2->p2.x, l2->p2.y);
+
+ *y_intersection = _det (l1_det, dy1,
+ l2_det, dy2) / den_det;
+
+ return 1;
+}
+*/
+static XFixed
+_compute_x (XLineFixed *line, XFixed y)
+{
+ XFixed dx = line->p2.x - line->p1.x;
+ double ex = (double) (y - line->p1.y) * (double) dx;
+ XFixed dy = line->p2.y - line->p1.y;
+
+ return line->p1.x + (ex / dy);
+}
+
+static double
+_compute_inverse_slope (XLineFixed *l)
+{
+ return (XFixedToDouble (l->p2.x - l->p1.x) /
+ XFixedToDouble (l->p2.y - l->p1.y));
+}
+
+static double
+_compute_x_intercept (XLineFixed *l, double inverse_slope)
+{
+ return XFixedToDouble (l->p1.x) - inverse_slope * XFixedToDouble (l->p1.y);
+}
+
+static int
+_lines_intersect (XLineFixed *l1, XLineFixed *l2, XFixed *y_intersection)
+{
+ /*
+ * x = m1y + b1
+ * x = m2y + b2
+ * m1y + b1 = m2y + b2
+ * y * (m1 - m2) = b2 - b1
+ * y = (b2 - b1) / (m1 - m2)
+ */
+ double m1 = _compute_inverse_slope (l1);
+ double b1 = _compute_x_intercept (l1, m1);
+ double m2 = _compute_inverse_slope (l2);
+ double b2 = _compute_x_intercept (l2, m2);
+
+ if (m1 == m2)
+ return 0;
+
+ *y_intersection = XDoubleToFixed ((b2 - b1) / (m1 - m2));
+ return 1;
+}
+
+static void
+_SortEdgeList (cairo_edge_t **active)
+{
+ cairo_edge_t *e, *en, *next;
+
+ /* sort active list */
+ for (e = *active; e; e = next)
+ {
+ next = e->next;
+ /*
+ * Find one later in the list that belongs before the
+ * current one
+ */
+ for (en = next; en; en = en->next)
+ {
+ if (_compare_cairo_edge_by_current_xthen_slope (e, en) > 0)
+ {
+ /*
+ * insert en before e
+ *
+ * extract en
+ */
+ en->prev->next = en->next;
+ if (en->next)
+ en->next->prev = en->prev;
+ /*
+ * insert en
+ */
+ if (e->prev)
+ e->prev->next = en;
+ else
+ *active = en;
+ en->prev = e->prev;
+ e->prev = en;
+ en->next = e;
+ /*
+ * start over at en
+ */
+ next = en;
+ break;
+ }
+ }
+ }
+}
+
+/* The algorithm here is pretty simple:
+
+ inactive = [edges]
+ y = min_p1_y (inactive)
+
+ while (num_active || num_inactive) {
+ active = all edges containing y
+
+ next_y = min ( min_p2_y (active), min_p1_y (inactive), min_intersection (active) )
+
+ fill_traps (active, y, next_y, fill_rule)
+
+ y = next_y
+ }
+
+ The invariants that hold during fill_traps are:
+
+ All edges in active contain both y and next_y
+ No edges in active intersect within y and next_y
+
+ These invariants mean that fill_traps is as simple as sorting the
+ active edges, forming a trapezoid between each adjacent pair. Then,
+ either the even-odd or winding rule is used to determine whether to
+ emit each of these trapezoids.
+
+ Warning: This function reorders the edges of the polygon provided.
+*/
+cairo_status_t
+cairo_traps_tessellate_polygon (cairo_traps_t *traps,
+ cairo_polygon_t *poly,
+ cairo_fill_rule_t fill_rule)
+{
+ cairo_status_t status;
+ int inactive;
+ cairo_edge_t *active;
+ cairo_edge_t *e, *en, *next;
+ XFixed y, next_y, intersect;
+ int in_out, num_edges = poly->num_edges;
+ cairo_edge_t *edges = poly->edges;
+
+ if (num_edges == 0)
+ return CAIRO_STATUS_SUCCESS;
+
+ qsort (edges, num_edges, sizeof (cairo_edge_t), _compare_cairo_edge_by_top);
+
+ y = edges[0].edge.p1.y;
+ active = 0;
+ inactive = 0;
+ while (active || inactive < num_edges)
+ {
+ for (e = active; e; e = e->next) {
+ e->current_x = _compute_x (&e->edge, y);
+ }
+
+ /* insert new active edges into list */
+ while (inactive < num_edges)
+ {
+ e = &edges[inactive];
+ if (e->edge.p1.y > y)
+ break;
+ /* move this edge into the active list */
+ inactive++;
+ e->current_x = _compute_x (&e->edge, y);
+
+ /* insert e at head of list */
+ e->next = active;
+ e->prev = NULL;
+ if (active)
+ active->prev = e;
+ active = e;
+ }
+
+ _SortEdgeList (&active);
+
+ /* find next inflection point */
+ if (active)
+ next_y = active->edge.p2.y;
+ else
+ next_y = edges[inactive].edge.p1.y;
+ for (e = active; e; e = en)
+ {
+ en = e->next;
+
+ if (e->edge.p2.y < next_y)
+ next_y = e->edge.p2.y;
+ /* check intersect */
+ if (en && e->current_x != en->current_x)
+ {
+ if (_lines_intersect (&e->edge, &en->edge, &intersect))
+ if (intersect > y) {
+ /* Need to guarantee that we get all the way past
+ the intersection point so that the edges sort
+ properly next time through the loop. */
+ if (_compute_x (&e->edge, intersect) < _compute_x (&en->edge, intersect))
+ intersect++;
+ if (intersect < next_y)
+ next_y = intersect;
+ }
+ }
+ }
+ /* check next inactive point */
+ if (inactive < num_edges && edges[inactive].edge.p1.y < next_y)
+ next_y = edges[inactive].edge.p1.y;
+
+ /* walk the list generating trapezoids */
+ in_out = 0;
+ for (e = active; e && (en = e->next); e = e->next)
+ {
+ if (fill_rule == CAIRO_FILL_RULE_WINDING) {
+ if (e->clockWise) {
+ in_out++;
+ } else {
+ in_out--;
+ }
+ if (in_out == 0) {
+ continue;
+ }
+ } else {
+ in_out++;
+ if (in_out % 2 == 0) {
+ continue;
+ }
+ }
+ status = cairo_traps_add_trap (traps, y, next_y, e->edge, en->edge);
+ if (status)
+ return status;
+ }
+
+ /* delete inactive edges from list */
+ for (e = active; e; e = next)
+ {
+ next = e->next;
+ if (e->edge.p2.y <= next_y)
+ {
+ if (e->prev)
+ e->prev->next = e->next;
+ else
+ active = e->next;
+ if (e->next)
+ e->next->prev = e->prev;
+ }
+ }
+
+ y = next_y;
+ }
+ return CAIRO_STATUS_SUCCESS;
+}