diff options
author | Chris Wilson <chris@chris-wilson.co.uk> | 2008-12-17 09:32:16 +0000 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2009-08-29 08:08:28 +0100 |
commit | f8bb3617c3a7ec598c42eff1f8562e3ccc95127f (patch) | |
tree | f6c8f949ab44ca126053fb5cea2b9b88a4748cb8 | |
parent | 7c499db8afe8a7cf8c512ec166fe7dbf11a25c02 (diff) |
Eliminate self-intersecting strokes.
We refactor the surface fallbacks to convert full strokes and fills to the
intermediate polygon representation (as opposed to before where we
returned the trapezoidal representation). This allow greater flexibility
to choose how then to rasterize the polygon. Where possible we use the
local spans rasteriser for its increased performance, but still have the
option to use the tessellator instead (for example, with the current
Render protocol which does not yet have a polygon image).
In order to accommodate this, the spans interface is tweaked to accept
whole polygons instead of a path and the tessellator is tweaked for speed.
Performance Impact
==================
...
Still measuring, expecting some severe regressions.
...
177 files changed, 6226 insertions, 2113 deletions
@@ -69,6 +69,24 @@ New experimental backends: more offloading onto the GPU. The initial work on the backend was performed by Eric Anholt. +Long standing bugs fixed: + + Self-intersecting strokes. + + A long standing bug where the coverage from overlapping semi-opaque + strokes (including neighbouring edges) was simply summed in lieu of + a costly global calculation has been fixed (by performing the costly + global calculation!) In order to mitigate the extra cost, the + tessellator has been overhauled and tune, which handles the fallback + for when we are unable to use the new span rasteriser on the stroke + (e.g. when using the current RENDER protocol). The large number of + pixel artefacts that implementing self-intersection elimination + removes is ample justification for the potential performance + regression. If you unfortunately do suffer a substantial performance + regression in your application, please consider obtaining a + cairo-trace and submitting it to us for analysis and inclusion into + our performance suite. + Snapshot 1.9.2 (2009-06-12) =========================== diff --git a/src/Makefile.am b/src/Makefile.am index f34d79c4..e461fbde 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -99,5 +99,7 @@ COMPILE_ARGS = $(PREPROCESS_ARGS) $(AM_CFLAGS) $(CFLAGS) # cairo has been compiled with symbol hiding. .c.i: $(cairoinclude_HEADERS) $(nodist_cairoinclude_HEADERS) cairoint.h $(top_builddir)/config.h $(CPP) $(PREPROCESS_ARGS) $< -o $@ +.c.s: $(cairoinclude_HEADERS) $(nodist_cairoinclude_HEADERS) cairoint.h $(top_builddir)/config.h + $(CC) $(COMPILE_ARGS) $< -S -o $@ include $(srcdir)/Makefile.am.analysis diff --git a/src/cairo-bentley-ottmann.c b/src/cairo-bentley-ottmann.c index 1d59d703..f64b7a37 100644 --- a/src/cairo-bentley-ottmann.c +++ b/src/cairo-bentley-ottmann.c @@ -1,6 +1,7 @@ /* * Copyright © 2004 Carl Worth * Copyright © 2006 Red Hat, Inc. + * Copyright © 2008 Chris Wilson * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -31,6 +32,7 @@ * * Contributor(s): * Carl D. Worth <cworth@cworth.org> + * Chris Wilson <chris@chris-wilson.co.uk> */ /* Provide definitions for standalone compilation */ @@ -42,14 +44,11 @@ #define DEBUG_VALIDATE 0 #define DEBUG_PRINT_STATE 0 +#define DEBUG_EVENTS 0 +#define DEBUG_TRAPS 0 typedef cairo_point_t cairo_bo_point32_t; -typedef struct _cairo_bo_point128 { - cairo_int128_t x; - cairo_int128_t y; -} cairo_bo_point128_t; - typedef struct _cairo_bo_intersect_ordinate { int32_t ordinate; enum { EXACT, INEXACT } exactness; @@ -63,34 +62,18 @@ typedef struct _cairo_bo_intersect_point { typedef struct _cairo_bo_edge cairo_bo_edge_t; typedef struct _sweep_line_elt sweep_line_elt_t; typedef struct _cairo_bo_trap cairo_bo_trap_t; -typedef struct _cairo_bo_traps cairo_bo_traps_t; -/* A deferred trapezoid of an edge. */ +/* A deferred trapezoid of an edge */ struct _cairo_bo_trap { cairo_bo_edge_t *right; int32_t top; }; -struct _cairo_bo_traps { - cairo_traps_t *traps; - cairo_freelist_t freelist; - - /* These form the closed bounding box of the original input - * points. */ - cairo_fixed_t xmin; - cairo_fixed_t ymin; - cairo_fixed_t xmax; - cairo_fixed_t ymax; -}; - struct _cairo_bo_edge { - cairo_bo_point32_t top; - cairo_bo_point32_t middle; - cairo_bo_point32_t bottom; - int dir; + cairo_edge_t edge; cairo_bo_edge_t *prev; cairo_bo_edge_t *next; - cairo_bo_trap_t *deferred_trap; + cairo_bo_trap_t deferred_trap; sweep_line_elt_t *sweep_line_elt; }; @@ -100,35 +83,39 @@ struct _sweep_line_elt { }; #define SKIP_ELT_TO_EDGE_ELT(elt) SKIP_LIST_ELT_TO_DATA (sweep_line_elt_t, (elt)) -#define SKIP_ELT_TO_EDGE(elt) (SKIP_ELT_TO_EDGE_ELT (elt)->edge) +#define SKIP_ELT_TO_EDGE(elt) (SKIP_ELT_TO_EDGE_ELT (elt)->edge) typedef enum { - CAIRO_BO_STATUS_INTERSECTION, - CAIRO_BO_STATUS_PARALLEL, - CAIRO_BO_STATUS_NO_INTERSECTION -} cairo_bo_status_t; - -typedef enum { - CAIRO_BO_EVENT_TYPE_START, CAIRO_BO_EVENT_TYPE_STOP, - CAIRO_BO_EVENT_TYPE_INTERSECTION + CAIRO_BO_EVENT_TYPE_INTERSECTION, + CAIRO_BO_EVENT_TYPE_START } cairo_bo_event_type_t; typedef struct _cairo_bo_event { cairo_bo_event_type_t type; + cairo_point_t point; +} cairo_bo_event_t; + +typedef struct _cairo_bo_start_event { + cairo_bo_event_type_t type; + cairo_point_t point; + cairo_bo_edge_t edge; +} cairo_bo_start_event_t; + +typedef struct _cairo_bo_skiplist_event { + cairo_bo_event_type_t type; + cairo_point_t point; cairo_bo_edge_t *e1; cairo_bo_edge_t *e2; - cairo_bo_point32_t point; skip_elt_t elt; -} cairo_bo_event_t; +} cairo_bo_skiplist_event_t; -#define SKIP_ELT_TO_EVENT(elt) SKIP_LIST_ELT_TO_DATA (cairo_bo_event_t, (elt)) +#define SKIP_ELT_TO_EVENT(elt) ((cairo_bo_event_t *) SKIP_LIST_ELT_TO_DATA (cairo_bo_skiplist_event_t, (elt))) typedef struct _cairo_bo_event_queue { - cairo_skip_list_t intersection_queue; + cairo_skip_list_t event_queue; - cairo_bo_event_t *startstop_events; - cairo_bo_event_t **sorted_startstop_event_ptrs; + cairo_bo_event_t **start_events; } cairo_bo_event_queue_t; /* This structure extends #cairo_skip_list_t, which must come first. */ @@ -136,16 +123,134 @@ typedef struct _cairo_bo_sweep_line { cairo_skip_list_t active_edges; cairo_bo_edge_t *head; cairo_bo_edge_t *tail; + cairo_bo_edge_t *stopped; int32_t current_y; } cairo_bo_sweep_line_t; +#if DEBUG_TRAPS +static void +dump_traps (cairo_traps_t *traps, const char *filename) +{ + FILE *file; + int n; + + if (getenv ("CAIRO_DEBUG_TRAPS") == NULL) + return; + + if (traps->has_limits) { + printf ("%s: limits=(%d, %d, %d, %d)\n", + filename, + traps->limits.p1.x, traps->limits.p1.y, + traps->limits.p2.x, traps->limits.p2.y); + } + printf ("%s: extents=(%d, %d, %d, %d)\n", + filename, + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + + file = fopen (filename, "a"); + if (file != NULL) { + for (n = 0; n < traps->num_traps; n++) { + fprintf (file, "%d %d L:(%d, %d), (%d, %d) R:(%d, %d), (%d, %d)\n", + traps->traps[n].top, + traps->traps[n].bottom, + traps->traps[n].left.p1.x, + traps->traps[n].left.p1.y, + traps->traps[n].left.p2.x, + traps->traps[n].left.p2.y, + traps->traps[n].right.p1.x, + traps->traps[n].right.p1.y, + traps->traps[n].right.p2.x, + traps->traps[n].right.p2.y); + } + fprintf (file, "\n"); + fclose (file); + } +} + +static void +dump_edges (cairo_bo_start_event_t *events, + int num_edges, + const char *filename) +{ + FILE *file; + int n; + + if (getenv ("CAIRO_DEBUG_TRAPS") == NULL) + return; + + file = fopen (filename, "a"); + if (file != NULL) { + for (n = 0; n < num_edges; n++) { + fprintf (file, "(%d, %d), (%d, %d) %d %d %d\n", + events[n].edge.edge.line.p1.x, + events[n].edge.edge.line.p1.y, + events[n].edge.edge.line.p2.x, + events[n].edge.edge.line.p2.y, + events[n].edge.edge.top, + events[n].edge.edge.bottom, + events[n].edge.edge.dir); + } + fprintf (file, "\n"); + fclose (file); + } +} +#endif + +static cairo_fixed_t +_line_compute_intersection_x_for_y (const cairo_line_t *line, + cairo_fixed_t y) +{ + cairo_fixed_t x, dy; + + if (y == line->p1.y) + return line->p1.x; + if (y == line->p2.y) + return line->p2.x; + + x = line->p1.x; + dy = line->p2.y - line->p1.y; + if (dy != 0) { + x += _cairo_fixed_mul_div_floor (y - line->p1.y, + line->p2.x - line->p1.x, + dy); + } + + return x; +} + +static cairo_fixed_t +_line_compute_intersection_y_for_x (const cairo_line_t *line, + cairo_fixed_t x) +{ + cairo_fixed_t y, dx; + + if (x == line->p1.x) + return line->p1.y; + if (x == line->p2.x) + return line->p2.y; + + y = line->p1.y; + dx = line->p2.x - line->p1.x; + if (dx != 0) { + y += _cairo_fixed_mul_div_floor (x - line->p1.x, + line->p2.y - line->p1.y, + dx); + } + + return y; +} static inline int _cairo_bo_point32_compare (cairo_bo_point32_t const *a, cairo_bo_point32_t const *b) { - int cmp = a->y - b->y; - if (cmp) return cmp; + int cmp; + + cmp = a->y - b->y; + if (cmp) + return cmp; + return a->x - b->x; } @@ -159,7 +264,7 @@ _cairo_bo_point32_compare (cairo_bo_point32_t const *a, * * which is: * - * (dx, dy) = (bottom.x - top.x, bottom.y - top.y) + * (dx, dy) = (line.p2.x - line.p1.x, line.p2.y - line.p1.y) * * We then define the slope of each edge as dx/dy, (which is the * inverse of the slope typically used in math instruction). We never @@ -183,17 +288,17 @@ _cairo_bo_point32_compare (cairo_bo_point32_t const *a, * the sense of the result will be exactly reversed for two edges that * have a common stop point. */ -static int -_slope_compare (cairo_bo_edge_t *a, - cairo_bo_edge_t *b) +static inline int +_slope_compare (const cairo_bo_edge_t *a, + const cairo_bo_edge_t *b) { /* XXX: We're assuming here that dx and dy will still fit in 32 * bits. That's not true in general as there could be overflow. We * should prevent that before the tessellation algorithm * begins. */ - int32_t adx = a->bottom.x - a->top.x; - int32_t bdx = b->bottom.x - b->top.x; + int32_t adx = a->edge.line.p2.x - a->edge.line.p1.x; + int32_t bdx = b->edge.line.p2.x - b->edge.line.p1.x; /* Since the dy's are all positive by construction we can fast * path several common cases. @@ -211,8 +316,8 @@ _slope_compare (cairo_bo_edge_t *a, /* Finally we actually need to do the general comparison. */ { - int32_t ady = a->bottom.y - a->top.y; - int32_t bdy = b->bottom.y - b->top.y; + int32_t ady = a->edge.line.p2.y - a->edge.line.p1.y; + int32_t bdy = b->edge.line.p2.y - b->edge.line.p1.y; cairo_int64_t adx_bdy = _cairo_int32x32_64_mul (adx, bdy); cairo_int64_t bdx_ady = _cairo_int32x32_64_mul (bdx, ady); @@ -270,23 +375,46 @@ edges_compare_x_for_y_general (const cairo_bo_edge_t *a, HAVE_ALL = HAVE_DX | HAVE_ADX | HAVE_BDX } have_dx_adx_bdx = HAVE_ALL; - ady = a->bottom.y - a->top.y; - adx = a->bottom.x - a->top.x; + /* don't bother solving for abscissa if the edges' bounding boxes + * can be used to order them. */ + { + int32_t amin, amax; + int32_t bmin, bmax; + if (a->edge.line.p1.x < a->edge.line.p2.x) { + amin = a->edge.line.p1.x; + amax = a->edge.line.p2.x; + } else { + amin = a->edge.line.p2.x; + amax = a->edge.line.p1.x; + } + if (b->edge.line.p1.x < b->edge.line.p2.x) { + bmin = b->edge.line.p1.x; + bmax = b->edge.line.p2.x; + } else { + bmin = b->edge.line.p2.x; + bmax = b->edge.line.p1.x; + } + if (amax < bmin) return -1; + if (amin > bmax) return +1; + } + + ady = a->edge.line.p2.y - a->edge.line.p1.y; + adx = a->edge.line.p2.x - a->edge.line.p1.x; if (adx == 0) have_dx_adx_bdx &= ~HAVE_ADX; - bdy = b->bottom.y - b->top.y; - bdx = b->bottom.x - b->top.x; + bdy = b->edge.line.p2.y - b->edge.line.p1.y; + bdx = b->edge.line.p2.x - b->edge.line.p1.x; if (bdx == 0) have_dx_adx_bdx &= ~HAVE_BDX; - dx = a->top.x - b->top.x; + dx = a->edge.line.p1.x - b->edge.line.p1.x; if (dx == 0) have_dx_adx_bdx &= ~HAVE_DX; #define L _cairo_int64x32_128_mul (_cairo_int32x32_64_mul (ady, bdy), dx) -#define A _cairo_int64x32_128_mul (_cairo_int32x32_64_mul (adx, bdy), y - a->top.y) -#define B _cairo_int64x32_128_mul (_cairo_int32x32_64_mul (bdx, ady), y - b->top.y) +#define A _cairo_int64x32_128_mul (_cairo_int32x32_64_mul (adx, bdy), y - a->edge.line.p1.y) +#define B _cairo_int64x32_128_mul (_cairo_int32x32_64_mul (bdx, ady), y - b->edge.line.p1.y) switch (have_dx_adx_bdx) { default: case HAVE_NONE: @@ -304,7 +432,7 @@ edges_compare_x_for_y_general (const cairo_bo_edge_t *a, /* 0 ∘ (Y - B_y) * B_dx * A_dy - (Y - A_y) * A_dx * B_dy */ if ((adx ^ bdx) < 0) { return adx; - } else if (a->top.y == b->top.y) { /* common origin */ + } else if (a->edge.line.p1.y == b->edge.line.p1.y) { /* common origin */ cairo_int64_t adx_bdy, bdx_ady; /* ∴ A_dx * B_dy ∘ B_dx * A_dy */ @@ -323,7 +451,7 @@ edges_compare_x_for_y_general (const cairo_bo_edge_t *a, cairo_int64_t ady_dx, dy_adx; ady_dx = _cairo_int32x32_64_mul (ady, dx); - dy_adx = _cairo_int32x32_64_mul (a->top.y - y, adx); + dy_adx = _cairo_int32x32_64_mul (a->edge.line.p1.y - y, adx); return _cairo_int64_cmp (ady_dx, dy_adx); } @@ -335,11 +463,12 @@ edges_compare_x_for_y_general (const cairo_bo_edge_t *a, cairo_int64_t bdy_dx, dy_bdx; bdy_dx = _cairo_int32x32_64_mul (bdy, dx); - dy_bdx = _cairo_int32x32_64_mul (y - b->top.y, bdx); + dy_bdx = _cairo_int32x32_64_mul (y - b->edge.line.p1.y, bdx); return _cairo_int64_cmp (bdy_dx, dy_bdx); } case HAVE_ALL: + /* XXX try comparing (a->edge.line.p2.x - b->edge.line.p2.x) et al */ return _cairo_int128_cmp (L, _cairo_int128_sub (B, A)); } #undef B @@ -377,16 +506,21 @@ edge_compare_for_y_against_x (const cairo_bo_edge_t *a, int32_t dx, dy; cairo_int64_t L, R; - adx = a->bottom.x - a->top.x; - dx = x - a->top.x; + if (x < a->edge.line.p1.x && x < a->edge.line.p2.x) + return 1; + if (x > a->edge.line.p1.x && x > a->edge.line.p2.x) + return -1; + + adx = a->edge.line.p2.x - a->edge.line.p1.x; + dx = x - a->edge.line.p1.x; if (adx == 0) return -dx; - if ((adx ^ dx) < 0) + if (dx == 0 || (adx ^ dx) < 0) return adx; - dy = y - a->top.y; - ady = a->bottom.y - a->top.y; + dy = y - a->edge.line.p1.y; + ady = a->edge.line.p2.y - a->edge.line.p1.y; L = _cairo_int32x32_64_mul (dy, adx); R = _cairo_int32x32_64_mul (dx, ady); @@ -412,17 +546,17 @@ edges_compare_x_for_y (const cairo_bo_edge_t *a, } have_ax_bx = HAVE_BOTH; int32_t ax, bx; - if (y == a->top.y) - ax = a->top.x; - else if (y == a->bottom.y) - ax = a->bottom.x; + if (y == a->edge.line.p1.y) + ax = a->edge.line.p1.x; + else if (y == a->edge.line.p2.y) + ax = a->edge.line.p2.x; else have_ax_bx &= ~HAVE_AX; - if (y == b->top.y) - bx = b->top.x; - else if (y == b->bottom.y) - bx = b->bottom.x; + if (y == b->edge.line.p1.y) + bx = b->edge.line.p1.x; + else if (y == b->edge.line.p2.y) + bx = b->edge.line.p2.x; else have_ax_bx &= ~HAVE_BX; @@ -439,82 +573,59 @@ edges_compare_x_for_y (const cairo_bo_edge_t *a, } } +static inline int +_line_equal (const cairo_line_t *a, const cairo_line_t *b) +{ + return a->p1.x == b->p1.x && a->p1.y == b->p1.y && + a->p2.x == b->p2.x && a->p2.y == b->p2.y; +} + static int _cairo_bo_sweep_line_compare_edges (cairo_bo_sweep_line_t *sweep_line, - cairo_bo_edge_t *a, - cairo_bo_edge_t *b) + const cairo_bo_edge_t *a, + const cairo_bo_edge_t *b) { int cmp; - if (a == b) - return 0; - - /* don't bother solving for abscissa if the edges' bounding boxes - * can be used to order them. */ - { - int32_t amin, amax; - int32_t bmin, bmax; - if (a->middle.x < a->bottom.x) { - amin = a->middle.x; - amax = a->bottom.x; - } else { - amin = a->bottom.x; - amax = a->middle.x; - } - if (b->middle.x < b->bottom.x) { - bmin = b->middle.x; - bmax = b->bottom.x; - } else { - bmin = b->bottom.x; - bmax = b->middle.x; - } - if (amax < bmin) return -1; - if (amin > bmax) return +1; - } - - cmp = edges_compare_x_for_y (a, b, sweep_line->current_y); - if (cmp) - return cmp; + /* compare the edges if not identical */ + if (! _line_equal (&a->edge.line, &b->edge.line)) { + cmp = edges_compare_x_for_y (a, b, sweep_line->current_y); + if (cmp) + return cmp; - /* The two edges intersect exactly at y, so fall back on slope - * comparison. We know that this compare_edges function will be - * called only when starting a new edge, (not when stopping an - * edge), so we don't have to worry about conditionally inverting - * the sense of _slope_compare. */ - cmp = _slope_compare (a, b); - if (cmp) - return cmp; + /* The two edges intersect exactly at y, so fall back on slope + * comparison. We know that this compare_edges function will be + * called only when starting a new edge, (not when stopping an + * edge), so we don't have to worry about conditionally inverting + * the sense of _slope_compare. */ + cmp = _slope_compare (a, b); + if (cmp) + return cmp; - /* We've got two collinear edges now. */ + /* We've got two collinear edges now. */ - /* Since we're dealing with start events, prefer comparing top - * edges before bottom edges. */ - cmp = _cairo_bo_point32_compare (&a->top, &b->top); - if (cmp) - return cmp; + /* Since we're dealing with start events, prefer comparing top + * edges before bottom edges. */ + cmp = a->edge.top - b->edge.top; + if (cmp) + return cmp; - cmp = _cairo_bo_point32_compare (&a->bottom, &b->bottom); - if (cmp) - return cmp; + cmp = a->edge.bottom - b->edge.bottom; + if (cmp) + return cmp; + } - /* Finally, we've got two identical edges. Let's finally - * discriminate by a simple pointer comparison, (which works only - * because we "know" the edges are all in a single array and don't - * move. */ - if (a > b) - return 1; - else - return -1; + return a - b; } static int _sweep_line_elt_compare (void *list, - void *a, - void *b) + const void *a, + const void *b) { cairo_bo_sweep_line_t *sweep_line = list; - sweep_line_elt_t *edge_elt_a = a; - sweep_line_elt_t *edge_elt_b = b; + const sweep_line_elt_t *edge_elt_a = a; + const sweep_line_elt_t *edge_elt_b = b; return _cairo_bo_sweep_line_compare_edges (sweep_line, edge_elt_a->edge, @@ -522,157 +633,44 @@ _sweep_line_elt_compare (void *list, } static inline int -cairo_bo_event_compare (cairo_bo_event_t const *a, - cairo_bo_event_t const *b) +cairo_bo_event_compare (const cairo_bo_event_t *a, + const cairo_bo_event_t *b) { int cmp; - /* The major motion of the sweep line is vertical (top-to-bottom), - * and the minor motion is horizontal (left-to-right), dues to the - * infinitesimal tilt rule. - * - * Our point comparison function respects these rules. - */ cmp = _cairo_bo_point32_compare (&a->point, &b->point); if (cmp) return cmp; - /* The events share a common point, so further discrimination is - * determined by the event type. Due to the infinitesimal - * shortening rule, stop events come first, then intersection - * events, then start events. - */ - if (a->type != b->type) { - if (a->type == CAIRO_BO_EVENT_TYPE_STOP) - return -1; - if (a->type == CAIRO_BO_EVENT_TYPE_START) - return 1; - - if (b->type == CAIRO_BO_EVENT_TYPE_STOP) - return 1; - if (b->type == CAIRO_BO_EVENT_TYPE_START) - return -1; - } - - /* At this stage we are looking at two events of the same type at - * the same point. The final sort key is a slope comparison. We - * need a different sense for start and stop events based on the - * shortening rule. - * - * Note: Fortunately, we get to ignore errors in the relative - * ordering of intersection events. This means we don't even have - * to look at e2 here, nor worry about which sense of the slope - * comparison test is used for intersection events. - */ - cmp = _slope_compare (a->e1, b->e1); - if (cmp) { - if (a->type == CAIRO_BO_EVENT_TYPE_START) - return cmp; - else - return - cmp; - } - - /* Next look at the opposite point. This leaves ambiguities only - * for identical edges. */ - if (a->type == CAIRO_BO_EVENT_TYPE_START) { - cmp = _cairo_bo_point32_compare (&b->e1->bottom, - &a->e1->bottom); - if (cmp) - return cmp; - } - else if (a->type == CAIRO_BO_EVENT_TYPE_STOP) { - cmp = _cairo_bo_point32_compare (&a->e1->top, - &b->e1->top); - if (cmp) - return cmp; - } - else { /* CAIRO_BO_EVENT_TYPE_INTERSECT */ - /* For two intersection events at the identical point, we - * don't care what order they sort in, but we do care that we - * have a stable sort. In particular intersections between - * different pairs of edges must never return 0. */ - cmp = _cairo_bo_point32_compare (&a->e2->top, &b->e2->top); - if (cmp) - return cmp; - cmp = _cairo_bo_point32_compare (&a->e2->bottom, &b->e2->bottom); - if (cmp) - return cmp; - cmp = _cairo_bo_point32_compare (&a->e1->top, &b->e1->top); - if (cmp) - return cmp; - cmp = _cairo_bo_point32_compare (&a->e1->bottom, &b->e1->bottom); - if (cmp) - return cmp; - } - - /* Discrimination based on the edge pointers. */ - if (a->e1 < b->e1) - return -1; - if (a->e1 > b->e1) - return +1; - if (a->e2 < b->e2) - return -1; - if (a->e2 > b->e2) - return +1; - return 0; -} - -static int -cairo_bo_event_compare_abstract (void *list, - void *a, - void *b) -{ - cairo_bo_event_t *event_a = a; - cairo_bo_event_t *event_b = b; + cmp = a->type - b->type; + if (cmp) + return cmp; - return cairo_bo_event_compare (event_a, event_b); + return a - b; } static int -cairo_bo_event_compare_pointers (const cairo_bo_event_t *a, - const cairo_bo_event_t *b) +cairo_bo_event_compare_skiplist (void *list, const void *a, const void *b) { - int cmp; - - if (a == b) - return 0; - cmp = cairo_bo_event_compare (a, b); - if (cmp) - return cmp; - - return a - b; + return cairo_bo_event_compare (a, b); } static inline cairo_int64_t -det32_64 (int32_t a, - int32_t b, - int32_t c, - int32_t d) +det32_64 (int32_t a, int32_t b, + int32_t c, int32_t d) { - cairo_int64_t ad; - cairo_int64_t bc; - /* det = a * d - b * c */ - ad = _cairo_int32x32_64_mul (a, d); - bc = _cairo_int32x32_64_mul (b, c); - - return _cairo_int64_sub (ad, bc); + return _cairo_int64_sub (_cairo_int32x32_64_mul (a, d), + _cairo_int32x32_64_mul (b, c)); } static inline cairo_int128_t -det64x32_128 (cairo_int64_t a, - int32_t b, - cairo_int64_t c, - int32_t d) +det64x32_128 (cairo_int64_t a, int32_t b, + cairo_int64_t c, int32_t d) { - cairo_int128_t ad; - cairo_int128_t bc; - /* det = a * d - b * c */ - ad = _cairo_int64x32_128_mul (a, d); - bc = _cairo_int64x32_128_mul (c, b); - - return _cairo_int128_sub (ad, bc); + return _cairo_int128_sub (_cairo_int64x32_128_mul (a, d), + _cairo_int64x32_128_mul (c, b)); } /* Compute the intersection of two lines as defined by two edges. The @@ -681,7 +679,7 @@ det64x32_128 (cairo_int64_t a, * Returns %CAIRO_BO_STATUS_INTERSECTION if there is an intersection or * %CAIRO_BO_STATUS_PARALLEL if the two lines are exactly parallel. */ -static cairo_bo_status_t +static cairo_bool_t intersect_lines (cairo_bo_edge_t *a, cairo_bo_edge_t *b, cairo_bo_intersect_point_t *intersection) @@ -694,11 +692,11 @@ intersect_lines (cairo_bo_edge_t *a, * What we're doing to mitigate this is to perform clamping in * cairo_bo_tessellate_polygon(). */ - int32_t dx1 = a->top.x - a->bottom.x; - int32_t dy1 = a->top.y - a->bottom.y; + int32_t dx1 = a->edge.line.p1.x - a->edge.line.p2.x; + int32_t dy1 = a->edge.line.p1.y - a->edge.line.p2.y; - int32_t dx2 = b->top.x - b->bottom.x; - int32_t dy2 = b->top.y - b->bottom.y; + int32_t dx2 = b->edge.line.p1.x - b->edge.line.p2.x; + int32_t dy2 = b->edge.line.p1.y - b->edge.line.p2.y; cairo_int64_t den_det; cairo_int64_t R; @@ -706,7 +704,7 @@ intersect_lines (cairo_bo_edge_t *a, den_det = det32_64 (dx1, dy1, dx2, dy2); if (_cairo_int64_is_zero (den_det)) - return CAIRO_BO_STATUS_PARALLEL; + return FALSE; /* Q: Can we determine that the lines do not intersect (within range) * much more cheaply than computing the intersection point i.e. by @@ -727,58 +725,90 @@ intersect_lines (cairo_bo_edge_t *a, * A similar substitution can be performed for s, yielding: * s * (ady*bdx - bdy*adx) = ady * (ax - bx) - adx * (ay - by) */ - R = det32_64 (dx2, dy2, b->top.x - a->top.x, b->top.y - a->top.y); + R = det32_64 (dx2, dy2, + b->edge.line.p1.x - a->edge.line.p1.x, + b->edge.line.p1.y - a->edge.line.p1.y); if (_cairo_int64_is_zero (R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; if (_cairo_int64_negative (den_det) ^ _cairo_int64_negative (R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; if (_cairo_int64_negative (den_det)) { if (_cairo_int64_ge (den_det, R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; } else { if (_cairo_int64_le (den_det, R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; } - R = det32_64 (dy1, dx1, a->top.y - b->top.y, a->top.x - b->top.x); + R = det32_64 (dy1, dx1, + a->edge.line.p1.y - b->edge.line.p1.y, + a->edge.line.p1.x - b->edge.line.p1.x); if (_cairo_int64_is_zero (R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; if (_cairo_int64_negative (den_det) ^ _cairo_int64_negative (R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; if (_cairo_int64_negative (den_det)) { if (_cairo_int64_ge (den_det, R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; } else { if (_cairo_int64_le (den_det, R)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; } /* We now know that the two lines should intersect within range. */ - a_det = det32_64 (a->top.x, a->top.y, - a->bottom.x, a->bottom.y); - b_det = det32_64 (b->top.x, b->top.y, - b->bottom.x, b->bottom.y); + a_det = det32_64 (a->edge.line.p1.x, a->edge.line.p1.y, + a->edge.line.p2.x, a->edge.line.p2.y); + b_det = det32_64 (b->edge.line.p1.x, b->edge.line.p1.y, + b->edge.line.p2.x, b->edge.line.p2.y); /* x = det (a_det, dx1, b_det, dx2) / den_det */ qr = _cairo_int_96by64_32x64_divrem (det64x32_128 (a_det, dx1, b_det, dx2), den_det); if (_cairo_int64_eq (qr.rem, den_det)) - return CAIRO_BO_STATUS_NO_INTERSECTION; - intersection->x.ordinate = _cairo_int64_to_int32 (qr.quo); + return FALSE; +#if 0 intersection->x.exactness = _cairo_int64_is_zero (qr.rem) ? EXACT : INEXACT; +#else + intersection->x.exactness = EXACT; + if (! _cairo_int64_is_zero (qr.rem)) { + if (_cairo_int64_negative (den_det) ^ _cairo_int64_negative (qr.rem)) + qr.rem = _cairo_int64_negate (qr.rem); + qr.rem = _cairo_int64_mul (qr.rem, _cairo_int32_to_int64 (2)); + if (_cairo_int64_ge (qr.rem, den_det)) { + qr.quo = _cairo_int64_add (qr.quo, + _cairo_int32_to_int64 (_cairo_int64_negative (qr.quo) ? -1 : 1)); + } else + intersection->x.exactness = INEXACT; + } +#endif + intersection->x.ordinate = _cairo_int64_to_int32 (qr.quo); /* y = det (a_det, dy1, b_det, dy2) / den_det */ qr = _cairo_int_96by64_32x64_divrem (det64x32_128 (a_det, dy1, b_det, dy2), den_det); if (_cairo_int64_eq (qr.rem, den_det)) - return CAIRO_BO_STATUS_NO_INTERSECTION; - intersection->y.ordinate = _cairo_int64_to_int32 (qr.quo); + return FALSE; +#if 0 intersection->y.exactness = _cairo_int64_is_zero (qr.rem) ? EXACT : INEXACT; +#else + intersection->y.exactness = EXACT; + if (! _cairo_int64_is_zero (qr.rem)) { + if (_cairo_int64_negative (den_det) ^ _cairo_int64_negative (qr.rem)) + qr.rem = _cairo_int64_negate (qr.rem); + qr.rem = _cairo_int64_mul (qr.rem, _cairo_int32_to_int64 (2)); + if (_cairo_int64_ge (qr.rem, den_det)) { + qr.quo = _cairo_int64_add (qr.quo, + _cairo_int32_to_int64 (_cairo_int64_negative (qr.quo) ? -1 : 1)); + } else + intersection->y.exactness = INEXACT; + } +#endif + intersection->y.ordinate = _cairo_int64_to_int32 (qr.quo); - return CAIRO_BO_STATUS_INTERSECTION; + return TRUE; } static int @@ -825,8 +855,10 @@ _cairo_bo_edge_contains_intersect_point (cairo_bo_edge_t *edge, * finder which needs them. */ - cmp_top = _cairo_bo_intersect_ordinate_32_compare (point->y, edge->top.y); - cmp_bottom = _cairo_bo_intersect_ordinate_32_compare (point->y, edge->bottom.y); + cmp_top = _cairo_bo_intersect_ordinate_32_compare (point->y, + edge->edge.top); + cmp_bottom = _cairo_bo_intersect_ordinate_32_compare (point->y, + edge->edge.bottom); if (cmp_top < 0 || cmp_bottom > 0) { @@ -849,10 +881,19 @@ _cairo_bo_edge_contains_intersect_point (cairo_bo_edge_t *edge, * edge, then the x value of the point must be less to be * considered as inside. */ - if (cmp_top == 0) - return (_cairo_bo_intersect_ordinate_32_compare (point->x, edge->top.x) > 0); - else /* cmp_bottom == 0 */ - return (_cairo_bo_intersect_ordinate_32_compare (point->x, edge->bottom.x) < 0); + if (cmp_top == 0) { + cairo_fixed_t top_x; + + top_x = _line_compute_intersection_x_for_y (&edge->edge.line, + edge->edge.top); + return _cairo_bo_intersect_ordinate_32_compare (point->x, top_x) > 0; + } else { /* cmp_bottom == 0 */ + cairo_fixed_t bot_x; + + bot_x = _line_compute_intersection_x_for_y (&edge->edge.line, + edge->edge.bottom); + return _cairo_bo_intersect_ordinate_32_compare (point->x, bot_x) < 0; + } } /* Compute the intersection of two edges. The result is provided as a @@ -871,23 +912,21 @@ _cairo_bo_edge_contains_intersect_point (cairo_bo_edge_t *edge, * effectively outside (no intersection is returned). Also, if the * intersection point has the same */ -static cairo_bo_status_t +static cairo_bool_t _cairo_bo_edge_intersect (cairo_bo_edge_t *a, cairo_bo_edge_t *b, cairo_bo_point32_t *intersection) { - cairo_bo_status_t status; cairo_bo_intersect_point_t quorem; - status = intersect_lines (a, b, &quorem); - if (status) - return status; + if (! intersect_lines (a, b, &quorem)) + return FALSE; if (! _cairo_bo_edge_contains_intersect_point (a, &quorem)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; if (! _cairo_bo_edge_contains_intersect_point (b, &quorem)) - return CAIRO_BO_STATUS_NO_INTERSECTION; + return FALSE; /* Now that we've correctly compared the intersection point and * determined that it lies within the edge, then we know that we @@ -897,31 +936,32 @@ _cairo_bo_edge_intersect (cairo_bo_edge_t *a, intersection->x = quorem.x.ordinate; intersection->y = quorem.y.ordinate; - return CAIRO_BO_STATUS_INTERSECTION; + return TRUE; } static void -_cairo_bo_event_init (cairo_bo_event_t *event, - cairo_bo_event_type_t type, - cairo_bo_edge_t *e1, - cairo_bo_edge_t *e2, - cairo_bo_point32_t point) +_cairo_bo_skiplist_event_init (cairo_bo_skiplist_event_t *event, + cairo_bo_event_type_t type, + cairo_bo_edge_t *e1, + cairo_bo_edge_t *e2, + const cairo_point_t *point) { event->type = type; event->e1 = e1; event->e2 = e2; - event->point = point; + event->point = *point; } static cairo_status_t _cairo_bo_event_queue_insert (cairo_bo_event_queue_t *queue, - cairo_bo_event_t *event) + cairo_bo_skiplist_event_t *event) { cairo_status_t status = CAIRO_STATUS_SUCCESS; - /* Don't insert if there's already an equivalent intersection event in the queue. */ - if (_cairo_skip_list_insert (&queue->intersection_queue, event, - event->type == CAIRO_BO_EVENT_TYPE_INTERSECTION) == NULL) + + /* Only insert if there is no prior identical intersection event. */ + if (unlikely (_cairo_skip_list_insert (&queue->event_queue, event) == NULL)) status = _cairo_error (CAIRO_STATUS_NO_MEMORY); + return status; } @@ -929,126 +969,102 @@ static void _cairo_bo_event_queue_delete (cairo_bo_event_queue_t *queue, cairo_bo_event_t *event) { - if (CAIRO_BO_EVENT_TYPE_INTERSECTION == event->type) - _cairo_skip_list_delete_given ( &queue->intersection_queue, &event->elt ); + _cairo_skip_list_delete_given (&queue->event_queue, + &((cairo_bo_skiplist_event_t *) event)->elt); } +#define NEXT_EVENT(Q) \ + ((Q)->chains[0] ? SKIP_ELT_TO_EVENT ((Q)->chains[0]) : NULL) static cairo_bo_event_t * _cairo_bo_event_dequeue (cairo_bo_event_queue_t *event_queue) { - skip_elt_t *elt = event_queue->intersection_queue.chains[0]; - cairo_bo_event_t *intersection = elt ? SKIP_ELT_TO_EVENT (elt) : NULL; - cairo_bo_event_t *startstop; + cairo_bo_event_t *event, *cmp; - startstop = *event_queue->sorted_startstop_event_ptrs; - if (startstop == NULL) - return intersection; + event = NEXT_EVENT (&event_queue->event_queue); - if (intersection == NULL || - cairo_bo_event_compare (startstop, intersection) <= 0) + cmp = *event_queue->start_events; + if (event == NULL || + (cmp != NULL && cairo_bo_event_compare (cmp, event) < 0)) { - event_queue->sorted_startstop_event_ptrs++; - return startstop; + event = cmp; + event_queue->start_events++; } - return intersection; + return event; } CAIRO_COMBSORT_DECLARE (_cairo_bo_event_queue_sort, cairo_bo_event_t *, - cairo_bo_event_compare_pointers) + cairo_bo_event_compare) -static cairo_status_t -_cairo_bo_event_queue_init (cairo_bo_event_queue_t *event_queue, - cairo_bo_edge_t *edges, - int num_edges) +static void +_cairo_bo_event_queue_init (cairo_bo_event_queue_t *event_queue, + cairo_bo_event_t **start_events, + int num_events) { - int i; - cairo_bo_event_t *events, **sorted_event_ptrs; - unsigned num_events = 2*num_edges; - - /* The skip_elt_t field of a cairo_bo_event_t isn't used for start - * or stop events, so this allocation is safe. XXX: make the - * event type a union so it doesn't always contain the skip - * elt? */ - events = _cairo_malloc_ab_plus_c (num_events, - sizeof (cairo_bo_event_t) + - sizeof (cairo_bo_event_t *), - sizeof (cairo_bo_event_t *)); - if (unlikely (events == NULL)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + _cairo_bo_event_queue_sort (start_events, num_events); + start_events[num_events] = NULL; - sorted_event_ptrs = (cairo_bo_event_t **) (events + num_events); - event_queue->startstop_events = events; - event_queue->sorted_startstop_event_ptrs = sorted_event_ptrs; + event_queue->start_events = start_events; - for (i = 0; i < num_edges; i++) { - sorted_event_ptrs[i] = &events[2*i]; - sorted_event_ptrs[i+num_edges] = &events[2*i+1]; - - /* Initialize "middle" to top */ - edges[i].middle = edges[i].top; - - _cairo_bo_event_init (&events[2*i], - CAIRO_BO_EVENT_TYPE_START, - &edges[i], NULL, - edges[i].top); - - _cairo_bo_event_init (&events[2*i+1], - CAIRO_BO_EVENT_TYPE_STOP, - &edges[i], NULL, - edges[i].bottom); - } + _cairo_skip_list_init (&event_queue->event_queue, + cairo_bo_event_compare_skiplist, + sizeof (cairo_bo_skiplist_event_t)); +} - _cairo_bo_event_queue_sort (sorted_event_ptrs, num_events); - event_queue->sorted_startstop_event_ptrs[num_events] = NULL; +static cairo_status_t +_cairo_bo_event_queue_insert_stop (cairo_bo_event_queue_t *event_queue, + cairo_bo_edge_t *edge) +{ + cairo_bo_point32_t point; + cairo_bo_skiplist_event_t event; - _cairo_skip_list_init (&event_queue->intersection_queue, - cairo_bo_event_compare_abstract, - sizeof (cairo_bo_event_t)); + point.y = edge->edge.bottom; + point.x = _line_compute_intersection_x_for_y (&edge->edge.line, + point.y); + _cairo_bo_skiplist_event_init (&event, + CAIRO_BO_EVENT_TYPE_STOP, + edge, NULL, + &point); - return CAIRO_STATUS_SUCCESS; + return _cairo_bo_event_queue_insert (event_queue, &event); } static void _cairo_bo_event_queue_fini (cairo_bo_event_queue_t *event_queue) { - _cairo_skip_list_fini (&event_queue->intersection_queue); - if (event_queue->startstop_events) - free (event_queue->startstop_events); + _cairo_skip_list_fini (&event_queue->event_queue); } -static cairo_status_t +static inline cairo_status_t _cairo_bo_event_queue_insert_if_intersect_below_current_y (cairo_bo_event_queue_t *event_queue, cairo_bo_edge_t *left, - cairo_bo_edge_t *right) + cairo_bo_edge_t *right) { - cairo_bo_status_t status; cairo_bo_point32_t intersection; - cairo_bo_event_t event; + cairo_bo_skiplist_event_t event; if (left == NULL || right == NULL) return CAIRO_STATUS_SUCCESS; + if (_line_equal (&left->edge.line, &right->edge.line)) + return CAIRO_STATUS_SUCCESS; + /* The names "left" and "right" here are correct descriptions of * the order of the two edges within the active edge list. So if a * slope comparison also puts left less than right, then we know - * that the intersection of these two segments has oalready + * that the intersection of these two segments has already * occurred before the current sweep line position. */ - if (_slope_compare (left, right) < 0) + if (_slope_compare (left, right) <= 0) return CAIRO_STATUS_SUCCESS; - status = _cairo_bo_edge_intersect (left, right, &intersection); - if (status == CAIRO_BO_STATUS_PARALLEL || - status == CAIRO_BO_STATUS_NO_INTERSECTION) - { + if (! _cairo_bo_edge_intersect (left, right, &intersection)) return CAIRO_STATUS_SUCCESS; - } - _cairo_bo_event_init (&event, - CAIRO_BO_EVENT_TYPE_INTERSECTION, - left, right, - intersection); + _cairo_bo_skiplist_event_init (&event, + CAIRO_BO_EVENT_TYPE_INTERSECTION, + left, right, + &intersection); return _cairo_bo_event_queue_insert (event_queue, &event); } @@ -1062,7 +1078,8 @@ _cairo_bo_sweep_line_init (cairo_bo_sweep_line_t *sweep_line) sweep_line->head = NULL; sweep_line->tail = NULL; - sweep_line->current_y = 0; + sweep_line->stopped = NULL; + sweep_line->current_y = INT32_MIN; } static void @@ -1079,14 +1096,13 @@ _cairo_bo_sweep_line_insert (cairo_bo_sweep_line_t *sweep_line, sweep_line_elt_t *sweep_line_elt; cairo_bo_edge_t **prev_of_next, **next_of_prev; - sweep_line_elt = _cairo_skip_list_insert (&sweep_line->active_edges, &edge, - 1 /* unique inserts*/); + sweep_line_elt = _cairo_skip_list_insert (&sweep_line->active_edges, &edge); if (unlikely (sweep_line_elt == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); next_elt = sweep_line_elt->elt.next[0]; if (next_elt) - prev_of_next = & (SKIP_ELT_TO_EDGE (next_elt)->prev); + prev_of_next = &SKIP_ELT_TO_EDGE (next_elt)->prev; else prev_of_next = &sweep_line->tail; @@ -1170,8 +1186,8 @@ static void _cairo_bo_edge_print (cairo_bo_edge_t *edge) { printf ("(0x%x, 0x%x)-(0x%x, 0x%x)", - edge->top.x, edge->top.y, - edge->bottom.x, edge->bottom.y); + edge->edge.line.p1.x, edge->edge.line.p1.y, + edge->edge.line.p2.x, edge->edge.line.p2.y); } static void @@ -1201,19 +1217,14 @@ static void _cairo_bo_event_queue_print (cairo_bo_event_queue_t *event_queue) { skip_elt_t *elt; + /* XXX: fixme to print the start/stop array too. */ - cairo_skip_list_t *queue = &event_queue->intersection_queue; - cairo_bo_event_t *event; + cairo_skip_list_t *queue = &event_queue->event_queue; printf ("Event queue:\n"); - for (elt = queue->chains[0]; - elt; - elt = elt->next[0]) - { - event = SKIP_ELT_TO_EVENT (elt); - _cairo_bo_event_print (event); - } + for (elt = queue->chains[0]; elt; elt = elt->next[0]) + _cairo_bo_event_print (SKIP_ELT_TO_EVENT (elt)); } static void @@ -1266,140 +1277,18 @@ _cairo_bo_sweep_line_print (cairo_bo_sweep_line_t *sweep_line) static void print_state (const char *msg, + cairo_bo_event_t *event, cairo_bo_event_queue_t *event_queue, cairo_bo_sweep_line_t *sweep_line) { - printf ("%s\n", msg); + printf ("%s ", msg); + _cairo_bo_event_print (event); _cairo_bo_event_queue_print (event_queue); _cairo_bo_sweep_line_print (sweep_line); printf ("\n"); } #endif -/* Adds the trapezoid, if any, of the left edge to the #cairo_traps_t - * of bo_traps. */ -static cairo_status_t -_cairo_bo_edge_end_trap (cairo_bo_edge_t *left, - int32_t bot, - cairo_bo_traps_t *bo_traps) -{ - cairo_fixed_t fixed_top, fixed_bot; - cairo_bo_trap_t *trap = left->deferred_trap; - cairo_bo_edge_t *right; - - if (!trap) - return CAIRO_STATUS_SUCCESS; - - /* If the right edge of the trapezoid stopped earlier than the - * left edge, then cut the trapezoid bottom early. */ - right = trap->right; - if (right->bottom.y < bot) - bot = right->bottom.y; - - fixed_top = trap->top; - fixed_bot = bot; - - /* Only emit trapezoids with positive height. */ - if (fixed_top < fixed_bot) { - cairo_line_t left_line; - cairo_line_t right_line; - cairo_fixed_t xmin = bo_traps->xmin; - cairo_fixed_t ymin = bo_traps->ymin; - fixed_top += ymin; - fixed_bot += ymin; - - left_line.p1.x = left->top.x + xmin; - left_line.p1.y = left->top.y + ymin; - right_line.p1.x = right->top.x + xmin; - right_line.p1.y = right->top.y + ymin; - - left_line.p2.x = left->bottom.x + xmin; - left_line.p2.y = left->bottom.y + ymin; - right_line.p2.x = right->bottom.x + xmin; - right_line.p2.y = right->bottom.y + ymin; - - /* Avoid emitting the trapezoid if it is obviously degenerate. - * TODO: need a real collinearity test here for the cases - * where the trapezoid is degenerate, yet the top and bottom - * coordinates aren't equal. */ - if (left_line.p1.x != right_line.p1.x || - left_line.p1.y != right_line.p1.y || - left_line.p2.x != right_line.p2.x || - left_line.p2.y != right_line.p2.y) - { - _cairo_traps_add_trap (bo_traps->traps, - fixed_top, fixed_bot, - &left_line, &right_line); - -#if DEBUG_PRINT_STATE - printf ("Deferred trap: left=(%08x, %08x)-(%08x,%08x) " - "right=(%08x,%08x)-(%08x,%08x) top=%08x, bot=%08x\n", - left->top.x, left->top.y, left->bottom.x, left->bottom.y, - right->top.x, right->top.y, right->bottom.x, right->bottom.y, - trap->top, bot); -#endif - } - } - - _cairo_freelist_free (&bo_traps->freelist, trap); - left->deferred_trap = NULL; - - return _cairo_traps_status (bo_traps->traps); -} - -/* Start a new trapezoid at the given top y coordinate, whose edges - * are `edge' and `edge->next'. If `edge' already has a trapezoid, - * then either add it to the traps in `bo_traps', if the trapezoid's - * right edge differs from `edge->next', or do nothing if the new - * trapezoid would be a continuation of the existing one. */ -static cairo_status_t -_cairo_bo_edge_start_or_continue_trap (cairo_bo_edge_t *edge, - int32_t top, - cairo_bo_traps_t *bo_traps) -{ - cairo_status_t status; - cairo_bo_trap_t *trap = edge->deferred_trap; - - if (trap) { - if (trap->right == edge->next) return CAIRO_STATUS_SUCCESS; - status = _cairo_bo_edge_end_trap (edge, top, bo_traps); - if (status) - return status; - } - - if (edge->next) { - trap = edge->deferred_trap = _cairo_freelist_alloc (&bo_traps->freelist); - if (!edge->deferred_trap) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - - trap->right = edge->next; - trap->top = top; - } - return CAIRO_STATUS_SUCCESS; -} - -static void -_cairo_bo_traps_init (cairo_bo_traps_t *bo_traps, - cairo_traps_t *traps, - cairo_fixed_t xmin, - cairo_fixed_t ymin, - cairo_fixed_t xmax, - cairo_fixed_t ymax) -{ - bo_traps->traps = traps; - _cairo_freelist_init (&bo_traps->freelist, sizeof(cairo_bo_trap_t)); - bo_traps->xmin = xmin; - bo_traps->ymin = ymin; - bo_traps->xmax = xmax; - bo_traps->ymax = ymax; -} - -static void -_cairo_bo_traps_fini (cairo_bo_traps_t *bo_traps) -{ - _cairo_freelist_fini (&bo_traps->freelist); -} - #if DEBUG_VALIDATE static void _cairo_bo_sweep_line_validate (cairo_bo_sweep_line_t *sweep_line) @@ -1428,184 +1317,403 @@ _cairo_bo_sweep_line_validate (cairo_bo_sweep_line_t *sweep_line) } #endif +#if DEBUG_EVENTS +static void CAIRO_PRINTF_FORMAT (1, 2) +event_log (const char *fmt, ...) +{ + FILE *file; + + if (getenv ("CAIRO_DEBUG_EVENTS") == NULL) + return; + + file = fopen ("bo-events.txt", "a"); + if (file != NULL) { + va_list ap; + + va_start (ap, fmt); + vfprintf (file, fmt, ap); + va_end (ap); + + fclose (file); + } +} +#endif + +static inline cairo_bool_t +edges_colinear (const cairo_bo_edge_t *a, const cairo_bo_edge_t *b) +{ + if (_line_equal (&a->edge.line, &b->edge.line)) + return TRUE; + + /* The choice of y is not truly arbitrary since we must guarantee that it + * is greater than the start of either line. + */ + return _slope_compare (a, b) == 0 && + edges_compare_x_for_y (a, b, + MAX (a->edge.line.p1.y, + b->edge.line.p1.y)) == 0; +} +/* Adds the trapezoid, if any, of the left edge to the #cairo_traps_t */ static cairo_status_t -_active_edges_to_traps (cairo_bo_edge_t *head, +_cairo_bo_edge_end_trap (cairo_bo_edge_t *left, + int32_t bot, + cairo_traps_t *traps) +{ + cairo_bo_trap_t *trap = &left->deferred_trap; + + /* Only emit (trivial) non-degenerate trapezoids with positive height. */ + if (likely (trap->top < bot && ! edges_colinear (left, trap->right))) { + assert (trap->right->edge.bottom >= bot); + _cairo_traps_add_trap (traps, + trap->top, bot, + &left->edge.line, &trap->right->edge.line); + +#if DEBUG_PRINT_STATE + printf ("Deferred trap: left=(%x, %x)-(%x,%x) " + "right=(%x,%x)-(%x,%x) top=%x, bot=%x\n", + left->edge.line.p1.x, left->edge.line.p1.y, + left->edge.line.p2.x, left->edge.line.p2.y, + trap->right->edge.line.p1.x, trap->right->edge.line.p1.y, + trap->right->edge.line.p2.x, trap->right->edge.line.p2.y, + trap->top, bot); +#endif +#if DEBUG_EVENTS + event_log ("end trap: %lu %lu %d %d\n", + (long) left, + (long) trap->right, + trap->top, + bot); +#endif + } + + trap->right = NULL; + + return _cairo_traps_status (traps); +} + + +/* Start a new trapezoid at the given top y coordinate, whose edges + * are `edge' and `edge->next'. If `edge' already has a trapezoid, + * then either add it to the traps in `traps', if the trapezoid's + * right edge differs from `edge->next', or do nothing if the new + * trapezoid would be a continuation of the existing one. */ +static inline cairo_status_t +_cairo_bo_edge_start_or_continue_trap (cairo_bo_edge_t *left, + cairo_bo_edge_t *right, + int top, + cairo_traps_t *traps) +{ + cairo_status_t status; + + if (left->deferred_trap.right == right) + return CAIRO_STATUS_SUCCESS; + + if (left->deferred_trap.right != NULL) { + if (right != NULL && edges_colinear (left->deferred_trap.right, right)) + { + /* continuation on right, so just swap edges */ + left->deferred_trap.right = right; + return CAIRO_STATUS_SUCCESS; + } + + status = _cairo_bo_edge_end_trap (left, top, traps); + if (unlikely (status)) + return status; + } + + if (right != NULL) { + left->deferred_trap.top = top; + left->deferred_trap.right = right; + +#if DEBUG_EVENTS + event_log ("begin trap: %lu %lu %d\n", + (long) left, + (long) right, + top); +#endif + } + + return CAIRO_STATUS_SUCCESS; +} + +static inline cairo_status_t +_active_edges_to_traps (cairo_bo_edge_t *left, int32_t top, cairo_fill_rule_t fill_rule, - cairo_bo_traps_t *bo_traps) + cairo_traps_t *traps) { + cairo_bo_edge_t *right; cairo_status_t status; - int in_out = 0; - cairo_bo_edge_t *edge; - for (edge = head; edge; edge = edge->next) { - if (fill_rule == CAIRO_FILL_RULE_WINDING) { - in_out += edge->dir; - if (in_out == 0) { - status = _cairo_bo_edge_end_trap (edge, top, bo_traps); - if (status) - return status; - continue; +#if DEBUG_PRINT_STATE + printf ("Processing active edges for %x\n", top); +#endif + + if (fill_rule == CAIRO_FILL_RULE_WINDING) { + while (left != NULL) { + int in_out; + + /* Greedily search for the closing edge, so that we generate the + * maximal span width with the minimal number of trapezoids. + */ + in_out = left->edge.dir; + + /* Check if there is a co-linear edge with an existing trap */ + right = left->next; + if (left->deferred_trap.right == NULL) { + while (right != NULL && right->deferred_trap.right == NULL) + right = right->next; + + if (right != NULL && edges_colinear (left, right)) { + /* continuation on left */ + left->deferred_trap = right->deferred_trap; + right->deferred_trap.right = NULL; + } } - } else { - in_out++; - if ((in_out & 1) == 0) { - status = _cairo_bo_edge_end_trap (edge, top, bo_traps); - if (status) - return status; - continue; + + /* End all subsumed traps */ + right = left->next; + while (right != NULL) { + if (right->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (right, top, traps); + if (unlikely (status)) + return status; + } + + in_out += right->edge.dir; + if (in_out == 0) { + cairo_bo_edge_t *next; + cairo_bool_t skip = FALSE; + + /* skip co-linear edges */ + next = right->next; + if (next != NULL) + skip = edges_colinear (right, next); + + if (! skip) + break; + } + + right = right->next; } + + status = _cairo_bo_edge_start_or_continue_trap (left, right, + top, traps); + if (unlikely (status)) + return status; + + left = right; + if (left != NULL) + left = left->next; } + } else { + while (left != NULL) { + int in_out = 0; + + right = left->next; + while (right != NULL) { + if (right->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (right, top, traps); + if (unlikely (status)) + return status; + } - status = _cairo_bo_edge_start_or_continue_trap (edge, top, bo_traps); - if (status) - return status; + if ((in_out++ & 1) == 0) { + cairo_bo_edge_t *next; + cairo_bool_t skip = FALSE; + + /* skip co-linear edges */ + next = right->next; + if (next != NULL) + skip = edges_colinear (right, next); + + if (! skip) + break; + } + + right = right->next; + } + + status = _cairo_bo_edge_start_or_continue_trap (left, right, + top, traps); + if (unlikely (status)) + return status; + + left = right; + if (left != NULL) + left = left->next; + } } return CAIRO_STATUS_SUCCESS; } + /* Execute a single pass of the Bentley-Ottmann algorithm on edges, * generating trapezoids according to the fill_rule and appending them * to traps. */ static cairo_status_t -_cairo_bentley_ottmann_tessellate_bo_edges (cairo_bo_edge_t *edges, - int num_edges, +_cairo_bentley_ottmann_tessellate_bo_edges (cairo_bo_event_t **start_events, + int num_events, cairo_fill_rule_t fill_rule, cairo_traps_t *traps, - cairo_fixed_t xmin, - cairo_fixed_t ymin, - cairo_fixed_t xmax, - cairo_fixed_t ymax, int *num_intersections) { - cairo_status_t status; + cairo_status_t status = CAIRO_STATUS_SUCCESS; /* silence compiler */ int intersection_count = 0; cairo_bo_event_queue_t event_queue; cairo_bo_sweep_line_t sweep_line; - cairo_bo_traps_t bo_traps; - cairo_bo_event_t *event, event_saved; - cairo_bo_edge_t *edge; + cairo_bo_event_t *event; cairo_bo_edge_t *left, *right; - cairo_bo_edge_t *edge1, *edge2; - - if (num_edges == 0) - return CAIRO_STATUS_SUCCESS; - - status = _cairo_bo_event_queue_init (&event_queue, edges, num_edges); - if (status) - return status; - - _cairo_bo_sweep_line_init (&sweep_line); - - _cairo_bo_traps_init (&bo_traps, traps, xmin, ymin, xmax, ymax); + cairo_bo_edge_t *e1, *e2; -#if DEBUG_PRINT_STATE - print_state ("After initializing", &event_queue, &sweep_line); +#if DEBUG_EVENTS + { + int i; + + for (i = 0; i < num_events; i++) { + cairo_bo_start_event_t *event = + ((cairo_bo_start_event_t **) start_events)[i]; + event_log ("edge: %lu (%d, %d) (%d, %d) (%d, %d) %d\n", + (long) &events[i].edge, + event->edge.edge.line.p1.x, + event->edge.edge.line.p1.y, + event->edge.edge.line.p2.x, + event->edge.edge.line.p2.y, + event->edge.top, + event->edge.bottom, + event->edge.edge.dir); + } + } #endif - while (1) - { - event = _cairo_bo_event_dequeue (&event_queue); - if (!event) - break; + _cairo_bo_event_queue_init (&event_queue, start_events, num_events); + _cairo_bo_sweep_line_init (&sweep_line); + while ((event = _cairo_bo_event_dequeue (&event_queue))) { if (event->point.y != sweep_line.current_y) { + for (e1 = sweep_line.stopped; e1; e1 = e1->next) { + if (e1->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (e1, + e1->edge.bottom, + traps); + if (unlikely (status)) + goto unwind; + } + } + sweep_line.stopped = NULL; + status = _active_edges_to_traps (sweep_line.head, sweep_line.current_y, - fill_rule, &bo_traps); - if (status) + fill_rule, traps); + if (unlikely (status)) goto unwind; sweep_line.current_y = event->point.y; } - event_saved = *event; - _cairo_bo_event_queue_delete (&event_queue, event); - event = &event_saved; +#if DEBUG_EVENTS + event_log ("event: %d (%ld, %ld) %lu, %lu\n", + event->type, + (long) event->point.x, + (long) event->point.y, + (long) event->e1, + (long) event->e2); +#endif switch (event->type) { case CAIRO_BO_EVENT_TYPE_START: - edge = event->e1; + e1 = &((cairo_bo_start_event_t *) event)->edge; + + status = _cairo_bo_sweep_line_insert (&sweep_line, e1); + if (unlikely (status)) + goto unwind; - status = _cairo_bo_sweep_line_insert (&sweep_line, edge); - if (status) + status = _cairo_bo_event_queue_insert_stop (&event_queue, e1); + if (unlikely (status)) goto unwind; - /* Cache the insert position for use in pass 2. - event->e2 = Sortlist::prev (sweep_line, edge); - */ - left = edge->prev; - right = edge->next; + /* check to see if this is a continuation of a stopped edge */ + /* XXX change to an infinitesimal lengthening rule */ + for (left = sweep_line.stopped; left; left = left->next) { + if (e1->edge.top <= left->edge.bottom && + edges_colinear (e1, left)) + { + e1->deferred_trap = left->deferred_trap; + if (left->prev != NULL) + left->prev = left->next; + else + sweep_line.stopped = left->next; + if (left->next != NULL) + left->next->prev = left->prev; + break; + } + } + + left = e1->prev; + right = e1->next; - status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, left, edge); - if (status) + status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, left, e1); + if (unlikely (status)) goto unwind; - status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, edge, right); - if (status) + status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, e1, right); + if (unlikely (status)) goto unwind; -#if DEBUG_PRINT_STATE - print_state ("After processing start", &event_queue, &sweep_line); -#endif break; case CAIRO_BO_EVENT_TYPE_STOP: - edge = event->e1; + e1 = ((cairo_bo_skiplist_event_t *) event)->e1; + _cairo_bo_event_queue_delete (&event_queue, event); - left = edge->prev; - right = edge->next; + left = e1->prev; + right = e1->next; - _cairo_bo_sweep_line_delete (&sweep_line, edge); + _cairo_bo_sweep_line_delete (&sweep_line, e1); - status = _cairo_bo_edge_end_trap (edge, edge->bottom.y, &bo_traps); - if (status) - goto unwind; + /* first, check to see if we have a continuation via a fresh edge */ + if (e1->deferred_trap.right != NULL) { + e1->next = sweep_line.stopped; + if (sweep_line.stopped != NULL) + sweep_line.stopped->prev = e1; + sweep_line.stopped = e1; + e1->prev = NULL; + } status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, left, right); - if (status) + if (unlikely (status)) goto unwind; -#if DEBUG_PRINT_STATE - print_state ("After processing stop", &event_queue, &sweep_line); -#endif break; case CAIRO_BO_EVENT_TYPE_INTERSECTION: - edge1 = event->e1; - edge2 = event->e2; + e1 = ((cairo_bo_skiplist_event_t *) event)->e1; + e2 = ((cairo_bo_skiplist_event_t *) event)->e2; + _cairo_bo_event_queue_delete (&event_queue, event); /* skip this intersection if its edges are not adjacent */ - if (edge2 != edge1->next) + if (e2 != e1->next) break; intersection_count++; - edge1->middle = event->point; - edge2->middle = event->point; + left = e1->prev; + right = e2->next; - left = edge1->prev; - right = edge2->next; - - _cairo_bo_sweep_line_swap (&sweep_line, edge1, edge2); + _cairo_bo_sweep_line_swap (&sweep_line, e1, e2); /* after the swap e2 is left of e1 */ - status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, - left, edge2); - if (status) + status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, left, e2); + if (unlikely (status)) goto unwind; - status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, - edge1, right); - if (status) + status = _cairo_bo_event_queue_insert_if_intersect_below_current_y (&event_queue, e1, right); + if (unlikely (status)) goto unwind; -#if DEBUG_PRINT_STATE - print_state ("After processing intersection", &event_queue, &sweep_line); -#endif break; } #if DEBUG_VALIDATE @@ -1614,29 +1722,22 @@ _cairo_bentley_ottmann_tessellate_bo_edges (cairo_bo_edge_t *edges, } *num_intersections = intersection_count; - unwind: - for (edge = sweep_line.head; edge; edge = edge->next) { - cairo_status_t status2 = _cairo_bo_edge_end_trap (edge, - sweep_line.current_y, - &bo_traps); - if (!status) - status = status2; + for (e1 = sweep_line.stopped; e1; e1 = e1->next) { + if (e1->deferred_trap.right != NULL) { + status = _cairo_bo_edge_end_trap (e1, e1->edge.bottom, traps); + if (unlikely (status)) + break; + } } - _cairo_bo_traps_fini (&bo_traps); + unwind: _cairo_bo_sweep_line_fini (&sweep_line); _cairo_bo_event_queue_fini (&event_queue); - return status; -} -static void -update_minmax(cairo_fixed_t *inout_min, - cairo_fixed_t *inout_max, - cairo_fixed_t v) -{ - if (v < *inout_min) - *inout_min = v; - if (v > *inout_max) - *inout_max = v; +#if DEBUG_EVENTS + event_log ("\n"); +#endif + + return status; } cairo_status_t @@ -1646,116 +1747,332 @@ _cairo_bentley_ottmann_tessellate_polygon (cairo_traps_t *traps, { int intersections; cairo_status_t status; - cairo_bo_edge_t stack_edges[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_edge_t)]; - cairo_bo_edge_t *edges; - cairo_fixed_t xmin = 0x7FFFFFFF; - cairo_fixed_t ymin = 0x7FFFFFFF; - cairo_fixed_t xmax = -0x80000000; - cairo_fixed_t ymax = -0x80000000; - cairo_box_t limit; - cairo_bool_t has_limits; - int num_bo_edges; + cairo_bo_start_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_start_event_t)]; + cairo_bo_start_event_t *events; + cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1]; + cairo_bo_event_t **event_ptrs; + int num_events; int i; - if (0 == polygon->num_edges) + num_events = polygon->num_edges; + if (unlikely (0 == num_events)) return CAIRO_STATUS_SUCCESS; - if (CAIRO_INJECT_FAULT ()) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - - has_limits = _cairo_traps_get_limit (traps, &limit); - - edges = stack_edges; - if (polygon->num_edges > ARRAY_LENGTH (stack_edges)) { - edges = _cairo_malloc_ab (polygon->num_edges, sizeof (cairo_bo_edge_t)); - if (unlikely (edges == NULL)) + events = stack_events; + event_ptrs = stack_event_ptrs; + if (num_events > ARRAY_LENGTH (stack_events)) { + events = _cairo_malloc_ab_plus_c (num_events, + sizeof (cairo_bo_start_event_t) + + sizeof (cairo_bo_event_t *), + sizeof (cairo_bo_event_t *)); + if (unlikely (events == NULL)) return _cairo_error (CAIRO_STATUS_NO_MEMORY); - } - /* Figure out the bounding box of the input coordinates and - * validate that we're not given invalid polygon edges. */ - for (i = 0; i < polygon->num_edges; i++) { - update_minmax (&xmin, &xmax, polygon->edges[i].edge.p1.x); - update_minmax (&ymin, &ymax, polygon->edges[i].edge.p1.y); - update_minmax (&xmin, &xmax, polygon->edges[i].edge.p2.x); - update_minmax (&ymin, &ymax, polygon->edges[i].edge.p2.y); - assert (polygon->edges[i].edge.p1.y <= polygon->edges[i].edge.p2.y && - "BUG: tessellator given upside down or horizontal edges"); + event_ptrs = (cairo_bo_event_t **) (events + num_events); } - /* The tessellation functions currently assume that no line - * segment extends more than 2^31-1 in either dimension. We - * guarantee this by offsetting the internal coordinates to the - * range [0,2^31-1], and clamping to 2^31-1 if a coordinate - * exceeds the range (and yes, this generates an incorrect - * result). First we have to clamp the bounding box itself. */ - /* XXX: Rather than changing the input values, a better approach - * would be to detect out-of-bounds input and return a - * CAIRO_STATUS_OVERFLOW value to the user. */ - if (xmax - xmin < 0) - xmax = xmin + 0x7FFFFFFF; - if (ymax - ymin < 0) - ymax = ymin + 0x7FFFFFFF; - - for (i = 0, num_bo_edges = 0; i < polygon->num_edges; i++) { - cairo_bo_edge_t *edge = &edges[num_bo_edges]; - cairo_point_t top = polygon->edges[i].edge.p1; - cairo_point_t bot = polygon->edges[i].edge.p2; - - /* Discard the edge if it lies outside the limits of traps. */ - if (has_limits) { - /* Strictly above or below the limits? */ - if (bot.y <= limit.p1.y || top.y >= limit.p2.y) - continue; - } + for (i = 0; i < num_events; i++) { + event_ptrs[i] = (cairo_bo_event_t *) &events[i]; - /* Offset coordinates into the non-negative range. */ - top.x -= xmin; - top.y -= ymin; - bot.x -= xmin; - bot.y -= ymin; - - /* If the coordinates are still negative, then their extent is - * overflowing 2^31-1. We're going to kludge it and clamp the - * coordinates into the clamped bounding box. */ - if (top.x < 0) top.x = xmax - xmin; - if (top.y < 0) top.y = ymax - ymin; - if (bot.x < 0) bot.x = xmax - xmin; - if (bot.y < 0) bot.y = ymax - ymin; - - if (top.y == bot.y) { - /* Clamping might have produced horizontal edges. Ignore - * those. */ - continue; - } - assert (top.y < bot.y && - "BUG: clamping the input range flipped the " - "orientation of an edge"); - - edge->top.x = top.x; - edge->top.y = top.y; - edge->bottom.x = bot.x; - edge->bottom.y = bot.y; - edge->dir = polygon->edges[i].dir; - edge->deferred_trap = NULL; - edge->prev = NULL; - edge->next = NULL; - edge->sweep_line_elt = NULL; - - num_bo_edges++; + events[i].type = CAIRO_BO_EVENT_TYPE_START; + events[i].point.y = polygon->edges[i].top; + events[i].point.x = + _line_compute_intersection_x_for_y (&polygon->edges[i].line, + events[i].point.y); + + events[i].edge.edge = polygon->edges[i]; + events[i].edge.deferred_trap.right = NULL; + events[i].edge.prev = NULL; + events[i].edge.next = NULL; + events[i].edge.sweep_line_elt = NULL; } +#if DEBUG_TRAPS + dump_edges (events, num_events, "bo-polygon-edges.txt"); +#endif + /* XXX: This would be the convenient place to throw in multiple * passes of the Bentley-Ottmann algorithm. It would merely * require storing the results of each pass into a temporary * cairo_traps_t. */ - status = _cairo_bentley_ottmann_tessellate_bo_edges (edges, num_bo_edges, + status = _cairo_bentley_ottmann_tessellate_bo_edges (event_ptrs, + num_events, fill_rule, traps, - xmin, ymin, xmax, ymax, &intersections); +#if DEBUG_TRAPS + dump_traps (traps, "bo-polygon-out.txt"); +#endif - if (edges != stack_edges) - free (edges); + if (events != stack_events) + free (events); + + return status; +} + +static cairo_bool_t +_points_outside (const cairo_fixed_t *x, const cairo_box_t *limits) +{ + if (x[0] < limits->p1.x || x[0] > limits->p2.x) + return TRUE; + if (x[1] < limits->p1.x || x[1] > limits->p2.x) + return TRUE; + if (x[2] < limits->p1.x || x[2] > limits->p2.x) + return TRUE; + if (x[3] < limits->p1.x || x[3] > limits->p2.x) + return TRUE; + + return FALSE; +} + +static int +_add_event (const cairo_line_t *line, + int32_t top, int32_t bottom, + int dir, + cairo_bo_start_event_t *event) +{ + if (top == bottom) + return 0; + + event->type = CAIRO_BO_EVENT_TYPE_START; + event->point.y = top; + event->point.x = _line_compute_intersection_x_for_y (line, top); + + event->edge.edge.line = *line; + event->edge.edge.top = top; + event->edge.edge.bottom = bottom; + event->edge.edge.dir = dir; + + event->edge.deferred_trap.right = NULL; + event->edge.prev = NULL; + event->edge.next = NULL; + event->edge.sweep_line_elt = NULL; + + return 1; +} + +static int +_compute_clipped_trapezoid_edges (const cairo_traps_t *traps, + const cairo_trapezoid_t *t, + cairo_bo_start_event_t *events) +{ + cairo_fixed_t x[4]; + int num_events = 0; + int top, bot; + + /* compute points in clockwise orientation */ + top = t->top; + bot = t->bottom; + + x[0] = _line_compute_intersection_x_for_y (&t->left, top); + x[1] = _line_compute_intersection_x_for_y (&t->right, top); + x[2] = _line_compute_intersection_x_for_y (&t->right, bot); + x[3] = _line_compute_intersection_x_for_y (&t->left, bot); + + if (traps->has_limits && _points_outside (x, &traps->limits)) { + cairo_line_t limits[2]; + cairo_fixed_t ly[2], ry[2]; + cairo_fixed_t ymin, ymax; + + limits[0].p1.x = traps->limits.p1.x; + limits[0].p1.y = traps->limits.p1.y; + limits[0].p2.x = traps->limits.p1.x; + limits[0].p2.y = traps->limits.p2.y; + + limits[1].p1.x = traps->limits.p2.x; + limits[1].p1.y = traps->limits.p1.y; + limits[1].p2.x = traps->limits.p2.x; + limits[1].p2.y = traps->limits.p2.y; + + ly[0] = _line_compute_intersection_y_for_x (&t->left, + traps->limits.p1.x); + ymin = ymax = ly[0]; + + ly[1] = _line_compute_intersection_y_for_x (&t->left, + traps->limits.p2.x); + if (ly[1] < ymin) + ymin = ly[1]; + if (ly[1] > ymax) + ymax = ly[1]; + + ry[0] = _line_compute_intersection_y_for_x (&t->right, + traps->limits.p1.x); + if (ry[0] < ymin) + ymin = ry[0]; + if (ry[0] > ymax) + ymax = ry[0]; + + ry[1] = _line_compute_intersection_y_for_x (&t->right, + traps->limits.p2.x); + if (ry[1] < ymin) + ymin = ry[1]; + if (ry[1] > ymax) + ymax = ry[1]; + + if (ymin > top) + top = ymin; + if (ymax < bot) + bot = ymax; + if (top >= bot) + return 0; + + /* left hand side intersects */ + + if (x[0] <= traps->limits.p1.x && x[3] <= traps->limits.p1.x) + { + num_events += _add_event (&limits[0], top, bot, 1, + events + num_events); + } + else if (x[0] >= traps->limits.p1.x && x[3] >= traps->limits.p1.x && + x[0] <= traps->limits.p2.x && x[3] <= traps->limits.p2.x) + { + num_events += _add_event (&t->left, top, bot, 1, + events + num_events); + } + else + { + if (ly[0] < ly[1]) { + if (ly[1] >= top) { + if (ly[0] < top) + ly[0] = top; + if (ly[1] > bot) + ly[1] = bot; + num_events += _add_event (&limits[0], top, ly[0], 1, + events + num_events); + num_events += _add_event (&t->left, ly[0], ly[1], 1, + events + num_events); + num_events += _add_event (&limits[1], ly[1], bot, 1, + events + num_events); + } + } else { + if (ly[1] <= bot) { + if (ly[1] < top) + ly[1] = top; + if (ly[0] > bot) + ly[0] = bot; + num_events += _add_event (&limits[1], top, ly[1], 1, + events + num_events); + num_events += _add_event (&t->left, ly[1], ly[0], 1, + events + num_events); + num_events += _add_event (&limits[0], ly[0], bot, 1, + events + num_events); + } + } + } + + /* right hand side intersects */ + + if (x[1] >= traps->limits.p2.x && x[2] >= traps->limits.p2.x) + { + num_events += _add_event (&limits[1], top, bot, -1, + events + num_events); + } + else if (x[1] <= traps->limits.p2.x && x[2] <= traps->limits.p2.x && + x[1] >= traps->limits.p1.x && x[2] >= traps->limits.p1.x) + { + num_events += _add_event (&t->right, top, bot, -1, + events + num_events); + } + else + { + if (ry[0] < ry[1]) { + if (ry[0] <= bot) { + if (ry[0] < top) + ry[0] = top; + if (ry[1] > bot) + ry[1] = bot; + num_events += _add_event (&limits[0], top, ry[0], -1, + events + num_events); + num_events += _add_event (&t->right, ry[0], ry[1], -1, + events + num_events); + num_events += _add_event (&limits[1], ry[1], bot, -1, + events + num_events); + } + } else { + if (ry[0] >= top) { + if (ry[0] > bot) + ry[0] = bot; + if (ry[1] < top) + ry[1] = top; + num_events += _add_event (&limits[1], top, ry[1], -1, + events + num_events); + num_events += _add_event (&t->right, ry[1], ry[0], -1, + events + num_events); + num_events += _add_event (&limits[0], ry[0], bot, -1, + events + num_events); + } + } + } + } else { + num_events += _add_event (&t->left, top, bot, 1, events + num_events); + num_events += _add_event (&t->right, top, bot, -1, events + num_events); + } + + return num_events; +} + +cairo_status_t +_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps) +{ + int intersections; + cairo_status_t status; + cairo_bo_start_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_start_event_t)]; + cairo_bo_start_event_t *events; + cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1]; + cairo_bo_event_t **event_ptrs; + int num_events; + int i; + + if (unlikely (0 == traps->num_traps)) + return CAIRO_STATUS_SUCCESS; + +#if DEBUG_TRAPS + dump_traps (traps, "bo-traps-in.txt"); +#endif + + /* we need at most 6 vertical edges to describe each clipped trapezoid */ + i = 6 * traps->num_traps; + + events = stack_events; + event_ptrs = stack_event_ptrs; + if (i > ARRAY_LENGTH (stack_events)) { + events = _cairo_malloc_ab_plus_c (i, + sizeof (cairo_bo_start_event_t) + + sizeof (cairo_bo_event_t *), + sizeof (cairo_bo_event_t *)); + if (unlikely (events == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + event_ptrs = (cairo_bo_event_t **) (events + i); + } + + num_events = 0; + for (i = 0; i < traps->num_traps; i++) { + num_events += _compute_clipped_trapezoid_edges (traps, + &traps->traps[i], + &events[num_events]); + } + + for (i = 0; i < num_events; i++) + event_ptrs[i] = (cairo_bo_event_t *) &events[i]; + + +#if DEBUG_TRAPS + dump_edges (events, num_events, "bo-traps-edges.txt"); +#endif + + _cairo_traps_clear (traps); + status = _cairo_bentley_ottmann_tessellate_bo_edges (event_ptrs, + num_events, + CAIRO_FILL_RULE_WINDING, + traps, + &intersections); + +#if DEBUG_TRAPS + dump_traps (traps, "bo-traps-out.txt"); +#endif + + if (events != stack_events) + free (events); return status; } @@ -1769,15 +2086,14 @@ edges_have_an_intersection_quadratic (cairo_bo_edge_t *edges, int i, j; cairo_bo_edge_t *a, *b; cairo_bo_point32_t intersection; - cairo_bo_status_t status; /* We must not be given any upside-down edges. */ for (i = 0; i < num_edges; i++) { assert (_cairo_bo_point32_compare (&edges[i].top, &edges[i].bottom) < 0); - edges[i].top.x <<= CAIRO_BO_GUARD_BITS; - edges[i].top.y <<= CAIRO_BO_GUARD_BITS; - edges[i].bottom.x <<= CAIRO_BO_GUARD_BITS; - edges[i].bottom.y <<= CAIRO_BO_GUARD_BITS; + edges[i].line.p1.x <<= CAIRO_BO_GUARD_BITS; + edges[i].line.p1.y <<= CAIRO_BO_GUARD_BITS; + edges[i].line.p2.x <<= CAIRO_BO_GUARD_BITS; + edges[i].line.p2.y <<= CAIRO_BO_GUARD_BITS; } for (i = 0; i < num_edges; i++) { @@ -1788,20 +2104,16 @@ edges_have_an_intersection_quadratic (cairo_bo_edge_t *edges, a = &edges[i]; b = &edges[j]; - status = _cairo_bo_edge_intersect (a, b, &intersection); - if (status == CAIRO_BO_STATUS_PARALLEL || - status == CAIRO_BO_STATUS_NO_INTERSECTION) - { + if (! _cairo_bo_edge_intersect (a, b, &intersection)) continue; - } printf ("Found intersection (%d,%d) between (%d,%d)-(%d,%d) and (%d,%d)-(%d,%d)\n", intersection.x, intersection.y, - a->top.x, a->top.y, - a->bottom.x, a->bottom.y, - b->top.x, b->top.y, - b->bottom.x, b->bottom.y); + a->line.p1.x, a->line.p1.y, + a->line.p2.x, a->line.p2.y, + b->line.p1.x, b->line.p1.y, + b->line.p2.x, b->line.p2.y); return TRUE; } @@ -2041,16 +2353,16 @@ main (void) for (i = 0; i < num_random; i++) { do { edge = &random_edges[i]; - edge->top.x = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); - edge->top.y = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); - edge->bottom.x = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); - edge->bottom.y = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); - if (edge->top.y > edge->bottom.y) { - int32_t tmp = edge->top.y; - edge->top.y = edge->bottom.y; - edge->bottom.y = tmp; + edge->line.p1.x = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); + edge->line.p1.y = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); + edge->line.p2.x = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); + edge->line.p2.y = (int32_t) (10.0 * (rand() / (RAND_MAX + 1.0))); + if (edge->line.p1.y > edge->line.p2.y) { + int32_t tmp = edge->line.p1.y; + edge->line.p1.y = edge->line.p2.y; + edge->line.p2.y = tmp; } - } while (edge->top.y == edge->bottom.y); + } while (edge->line.p1.y == edge->line.p2.y); } sprintf (random_name, "random-%02d", num_random); @@ -2061,4 +2373,3 @@ main (void) return 0; } #endif - diff --git a/src/cairo-clip.c b/src/cairo-clip.c index 93348945..c26f4fa2 100644 --- a/src/cairo-clip.c +++ b/src/cairo-clip.c @@ -588,17 +588,19 @@ _cairo_clip_path_to_region (cairo_clip_path_t *clip_path) _cairo_box_from_rectangle (&box, &clip_path->extents); _cairo_traps_limit (&traps, &box); - status = _cairo_path_fixed_fill_to_traps (&clip_path->path, - clip_path->fill_rule, - clip_path->tolerance, - &traps); - if (unlikely (status)) + status = _cairo_path_fixed_fill_rectilinear_to_traps (&clip_path->path, + clip_path->fill_rule, + &traps); + if (status) { + _cairo_traps_fini (&traps); + clip_path->flags |= CAIRO_CLIP_PATH_REGION_IS_UNSUPPORTED; return status; + } status = _cairo_traps_extract_region (&traps, &clip_path->region); _cairo_traps_fini (&traps); - if (status) { + if (unlikely (status)) { clip_path->flags |= CAIRO_CLIP_PATH_REGION_IS_UNSUPPORTED; return status; } diff --git a/src/cairo-fixed-private.h b/src/cairo-fixed-private.h index 630f0327..e3add4ae 100644 --- a/src/cairo-fixed-private.h +++ b/src/cairo-fixed-private.h @@ -226,15 +226,61 @@ _cairo_fixed_mul (cairo_fixed_t a, cairo_fixed_t b) return _cairo_int64_to_int32(_cairo_int64_rsl (temp, CAIRO_FIXED_FRAC_BITS)); } -/* computes a * b / c */ +/* computes round (a * b / c) */ static inline cairo_fixed_t _cairo_fixed_mul_div (cairo_fixed_t a, cairo_fixed_t b, cairo_fixed_t c) { cairo_int64_t ab = _cairo_int32x32_64_mul (a, b); cairo_int64_t c64 = _cairo_int32_to_int64 (c); - cairo_int64_t quo = _cairo_int64_divrem (ab, c64).quo; + return _cairo_int64_to_int32 (_cairo_int64_divrem (ab, c64).quo); +} + +/* computes floor (a * b / c) */ +static inline cairo_fixed_t +_cairo_fixed_mul_div_floor (cairo_fixed_t a, cairo_fixed_t b, cairo_fixed_t c) +{ + return _cairo_int64_32_div (_cairo_int32x32_64_mul (a, b), c); +} + + +static inline cairo_fixed_t +_cairo_edge_compute_intersection_y_for_x (const cairo_point_t *p1, + const cairo_point_t *p2, + cairo_fixed_t x) +{ + cairo_fixed_t y, dx; + + if (x == p1->x) + return p1->y; + if (x == p2->x) + return p2->y; - return _cairo_int64_to_int32(quo); + y = p1->y; + dx = p2->x - p1->x; + if (dx != 0) + y += _cairo_fixed_mul_div_floor (x - p1->x, p2->y - p1->y, dx); + + return y; +} + +static inline cairo_fixed_t +_cairo_edge_compute_intersection_x_for_y (const cairo_point_t *p1, + const cairo_point_t *p2, + cairo_fixed_t y) +{ + cairo_fixed_t x, dy; + + if (y == p1->y) + return p1->x; + if (y == p2->y) + return p2->x; + + x = p1->x; + dy = p2->y - p1->y; + if (dy != 0) + x += _cairo_fixed_mul_div_floor (y - p1->y, p2->x - p1->x, dy); + + return x; } #else diff --git a/src/cairo-fixed-type-private.h b/src/cairo-fixed-type-private.h index 7dd57970..730ed3e1 100644 --- a/src/cairo-fixed-type-private.h +++ b/src/cairo-fixed-type-private.h @@ -67,4 +67,9 @@ typedef int32_t cairo_fixed_t; /* An unsigned type of the same size as #cairo_fixed_t */ typedef uint32_t cairo_fixed_unsigned_t; +typedef struct _cairo_point { + cairo_fixed_t x; + cairo_fixed_t y; +} cairo_point_t; + #endif /* CAIRO_FIXED_TYPE_PRIVATE_H */ diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c index 2257db0f..3508b5e9 100644 --- a/src/cairo-gstate.c +++ b/src/cairo-gstate.c @@ -1184,7 +1184,7 @@ _cairo_gstate_stroke_extents (cairo_gstate_t *gstate, &gstate->ctm_inverse, gstate->tolerance, &traps); - if (status == CAIRO_STATUS_SUCCESS) { + if (likely (status == CAIRO_STATUS_SUCCESS)) { _cairo_gstate_traps_extents_to_user_rectangle (gstate, &traps, x1, y1, x2, y2); } @@ -1209,7 +1209,7 @@ _cairo_gstate_fill_extents (cairo_gstate_t *gstate, gstate->fill_rule, gstate->tolerance, &traps); - if (status == CAIRO_STATUS_SUCCESS) { + if (likely (status == CAIRO_STATUS_SUCCESS)) { _cairo_gstate_traps_extents_to_user_rectangle (gstate, &traps, x1, y1, x2, y2); } diff --git a/src/cairo-hash.c b/src/cairo-hash.c index 51303f5a..15159d92 100644 --- a/src/cairo-hash.c +++ b/src/cairo-hash.c @@ -191,9 +191,6 @@ _cairo_hash_table_create (cairo_hash_keys_equal_func_t keys_equal) void _cairo_hash_table_destroy (cairo_hash_table_t *hash_table) { - if (hash_table == NULL) - return; - /* The hash table must be empty. Otherwise, halt. */ assert (hash_table->live_entries == 0); /* No iterators can be running. Otherwise, halt. */ @@ -525,9 +522,6 @@ _cairo_hash_table_foreach (cairo_hash_table_t *hash_table, unsigned long i; cairo_hash_entry_t *entry; - if (hash_table == NULL) - return; - /* Mark the table for iteration */ ++hash_table->iterating; for (i = 0; i < hash_table->arrangement->size; i++) { diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c index 6c83388e..94ed0e91 100644 --- a/src/cairo-matrix.c +++ b/src/cairo-matrix.c @@ -688,16 +688,9 @@ _cairo_matrix_is_integer_translation (const cairo_matrix_t *matrix, return FALSE; } -/* By pixel exact here, we mean a matrix that is composed only of - * 90 degree rotations, flips, and integer translations and produces a 1:1 - * mapping between source and destination pixels. If we transform an image - * with a pixel-exact matrix, filtering is not useful. - */ -cairo_private cairo_bool_t -_cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) +cairo_bool_t +_cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix) { - cairo_fixed_t x0_fixed, y0_fixed; - if (matrix->xy == 0.0 && matrix->yx == 0.0) { if (! (matrix->xx == 1.0 || matrix->xx == -1.0)) return FALSE; @@ -711,6 +704,22 @@ _cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) } else return FALSE; + return TRUE; +} + +/* By pixel exact here, we mean a matrix that is composed only of + * 90 degree rotations, flips, and integer translations and produces a 1:1 + * mapping between source and destination pixels. If we transform an image + * with a pixel-exact matrix, filtering is not useful. + */ +cairo_bool_t +_cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) +{ + cairo_fixed_t x0_fixed, y0_fixed; + + if (! _cairo_matrix_has_unity_scale (matrix)) + return FALSE; + x0_fixed = _cairo_fixed_from_double (matrix->x0); y0_fixed = _cairo_fixed_from_double (matrix->y0); diff --git a/src/cairo-path-fill.c b/src/cairo-path-fill.c index 123da96e..2b5c4c8e 100644 --- a/src/cairo-path-fill.c +++ b/src/cairo-path-fill.c @@ -39,29 +39,25 @@ typedef struct cairo_filler { double tolerance; - cairo_traps_t *traps; - cairo_point_t current_point; - - cairo_polygon_t polygon; + cairo_polygon_t *polygon; } cairo_filler_t; static void -_cairo_filler_init (cairo_filler_t *filler, double tolerance, cairo_traps_t *traps) +_cairo_filler_init (cairo_filler_t *filler, + double tolerance, + cairo_polygon_t *polygon) { filler->tolerance = tolerance; - filler->traps = traps; + filler->polygon = polygon; filler->current_point.x = 0; filler->current_point.y = 0; - - _cairo_polygon_init (&filler->polygon); } static void _cairo_filler_fini (cairo_filler_t *filler) { - _cairo_polygon_fini (&filler->polygon); } static cairo_status_t @@ -69,14 +65,14 @@ _cairo_filler_move_to (void *closure, const cairo_point_t *point) { cairo_filler_t *filler = closure; - cairo_polygon_t *polygon = &filler->polygon; + cairo_polygon_t *polygon = filler->polygon; _cairo_polygon_close (polygon); _cairo_polygon_move_to (polygon, point); filler->current_point = *point; - return _cairo_polygon_status (&filler->polygon); + return _cairo_polygon_status (filler->polygon); } static cairo_status_t @@ -84,13 +80,13 @@ _cairo_filler_line_to (void *closure, const cairo_point_t *point) { cairo_filler_t *filler = closure; - cairo_polygon_t *polygon = &filler->polygon; + cairo_polygon_t *polygon = filler->polygon; _cairo_polygon_line_to (polygon, point); filler->current_point = *point; - return _cairo_polygon_status (&filler->polygon); + return _cairo_polygon_status (filler->polygon); } static cairo_status_t @@ -103,11 +99,10 @@ _cairo_filler_curve_to (void *closure, cairo_spline_t spline; if (! _cairo_spline_init (&spline, - _cairo_filler_line_to, - filler, + _cairo_filler_line_to, filler, &filler->current_point, b, c, d)) { - return CAIRO_STATUS_SUCCESS; + return _cairo_filler_line_to (closure, d); } return _cairo_spline_decompose (&spline, filler->tolerance); @@ -117,36 +112,22 @@ static cairo_status_t _cairo_filler_close_path (void *closure) { cairo_filler_t *filler = closure; - cairo_polygon_t *polygon = &filler->polygon; + cairo_polygon_t *polygon = filler->polygon; _cairo_polygon_close (polygon); return _cairo_polygon_status (polygon); } -static cairo_int_status_t -_cairo_path_fixed_fill_rectangle (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_traps_t *traps); - cairo_status_t -_cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - double tolerance, - cairo_traps_t *traps) +_cairo_path_fixed_fill_to_polygon (const cairo_path_fixed_t *path, + double tolerance, + cairo_polygon_t *polygon) { - cairo_status_t status = CAIRO_STATUS_SUCCESS; cairo_filler_t filler; + cairo_status_t status; - traps->maybe_region = path->maybe_fill_region; - - /* Before we do anything else, we use a special-case filler for - * a device-axis aligned rectangle if possible. */ - status = _cairo_path_fixed_fill_rectangle (path, fill_rule, traps); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - return status; - - _cairo_filler_init (&filler, tolerance, traps); + _cairo_filler_init (&filler, tolerance, polygon); status = _cairo_path_fixed_interpret (path, CAIRO_DIRECTION_FORWARD, @@ -156,25 +137,48 @@ _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, _cairo_filler_close_path, &filler); if (unlikely (status)) - goto BAIL; + return status; - _cairo_polygon_close (&filler.polygon); - status = _cairo_polygon_status (&filler.polygon); - if (unlikely (status)) - goto BAIL; + _cairo_polygon_close (polygon); + status = _cairo_polygon_status (polygon); + _cairo_filler_fini (&filler); - status = _cairo_bentley_ottmann_tessellate_polygon (filler.traps, - &filler.polygon, - fill_rule); + return status; +} + +cairo_status_t +_cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_traps_t *traps) +{ + cairo_polygon_t polygon; + cairo_status_t status; + + if (path->is_rectilinear) { + status = _cairo_path_fixed_fill_rectilinear_to_traps (path, + fill_rule, + traps); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + } + + _cairo_polygon_init (&polygon); + status = _cairo_path_fixed_fill_to_polygon (path, + tolerance, + &polygon); if (unlikely (status)) - goto BAIL; + goto CLEANUP; -BAIL: - _cairo_filler_fini (&filler); + status = _cairo_bentley_ottmann_tessellate_polygon (traps, &polygon, + fill_rule); + CLEANUP: + _cairo_polygon_fini (&polygon); return status; } + /* This special-case filler supports only a path that describes a * device-axis aligned rectangle. It exists to avoid the overhead of * the general tessellator when drawing very common rectangles. @@ -182,15 +186,14 @@ BAIL: * If the path described anything but a device-axis aligned rectangle, * this function will return %CAIRO_INT_STATUS_UNSUPPORTED. */ -static cairo_int_status_t -_cairo_path_fixed_fill_rectangle (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_traps_t *traps) +cairo_int_status_t +_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + cairo_traps_t *traps) { cairo_box_t box; - if (! path->is_rectilinear) - return CAIRO_INT_STATUS_UNSUPPORTED; + assert (path->is_rectilinear); if (_cairo_path_fixed_is_box (path, &box)) { if (box.p1.x > box.p2.x) { diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c index 4fa4ce5a..208e3903 100644 --- a/src/cairo-path-stroke.c +++ b/src/cairo-path-stroke.c @@ -33,6 +33,7 @@ * * Contributor(s): * Carl D. Worth <cworth@cworth.org> + * Chris Wilson <chris@chris-wilson.co.uk> */ #define _BSD_SOURCE /* for hypot() */ @@ -60,7 +61,18 @@ typedef struct cairo_stroker { double ctm_determinant; cairo_bool_t ctm_det_positive; - cairo_traps_t *traps; + void *closure; + cairo_status_t (*add_external_edge) (void *closure, + const cairo_point_t *p1, + const cairo_point_t *p2); + cairo_status_t (*add_triangle) (void *closure, + const cairo_point_t triangle[3]); + cairo_status_t (*add_triangle_fan) (void *closure, + const cairo_point_t *midpt, + const cairo_point_t *points, + int npoints); + cairo_status_t (*add_convex_quad) (void *closure, + const cairo_point_t quad[4]); cairo_pen_t pen; @@ -141,8 +153,7 @@ _cairo_stroker_init (cairo_stroker_t *stroker, cairo_stroke_style_t *stroke_style, const cairo_matrix_t *ctm, const cairo_matrix_t *ctm_inverse, - double tolerance, - cairo_traps_t *traps) + double tolerance) { cairo_status_t status; @@ -150,7 +161,6 @@ _cairo_stroker_init (cairo_stroker_t *stroker, stroker->ctm = ctm; stroker->ctm_inverse = ctm_inverse; stroker->tolerance = tolerance; - stroker->traps = traps; stroker->ctm_determinant = _cairo_matrix_compute_determinant (stroker->ctm); stroker->ctm_det_positive = stroker->ctm_determinant >= 0.0; @@ -161,35 +171,45 @@ _cairo_stroker_init (cairo_stroker_t *stroker, if (unlikely (status)) return status; + stroker->has_bounds = FALSE; + stroker->has_current_face = FALSE; stroker->has_first_face = FALSE; stroker->has_initial_sub_path = FALSE; _cairo_stroker_dash_init (&stroker->dash, stroke_style); - stroker->has_bounds = _cairo_traps_get_limit (traps, &stroker->bounds); - if (stroker->has_bounds) { - /* Extend the bounds in each direction to account for the maximum area - * we might generate trapezoids, to capture line segments that are outside - * of the bounds but which might generate rendering that's within bounds. - */ - double dx, dy; - cairo_fixed_t fdx, fdy; + stroker->add_external_edge = NULL; - _cairo_stroke_style_max_distance_from_path (stroker->style, - stroker->ctm, - &dx, &dy); + return CAIRO_STATUS_SUCCESS; +} - fdx = _cairo_fixed_from_double (dx); - stroker->bounds.p1.x -= fdx; - stroker->bounds.p2.x += fdx; +static void +_cairo_stroker_limit (cairo_stroker_t *stroker, + const cairo_box_t *box) +{ + double dx, dy; + cairo_fixed_t fdx, fdy; - fdy = _cairo_fixed_from_double (dy); - stroker->bounds.p1.y -= fdy; - stroker->bounds.p2.y += fdy; - } + stroker->has_bounds = TRUE; + stroker->bounds = *box; - return CAIRO_STATUS_SUCCESS; + /* Extend the bounds in each direction to account for the maximum area + * we might generate trapezoids, to capture line segments that are outside + * of the bounds but which might generate rendering that's within bounds. + */ + + _cairo_stroke_style_max_distance_from_path (stroker->style, stroker->ctm, + &dx, &dy); + + fdx = _cairo_fixed_from_double (dx); + fdy = _cairo_fixed_from_double (dy); + + stroker->bounds.p1.x -= fdx; + stroker->bounds.p2.x += fdx; + + stroker->bounds.p1.y -= fdy; + stroker->bounds.p2.y += fdy; } static void @@ -199,14 +219,15 @@ _cairo_stroker_fini (cairo_stroker_t *stroker) } static void -_translate_point (cairo_point_t *point, cairo_point_t *offset) +_translate_point (cairo_point_t *point, const cairo_point_t *offset) { point->x += offset->x; point->y += offset->y; } static int -_cairo_stroker_face_clockwise (cairo_stroke_face_t *in, cairo_stroke_face_t *out) +_cairo_stroker_join_is_clockwise (const cairo_stroke_face_t *in, + const cairo_stroke_face_t *out) { cairo_slope_t in_slope, out_slope; @@ -232,76 +253,209 @@ _cairo_slope_compare_sgn (double dx1, double dy1, double dx2, double dy2) return 0; } +static inline int +_range_step (int i, int step, int max) +{ + i += step; + if (i < 0) + i = max - 1; + if (i >= max) + i = 0; + return i; +} + +/* + * Construct a fan around the midpoint using the vertices from pen between + * inpt and outpt. + */ static cairo_status_t -_cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_stroke_face_t *out) +_tessellate_fan (cairo_stroker_t *stroker, + const cairo_slope_t *in_vector, + const cairo_slope_t *out_vector, + const cairo_point_t *midpt, + const cairo_point_t *inpt, + const cairo_point_t *outpt, + cairo_bool_t clockwise) { - int clockwise = _cairo_stroker_face_clockwise (out, in); - cairo_point_t *inpt, *outpt; + cairo_point_t stack_points[64], *points = stack_points; + int start, stop, step, i, npoints; cairo_status_t status; - if (in->cw.x == out->cw.x - && in->cw.y == out->cw.y - && in->ccw.x == out->ccw.x - && in->ccw.y == out->ccw.y) + if (clockwise) { + step = -1; + + start = _cairo_pen_find_active_ccw_vertex_index (&stroker->pen, + in_vector); + if (_cairo_slope_compare (&stroker->pen.vertices[start].slope_ccw, + in_vector) < 0) + start = _range_step (start, -1, stroker->pen.num_vertices); + + stop = _cairo_pen_find_active_ccw_vertex_index (&stroker->pen, + out_vector); + if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_cw, + out_vector) > 0) + { + stop = _range_step (stop, 1, stroker->pen.num_vertices); + if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_ccw, + in_vector) < 0) + { + goto BEVEL; + } + } + + npoints = start - stop; + } else { + step = 1; + + start = _cairo_pen_find_active_cw_vertex_index (&stroker->pen, + in_vector); + if (_cairo_slope_compare (&stroker->pen.vertices[start].slope_cw, + in_vector) < 0) + start = _range_step (start, 1, stroker->pen.num_vertices); + + stop = _cairo_pen_find_active_cw_vertex_index (&stroker->pen, + out_vector); + if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_ccw, + out_vector) > 0) + { + stop = _range_step (stop, -1, stroker->pen.num_vertices); + if (_cairo_slope_compare (&stroker->pen.vertices[stop].slope_cw, + in_vector) < 0) + { + goto BEVEL; + } + } + + npoints = stop - start; + } + stop = _range_step (stop, step, stroker->pen.num_vertices); + + if (npoints < 0) + npoints += stroker->pen.num_vertices; + npoints += 2; + + if (npoints <= 1) + goto BEVEL; + + if (npoints > ARRAY_LENGTH (stack_points)) { + points = _cairo_malloc_ab (npoints, sizeof (cairo_point_t)); + if (unlikely (points == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + + /* Construct the fan. */ + npoints = 0; + points[npoints++] = *inpt; + for (i = start; + i != stop; + i = _range_step (i, step, stroker->pen.num_vertices)) + { + points[npoints] = *midpt; + _translate_point (&points[npoints], &stroker->pen.vertices[i].point); + npoints++; + } + points[npoints++] = *outpt; + + if (stroker->add_external_edge != NULL) { + for (i = 0; i < npoints - 1; i++) { + if (clockwise) { + status = stroker->add_external_edge (stroker->closure, + &points[i], &points[i+1]); + } else { + status = stroker->add_external_edge (stroker->closure, + &points[i+1], &points[i]); + } + if (unlikely (status)) + break; + } + } else { + status = stroker->add_triangle_fan (stroker->closure, + midpt, points, npoints); + } + + if (points != stack_points) + free (points); + + return status; + +BEVEL: + /* Ensure a leak free connection... */ + if (stroker->add_external_edge != NULL) { + if (clockwise) + return stroker->add_external_edge (stroker->closure, inpt, outpt); + else + return stroker->add_external_edge (stroker->closure, outpt, inpt); + } else { + stack_points[0] = *midpt; + stack_points[1] = *inpt; + stack_points[2] = *outpt; + return stroker->add_triangle (stroker->closure, stack_points); + } +} + +static cairo_status_t +_cairo_stroker_join (cairo_stroker_t *stroker, + const cairo_stroke_face_t *in, + const cairo_stroke_face_t *out) +{ + int clockwise = _cairo_stroker_join_is_clockwise (out, in); + const cairo_point_t *inpt, *outpt; + cairo_point_t points[4]; + cairo_status_t status; + + if (in->cw.x == out->cw.x && in->cw.y == out->cw.y && + in->ccw.x == out->ccw.x && in->ccw.y == out->ccw.y) { return CAIRO_STATUS_SUCCESS; } if (clockwise) { + if (stroker->add_external_edge != NULL) { + status = stroker->add_external_edge (stroker->closure, + &out->cw, &in->point); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &in->point, &in->cw); + if (unlikely (status)) + return status; + } + inpt = &in->ccw; outpt = &out->ccw; } else { - inpt = &in->cw; - outpt = &out->cw; - } - - switch (stroker->style->line_join) { - case CAIRO_LINE_JOIN_ROUND: { - int i; - int start, step, stop; - cairo_point_t tri[3]; - cairo_pen_t *pen = &stroker->pen; - - tri[0] = in->point; - if (clockwise) { - start = - _cairo_pen_find_active_ccw_vertex_index (pen, &in->dev_vector); - stop = - _cairo_pen_find_active_ccw_vertex_index (pen, &out->dev_vector); - step = -1; - } else { - start = - _cairo_pen_find_active_cw_vertex_index (pen, &in->dev_vector); - stop = - _cairo_pen_find_active_cw_vertex_index (pen, &out->dev_vector); - step = +1; - } + if (stroker->add_external_edge != NULL) { + status = stroker->add_external_edge (stroker->closure, + &in->ccw, &in->point); + if (unlikely (status)) + return status; - i = start; - tri[1] = *inpt; - while (i != stop) { - tri[2] = in->point; - _translate_point (&tri[2], &pen->vertices[i].point); - status = _cairo_traps_tessellate_triangle (stroker->traps, tri); + status = stroker->add_external_edge (stroker->closure, + &in->point, &out->ccw); if (unlikely (status)) return status; - tri[1] = tri[2]; - i += step; - if (i < 0) - i = pen->num_vertices - 1; - if (i >= pen->num_vertices) - i = 0; } - tri[2] = *outpt; - - return _cairo_traps_tessellate_triangle (stroker->traps, tri); + inpt = &in->cw; + outpt = &out->cw; } + + switch (stroker->style->line_join) { + case CAIRO_LINE_JOIN_ROUND: + /* construct a fan around the common midpoint */ + return _tessellate_fan (stroker, + &in->dev_vector, + &out->dev_vector, + &in->point, inpt, outpt, + clockwise); + case CAIRO_LINE_JOIN_MITER: default: { /* dot product of incoming slope vector with outgoing slope vector */ - double in_dot_out = ((-in->usr_vector.x * out->usr_vector.x)+ - (-in->usr_vector.y * out->usr_vector.y)); + double in_dot_out = -in->usr_vector.x * out->usr_vector.x + + -in->usr_vector.y * out->usr_vector.y; double ml = stroker->style->miter_limit; /* Check the miter limit -- lines meeting at an acute angle @@ -361,13 +515,10 @@ _cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_st * 2 <= ml² (1 - in · out) * */ - if (2 <= ml * ml * (1 - in_dot_out)) - { + if (2 <= ml * ml * (1 - in_dot_out)) { double x1, y1, x2, y2; double mx, my; double dx1, dx2, dy1, dy2; - cairo_point_t outer; - cairo_point_t quad[4]; double ix, iy; double fdx1, fdy1, fdx2, fdy2; double mdx, mdy; @@ -435,74 +586,90 @@ _cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_st if (_cairo_slope_compare_sgn (fdx1, fdy1, mdx, mdy) != _cairo_slope_compare_sgn (fdx2, fdy2, mdx, mdy)) { - /* - * Draw the quadrilateral - */ - outer.x = _cairo_fixed_from_double (mx); - outer.y = _cairo_fixed_from_double (my); - - quad[0] = in->point; - quad[1] = *inpt; - quad[2] = outer; - quad[3] = *outpt; - - return _cairo_traps_tessellate_convex_quad (stroker->traps, quad); + if (stroker->add_external_edge != NULL) { + points[0].x = _cairo_fixed_from_double (mx); + points[0].y = _cairo_fixed_from_double (my); + + if (clockwise) { + status = stroker->add_external_edge (stroker->closure, + inpt, &points[0]); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &points[0], outpt); + if (unlikely (status)) + return status; + } else { + status = stroker->add_external_edge (stroker->closure, + outpt, &points[0]); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &points[0], inpt); + if (unlikely (status)) + return status; + } + + return CAIRO_STATUS_SUCCESS; + } else { + points[0] = in->point; + points[1] = *inpt; + points[2].x = _cairo_fixed_from_double (mx); + points[2].y = _cairo_fixed_from_double (my); + points[3] = *outpt; + + return stroker->add_convex_quad (stroker->closure, points); + } } } - /* fall through ... */ } - case CAIRO_LINE_JOIN_BEVEL: { - cairo_point_t tri[3]; - tri[0] = in->point; - tri[1] = *inpt; - tri[2] = *outpt; - return _cairo_traps_tessellate_triangle (stroker->traps, tri); - } + /* fall through ... */ + + case CAIRO_LINE_JOIN_BEVEL: + if (stroker->add_external_edge != NULL) { + if (clockwise) { + return stroker->add_external_edge (stroker->closure, + inpt, outpt); + } else { + return stroker->add_external_edge (stroker->closure, + outpt, inpt); + } + } else { + points[0] = in->point; + points[1] = *inpt; + points[2] = *outpt; + + return stroker->add_triangle (stroker->closure, points); + } } } static cairo_status_t -_cairo_stroker_add_cap (cairo_stroker_t *stroker, cairo_stroke_face_t *f) +_cairo_stroker_add_cap (cairo_stroker_t *stroker, + const cairo_stroke_face_t *f) { - cairo_status_t status; - - if (stroker->style->line_cap == CAIRO_LINE_CAP_BUTT) - return CAIRO_STATUS_SUCCESS; - switch (stroker->style->line_cap) { case CAIRO_LINE_CAP_ROUND: { - int i; - int start, stop; cairo_slope_t slope; - cairo_point_t tri[3]; - cairo_pen_t *pen = &stroker->pen; - - slope = f->dev_vector; - start = _cairo_pen_find_active_cw_vertex_index (pen, &slope); - slope.dx = -slope.dx; - slope.dy = -slope.dy; - stop = _cairo_pen_find_active_cw_vertex_index (pen, &slope); - - tri[0] = f->point; - tri[1] = f->cw; - for (i=start; i != stop; i = (i+1) % pen->num_vertices) { - tri[2] = f->point; - _translate_point (&tri[2], &pen->vertices[i].point); - status = _cairo_traps_tessellate_triangle (stroker->traps, tri); - if (unlikely (status)) - return status; - tri[1] = tri[2]; - } - tri[2] = f->ccw; - return _cairo_traps_tessellate_triangle (stroker->traps, tri); + slope.dx = -f->dev_vector.dx; + slope.dy = -f->dev_vector.dy; + + return _tessellate_fan (stroker, + &f->dev_vector, + &slope, + &f->point, &f->cw, &f->ccw, + FALSE); + } + case CAIRO_LINE_CAP_SQUARE: { double dx, dy; cairo_slope_t fvector; - cairo_point_t occw, ocw; - cairo_polygon_t polygon; + cairo_point_t quad[4]; dx = f->usr_vector.x; dy = f->usr_vector.y; @@ -511,38 +678,52 @@ _cairo_stroker_add_cap (cairo_stroker_t *stroker, cairo_stroke_face_t *f) cairo_matrix_transform_distance (stroker->ctm, &dx, &dy); fvector.dx = _cairo_fixed_from_double (dx); fvector.dy = _cairo_fixed_from_double (dy); - occw.x = f->ccw.x + fvector.dx; - occw.y = f->ccw.y + fvector.dy; - ocw.x = f->cw.x + fvector.dx; - ocw.y = f->cw.y + fvector.dy; - - _cairo_polygon_init (&polygon); - _cairo_polygon_move_to (&polygon, &f->cw); - _cairo_polygon_line_to (&polygon, &ocw); - _cairo_polygon_line_to (&polygon, &occw); - _cairo_polygon_line_to (&polygon, &f->ccw); - _cairo_polygon_close (&polygon); - status = _cairo_polygon_status (&polygon); - - if (status == CAIRO_STATUS_SUCCESS) { - status = _cairo_bentley_ottmann_tessellate_polygon (stroker->traps, - &polygon, - CAIRO_FILL_RULE_WINDING); - } - _cairo_polygon_fini (&polygon); + quad[0] = f->ccw; + quad[1].x = f->ccw.x + fvector.dx; + quad[1].y = f->ccw.y + fvector.dy; + quad[2].x = f->cw.x + fvector.dx; + quad[2].y = f->cw.y + fvector.dy; + quad[3] = f->cw; - return status; + if (stroker->add_external_edge != NULL) { + cairo_status_t status; + + status = stroker->add_external_edge (stroker->closure, + &quad[0], &quad[1]); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &quad[1], &quad[2]); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &quad[2], &quad[3]); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; + } else { + return stroker->add_convex_quad (stroker->closure, quad); + } } + case CAIRO_LINE_CAP_BUTT: default: - return CAIRO_STATUS_SUCCESS; + if (stroker->add_external_edge != NULL) { + return stroker->add_external_edge (stroker->closure, + &f->ccw, &f->cw); + } else { + return CAIRO_STATUS_SUCCESS; + } } } static cairo_status_t _cairo_stroker_add_leading_cap (cairo_stroker_t *stroker, - cairo_stroke_face_t *face) + const cairo_stroke_face_t *face) { cairo_stroke_face_t reversed; cairo_point_t t; @@ -563,7 +744,7 @@ _cairo_stroker_add_leading_cap (cairo_stroker_t *stroker, static cairo_status_t _cairo_stroker_add_trailing_cap (cairo_stroker_t *stroker, - cairo_stroke_face_t *face) + const cairo_stroke_face_t *face) { return _cairo_stroker_add_cap (stroker, face); } @@ -617,55 +798,6 @@ _compute_normalized_device_slope (double *dx, double *dy, static void _compute_face (const cairo_point_t *point, cairo_slope_t *dev_slope, double slope_dx, double slope_dy, - cairo_stroker_t *stroker, cairo_stroke_face_t *face); - -static cairo_status_t -_cairo_stroker_add_caps (cairo_stroker_t *stroker) -{ - cairo_status_t status; - /* check for a degenerative sub_path */ - if (stroker->has_initial_sub_path - && !stroker->has_first_face - && !stroker->has_current_face - && stroker->style->line_cap == CAIRO_LINE_JOIN_ROUND) - { - /* pick an arbitrary slope to use */ - double dx = 1.0, dy = 0.0; - cairo_slope_t slope = { CAIRO_FIXED_ONE, 0 }; - cairo_stroke_face_t face; - - _compute_normalized_device_slope (&dx, &dy, stroker->ctm_inverse, NULL); - - /* arbitrarily choose first_point - * first_point and current_point should be the same */ - _compute_face (&stroker->first_point, &slope, dx, dy, stroker, &face); - - status = _cairo_stroker_add_leading_cap (stroker, &face); - if (unlikely (status)) - return status; - status = _cairo_stroker_add_trailing_cap (stroker, &face); - if (unlikely (status)) - return status; - } - - if (stroker->has_first_face) { - status = _cairo_stroker_add_leading_cap (stroker, &stroker->first_face); - if (unlikely (status)) - return status; - } - - if (stroker->has_current_face) { - status = _cairo_stroker_add_trailing_cap (stroker, &stroker->current_face); - if (unlikely (status)) - return status; - } - - return CAIRO_STATUS_SUCCESS; -} - -static void -_compute_face (const cairo_point_t *point, cairo_slope_t *dev_slope, - double slope_dx, double slope_dy, cairo_stroker_t *stroker, cairo_stroke_face_t *face) { double face_dx, face_dy; @@ -712,6 +844,55 @@ _compute_face (const cairo_point_t *point, cairo_slope_t *dev_slope, } static cairo_status_t +_cairo_stroker_add_caps (cairo_stroker_t *stroker) +{ + cairo_status_t status; + + /* check for a degenerative sub_path */ + if (stroker->has_initial_sub_path + && ! stroker->has_first_face + && ! stroker->has_current_face + && stroker->style->line_cap == CAIRO_LINE_JOIN_ROUND) + { + /* pick an arbitrary slope to use */ + double dx = 1.0, dy = 0.0; + cairo_slope_t slope = { CAIRO_FIXED_ONE, 0 }; + cairo_stroke_face_t face; + + _compute_normalized_device_slope (&dx, &dy, + stroker->ctm_inverse, NULL); + + /* arbitrarily choose first_point + * first_point and current_point should be the same */ + _compute_face (&stroker->first_point, &slope, dx, dy, stroker, &face); + + status = _cairo_stroker_add_leading_cap (stroker, &face); + if (unlikely (status)) + return status; + + status = _cairo_stroker_add_trailing_cap (stroker, &face); + if (unlikely (status)) + return status; + } + + if (stroker->has_first_face) { + status = _cairo_stroker_add_leading_cap (stroker, + &stroker->first_face); + if (unlikely (status)) + return status; + } + + if (stroker->has_current_face) { + status = _cairo_stroker_add_trailing_cap (stroker, + &stroker->current_face); + if (unlikely (status)) + return status; + } + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t _cairo_stroker_add_sub_edge (cairo_stroker_t *stroker, const cairo_point_t *p1, const cairo_point_t *p2, @@ -720,24 +901,42 @@ _cairo_stroker_add_sub_edge (cairo_stroker_t *stroker, cairo_stroke_face_t *start, cairo_stroke_face_t *end) { - cairo_point_t rectangle[4]; - _compute_face (p1, dev_slope, slope_dx, slope_dy, stroker, start); - - /* XXX: This could be optimized slightly by not calling - _compute_face again but rather translating the relevant - fields from start. */ - _compute_face (p2, dev_slope, slope_dx, slope_dy, stroker, end); + *end = *start; if (p1->x == p2->x && p1->y == p2->y) return CAIRO_STATUS_SUCCESS; - rectangle[0] = start->cw; - rectangle[1] = start->ccw; - rectangle[2] = end->ccw; - rectangle[3] = end->cw; + end->point = *p2; + end->ccw.x += p2->x - p1->x; + end->ccw.y += p2->y - p1->y; + end->cw.x += p2->x - p1->x; + end->cw.y += p2->y - p1->y; + + if (stroker->add_external_edge != NULL) { + cairo_status_t status; - return _cairo_traps_tessellate_convex_quad (stroker->traps, rectangle); + status = stroker->add_external_edge (stroker->closure, + &end->cw, &start->cw); + if (unlikely (status)) + return status; + + status = stroker->add_external_edge (stroker->closure, + &start->ccw, &end->ccw); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; + } else { + cairo_point_t quad[4]; + + quad[0] = start->cw; + quad[1] = end->cw; + quad[2] = end->ccw; + quad[3] = start->ccw; + + return stroker->add_convex_quad (stroker->closure, quad); + } } static cairo_status_t @@ -747,6 +946,9 @@ _cairo_stroker_move_to (void *closure, cairo_stroker_t *stroker = closure; cairo_status_t status; + /* reset the dash pattern for new sub paths */ + _cairo_stroker_dash_start (&stroker->dash); + /* Cap the start and end of the previous sub path as needed */ status = _cairo_stroker_add_caps (stroker); if (unlikely (status)) @@ -763,40 +965,29 @@ _cairo_stroker_move_to (void *closure, } static cairo_status_t -_cairo_stroker_move_to_dashed (void *closure, - const cairo_point_t *point) -{ - cairo_stroker_t *stroker = closure; - - /* reset the dash pattern for new sub paths */ - _cairo_stroker_dash_start (&stroker->dash); - - return _cairo_stroker_move_to (closure, point); -} - -static cairo_status_t _cairo_stroker_line_to (void *closure, - const cairo_point_t *p2) + const cairo_point_t *point) { - cairo_status_t status; cairo_stroker_t *stroker = closure; cairo_stroke_face_t start, end; cairo_point_t *p1 = &stroker->current_point; cairo_slope_t dev_slope; double slope_dx, slope_dy; + cairo_status_t status; stroker->has_initial_sub_path = TRUE; - if (p1->x == p2->x && p1->y == p2->y) + if (p1->x == point->x && p1->y == point->y) return CAIRO_STATUS_SUCCESS; - _cairo_slope_init (&dev_slope, p1, p2); - slope_dx = _cairo_fixed_to_double (p2->x - p1->x); - slope_dy = _cairo_fixed_to_double (p2->y - p1->y); - _compute_normalized_device_slope (&slope_dx, &slope_dy, stroker->ctm_inverse, NULL); + _cairo_slope_init (&dev_slope, p1, point); + slope_dx = _cairo_fixed_to_double (point->x - p1->x); + slope_dy = _cairo_fixed_to_double (point->y - p1->y); + _compute_normalized_device_slope (&slope_dx, &slope_dy, + stroker->ctm_inverse, NULL); status = _cairo_stroker_add_sub_edge (stroker, - p1, p2, + p1, point, &dev_slope, slope_dx, slope_dy, &start, &end); @@ -805,10 +996,12 @@ _cairo_stroker_line_to (void *closure, if (stroker->has_current_face) { /* Join with final face from previous segment */ - status = _cairo_stroker_join (stroker, &stroker->current_face, &start); + status = _cairo_stroker_join (stroker, + &stroker->current_face, + &start); if (unlikely (status)) return status; - } else if (!stroker->has_first_face) { + } else if (! stroker->has_first_face) { /* Save sub path's first face in case needed for closing join */ stroker->first_face = start; stroker->has_first_face = TRUE; @@ -816,7 +1009,7 @@ _cairo_stroker_line_to (void *closure, stroker->current_face = end; stroker->has_current_face = TRUE; - stroker->current_point = *p2; + stroker->current_point = *point; return CAIRO_STATUS_SUCCESS; } @@ -858,7 +1051,7 @@ _cairo_stroker_line_to_dashed (void *closure, slope_dy = _cairo_fixed_to_double (p2->y - p1->y); if (! _compute_normalized_device_slope (&slope_dx, &slope_dy, - stroker->ctm_inverse, &mag)) + stroker->ctm_inverse, &mag)) { return CAIRO_STATUS_SUCCESS; } @@ -973,121 +1166,22 @@ _cairo_stroker_curve_to (void *closure, const cairo_point_t *d) { cairo_stroker_t *stroker = closure; - cairo_pen_stroke_spline_t spline_pen; - cairo_stroke_face_t start, end; - cairo_point_t extra_points[4]; - cairo_point_t *a = &stroker->current_point; - double initial_slope_dx, initial_slope_dy; - double final_slope_dx, final_slope_dy; - cairo_status_t status; - - status = _cairo_pen_stroke_spline_init (&spline_pen, - &stroker->pen, - a, b, c, d); - if (status == CAIRO_INT_STATUS_DEGENERATE) - return _cairo_stroker_line_to (closure, d); - else if (unlikely (status)) - return status; - - initial_slope_dx = _cairo_fixed_to_double (spline_pen.spline.initial_slope.dx); - initial_slope_dy = _cairo_fixed_to_double (spline_pen.spline.initial_slope.dy); - final_slope_dx = _cairo_fixed_to_double (spline_pen.spline.final_slope.dx); - final_slope_dy = _cairo_fixed_to_double (spline_pen.spline.final_slope.dy); - - if (_compute_normalized_device_slope (&initial_slope_dx, &initial_slope_dy, - stroker->ctm_inverse, NULL)) - { - _compute_face (a, - &spline_pen.spline.initial_slope, - initial_slope_dx, initial_slope_dy, - stroker, &start); - } - - if (_compute_normalized_device_slope (&final_slope_dx, &final_slope_dy, - stroker->ctm_inverse, NULL)) - { - _compute_face (d, - &spline_pen.spline.final_slope, - final_slope_dx, final_slope_dy, - stroker, &end); - } - - if (stroker->has_current_face) { - status = _cairo_stroker_join (stroker, &stroker->current_face, &start); - if (unlikely (status)) - goto CLEANUP_PEN; - } else if (! stroker->has_first_face) { - stroker->first_face = start; - stroker->has_first_face = TRUE; - } - stroker->current_face = end; - stroker->has_current_face = TRUE; - - extra_points[0] = start.cw; - extra_points[0].x -= start.point.x; - extra_points[0].y -= start.point.y; - extra_points[1] = start.ccw; - extra_points[1].x -= start.point.x; - extra_points[1].y -= start.point.y; - extra_points[2] = end.cw; - extra_points[2].x -= end.point.x; - extra_points[2].y -= end.point.y; - extra_points[3] = end.ccw; - extra_points[3].x -= end.point.x; - extra_points[3].y -= end.point.y; - - status = _cairo_pen_add_points (&spline_pen.pen, extra_points, 4); - if (unlikely (status)) - goto CLEANUP_PEN; - - status = _cairo_pen_stroke_spline (&spline_pen, - stroker->tolerance, - stroker->traps); - - CLEANUP_PEN: - _cairo_pen_stroke_spline_fini (&spline_pen); - - stroker->current_point = *d; - - return status; -} - -/* We're using two different algorithms here for dashed and un-dashed - * splines. The dashed algorithm uses the existing line dashing - * code. It's linear in path length, but gets subtly wrong results for - * self-intersecting paths (an outstanding but for self-intersecting - * non-curved paths as well). The non-dashed algorithm tessellates a - * single polygon for the whole curve. It handles the - * self-intersecting problem, but it's (unsurprisingly) not O(n) and - * more significantly, it doesn't yet handle dashes. - * - * The only reason we're doing split algorithms here is to - * minimize the impact of fixing the splines-aren't-dashed bug for - * 1.0.2. Long-term the right answer is to rewrite the whole pile - * of stroking code so that the entire result is computed as a - * single polygon that is tessellated, (that is, stroking can be - * built on top of filling). That will solve the self-intersecting - * problem. It will also increase the importance of implementing - * an efficient and more robust tessellator. - */ -static cairo_status_t -_cairo_stroker_curve_to_dashed (void *closure, - const cairo_point_t *b, - const cairo_point_t *c, - const cairo_point_t *d) -{ - cairo_stroker_t *stroker = closure; cairo_spline_t spline; - cairo_point_t *a = &stroker->current_point; cairo_line_join_t line_join_save; - cairo_status_t status; + cairo_stroke_face_t face; + double slope_dx, slope_dy; + cairo_path_fixed_line_to_func_t *line_to; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + + line_to = stroker->dash.dashed ? + _cairo_stroker_line_to_dashed : + _cairo_stroker_line_to; if (! _cairo_spline_init (&spline, - _cairo_stroker_line_to_dashed, - stroker, - a, b, c, d)) + line_to, stroker, + &stroker->current_point, b, c, d)) { - return _cairo_stroker_line_to_dashed (closure, d); + return line_to (closure, d); } /* If the line width is so small that the pen is reduced to a @@ -1095,23 +1189,71 @@ _cairo_stroker_curve_to_dashed (void *closure, if (stroker->pen.num_vertices <= 1) return CAIRO_STATUS_SUCCESS; + /* Compute the initial face */ + if (! stroker->dash.dashed || stroker->dash.dash_on) { + slope_dx = _cairo_fixed_to_double (spline.initial_slope.dx); + slope_dy = _cairo_fixed_to_double (spline.initial_slope.dy); + if (_compute_normalized_device_slope (&slope_dx, &slope_dy, + stroker->ctm_inverse, NULL)) + { + _compute_face (&stroker->current_point, + &spline.initial_slope, + slope_dx, slope_dy, + stroker, &face); + } + if (stroker->has_current_face) { + status = _cairo_stroker_join (stroker, + &stroker->current_face, &face); + if (unlikely (status)) + return status; + } else if (! stroker->has_first_face) { + stroker->first_face = face; + stroker->has_first_face = TRUE; + } + + stroker->current_face = face; + stroker->has_current_face = TRUE; + } + /* Temporarily modify the stroker to use round joins to guarantee * smooth stroked curves. */ line_join_save = stroker->style->line_join; stroker->style->line_join = CAIRO_LINE_JOIN_ROUND; status = _cairo_spline_decompose (&spline, stroker->tolerance); + if (unlikely (status)) + return status; + + /* And join the final face */ + if (! stroker->dash.dashed || stroker->dash.dash_on) { + slope_dx = _cairo_fixed_to_double (spline.final_slope.dx); + slope_dy = _cairo_fixed_to_double (spline.final_slope.dy); + if (_compute_normalized_device_slope (&slope_dx, &slope_dy, + stroker->ctm_inverse, NULL)) + { + _compute_face (&stroker->current_point, + &spline.final_slope, + slope_dx, slope_dy, + stroker, &face); + } + + status = _cairo_stroker_join (stroker, &stroker->current_face, &face); + if (unlikely (status)) + return status; + + stroker->current_face = face; + } stroker->style->line_join = line_join_save; - return status; + return CAIRO_STATUS_SUCCESS; } static cairo_status_t _cairo_stroker_close_path (void *closure) { - cairo_status_t status; cairo_stroker_t *stroker = closure; + cairo_status_t status; if (stroker->dash.dashed) status = _cairo_stroker_line_to_dashed (stroker, &stroker->first_point); @@ -1122,7 +1264,9 @@ _cairo_stroker_close_path (void *closure) if (stroker->has_first_face && stroker->has_current_face) { /* Join first and final faces of sub path */ - status = _cairo_stroker_join (stroker, &stroker->current_face, &stroker->first_face); + status = _cairo_stroker_join (stroker, + &stroker->current_face, + &stroker->first_face); if (unlikely (status)) return status; } else { @@ -1139,11 +1283,100 @@ _cairo_stroker_close_path (void *closure) return CAIRO_STATUS_SUCCESS; } -static cairo_int_status_t -_cairo_path_fixed_stroke_rectilinear (const cairo_path_fixed_t *path, - cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_traps_t *traps); +cairo_status_t +_cairo_path_fixed_stroke_to_shaper (cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + cairo_matrix_t *ctm, + cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_status_t (*add_triangle) (void *closure, + const cairo_point_t triangle[3]), + cairo_status_t (*add_triangle_fan) (void *closure, + const cairo_point_t *midpt, + const cairo_point_t *points, + int npoints), + cairo_status_t (*add_convex_quad) (void *closure, + const cairo_point_t quad[4]), + void *closure) +{ + cairo_stroker_t stroker; + cairo_status_t status; + + status = _cairo_stroker_init (&stroker, stroke_style, + ctm, ctm_inverse, tolerance); + if (unlikely (status)) + return status; + + stroker.add_triangle = add_triangle; + stroker.add_triangle_fan = add_triangle_fan; + stroker.add_convex_quad = add_convex_quad; + stroker.closure = closure; + + status = _cairo_path_fixed_interpret (path, + CAIRO_DIRECTION_FORWARD, + _cairo_stroker_move_to, + stroker.dash.dashed ? + _cairo_stroker_line_to_dashed : + _cairo_stroker_line_to, + _cairo_stroker_curve_to, + _cairo_stroker_close_path, + &stroker); + + if (unlikely (status)) + goto BAIL; + + /* Cap the start and end of the final sub path as needed */ + status = _cairo_stroker_add_caps (&stroker); + +BAIL: + _cairo_stroker_fini (&stroker); + + return status; +} + +cairo_status_t +_cairo_path_fixed_stroke_to_polygon (const cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + const cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_polygon_t *polygon) +{ + cairo_stroker_t stroker; + cairo_status_t status; + + status = _cairo_stroker_init (&stroker, stroke_style, + ctm, ctm_inverse, tolerance); + if (unlikely (status)) + return status; + + stroker.add_external_edge = _cairo_polygon_add_external_edge, + stroker.closure = polygon; + + if (polygon->has_limits) + _cairo_stroker_limit (&stroker, &polygon->limits); + + status = _cairo_path_fixed_interpret (path, + CAIRO_DIRECTION_FORWARD, + _cairo_stroker_move_to, + stroker.dash.dashed ? + _cairo_stroker_line_to_dashed : + _cairo_stroker_line_to, + _cairo_stroker_curve_to, + _cairo_stroker_close_path, + &stroker); + + if (unlikely (status)) + goto BAIL; + + /* Cap the start and end of the final sub path as needed */ + status = _cairo_stroker_add_caps (&stroker); + +BAIL: + _cairo_stroker_fini (&stroker); + + return status; +} cairo_status_t _cairo_path_fixed_stroke_to_traps (const cairo_path_fixed_t *path, @@ -1154,50 +1387,44 @@ _cairo_path_fixed_stroke_to_traps (const cairo_path_fixed_t *path, cairo_traps_t *traps) { cairo_status_t status; - cairo_stroker_t stroker; + cairo_polygon_t polygon; /* Before we do anything else, we attempt the rectilinear * stroker. It's careful to generate trapezoids that align to * device-pixel boundaries when possible. Many backends can render * those much faster than non-aligned trapezoids, (by using clip * regions, etc.) */ - status = _cairo_path_fixed_stroke_rectilinear (path, - stroke_style, - ctm, - traps); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - return status; + if (path->is_rectilinear) { + status = _cairo_path_fixed_stroke_rectilinear_to_traps (path, + stroke_style, + ctm, + traps); + if (status != CAIRO_INT_STATUS_UNSUPPORTED) + return status; + } - status = _cairo_stroker_init (&stroker, stroke_style, - ctm, ctm_inverse, tolerance, - traps); + _cairo_polygon_init (&polygon); + if (traps->has_limits) + _cairo_polygon_limit (&polygon, &traps->limits); + + status = _cairo_path_fixed_stroke_to_polygon (path, + stroke_style, + ctm, + ctm_inverse, + tolerance, + &polygon); if (unlikely (status)) - return status; + goto BAIL; - if (stroker.style->dash) - status = _cairo_path_fixed_interpret (path, - CAIRO_DIRECTION_FORWARD, - _cairo_stroker_move_to_dashed, - _cairo_stroker_line_to_dashed, - _cairo_stroker_curve_to_dashed, - _cairo_stroker_close_path, - &stroker); - else - status = _cairo_path_fixed_interpret (path, - CAIRO_DIRECTION_FORWARD, - _cairo_stroker_move_to, - _cairo_stroker_line_to, - _cairo_stroker_curve_to, - _cairo_stroker_close_path, - &stroker); + status = _cairo_polygon_status (&polygon); if (unlikely (status)) goto BAIL; - /* Cap the start and end of the final sub path as needed */ - status = _cairo_stroker_add_caps (&stroker); + status = _cairo_bentley_ottmann_tessellate_polygon (traps, &polygon, + CAIRO_FILL_RULE_WINDING); BAIL: - _cairo_stroker_fini (&stroker); + _cairo_polygon_fini (&polygon); return status; } @@ -1263,6 +1490,9 @@ _cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t *stroker, _cairo_stroker_dash_init (&stroker->dash, stroke_style); stroker->has_bounds = FALSE; + + /* As we incrementally tessellate, we do not eliminate self-intersections */ + stroker->traps->has_intersections = TRUE; } static void @@ -1583,8 +1813,7 @@ _cairo_rectilinear_stroker_line_to (void *closure, cairo_status_t status; /* We only support horizontal or vertical elements. */ - if (! (a->x == b->x || a->y == b->y)) - return CAIRO_INT_STATUS_UNSUPPORTED; + assert (a->x == b->x || a->y == b->y); /* We don't draw anything for degenerate paths. */ if (a->x == b->x && a->y == b->y) @@ -1620,8 +1849,7 @@ _cairo_rectilinear_stroker_line_to_dashed (void *closure, return CAIRO_STATUS_SUCCESS; /* We only support horizontal or vertical elements. */ - if (! (a->x == b->x || a->y == b->y)) - return CAIRO_INT_STATUS_UNSUPPORTED; + assert (a->x == b->x || a->y == b->y); fully_in_bounds = TRUE; if (stroker->has_bounds && @@ -1736,11 +1964,11 @@ _cairo_rectilinear_stroker_close_path (void *closure) return CAIRO_STATUS_SUCCESS; } -static cairo_int_status_t -_cairo_path_fixed_stroke_rectilinear (const cairo_path_fixed_t *path, - cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_traps_t *traps) +cairo_int_status_t +_cairo_path_fixed_stroke_rectilinear_to_traps (const cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + cairo_traps_t *traps) { cairo_rectilinear_stroker_t rectilinear_stroker; cairo_int_status_t status; @@ -1755,8 +1983,8 @@ _cairo_path_fixed_stroke_rectilinear (const cairo_path_fixed_t *path, * UNSUPPORTED from _cairo_rectilinear_stroker_line_to if any * non-rectilinear line_to is encountered. */ - if (! path->is_rectilinear) - return CAIRO_INT_STATUS_UNSUPPORTED; + assert (path->is_rectilinear); + if (stroke_style->line_join != CAIRO_LINE_JOIN_MITER) return CAIRO_INT_STATUS_UNSUPPORTED; /* If the miter limit turns right angles into bevels, then we @@ -1770,11 +1998,8 @@ _cairo_path_fixed_stroke_rectilinear (const cairo_path_fixed_t *path, { return CAIRO_INT_STATUS_UNSUPPORTED; } - if (! (_cairo_matrix_is_identity (ctm) || - _cairo_matrix_is_translation (ctm))) - { + if (! _cairo_matrix_has_unity_scale (ctm)) return CAIRO_INT_STATUS_UNSUPPORTED; - } _cairo_rectilinear_stroker_init (&rectilinear_stroker, stroke_style, diff --git a/src/cairo-pen.c b/src/cairo-pen.c index b2fd8557..8db8fcb3 100644 --- a/src/cairo-pen.c +++ b/src/cairo-pen.c @@ -393,194 +393,3 @@ _cairo_pen_find_active_ccw_vertex_index (const cairo_pen_t *pen, return i; } - -static int -_cairo_pen_stroke_spline_add_convolved_point (cairo_pen_stroke_spline_t *stroker, - const cairo_point_t *last_point, - const cairo_slope_t *slope, - cairo_point_t *last_hull_point, - int active, - int step) -{ - do { - cairo_point_t hull_point; - - hull_point.x = last_point->x + stroker->pen.vertices[active].point.x; - hull_point.y = last_point->y + stroker->pen.vertices[active].point.y; - _cairo_polygon_add_edge (&stroker->polygon, - last_hull_point, &hull_point, - step); - *last_hull_point = hull_point; - - /* The strict inequalities here ensure that if a spline slope - * compares identically with either of the slopes of the - * active vertex, then it remains the active vertex. This is - * very important since otherwise we can trigger an infinite - * loop in the case of a degenerate pen, (a line), where - * neither vertex considers itself active for the slope---one - * will consider it as equal and reject, and the other will - * consider it unequal and reject. This is due to the inherent - * ambiguity when comparing slopes that differ by exactly - * pi. */ - if (_cairo_slope_compare (slope, - &stroker->pen.vertices[active].slope_ccw) > 0) - { - if (++active == stroker->pen.num_vertices) - active = 0; - } - else if (_cairo_slope_compare (slope, - &stroker->pen.vertices[active].slope_cw) < 0) - { - if (--active == -1) - active = stroker->pen.num_vertices - 1; - } - else - { - return active; - } - } while (TRUE); -} - - -/* Compute outline of a given spline using the pen. - * The trapezoids needed to fill that outline will be added to traps - */ -cairo_status_t -_cairo_pen_stroke_spline (cairo_pen_stroke_spline_t *stroker, - double tolerance, - cairo_traps_t *traps) -{ - cairo_status_t status; - cairo_slope_t slope; - - /* If the line width is so small that the pen is reduced to a - single point, then we have nothing to do. */ - if (stroker->pen.num_vertices <= 1) - return CAIRO_STATUS_SUCCESS; - - /* open the polygon */ - slope = stroker->spline.initial_slope; - stroker->forward_vertex = - _cairo_pen_find_active_cw_vertex_index (&stroker->pen, &slope); - stroker->forward_hull_point.x = stroker->last_point.x + - stroker->pen.vertices[stroker->forward_vertex].point.x; - stroker->forward_hull_point.y = stroker->last_point.y + - stroker->pen.vertices[stroker->forward_vertex].point.y; - - slope.dx = -slope.dx; - slope.dy = -slope.dy; - stroker->backward_vertex = - _cairo_pen_find_active_cw_vertex_index (&stroker->pen, &slope); - stroker->backward_hull_point.x = stroker->last_point.x + - stroker->pen.vertices[stroker->backward_vertex].point.x; - stroker->backward_hull_point.y = stroker->last_point.y + - stroker->pen.vertices[stroker->backward_vertex].point.y; - - _cairo_polygon_add_edge (&stroker->polygon, - &stroker->backward_hull_point, - &stroker->forward_hull_point, - 1); - - status = _cairo_spline_decompose (&stroker->spline, tolerance); - if (unlikely (status)) - return status; - - /* close the polygon */ - slope = stroker->spline.final_slope; - _cairo_pen_stroke_spline_add_convolved_point (stroker, - &stroker->last_point, - &slope, - &stroker->forward_hull_point, - stroker->forward_vertex, - 1); - - slope.dx = -slope.dx; - slope.dy = -slope.dy; - _cairo_pen_stroke_spline_add_convolved_point (stroker, - &stroker->last_point, - &slope, - &stroker->backward_hull_point, - stroker->backward_vertex, - -1); - - _cairo_polygon_add_edge (&stroker->polygon, - &stroker->forward_hull_point, - &stroker->backward_hull_point, - 1); - - status = _cairo_polygon_status (&stroker->polygon); - if (unlikely (status)) - return status; - - status = _cairo_bentley_ottmann_tessellate_polygon (traps, - &stroker->polygon, - CAIRO_FILL_RULE_WINDING); - - return status; -} - -static cairo_status_t -_cairo_pen_stroke_spline_add_point (void *closure, - const cairo_point_t *point) -{ - cairo_pen_stroke_spline_t *stroker = closure; - cairo_slope_t slope; - - _cairo_slope_init (&slope, &stroker->last_point, point); - stroker->forward_vertex = - _cairo_pen_stroke_spline_add_convolved_point (stroker, - &stroker->last_point, - &slope, - &stroker->forward_hull_point, - stroker->forward_vertex, - 1); - - slope.dx = -slope.dx; - slope.dy = -slope.dy; - stroker->backward_vertex = - _cairo_pen_stroke_spline_add_convolved_point (stroker, - &stroker->last_point, - &slope, - &stroker->backward_hull_point, - stroker->backward_vertex, - -1); - stroker->last_point = *point; - - return CAIRO_STATUS_SUCCESS; -} - -cairo_int_status_t -_cairo_pen_stroke_spline_init (cairo_pen_stroke_spline_t *stroker, - const cairo_pen_t *pen, - const cairo_point_t *a, - const cairo_point_t *b, - const cairo_point_t *c, - const cairo_point_t *d) -{ - cairo_int_status_t status; - - if (! _cairo_spline_init (&stroker->spline, - _cairo_pen_stroke_spline_add_point, - stroker, - a, b, c, d)) - { - return CAIRO_INT_STATUS_DEGENERATE; - } - - status = _cairo_pen_init_copy (&stroker->pen, pen); - if (unlikely (status)) - return status; - - _cairo_polygon_init (&stroker->polygon); - - stroker->last_point = *a; - - return CAIRO_STATUS_SUCCESS; -} - -void -_cairo_pen_stroke_spline_fini (cairo_pen_stroke_spline_t *stroker) -{ - _cairo_polygon_fini (&stroker->polygon); - _cairo_pen_fini (&stroker->pen); -} diff --git a/src/cairo-polygon.c b/src/cairo-polygon.c index 202cb4d7..ac4d0945 100644 --- a/src/cairo-polygon.c +++ b/src/cairo-polygon.c @@ -49,6 +49,18 @@ _cairo_polygon_init (cairo_polygon_t *polygon) polygon->edges_size = ARRAY_LENGTH (polygon->edges_embedded); polygon->has_current_point = FALSE; + polygon->has_limits = FALSE; + + polygon->extents.p1.x = polygon->extents.p1.y = INT32_MAX; + polygon->extents.p2.x = polygon->extents.p2.y = INT32_MIN; +} + +void +_cairo_polygon_limit (cairo_polygon_t *polygon, + const cairo_box_t *limits) +{ + polygon->has_limits = TRUE; + polygon->limits = *limits; } void @@ -93,17 +105,16 @@ _cairo_polygon_grow (cairo_polygon_t *polygon) return TRUE; } -void -_cairo_polygon_add_edge (cairo_polygon_t *polygon, - const cairo_point_t *p1, - const cairo_point_t *p2, - int dir) +static void +_add_edge (cairo_polygon_t *polygon, + const cairo_point_t *p1, + const cairo_point_t *p2, + int top, int bottom, + int dir) { cairo_edge_t *edge; - /* drop horizontal edges */ - if (p1->y == p2->y) - return; + assert (top < bottom); if (polygon->num_edges == polygon->edges_size) { if (! _cairo_polygon_grow (polygon)) @@ -111,26 +122,247 @@ _cairo_polygon_add_edge (cairo_polygon_t *polygon, } edge = &polygon->edges[polygon->num_edges++]; + edge->line.p1 = *p1; + edge->line.p2 = *p2; + edge->top = top; + edge->bottom = bottom; + edge->dir = dir; + + if (top < polygon->extents.p1.y) + polygon->extents.p1.y = top; + if (bottom > polygon->extents.p2.y) + polygon->extents.p2.y = bottom; + + if (p1->x < polygon->extents.p1.x || p1->x > polygon->extents.p2.x) { + cairo_fixed_t x = p1->x; + if (top != p1->y) + x = _cairo_edge_compute_intersection_x_for_y (p1, p2, top); + if (x < polygon->extents.p1.x) + polygon->extents.p1.x = x; + if (x > polygon->extents.p2.x) + polygon->extents.p2.x = x; + } + + if (p2->x < polygon->extents.p1.x || p2->x > polygon->extents.p2.x) { + cairo_fixed_t x = p2->x; + if (bottom != p2->y) + x = _cairo_edge_compute_intersection_x_for_y (p1, p2, bottom); + if (x < polygon->extents.p1.x) + polygon->extents.p1.x = x; + if (x > polygon->extents.p2.x) + polygon->extents.p2.x = x; + } +} + +static void +_add_clipped_edge (cairo_polygon_t *polygon, + const cairo_point_t *p1, + const cairo_point_t *p2, + int dir) +{ + cairo_point_t p[2]; + int top_y, bot_y; + + if (p1->x <= polygon->limits.p1.x && p2->x <= polygon->limits.p1.x) + { + p[0].x = polygon->limits.p1.x; + p[0].y = polygon->limits.p1.y; + top_y = p1->y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p1.x; + p[1].y = polygon->limits.p2.y; + bot_y = p2->y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + } + else if (p1->x >= polygon->limits.p2.x && p2->x >= polygon->limits.p2.x) + { + p[0].x = polygon->limits.p2.x; + p[0].y = polygon->limits.p1.y; + top_y = p1->y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p2.x; + p[1].y = polygon->limits.p2.y; + bot_y = p2->y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + } + else if (p1->x >= polygon->limits.p1.x && p2->x >= polygon->limits.p1.x && + p1->x <= polygon->limits.p2.x && p2->x <= polygon->limits.p2.x) + { + top_y = p1->y; + if (top_y < polygon->limits.p1.y) + top_y = polygon->limits.p1.y; + + bot_y = p2->y; + if (bot_y > polygon->limits.p2.y) + bot_y = polygon->limits.p2.y; + + _add_edge (polygon, p1, p2, top_y, bot_y, dir); + } + else + { + int left_y, right_y; + int p1_y, p2_y; + + left_y = _cairo_edge_compute_intersection_y_for_x (p1, p2, + polygon->limits.p1.x); + right_y = _cairo_edge_compute_intersection_y_for_x (p1, p2, + polygon->limits.p2.x); + + if (left_y == right_y) /* horizontal within bounds */ + return; + + p1_y = p1->y; + p2_y = p2->y; + + if (left_y < right_y) { + if (p1->x < polygon->limits.p1.x && left_y > polygon->limits.p1.y) + { + p[0].x = polygon->limits.p1.x; + p[0].y = polygon->limits.p1.y; + top_y = p1_y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p1.x; + p[1].y = polygon->limits.p2.y; + bot_y = left_y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + if (bot_y > top_y) + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + p1_y = bot_y; + } + + if (p2->x > polygon->limits.p2.x && right_y < polygon->limits.p2.y) + { + p[0].x = polygon->limits.p2.x; + p[0].y = polygon->limits.p1.y; + top_y = right_y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p2.x; + p[1].y = polygon->limits.p2.y; + bot_y = p2_y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + if (bot_y > top_y) + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + p2_y = top_y; + } + } else { + if (p1->x > polygon->limits.p2.x && right_y > polygon->limits.p1.y) + { + p[0].x = polygon->limits.p2.x; + p[0].y = polygon->limits.p1.y; + top_y = p1_y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p2.x; + p[1].y = polygon->limits.p2.y; + bot_y = right_y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + if (bot_y > top_y) + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + p1_y = bot_y; + } + + if (p2->x < polygon->limits.p1.x && left_y < polygon->limits.p2.y) + { + p[0].x = polygon->limits.p1.x; + p[0].y = polygon->limits.p1.y; + top_y = left_y; + if (top_y < p[0].y) + top_y = p[0].y; + + p[1].x = polygon->limits.p1.x; + p[1].y = polygon->limits.p2.y; + bot_y = p2_y; + if (bot_y > p[1].y) + bot_y = p[1].y; + + if (bot_y > top_y) + _add_edge (polygon, &p[0], &p[1], top_y, bot_y, dir); + p2_y = top_y; + } + } + + if (p1_y < polygon->limits.p1.y) + p1_y = polygon->limits.p1.y; + if (p2_y > polygon->limits.p2.y) + p2_y = polygon->limits.p2.y; + if (p2_y > p1_y) + _add_edge (polygon, p1, p2, p1_y, p2_y, dir); + } +} + +static void +_cairo_polygon_add_edge (cairo_polygon_t *polygon, + const cairo_point_t *p1, + const cairo_point_t *p2) +{ + int dir; + + /* drop horizontal edges */ + if (p1->y == p2->y) + return; + if (p1->y < p2->y) { - edge->edge.p1 = *p1; - edge->edge.p2 = *p2; - edge->dir = dir; + dir = 1; } else { - edge->edge.p1 = *p2; - edge->edge.p2 = *p1; - edge->dir = -dir; + const cairo_point_t *t; + t = p1, p1 = p2, p2 = t; + dir = -1; } + + if (polygon->has_limits) { + if (p2->y <= polygon->limits.p1.y) + return; + + if (p1->y >= polygon->limits.p2.y) + return; + + _add_clipped_edge (polygon, p1, p2, dir); + } else + _add_edge (polygon, p1, p2, p1->y, p2->y, dir); +} + +cairo_status_t +_cairo_polygon_add_external_edge (void *polygon, + const cairo_point_t *p1, + const cairo_point_t *p2) +{ + _cairo_polygon_add_edge (polygon, p1, p2); + return _cairo_polygon_status (polygon); } +/* flattened path operations */ + void _cairo_polygon_move_to (cairo_polygon_t *polygon, const cairo_point_t *point) { - if (! polygon->has_current_point) + if (! polygon->has_current_point) { polygon->first_point = *point; + polygon->has_current_point = TRUE; + } polygon->current_point = *point; - polygon->has_current_point = TRUE; } void @@ -138,7 +370,7 @@ _cairo_polygon_line_to (cairo_polygon_t *polygon, const cairo_point_t *point) { if (polygon->has_current_point) - _cairo_polygon_add_edge (polygon, &polygon->current_point, point, 1); + _cairo_polygon_add_edge (polygon, &polygon->current_point, point); _cairo_polygon_move_to (polygon, point); } @@ -149,8 +381,7 @@ _cairo_polygon_close (cairo_polygon_t *polygon) if (polygon->has_current_point) { _cairo_polygon_add_edge (polygon, &polygon->current_point, - &polygon->first_point, - 1); + &polygon->first_point); polygon->has_current_point = FALSE; } diff --git a/src/cairo-skiplist-private.h b/src/cairo-skiplist-private.h index 250b5a26..c0e5bce5 100644 --- a/src/cairo-skiplist-private.h +++ b/src/cairo-skiplist-private.h @@ -62,7 +62,7 @@ typedef struct _skip_elt { #define SKIP_LIST_ELT_TO_DATA(type, elt) ((type *) ((char *) (elt) - (sizeof (type) - sizeof (skip_elt_t)))) typedef int -(*cairo_skip_list_compare_t) (void *list, void *a, void *b); +(*cairo_skip_list_compare_t) (void *list, const void *a, const void *b); typedef struct _skip_list { cairo_skip_list_compare_t compare; @@ -101,7 +101,7 @@ _cairo_skip_list_fini (cairo_skip_list_t *list); * Otherwise data will be copied (elt_size bytes from <data> via * memcpy) and the new element is returned. */ cairo_private void * -_cairo_skip_list_insert (cairo_skip_list_t *list, void *data, int unique); +_cairo_skip_list_insert (cairo_skip_list_t *list, void *data); /* Find an element which compare considers equal to <data> */ cairo_private void * diff --git a/src/cairo-skiplist.c b/src/cairo-skiplist.c index 18d69ca6..8d8c8d1f 100644 --- a/src/cairo-skiplist.c +++ b/src/cairo-skiplist.c @@ -189,7 +189,7 @@ free_elt (cairo_skip_list_t *list, skip_elt_t *elt) * Insert 'data' into the list */ void * -_cairo_skip_list_insert (cairo_skip_list_t *list, void *data, int unique) +_cairo_skip_list_insert (cairo_skip_list_t *list, void *data) { skip_elt_t **update[MAX_LEVEL]; skip_elt_t *prev[MAX_LEVEL]; @@ -209,7 +209,7 @@ _cairo_skip_list_insert (cairo_skip_list_t *list, void *data, int unique) for (; (elt = next[i]); next = elt->next) { int cmp = list->compare (list, ELT_DATA(elt), data); - if (unique && 0 == cmp) + if (0 == cmp) return ELT_DATA(elt); if (cmp > 0) break; @@ -369,7 +369,7 @@ typedef struct { } test_elt_t; static int -test_cmp (void *list, void *A, void *B) +test_cmp (void *list, const void *A, const void *B) { const test_elt_t *a = A, *b = B; return a->n - b->n; @@ -386,7 +386,7 @@ main (void) for (n = 0; n < 10000000; n++) { void *elt_and_data; elt.n = n; - elt_and_data = _cairo_skip_list_insert (&list, &elt, TRUE); + elt_and_data = _cairo_skip_list_insert (&list, &elt); assert (elt_and_data != NULL); } _cairo_skip_list_fini (&list); diff --git a/src/cairo-slope.c b/src/cairo-slope.c index 35c53722..15685b78 100644 --- a/src/cairo-slope.c +++ b/src/cairo-slope.c @@ -94,9 +94,7 @@ _cairo_slope_compare (const cairo_slope_t *a, const cairo_slope_t *b) * of b by an infinitesimally small amount, (that is, 'a' will * always be considered less than 'b'). */ - if (((a->dx > 0) != (b->dx > 0)) || - ((a->dy > 0) != (b->dy > 0))) - { + if ((a->dx ^ b->dx) < 0 || (a->dy ^ b->dy) < 0) { if (a->dx > 0 || (a->dx == 0 && a->dy > 0)) return +1; else diff --git a/src/cairo-spans-private.h b/src/cairo-spans-private.h index c706b22a..9f6f1169 100644 --- a/src/cairo-spans-private.h +++ b/src/cairo-spans-private.h @@ -75,22 +75,15 @@ struct _cairo_scan_converter { /* Destroy this scan converter. */ cairo_destroy_func_t destroy; - /* Add an edge to the converter. */ - cairo_status_t - (*add_edge)( - void *abstract_converter, - cairo_fixed_t x1, - cairo_fixed_t y1, - cairo_fixed_t x2, - cairo_fixed_t y2); + /* Add a polygon (set of edges) to the converter. */ + cairo_status_t (*add_polygon) (void *abstract_converter, + const cairo_polygon_t *polygon); /* Generates coverage spans for rows for the added edges and calls * the renderer function for each row. After generating spans the * only valid thing to do with the converter is to destroy it. */ - cairo_status_t - (*generate)( - void *abstract_converter, - cairo_span_renderer_t *renderer); + cairo_status_t (*generate) (void *abstract_converter, + cairo_span_renderer_t *renderer); /* Private status. Read with _cairo_scan_converter_status(). */ cairo_status_t status; @@ -99,12 +92,11 @@ struct _cairo_scan_converter { /* Scan converter constructors. */ cairo_private cairo_scan_converter_t * -_cairo_tor_scan_converter_create( - int xmin, - int ymin, - int xmax, - int ymax, - cairo_fill_rule_t fill_rule); +_cairo_tor_scan_converter_create (int xmin, + int ymin, + int xmax, + int ymax, + cairo_fill_rule_t fill_rule); /* cairo-spans.c: */ @@ -132,14 +124,13 @@ _cairo_span_renderer_set_error (void *abstract_renderer, cairo_status_t error); cairo_private cairo_status_t -_cairo_path_fixed_fill_using_spans (cairo_operator_t op, - const cairo_pattern_t *pattern, - cairo_path_fixed_t *path, - cairo_surface_t *dst, - cairo_fill_rule_t fill_rule, - double tolerance, - cairo_antialias_t antialias, - const cairo_composite_rectangles_t *rects, - cairo_region_t *clip_region); +_cairo_surface_composite_polygon (cairo_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *pattern, + cairo_fill_rule_t fill_rule, + cairo_antialias_t antialias, + const cairo_composite_rectangles_t *rects, + cairo_polygon_t *polygon, + cairo_region_t *clip_region); #endif /* CAIRO_SPANS_PRIVATE_H */ diff --git a/src/cairo-spans.c b/src/cairo-spans.c index 48396b54..369cd582 100644 --- a/src/cairo-spans.c +++ b/src/cairo-spans.c @@ -26,107 +26,7 @@ */ #include "cairoint.h" -typedef struct { - cairo_scan_converter_t *converter; - cairo_point_t current_point; - cairo_point_t first_point; - cairo_bool_t has_first_point; -} scan_converter_filler_t; - -static void -scan_converter_filler_init (scan_converter_filler_t *filler, - cairo_scan_converter_t *converter) -{ - filler->converter = converter; - filler->current_point.x = 0; - filler->current_point.y = 0; - filler->first_point = filler->current_point; - filler->has_first_point = FALSE; -} - -static cairo_status_t -scan_converter_filler_close_path (void *closure) -{ - scan_converter_filler_t *filler = closure; - cairo_status_t status; - - filler->has_first_point = FALSE; - - if (filler->first_point.x == filler->current_point.x && - filler->first_point.y == filler->current_point.y) - { - return CAIRO_STATUS_SUCCESS; - } - - status = filler->converter->add_edge ( - filler->converter, - filler->current_point.x, filler->current_point.y, - filler->first_point.x, filler->first_point.y); - - filler->current_point = filler->first_point; - return status; -} - -static cairo_status_t -scan_converter_filler_move_to (void *closure, - const cairo_point_t *p) -{ - scan_converter_filler_t *filler = closure; - if (filler->has_first_point) { - cairo_status_t status = scan_converter_filler_close_path (closure); - if (status) - return status; - } - filler->current_point.x = p->x; - filler->current_point.y = p->y; - filler->first_point = filler->current_point; - filler->has_first_point = TRUE; - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -scan_converter_filler_line_to (void *closure, - const cairo_point_t *p) -{ - scan_converter_filler_t *filler = closure; - cairo_status_t status; - cairo_point_t to; - - to.x = p->x; - to.y = p->y; - - status = filler->converter->add_edge ( - filler->converter, - filler->current_point.x, filler->current_point.y, - to.x, to.y); - - filler->current_point = to; - - return status; -} - -static cairo_status_t -_cairo_path_fixed_fill_to_scan_converter ( - cairo_path_fixed_t *path, - double tolerance, - cairo_scan_converter_t *converter) -{ - scan_converter_filler_t filler; - cairo_status_t status; - - scan_converter_filler_init (&filler, converter); - - status = _cairo_path_fixed_interpret_flat ( - path, CAIRO_DIRECTION_FORWARD, - scan_converter_filler_move_to, - scan_converter_filler_line_to, - scan_converter_filler_close_path, - &filler, tolerance); - if (status) - return status; - - return scan_converter_filler_close_path (&filler); -} +#include "cairo-fixed-private.h" static cairo_scan_converter_t * _create_scan_converter (cairo_fill_rule_t fill_rule, @@ -135,51 +35,50 @@ _create_scan_converter (cairo_fill_rule_t fill_rule, { if (antialias == CAIRO_ANTIALIAS_NONE) { ASSERT_NOT_REACHED; - return _cairo_scan_converter_create_in_error ( - CAIRO_INT_STATUS_UNSUPPORTED); - } - else { - return _cairo_tor_scan_converter_create ( - rects->mask.x, - rects->mask.y, - rects->mask.x + rects->width, - rects->mask.y + rects->height, - fill_rule); + return NULL; } + + return _cairo_tor_scan_converter_create (rects->mask.x, + rects->mask.y, + rects->mask.x + rects->width, + rects->mask.y + rects->height, + fill_rule); } +/* XXX Add me to the compositor interface. Ok, first create the compositor + * interface, and then add this with associated fallback! + */ cairo_status_t -_cairo_path_fixed_fill_using_spans (cairo_operator_t op, - const cairo_pattern_t *pattern, - cairo_path_fixed_t *path, - cairo_surface_t *dst, - cairo_fill_rule_t fill_rule, - double tolerance, - cairo_antialias_t antialias, - const cairo_composite_rectangles_t *rects, - cairo_region_t *clip_region) +_cairo_surface_composite_polygon (cairo_surface_t *surface, + cairo_operator_t op, + const cairo_pattern_t *pattern, + cairo_fill_rule_t fill_rule, + cairo_antialias_t antialias, + const cairo_composite_rectangles_t *rects, + cairo_polygon_t *polygon, + cairo_region_t *clip_region) { + cairo_span_renderer_t *renderer; + cairo_scan_converter_t *converter; cairo_status_t status; - cairo_span_renderer_t *renderer = _cairo_surface_create_span_renderer ( - op, pattern, dst, antialias, rects, clip_region); - cairo_scan_converter_t *converter = _create_scan_converter ( - fill_rule, antialias, rects); - status = _cairo_path_fixed_fill_to_scan_converter ( - path, tolerance, converter); - if (status) - goto BAIL; + converter = _create_scan_converter (fill_rule, antialias, rects); + status = converter->add_polygon (converter, polygon); + if (unlikely (status)) + goto CLEANUP_CONVERTER; + renderer = _cairo_surface_create_span_renderer (op, pattern, surface, + antialias, rects, + clip_region); status = converter->generate (converter, renderer); - if (status) - goto BAIL; + if (unlikely (status)) + goto CLEANUP_RENDERER; status = renderer->finish (renderer); - if (status) - goto BAIL; - BAIL: + CLEANUP_RENDERER: renderer->destroy (renderer); + CLEANUP_CONVERTER: converter->destroy (converter); return status; } @@ -191,17 +90,11 @@ _cairo_nil_destroy (void *abstract) } static cairo_status_t -_cairo_nil_scan_converter_add_edge (void *abstract_converter, - cairo_fixed_t x1, - cairo_fixed_t y1, - cairo_fixed_t x2, - cairo_fixed_t y2) +_cairo_nil_scan_converter_add_polygon (void *abstract_converter, + const cairo_polygon_t *polygon) { (void) abstract_converter; - (void) x1; - (void) y1; - (void) x2; - (void) y2; + (void) polygon; return _cairo_scan_converter_status (abstract_converter); } @@ -229,7 +122,7 @@ _cairo_scan_converter_set_error (void *abstract_converter, if (error == CAIRO_STATUS_SUCCESS) ASSERT_NOT_REACHED; if (converter->status == CAIRO_STATUS_SUCCESS) { - converter->add_edge = _cairo_nil_scan_converter_add_edge; + converter->add_polygon = _cairo_nil_scan_converter_add_polygon; converter->generate = _cairo_nil_scan_converter_generate; converter->status = error; } diff --git a/src/cairo-surface-fallback.c b/src/cairo-surface-fallback.c index 3bfb17de..7ab12b4d 100644 --- a/src/cairo-surface-fallback.c +++ b/src/cairo-surface-fallback.c @@ -34,6 +34,8 @@ * * Contributor(s): * Carl D. Worth <cworth@cworth.org> + * Joonas Pihlaja <jpihlaja@cc.helsinki.fi> + * Chris Wilson <chris@chris-wilson.co.uk> */ #include "cairoint.h" @@ -41,6 +43,7 @@ #include "cairo-surface-fallback-private.h" #include "cairo-clip-private.h" #include "cairo-region-private.h" +#include "cairo-spans-private.h" typedef struct { cairo_surface_t *dst; @@ -739,8 +742,15 @@ _clip_and_composite_trapezoids (const cairo_pattern_t *src, } } - /* Otherwise we need to render the trapezoids to a mask and composite - * in the usual fashion. + /* No fast path, exclude self-intersections and clip trapezoids. */ + if (traps->has_intersections) { + status = _cairo_bentley_ottmann_tessellate_traps (traps); + if (unlikely (status)) + return status; + } + + /* Otherwise render the trapezoids to a mask and composite in the usual + * fashion. */ if (_cairo_operator_bounded_by_mask (op)) { cairo_rectangle_int_t trap_extents; @@ -754,20 +764,20 @@ _clip_and_composite_trapezoids (const cairo_pattern_t *src, traps_info.traps = traps; traps_info.antialias = antialias; + return _clip_and_composite (clip, op, src, _composite_traps_draw_func, &traps_info, dst, extents); } typedef struct { - cairo_path_fixed_t *path; + cairo_polygon_t *polygon; cairo_fill_rule_t fill_rule; - double tolerance; cairo_antialias_t antialias; -} cairo_composite_spans_fill_info_t; +} cairo_composite_spans_info_t; static cairo_status_t -_composite_spans_fill_func (void *closure, +_composite_spans_draw_func (void *closure, cairo_operator_t op, const cairo_pattern_t *src, cairo_surface_t *dst, @@ -777,7 +787,7 @@ _composite_spans_fill_func (void *closure, cairo_region_t *clip_region) { cairo_composite_rectangles_t rects; - cairo_composite_spans_fill_info_t *info = closure; + cairo_composite_spans_info_t *info = closure; _cairo_composite_rectangles_init ( &rects, extents->x, extents->y, @@ -789,12 +799,12 @@ _composite_spans_fill_func (void *closure, rects.dst.x -= dst_x; rects.dst.y -= dst_y; - return _cairo_path_fixed_fill_using_spans (op, src, info->path, dst, - info->fill_rule, - info->tolerance, - info->antialias, - &rects, - clip_region); + return _cairo_surface_composite_polygon (dst, op, src, + info->fill_rule, + info->antialias, + &rects, + info->polygon, + clip_region); } static cairo_bool_t @@ -930,11 +940,12 @@ _cairo_surface_fallback_stroke (cairo_surface_t *surface, cairo_antialias_t antialias, cairo_clip_t *clip) { - cairo_status_t status; + cairo_polygon_t polygon; cairo_traps_t traps; cairo_box_t box; cairo_rectangle_int_t extents; cairo_bool_t is_bounded; + cairo_status_t status; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); @@ -952,22 +963,70 @@ _cairo_surface_fallback_stroke (cairo_surface_t *surface, _cairo_box_from_rectangle (&box, &extents); + _cairo_polygon_init (&polygon); + _cairo_polygon_limit (&polygon, &box); + _cairo_traps_init (&traps); _cairo_traps_limit (&traps, &box); - status = _cairo_path_fixed_stroke_to_traps (path, - stroke_style, - ctm, ctm_inverse, - tolerance, - &traps); + if (path->is_rectilinear) { + status = _cairo_path_fixed_stroke_rectilinear_to_traps (path, + stroke_style, + ctm, + &traps); + if (likely (status == CAIRO_STATUS_SUCCESS)) + goto DO_TRAPS; + + if (_cairo_status_is_error (status)) + goto CLEANUP; + } + + status = _cairo_path_fixed_stroke_to_polygon (path, + stroke_style, + ctm, ctm_inverse, + tolerance, + &polygon); if (unlikely (status)) - goto FAIL; + goto CLEANUP; + + if (_cairo_operator_bounded_by_mask (op)) { + cairo_rectangle_int_t polygon_extents; - status = _clip_and_composite_trapezoids (source, op, - surface, &traps, antialias, + _cairo_box_round_to_rectangle (&polygon.extents, &polygon_extents); + if (! _cairo_rectangle_intersect (&extents, &polygon_extents)) + goto CLEANUP; + } + + if (antialias != CAIRO_ANTIALIAS_NONE && + _cairo_surface_check_span_renderer (op, source, surface, + antialias, NULL)) + { + cairo_composite_spans_info_t info; + + info.polygon = &polygon; + info.fill_rule = CAIRO_FILL_RULE_WINDING; + info.antialias = antialias; + + status = _clip_and_composite (clip, op, source, + _composite_spans_draw_func, + &info, surface, &extents); + goto CLEANUP; + } + + /* Fall back to trapezoid fills. */ + status = _cairo_bentley_ottmann_tessellate_polygon (&traps, + &polygon, + CAIRO_FILL_RULE_WINDING); + if (unlikely (status)) + goto CLEANUP; + + DO_TRAPS: + status = _clip_and_composite_trapezoids (source, op, surface, + &traps, antialias, clip, &extents); - FAIL: + CLEANUP: _cairo_traps_fini (&traps); + _cairo_polygon_fini (&polygon); return status; } @@ -982,11 +1041,12 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, cairo_antialias_t antialias, cairo_clip_t *clip) { - cairo_status_t status; + cairo_polygon_t polygon; cairo_traps_t traps; cairo_box_t box; cairo_rectangle_int_t extents; cairo_bool_t is_bounded; + cairo_status_t status; is_bounded = _cairo_surface_get_extents (surface, &extents); assert (is_bounded || clip); @@ -1002,71 +1062,69 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, if (! _rectangle_intersect_clip (&extents, clip)) return CAIRO_STATUS_SUCCESS; - /* XXX future direction: - status = _cairo_path_fixed_fill_to_region (path, fill_rule, ®ion); - if (status != CAIRO_STATUS_INT_UNSUPPORTED) { - if (unlikely (status)) - return status; + _cairo_box_from_rectangle (&box, &extents); - status = _clip_and_composite_region (); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - return status; - } - */ - - /* Ask if the surface would like to render this combination of - * op/source/dst/antialias with spans or not, but don't actually - * make a renderer yet. We'll try to hit the region optimisations - * in _clip_and_composite_trapezoids() if it looks like the path - * is a region. */ - /* TODO: Until we have a mono scan converter we won't even try - * to use spans for CAIRO_ANTIALIAS_NONE. */ - /* TODO: The region filling code should be lifted from - * _clip_and_composite_trapezoids() and given first priority - * explicitly before deciding between spans and trapezoids. */ - if (antialias != CAIRO_ANTIALIAS_NONE && ! path->is_rectilinear && - _cairo_surface_check_span_renderer ( - op, source, surface, antialias, NULL)) + _cairo_polygon_init (&polygon); + _cairo_polygon_limit (&polygon, &box); + + _cairo_traps_init (&traps); + _cairo_traps_limit (&traps, &box); + + if (path->is_rectilinear) { + status = _cairo_path_fixed_fill_rectilinear_to_traps (path, + fill_rule, + &traps); + if (likely (status == CAIRO_STATUS_SUCCESS)) + goto DO_TRAPS; + + if (_cairo_status_is_error (status)) + goto CLEANUP; + } + + status = _cairo_path_fixed_fill_to_polygon (path, + tolerance, + &polygon); + if (unlikely (status)) + goto CLEANUP; + + if (_cairo_operator_bounded_by_mask (op)) { + cairo_rectangle_int_t polygon_extents; + + _cairo_box_round_to_rectangle (&polygon.extents, &polygon_extents); + if (! _cairo_rectangle_intersect (&extents, &polygon_extents)) + goto CLEANUP; + } + + if (antialias != CAIRO_ANTIALIAS_NONE && + _cairo_surface_check_span_renderer (op, source, surface, + antialias, NULL)) { - cairo_composite_spans_fill_info_t info; - info.path = path; + cairo_composite_spans_info_t info; + + info.polygon = &polygon; info.fill_rule = fill_rule; - info.tolerance = tolerance; info.antialias = antialias; - if (_cairo_operator_bounded_by_mask (op)) { - cairo_rectangle_int_t path_extents; - - _cairo_path_fixed_approximate_clip_extents (path, - &path_extents); - if (! _cairo_rectangle_intersect (&extents, &path_extents)) - return CAIRO_STATUS_SUCCESS; - } - - return _clip_and_composite (clip, op, source, - _composite_spans_fill_func, - &info, - surface, - &extents); + status = _clip_and_composite (clip, op, source, + _composite_spans_draw_func, + &info, surface, &extents); + goto CLEANUP; } /* Fall back to trapezoid fills. */ - _cairo_box_from_rectangle (&box, &extents); - _cairo_traps_init (&traps); - _cairo_traps_limit (&traps, &box); - - status = _cairo_path_fixed_fill_to_traps (path, - fill_rule, - tolerance, - &traps); + status = _cairo_bentley_ottmann_tessellate_polygon (&traps, + &polygon, + fill_rule); if (unlikely (status)) - goto FAIL; + goto CLEANUP; + DO_TRAPS: status = _clip_and_composite_trapezoids (source, op, surface, &traps, antialias, clip, &extents); - FAIL: + CLEANUP: _cairo_traps_fini (&traps); + _cairo_polygon_fini (&polygon); return status; } diff --git a/src/cairo-tor-scan-converter.c b/src/cairo-tor-scan-converter.c index f6b5b430..75cda4fe 100644 --- a/src/cairo-tor-scan-converter.c +++ b/src/cairo-tor-scan-converter.c @@ -199,11 +199,8 @@ glitter_scan_converter_reset( * converter should be reset or destroyed. Dir must be +1 or -1, * with the latter reversing the orientation of the edge. */ I glitter_status_t -glitter_scan_converter_add_edge( - glitter_scan_converter_t *converter, - glitter_input_scaled_t x1, glitter_input_scaled_t y1, - glitter_input_scaled_t x2, glitter_input_scaled_t y2, - int dir); +glitter_scan_converter_add_edge (glitter_scan_converter_t *converter, + const cairo_edge_t *edge); /* Render the polygon in the scan converter to the given A8 format * image raster. Only the pixels accessible as pixels[y*stride+x] for @@ -623,10 +620,8 @@ _pool_alloc_from_new_chunk( } if (NULL == chunk) { - chunk = _pool_chunk_create( - pool->current, - capacity); - if (NULL == chunk) + chunk = _pool_chunk_create (pool->current, capacity); + if (unlikely (NULL == chunk)) return NULL; } pool->current = chunk; @@ -643,9 +638,7 @@ _pool_alloc_from_new_chunk( * allocation failures. The pool retains ownership of the returned * memory. */ inline static void * -pool_alloc( - struct pool *pool, - size_t size) +pool_alloc (struct pool *pool, size_t size) { struct _pool_chunk *chunk = pool->current; @@ -653,15 +646,14 @@ pool_alloc( void *obj = ((unsigned char*)chunk + sizeof(*chunk) + chunk->size); chunk->size += size; return obj; - } - else { + } else { return _pool_alloc_from_new_chunk(pool, size); } } /* Relinquish all pool_alloced memory back to the pool. */ static void -pool_reset(struct pool *pool) +pool_reset (struct pool *pool) { /* Transfer all used chunks to the chunk free list. */ struct _pool_chunk *chunk = pool->current; @@ -680,19 +672,18 @@ pool_reset(struct pool *pool) /* Rewinds the cell list's cursor to the beginning. After rewinding * we're good to cell_list_find() the cell any x coordinate. */ inline static void -cell_list_rewind(struct cell_list *cells) +cell_list_rewind (struct cell_list *cells) { cells->cursor = &cells->head; } /* Rewind the cell list if its cursor has been advanced past x. */ inline static void -cell_list_maybe_rewind(struct cell_list *cells, int x) +cell_list_maybe_rewind (struct cell_list *cells, int x) { struct cell *tail = *cells->cursor; - if (tail->x > x) { - cell_list_rewind(cells); - } + if (tail->x > x) + cell_list_rewind (cells); } static void @@ -704,24 +695,24 @@ cell_list_init(struct cell_list *cells) cells->tail.next = NULL; cells->tail.x = INT_MAX; cells->head = &cells->tail; - cell_list_rewind(cells); + cell_list_rewind (cells); } static void cell_list_fini(struct cell_list *cells) { - pool_fini(cells->cell_pool.base); - cell_list_init(cells); + pool_fini (cells->cell_pool.base); + cell_list_init (cells); } /* Empty the cell list. This is called at the start of every pixel * row. */ inline static void -cell_list_reset(struct cell_list *cells) +cell_list_reset (struct cell_list *cells) { - cell_list_rewind(cells); + cell_list_rewind (cells); cells->head = &cells->tail; - pool_reset(cells->cell_pool.base); + pool_reset (cells->cell_pool.base); } /* Find a cell at the given x-coordinate. Returns %NULL if a new cell @@ -730,7 +721,7 @@ cell_list_reset(struct cell_list *cells) * cell_list_rewind(). Ownership of the returned cell is retained by * the cell list. */ inline static struct cell * -cell_list_find(struct cell_list *cells, int x) +cell_list_find (struct cell_list *cells, int x) { struct cell **cursor = cells->cursor; struct cell *tail; @@ -749,11 +740,12 @@ cell_list_find(struct cell_list *cells, int x) if (tail->x == x) { return tail; } else { - struct cell *cell = pool_alloc( - cells->cell_pool.base, - sizeof(struct cell)); - if (NULL == cell) + struct cell *cell; + + cell = pool_alloc (cells->cell_pool.base, sizeof (struct cell)); + if (unlikely (NULL == cell)) return NULL; + *cursor = cell; cell->next = tail; cell->x = x; @@ -785,17 +777,18 @@ cell_list_find_pair(struct cell_list *cells, int x1, int x2) cell1 = *cursor; if (cell1->x > x1) break; + if (cell1->x == x1) goto found_first; + cursor = &cell1->next; }); } /* New first cell at x1. */ - newcell = pool_alloc( - cells->cell_pool.base, - sizeof(struct cell)); - if (NULL != newcell) { + newcell = pool_alloc (cells->cell_pool.base, + sizeof (struct cell)); + if (likely (NULL != newcell)) { *cursor = newcell; newcell->next = cell1; newcell->x = x1; @@ -818,10 +811,9 @@ cell_list_find_pair(struct cell_list *cells, int x1, int x2) } /* New second cell at x2. */ - newcell = pool_alloc( - cells->cell_pool.base, - sizeof(struct cell)); - if (NULL != newcell) { + newcell = pool_alloc (cells->cell_pool.base, + sizeof (struct cell)); + if (likely (NULL != newcell)) { *cursor = newcell; newcell->next = cell2; newcell->x = x2; @@ -849,12 +841,13 @@ cell_list_add_unbounded_subspan( GRID_X_TO_INT_FRAC(x, ix, fx); - cell = cell_list_find(cells, ix); - if (cell) { + cell = cell_list_find (cells, ix); + if (likely (cell != NULL)) { cell->uncovered_area += 2*fx; cell->covered_height++; return GLITTER_STATUS_SUCCESS; } + return GLITTER_STATUS_NO_MEMORY; } @@ -874,17 +867,16 @@ cell_list_add_subspan( if (ix1 != ix2) { struct cell_pair p; p = cell_list_find_pair(cells, ix1, ix2); - if (p.cell1 && p.cell2) { + if (likely (p.cell1 != NULL && p.cell2 != NULL)) { p.cell1->uncovered_area += 2*fx1; ++p.cell1->covered_height; p.cell2->uncovered_area -= 2*fx2; --p.cell2->covered_height; return GLITTER_STATUS_SUCCESS; } - } - else { + } else { struct cell *cell = cell_list_find(cells, ix1); - if (cell) { + if (likely (cell != NULL)) { cell->uncovered_area += 2*(fx1-fx2); return GLITTER_STATUS_SUCCESS; } @@ -938,8 +930,9 @@ cell_list_render_edge( /* We always know that ix1 is >= the cell list cursor in this * case due to the no-intersections precondition. */ struct cell *cell = cell_list_find(cells, ix1); - if (NULL == cell) + if (unlikely (NULL == cell)) return GLITTER_STATUS_NO_MEMORY; + cell->covered_height += sign*GRID_Y; cell->uncovered_area += sign*(fx1 + fx2)*GRID_Y; return GLITTER_STATUS_SUCCESS; @@ -988,7 +981,7 @@ cell_list_render_edge( cell_list_maybe_rewind(cells, ix1); pair = cell_list_find_pair(cells, ix1, ix1+1); - if (!pair.cell1 || !pair.cell2) + if (unlikely (!pair.cell1 || !pair.cell2)) return GLITTER_STATUS_NO_MEMORY; pair.cell1->uncovered_area += sign*y.quo*(GRID_X + fx1); @@ -1016,7 +1009,7 @@ cell_list_render_edge( ++ix1; cell = cell_list_find(cells, ix1); - if (NULL == cell) + if (unlikely (NULL == cell)) return GLITTER_STATUS_NO_MEMORY; } while (ix1 != ix2); @@ -1030,22 +1023,22 @@ cell_list_render_edge( } static void -polygon_init(struct polygon *polygon) +polygon_init (struct polygon *polygon) { polygon->ymin = polygon->ymax = 0; polygon->y_buckets = polygon->y_buckets_embedded; - pool_init(polygon->edge_pool.base, - 8192 - sizeof(struct _pool_chunk), - sizeof(polygon->edge_pool.embedded)); + pool_init (polygon->edge_pool.base, + 8192 - sizeof (struct _pool_chunk), + sizeof (polygon->edge_pool.embedded)); } static void -polygon_fini(struct polygon *polygon) +polygon_fini (struct polygon *polygon) { if (polygon->y_buckets != polygon->y_buckets_embedded) - free(polygon->y_buckets); - pool_fini(polygon->edge_pool.base); - polygon_init(polygon); + free (polygon->y_buckets); + + pool_fini (polygon->edge_pool.base); } /* Empties the polygon of all edges. The polygon is then prepared to @@ -1063,11 +1056,12 @@ polygon_reset( pool_reset(polygon->edge_pool.base); - if (h > 0x7FFFFFFFU - EDGE_Y_BUCKET_HEIGHT) + if (unlikely (h > 0x7FFFFFFFU - EDGE_Y_BUCKET_HEIGHT)) goto bail_no_mem; /* even if you could, you wouldn't want to. */ if (polygon->y_buckets != polygon->y_buckets_embedded) free (polygon->y_buckets); + polygon->y_buckets = polygon->y_buckets_embedded; if (num_buckets > ARRAY_LENGTH (polygon->y_buckets_embedded)) { polygon->y_buckets = _cairo_malloc_ab (num_buckets, @@ -1099,11 +1093,8 @@ _polygon_insert_edge_into_its_y_bucket( } inline static glitter_status_t -polygon_add_edge( - struct polygon *polygon, - int x0, int y0, - int x1, int y1, - int dir) +polygon_add_edge (struct polygon *polygon, + const cairo_edge_t *edge) { struct edge *e; grid_scaled_x_t dx; @@ -1112,54 +1103,45 @@ polygon_add_edge( grid_scaled_y_t ymin = polygon->ymin; grid_scaled_y_t ymax = polygon->ymax; - if (y0 == y1) - return GLITTER_STATUS_SUCCESS; + assert (edge->bottom > edge->top); - if (y0 > y1) { - int tmp; - tmp = x0; x0 = x1; x1 = tmp; - tmp = y0; y0 = y1; y1 = tmp; - dir = -dir; - } - - if (y0 >= ymax || y1 <= ymin) + if (unlikely (edge->top >= ymax || edge->bottom <= ymin)) return GLITTER_STATUS_SUCCESS; - e = pool_alloc(polygon->edge_pool.base, - sizeof(struct edge)); - if (NULL == e) + e = pool_alloc (polygon->edge_pool.base, sizeof (struct edge)); + if (unlikely (NULL == e)) return GLITTER_STATUS_NO_MEMORY; - dx = x1 - x0; - dy = y1 - y0; + dx = edge->line.p2.x - edge->line.p1.x; + dy = edge->line.p2.y - edge->line.p1.y; e->dy = dy; - e->dxdy = floored_divrem(dx, dy); + e->dxdy = floored_divrem (dx, dy); - if (ymin <= y0) { - ytop = y0; - e->x.quo = x0; - e->x.rem = 0; - } - else { + if (ymin <= edge->top) + ytop = edge->top; + else ytop = ymin; - e->x = floored_muldivrem(ymin - y0, dx, dy); - e->x.quo += x0; + if (ytop == edge->line.p1.y) { + e->x.quo = edge->line.p1.x; + e->x.rem = 0; + } else { + e->x = floored_muldivrem (ytop - edge->line.p1.y, dx, dy); + e->x.quo += edge->line.p1.x; } - e->dir = dir; + e->dir = edge->dir; e->ytop = ytop; - ybot = y1 < ymax ? y1 : ymax; + ybot = edge->bottom < ymax ? edge->bottom : ymax; e->height_left = ybot - ytop; if (e->height_left >= GRID_Y) { - e->dxdy_full = floored_muldivrem(GRID_Y, dx, dy); - } - else { + e->dxdy_full = floored_muldivrem (GRID_Y, dx, dy); + } else { e->dxdy_full.quo = 0; e->dxdy_full.rem = 0; } - _polygon_insert_edge_into_its_y_bucket(polygon, e); + _polygon_insert_edge_into_its_y_bucket (polygon, e); e->x.rem -= dy; /* Bias the remainder for faster * edge advancement. */ @@ -1167,8 +1149,7 @@ polygon_add_edge( } static void -active_list_reset( - struct active_list *active) +active_list_reset (struct active_list *active) { active->head = NULL; active->min_height = 0; @@ -1292,8 +1273,7 @@ active_list_merge_edges_from_polygon( subrow_edges = tail; if (tail->height_left < min_height) min_height = tail->height_left; - } - else { + } else { ptail = &tail->next; } } @@ -1347,9 +1327,8 @@ active_list_substep_edges( } inline static glitter_status_t -apply_nonzero_fill_rule_for_subrow( - struct active_list *active, - struct cell_list *coverages) +apply_nonzero_fill_rule_for_subrow (struct active_list *active, + struct cell_list *coverages) { struct edge *edge = active->head; int winding = 0; @@ -1357,25 +1336,26 @@ apply_nonzero_fill_rule_for_subrow( int xend; int status; - cell_list_rewind(coverages); + cell_list_rewind (coverages); while (NULL != edge) { xstart = edge->x.quo; winding = edge->dir; while (1) { edge = edge->next; - if (NULL == edge) { - return cell_list_add_unbounded_subspan( - coverages, xstart); - } + if (NULL == edge) + return cell_list_add_unbounded_subspan (coverages, xstart); + winding += edge->dir; - if (0 == winding) - break; + if (0 == winding) { + if (edge->next == NULL || edge->next->x.quo != edge->x.quo) + break; + } } xend = edge->x.quo; - status = cell_list_add_subspan(coverages, xstart, xend); - if (status) + status = cell_list_add_subspan (coverages, xstart, xend); + if (unlikely (status)) return status; edge = edge->next; @@ -1385,29 +1365,33 @@ apply_nonzero_fill_rule_for_subrow( } static glitter_status_t -apply_evenodd_fill_rule_for_subrow( - struct active_list *active, - struct cell_list *coverages) +apply_evenodd_fill_rule_for_subrow (struct active_list *active, + struct cell_list *coverages) { struct edge *edge = active->head; int xstart; int xend; int status; - cell_list_rewind(coverages); + cell_list_rewind (coverages); while (NULL != edge) { xstart = edge->x.quo; - edge = edge->next; - if (NULL == edge) { - return cell_list_add_unbounded_subspan( - coverages, xstart); + while (1) { + edge = edge->next; + if (NULL == edge) + return cell_list_add_unbounded_subspan (coverages, xstart); + + if (edge->next == NULL || edge->next->x.quo != edge->x.quo) + break; + + edge = edge->next; } xend = edge->x.quo; - status = cell_list_add_subspan(coverages, xstart, xend); - if (status) + status = cell_list_add_subspan (coverages, xstart, xend); + if (unlikely (status)) return status; edge = edge->next; @@ -1417,9 +1401,8 @@ apply_evenodd_fill_rule_for_subrow( } static glitter_status_t -apply_nonzero_fill_rule_and_step_edges( - struct active_list *active, - struct cell_list *coverages) +apply_nonzero_fill_rule_and_step_edges (struct active_list *active, + struct cell_list *coverages) { struct edge **cursor = &active->head; struct edge *left_edge; @@ -1431,32 +1414,30 @@ apply_nonzero_fill_rule_and_step_edges( int winding = left_edge->dir; left_edge->height_left -= GRID_Y; - if (left_edge->height_left) { + if (left_edge->height_left) cursor = &left_edge->next; - } - else { + else *cursor = left_edge->next; - } while (1) { right_edge = *cursor; - - if (NULL == right_edge) { - return cell_list_render_edge( - coverages, left_edge, +1); - } + if (NULL == right_edge) + return cell_list_render_edge (coverages, left_edge, +1); right_edge->height_left -= GRID_Y; - if (right_edge->height_left) { + if (right_edge->height_left) cursor = &right_edge->next; - } - else { + else *cursor = right_edge->next; - } winding += right_edge->dir; - if (0 == winding) - break; + if (0 == winding) { + if (right_edge->next == NULL || + right_edge->next->x.quo != right_edge->x.quo) + { + break; + } + } right_edge->x.quo += right_edge->dxdy_full.quo; right_edge->x.rem += right_edge->dxdy_full.rem; @@ -1466,13 +1447,12 @@ apply_nonzero_fill_rule_and_step_edges( } } - status = cell_list_render_edge( - coverages, left_edge, +1); - if (status) + status = cell_list_render_edge (coverages, left_edge, +1); + if (unlikely (status)) return status; - status = cell_list_render_edge( - coverages, right_edge, -1); - if (status) + + status = cell_list_render_edge (coverages, right_edge, -1); + if (unlikely (status)) return status; left_edge = *cursor; @@ -1482,9 +1462,8 @@ apply_nonzero_fill_rule_and_step_edges( } static glitter_status_t -apply_evenodd_fill_rule_and_step_edges( - struct active_list *active, - struct cell_list *coverages) +apply_evenodd_fill_rule_and_step_edges (struct active_list *active, + struct cell_list *coverages) { struct edge **cursor = &active->head; struct edge *left_edge; @@ -1495,35 +1474,42 @@ apply_evenodd_fill_rule_and_step_edges( struct edge *right_edge; left_edge->height_left -= GRID_Y; - if (left_edge->height_left) { + if (left_edge->height_left) cursor = &left_edge->next; - } - else { + else *cursor = left_edge->next; - } - right_edge = *cursor; + while (1) { + right_edge = *cursor; + if (NULL == right_edge) + return cell_list_render_edge (coverages, left_edge, +1); - if (NULL == right_edge) { - return cell_list_render_edge( - coverages, left_edge, +1); - } + right_edge->height_left -= GRID_Y; + if (right_edge->height_left) + cursor = &right_edge->next; + else + *cursor = right_edge->next; - right_edge->height_left -= GRID_Y; - if (right_edge->height_left) { - cursor = &right_edge->next; - } - else { - *cursor = right_edge->next; + if (right_edge->next == NULL || + right_edge->next->x.quo != right_edge->x.quo) + { + break; + } + + right_edge->x.quo += right_edge->dxdy_full.quo; + right_edge->x.rem += right_edge->dxdy_full.rem; + if (right_edge->x.rem >= 0) { + ++right_edge->x.quo; + right_edge->x.rem -= right_edge->dy; + } } - status = cell_list_render_edge( - coverages, left_edge, +1); - if (status) + status = cell_list_render_edge (coverages, left_edge, +1); + if (unlikely (status)) return status; - status = cell_list_render_edge( - coverages, right_edge, -1); - if (status) + + status = cell_list_render_edge (coverages, right_edge, -1); + if (unlikely (status)) return status; left_edge = *cursor; @@ -1692,26 +1678,28 @@ glitter_scan_converter_reset( } while (0) I glitter_status_t -glitter_scan_converter_add_edge( - glitter_scan_converter_t *converter, - glitter_input_scaled_t x1, glitter_input_scaled_t y1, - glitter_input_scaled_t x2, glitter_input_scaled_t y2, - int dir) +glitter_scan_converter_add_edge (glitter_scan_converter_t *converter, + const cairo_edge_t *edge) { - /* XXX: possible overflows if GRID_X/Y > 2**GLITTER_INPUT_BITS */ - grid_scaled_y_t sx1, sy1; - grid_scaled_y_t sx2, sy2; + cairo_edge_t e; - INPUT_TO_GRID_Y(y1, sy1); - INPUT_TO_GRID_Y(y2, sy2); - if (sy1 == sy2) + INPUT_TO_GRID_Y (edge->top, e.top); + INPUT_TO_GRID_Y (edge->bottom, e.bottom); + if (e.top == e.bottom) + return GLITTER_STATUS_SUCCESS; + + /* XXX: possible overflows if GRID_X/Y > 2**GLITTER_INPUT_BITS */ + INPUT_TO_GRID_Y (edge->line.p1.y, e.line.p1.y); + INPUT_TO_GRID_Y (edge->line.p2.y, e.line.p2.y); + if (e.line.p1.y == e.line.p2.y) return GLITTER_STATUS_SUCCESS; - INPUT_TO_GRID_X(x1, sx1); - INPUT_TO_GRID_X(x2, sx2); + INPUT_TO_GRID_X (edge->line.p1.x, e.line.p1.x); + INPUT_TO_GRID_X (edge->line.p2.x, e.line.p2.x); - return polygon_add_edge( - converter->polygon, sx1, sy1, sx2, sy2, dir); + e.dir = edge->dir; + + return polygon_add_edge (converter->polygon, &e); } #ifndef GLITTER_BLIT_COVERAGES_BEGIN @@ -1756,58 +1744,54 @@ glitter_scan_converter_render( /* Determine if we can ignore this row or use the full pixel * stepper. */ - if (GRID_Y == EDGE_Y_BUCKET_HEIGHT - && !polygon->y_buckets[i]) - { - if (!active->head) { - GLITTER_BLIT_COVERAGES_EMPTY(i+ymin_i, xmin_i, xmax_i); + if (GRID_Y == EDGE_Y_BUCKET_HEIGHT && ! polygon->y_buckets[i]) { + if (! active->head) { + GLITTER_BLIT_COVERAGES_EMPTY (i+ymin_i, xmin_i, xmax_i); continue; } - do_full_step = active_list_can_step_full_row(active); + + do_full_step = active_list_can_step_full_row (active); } - cell_list_reset(coverages); + cell_list_reset (coverages); if (do_full_step) { /* Step by a full pixel row's worth. */ if (nonzero_fill) { - status = apply_nonzero_fill_rule_and_step_edges( - active, coverages); - } - else { - status = apply_evenodd_fill_rule_and_step_edges( - active, coverages); + status = apply_nonzero_fill_rule_and_step_edges (active, + coverages); + } else { + status = apply_evenodd_fill_rule_and_step_edges (active, + coverages); } - } - else { + } else { /* Subsample this row. */ grid_scaled_y_t suby; for (suby = 0; suby < GRID_Y; suby++) { grid_scaled_y_t y = (i+ymin_i)*GRID_Y + suby; - active_list_merge_edges_from_polygon( - active, y, polygon); + active_list_merge_edges_from_polygon (active, y, polygon); - if (nonzero_fill) - status |= apply_nonzero_fill_rule_for_subrow( - active, coverages); - else - status |= apply_evenodd_fill_rule_for_subrow( - active, coverages); + if (nonzero_fill) { + status |= apply_nonzero_fill_rule_for_subrow (active, + coverages); + } else { + status |= apply_evenodd_fill_rule_for_subrow (active, + coverages); + } active_list_substep_edges(active); } } - if (status) + if (unlikely (status)) return status; GLITTER_BLIT_COVERAGES(coverages, i+ymin_i, xmin_i, xmax_i); - if (!active->head) { + if (! active->head) { active->min_height = INT_MAX; - } - else { + } else { active->min_height -= GRID_Y; } } @@ -1860,7 +1844,7 @@ blit_with_span_renderer( /* Allocate enough spans for the row. */ pool_reset (span_pool); spans = pool_alloc (span_pool, sizeof(spans[0])*num_spans); - if (spans == NULL) + if (unlikely (spans == NULL)) return GLITTER_STATUS_NO_MEMORY; num_spans = 0; @@ -1906,6 +1890,7 @@ blit_empty_with_span_renderer (cairo_span_renderer_t *renderer, int y) struct _cairo_tor_scan_converter { cairo_scan_converter_t base; + glitter_scan_converter_t converter[1]; cairo_fill_rule_t fill_rule; @@ -1918,9 +1903,9 @@ struct _cairo_tor_scan_converter { typedef struct _cairo_tor_scan_converter cairo_tor_scan_converter_t; static void -_cairo_tor_scan_converter_destroy(void *abstract_converter) +_cairo_tor_scan_converter_destroy (void *converter) { - cairo_tor_scan_converter_t *self = abstract_converter; + cairo_tor_scan_converter_t *self = converter; if (self == NULL) { return; } @@ -1930,69 +1915,70 @@ _cairo_tor_scan_converter_destroy(void *abstract_converter) } static cairo_status_t -_cairo_tor_scan_converter_add_edge( - void *abstract_converter, - cairo_fixed_t x1, - cairo_fixed_t y1, - cairo_fixed_t x2, - cairo_fixed_t y2) +_cairo_tor_scan_converter_add_polygon (void *converter, + const cairo_polygon_t *polygon) { - cairo_tor_scan_converter_t *self = abstract_converter; + cairo_tor_scan_converter_t *self = converter; cairo_status_t status; - status = glitter_scan_converter_add_edge ( - self->converter, - x1, y1, x2, y2, +1); - if (status) { - return _cairo_scan_converter_set_error (self, - _cairo_error (status)); + int i; + + for (i = 0; i < polygon->num_edges; i++) { + status = glitter_scan_converter_add_edge (self->converter, + &polygon->edges[i]); + if (unlikely (status)) { + return _cairo_scan_converter_set_error (self, + _cairo_error (status)); + } } + return CAIRO_STATUS_SUCCESS; } static cairo_status_t -_cairo_tor_scan_converter_generate( - void *abstract_converter, - cairo_span_renderer_t *renderer) +_cairo_tor_scan_converter_generate (void *converter, + cairo_span_renderer_t *renderer) { - cairo_tor_scan_converter_t *self = abstract_converter; - cairo_status_t status = glitter_scan_converter_render ( - self->converter, - self->fill_rule == CAIRO_FILL_RULE_WINDING, - renderer, - self->span_pool.base); - if (status) { - return _cairo_scan_converter_set_error (self, - _cairo_error (status)); - } + cairo_tor_scan_converter_t *self = converter; + cairo_status_t status; + + status = glitter_scan_converter_render (self->converter, + self->fill_rule == CAIRO_FILL_RULE_WINDING, + renderer, + self->span_pool.base); + if (unlikely (status)) + return _cairo_scan_converter_set_error (self, _cairo_error (status)); + return CAIRO_STATUS_SUCCESS; } cairo_scan_converter_t * -_cairo_tor_scan_converter_create( - int xmin, - int ymin, - int xmax, - int ymax, - cairo_fill_rule_t fill_rule) +_cairo_tor_scan_converter_create (int xmin, + int ymin, + int xmax, + int ymax, + cairo_fill_rule_t fill_rule) { + cairo_tor_scan_converter_t *self; cairo_status_t status; - cairo_tor_scan_converter_t *self = - calloc (1, sizeof(struct _cairo_tor_scan_converter)); - if (self == NULL) + + self = calloc (1, sizeof(struct _cairo_tor_scan_converter)); + if (unlikely (self == NULL)) { + status = _cairo_error (CAIRO_STATUS_NO_MEMORY); goto bail_nomem; + } - self->base.destroy = &_cairo_tor_scan_converter_destroy; - self->base.add_edge = &_cairo_tor_scan_converter_add_edge; - self->base.generate = &_cairo_tor_scan_converter_generate; + self->base.destroy = _cairo_tor_scan_converter_destroy; + self->base.add_polygon = _cairo_tor_scan_converter_add_polygon; + self->base.generate = _cairo_tor_scan_converter_generate; pool_init (self->span_pool.base, 250 * sizeof(self->span_pool.embedded[0]), sizeof(self->span_pool.embedded)); _glitter_scan_converter_init (self->converter); - status = glitter_scan_converter_reset ( - self->converter, xmin, ymin, xmax, ymax); - if (status != CAIRO_STATUS_SUCCESS) + status = glitter_scan_converter_reset (self->converter, + xmin, ymin, xmax, ymax); + if (unlikely (status)) goto bail; self->fill_rule = fill_rule; @@ -2002,5 +1988,5 @@ _cairo_tor_scan_converter_create( bail: self->base.destroy(&self->base); bail_nomem: - return _cairo_scan_converter_create_in_error (CAIRO_STATUS_NO_MEMORY); + return _cairo_scan_converter_create_in_error (status); } diff --git a/src/cairo-toy-font-face.c b/src/cairo-toy-font-face.c index a9b600a5..e7c841a9 100644 --- a/src/cairo-toy-font-face.c +++ b/src/cairo-toy-font-face.c @@ -520,5 +520,6 @@ _cairo_toy_font_face_reset_static_data (void) cairo_toy_font_face_hash_table = NULL; CAIRO_MUTEX_UNLOCK (_cairo_toy_font_face_mutex); - _cairo_hash_table_destroy (hash_table); + if (hash_table != NULL) + _cairo_hash_table_destroy (hash_table); } diff --git a/src/cairo-traps.c b/src/cairo-traps.c index 343d77fd..057e9506 100644 --- a/src/cairo-traps.c +++ b/src/cairo-traps.c @@ -63,6 +63,7 @@ _cairo_traps_init (cairo_traps_t *traps) traps->extents.p2.x = traps->extents.p2.y = INT32_MIN; traps->has_limits = FALSE; + traps->has_intersections = FALSE; } void @@ -74,14 +75,6 @@ _cairo_traps_limit (cairo_traps_t *traps, traps->limits = *limits; } -cairo_bool_t -_cairo_traps_get_limit (cairo_traps_t *traps, - cairo_box_t *limits) -{ - *limits = traps->limits; - return traps->has_limits; -} - void _cairo_traps_clear (cairo_traps_t *traps) { @@ -90,6 +83,7 @@ _cairo_traps_clear (cairo_traps_t *traps) traps->maybe_region = 1; traps->num_traps = 0; + traps->has_intersections = FALSE; traps->extents.p1.x = traps->extents.p1.y = INT32_MAX; traps->extents.p2.x = traps->extents.p2.y = INT32_MIN; } @@ -165,6 +159,12 @@ _cairo_traps_grow (cairo_traps_t *traps) return TRUE; } +static cairo_fixed_t +_line_compute_intersection_x_for_y (const cairo_line_t *line, + cairo_fixed_t y) +{ + return _cairo_edge_compute_intersection_x_for_y (&line->p1, &line->p2, y); +} void _cairo_traps_add_trap (cairo_traps_t *traps, cairo_fixed_t top, cairo_fixed_t bottom, @@ -257,23 +257,44 @@ _cairo_traps_add_trap (cairo_traps_t *traps, traps->extents.p1.y = top; if (bottom > traps->extents.p2.y) traps->extents.p2.y = bottom; - /* - * This isn't generally accurate, but it is close enough for - * this purpose. Assuming that the left and right segments always - * contain the trapezoid vertical extents, these compares will - * yield a containing box. Assuming that the points all come from - * the same figure which will eventually be completely drawn, then - * the compares will yield the correct overall extents - */ - if (left->p1.x < traps->extents.p1.x) - traps->extents.p1.x = left->p1.x; - if (left->p2.x < traps->extents.p1.x) - traps->extents.p1.x = left->p2.x; - if (right->p1.x > traps->extents.p2.x) - traps->extents.p2.x = right->p1.x; - if (right->p2.x > traps->extents.p2.x) - traps->extents.p2.x = right->p2.x; + if (left->p1.x < traps->extents.p1.x) { + cairo_fixed_t x = left->p1.x; + if (top != left->p1.y) { + x = _line_compute_intersection_x_for_y (left, top); + if (x < traps->extents.p1.x) + traps->extents.p1.x = x; + } else + traps->extents.p1.x = x; + } + if (left->p2.x < traps->extents.p1.x) { + cairo_fixed_t x = left->p2.x; + if (bottom != left->p2.y) { + x = _line_compute_intersection_x_for_y (left, bottom); + if (x < traps->extents.p1.x) + traps->extents.p1.x = x; + } else + traps->extents.p1.x = x; + } + + if (right->p1.x > traps->extents.p2.x) { + cairo_fixed_t x = right->p1.x; + if (top != right->p1.y) { + x = _line_compute_intersection_x_for_y (right, top); + if (x > traps->extents.p2.x) + traps->extents.p2.x = x; + } else + traps->extents.p2.x = x; + } + if (right->p2.x > traps->extents.p2.x) { + cairo_fixed_t x = right->p2.x; + if (bottom != right->p2.y) { + x = _line_compute_intersection_x_for_y (right, bottom); + if (x > traps->extents.p2.x) + traps->extents.p2.x = x; + } else + traps->extents.p2.x = x; + } traps->num_traps++; } @@ -284,9 +305,8 @@ _compare_point_fixed_by_y (const void *av, const void *bv) const cairo_point_t *a = av, *b = bv; int ret = a->y - b->y; - if (ret == 0) { + if (ret == 0) ret = a->x - b->x; - } return ret; } diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h index b5536209..fa706785 100644 --- a/src/cairo-types-private.h +++ b/src/cairo-types-private.h @@ -177,13 +177,7 @@ typedef enum _cairo_internal_surface_type { #define CAIRO_HAS_TEST_NULL_SURFACE 1 #define CAIRO_HAS_TEST_WRAPPING_SURFACE 1 -typedef struct _cairo_point { - cairo_fixed_t x; - cairo_fixed_t y; -} cairo_point_t; - -typedef struct _cairo_slope -{ +typedef struct _cairo_slope { cairo_fixed_t dx; cairo_fixed_t dy; } cairo_slope_t, cairo_distance_t; @@ -244,7 +238,8 @@ typedef enum _cairo_direction { } cairo_direction_t; typedef struct _cairo_edge { - cairo_line_t edge; + cairo_line_t line; + int top, bottom; int dir; } cairo_edge_t; @@ -255,6 +250,10 @@ typedef struct _cairo_polygon { cairo_point_t current_point; cairo_bool_t has_current_point; + cairo_box_t extents; + cairo_box_t limits; + cairo_bool_t has_limits; + int num_edges; int edges_size; cairo_edge_t *edges; diff --git a/src/cairo-wideint-private.h b/src/cairo-wideint-private.h index f5aac284..ec7fa1f6 100644 --- a/src/cairo-wideint-private.h +++ b/src/cairo-wideint-private.h @@ -51,6 +51,9 @@ #if !HAVE_UINT64_T +cairo_uquorem64_t I +_cairo_uint64_divrem (cairo_uint64_t num, cairo_uint64_t den); + cairo_uint64_t I _cairo_uint32_to_uint64 (uint32_t i); #define _cairo_uint64_to_uint32(a) ((a).lo) cairo_uint64_t I _cairo_uint64_add (cairo_uint64_t a, cairo_uint64_t b); @@ -90,6 +93,16 @@ int I _cairo_int64_cmp (cairo_int64_t a, cairo_int64_t b); #else +static inline cairo_uquorem64_t +_cairo_uint64_divrem (cairo_uint64_t num, cairo_uint64_t den) +{ + cairo_uquorem64_t qr; + + qr.quo = num / den; + qr.rem = num % den; + return qr; +} + #define _cairo_uint32_to_uint64(i) ((uint64_t) (i)) #define _cairo_uint64_to_uint32(i) ((uint32_t) (i)) #define _cairo_uint64_add(a,b) ((a) + (b)) @@ -147,11 +160,35 @@ int I _cairo_int64_cmp (cairo_int64_t a, cairo_int64_t b); * a function which returns both for the 'native' type as well */ -cairo_uquorem64_t I -_cairo_uint64_divrem (cairo_uint64_t num, cairo_uint64_t den); - -cairo_quorem64_t I -_cairo_int64_divrem (cairo_int64_t num, cairo_int64_t den); +static inline cairo_quorem64_t +_cairo_int64_divrem (cairo_int64_t num, cairo_int64_t den) +{ + int num_neg = _cairo_int64_negative (num); + int den_neg = _cairo_int64_negative (den); + cairo_uquorem64_t uqr; + cairo_quorem64_t qr; + + if (num_neg) + num = _cairo_int64_negate (num); + if (den_neg) + den = _cairo_int64_negate (den); + uqr = _cairo_uint64_divrem (num, den); + if (num_neg) + qr.rem = _cairo_int64_negate (uqr.rem); + else + qr.rem = uqr.rem; + if (num_neg != den_neg) + qr.quo = (cairo_int64_t) _cairo_int64_negate (uqr.quo); + else + qr.quo = (cairo_int64_t) uqr.quo; + return qr; +} + +static inline int32_t +_cairo_int64_32_div (cairo_int64_t num, int32_t den) +{ + return num / den; +} /* * 128-bit datatypes. Again, provide two implementations in diff --git a/src/cairo-wideint.c b/src/cairo-wideint.c index 45655938..1843a602 100644 --- a/src/cairo-wideint.c +++ b/src/cairo-wideint.c @@ -39,16 +39,6 @@ #define _cairo_uint32s_to_uint64(h,l) ((uint64_t) (h) << 32 | (l)) -cairo_uquorem64_t -_cairo_uint64_divrem (cairo_uint64_t num, cairo_uint64_t den) -{ - cairo_uquorem64_t qr; - - qr.quo = num / den; - qr.rem = num % den; - return qr; -} - #else cairo_uint64_t @@ -317,30 +307,6 @@ _cairo_uint64_divrem (cairo_uint64_t num, cairo_uint64_t den) #endif /* !HAVE_UINT64_T */ -cairo_quorem64_t -_cairo_int64_divrem (cairo_int64_t num, cairo_int64_t den) -{ - int num_neg = _cairo_int64_negative (num); - int den_neg = _cairo_int64_negative (den); - cairo_uquorem64_t uqr; - cairo_quorem64_t qr; - - if (num_neg) - num = _cairo_int64_negate (num); - if (den_neg) - den = _cairo_int64_negate (den); - uqr = _cairo_uint64_divrem (num, den); - if (num_neg) - qr.rem = _cairo_int64_negate (uqr.rem); - else - qr.rem = uqr.rem; - if (num_neg != den_neg) - qr.quo = (cairo_int64_t) _cairo_int64_negate (uqr.quo); - else - qr.quo = (cairo_int64_t) uqr.quo; - return qr; -} - #if HAVE_UINT128_T cairo_uquorem128_t diff --git a/src/cairoint.h b/src/cairoint.h index 12e40988..30b0c4c0 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -952,6 +952,7 @@ typedef struct _cairo_traps { cairo_status_t status; cairo_box_t extents; + cairo_box_t limits; unsigned int maybe_region : 1; /* hint: 0 implies that it cannot be */ @@ -962,7 +963,7 @@ typedef struct _cairo_traps { cairo_trapezoid_t traps_embedded[4]; cairo_bool_t has_limits; - cairo_box_t limits; + cairo_bool_t has_intersections; } cairo_traps_t; #define CAIRO_FONT_SLANT_DEFAULT CAIRO_FONT_SLANT_NORMAL @@ -1609,13 +1610,36 @@ _cairo_path_fixed_in_fill (const cairo_path_fixed_t *path, /* cairo-path-fill.c */ cairo_private cairo_status_t -_cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - double tolerance, - cairo_traps_t *traps); +_cairo_path_fixed_fill_to_polygon (const cairo_path_fixed_t *path, + double tolerance, + cairo_polygon_t *polygon); + +cairo_private cairo_int_status_t +_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + cairo_traps_t *traps); + +cairo_private cairo_status_t +_cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, + cairo_fill_rule_t fill_rule, + double tolerance, + cairo_traps_t *traps); /* cairo-path-stroke.c */ cairo_private cairo_status_t +_cairo_path_fixed_stroke_to_polygon (const cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + const cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_polygon_t *polygon); + +cairo_private cairo_int_status_t +_cairo_path_fixed_stroke_rectilinear_to_traps (const cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + cairo_traps_t *traps); +cairo_private cairo_status_t _cairo_path_fixed_stroke_to_traps (const cairo_path_fixed_t *path, cairo_stroke_style_t *stroke_style, const cairo_matrix_t *ctm, @@ -1623,6 +1647,22 @@ _cairo_path_fixed_stroke_to_traps (const cairo_path_fixed_t *path, double tolerance, cairo_traps_t *traps); +cairo_private cairo_status_t +_cairo_path_fixed_stroke_to_shaper (cairo_path_fixed_t *path, + cairo_stroke_style_t *stroke_style, + cairo_matrix_t *ctm, + cairo_matrix_t *ctm_inverse, + double tolerance, + cairo_status_t (*add_triangle) (void *closure, + const cairo_point_t triangle[3]), + cairo_status_t (*add_triangle_fan) (void *closure, + const cairo_point_t *midpt, + const cairo_point_t *points, + int npoints), + cairo_status_t (*add_quad) (void *closure, + const cairo_point_t quad[4]), + void *closure); + /* cairo-scaled-font.c */ cairo_private void @@ -2201,45 +2241,21 @@ cairo_private int _cairo_pen_find_active_ccw_vertex_index (const cairo_pen_t *pen, const cairo_slope_t *slope); -typedef struct _cairo_pen_stroke_spline { - cairo_pen_t pen; - cairo_spline_t spline; - cairo_polygon_t polygon; - cairo_point_t last_point; - cairo_point_t forward_hull_point; - cairo_point_t backward_hull_point; - int forward_vertex; - int backward_vertex; -} cairo_pen_stroke_spline_t; - -cairo_private cairo_int_status_t -_cairo_pen_stroke_spline_init (cairo_pen_stroke_spline_t *stroker, - const cairo_pen_t *pen, - const cairo_point_t *a, - const cairo_point_t *b, - const cairo_point_t *c, - const cairo_point_t *d); - -cairo_private cairo_status_t -_cairo_pen_stroke_spline (cairo_pen_stroke_spline_t *pen, - double tolerance, - cairo_traps_t *traps); - -cairo_private void -_cairo_pen_stroke_spline_fini (cairo_pen_stroke_spline_t *stroker); - /* cairo-polygon.c */ cairo_private void _cairo_polygon_init (cairo_polygon_t *polygon); cairo_private void -_cairo_polygon_fini (cairo_polygon_t *polygon); +_cairo_polygon_limit (cairo_polygon_t *polygon, + const cairo_box_t *limits); cairo_private void -_cairo_polygon_add_edge (cairo_polygon_t *polygon, - const cairo_point_t *p1, - const cairo_point_t *p2, - int dir); +_cairo_polygon_fini (cairo_polygon_t *polygon); + +cairo_private cairo_status_t +_cairo_polygon_add_external_edge (void *polygon, + const cairo_point_t *p1, + const cairo_point_t *p2); cairo_private void _cairo_polygon_move_to (cairo_polygon_t *polygon, @@ -2252,7 +2268,7 @@ _cairo_polygon_line_to (cairo_polygon_t *polygon, cairo_private void _cairo_polygon_close (cairo_polygon_t *polygon); -#define _cairo_polygon_status(P) (P)->status +#define _cairo_polygon_status(P) ((cairo_polygon_t *) (P))->status /* cairo-spline.c */ cairo_private cairo_bool_t @@ -2310,6 +2326,9 @@ _cairo_matrix_is_integer_translation(const cairo_matrix_t *matrix, int *itx, int *ity); cairo_private cairo_bool_t +_cairo_matrix_has_unity_scale (const cairo_matrix_t *matrix); + +cairo_private cairo_bool_t _cairo_matrix_is_pixel_exact (const cairo_matrix_t *matrix) cairo_pure; cairo_private double @@ -2330,10 +2349,6 @@ cairo_private void _cairo_traps_limit (cairo_traps_t *traps, cairo_box_t *limits); -cairo_private cairo_bool_t -_cairo_traps_get_limit (cairo_traps_t *traps, - cairo_box_t *limits); - cairo_private void _cairo_traps_init_box (cairo_traps_t *traps, const cairo_box_t *box); @@ -2372,6 +2387,9 @@ _cairo_bentley_ottmann_tessellate_polygon (cairo_traps_t *traps, const cairo_polygon_t *polygon, cairo_fill_rule_t fill_rule); +cairo_private cairo_status_t +_cairo_bentley_ottmann_tessellate_traps (cairo_traps_t *traps); + cairo_private int _cairo_traps_contain (const cairo_traps_t *traps, double x, double y); diff --git a/test/Makefile.am b/test/Makefile.am index a0523932..9b5ee2a8 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -4,8 +4,6 @@ include $(top_srcdir)/test/Makefile.sources SUBDIRS=pdiff . -CLEANFILES += have-similar.* - # Then we have a collection of tests that are only run if certain # features are compiled into cairo if HAVE_PTHREAD @@ -147,8 +145,10 @@ REFERENCE_IMAGES = \ bitmap-font.rgb24.ref.png \ caps-joins-alpha.quartz.ref.png \ caps-joins-alpha.ref.png \ + caps-joins-alpha.xlib.ref.png \ caps-joins-curve.ps.ref.png \ caps-joins-curve.ref.png \ + caps-joins-curve.xlib.ref.png \ caps-joins.ps.ref.png \ caps-joins.ref.png \ caps-sub-paths.ref.png \ @@ -156,6 +156,8 @@ REFERENCE_IMAGES = \ caps.ref.png \ clear.argb32.ref.png \ clear.rgb24.ref.png \ + clear.pdf.argb32.ref.png \ + clear.ps.argb32.ref.png \ clear.svg12.argb32.xfail.png \ clear.svg12.rgb24.xfail.png \ clip-all.ref.png \ @@ -237,6 +239,7 @@ REFERENCE_IMAGES = \ clipped-group.ps3.ref.png \ clipped-group.ref.png \ clipped-surface.ref.png \ + clipped-trapezoids.ref.png \ close-path-current-point.ps.ref.png \ close-path-current-point.ref.png \ close-path.ps2.ref.png \ @@ -275,6 +278,7 @@ REFERENCE_IMAGES = \ dash-curve.ps3.ref.png \ dash-curve.quartz.ref.png \ dash-curve.ref.png \ + dash-curve.xlib.ref.png \ dash-infinite-loop.ref.png \ dash-no-dash.ref.png \ dash-offset-negative.ref.png \ @@ -302,16 +306,18 @@ REFERENCE_IMAGES = \ degenerate-curve-to.ps.xfail.png \ degenerate-dash.ps.xfail.png \ degenerate-dash.ref.png \ + degenerate-dash.xlib.ref.png \ degenerate-path.ps.argb32.xfail.png \ degenerate-path.ps.rgb24.xfail.png \ degenerate-path.quartz.ref.png \ degenerate-path.quartz.rgb24.ref.png \ - degenerate-path.ref.png \ + degenerate-path.argb32.ref.png \ degenerate-path.rgb24.ref.png \ degenerate-pen.ps2.ref.png \ degenerate-pen.ps3.ref.png \ degenerate-pen.quartz.ref.png \ degenerate-pen.ref.png \ + degenerate-pen.xlib.ref.png \ degenerate-rel-curve-to.ref.png \ degenerate-rel-curve-to.ps.xfail.png \ device-offset-fractional.gl.xfail.png \ @@ -389,8 +395,10 @@ REFERENCE_IMAGES = \ fill-and-stroke.ps3.rgb24.ref.png \ fill-and-stroke.quartz.ref.png \ fill-and-stroke.quartz.rgb24.ref.png \ - fill-and-stroke.ref.png \ + fill-and-stroke.argb32.ref.png \ fill-and-stroke.rgb24.ref.png \ + fill-and-stroke.xlib.argb32.ref.png \ + fill-and-stroke.xlib.rgb24.ref.png \ fill-degenerate-sort-order.ps.argb32.xfail.png \ fill-degenerate-sort-order.ps.rgb24.xfail.png \ fill-degenerate-sort-order.quartz.ref.png \ @@ -469,14 +477,12 @@ REFERENCE_IMAGES = \ ft-text-antialias-none.ps3.argb32.ref.png \ ft-text-antialias-none.ref.png \ ft-text-vertical-layout-type1.pdf.ref.png \ - ft-text-vertical-layout-type1.ps2.ref.png \ - ft-text-vertical-layout-type1.ps3.ref.png \ + ft-text-vertical-layout-type1.ps.ref.png \ ft-text-vertical-layout-type1.ref.png \ ft-text-vertical-layout-type1.svg.ref.png \ ft-text-vertical-layout-type1.xlib.ref.png \ ft-text-vertical-layout-type3.pdf.ref.png \ - ft-text-vertical-layout-type3.ps2.ref.png \ - ft-text-vertical-layout-type3.ps3.ref.png \ + ft-text-vertical-layout-type3.ps.ref.png \ ft-text-vertical-layout-type3.ref.png \ ft-text-vertical-layout-type3.svg.ref.png \ ft-text-vertical-layout-type3.xlib.ref.png \ @@ -538,9 +544,11 @@ REFERENCE_IMAGES = \ leaky-dashed-rectangle.pdf.ref.png \ leaky-dashed-rectangle.ps.ref.png \ leaky-dashed-rectangle.ref.png \ + leaky-dashed-rectangle.xlib.ref.png \ leaky-dashed-stroke.ps2.ref.png \ leaky-dashed-stroke.ps3.ref.png \ leaky-dashed-stroke.ref.png \ + leaky-dashed-stroke.xlib.ref.png \ leaky-polygon.ps2.ref.png \ leaky-polygon.ps3.ref.png \ leaky-polygon.ref.png \ @@ -613,13 +621,14 @@ REFERENCE_IMAGES = \ miter-precision.ps3.ref.png \ miter-precision.ref.png \ move-to-show-surface.ref.png \ + new-sub-path.pdf.argb32.ref.png \ new-sub-path.ps2.argb32.ref.png \ new-sub-path.ps2.rgb24.ref.png \ new-sub-path.ps3.argb32.ref.png \ new-sub-path.ps3.rgb24.ref.png \ new-sub-path.quartz.ref.png \ new-sub-path.quartz.rgb24.ref.png \ - new-sub-path.ref.png \ + new-sub-path.argb32.ref.png \ new-sub-path.rgb24.ref.png \ nil-surface.ref.png \ nil-surface.rgb24.ref.png \ @@ -726,11 +735,13 @@ REFERENCE_IMAGES = \ radial-gradient.pdf.ref.png \ radial-gradient.quartz.ref.png \ radial-gradient.ref.png \ - random-intersections.ps2.ref.png \ - random-intersections.ps3.ref.png \ - random-intersections.quartz.ref.png \ - random-intersections.ref.png \ - random-intersections.xlib.ref.png \ + random-intersections-eo.ps.ref.png \ + random-intersections-eo.quartz.ref.png \ + random-intersections-eo.ref.png \ + random-intersections-eo.xlib.ref.png \ + random-intersections-nonzero.ref.png \ + random-intersections-nonzero.ps.ref.png \ + random-intersections-nonzero.xlib.ref.png \ rectangle-rounding-error.ref.png \ rectilinear-dash.ref.png \ rectilinear-fill.ref.png \ @@ -783,16 +794,9 @@ REFERENCE_IMAGES = \ self-copy.ps2.ref.png \ self-copy.ps3.ref.png \ self-copy.ref.png \ - self-intersecting.argb32.xfail.png \ - self-intersecting.pdf.argb32.xfail.png \ - self-intersecting.pdf.rgb24.xfail.png \ - self-intersecting.ps.argb32.xfail.png \ - self-intersecting.ps.rgb24.xfail.png \ + self-intersecting.ps.ref.png \ self-intersecting.ref.png \ - self-intersecting.rgb24.ref.png \ - self-intersecting.rgb24.xfail.png \ - self-intersecting.xlib.argb32.xfail.png \ - self-intersecting.xlib.rgb24.xfail.png \ + self-intersecting.xlib.ref.png \ set-source.ref.png \ set-source.rgb24.ref.png \ show-glyphs-many.ref.png \ @@ -815,6 +819,7 @@ REFERENCE_IMAGES = \ smask-paint.svg.ref.png \ smask-stroke.pdf.xfail.png \ smask-stroke.ref.png \ + smask-stroke.xlib.ref.png \ smask-text.pdf.ref.png \ smask-text.ps2.ref.png \ smask-text.ps3.ref.png \ @@ -822,8 +827,7 @@ REFERENCE_IMAGES = \ smask-text.svg.ref.png \ smask-text.xlib.ref.png \ smask.pdf.xfail.png \ - smask.ps2.ref.png \ - smask.ps3.ref.png \ + smask.ps.ref.png \ smask.ref.png \ smask.svg.ref.png \ smask.xlib.ref.png \ @@ -844,15 +848,16 @@ REFERENCE_IMAGES = \ spline-decomposition.ps.ref.png \ spline-decomposition.ref.png \ spline-decomposition.svg.ref.png \ + spline-decomposition.xlib.ref.png \ stroke-ctm-caps.ps2.ref.png \ stroke-ctm-caps.ps3.ref.png \ stroke-ctm-caps.quartz.ref.png \ stroke-ctm-caps.ref.png \ stroke-image.pdf.ref.png \ - stroke-image.ps2.ref.png \ - stroke-image.ps3.ref.png \ + stroke-image.ps.ref.png \ stroke-image.quartz.ref.png \ stroke-image.ref.png \ + stroke-image.xlib.ref.png \ surface-pattern-big-scale-down.ref.png \ surface-pattern-big-scale-down.ps.xfail.png \ surface-pattern-scale-down.pdf.ref.png \ @@ -900,6 +905,7 @@ REFERENCE_IMAGES = \ text-rotate.quartz.ref.png \ text-rotate.ref.png \ text-rotate.svg.ref.png \ + text-rotate.xlib.ref.png \ text-transform.pdf.ref.png \ text-transform.ps2.ref.png \ text-transform.ps3.ref.png \ @@ -923,6 +929,7 @@ REFERENCE_IMAGES = \ twin.ps.ref.png \ twin.ref.png \ twin.svg.ref.png \ + twin.xlib.ref.png \ unantialiased-shapes.quartz.ref.png \ unantialiased-shapes.ref.png \ unbounded-operator.gl.argb32.xfail.png \ @@ -934,7 +941,7 @@ REFERENCE_IMAGES = \ unbounded-operator.quartz.rgb24.ref.png \ unbounded-operator.ref.png \ unbounded-operator.rgb24.ref.png \ - unbounded-operator.svg12.argb32.xfail.png \ + unbounded-operator.svg12.argb32.ref.png \ unbounded-operator.svg12.rgb24.xfail.png \ unbounded-operator.xlib.rgb24.ref.png \ user-font-mask.pdf.ref.png \ @@ -953,8 +960,7 @@ REFERENCE_IMAGES = \ user-font-rescale.ps3.ref.png \ user-font-rescale.ref.png \ user-font-rescale.svg.ref.png \ - user-font.ps2.ref.png \ - user-font.ps3.ref.png \ + user-font.ps.ref.png \ user-font.ref.png \ user-font.svg.ref.png \ user-font.xlib.ref.png \ @@ -1132,14 +1138,22 @@ imagediff_LDADD = \ $(top_builddir)/src/libcairo.la png_flatten_SOURCES = png-flatten.c -png_flatten_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) +png_flatten_LDADD = $(top_builddir)/src/libcairo.la \ + $(CAIRO_LDADD) if BUILD_ANY2PPM check_PROGRAMS += any2ppm any2ppm_CFLAGS = $(AM_CFLAGS) $(POPPLER_CFLAGS) $(LIBRSVG_CFLAGS) $(LIBSPECTRE_CFLAGS) # add LDADD, so poppler/librsvg uses "our" cairo any2ppm_LDFLAGS = $(AM_LDFLAGS) $(CAIRO_TEST_UNDEFINED_LDFLAGS) -any2ppm_LDADD = $(top_builddir)/util/cairo-script/libcairo-script-interpreter.la $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(CAIROBOILERPLATE_LIBS) $(POPPLER_LIBS) $(LIBRSVG_LIBS) $(LIBSPECTRE_LIBS) +any2ppm_LDADD = \ + $(top_builddir)/util/cairo-script/libcairo-script-interpreter.la \ + $(top_builddir)/src/libcairo.la \ + $(CAIRO_LDADD) \ + $(CAIROBOILERPLATE_LIBS) \ + $(POPPLER_LIBS) \ + $(LIBRSVG_LIBS) \ + $(LIBSPECTRE_LIBS) endif if CAIRO_CAN_TEST_PDF_SURFACE @@ -1147,7 +1161,9 @@ check_PROGRAMS += pdf2png pdf2png_CFLAGS = $(AM_CFLAGS) $(POPPLER_CFLAGS) # add LDADD, so poppler uses "our" cairo pdf2png_LDFLAGS = $(AM_LDFLAGS) $(CAIRO_TEST_UNDEFINED_LDFLAGS) -pdf2png_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(POPPLER_LIBS) +pdf2png_LDADD = $(top_builddir)/src/libcairo.la \ + $(CAIRO_LDADD) \ + $(POPPLER_LIBS) endif if CAIRO_CAN_TEST_SVG_SURFACE @@ -1155,7 +1171,9 @@ check_PROGRAMS += svg2png svg2png_CFLAGS = $(AM_CFLAGS) $(LIBRSVG_CFLAGS) # add LDADD, so librsvg uses "our" cairo svg2png_LDFLAGS = $(AM_LDFLAGS) $(CAIRO_TEST_UNDEFINED_LDFLAGS) -svg2png_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(LIBRSVG_LIBS) +svg2png_LDADD = $(top_builddir)/src/libcairo.la \ + $(CAIRO_LDADD) \ + $(LIBRSVG_LIBS) endif if CAIRO_HAS_SPECTRE @@ -1163,7 +1181,9 @@ check_PROGRAMS += ps2png ps2png_CFLAGS = $(AM_CFLAGS) $(LIBSPECTRE_CFLAGS) # add LDADD, so ps2png uses "our" cairo ps2png_LDFLAGS = $(AM_LDFLAGS) $(CAIRO_TEST_UNDEFINED_LDFLAGS) -ps2png_LDADD = $(top_builddir)/src/libcairo.la $(CAIRO_LDADD) $(LIBSPECTRE_LIBS) +ps2png_LDADD = $(top_builddir)/src/libcairo.la \ + $(CAIRO_LDADD) \ + $(LIBSPECTRE_LIBS) endif EXTRA_PROGRAMS += $(TESTS) diff --git a/test/Makefile.sources b/test/Makefile.sources index 50b4d3ea..0a58ac07 100644 --- a/test/Makefile.sources +++ b/test/Makefile.sources @@ -150,7 +150,8 @@ test_sources = \ png.c \ push-group.c \ radial-gradient.c \ - random-intersections.c \ + random-intersections-eo.c \ + random-intersections-nonzero.c \ rectangle-rounding-error.c \ rectilinear-fill.c \ rectilinear-miter-limit.c \ diff --git a/test/caps-joins-alpha.ref.png b/test/caps-joins-alpha.ref.png Binary files differindex 1d34c73b..b28d936e 100644 --- a/test/caps-joins-alpha.ref.png +++ b/test/caps-joins-alpha.ref.png diff --git a/test/caps-joins-alpha.xlib.ref.png b/test/caps-joins-alpha.xlib.ref.png Binary files differnew file mode 100644 index 00000000..288a5005 --- /dev/null +++ b/test/caps-joins-alpha.xlib.ref.png diff --git a/test/caps-joins-curve.ref.png b/test/caps-joins-curve.ref.png Binary files differindex 9f763013..cb4034b0 100644 --- a/test/caps-joins-curve.ref.png +++ b/test/caps-joins-curve.ref.png diff --git a/test/caps-joins-curve.xlib.ref.png b/test/caps-joins-curve.xlib.ref.png Binary files differnew file mode 100644 index 00000000..be7688d0 --- /dev/null +++ b/test/caps-joins-curve.xlib.ref.png diff --git a/test/clear.pdf.argb32.ref.png b/test/clear.pdf.argb32.ref.png Binary files differnew file mode 100644 index 00000000..0960f485 --- /dev/null +++ b/test/clear.pdf.argb32.ref.png diff --git a/test/clear.ps.argb32.ref.png b/test/clear.ps.argb32.ref.png Binary files differnew file mode 100644 index 00000000..0960f485 --- /dev/null +++ b/test/clear.ps.argb32.ref.png diff --git a/test/clip-operator.svg12.argb32.xfail.png b/test/clip-operator.svg12.argb32.xfail.png Binary files differindex be0696e0..1c21d15f 100644 --- a/test/clip-operator.svg12.argb32.xfail.png +++ b/test/clip-operator.svg12.argb32.xfail.png diff --git a/test/clip-operator.svg12.rgb24.xfail.png b/test/clip-operator.svg12.rgb24.xfail.png Binary files differindex 494852da..f79de48e 100644 --- a/test/clip-operator.svg12.rgb24.xfail.png +++ b/test/clip-operator.svg12.rgb24.xfail.png diff --git a/test/clipped-group.pdf.ref.png b/test/clipped-group.pdf.ref.png Binary files differindex 64958608..23db5a4f 100644 --- a/test/clipped-group.pdf.ref.png +++ b/test/clipped-group.pdf.ref.png diff --git a/test/clipped-trapezoids-ref.png b/test/clipped-trapezoids-ref.png Binary files differnew file mode 100644 index 00000000..3fd300c0 --- /dev/null +++ b/test/clipped-trapezoids-ref.png diff --git a/test/clipped-trapezoids.c b/test/clipped-trapezoids.c new file mode 100644 index 00000000..1f96daa8 --- /dev/null +++ b/test/clipped-trapezoids.c @@ -0,0 +1,95 @@ +/* + * Copyright 2008 Chris Wilson + * + * 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 + * Chris Wilson not be used in advertising or publicity pertaining to + * distribution of the software without specific, written prior + * permission. Chris Wilson makes no representations about the + * suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * CHRIS WILSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL CHRIS WILSON 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. + * + * Author: Chris Wilson <chris@chris-wilson.co.uk> + */ + +#include "cairo-test.h" + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + double dash[2] = { 8, 4 }; + double radius; + + radius = width; + if (height > radius) + radius = height; + + /* fill the background using a big circle */ + cairo_arc (cr, 0, 0, 4 * radius, 0, 2 * M_PI); + cairo_fill (cr); + + /* a rotated square - overlapping the corners */ + cairo_save (cr); + cairo_save (cr); + cairo_translate (cr, width/2, height/2); + cairo_rotate (cr, M_PI/4); + cairo_scale (cr, M_SQRT2, M_SQRT2); + cairo_rectangle (cr, -width/2, -height/2, width, height); + cairo_restore (cr); + cairo_set_source_rgba (cr, 0, 1, 0, .5); + cairo_set_line_width (cr, radius/2); + cairo_stroke (cr); + cairo_restore (cr); + + /* and put some circles in the corners */ + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_new_sub_path (cr); + cairo_arc (cr, 0, 0, radius/4, 0, 2 * M_PI); + cairo_new_sub_path (cr); + cairo_arc (cr, width, 0, radius/4, 0, 2 * M_PI); + cairo_new_sub_path (cr); + cairo_arc (cr, width, height, radius/4, 0, 2 * M_PI); + cairo_new_sub_path (cr); + cairo_arc (cr, 0, height, radius/4, 0, 2 * M_PI); + cairo_fill (cr); + + /* a couple of pixel-aligned lines */ + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_move_to (cr, width/2, -height); + cairo_rel_line_to (cr, 0, 3*height); + cairo_move_to (cr, -width, height/2); + cairo_rel_line_to (cr, 3*width, 0); + cairo_stroke (cr); + + /* a couple of dashed diagonals */ + cairo_save (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_dash (cr, dash, 2, 0); + cairo_set_line_width (cr, 4.); + cairo_move_to (cr, -width, -height); + cairo_line_to (cr, width+width, height+height); + cairo_move_to (cr, width+width, -height); + cairo_line_to (cr, -width, height+height); + cairo_stroke (cr); + cairo_restore (cr); + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (clipped_trapezoids, + "Tests clipping of trapezoids larger than the surface", + "clip", /* keywords */ + NULL, /* requirements */ + 40, 40, + NULL, draw) diff --git a/test/clipped-trapezoids.ref.png b/test/clipped-trapezoids.ref.png Binary files differnew file mode 100644 index 00000000..975a692d --- /dev/null +++ b/test/clipped-trapezoids.ref.png diff --git a/test/close-path-current-point.ref.png b/test/close-path-current-point.ref.png Binary files differindex f30002c6..70f9accc 100644 --- a/test/close-path-current-point.ref.png +++ b/test/close-path-current-point.ref.png diff --git a/test/dash-caps-joins.ref.png b/test/dash-caps-joins.ref.png Binary files differindex 15a06798..bde36971 100644 --- a/test/dash-caps-joins.ref.png +++ b/test/dash-caps-joins.ref.png diff --git a/test/dash-curve.ref.png b/test/dash-curve.ref.png Binary files differindex a590fc43..b940c1b4 100644 --- a/test/dash-curve.ref.png +++ b/test/dash-curve.ref.png diff --git a/test/dash-curve.xlib.ref.png b/test/dash-curve.xlib.ref.png Binary files differnew file mode 100644 index 00000000..76212601 --- /dev/null +++ b/test/dash-curve.xlib.ref.png diff --git a/test/dash-scale.ref.png b/test/dash-scale.ref.png Binary files differindex a0f004b3..cbfb6bd3 100644 --- a/test/dash-scale.ref.png +++ b/test/dash-scale.ref.png diff --git a/test/degenerate-arc.ref.png b/test/degenerate-arc.ref.png Binary files differindex 1d131b22..d83e2c7b 100644 --- a/test/degenerate-arc.ref.png +++ b/test/degenerate-arc.ref.png diff --git a/test/degenerate-dash.ref.png b/test/degenerate-dash.ref.png Binary files differindex ec51a3ce..2ddfc8bb 100644 --- a/test/degenerate-dash.ref.png +++ b/test/degenerate-dash.ref.png diff --git a/test/degenerate-dash.xlib.ref.png b/test/degenerate-dash.xlib.ref.png Binary files differnew file mode 100644 index 00000000..a1b2e830 --- /dev/null +++ b/test/degenerate-dash.xlib.ref.png diff --git a/test/degenerate-path.argb32.ref.png b/test/degenerate-path.argb32.ref.png Binary files differnew file mode 100644 index 00000000..e647677c --- /dev/null +++ b/test/degenerate-path.argb32.ref.png diff --git a/test/degenerate-path.ref.png b/test/degenerate-path.ref.png Binary files differdeleted file mode 100644 index 102e8905..00000000 --- a/test/degenerate-path.ref.png +++ /dev/null diff --git a/test/degenerate-path.rgb24.ref.png b/test/degenerate-path.rgb24.ref.png Binary files differindex 881c0194..d8210a54 100644 --- a/test/degenerate-path.rgb24.ref.png +++ b/test/degenerate-path.rgb24.ref.png diff --git a/test/degenerate-pen.ref.png b/test/degenerate-pen.ref.png Binary files differindex 5961ddd4..b7dcaadc 100644 --- a/test/degenerate-pen.ref.png +++ b/test/degenerate-pen.ref.png diff --git a/test/degenerate-pen.xlib.ref.png b/test/degenerate-pen.xlib.ref.png Binary files differnew file mode 100644 index 00000000..b7dcaadc --- /dev/null +++ b/test/degenerate-pen.xlib.ref.png diff --git a/test/device-offset-fractional.pdf.xfail.png b/test/device-offset-fractional.pdf.xfail.png Binary files differindex 77a49632..6248b4af 100644 --- a/test/device-offset-fractional.pdf.xfail.png +++ b/test/device-offset-fractional.pdf.xfail.png diff --git a/test/fill-and-stroke-alpha-add.ref.png b/test/fill-and-stroke-alpha-add.ref.png Binary files differindex 412dd4f5..c69b6bd3 100644 --- a/test/fill-and-stroke-alpha-add.ref.png +++ b/test/fill-and-stroke-alpha-add.ref.png diff --git a/test/fill-and-stroke-alpha-add.svg12.xfail.png b/test/fill-and-stroke-alpha-add.svg12.xfail.png Binary files differindex 32962f7a..c1d7d6fc 100644 --- a/test/fill-and-stroke-alpha-add.svg12.xfail.png +++ b/test/fill-and-stroke-alpha-add.svg12.xfail.png diff --git a/test/fill-and-stroke-alpha.ref.png b/test/fill-and-stroke-alpha.ref.png Binary files differindex fd4b81be..ff2e156c 100644 --- a/test/fill-and-stroke-alpha.ref.png +++ b/test/fill-and-stroke-alpha.ref.png diff --git a/test/fill-and-stroke.argb32.ref.png b/test/fill-and-stroke.argb32.ref.png Binary files differnew file mode 100644 index 00000000..01d92c88 --- /dev/null +++ b/test/fill-and-stroke.argb32.ref.png diff --git a/test/fill-and-stroke.ref.png b/test/fill-and-stroke.ref.png Binary files differdeleted file mode 100644 index 298f6945..00000000 --- a/test/fill-and-stroke.ref.png +++ /dev/null diff --git a/test/fill-and-stroke.rgb24.ref.png b/test/fill-and-stroke.rgb24.ref.png Binary files differindex ff886eb2..7228813b 100644 --- a/test/fill-and-stroke.rgb24.ref.png +++ b/test/fill-and-stroke.rgb24.ref.png diff --git a/test/fill-and-stroke.xlib.argb32.ref.png b/test/fill-and-stroke.xlib.argb32.ref.png Binary files differnew file mode 100644 index 00000000..5f77c926 --- /dev/null +++ b/test/fill-and-stroke.xlib.argb32.ref.png diff --git a/test/fill-and-stroke.xlib.rgb24.ref.png b/test/fill-and-stroke.xlib.rgb24.ref.png Binary files differnew file mode 100644 index 00000000..f4035ebe --- /dev/null +++ b/test/fill-and-stroke.xlib.rgb24.ref.png diff --git a/test/filter-nearest-offset.pdf.xfail.png b/test/filter-nearest-offset.pdf.xfail.png Binary files differindex ffe2df12..4d436aa1 100644 --- a/test/filter-nearest-offset.pdf.xfail.png +++ b/test/filter-nearest-offset.pdf.xfail.png diff --git a/test/filter-nearest-transformed.pdf.xfail.png b/test/filter-nearest-transformed.pdf.xfail.png Binary files differindex 7eb5988b..5ad98a75 100644 --- a/test/filter-nearest-transformed.pdf.xfail.png +++ b/test/filter-nearest-transformed.pdf.xfail.png diff --git a/test/ft-text-vertical-layout-type1.ps.ref.png b/test/ft-text-vertical-layout-type1.ps.ref.png Binary files differnew file mode 100644 index 00000000..05cdf103 --- /dev/null +++ b/test/ft-text-vertical-layout-type1.ps.ref.png diff --git a/test/ft-text-vertical-layout-type1.ps2.ref.png b/test/ft-text-vertical-layout-type1.ps2.ref.png Binary files differdeleted file mode 100644 index 4523495b..00000000 --- a/test/ft-text-vertical-layout-type1.ps2.ref.png +++ /dev/null diff --git a/test/ft-text-vertical-layout-type1.ps3.ref.png b/test/ft-text-vertical-layout-type1.ps3.ref.png Binary files differdeleted file mode 100644 index 4523495b..00000000 --- a/test/ft-text-vertical-layout-type1.ps3.ref.png +++ /dev/null diff --git a/test/ft-text-vertical-layout-type1.ref.png b/test/ft-text-vertical-layout-type1.ref.png Binary files differindex 1accc0b3..6f0df7b3 100644 --- a/test/ft-text-vertical-layout-type1.ref.png +++ b/test/ft-text-vertical-layout-type1.ref.png diff --git a/test/ft-text-vertical-layout-type1.svg.ref.png b/test/ft-text-vertical-layout-type1.svg.ref.png Binary files differindex 0be400c1..326a240d 100644 --- a/test/ft-text-vertical-layout-type1.svg.ref.png +++ b/test/ft-text-vertical-layout-type1.svg.ref.png diff --git a/test/ft-text-vertical-layout-type1.xlib.ref.png b/test/ft-text-vertical-layout-type1.xlib.ref.png Binary files differindex 2b74aa60..44a1ec7a 100644 --- a/test/ft-text-vertical-layout-type1.xlib.ref.png +++ b/test/ft-text-vertical-layout-type1.xlib.ref.png diff --git a/test/ft-text-vertical-layout-type3.ps.ref.png b/test/ft-text-vertical-layout-type3.ps.ref.png Binary files differnew file mode 100644 index 00000000..bcc208d8 --- /dev/null +++ b/test/ft-text-vertical-layout-type3.ps.ref.png diff --git a/test/ft-text-vertical-layout-type3.ps2.ref.png b/test/ft-text-vertical-layout-type3.ps2.ref.png Binary files differdeleted file mode 100644 index e6ad8fc0..00000000 --- a/test/ft-text-vertical-layout-type3.ps2.ref.png +++ /dev/null diff --git a/test/ft-text-vertical-layout-type3.ps3.ref.png b/test/ft-text-vertical-layout-type3.ps3.ref.png Binary files differdeleted file mode 100644 index e6ad8fc0..00000000 --- a/test/ft-text-vertical-layout-type3.ps3.ref.png +++ /dev/null diff --git a/test/ft-text-vertical-layout-type3.ref.png b/test/ft-text-vertical-layout-type3.ref.png Binary files differindex 1bda421c..94048f1c 100644 --- a/test/ft-text-vertical-layout-type3.ref.png +++ b/test/ft-text-vertical-layout-type3.ref.png diff --git a/test/ft-text-vertical-layout-type3.svg.ref.png b/test/ft-text-vertical-layout-type3.svg.ref.png Binary files differindex cddb955a..985a8de6 100644 --- a/test/ft-text-vertical-layout-type3.svg.ref.png +++ b/test/ft-text-vertical-layout-type3.svg.ref.png diff --git a/test/ft-text-vertical-layout-type3.xlib.ref.png b/test/ft-text-vertical-layout-type3.xlib.ref.png Binary files differindex 8ec2ebec..7a7f68f7 100644 --- a/test/ft-text-vertical-layout-type3.xlib.ref.png +++ b/test/ft-text-vertical-layout-type3.xlib.ref.png diff --git a/test/group-unaligned.svg.argb32.xfail.png b/test/group-unaligned.svg.argb32.xfail.png Binary files differindex 01c34bec..38550376 100644 --- a/test/group-unaligned.svg.argb32.xfail.png +++ b/test/group-unaligned.svg.argb32.xfail.png diff --git a/test/joins.ref.png b/test/joins.ref.png Binary files differindex eee7627e..f8e33f8a 100644 --- a/test/joins.ref.png +++ b/test/joins.ref.png diff --git a/test/leaky-dashed-rectangle.pdf.ref.png b/test/leaky-dashed-rectangle.pdf.ref.png Binary files differindex 690cb363..c0ba7b2c 100644 --- a/test/leaky-dashed-rectangle.pdf.ref.png +++ b/test/leaky-dashed-rectangle.pdf.ref.png diff --git a/test/leaky-dashed-rectangle.xlib.ref.png b/test/leaky-dashed-rectangle.xlib.ref.png Binary files differnew file mode 100644 index 00000000..690cb363 --- /dev/null +++ b/test/leaky-dashed-rectangle.xlib.ref.png diff --git a/test/leaky-dashed-stroke.ref.png b/test/leaky-dashed-stroke.ref.png Binary files differindex e79d4d11..ae64dae4 100644 --- a/test/leaky-dashed-stroke.ref.png +++ b/test/leaky-dashed-stroke.ref.png diff --git a/test/leaky-dashed-stroke.xlib.ref.png b/test/leaky-dashed-stroke.xlib.ref.png Binary files differnew file mode 100644 index 00000000..4ebf1a7f --- /dev/null +++ b/test/leaky-dashed-stroke.xlib.ref.png diff --git a/test/line-width-scale.ref.png b/test/line-width-scale.ref.png Binary files differindex c40bce39..e012b6e2 100644 --- a/test/line-width-scale.ref.png +++ b/test/line-width-scale.ref.png diff --git a/test/long-dashed-lines.ref.png b/test/long-dashed-lines.ref.png Binary files differindex caf8b5e5..09829b71 100644 --- a/test/long-dashed-lines.ref.png +++ b/test/long-dashed-lines.ref.png diff --git a/test/mask-glyphs.svg.ref.png b/test/mask-glyphs.svg.ref.png Binary files differindex 0058afc1..5d524ddc 100644 --- a/test/mask-glyphs.svg.ref.png +++ b/test/mask-glyphs.svg.ref.png diff --git a/test/meta-surface-pattern.pdf.argb32.ref.png b/test/meta-surface-pattern.pdf.argb32.ref.png Binary files differindex 04332228..015f7445 100644 --- a/test/meta-surface-pattern.pdf.argb32.ref.png +++ b/test/meta-surface-pattern.pdf.argb32.ref.png diff --git a/test/meta-surface-pattern.pdf.rgb24.ref.png b/test/meta-surface-pattern.pdf.rgb24.ref.png Binary files differindex b59a9caf..1762e8ac 100644 --- a/test/meta-surface-pattern.pdf.rgb24.ref.png +++ b/test/meta-surface-pattern.pdf.rgb24.ref.png diff --git a/test/meta-surface-pattern.svg.argb32.ref.png b/test/meta-surface-pattern.svg.argb32.ref.png Binary files differindex 86f6b612..ff4154d6 100644 --- a/test/meta-surface-pattern.svg.argb32.ref.png +++ b/test/meta-surface-pattern.svg.argb32.ref.png diff --git a/test/meta-surface-pattern.svg.rgb24.ref.png b/test/meta-surface-pattern.svg.rgb24.ref.png Binary files differindex bbd89363..d2d53724 100644 --- a/test/meta-surface-pattern.svg.rgb24.ref.png +++ b/test/meta-surface-pattern.svg.rgb24.ref.png diff --git a/test/new-sub-path.argb32.ref.png b/test/new-sub-path.argb32.ref.png Binary files differnew file mode 100644 index 00000000..4ecaa0f5 --- /dev/null +++ b/test/new-sub-path.argb32.ref.png diff --git a/test/new-sub-path.pdf.argb32.ref.png b/test/new-sub-path.pdf.argb32.ref.png Binary files differnew file mode 100644 index 00000000..41fe1314 --- /dev/null +++ b/test/new-sub-path.pdf.argb32.ref.png diff --git a/test/new-sub-path.ref.png b/test/new-sub-path.ref.png Binary files differdeleted file mode 100644 index 7319ab3f..00000000 --- a/test/new-sub-path.ref.png +++ /dev/null diff --git a/test/new-sub-path.rgb24.ref.png b/test/new-sub-path.rgb24.ref.png Binary files differindex 8cbc7316..938344a5 100644 --- a/test/new-sub-path.rgb24.ref.png +++ b/test/new-sub-path.rgb24.ref.png diff --git a/test/operator-source.svg12.argb32.xfail.png b/test/operator-source.svg12.argb32.xfail.png Binary files differindex ccf43156..722e5ac4 100644 --- a/test/operator-source.svg12.argb32.xfail.png +++ b/test/operator-source.svg12.argb32.xfail.png diff --git a/test/operator-source.svg12.rgb24.xfail.png b/test/operator-source.svg12.rgb24.xfail.png Binary files differindex 827521b8..5f445fc5 100644 --- a/test/operator-source.svg12.rgb24.xfail.png +++ b/test/operator-source.svg12.rgb24.xfail.png diff --git a/test/over-around-source.pdf.argb32.ref.png b/test/over-around-source.pdf.argb32.ref.png Binary files differindex 02af76a9..da700af7 100644 --- a/test/over-around-source.pdf.argb32.ref.png +++ b/test/over-around-source.pdf.argb32.ref.png diff --git a/test/random-intersections.c b/test/random-intersections-eo.c index 75e6b626..d35894f3 100644 --- a/test/random-intersections.c +++ b/test/random-intersections-eo.c @@ -69,7 +69,7 @@ draw (cairo_t *cr, int width, int height) return CAIRO_TEST_SUCCESS; } -CAIRO_TEST (random_intersections, +CAIRO_TEST (random_intersections_eo, "Tests the tessellator trapezoid generation and intersection computation", "trap", /* keywords */ NULL, /* requirements */ diff --git a/test/random-intersections-eo.ps.ref.png b/test/random-intersections-eo.ps.ref.png Binary files differnew file mode 100644 index 00000000..4bb11d6f --- /dev/null +++ b/test/random-intersections-eo.ps.ref.png diff --git a/test/random-intersections.quartz.ref.png b/test/random-intersections-eo.quartz.ref.png Binary files differindex ef76cba9..ef76cba9 100644 --- a/test/random-intersections.quartz.ref.png +++ b/test/random-intersections-eo.quartz.ref.png diff --git a/test/random-intersections-eo.ref.png b/test/random-intersections-eo.ref.png Binary files differnew file mode 100644 index 00000000..ea23a878 --- /dev/null +++ b/test/random-intersections-eo.ref.png diff --git a/test/random-intersections-eo.xlib.ref.png b/test/random-intersections-eo.xlib.ref.png Binary files differnew file mode 100644 index 00000000..942a5fc7 --- /dev/null +++ b/test/random-intersections-eo.xlib.ref.png diff --git a/test/random-intersections-nonzero.c b/test/random-intersections-nonzero.c new file mode 100644 index 00000000..cad047e0 --- /dev/null +++ b/test/random-intersections-nonzero.c @@ -0,0 +1,79 @@ +/* + * Copyright © 2006 M Joonas Pihlaja + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Author: M Joonas Pihlaja <jpihlaja@cc.helsinki.fi> + */ +#include "cairo-test.h" + +#define SIZE 512 +#define NUM_SEGMENTS 128 + +static uint32_t state; + +static double +uniform_random (double minval, double maxval) +{ + static uint32_t const poly = 0x9a795537U; + uint32_t n = 32; + while (n-->0) + state = 2*state < state ? (2*state ^ poly) : 2*state; + return minval + state * (maxval - minval) / 4294967296.0; +} + +static cairo_test_status_t +draw (cairo_t *cr, int width, int height) +{ + int i; + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_paint (cr); + + state = 0x12345678; + cairo_translate (cr, 1, 1); + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING); + + cairo_move_to (cr, 0, 0); + for (i = 0; i < NUM_SEGMENTS; i++) { + double x = uniform_random (0, width); + double y = uniform_random (0, height); + cairo_line_to (cr, x, y); + } + cairo_close_path (cr); + + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_fill_preserve (cr); + cairo_set_source_rgb (cr, 0, 1, 0); + cairo_set_line_width (cr, 0.5); + cairo_stroke (cr); + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (random_intersections_nonzero, + "Tests the tessellator trapezoid generation and intersection computation", + "trap", /* keywords */ + NULL, /* requirements */ + SIZE+3, SIZE+3, + NULL, draw) + + diff --git a/test/random-intersections-nonzero.ps.ref.png b/test/random-intersections-nonzero.ps.ref.png Binary files differnew file mode 100644 index 00000000..04310910 --- /dev/null +++ b/test/random-intersections-nonzero.ps.ref.png diff --git a/test/random-intersections-nonzero.ref.png b/test/random-intersections-nonzero.ref.png Binary files differnew file mode 100644 index 00000000..3ea650c9 --- /dev/null +++ b/test/random-intersections-nonzero.ref.png diff --git a/test/random-intersections-nonzero.xlib.ref.png b/test/random-intersections-nonzero.xlib.ref.png Binary files differnew file mode 100644 index 00000000..69e06f8c --- /dev/null +++ b/test/random-intersections-nonzero.xlib.ref.png diff --git a/test/random-intersections.ps2.ref.png b/test/random-intersections.ps2.ref.png Binary files differdeleted file mode 100644 index 590c8324..00000000 --- a/test/random-intersections.ps2.ref.png +++ /dev/null diff --git a/test/random-intersections.ps3.ref.png b/test/random-intersections.ps3.ref.png Binary files differdeleted file mode 100644 index 590c8324..00000000 --- a/test/random-intersections.ps3.ref.png +++ /dev/null diff --git a/test/random-intersections.ref.png b/test/random-intersections.ref.png Binary files differdeleted file mode 100644 index ace75a24..00000000 --- a/test/random-intersections.ref.png +++ /dev/null diff --git a/test/random-intersections.xlib.ref.png b/test/random-intersections.xlib.ref.png Binary files differdeleted file mode 100644 index 3188edef..00000000 --- a/test/random-intersections.xlib.ref.png +++ /dev/null diff --git a/test/reflected-stroke.ref.png b/test/reflected-stroke.ref.png Binary files differindex 20f89c84..2dd865ba 100644 --- a/test/reflected-stroke.ref.png +++ b/test/reflected-stroke.ref.png diff --git a/test/rel-path.ref.png b/test/rel-path.ref.png Binary files differindex 7b7007f3..18e67ca3 100644 --- a/test/rel-path.ref.png +++ b/test/rel-path.ref.png diff --git a/test/rel-path.rgb24.ref.png b/test/rel-path.rgb24.ref.png Binary files differindex 0a8a8732..c317d11b 100644 --- a/test/rel-path.rgb24.ref.png +++ b/test/rel-path.rgb24.ref.png diff --git a/test/scale-offset-image.meta.xfail.png b/test/scale-offset-image.meta.xfail.png Binary files differdeleted file mode 100644 index 3e0191a1..00000000 --- a/test/scale-offset-image.meta.xfail.png +++ /dev/null diff --git a/test/scale-offset-image.pdf.xfail.png b/test/scale-offset-image.pdf.xfail.png Binary files differindex 3eacbbc8..76e6fb76 100644 --- a/test/scale-offset-image.pdf.xfail.png +++ b/test/scale-offset-image.pdf.xfail.png diff --git a/test/scale-offset-image.xfail.png b/test/scale-offset-image.xfail.png Binary files differindex 3e0191a1..fef3a399 100644 --- a/test/scale-offset-image.xfail.png +++ b/test/scale-offset-image.xfail.png diff --git a/test/scale-offset-image.xlib-fallback.xfail.png b/test/scale-offset-image.xlib-fallback.xfail.png Binary files differindex 1a286cdb..0dd71002 100644 --- a/test/scale-offset-image.xlib-fallback.xfail.png +++ b/test/scale-offset-image.xlib-fallback.xfail.png diff --git a/test/scale-offset-image.xlib.xfail.png b/test/scale-offset-image.xlib.xfail.png Binary files differindex efb00917..89611db5 100644 --- a/test/scale-offset-image.xlib.xfail.png +++ b/test/scale-offset-image.xlib.xfail.png diff --git a/test/scale-offset-similar.meta.xfail.png b/test/scale-offset-similar.meta.xfail.png Binary files differindex 83d53e65..0f2553e4 100644 --- a/test/scale-offset-similar.meta.xfail.png +++ b/test/scale-offset-similar.meta.xfail.png diff --git a/test/scale-offset-similar.pdf.xfail.png b/test/scale-offset-similar.pdf.xfail.png Binary files differindex 7808aeb9..38b9a209 100644 --- a/test/scale-offset-similar.pdf.xfail.png +++ b/test/scale-offset-similar.pdf.xfail.png diff --git a/test/scale-offset-similar.xfail.png b/test/scale-offset-similar.xfail.png Binary files differindex 3e0191a1..fef3a399 100644 --- a/test/scale-offset-similar.xfail.png +++ b/test/scale-offset-similar.xfail.png diff --git a/test/scale-offset-similar.xlib-fallback.xfail.png b/test/scale-offset-similar.xlib-fallback.xfail.png Binary files differindex 1a286cdb..0dd71002 100644 --- a/test/scale-offset-similar.xlib-fallback.xfail.png +++ b/test/scale-offset-similar.xlib-fallback.xfail.png diff --git a/test/scale-offset-similar.xlib.xfail.png b/test/scale-offset-similar.xlib.xfail.png Binary files differindex d58aebab..09e17daa 100644 --- a/test/scale-offset-similar.xlib.xfail.png +++ b/test/scale-offset-similar.xlib.xfail.png diff --git a/test/self-intersecting.argb32.xfail.png b/test/self-intersecting.argb32.xfail.png Binary files differdeleted file mode 100644 index f644ed46..00000000 --- a/test/self-intersecting.argb32.xfail.png +++ /dev/null diff --git a/test/self-intersecting.c b/test/self-intersecting.c index 6719e93f..8053235a 100644 --- a/test/self-intersecting.c +++ b/test/self-intersecting.c @@ -47,6 +47,9 @@ static cairo_test_status_t draw (cairo_t *cr, int width, int height) { + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + cairo_translate (cr, 1.0, 1.0); cairo_set_source_rgb (cr, 1, 0, 0); /* red */ diff --git a/test/self-intersecting.pdf.argb32.xfail.png b/test/self-intersecting.pdf.argb32.xfail.png Binary files differdeleted file mode 100644 index eb38a893..00000000 --- a/test/self-intersecting.pdf.argb32.xfail.png +++ /dev/null diff --git a/test/self-intersecting.pdf.rgb24.xfail.png b/test/self-intersecting.pdf.rgb24.xfail.png Binary files differdeleted file mode 100644 index a79a2975..00000000 --- a/test/self-intersecting.pdf.rgb24.xfail.png +++ /dev/null diff --git a/test/self-intersecting.ps.argb32.xfail.png b/test/self-intersecting.ps.ref.png Binary files differindex 84fde017..84fde017 100644 --- a/test/self-intersecting.ps.argb32.xfail.png +++ b/test/self-intersecting.ps.ref.png diff --git a/test/self-intersecting.ps.rgb24.xfail.png b/test/self-intersecting.ps.rgb24.xfail.png Binary files differdeleted file mode 100644 index c8136070..00000000 --- a/test/self-intersecting.ps.rgb24.xfail.png +++ /dev/null diff --git a/test/self-intersecting.ref.png b/test/self-intersecting.ref.png Binary files differindex 384b0abc..b2f42599 100644 --- a/test/self-intersecting.ref.png +++ b/test/self-intersecting.ref.png diff --git a/test/self-intersecting.rgb24.ref.png b/test/self-intersecting.rgb24.ref.png Binary files differdeleted file mode 100644 index 51217926..00000000 --- a/test/self-intersecting.rgb24.ref.png +++ /dev/null diff --git a/test/self-intersecting.rgb24.xfail.png b/test/self-intersecting.rgb24.xfail.png Binary files differdeleted file mode 100644 index 958215c2..00000000 --- a/test/self-intersecting.rgb24.xfail.png +++ /dev/null diff --git a/test/self-intersecting.xlib.argb32.xfail.png b/test/self-intersecting.xlib.argb32.xfail.png Binary files differdeleted file mode 100644 index f644ed46..00000000 --- a/test/self-intersecting.xlib.argb32.xfail.png +++ /dev/null diff --git a/test/self-intersecting.xlib.ref.png b/test/self-intersecting.xlib.ref.png Binary files differnew file mode 100644 index 00000000..f8fe0c03 --- /dev/null +++ b/test/self-intersecting.xlib.ref.png diff --git a/test/self-intersecting.xlib.rgb24.xfail.png b/test/self-intersecting.xlib.rgb24.xfail.png Binary files differdeleted file mode 100644 index 958215c2..00000000 --- a/test/self-intersecting.xlib.rgb24.xfail.png +++ /dev/null diff --git a/test/smask-stroke.ref.png b/test/smask-stroke.ref.png Binary files differindex 0d3f7fea..c94a09fc 100644 --- a/test/smask-stroke.ref.png +++ b/test/smask-stroke.ref.png diff --git a/test/smask-stroke.xlib.ref.png b/test/smask-stroke.xlib.ref.png Binary files differnew file mode 100644 index 00000000..71f427bb --- /dev/null +++ b/test/smask-stroke.xlib.ref.png diff --git a/test/smask.ps.ref.png b/test/smask.ps.ref.png Binary files differnew file mode 100644 index 00000000..31ccc17b --- /dev/null +++ b/test/smask.ps.ref.png diff --git a/test/smask.ps2.ref.png b/test/smask.ps2.ref.png Binary files differdeleted file mode 100644 index c006bbda..00000000 --- a/test/smask.ps2.ref.png +++ /dev/null diff --git a/test/smask.ps3.ref.png b/test/smask.ps3.ref.png Binary files differdeleted file mode 100644 index c006bbda..00000000 --- a/test/smask.ps3.ref.png +++ /dev/null diff --git a/test/smask.ref.png b/test/smask.ref.png Binary files differindex e1904548..b5919de3 100644 --- a/test/smask.ref.png +++ b/test/smask.ref.png diff --git a/test/smask.svg.ref.png b/test/smask.svg.ref.png Binary files differindex b4ad5270..b9c0308d 100644 --- a/test/smask.svg.ref.png +++ b/test/smask.svg.ref.png diff --git a/test/smask.xlib.ref.png b/test/smask.xlib.ref.png Binary files differindex bb70abfe..5ce0082c 100644 --- a/test/smask.xlib.ref.png +++ b/test/smask.xlib.ref.png diff --git a/test/spline-decomposition.pdf.ref.png b/test/spline-decomposition.pdf.ref.png Binary files differindex 9ea094a7..5afa0949 100644 --- a/test/spline-decomposition.pdf.ref.png +++ b/test/spline-decomposition.pdf.ref.png diff --git a/test/spline-decomposition.ps.ref.png b/test/spline-decomposition.ps.ref.png Binary files differindex 2b3c07dc..e6324041 100644 --- a/test/spline-decomposition.ps.ref.png +++ b/test/spline-decomposition.ps.ref.png diff --git a/test/spline-decomposition.ref.png b/test/spline-decomposition.ref.png Binary files differindex bac35a9c..426aefaa 100644 --- a/test/spline-decomposition.ref.png +++ b/test/spline-decomposition.ref.png diff --git a/test/spline-decomposition.svg.ref.png b/test/spline-decomposition.svg.ref.png Binary files differindex 9ea094a7..5afa0949 100644 --- a/test/spline-decomposition.svg.ref.png +++ b/test/spline-decomposition.svg.ref.png diff --git a/test/spline-decomposition.xlib.ref.png b/test/spline-decomposition.xlib.ref.png Binary files differnew file mode 100644 index 00000000..30d05b3b --- /dev/null +++ b/test/spline-decomposition.xlib.ref.png diff --git a/test/stroke-ctm-caps.ref.png b/test/stroke-ctm-caps.ref.png Binary files differindex f3643576..799ff392 100644 --- a/test/stroke-ctm-caps.ref.png +++ b/test/stroke-ctm-caps.ref.png diff --git a/test/stroke-image.pdf.ref.png b/test/stroke-image.pdf.ref.png Binary files differindex f3eb75d8..80df1004 100644 --- a/test/stroke-image.pdf.ref.png +++ b/test/stroke-image.pdf.ref.png diff --git a/test/stroke-image.ps.ref.png b/test/stroke-image.ps.ref.png Binary files differnew file mode 100644 index 00000000..208e008b --- /dev/null +++ b/test/stroke-image.ps.ref.png diff --git a/test/stroke-image.ps2.ref.png b/test/stroke-image.ps2.ref.png Binary files differdeleted file mode 100644 index a1871674..00000000 --- a/test/stroke-image.ps2.ref.png +++ /dev/null diff --git a/test/stroke-image.ps3.ref.png b/test/stroke-image.ps3.ref.png Binary files differdeleted file mode 100644 index a1871674..00000000 --- a/test/stroke-image.ps3.ref.png +++ /dev/null diff --git a/test/stroke-image.ref.png b/test/stroke-image.ref.png Binary files differindex c8b9417d..a3c57aa1 100644 --- a/test/stroke-image.ref.png +++ b/test/stroke-image.ref.png diff --git a/test/stroke-image.xlib.ref.png b/test/stroke-image.xlib.ref.png Binary files differnew file mode 100644 index 00000000..dbb5c5a1 --- /dev/null +++ b/test/stroke-image.xlib.ref.png diff --git a/test/surface-pattern.pdf.xfail.png b/test/surface-pattern.pdf.xfail.png Binary files differindex a43dc4d0..fadc2c24 100644 --- a/test/surface-pattern.pdf.xfail.png +++ b/test/surface-pattern.pdf.xfail.png diff --git a/test/text-rotate.pdf.ref.png b/test/text-rotate.pdf.ref.png Binary files differindex bdd64e6e..ea82bfe4 100644 --- a/test/text-rotate.pdf.ref.png +++ b/test/text-rotate.pdf.ref.png diff --git a/test/text-rotate.ref.png b/test/text-rotate.ref.png Binary files differindex b2273989..291c6c7d 100644 --- a/test/text-rotate.ref.png +++ b/test/text-rotate.ref.png diff --git a/test/text-rotate.svg.ref.png b/test/text-rotate.svg.ref.png Binary files differindex 7ffd356b..9d887a02 100644 --- a/test/text-rotate.svg.ref.png +++ b/test/text-rotate.svg.ref.png diff --git a/test/text-rotate.xlib.ref.png b/test/text-rotate.xlib.ref.png Binary files differnew file mode 100644 index 00000000..3d359649 --- /dev/null +++ b/test/text-rotate.xlib.ref.png diff --git a/test/twin.ps.ref.png b/test/twin.ps.ref.png Binary files differindex e75062ca..16b49ba6 100644 --- a/test/twin.ps.ref.png +++ b/test/twin.ps.ref.png diff --git a/test/twin.ref.png b/test/twin.ref.png Binary files differindex 3c46b02a..f5d524fb 100644 --- a/test/twin.ref.png +++ b/test/twin.ref.png diff --git a/test/twin.xlib.ref.png b/test/twin.xlib.ref.png Binary files differnew file mode 100644 index 00000000..04421867 --- /dev/null +++ b/test/twin.xlib.ref.png diff --git a/test/unantialiased-shapes.ref.png b/test/unantialiased-shapes.ref.png Binary files differindex 128a2a16..c909d387 100644 --- a/test/unantialiased-shapes.ref.png +++ b/test/unantialiased-shapes.ref.png diff --git a/test/unbounded-operator.svg12.argb32.ref.png b/test/unbounded-operator.svg12.argb32.ref.png Binary files differnew file mode 100644 index 00000000..45b173fa --- /dev/null +++ b/test/unbounded-operator.svg12.argb32.ref.png diff --git a/test/unbounded-operator.svg12.argb32.xfail.png b/test/unbounded-operator.svg12.argb32.xfail.png Binary files differdeleted file mode 100644 index 15965c8e..00000000 --- a/test/unbounded-operator.svg12.argb32.xfail.png +++ /dev/null diff --git a/test/unbounded-operator.svg12.rgb24.xfail.png b/test/unbounded-operator.svg12.rgb24.xfail.png Binary files differindex 828a9db9..c369fd26 100644 --- a/test/unbounded-operator.svg12.rgb24.xfail.png +++ b/test/unbounded-operator.svg12.rgb24.xfail.png diff --git a/test/user-font-proxy.svg.ref.png b/test/user-font-proxy.svg.ref.png Binary files differindex 6c458485..747750a5 100644 --- a/test/user-font-proxy.svg.ref.png +++ b/test/user-font-proxy.svg.ref.png diff --git a/test/user-font.ps.ref.png b/test/user-font.ps.ref.png Binary files differnew file mode 100644 index 00000000..63f28969 --- /dev/null +++ b/test/user-font.ps.ref.png diff --git a/test/user-font.ps2.ref.png b/test/user-font.ps2.ref.png Binary files differdeleted file mode 100644 index e1dd00ab..00000000 --- a/test/user-font.ps2.ref.png +++ /dev/null diff --git a/test/user-font.ps3.ref.png b/test/user-font.ps3.ref.png Binary files differdeleted file mode 100644 index e1dd00ab..00000000 --- a/test/user-font.ps3.ref.png +++ /dev/null diff --git a/test/user-font.ref.png b/test/user-font.ref.png Binary files differindex 753fc7bc..ab18e1ce 100644 --- a/test/user-font.ref.png +++ b/test/user-font.ref.png diff --git a/test/user-font.xlib.ref.png b/test/user-font.xlib.ref.png Binary files differindex abc31171..d81ecf1d 100644 --- a/test/user-font.xlib.ref.png +++ b/test/user-font.xlib.ref.png diff --git a/util/.gitignore b/util/.gitignore index aa0e19b5..01eb637d 100644 --- a/util/.gitignore +++ b/util/.gitignore @@ -2,6 +2,9 @@ .libs Makefile Makefile.in +show-edges +show-events +show-traps *.so *.la *.lo diff --git a/util/Makefile.am b/util/Makefile.am index d90d852f..79bc3362 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -6,6 +6,25 @@ if BUILD_TRACE SUBDIRS += cairo-trace endif +AM_CPPFLAGS = -I$(top_srcdir)/src + +EXTRA_PROGRAMS += show-traps show-edges show-events + +show_traps_SOURCES = show-traps.c +show_traps_CFLAGS = $(gtk_CFLAGS) +#show_traps_LDADD = $(top_builddir)/src/libcairo.la $(gtk_LIBS) +show_traps_LDADD = $(gtk_LIBS) + +show_edges_SOURCES = show-edges.c +show_edges_CFLAGS = $(gtk_CFLAGS) +#show_edges_LDADD = $(top_builddir)/src/libcairo.la $(gtk_LIBS) +show_edges_LDADD = $(gtk_LIBS) + +show_events_SOURCES = show-events.c +show_events_CFLAGS = $(gtk_CFLAGS) +#show_events_LDADD = $(top_builddir)/src/libcairo.la $(gtk_LIBS) +show_events_LDADD = $(gtk_LIBS) + util: malloc-stats.so backtrace-symbols.so .la.so: @@ -20,12 +39,10 @@ AM_LDFLAGS = -module -avoid-version -export-dynamic -rpath /dev/null EXTRA_LTLIBRARIES += malloc-stats.la backtrace-symbols.la -backtrace_symbols_la_LIBADD = -lbfd -liberty +backtrace_symbols_la_LIBADD = -lbfd -liberty #malloc_stats_la_LIBADD = $(backtrace_symbols_la_LIBADD) backtrace-symbols.lo -noinst_PROGRAMS = - if HAVE_GTK EXTRA_PROGRAMS += font-view font_view_CFLAGS = $(gtk_CFLAGS) diff --git a/util/show-edges.c b/util/show-edges.c new file mode 100644 index 00000000..d3b94bc0 --- /dev/null +++ b/util/show-edges.c @@ -0,0 +1,1189 @@ +#define _GNU_SOURCE +#include <gtk/gtk.h> +#include <stdlib.h> +#include <stdio.h> +#include <gdk/gdkkeysyms.h> +#include <math.h> + +typedef struct _point { + gdouble x, y; +} point_t; +typedef struct _box { + point_t p1, p2; +} box_t; +typedef struct _line { + point_t p1, p2; +} line_t; +typedef struct _trapezoid { + gdouble top, bottom; + line_t left, right; +} trapezoid_t; +typedef struct _traps { + struct _traps *next, *prev; + box_t extents; + int num_traps; + int size; + trapezoid_t traps[0]; +} traps_t; + +typedef struct _edge { + line_t line; + gdouble top, bottom; + point_t p1, p2; + int dir; +} edge_t; +typedef struct _edges { + struct _edges *next, *prev; + box_t extents; + int num_edges; + int size; + edge_t edges[0]; +} edges_t; + +typedef struct _TrapView { + GtkWidget widget; + + struct _TrapView *group_head; + struct _TrapView *group_next; + struct _TrapView *group_prev; + + traps_t *traps_list; + traps_t *current_traps; + + edges_t *edges_list; + edges_t *current_edges; + + double px, py; + + gint mag_x, mag_y; + gint mag_size; + gdouble mag_zoom; + gboolean in_mag_drag; + gint mag_drag_x, mag_drag_y; +} TrapView; + +typedef struct _TrapViewClass { + GtkWidgetClass parent_class; +} TrapViewClass; + +G_DEFINE_TYPE (TrapView, trap_view, GTK_TYPE_WIDGET) + +static gdouble +_compute_intersection_x_for_y (const line_t *line, + gdouble y) +{ + gdouble dx = line->p2.x - line->p1.x; + gdouble dy = line->p2.y - line->p1.y; + gdouble x; + + if (y == line->p1.y) + return line->p1.x; + if (y == line->p2.y) + return line->p2.x; + + x = line->p1.x; + if (dy != 0) + x += (y - line->p1.y)*dx/dy; + return x; +} + +static void +_compute_intersection_point (const line_t *line, + gdouble y, + point_t *p) +{ + p->x = _compute_intersection_x_for_y (line, p->y = y); +} + +static void +trap_view_draw (TrapView *self, cairo_t *cr) +{ + traps_t *traps; + edges_t *edges; + gdouble sf_x, sf_y, sf; + gdouble mid, dim; + gdouble x0, x1, y0, y1; + double dash[2] = {8, 8}; + double dots[2] = {0., 1.}; + int n; + box_t extents; + point_t p; + + cairo_save (cr); + cairo_save (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + cairo_restore (cr); + + traps = self->current_traps; + edges = self->current_edges; + if (traps == NULL && edges == NULL) + return; + + if (traps != NULL) { + extents = traps->extents; + if (edges != NULL) { + if (edges->extents.p1.x < extents.p1.x) + extents.p1.x = edges->extents.p1.x; + if (edges->extents.p1.y < extents.p1.y) + extents.p1.y = edges->extents.p1.y; + if (edges->extents.p2.x > extents.p2.x) + extents.p2.x = edges->extents.p2.x; + if (edges->extents.p2.y > extents.p2.y) + extents.p2.y = edges->extents.p2.y; + } + } else + extents = edges->extents; + + mid = (extents.p2.x + extents.p1.x) / 2.; + dim = (extents.p2.x - extents.p1.x) / 2. * 1.25; + sf_x = self->widget.allocation.width / dim / 2; + + mid = (extents.p2.y + extents.p1.y) / 2.; + dim = (extents.p2.y - extents.p1.y) / 2. * 1.25; + sf_y = self->widget.allocation.height / dim / 2; + + sf = MIN (sf_x, sf_y); + + mid = (extents.p2.x + extents.p1.x) / 2.; + dim = sf_x / sf * (extents.p2.x - extents.p1.x) / 2. * 1.25; + x0 = mid - dim; + x1 = mid + dim; + mid = (extents.p2.y + extents.p1.y) / 2.; + dim = sf_y / sf * (extents.p2.y - extents.p1.y) / 2. * 1.25; + y0 = mid - dim; + y1 = mid + dim; + + if (traps != NULL) { + cairo_save (cr); + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_set_source_rgba (cr, 0, 1, 0, .2); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_fill (cr); + } + cairo_restore (cr); + } + + if (edges == NULL) { + cairo_save (cr); + + /* top, bottom */ + cairo_save (cr); { + cairo_matrix_t m; + cairo_matrix_init_scale (&m, sf, sf); + cairo_matrix_translate (&m, -x0, -y0); + + cairo_set_line_width (cr, 1.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + } + } cairo_restore (cr); + + /* left extents */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->left.p1.x, t->left.p1.y); + cairo_line_to (cr, t->left.p2.x, t->left.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_line_width (cr, 1.); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + /* left line */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_stroke (cr); + } cairo_restore (cr); + + /* right extents */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->right.p1.x, t->right.p1.y); + cairo_line_to (cr, t->right.p2.x, t->right.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_line_width (cr, 1.); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + /* right line */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_stroke (cr); + } cairo_restore (cr); + } + + /* end-points */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } cairo_restore (cr); + + cairo_restore (cr); + } else { + cairo_save (cr); + + for (n = 0; n < edges->num_edges; n++) { + const edge_t *e = &edges->edges[n]; + + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_line_to (cr, e->p2.x, e->p2.y); + } cairo_restore (cr); + + if (e->dir < 0) { + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_dash (cr, dash, 2, dash[0]); + } else { + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_dash (cr, dash, 2, 0.); + } + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_close_path (cr); + cairo_move_to (cr, e->p2.x, e->p2.y); + cairo_close_path (cr); + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } + + cairo_restore (cr); + } + + /* draw a zoom view of the area around the mouse */ + { + cairo_save (cr); + double zoom = self->mag_zoom; + int size = self->mag_size; + + /* bottom right */ + cairo_rectangle (cr, self->mag_x, self->mag_y, size, size); + cairo_stroke_preserve (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill_preserve (cr); + cairo_clip (cr); + + /* compute roi in extents */ + cairo_translate (cr, self->mag_x + size/2, self->mag_y + size/2); + + if (traps != NULL) { + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_set_source_rgba (cr, 0, 1, 0, .2); + cairo_fill (cr); + } + cairo_restore (cr); + } + + if (edges == NULL) { + cairo_save (cr); { + cairo_matrix_t m; + cairo_matrix_init_scale (&m, zoom, zoom); + cairo_matrix_translate (&m, -(self->px / sf + x0), -(self->py /sf + y0)); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_line_width (cr, 1.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + } + } cairo_restore (cr); + + cairo_save (cr); { /* left extents */ + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->left.p1.x, t->left.p1.y); + cairo_line_to (cr, t->left.p2.x, t->left.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_line_width (cr, .5); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + cairo_save (cr); { /* right extents */ + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->right.p1.x, t->right.p1.y); + cairo_line_to (cr, t->right.p2.x, t->right.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_line_width (cr, .5); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + cairo_save (cr); { /* left lines */ + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_stroke (cr); + } cairo_restore (cr); + cairo_save (cr); { /* right lines */ + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_stroke (cr); + } cairo_restore (cr); + + /* end-points */ + cairo_save (cr); { + double dots[2] = {0., 1.}; + + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } cairo_restore (cr); + } else { + cairo_save (cr); + + for (n = 0; n < edges->num_edges; n++) { + const edge_t *e = &edges->edges[n]; + + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_line_to (cr, e->p2.x, e->p2.y); + } cairo_restore (cr); + + if (e->dir < 0) { + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_dash (cr, dash, 2, dash[0]); + } else { + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_dash (cr, dash, 2, 0.); + } + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_close_path (cr); + cairo_move_to (cr, e->p2.x, e->p2.y); + cairo_close_path (cr); + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } + + cairo_restore (cr); + } + + /* grid */ + cairo_save (cr); { + int i; + + cairo_translate (cr, + -zoom*fmod (self->px/sf + x0, 1.), + -zoom*fmod (self->py/sf + y0, 1.)); + for (i = -size/2/zoom - 1; i <= size/2/zoom + 1; i++) { + cairo_move_to (cr, zoom*i, -size/2); + cairo_line_to (cr, zoom*i, size/2 + zoom); + cairo_move_to (cr, -size/2, zoom*i); + cairo_line_to (cr, size/2 + zoom, zoom*i); + } + cairo_set_source_rgba (cr, .7, .7, .7, .5); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + } cairo_restore (cr); + } + + cairo_restore (cr); +} + +static gdouble +trapezoid_area (const trapezoid_t *t) +{ + gdouble inner_left, inner_right; + gdouble outer_left, outer_right; + gdouble height; + gdouble area; + + /* split into 3 sections: a rectangle with a pair of triangular bookends */ + inner_left = _compute_intersection_x_for_y (&t->left, t->top); + outer_left = _compute_intersection_x_for_y (&t->left, t->bottom); + if (outer_left > inner_left) { + gdouble t = outer_left; + outer_left = inner_left; + inner_left = t; + } + + inner_right = _compute_intersection_x_for_y (&t->right, t->top); + outer_right = _compute_intersection_x_for_y (&t->right, t->bottom); + if (outer_right > inner_right) { + gdouble t = outer_right; + outer_right = inner_right; + inner_right = t; + } + + if (outer_left > outer_right) { /* reverse */ + gdouble t; + + t = outer_left; + outer_left = inner_right; + inner_right = t; + + t = inner_left; + inner_left = outer_right; + outer_right = t; + } + + height = t->bottom - t->top; + area = (inner_left - outer_left) * height / 2; + area += (outer_right - inner_right) * height / 2; + area += (inner_right - inner_left) * height; + + return area; +} + +static gdouble +traps_compute_total_area (const traps_t *traps) +{ + int n; + gdouble area = 0.; + for (n = 0; n < traps->num_traps; n++) + area += trapezoid_area (&traps->traps[n]); + return area; +} + +static void +trap_view_draw_labels (TrapView *self, cairo_t *cr) +{ + PangoLayout *layout; + gint width, height; + gdouble total_area; + gchar *str; + traps_t *traps; + + traps = self->current_traps; + if (traps == NULL) + return; + + /* convert total area from fixed-point (assuming 24.8) */ + total_area = traps_compute_total_area (traps) / (256. * 256.); + str = g_strdup_printf ("Number of trapezoids:\t%d\n" + "Total area of trapezoids:\t%.2f", + traps->num_traps, + total_area); + layout = gtk_widget_create_pango_layout (&self->widget, str); + g_free (str); + + pango_layout_get_pixel_size (layout, &width, &height); + + cairo_move_to (cr, 10, 10 + height); + pango_cairo_show_layout (cr, layout); + g_object_unref (layout); +} + +static gboolean +trap_view_expose (GtkWidget *w, GdkEventExpose *ev) +{ + TrapView *self = (TrapView *) w; + cairo_t *cr; + + cr = gdk_cairo_create (w->window); + gdk_cairo_region (cr, ev->region); + cairo_clip (cr); + + trap_view_draw (self, cr); + trap_view_draw_labels (self, cr); + + cairo_destroy (cr); + return FALSE; +} + +static void +trap_view_advance (TrapView *self) +{ + if (self->current_traps && self->current_traps->prev) + self->current_traps = self->current_traps->prev; + if (self->current_edges && self->current_edges->prev) + self->current_edges = self->current_edges->prev; + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_back (TrapView *self) +{ + if (self->current_traps && self->current_traps->next) + self->current_traps = self->current_traps->next; + if (self->current_edges && self->current_edges->next) + self->current_edges = self->current_edges->next; + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_group_foreach (TrapView *group, GFunc func, gpointer data) +{ + while (group) { + func (group, data); + group = group->group_next; + } +} + +static gboolean +trap_view_key_press (GtkWidget *w, GdkEventKey *ev) +{ + TrapView *self = (TrapView *) w; + + switch (ev->keyval) { + case GDK_BackSpace: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_back, + NULL); + break; + + case GDK_space: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + break; + + case GDK_Return: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + break; + + case GDK_Escape: + case GDK_Q: + gtk_main_quit (); + break; + } + + return FALSE; +} + +static gboolean +trap_view_button_press (GtkWidget *w, GdkEventButton *ev) +{ + TrapView *self = (TrapView *) w; + + if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + if (ev->type == GDK_BUTTON_PRESS) { + if (self->current_traps == NULL) + return FALSE; + + if (ev->button == 1) { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + } else if (ev->button == 3) { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_back, + NULL); + } + } + } + else + { + self->in_mag_drag = TRUE; + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } + + return FALSE; +} + +static gboolean +trap_view_button_release (GtkWidget *w, GdkEventButton *ev) +{ + TrapView *self = (TrapView *) w; + + self->in_mag_drag = FALSE; + + return FALSE; +} + +static void +trap_view_update_mouse (TrapView *self, GdkEventMotion *ev) +{ + self->px = ev->x; + self->py = ev->y; + + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_update_magnifier (TrapView *self, gint *xy) +{ + self->mag_x = xy[0]; + self->mag_y = xy[1]; + + gtk_widget_queue_draw (&self->widget); +} + +static gboolean +trap_view_motion (GtkWidget *w, GdkEventMotion *ev) +{ + TrapView *self = (TrapView *) w; + + if (self->in_mag_drag) { + int xy[2]; + + xy[0] = self->mag_x + ev->x - self->mag_drag_x; + xy[1] = self->mag_y + ev->y - self->mag_drag_y; + + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_update_magnifier, + xy); + + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } else if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_update_mouse, + ev); + } + + return FALSE; +} + +static void +trap_view_realize (GtkWidget *widget) +{ + GdkWindowAttr attributes; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_EXPOSURE_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, + GDK_WA_X | GDK_WA_Y | + GDK_WA_VISUAL | GDK_WA_COLORMAP); + gdk_window_set_user_data (widget->window, widget); + + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); +} + +static void +trap_view_size_allocate (GtkWidget *w, GdkRectangle *r) +{ + TrapView *self = (TrapView *) w; + + GTK_WIDGET_CLASS (trap_view_parent_class)->size_allocate (w, r); + + self->mag_x = w->allocation.width - self->mag_size - 10; + self->mag_y = w->allocation.height - self->mag_size - 10; +} + +static void +trap_view_finalize (GObject *obj) +{ + G_OBJECT_CLASS (trap_view_parent_class)->finalize (obj); +} + +static void +trap_view_class_init (TrapViewClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + object_class->finalize = trap_view_finalize; + + widget_class->realize = trap_view_realize; + widget_class->size_allocate = trap_view_size_allocate; + widget_class->expose_event = trap_view_expose; + widget_class->key_press_event = trap_view_key_press; + widget_class->button_press_event = trap_view_button_press; + widget_class->button_release_event = trap_view_button_release; + widget_class->motion_notify_event = trap_view_motion; +} + +static void +trap_view_init (TrapView *self) +{ + self->mag_zoom = 10; + self->mag_size = 200; + + GTK_WIDGET_SET_FLAGS (self, GTK_CAN_FOCUS); +} + +static traps_t * +_traps_add_trapezoid (TrapView *tv, traps_t *traps, const trapezoid_t *trap) +{ + if (trap->top < traps->extents.p1.y) + traps->extents.p1.y = trap->top; + if (trap->bottom > traps->extents.p2.y) + traps->extents.p2.y = trap->bottom; + + if (trap->left.p1.x < traps->extents.p1.x) + traps->extents.p1.x = trap->left.p1.x; + if (trap->left.p2.x < traps->extents.p1.x) + traps->extents.p1.x = trap->left.p2.x; + + if (trap->right.p1.x > traps->extents.p2.x) + traps->extents.p2.x = trap->right.p1.x; + if (trap->right.p2.x > traps->extents.p2.x) + traps->extents.p2.x = trap->right.p2.x; + + if (traps->num_traps == traps->size) { + int newsize = 2 * traps->size; + void *newtraps; + + newtraps = g_realloc (traps, + sizeof (traps_t) + newsize * sizeof (trapezoid_t)); + if (newtraps == NULL) + return traps; + + if (tv->current_traps == traps) + tv->current_traps = newtraps; + + traps = newtraps; + traps->size = newsize; + + if (traps->next != NULL) + traps->next->prev = newtraps; + if (traps->prev != NULL) + traps->prev->next = newtraps; + else + tv->traps_list = newtraps; + } + + traps->traps[traps->num_traps++] = *trap; + + return traps; +} + +static traps_t * +traps_new (TrapView *tv) +{ + traps_t *t; + + t = g_malloc (sizeof (traps_t) + 16 * sizeof (trapezoid_t)); + t->prev = NULL; + t->next = tv->traps_list; + if (tv->traps_list) + tv->traps_list->prev = t; + tv->traps_list = t; + + if (tv->current_traps == NULL) + tv->current_traps = t; + + t->size = 16; + t->num_traps = 0; + t->extents.p1.x = G_MAXDOUBLE; + t->extents.p1.y = G_MAXDOUBLE; + t->extents.p2.x = -G_MAXDOUBLE; + t->extents.p2.y = -G_MAXDOUBLE; + + return t; +} + +static edges_t * +_edges_add_edge (TrapView *tv, edges_t *edges, edge_t *e) +{ + if (e->top < edges->extents.p1.y) + edges->extents.p1.y = e->top; + if (e->bottom > edges->extents.p2.y) + edges->extents.p2.y = e->bottom; + + _compute_intersection_point (&e->line, e->top, &e->p1); + _compute_intersection_point (&e->line, e->bottom, &e->p2); + + if (e->p1.x < edges->extents.p1.x) + edges->extents.p1.x = e->p1.x; + if (e->p2.x < edges->extents.p1.x) + edges->extents.p1.x = e->p2.x; + + if (e->p1.x > edges->extents.p2.x) + edges->extents.p2.x = e->p1.x; + if (e->p2.x > edges->extents.p2.x) + edges->extents.p2.x = e->p2.x; + + if (edges->num_edges == edges->size) { + int newsize = 2 * edges->size; + void *newedges; + + newedges = g_realloc (edges, + sizeof (edges_t) + newsize * sizeof (edge_t)); + if (newedges == NULL) + return edges; + + if (tv->current_edges == edges) + tv->current_edges = newedges; + + edges = newedges; + edges->size = newsize; + + if (edges->next != NULL) + edges->next->prev = newedges; + if (edges->prev != NULL) + edges->prev->next = newedges; + else + tv->edges_list = newedges; + } + + edges->edges[edges->num_edges++] = *e; + + return edges; +} + +static edges_t * +edges_new (TrapView *tv) +{ + edges_t *t; + + t = g_malloc (sizeof (edges_t) + 16 * sizeof (edge_t)); + t->prev = NULL; + t->next = tv->edges_list; + if (tv->edges_list) + tv->edges_list->prev = t; + tv->edges_list = t; + + if (tv->current_edges == NULL) + tv->current_edges = t; + + t->size = 16; + t->num_edges = 0; + t->extents.p1.x = G_MAXDOUBLE; + t->extents.p1.y = G_MAXDOUBLE; + t->extents.p2.x = -G_MAXDOUBLE; + t->extents.p2.y = -G_MAXDOUBLE; + + return t; +} + +int +main (int argc, char **argv) +{ + TrapView *tv, *tv2, *group_head = NULL, *group_prev = NULL; + traps_t *traps; + edges_t *edges; + GtkWidget *window, *hbox; + FILE *file; + char *line = NULL; + size_t len = 0; + + gtk_init (&argc, &argv); + + hbox = gtk_hbox_new (TRUE, 0); + + tv = g_object_new (trap_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &tv->widget, TRUE, TRUE, 0); + gtk_widget_show (&tv->widget); + + tv->group_prev = group_prev; + tv->group_next = NULL; + if (group_prev) + group_prev->group_next = tv; + group_prev = tv; + if (group_head == NULL) + group_head = tv; + tv->group_head = group_head; + + file = fopen (argv[1], "r"); + if (file != NULL) { + edges = edges_new (tv); + while (getline (&line, &len, file) != -1) { + edge_t e; + + if (sscanf (line, + "(%lf, %lf), (%lf, %lf) %lf %lf %d", + &e.line.p1.x, &e.line.p1.y, + &e.line.p2.x, &e.line.p2.y, + &e.top, &e.bottom, + &e.dir) == 7) { + edges = _edges_add_edge (tv, edges, &e); + } else { + if (edges->num_edges) { + g_print ("read %d edges\n", edges->num_edges); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + edges->extents.p1.x, edges->extents.p1.y, + edges->extents.p2.x, edges->extents.p2.y); + edges = edges_new (tv); + } + } + } + + if (edges->num_edges) { + g_print ("read %d edges\n", edges->num_edges); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + edges->extents.p1.x, edges->extents.p1.y, + edges->extents.p2.x, edges->extents.p2.y); + } + + fclose (file); + } + + file = fopen (argv[2], "r"); + if (file != NULL) { + traps = traps_new (tv); + while (getline (&line, &len, file) != -1) { + trapezoid_t t; + + if (sscanf (line, + "%lf %lf L:(%lf, %lf), (%lf, %lf) R:(%lf, %lf), (%lf, %lf)", + &t.top, &t.bottom, + &t.left.p1.x, &t.left.p1.y, + &t.left.p2.x, &t.left.p2.y, + &t.right.p1.x, &t.right.p1.y, + &t.right.p2.x, &t.right.p2.y) == 10) { + traps = _traps_add_trapezoid (tv, traps, &t); + } else { + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + traps = traps_new (tv); + } + } + } + + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + } + + fclose (file); + } + + free (line); + + tv2 = g_object_new (trap_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &tv2->widget, TRUE, TRUE, 0); + gtk_widget_show (&tv2->widget); + + tv2->traps_list = tv->traps_list; + tv2->current_traps = tv->current_traps; + + tv2->group_prev = group_prev; + tv2->group_next = NULL; + group_prev->group_next = tv2; + group_prev = tv2; + tv2->group_head = group_head; + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (window, "delete-event", + G_CALLBACK (gtk_main_quit), NULL); + gtk_widget_set_size_request (window, 512, 512); + gtk_container_add (GTK_CONTAINER (window), hbox); + gtk_widget_show (hbox); + gtk_widget_show (window); + + gtk_main (); + return 0; +} diff --git a/util/show-events.c b/util/show-events.c new file mode 100644 index 00000000..8bff3efc --- /dev/null +++ b/util/show-events.c @@ -0,0 +1,845 @@ +#define _GNU_SOURCE +#include <gtk/gtk.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <gdk/gdkkeysyms.h> +#include <math.h> + +typedef struct _point { + gdouble x, y; +} point_t; +typedef struct _box { + point_t p1, p2; +} box_t; +typedef struct _line { + point_t p1, p2; +} line_t; + +typedef struct _edge { + gulong id; + line_t line; + gdouble top, bottom; + point_t p1, p2; + int dir; +} edge_t; +typedef struct _trapezoid { + gdouble top, bottom; + const edge_t *left, *right; +} trapezoid_t; +typedef struct _traps { + int num_traps; + int size; + trapezoid_t traps[0]; +} traps_t; + +typedef struct _edges { + GHashTable *ht; + + int num_edges; + int size; + edge_t edges[0]; +} edges_t; + +typedef struct _event { + enum { + START_EDGE, + END_EDGE, + INTERSECTION, + START_TRAP, + END_TRAP, + } type; + + int x, y; /* (top, bottom) for trap */ + long e1, e2; +} event_t; + +typedef struct _events { + struct _events *prev, *next; + + box_t extents; + edges_t *edges; + traps_t *prototraps; + traps_t *traps; + + int current_event; + int num_events; + int size_events; + event_t *events; +} events_t; + +typedef struct _EventView { + GtkWidget widget; + + events_t *events_list; + events_t *current_events; + + double px, py; + + gint mag_x, mag_y; + gint mag_size; + gdouble mag_zoom; + gboolean in_mag_drag; + gint mag_drag_x, mag_drag_y; +} EventView; + +typedef struct _EventViewClass { + GtkWidgetClass parent_class; +} EventViewClass; + +G_DEFINE_TYPE (EventView, event_view, GTK_TYPE_WIDGET) + +static edge_t * +edges_lookup (edges_t *edges, gulong id) +{ + return &edges->edges[GPOINTER_TO_UINT(g_hash_table_lookup (edges->ht, + GUINT_TO_POINTER + (id)))]; +} + +static gdouble +_compute_intersection_x_for_y (const line_t *line, + gdouble y) +{ + gdouble dx = line->p2.x - line->p1.x; + gdouble dy = line->p2.y - line->p1.y; + gdouble x; + + if (y == line->p1.y) + return line->p1.x; + if (y == line->p2.y) + return line->p2.x; + + x = line->p1.x; + if (dy != 0) + x += (y - line->p1.y)*dx/dy; + return x; +} + +static void +_compute_intersection_point (const line_t *line, + gdouble y, + point_t *p) +{ + p->x = _compute_intersection_x_for_y (line, p->y = y); +} + +static void +_edge_path (cairo_t *cr, const cairo_matrix_t *m, const edge_t *e) +{ + double x, y; + + x = e->p1.x; y = e->p1.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_move_to (cr, x, y); + + x = e->p2.x; y = e->p2.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_line_to (cr, x, y); + + if (e->dir < 0) { + cairo_set_source_rgb (cr, 0, 0, 1); + } else { + cairo_set_source_rgb (cr, 1, 0, 0); + } +} + +static void +_events_draw (events_t *events, cairo_t *cr, cairo_matrix_t *m) +{ + double dash[2] = {8, 8}; + point_t p; + int n; + + /* first the existing and proto-traps */ + cairo_save (cr); { + cairo_set_matrix (cr, m); + + cairo_set_source_rgba (cr, 1, 0, 0, .15); + for (n = 0; n < events->prototraps->num_traps; n++) { + const trapezoid_t *t = &events->prototraps->traps[n]; + + _compute_intersection_point (&t->left->line, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right->line, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right->line, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left->line, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_fill (cr); + } + + cairo_set_source_rgba (cr, 0, 1, 0, .2); + for (n = 0; n < events->traps->num_traps; n++) { + const trapezoid_t *t = &events->traps->traps[n]; + + _compute_intersection_point (&t->left->line, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right->line, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right->line, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left->line, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_fill (cr); + } + } cairo_restore (cr); + + /* known edges */ + cairo_save (cr); + cairo_set_line_width (cr, 1.); + for (n = 0; n < events->edges->num_edges; n++) { + const edge_t *e = &events->edges->edges[n]; + double x, y; + + x = e->p1.x; y = e->p1.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_move_to (cr, x, y); + + x = e->p2.x; y = e->p2.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_line_to (cr, x, y); + + if (e->dir < 0) { + cairo_set_source_rgba (cr, 0, 0, 1., .4); + cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1]) + dash[0]); + } else { + cairo_set_source_rgba (cr, 1, 0, 0., 4); + cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1])); + } + + cairo_stroke (cr); + + x = e->p1.x; y = e->p1.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_arc (cr, x, y, 2., 0, 2 * G_PI); + + x = e->p2.x; y = e->p2.y; + cairo_matrix_transform_point (m, &x, &y); + cairo_arc (cr, x, y, 2., 0, 2 * G_PI); + + cairo_fill (cr); + } + cairo_restore (cr); + + /* event time */ + cairo_save (cr); { + event_t *e; + double x, y; + + e = &events->events[events->current_event]; + + cairo_set_line_width (cr, 2.); + cairo_set_matrix (cr, m); + cairo_move_to (cr, + events->extents.p1.x, + e->y); + cairo_line_to (cr, + events->extents.p2.x, + e->y); + cairo_identity_matrix (cr); + cairo_stroke (cr); + + x = e->x; y = e->y; + cairo_matrix_transform_point (m, &x, &y); + switch (e->type) { + case START_EDGE: + case END_EDGE: + case INTERSECTION: + cairo_arc (cr, x, y, 4., 0, 2 * G_PI); + break; + case START_TRAP: + case END_TRAP: + break; + } + switch (e->type) { + case START_EDGE: + cairo_set_source_rgb (cr, 1, 0, 0); + break; + case END_EDGE: + cairo_set_source_rgb (cr, 0, 0, 1); + break; + case INTERSECTION: + cairo_set_source_rgb (cr, 1, 0, 1); + break; + case START_TRAP: + case END_TRAP: + break; + } + cairo_fill (cr); + + cairo_set_line_width (cr, 1.); + switch (e->type) { + case START_EDGE: + _edge_path (cr, m, edges_lookup (events->edges, e->e1)); + cairo_stroke (cr); + break; + case END_EDGE: + _edge_path (cr, m, edges_lookup (events->edges, e->e1)); + cairo_stroke (cr); + break; + case INTERSECTION: + _edge_path (cr, m, edges_lookup (events->edges, e->e1)); + cairo_stroke (cr); + _edge_path (cr, m, edges_lookup (events->edges, e->e2)); + cairo_stroke (cr); + break; + } + } cairo_restore (cr); +} + +static void +event_view_draw (EventView *self, cairo_t *cr) +{ + events_t *events; + gdouble sf_x, sf_y, sf; + gdouble mid, dim; + gdouble x0, x1, y0, y1; + cairo_matrix_t m; + + cairo_save (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + cairo_restore (cr); + + events = self->current_events; + if (events == NULL) + return; + + mid = (events->extents.p2.x + events->extents.p1.x) / 2.; + dim = (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2; + sf_x = self->widget.allocation.width / dim / 2; + + mid = (events->extents.p2.y + events->extents.p1.y) / 2.; + dim = (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2; + sf_y = self->widget.allocation.height / dim / 2; + + sf = MIN (sf_x, sf_y); + + mid = (events->extents.p2.x + events->extents.p1.x) / 2.; + dim = sf_x / sf * (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2; + x0 = mid - dim; + x1 = mid + dim; + mid = (events->extents.p2.y + events->extents.p1.y) / 2.; + dim = sf_y / sf * (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2; + y0 = mid - dim; + y1 = mid + dim; + + cairo_matrix_init_scale (&m, sf, sf); + cairo_matrix_translate (&m, -x0, -y0); + _events_draw (events, cr, &m); + + /* draw a zoom view of the area around the mouse */ + cairo_save (cr); { + double zoom = self->mag_zoom; + int size = self->mag_size; + + /* bottom right */ + cairo_rectangle (cr, self->mag_x, self->mag_y, size, size); + cairo_stroke_preserve (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill_preserve (cr); + cairo_clip (cr); + + /* compute roi in extents */ + cairo_translate (cr, self->mag_x + size/2, self->mag_y + size/2); + + cairo_matrix_init_scale (&m, zoom, zoom); + cairo_matrix_translate (&m, -(self->px / sf + x0), -(self->py /sf + y0)); + _events_draw (events, cr, &m); + + /* grid */ + cairo_save (cr); { + int i; + + cairo_translate (cr, + -zoom*fmod (self->px/sf + x0, 1.), + -zoom*fmod (self->py/sf + y0, 1.)); + for (i = -size/2/zoom - 1; i <= size/2/zoom + 1; i++) { + cairo_move_to (cr, zoom*i, -size/2); + cairo_line_to (cr, zoom*i, size/2 + zoom); + cairo_move_to (cr, -size/2, zoom*i); + cairo_line_to (cr, size/2 + zoom, zoom*i); + } + cairo_set_source_rgba (cr, .7, .7, .7, .5); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + } cairo_restore (cr); + } cairo_restore (cr); +} + +static void +event_view_draw_labels (EventView *self, cairo_t *cr) +{ + events_t *events; + + events = self->current_events; + if (events == NULL) + return; + +} + +static gboolean +event_view_expose (GtkWidget *w, GdkEventExpose *ev) +{ + EventView *self = (EventView *) w; + cairo_t *cr; + + cr = gdk_cairo_create (w->window); + gdk_cairo_region (cr, ev->region); + cairo_clip (cr); + + event_view_draw (self, cr); + event_view_draw_labels (self, cr); + + cairo_destroy (cr); + return FALSE; +} + +static void +traps_clear (traps_t *traps) +{ + traps->num_traps = 0; +} + +static traps_t * +traps_add (traps_t *traps, int top, int bot, const edge_t *e1, const edge_t *e2) +{ + trapezoid_t *t; + + if (traps->num_traps == traps->size) { + traps->size *= 2; + traps = g_realloc (traps, + sizeof (traps_t) + traps->size*sizeof (trapezoid_t)); + } + + t = &traps->traps[traps->num_traps++]; + t->top = top; + if (bot > e1->bottom) + bot = e1->bottom; + if (bot > e2->bottom) + bot = e2->bottom; + t->bottom = bot; + + t->left = e1; + t->right = e2; + + return traps; +} + +static void +traps_remove (traps_t *traps, int top, const edge_t *e1, const edge_t *e2) +{ + int n; + + for (n = 0; n < traps->num_traps; n++) { + trapezoid_t *t = &traps->traps[n]; + if (t->top == top && t->left == e1 && t->right == e2) + break; + } + if (n < traps->num_traps) { + g_memmove (&traps->traps[n], + &traps->traps[n+1], + (traps->num_traps-n+1) * sizeof (trapezoid_t)); + traps->num_traps--; + } +} + +static void +event_next (EventView *self) +{ + events_t *events; + event_t *e; + + events = self->current_events; + if (++events->current_event == events->num_events) { + return; + } else if (events->current_event >= events->num_events) { + traps_clear (events->prototraps); + traps_clear (events->traps); + events->current_event = 0; + + self->current_events = events->next; + if (self->current_events == NULL) + self->current_events = self->events_list; + events = self->current_events; + } + + e = &events->events[events->current_event]; + switch (e->type) { + case START_TRAP: + events->prototraps = traps_add (events->prototraps, + e->x, G_MAXINT, + edges_lookup (events->edges, e->e1), + edges_lookup (events->edges, e->e2)); + break; + case END_TRAP: + traps_remove (events->prototraps, + e->x, + edges_lookup (events->edges, e->e1), + edges_lookup (events->edges, e->e2)); + events->traps = traps_add (events->traps, + e->x, e->y, + edges_lookup (events->edges, e->e1), + edges_lookup (events->edges, e->e2)); + break; + } +} + +static gboolean +event_view_button_press (GtkWidget *w, GdkEventButton *ev) +{ + EventView *self = (EventView *) w; + + if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + if (ev->type == GDK_BUTTON_PRESS) { + event_next (self); + gtk_widget_queue_draw (w); + } + } + else + { + self->in_mag_drag = TRUE; + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } + + return FALSE; +} + +static gboolean +event_view_button_release (GtkWidget *w, GdkEventButton *ev) +{ + EventView *self = (EventView *) w; + + self->in_mag_drag = FALSE; + + return FALSE; +} + +static gboolean +event_view_motion (GtkWidget *w, GdkEventMotion *ev) +{ + EventView *self = (EventView *) w; + + if (self->in_mag_drag) { + self->mag_x += ev->x - self->mag_drag_x; + self->mag_y += ev->y - self->mag_drag_y; + + gtk_widget_queue_draw (&self->widget); + + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } else if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + self->px = ev->x; + self->py = ev->y; + + gtk_widget_queue_draw (&self->widget); + } + + return FALSE; +} + +static void +event_view_realize (GtkWidget *widget) +{ + GdkWindowAttr attributes; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_EXPOSURE_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, + GDK_WA_X | GDK_WA_Y | + GDK_WA_VISUAL | GDK_WA_COLORMAP); + gdk_window_set_user_data (widget->window, widget); + + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); +} + +static void +event_view_size_allocate (GtkWidget *w, GdkRectangle *r) +{ + EventView *self = (EventView *) w; + + GTK_WIDGET_CLASS (event_view_parent_class)->size_allocate (w, r); + + self->mag_x = w->allocation.width - self->mag_size - 10; + self->mag_y = w->allocation.height - self->mag_size - 10; +} + +static void +event_view_finalize (GObject *obj) +{ + G_OBJECT_CLASS (event_view_parent_class)->finalize (obj); +} + +static void +event_view_class_init (EventViewClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + object_class->finalize = event_view_finalize; + + widget_class->realize = event_view_realize; + widget_class->size_allocate = event_view_size_allocate; + widget_class->expose_event = event_view_expose; + widget_class->button_press_event = event_view_button_press; + widget_class->button_release_event = event_view_button_release; + widget_class->motion_notify_event = event_view_motion; +} + +static void +event_view_init (EventView *self) +{ + self->mag_zoom = 10; + self->mag_size = 200; +} + +static traps_t * +traps_new (void) +{ + traps_t *t; + + t = g_malloc (sizeof (traps_t) + 16 * sizeof (trapezoid_t)); + + t->size = 16; + t->num_traps = 0; + + return t; +} + +static edges_t * +_edges_add_edge (edges_t *edges, edge_t *e, box_t *extents) +{ + if (e->top < extents->p1.y) + extents->p1.y = e->top; + if (e->bottom > extents->p2.y) + extents->p2.y = e->bottom; + + _compute_intersection_point (&e->line, e->top, &e->p1); + _compute_intersection_point (&e->line, e->bottom, &e->p2); + + if (e->p1.x < extents->p1.x) + extents->p1.x = e->p1.x; + if (e->p2.x < extents->p1.x) + extents->p1.x = e->p2.x; + + if (e->p1.x > extents->p2.x) + extents->p2.x = e->p1.x; + if (e->p2.x > extents->p2.x) + extents->p2.x = e->p2.x; + + if (edges->num_edges == edges->size) { + edges->size *= 2; + edges = g_realloc (edges, + sizeof (edges_t) + edges->size * sizeof (edge_t)); + } + + g_hash_table_insert (edges->ht, + GUINT_TO_POINTER (e->id), + GUINT_TO_POINTER (edges->num_edges)); + edges->edges[edges->num_edges++] = *e; + + return edges; +} + +static void +_events_add_event (events_t *events, + int type, + int x, int y, + gulong e1, gulong e2) +{ + event_t *e; + + if (events->num_events == events->size_events) { + int newsize = 2 * events->size_events; + void *newevents; + + newevents = g_renew (event_t, events->events, newsize); + events->events = newevents; + events->size_events = newsize; + } + + e = &events->events[events->num_events++]; + e->type = type; + e->x = x; + e->y = y; + e->e1 = e1; + e->e2 = e2; +} + +static edges_t * +edges_new (void) +{ + edges_t *t; + + t = g_malloc (sizeof (edges_t) + 16 * sizeof (edge_t)); + t->ht = g_hash_table_new (NULL, NULL); + t->size = 16; + t->num_edges = 0; + + return t; +} + +static events_t * +events_new (void) +{ + events_t *events; + + events = g_malloc (sizeof (events_t)); + + events->next = NULL; + events->prev = NULL; + + events->events = g_new (event_t, 16); + events->size_events = 16; + events->num_events = 0; + events->current_event = 0; + + events->edges = edges_new (); + events->prototraps = traps_new (); + events->traps = traps_new (); + + events->extents.p1.x = G_MAXDOUBLE; + events->extents.p1.y = G_MAXDOUBLE; + events->extents.p2.x = -G_MAXDOUBLE; + events->extents.p2.y = -G_MAXDOUBLE; + + return events; +} + +static void +events_read (EventView *ev, const char *filename) +{ + FILE *file; + + file = fopen (filename, "r"); + if (file != NULL) { + char *line = NULL; + size_t len = 0; + events_t *events; + + events = ev->events_list = events_new (); + while (getline (&line, &len, file) != -1) { + line = g_strstrip (line); + if (*line == '\0') { + events->next = events_new (); + events->next->prev = events; + events = events->next; + } else if (g_str_has_prefix (line, "edge:")) { + edge_t edge; + + sscanf (line, "edge: %lu (%lf, %lf) (%lf, %lf) (%lf, %lf) %d", + &edge.id, + &edge.line.p1.x, + &edge.line.p1.y, + &edge.line.p2.x, + &edge.line.p2.y, + &edge.top, + &edge.bottom, + &edge.dir); + + events->edges = _edges_add_edge (events->edges, + &edge, + &events->extents); + } else if (g_str_has_prefix (line, "event:")) { + int type; + int x,y; + gulong e1, e2; + + sscanf (line, "event: %d (%d, %d) %lu %lu", + &type, &x, &y, + &e1, &e2); + + _events_add_event (events, type, x, y, e1, e2); + } else if (g_str_has_prefix (line, "begin trap:")) { + int top; + gulong e1, e2; + + sscanf (line, "begin trap: %lu %lu %u", &e1, &e2, &top); + + _events_add_event (events, START_TRAP, top, 0, e1, e2); + } else if (g_str_has_prefix (line, "end trap:")) { + int top, bottom; + gulong e1, e2; + + sscanf (line, "end trap: %lu %lu %d %d", + &e1, &e2, &top, &bottom); + + _events_add_event (events, END_TRAP, top, bottom, e1, e2); + } + } + + ev->current_events = ev->events_list; + + free (line); + fclose (file); + } +} + +static gboolean +timeout_advance (EventView *self) +{ + event_next (self); + gtk_widget_queue_draw (&self->widget); + return TRUE; +} + +int +main (int argc, char **argv) +{ + EventView *ev; + GtkWidget *window, *hbox; + + gtk_init (&argc, &argv); + + hbox = gtk_hbox_new (TRUE, 0); + + ev = g_object_new (event_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &ev->widget, TRUE, TRUE, 0); + gtk_widget_show (&ev->widget); + + events_read (ev, argv[1]); + g_timeout_add (750, (GSourceFunc) timeout_advance, ev); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 800, 800); + g_signal_connect (window, "delete-event", + G_CALLBACK (gtk_main_quit), NULL); + gtk_container_add (GTK_CONTAINER (window), hbox); + gtk_widget_show (hbox); + gtk_widget_show (window); + + gtk_main (); + return 0; +} diff --git a/util/show-traps.c b/util/show-traps.c new file mode 100644 index 00000000..9a99aba4 --- /dev/null +++ b/util/show-traps.c @@ -0,0 +1,1239 @@ +#define _GNU_SOURCE +#include <gtk/gtk.h> +#include <stdlib.h> +#include <stdio.h> +#include <gdk/gdkkeysyms.h> +#include <math.h> + +typedef struct _point { + gdouble x, y; +} point_t; +typedef struct _box { + point_t p1, p2; +} box_t; +typedef struct _line { + point_t p1, p2; +} line_t; +typedef struct _trapezoid { + gdouble top, bottom; + line_t left, right; +} trapezoid_t; +typedef struct _traps { + struct _traps *next, *prev; + box_t extents; + int num_traps; + int size; + trapezoid_t traps[0]; +} traps_t; + +typedef struct _edge { + line_t line; + gdouble top, bottom; + point_t p1, p2; + int dir; +} edge_t; +typedef struct _edges { + struct _edges *next, *prev; + box_t extents; + int num_edges; + int size; + edge_t edges[0]; +} edges_t; + +typedef struct _TrapView { + GtkWidget widget; + + struct _TrapView *group_head; + struct _TrapView *group_next; + struct _TrapView *group_prev; + + traps_t *traps_list; + traps_t *current_traps; + + edges_t *edges_list; + edges_t *current_edges; + + double px, py; + + gint mag_x, mag_y; + gint mag_size; + gdouble mag_zoom; + gboolean in_mag_drag; + gint mag_drag_x, mag_drag_y; +} TrapView; + +typedef struct _TrapViewClass { + GtkWidgetClass parent_class; +} TrapViewClass; + +G_DEFINE_TYPE (TrapView, trap_view, GTK_TYPE_WIDGET) + +static gdouble +_compute_intersection_x_for_y (const line_t *line, + gdouble y) +{ + gdouble dx = line->p2.x - line->p1.x; + gdouble dy = line->p2.y - line->p1.y; + gdouble x; + + if (y == line->p1.y) + return line->p1.x; + if (y == line->p2.y) + return line->p2.x; + + x = line->p1.x; + if (dy != 0) + x += (y - line->p1.y)*dx/dy; + return x; +} + +static void +_compute_intersection_point (const line_t *line, + gdouble y, + point_t *p) +{ + p->x = _compute_intersection_x_for_y (line, p->y = y); +} + +static void +trap_view_draw (TrapView *self, cairo_t *cr) +{ + traps_t *traps; + edges_t *edges; + gdouble sf_x, sf_y, sf; + gdouble mid, dim; + gdouble x0, x1, y0, y1; + double dash[2] = {8, 8}; + double dots[2] = {0., 1.}; + int n; + box_t extents; + point_t p; + + cairo_save (cr); + cairo_save (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + cairo_restore (cr); + + traps = self->current_traps; + if (traps == NULL) + return; + + edges = self->current_edges; + + extents = traps->extents; + if (edges != NULL) { + if (edges->extents.p1.x < extents.p1.x) + extents.p1.x = edges->extents.p1.x; + if (edges->extents.p1.y < extents.p1.y) + extents.p1.y = edges->extents.p1.y; + if (edges->extents.p2.x > extents.p2.x) + extents.p2.x = edges->extents.p2.x; + if (edges->extents.p2.y > extents.p2.y) + extents.p2.y = edges->extents.p2.y; + } + + mid = (extents.p2.x + extents.p1.x) / 2.; + dim = (extents.p2.x - extents.p1.x) / 2. * 1.25; + sf_x = self->widget.allocation.width / dim / 2; + + mid = (extents.p2.y + extents.p1.y) / 2.; + dim = (extents.p2.y - extents.p1.y) / 2. * 1.25; + sf_y = self->widget.allocation.height / dim / 2; + + sf = MIN (sf_x, sf_y); + + mid = (extents.p2.x + extents.p1.x) / 2.; + dim = sf_x / sf * (extents.p2.x - extents.p1.x) / 2. * 1.25; + x0 = mid - dim; + x1 = mid + dim; + mid = (extents.p2.y + extents.p1.y) / 2.; + dim = sf_y / sf * (extents.p2.y - extents.p1.y) / 2. * 1.25; + y0 = mid - dim; + y1 = mid + dim; + + cairo_save (cr); + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_set_source_rgba (cr, 0, 1, 0, .2); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_fill (cr); + } + cairo_restore (cr); + + if (edges == NULL) { + cairo_save (cr); + + /* top, bottom */ + cairo_save (cr); { + cairo_matrix_t m; + cairo_matrix_init_scale (&m, sf, sf); + cairo_matrix_translate (&m, -x0, -y0); + + cairo_set_line_width (cr, 1.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + } + } cairo_restore (cr); + + /* left extents */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->left.p1.x, t->left.p1.y); + cairo_line_to (cr, t->left.p2.x, t->left.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_line_width (cr, 1.); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + /* left line */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_stroke (cr); + } cairo_restore (cr); + + /* right extents */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->right.p1.x, t->right.p1.y); + cairo_line_to (cr, t->right.p2.x, t->right.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_line_width (cr, 1.); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + /* right line */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_stroke (cr); + } cairo_restore (cr); + } + + /* end-points */ + cairo_save (cr); { + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } cairo_restore (cr); + + cairo_restore (cr); + } else { + cairo_save (cr); + + for (n = 0; n < edges->num_edges; n++) { + const edge_t *e = &edges->edges[n]; + + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_line_to (cr, e->p2.x, e->p2.y); + } cairo_restore (cr); + + if (e->dir < 0) { + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_dash (cr, dash, 2, dash[0]); + } else { + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_dash (cr, dash, 2, 0.); + } + + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + + cairo_save (cr); { + cairo_scale (cr, sf, sf); + cairo_translate (cr, -x0, -y0); + cairo_move_to (cr, e->p1.x, e->p1.y); + cairo_close_path (cr); + cairo_move_to (cr, e->p2.x, e->p2.y); + cairo_close_path (cr); + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } + + cairo_restore (cr); + } + + /* draw a zoom view of the area around the mouse */ + { + cairo_save (cr); + double zoom = self->mag_zoom; + int size = self->mag_size; + + /* bottom right */ + cairo_rectangle (cr, self->mag_x, self->mag_y, size, size); + cairo_stroke_preserve (cr); + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_fill_preserve (cr); + cairo_clip (cr); + + /* compute roi in extents */ + cairo_translate (cr, self->mag_x + size/2, self->mag_y + size/2); + + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->top, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + cairo_close_path (cr); + cairo_set_source_rgba (cr, 0, 1, 0, .2); + cairo_fill (cr); + } + cairo_restore (cr); + + cairo_save (cr); { + cairo_matrix_t m; + cairo_matrix_init_scale (&m, zoom, zoom); + cairo_matrix_translate (&m, -(self->px / sf + x0), -(self->py /sf + y0)); + + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_line_width (cr, 1.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + + _compute_intersection_point (&t->left, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->top, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_move_to (cr, floor (p.x), floor (p.y) + .5); + cairo_set_dash (cr, dash, 2, fmod (floor (p.x), dash[0] + dash[1])); + + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_matrix_transform_point (&m, &p.x, &p.y); + cairo_line_to (cr, ceil (p.x), floor (p.y) + .5); + cairo_stroke (cr); + } + } cairo_restore (cr); + + cairo_save (cr); { /* left extents */ + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->left.p1.x, t->left.p1.y); + cairo_line_to (cr, t->left.p2.x, t->left.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_set_line_width (cr, .5); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + cairo_save (cr); { /* right extents */ + cairo_save (cr); { + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + cairo_move_to (cr, t->right.p1.x, t->right.p1.y); + cairo_line_to (cr, t->right.p2.x, t->right.p2.y); + } + } cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_set_line_width (cr, .5); + cairo_set_dash (cr, dash, 2, 0.); + cairo_stroke (cr); + } cairo_restore (cr); + + cairo_save (cr); { /* left lines */ + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 1, 0, 0); + cairo_stroke (cr); + } cairo_restore (cr); + cairo_save (cr); { /* right lines */ + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_line_to (cr, p.x, p.y); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 1); + cairo_stroke (cr); + } cairo_restore (cr); + + /* end-points */ + cairo_save (cr); { + double dots[2] = {0., 1.}; + + cairo_save (cr); + cairo_scale (cr, zoom, zoom); + cairo_translate (cr, -(self->px / sf + x0), -(self->py /sf + y0)); + for (n = 0; n < traps->num_traps; n++) { + const trapezoid_t *t = &traps->traps[n]; + _compute_intersection_point (&t->left, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->left, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->top, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + _compute_intersection_point (&t->right, t->bottom, &p); + cairo_move_to (cr, p.x, p.y); + cairo_close_path (cr); + } + cairo_restore (cr); + cairo_set_source_rgb (cr, 0, 0, 0); + cairo_set_dash (cr, dots, 2, 0.); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_width (cr, 4.); + cairo_stroke (cr); + } cairo_restore (cr); + + /* grid */ + cairo_save (cr); { + int i; + + cairo_translate (cr, + -zoom*fmod (self->px/sf + x0, 1.), + -zoom*fmod (self->py/sf + y0, 1.)); + for (i = -size/2/zoom - 1; i <= size/2/zoom + 1; i++) { + cairo_move_to (cr, zoom*i, -size/2); + cairo_line_to (cr, zoom*i, size/2 + zoom); + cairo_move_to (cr, -size/2, zoom*i); + cairo_line_to (cr, size/2 + zoom, zoom*i); + } + cairo_set_source_rgba (cr, .7, .7, .7, .5); + cairo_set_line_width (cr, 1.); + cairo_stroke (cr); + } cairo_restore (cr); + } + + cairo_restore (cr); +} + +static gdouble +trapezoid_area (const trapezoid_t *t) +{ + gdouble inner_left, inner_right; + gdouble outer_left, outer_right; + gdouble height; + gdouble area; + + /* split into 3 sections: a rectangle with a pair of triangular bookends */ + inner_left = _compute_intersection_x_for_y (&t->left, t->top); + outer_left = _compute_intersection_x_for_y (&t->left, t->bottom); + if (outer_left > inner_left) { + gdouble t = outer_left; + outer_left = inner_left; + inner_left = t; + } + + inner_right = _compute_intersection_x_for_y (&t->right, t->top); + outer_right = _compute_intersection_x_for_y (&t->right, t->bottom); + if (outer_right > inner_right) { + gdouble t = outer_right; + outer_right = inner_right; + inner_right = t; + } + + if (outer_left > outer_right) { /* reverse */ + gdouble t; + + t = outer_left; + outer_left = inner_right; + inner_right = t; + + t = inner_left; + inner_left = outer_right; + outer_right = t; + } + + height = t->bottom - t->top; + area = (inner_left - outer_left) * height / 2; + area += (outer_right - inner_right) * height / 2; + area += (inner_right - inner_left) * height; + + return area; +} + +static gdouble +traps_compute_total_area (const traps_t *traps) +{ + int n; + gdouble area = 0.; + for (n = 0; n < traps->num_traps; n++) + area += trapezoid_area (&traps->traps[n]); + return area; +} + +static void +trap_view_draw_labels (TrapView *self, cairo_t *cr) +{ + PangoLayout *layout; + gint width, height; + gdouble total_area; + gchar *str; + traps_t *traps; + + traps = self->current_traps; + if (traps == NULL) + return; + + /* convert total area from fixed-point (assuming 24.8) */ + total_area = traps_compute_total_area (traps) / (256. * 256.); + str = g_strdup_printf ("Number of trapezoids:\t%d\n" + "Total area of trapezoids:\t%.2f", + traps->num_traps, + total_area); + layout = gtk_widget_create_pango_layout (&self->widget, str); + g_free (str); + + pango_layout_get_pixel_size (layout, &width, &height); + + cairo_move_to (cr, 10, 10 + height); + pango_cairo_show_layout (cr, layout); + g_object_unref (layout); +} + +static gboolean +trap_view_expose (GtkWidget *w, GdkEventExpose *ev) +{ + TrapView *self = (TrapView *) w; + cairo_t *cr; + + cr = gdk_cairo_create (w->window); + gdk_cairo_region (cr, ev->region); + cairo_clip (cr); + + trap_view_draw (self, cr); + trap_view_draw_labels (self, cr); + + cairo_destroy (cr); + return FALSE; +} + +static void +trap_view_advance (TrapView *self) +{ + if (self->current_traps && self->current_traps->prev) + self->current_traps = self->current_traps->prev; + if (self->current_edges && self->current_edges->prev) + self->current_edges = self->current_edges->prev; + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_back (TrapView *self) +{ + if (self->current_traps && self->current_traps->next) + self->current_traps = self->current_traps->next; + if (self->current_edges && self->current_edges->next) + self->current_edges = self->current_edges->next; + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_group_foreach (TrapView *group, GFunc func, gpointer data) +{ + while (group) { + func (group, data); + group = group->group_next; + } +} + +static gboolean +trap_view_key_press (GtkWidget *w, GdkEventKey *ev) +{ + TrapView *self = (TrapView *) w; + + switch (ev->keyval) { + case GDK_BackSpace: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_back, + NULL); + break; + + case GDK_space: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + break; + + case GDK_Return: + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + break; + + case GDK_Escape: + case GDK_Q: + gtk_main_quit (); + break; + } + + return FALSE; +} + +static gboolean +trap_view_button_press (GtkWidget *w, GdkEventButton *ev) +{ + TrapView *self = (TrapView *) w; + + if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + if (ev->type == GDK_BUTTON_PRESS) { + if (self->current_traps == NULL) + return FALSE; + + if (ev->button == 1) { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_advance, + NULL); + } else if (ev->button == 3) { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_back, + NULL); + } + } + } + else + { + self->in_mag_drag = TRUE; + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } + + return FALSE; +} + +static gboolean +trap_view_button_release (GtkWidget *w, GdkEventButton *ev) +{ + TrapView *self = (TrapView *) w; + + self->in_mag_drag = FALSE; + + return FALSE; +} + +static void +trap_view_update_mouse (TrapView *self, GdkEventMotion *ev) +{ + self->px = ev->x; + self->py = ev->y; + + gtk_widget_queue_draw (&self->widget); +} + +static void +trap_view_update_magnifier (TrapView *self, gint *xy) +{ + self->mag_x = xy[0]; + self->mag_y = xy[1]; + + gtk_widget_queue_draw (&self->widget); +} + +static gboolean +trap_view_motion (GtkWidget *w, GdkEventMotion *ev) +{ + TrapView *self = (TrapView *) w; + + if (self->in_mag_drag) { + int xy[2]; + + xy[0] = self->mag_x + ev->x - self->mag_drag_x; + xy[1] = self->mag_y + ev->y - self->mag_drag_y; + + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_update_magnifier, + xy); + + self->mag_drag_x = ev->x; + self->mag_drag_y = ev->y; + } else if (ev->x < self->mag_x || + ev->y < self->mag_y || + ev->x > self->mag_x + self->mag_size || + ev->y > self->mag_y + self->mag_size) + { + trap_view_group_foreach (self->group_head, + (GFunc) trap_view_update_mouse, + ev); + } + + return FALSE; +} + +static void +trap_view_realize (GtkWidget *widget) +{ + GdkWindowAttr attributes; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gtk_widget_get_visual (widget); + attributes.colormap = gtk_widget_get_colormap (widget); + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_MOTION_MASK | + GDK_EXPOSURE_MASK; + + widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, + GDK_WA_X | GDK_WA_Y | + GDK_WA_VISUAL | GDK_WA_COLORMAP); + gdk_window_set_user_data (widget->window, widget); + + widget->style = gtk_style_attach (widget->style, widget->window); + gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); +} + +static void +trap_view_size_allocate (GtkWidget *w, GdkRectangle *r) +{ + TrapView *self = (TrapView *) w; + + GTK_WIDGET_CLASS (trap_view_parent_class)->size_allocate (w, r); + + self->mag_x = w->allocation.width - self->mag_size - 10; + self->mag_y = w->allocation.height - self->mag_size - 10; +} + +static void +trap_view_finalize (GObject *obj) +{ + G_OBJECT_CLASS (trap_view_parent_class)->finalize (obj); +} + +static void +trap_view_class_init (TrapViewClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + object_class->finalize = trap_view_finalize; + + widget_class->realize = trap_view_realize; + widget_class->size_allocate = trap_view_size_allocate; + widget_class->expose_event = trap_view_expose; + widget_class->key_press_event = trap_view_key_press; + widget_class->button_press_event = trap_view_button_press; + widget_class->button_release_event = trap_view_button_release; + widget_class->motion_notify_event = trap_view_motion; +} + +static void +trap_view_init (TrapView *self) +{ + self->mag_zoom = 10; + self->mag_size = 200; + + GTK_WIDGET_SET_FLAGS (self, GTK_CAN_FOCUS); +} + +static traps_t * +_traps_add_trapezoid (TrapView *tv, traps_t *traps, const trapezoid_t *trap) +{ + if (trap->top < traps->extents.p1.y) + traps->extents.p1.y = trap->top; + if (trap->bottom > traps->extents.p2.y) + traps->extents.p2.y = trap->bottom; + + if (trap->left.p1.x < traps->extents.p1.x) + traps->extents.p1.x = trap->left.p1.x; + if (trap->left.p2.x < traps->extents.p1.x) + traps->extents.p1.x = trap->left.p2.x; + + if (trap->right.p1.x > traps->extents.p2.x) + traps->extents.p2.x = trap->right.p1.x; + if (trap->right.p2.x > traps->extents.p2.x) + traps->extents.p2.x = trap->right.p2.x; + + if (traps->num_traps == traps->size) { + int newsize = 2 * traps->size; + void *newtraps; + + newtraps = g_realloc (traps, + sizeof (traps_t) + newsize * sizeof (trapezoid_t)); + if (newtraps == NULL) + return traps; + + if (tv->current_traps == traps) + tv->current_traps = newtraps; + + traps = newtraps; + traps->size = newsize; + + if (traps->next != NULL) + traps->next->prev = newtraps; + if (traps->prev != NULL) + traps->prev->next = newtraps; + else + tv->traps_list = newtraps; + } + + traps->traps[traps->num_traps++] = *trap; + + return traps; +} + +static traps_t * +traps_new (TrapView *tv) +{ + traps_t *t; + + t = g_malloc (sizeof (traps_t) + 16 * sizeof (trapezoid_t)); + t->prev = NULL; + t->next = tv->traps_list; + if (tv->traps_list) + tv->traps_list->prev = t; + tv->traps_list = t; + + if (tv->current_traps == NULL) + tv->current_traps = t; + + t->size = 16; + t->num_traps = 0; + t->extents.p1.x = G_MAXDOUBLE; + t->extents.p1.y = G_MAXDOUBLE; + t->extents.p2.x = -G_MAXDOUBLE; + t->extents.p2.y = -G_MAXDOUBLE; + + return t; +} + +static edges_t * +_edges_add_edge (TrapView *tv, edges_t *edges, edge_t *e) +{ + if (e->top < edges->extents.p1.y) + edges->extents.p1.y = e->top; + if (e->bottom > edges->extents.p2.y) + edges->extents.p2.y = e->bottom; + + _compute_intersection_point (&e->line, e->top, &e->p1); + _compute_intersection_point (&e->line, e->bottom, &e->p2); + + if (e->p1.x < edges->extents.p1.x) + edges->extents.p1.x = e->p1.x; + if (e->p2.x < edges->extents.p1.x) + edges->extents.p1.x = e->p2.x; + + if (e->p1.x > edges->extents.p2.x) + edges->extents.p2.x = e->p1.x; + if (e->p2.x > edges->extents.p2.x) + edges->extents.p2.x = e->p2.x; + + if (edges->num_edges == edges->size) { + int newsize = 2 * edges->size; + void *newedges; + + newedges = g_realloc (edges, + sizeof (edges_t) + newsize * sizeof (edge_t)); + if (newedges == NULL) + return edges; + + if (tv->current_edges == edges) + tv->current_edges = newedges; + + edges = newedges; + edges->size = newsize; + + if (edges->next != NULL) + edges->next->prev = newedges; + if (edges->prev != NULL) + edges->prev->next = newedges; + else + tv->edges_list = newedges; + } + + edges->edges[edges->num_edges++] = *e; + + return edges; +} + +static edges_t * +edges_new (TrapView *tv) +{ + edges_t *t; + + t = g_malloc (sizeof (edges_t) + 16 * sizeof (edge_t)); + t->prev = NULL; + t->next = tv->edges_list; + if (tv->edges_list) + tv->edges_list->prev = t; + tv->edges_list = t; + + if (tv->current_edges == NULL) + tv->current_edges = t; + + t->size = 16; + t->num_edges = 0; + t->extents.p1.x = G_MAXDOUBLE; + t->extents.p1.y = G_MAXDOUBLE; + t->extents.p2.x = -G_MAXDOUBLE; + t->extents.p2.y = -G_MAXDOUBLE; + + return t; +} + +int +main (int argc, char **argv) +{ + TrapView *tv, *group_head = NULL, *group_prev = NULL; + GtkWidget *window, *hbox; + FILE *file; + + gtk_init (&argc, &argv); + + hbox = gtk_hbox_new (TRUE, 0); + + file = fopen (argv[1], "r"); + if (file != NULL) { + char *line = NULL; + size_t len = 0; + traps_t *traps; + + tv = g_object_new (trap_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &tv->widget, TRUE, TRUE, 0); + gtk_widget_show (&tv->widget); + + tv->group_prev = group_prev; + tv->group_next = NULL; + if (group_prev) + group_prev->group_next = tv; + group_prev = tv; + if (group_head == NULL) + group_head = tv; + tv->group_head = group_head; + + traps = traps_new (tv); + while (getline (&line, &len, file) != -1) { + trapezoid_t t; + + if (sscanf (line, + "%lf %lf L:(%lf, %lf), (%lf, %lf) R:(%lf, %lf), (%lf, %lf)", + &t.top, &t.bottom, + &t.left.p1.x, &t.left.p1.y, + &t.left.p2.x, &t.left.p2.y, + &t.right.p1.x, &t.right.p1.y, + &t.right.p2.x, &t.right.p2.y) == 10) { + traps = _traps_add_trapezoid (tv, traps, &t); + } else { + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + traps = traps_new (tv); + } + } + } + free (line); + fclose (file); + + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + } + } + + file = fopen (argv[2], "r"); + if (file != NULL) { + char *line = NULL; + size_t len = 0; + traps_t *traps; + + tv = g_object_new (trap_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &tv->widget, TRUE, TRUE, 0); + gtk_widget_show (&tv->widget); + + tv->group_prev = group_prev; + tv->group_next = NULL; + if (group_prev) + group_prev->group_next = tv; + group_prev = tv; + if (group_head == NULL) + group_head = tv; + tv->group_head = group_head; + + traps = traps_new (tv); + while (getline (&line, &len, file) != -1) { + trapezoid_t t; + + if (sscanf (line, + "%lf %lf L:(%lf, %lf), (%lf, %lf) R:(%lf, %lf), (%lf, %lf)", + &t.top, &t.bottom, + &t.left.p1.x, &t.left.p1.y, + &t.left.p2.x, &t.left.p2.y, + &t.right.p1.x, &t.right.p1.y, + &t.right.p2.x, &t.right.p2.y) == 10) { + traps = _traps_add_trapezoid (tv, traps, &t); + } else { + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + traps = traps_new (tv); + } + } + } + free (line); + fclose (file); + + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + } + } + +#if 1 + if (argc >=4) { + file = fopen (argv[3], "r"); + if (file != NULL) { + char *line = NULL; + size_t len = 0; + edges_t *edges; + + edges = edges_new (tv); + while (getline (&line, &len, file) != -1) { + edge_t e; + + if (sscanf (line, + "(%lf, %lf), (%lf, %lf) %lf %lf %d", + &e.line.p1.x, &e.line.p1.y, + &e.line.p2.x, &e.line.p2.y, + &e.top, &e.bottom, + &e.dir) == 7) { + edges = _edges_add_edge (tv, edges, &e); + } else { + if (edges->num_edges) { + g_print ("read %d edges\n", edges->num_edges); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + edges->extents.p1.x, edges->extents.p1.y, + edges->extents.p2.x, edges->extents.p2.y); + edges = edges_new (tv); + } + } + } + free (line); + fclose (file); + + if (edges->num_edges) { + g_print ("read %d edges\n", edges->num_edges); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + edges->extents.p1.x, edges->extents.p1.y, + edges->extents.p2.x, edges->extents.p2.y); + } + } + } +#else + if (argc >= 4) { + file = fopen (argv[3], "r"); + if (file != NULL) { + char *line = NULL; + size_t len = 0; + traps_t *traps; + + tv = g_object_new (trap_view_get_type (), NULL); + gtk_box_pack_start (GTK_BOX (hbox), &tv->widget, TRUE, TRUE, 0); + gtk_widget_show (&tv->widget); + + tv->group_prev = group_prev; + tv->group_next = NULL; + if (group_prev) + group_prev->group_next = tv; + group_prev = tv; + if (group_head == NULL) + group_head = tv; + tv->group_head = group_head; + + traps = traps_new (tv); + while (getline (&line, &len, file) != -1) { + trapezoid_t t; + + if (sscanf (line, + "%lf %lf L:(%lf, %lf), (%lf, %lf) R:(%lf, %lf), (%lf, %lf)", + &t.top, &t.bottom, + &t.left.p1.x, &t.left.p1.y, + &t.left.p2.x, &t.left.p2.y, + &t.right.p1.x, &t.right.p1.y, + &t.right.p2.x, &t.right.p2.y) == 10) { + traps = _traps_add_trapezoid (tv, traps, &t); + } else { + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + traps = traps_new (tv); + } + } + } + free (line); + fclose (file); + + if (traps->num_traps) { + g_print ("read %d trapezoids\n", traps->num_traps); + g_print ("extents=(%lg, %lg), (%lg, %lg)\n", + traps->extents.p1.x, traps->extents.p1.y, + traps->extents.p2.x, traps->extents.p2.y); + } + } + } +#endif + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (window, "delete-event", + G_CALLBACK (gtk_main_quit), NULL); + gtk_widget_set_size_request (window, 512, 512); + gtk_container_add (GTK_CONTAINER (window), hbox); + gtk_widget_show (hbox); + gtk_widget_show (window); + + gtk_main (); + return 0; +} |