diff options
author | Matthias Kramm <kramm@quiss.org> | 2009-05-02 23:51:23 +0200 |
---|---|---|
committer | Matthias Kramm <kramm@quiss.org> | 2009-05-02 23:51:23 +0200 |
commit | ae7c92fe5721f97e786a8bbe9153eadbf292460d (patch) | |
tree | 84a41666c770eda29d9aef3e2efa24e3410ba91c /lib/gfxpoly | |
parent | bf04757cd94e94c1f67fa3d2a4e3e59fa5bce0c0 (diff) |
polygon intersector: performance improvements and bugfixes
Diffstat (limited to 'lib/gfxpoly')
-rw-r--r-- | lib/gfxpoly/Makefile | 2 | ||||
-rw-r--r-- | lib/gfxpoly/active.c | 39 | ||||
-rw-r--r-- | lib/gfxpoly/active.h | 4 | ||||
-rw-r--r-- | lib/gfxpoly/convert.c | 6 | ||||
-rw-r--r-- | lib/gfxpoly/convert.h | 2 | ||||
-rw-r--r-- | lib/gfxpoly/poly.c | 307 | ||||
-rw-r--r-- | lib/gfxpoly/poly.h | 26 | ||||
-rw-r--r-- | lib/gfxpoly/renderpoly.c | 2 | ||||
-rw-r--r-- | lib/gfxpoly/test.c | 272 |
9 files changed, 545 insertions, 115 deletions
diff --git a/lib/gfxpoly/Makefile b/lib/gfxpoly/Makefile index 35e2039f..068901aa 100644 --- a/lib/gfxpoly/Makefile +++ b/lib/gfxpoly/Makefile @@ -31,7 +31,7 @@ renderpoly.o: renderpoly.c wind.h poly.h renderpoly.h xrow.o: xrow.c xrow.h ../q.h ../mem.h $(CC) -c xrow.c -o xrow.o -SWF = ../librfxswf.a +SWF = ../librfxswf.a ../libpdf.a ../libgfx.a -lstdc++ -lfontconfig test: ../libbase.a ../libgfx.a test.c $(OBJS) poly.h convert.h $(CC) test.c $(OBJS) $(SWF) ../libbase.a ../libgfx.a -o test -lm -lz -ljpeg -lfreetype diff --git a/lib/gfxpoly/active.c b/lib/gfxpoly/active.c index 51e07969..b0ef07ca 100644 --- a/lib/gfxpoly/active.c +++ b/lib/gfxpoly/active.c @@ -12,27 +12,44 @@ void actlist_destroy(actlist_t*a) free(a); } -void actlist_verify_and_dump(actlist_t*a, int32_t y) +void actlist_dump(actlist_t*a, int32_t y) { segment_t*s = a->list; - assert(!s || !s->left); double lastx; + char bad = 0; while(s) { if(y) { double x = ((double)s->delta.x*(y-s->a.y)/s->delta.y)+s->a.x; if(s!=a->list) { - if(lastx>x) fprintf(stderr, "?%f<->%f? ", lastx, x); + if(lastx>x) + fprintf(stderr, "?%f<->%f? ", lastx, x); } lastx = x; } - assert(!s->left || s->left->right == s); - assert(!s->right || s->right->left == s); fprintf(stderr, "[%d]", s->nr); s = s->right; if(s) fprintf(stderr, " "); else fprintf(stderr, "\n"); } } +void actlist_verify(actlist_t*a, int32_t y) +{ + segment_t*s = a->list; + assert(!s || !s->left); + double lastx; + while(s) { + if(y) { + double x = ((double)s->delta.x*(y-s->a.y)/s->delta.y)+s->a.x; + if(s!=a->list) { + assert(lastx<=x); + } + lastx = x; + } + assert(!s->left || s->left->right == s); + assert(!s->right || s->right->left == s); + s = s->right; + } +} segment_t* actlist_find(actlist_t*a, point_t p1, point_t p2) { @@ -108,6 +125,18 @@ segment_t* actlist_leftmost(actlist_t*a) return a->list; } +segment_t* actlist_rightmost(actlist_t*a) +{ + /* this is only used in checks, so it doesn't matter that it's slow */ + segment_t*s = a->list; + segment_t*last = 0; + while(s) { + last = s; + s = s->right; + } + return last; +} + segment_t* actlist_left(actlist_t*a, segment_t*s) { return s->left; diff --git a/lib/gfxpoly/active.h b/lib/gfxpoly/active.h index 791e6599..f567bea9 100644 --- a/lib/gfxpoly/active.h +++ b/lib/gfxpoly/active.h @@ -14,13 +14,15 @@ typedef struct _actlist actlist_t* actlist_new(); void actlist_destroy(actlist_t*a); int actlist_size(actlist_t*a); -void actlist_verify_and_dump(actlist_t*a, int32_t y); +void actlist_verify(actlist_t*a, int32_t y); +void actlist_dump(actlist_t*a, int32_t y); segment_t* actlist_find(actlist_t*a, point_t p1, point_t p2); // finds segment immediately to the left of p1 (breaking ties w/ p2) void actlist_insert(actlist_t*a, point_t p, segment_t*s); void actlist_delete(actlist_t*a, segment_t*s); void actlist_swap(actlist_t*a, segment_t*s1, segment_t*s2); segment_t* actlist_left(actlist_t*a, segment_t*s); segment_t* actlist_leftmost(actlist_t*a); +segment_t* actlist_rightmost(actlist_t*a); segment_t* actlist_right(actlist_t*a, segment_t*s); #endif diff --git a/lib/gfxpoly/convert.c b/lib/gfxpoly/convert.c index 5615612c..f404dfc2 100644 --- a/lib/gfxpoly/convert.c +++ b/lib/gfxpoly/convert.c @@ -3,16 +3,16 @@ #include <assert.h> #include <string.h> #include "../gfxdevice.h" +#include "../mem.h" #include "poly.h" static edge_t*edge_new(int x1, int y1, int x2, int y2) { - edge_t*s = malloc(sizeof(edge_t)); + edge_t*s = rfx_calloc(sizeof(edge_t)); s->a.x = x1; s->a.y = y1; s->b.x = x2; s->b.y = y2; - s->next = 0; return s; } @@ -29,7 +29,7 @@ static inline void gfxpoly_add_edge(gfxpoly_t*poly, double _x1, double _y1, doub } } -gfxpoly_t* gfxpoly_fillToPoly(gfxline_t*line, double gridsize) +gfxpoly_t* gfxpoly_from_gfxline(gfxline_t*line, double gridsize) { gfxpoly_t*p = gfxpoly_new(gridsize); diff --git a/lib/gfxpoly/convert.h b/lib/gfxpoly/convert.h index d1d5344a..93bf980f 100644 --- a/lib/gfxpoly/convert.h +++ b/lib/gfxpoly/convert.h @@ -3,7 +3,7 @@ #include "poly.h" -gfxpoly_t* gfxpoly_fillToPoly(gfxline_t*line, double gridsize); +gfxpoly_t* gfxpoly_from_gfxline(gfxline_t*line, double gridsize); gfxpoly_t* gfxpoly_from_file(const char*filename, double gridsize); #endif //__poly_convert_h__ diff --git a/lib/gfxpoly/poly.c b/lib/gfxpoly/poly.c index 97289892..bdbddf32 100644 --- a/lib/gfxpoly/poly.c +++ b/lib/gfxpoly/poly.c @@ -10,9 +10,15 @@ #include "xrow.h" #include "wind.h" -//#define DEBUG -//#undef assert -//#define assert(x) +#define DEBUG +#define CHECKS + +#ifndef CHECKS +#ifdef assert +#undef assert +#endif +#define assert(x) +#endif char point_equals(const void*o1, const void*o2) { @@ -55,7 +61,7 @@ typedef struct _status { edge_t*output; xrow_t*xrow; windrule_t*windrule; -#ifdef DEBUG +#ifdef CHECKS dict_t*seen_crossings; //list of crossing we saw so far dict_t*intersecting_segs; //list of segments intersecting in this scanline dict_t*segs_with_point; //lists of segments that received a point in this scanline @@ -84,9 +90,13 @@ int compare_events(const void*_a,const void*_b) event_t* b = (event_t*)_b; int d = b->p.y - a->p.y; if(d) return d; - /* we should schedule start events after end/intersect. - The order of end/intersect doesn't actually matter, however, - so this might be doing too much */ + /* we need to schedule end before intersect (so that a segment about + to end has a chance to tear up a few other segs first) and start + events after intersect (so that start segments don't position themselves + between two segments about to intersect (not a problem as such, but makes + things slower)). Horizontal lines come last, because the only purpose + they have is to create snapping coordinates for the segments (still) + existing in this scanline */ d = b->type - a->type; if(d) return d; d = b->p.x - a->p.x; @@ -109,6 +119,15 @@ void gfxpoly_destroy(gfxpoly_t*poly) } free(poly); } +int gfxpoly_size(gfxpoly_t*poly) +{ + edge_t* s = poly->edges; + int t=0; + while(s) { + s = s->next;t++; + } + return t; +} char gfxpoly_check(gfxpoly_t*poly) { edge_t* s = poly->edges; @@ -177,6 +196,13 @@ void event_dump(event_t*e) static inline max32(int32_t v1, int32_t v2) {return v1>v2?v1:v2;} static inline min32(int32_t v1, int32_t v2) {return v1<v2?v1:v2;} +void segment_dump(segment_t*s) +{ + fprintf(stderr, "(%d,%d)->(%d,%d) ", s->a.x, s->a.y, s->b.x, s->b.y); + fprintf(stderr, " dx:%d dy:%d k:%f dx/dy=%f\n", s->delta.x, s->delta.y, s->k, + (double)s->delta.x / s->delta.y); +} + void segment_init(segment_t*s, int x1, int y1, int x2, int y2, windstate_t windstate, int polygon_nr) { if(y1<y2) { @@ -204,6 +230,9 @@ void segment_init(segment_t*s, int x1, int y1, int x2, int y2, windstate_t winds s->left = s->right = 0; s->delta.x = x2-x1; s->delta.y = y2-y1; + s->minx = min32(x1,x2); + s->maxx = max32(x1,x2); + s->pos = s->a; s->polygon_nr = polygon_nr; #define XDEBUG @@ -253,6 +282,8 @@ void gfxpoly_enqueue(edge_t*list, heap_t*queue, windstate_t initial, int polygon continue; } segment_t*s = segment_new(l->a.x, l->a.y, l->b.x, l->b.y, initial, polygon_nr); + if(l->tmp) + s->nr = l->tmp; #ifdef DEBUG fprintf(stderr, "[%d] (%d,%d) -> (%d,%d) %s\n", s->nr, s->a.x, s->a.y, s->b.x, s->b.y, @@ -284,24 +315,31 @@ void schedule_crossing(status_t*status, segment_t*s1, segment_t*s2) /* the code that's required (and the checks you can perform) before it can be said with 100% certainty that we indeed have a valid crossing amazes me every time. -mk */ - assert(s1!=s2); - /* we probably could precompute these */ - int32_t minx1 = min32(s1->a.x,s1->b.x); +#ifdef CHECKS + assert(s1!=s2); + assert(s1->right == s2); + assert(s2->left == s1); int32_t miny1 = min32(s1->a.y,s1->b.y); - int32_t maxx1 = max32(s1->a.x,s1->b.x); int32_t maxy1 = max32(s1->a.y,s1->b.y); - int32_t minx2 = min32(s2->a.x,s2->b.x); int32_t miny2 = min32(s2->a.y,s2->b.y); - int32_t maxx2 = max32(s2->a.x,s2->b.x); int32_t maxy2 = max32(s2->a.y,s2->b.y); - + int32_t minx1 = min32(s1->a.x,s1->b.x); + int32_t minx2 = min32(s2->a.x,s2->b.x); + int32_t maxx1 = max32(s1->a.x,s1->b.x); + int32_t maxx2 = max32(s2->a.x,s2->b.x); + /* check that precomputation is sane */ + assert(minx1 == s1->minx && minx2 == s2->minx); + assert(maxx1 == s1->maxx && maxx2 == s2->maxx); /* both segments are active, so this can't happen */ assert(!(maxy1 <= miny2 || maxy2 <= miny1)); + /* we know that right now, s2 is to the right of s1, so there's + no way the complete bounding box of s1 is to the right of s1 */ + assert(!(s1->minx > s2->maxx)); + assert(s1->minx != s2->maxx || (!s1->delta.x && !s2->delta.x)); +#endif - /* TODO: optimize this. remove y, precompute the two x values */ - if(maxx1 <= minx2 || maxx2 <= minx1 || - maxy1 <= miny2 || maxy2 <= miny1) { + if(s1->maxx <= s2->minx) { /* bounding boxes don't intersect */ return; } @@ -309,9 +347,7 @@ void schedule_crossing(status_t*status, segment_t*s1, segment_t*s2) if(dict_contains(&s1->scheduled_crossings, s2)) { /* FIXME: this whole segment hashing thing is really slow */ //fprintf(stderr, "Encountered crossing between [%d] and [%d] twice\n", s1->nr, s2->nr); - - // we already know about this one - return; + return; // we already know about this one } double adx = s1->delta.x; @@ -388,12 +424,17 @@ void schedule_crossing(status_t*status, segment_t*s1, segment_t*s2) p.y = (int32_t)ceil((+lb*ady -la*bdy) / det); assert(p.y >= status->y); -#ifdef DEBUG +#ifdef CHECKS + assert(p.x >= s1->minx && p.x <= s1->maxx); + assert(p.x >= s2->minx && p.x <= s2->maxx); + point_t pair; pair.x = s1->nr; pair.y = s2->nr; assert(!dict_contains(status->seen_crossings, &pair)); dict_put(status->seen_crossings, &pair, 0); +#endif +#ifdef DEBUG fprintf(stderr, "schedule crossing between [%d] and [%d] at (%d,%d)\n", s1->nr, s2->nr, p.x, p.y); #endif @@ -416,7 +457,7 @@ void exchange_two(status_t*status, event_t*e) //exchange two segments in list segment_t*s1 = e->s1; segment_t*s2 = e->s2; -#ifdef DEBUG +#ifdef CHECKS if(!dict_contains(status->intersecting_segs, s1)) dict_put(status->intersecting_segs, s1, 0); if(!dict_contains(status->intersecting_segs, s2)) @@ -455,19 +496,21 @@ static void insert_point_into_segment(status_t*status, segment_t*s, point_t p) { assert(s->pos.x != p.x || s->pos.y != p.y); -#ifdef DEBUG +#ifdef CHECKS if(!dict_contains(status->segs_with_point, s)) dict_put(status->segs_with_point, s, 0); + assert(s->fs_out_ok); #endif - assert(s->fs_out_ok); if(s->fs_out) { #ifdef DEBUG - fprintf(stderr, "[%d] receives next point (%d,%d) (drawing)\n", s->nr, p.x, p.y); + fprintf(stderr, "[%d] receives next point (%d,%d)->(%d,%d) (drawing)\n", s->nr, + s->pos.x, s->pos.y, p.x, p.y); #endif // omit horizontal lines if(s->pos.y != p.y) { - edge_t*e = malloc(sizeof(edge_t)); + edge_t*e = rfx_calloc(sizeof(edge_t)); + e->tmp = s->nr; e->a = s->pos; e->b = p; assert(e->a.y != e->b.y); @@ -482,12 +525,69 @@ static void insert_point_into_segment(status_t*status, segment_t*s, point_t p) s->pos = p; } -/* possible optimizations: - 1.) keep two different active lists around, one for negative and one for - positive slopes - 2.) delay starting events until after this function (tricky, because we *do* - need the start coordinates) -*/ +/* by restricting the recalculation of line segments to a range between the lowest + and the highest modified segment, we only do about a 33% overprocessing of fill + styles. (update: that statistic might be outdated now that xmin/xmax are double) */ +typedef struct _segrange { + double xmin; + segment_t*segmin; + double xmax; + segment_t*segmax; +} segrange_t; + +static inline char xpos_eq(segment_t*s1, segment_t*s2, int y) +{ + if(XPOS_EQ(s1, s2, y)) { + return 1; + } + return 0; +} + +void segrange_adjust_endpoints(segrange_t*range, int y) +{ +#ifdef CHECK + /* this would mean that the segment left/right of the minimum/maximum + intersects the current segment exactly at the scanline, but somehow + wasn't found to be passing through the same snapping box */ + assert(!min || !min->left || !XPOS_EQ(min, min->left, y)); + assert(!max || !max->right || !XPOS_EQ(max, max->right, y)); +#endif + + segment_t*min = range->segmin; + segment_t*max = range->segmax; + if(min) while(min->left && xpos_eq(min, min->left, y)) { + min = min->left; + } + if(max) while(max->right && xpos_eq(max, max->right, y)) { + max = max->right; + } + range->segmin = min; + range->segmax = max; +} +void segrange_test_segment_min(segrange_t*range, segment_t*seg, int y) +{ + if(!seg) return; + /* we need to calculate the xpos anew (and can't use start coordinate or + intersection coordinate), because we need the xpos exactly at the end of + this scanline. + TODO: might be faster to use XPOS_COMPARE here (see also _max) + */ + double x = XPOS(seg, y); + if(!range->segmin || x<range->xmin) { + range->segmin = seg; + range->xmin = x; + } +} +void segrange_test_segment_max(segrange_t*range, segment_t*seg, int y) +{ + if(!seg) return; + double x = XPOS(seg, y); + if(!range->segmax || x>range->xmax) { + range->segmax = seg; + range->xmax = x; + } +} + /* SLOPE_POSITIVE: \+ \ + @@ -497,22 +597,26 @@ static void insert_point_into_segment(status_t*status, segment_t*s, point_t p) I \ I \ ------- + \ + \ */ -static void add_points_to_positively_sloped_segments(status_t*status, int32_t y) +static void add_points_to_positively_sloped_segments(status_t*status, int32_t y, segrange_t*range) { + segment_t*first=0, *last = 0; int t; for(t=0;t<status->xrow->num;t++) { box_t box = box_new(status->xrow->x[t], y); segment_t*seg = actlist_find(status->actlist, box.left2, box.left2); seg = actlist_right(status->actlist, seg); + while(seg) { if(seg->a.y == y) { - // this segment just started, ignore it + // this segment started in this scanline, ignore it + seg->changed = 1;last = seg;if(!first) {first=seg;} } else if(seg->delta.x < 0) { // ignore segment w/ negative slope } else { double d1 = LINE_EQ(box.right1, seg); double d2 = LINE_EQ(box.right2, seg); if(d1>=0 || d2>=0) { + seg->changed = 1;last = seg;if(!first) {first=seg;} insert_point_into_segment(status, seg, box.right2); } else { break; @@ -521,6 +625,8 @@ static void add_points_to_positively_sloped_segments(status_t*status, int32_t y) seg = actlist_right(status->actlist, seg); } } + segrange_test_segment_min(range, first, y); + segrange_test_segment_max(range, last, y); } /* SLOPE_NEGATIVE: | + /| + / / @@ -530,21 +636,27 @@ static void add_points_to_positively_sloped_segments(status_t*status, int32_t y) | I | /I / | /+ |/ + / */ -static void add_points_to_negatively_sloped_segments(status_t*status, int32_t y) +static void add_points_to_negatively_sloped_segments(status_t*status, int32_t y, segrange_t*range) { + segment_t*first=0, *last = 0; + int firstx,lastx; int t; for(t=status->xrow->num-1;t>=0;t--) { box_t box = box_new(status->xrow->x[t], y); segment_t*seg = actlist_find(status->actlist, box.right2, box.right2); + while(seg) { if(seg->a.y == y) { - // this segment just started, ignore it + // this segment started in this scanline, ignore it + seg->changed = 1;last = seg;if(!first) {first=seg;} + if(!first) {first=seg; firstx = seg->a.x;} } else if(seg->delta.x >= 0) { // ignore segment w/ positive slope } else { double d1 = LINE_EQ(box.left1, seg); double d2 = LINE_EQ(box.left2, seg); if(d1<0 || d2<0) { + seg->changed = 1;last = seg;if(!first) {first=seg;} insert_point_into_segment(status, seg, box.right2); } else { break; @@ -553,30 +665,63 @@ static void add_points_to_negatively_sloped_segments(status_t*status, int32_t y) seg = actlist_left(status->actlist, seg); } } + segrange_test_segment_min(range, last, y); + segrange_test_segment_max(range, first, y); } -static void recalculate_windings(status_t*status) +static void recalculate_windings(status_t*status, segrange_t*range) { - /* TODO: we could use some clever second linked list structure so that we - only need to process points we know we marked */ + segrange_adjust_endpoints(range, status->y); - segment_t*s = actlist_leftmost(status->actlist); + segment_t*s = range->segmin; + segment_t*end = range->segmax; segment_t*last = 0; - while(s) { - windstate_t wind = last?last->wind:status->windrule->start(status->num_polygons); - s->wind = status->windrule->add(wind, s->fs, s->dir, s->polygon_nr); - s->fs_out = status->windrule->diff(&wind, &s->wind); - s->fs_out_ok = 1; + +#ifdef CHECKS + /* test sanity: check that we don't have changed segments + outside of the given range */ + s = actlist_leftmost(status->actlist); + while(s && s!=range->segmin) { + assert(!s->changed); + s = actlist_right(status->actlist, s); + } + s = actlist_rightmost(status->actlist); + while(s && s!=range->segmax) { + assert(!s->changed); + s = actlist_left(status->actlist, s); + } + /* in check mode, go through the whole interval so we can test + that all polygons where the fillstyle changed also have seg->changed=1 */ + s = actlist_leftmost(status->actlist); + end = 0; +#endif + + if(end) + end = actlist_right(status->actlist, end); + while(s!=end) { +#ifndef CHECK + if(s->changed) +#endif + { + segment_t* left = actlist_left(status->actlist, s); + windstate_t wind = left?left->wind:status->windrule->start(status->num_polygons); + s->wind = status->windrule->add(wind, s->fs, s->dir, s->polygon_nr); + fillstyle_t*fs_old = s->fs_out; + s->fs_out = status->windrule->diff(&wind, &s->wind); + + assert(!(!s->changed && fs_old!=s->fs_out)); + s->changed = 0; + + s->fs_out_ok = 1; #ifdef DEBUG - fprintf(stderr, "[%d] %s/%d/%s/%s ", s->nr, s->dir==DIR_UP?"up":"down", s->wind.wind_nr, s->wind.is_filled?"fill":"nofill", s->fs_out?"draw":"omit"); + fprintf(stderr, "[%d] %s/%d/%s/%s ", s->nr, s->dir==DIR_UP?"up":"down", s->wind.wind_nr, s->wind.is_filled?"fill":"nofill", s->fs_out?"draw":"omit"); #endif - last = s; + } s = actlist_right(status->actlist, s); } #ifdef DEBUG fprintf(stderr, "\n"); #endif - } /* we need to handle horizontal lines in order to add points to segments @@ -596,25 +741,19 @@ void intersect_with_horizontal(status_t*status, segment_t*h) while(s!=right) { assert(s); - /* - x1 + ((x2-x1)*(y-y1)) / dy = - (x1*(y2-y) + x2*(y-y1)) / dy - */ - point_t p; - p.y = status->y; - p.x = XPOS(s, p.y); + int x = XPOS_INT(s, status->y); #ifdef DEBUG fprintf(stderr, "...into [%d] (%d,%d) -> (%d,%d) at (%d,%d)\n", s->nr, s->a.x, s->a.y, s->b.x, s->b.y, - p.x, p.y + x, status->y ); #endif - assert(p.x >= h->a.x); - assert(p.x <= h->b.x); - assert(s->delta.x > 0 && p.x >= s->a.x || s->delta.x <= 0 && p.x <= s->a.x); - assert(s->delta.x > 0 && p.x <= s->b.x || s->delta.x <= 0 && p.x >= s->b.x); - xrow_add(status->xrow, p.x); + assert(x >= h->a.x); + assert(x <= h->b.x); + assert(s->delta.x > 0 && x >= s->a.x || s->delta.x <= 0 && x <= s->a.x); + assert(s->delta.x > 0 && x <= s->b.x || s->delta.x <= 0 && x >= s->b.x); + xrow_add(status->xrow, x); s = actlist_right(status->actlist, s); } @@ -628,6 +767,7 @@ void event_apply(status_t*status, event_t*e) event_dump(e); #endif intersect_with_horizontal(status, e->s1); + segment_destroy(e->s1);e->s1=0; break; } case EVENT_END: { @@ -635,6 +775,8 @@ void event_apply(status_t*status, event_t*e) segment_t*s = e->s1; #ifdef DEBUG event_dump(e); +#endif +#ifdef CHECKS dict_del(status->intersecting_segs, s); dict_del(status->segs_with_point, s); assert(!dict_contains(status->intersecting_segs, s)); @@ -679,7 +821,7 @@ void event_apply(status_t*status, event_t*e) char del1 = dict_del(&e->s1->scheduled_crossings, e->s2); char del2 = dict_del(&e->s2->scheduled_crossings, e->s1); assert(del1 && del2); -#ifdef DEBUG +#ifdef CHECKS point_t pair; pair.x = e->s1->nr; pair.y = e->s2->nr; @@ -691,7 +833,7 @@ void event_apply(status_t*status, event_t*e) } } -#ifdef DEBUG +#ifdef CHECKS void check_status(status_t*status) { DICT_ITERATE_KEY(status->intersecting_segs, segment_t*, s) { @@ -732,7 +874,12 @@ static void add_horizontals(gfxpoly_t*poly, windrule_t*windrule) int x = 0; char fill = 0; #ifdef DEBUG - actlist_verify_and_dump(actlist, y-1); + fprintf(stderr, "----------------------------------- %d\n", y); + actlist_dump(actlist, y-1); +#endif +#ifdef CHECKS + /* FIXME: this actually fails sometimes */ + actlist_verify(actlist, y-1); #endif do { if(fill && x != e->p.x) { @@ -790,14 +937,14 @@ static void add_horizontals(gfxpoly_t*poly, windrule_t*windrule) if(e->type == EVENT_END) segment_destroy(s); + free(e); e = heap_chopmax(queue); } while(e && y == e->p.y); - if(fill) { - fprintf(stderr, "Error: polygon is bleeding\n"); - exit(0); - } + assert(!fill); // check that polygon is not bleeding } + actlist_destroy(actlist); + heap_destroy(queue); } gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule) @@ -812,7 +959,7 @@ gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule) status.queue = queue; status.windrule = windrule; status.actlist = actlist_new(); -#ifdef DEBUG +#ifdef CHECKS status.seen_crossings = dict_new2(&point_type); #endif @@ -821,11 +968,17 @@ gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule) event_t*e = heap_chopmax(queue); while(e) { status.y = e->p.y; -#ifdef DEBUG +#ifdef CHECKS status.intersecting_segs = dict_new2(&ptr_type); status.segs_with_point = dict_new2(&ptr_type); +#endif + +#ifdef DEBUG fprintf(stderr, "----------------------------------- %d\n", status.y); - actlist_verify_and_dump(status.actlist, status.y-1); + actlist_dump(status.actlist, status.y-1); +#endif +#ifdef CHECKS + actlist_verify(status.actlist, status.y-1); #endif xrow_reset(status.xrow); do { @@ -836,16 +989,18 @@ gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule) } while(e && status.y == e->p.y); xrow_sort(status.xrow); - add_points_to_positively_sloped_segments(&status, status.y); - add_points_to_negatively_sloped_segments(&status, status.y); - recalculate_windings(&status); -#ifdef DEBUG + segrange_t range; + memset(&range, 0, sizeof(range)); + add_points_to_positively_sloped_segments(&status, status.y, &range); + add_points_to_negatively_sloped_segments(&status, status.y, &range); + recalculate_windings(&status, &range); +#ifdef CHECKS check_status(&status); dict_destroy(status.intersecting_segs); dict_destroy(status.segs_with_point); #endif } -#ifdef DEBUG +#ifdef CHECKS dict_destroy(status.seen_crossings); #endif actlist_destroy(status.actlist); diff --git a/lib/gfxpoly/poly.h b/lib/gfxpoly/poly.h index 54d54b11..2a76a4c3 100644 --- a/lib/gfxpoly/poly.h +++ b/lib/gfxpoly/poly.h @@ -5,7 +5,7 @@ #include "../q.h" typedef enum {DIR_UP, DIR_DOWN} segment_dir_t; -typedef enum {EVENT_CROSS, EVENT_END, EVENT_START, EVENT_HORIZONTAL} eventtype_t; +typedef enum {EVENT_CROSS, EVENT_END, EVENT_CORNER, EVENT_START, EVENT_HORIZONTAL} eventtype_t; typedef enum {SLOPE_POSITIVE, SLOPE_NEGATIVE} slope_t; typedef struct _point { @@ -21,6 +21,7 @@ typedef struct _edge { point_t a; point_t b; fillstyle_t*style; + int tmp; struct _edge *next; } edge_t; @@ -43,6 +44,7 @@ typedef struct _segment { point_t b; point_t delta; double k; //k = a.x*b.y-a.y*b.x = delta.y*a.x - delta.x*a.y (=0 for points on the segment) + int minx, maxx; segment_dir_t dir; fillstyle_t*fs; @@ -55,14 +57,31 @@ typedef struct _segment { struct _segment*left; struct _segment*right; - + char changed; + point_t pos; dict_t scheduled_crossings; } segment_t; #define LINE_EQ(p,s) ((double)(s)->delta.y*(p).x - (double)(s)->delta.x*(p).y - (s)->k) -#define XPOS(s,ypos) ((s)->a.x + ceil(((s)->delta.x * (double)((ypos) - (s)->a.y)) / (s)->delta.y)) + +/* x1 + ((x2-x1)*(y-y1)) / dy = + (x1*(y2-y1) + (x2-x1)*(y-y1)) / dy = + (x1*(y2-y) + x2 *(y-y1)) / dy = + (x1*y2 - x2*y1 + x2*y - y*x1) / dy = + (k + x2*y - x1*y) / dy + (k + dx*y) / dy +*/ +//#define XPOS(s,ypos) ((s)->a.x + ((s)->delta.x * (double)((ypos) - (s)->a.y)) / (s)->delta.y) +#define XPOS(s,ypos) (((s)->k + (double)(s)->delta.x*ypos) / (s)->delta.y) + +#define XPOS_INT(s,ypos) ((int)ceil(XPOS((s),ypos))) +#define XDIFF(s1,s2,ypos) (((s1)->k + (double)(s1)->delta.x*ypos)*(s2)->delta.y - \ + ((s2)->k + (double)(s1)->delta.x*ypos)*(s1)->delta.y) + +// rewrite as XDIFF==0? +#define XPOS_EQ(s1,s2,ypos) (XPOS((s1),(ypos))==XPOS((s2),(ypos))) typedef struct _gfxpoly { double gridsize; @@ -71,6 +90,7 @@ typedef struct _gfxpoly { gfxpoly_t* gfxpoly_new(double gridsize); char gfxpoly_check(gfxpoly_t*poly); +int gfxpoly_size(gfxpoly_t*poly); void gfxpoly_dump(gfxpoly_t*poly); gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule); diff --git a/lib/gfxpoly/renderpoly.c b/lib/gfxpoly/renderpoly.c index 2373a649..06440fa1 100644 --- a/lib/gfxpoly/renderpoly.c +++ b/lib/gfxpoly/renderpoly.c @@ -253,8 +253,6 @@ intbbox_t intbbox_from_polygon(gfxpoly_t*polygon, double zoom) b.width = b.xmax - b.xmin; b.height = b.ymax - b.ymin; - printf("%dx%d\n", b.width, b.height); - return b; } diff --git a/lib/gfxpoly/test.c b/lib/gfxpoly/test.c index 6e698ca2..aff9208e 100644 --- a/lib/gfxpoly/test.c +++ b/lib/gfxpoly/test.c @@ -26,6 +26,118 @@ gfxline_t*mkstar(int x1, int y1, int x2, int y2) return line; } +gfxline_t* mkrandomshape(int range, int n) +{ + int i; + gfxline_t* line = malloc(sizeof(gfxline_t)*n*2); + for(i=0;i<n;i++) { + line[i].type = i?gfx_lineTo:gfx_moveTo; + line[i].x = lrand48()%range - range/2; + line[i].y = lrand48()%range - range/2; + line[i].next = &line[i+1]; + line[n*2-i-1].type = gfx_lineTo; + line[n*2-i-1].x = line[i].x; + line[n*2-i-1].y = line[i].y; + line[n*2-i-1].next = &line[n*2-i]; + } + line[n*2-1].next = 0; + line[n-1].x = line[0].x; + line[n-1].y = line[0].y; + line[n-1].next = 0; +} + +gfxline_t* mkchessboard() +{ + gfxline_t*b = 0; + int x,y; + unsigned int r = 0; + int spacing = 20; + + //int num_caros = 40; + //int l = 5; + //char do_centerpiece=1; + + int num_caros = 4; + int l=1; + char do_centerpiece=0; + + for(x=-l;x<=l;x++) + for(y=-l;y<=l;y++) { + /* pseudo random */ + r = crc32_add_byte(r, x);r = crc32_add_byte(r, y); + if(r&1) { + gfxline_t*box; + if(r&2) { + box = gfxline_makerectangle(x*spacing,y*spacing,(x+1)*spacing,(y+1)*spacing); + } else { + box = gfxline_makerectangle((x+1)*spacing,y*spacing,x*spacing,(y+1)*spacing); + } + b = gfxline_append(b, box); + } + } + + int t; + for(t=0;t<num_caros;t++) { + r = crc32_add_byte(r, t); + int x=(r%10-5)*spacing; + int y=((r>>4)%10-5)*spacing; + int sizex = ((r>>8)%4)*spacing; + int sizey = sizex; + if(r&65536) + sizex = -sizex; + gfxline_t*l = malloc(sizeof(gfxline_t)*5); + l[0].type = gfx_moveTo;l[0].next = &l[1]; + l[1].type = gfx_lineTo;l[1].next = &l[2]; + l[2].type = gfx_lineTo;l[2].next = &l[3]; + l[3].type = gfx_lineTo;l[3].next = &l[4]; + l[4].type = gfx_lineTo;l[4].next = 0; + l[0].x = x; + l[0].y = y-sizey; + l[1].x = x+sizex; + l[1].y = y; + l[2].x = x; + l[2].y = y+sizey; + l[3].x = x-sizex; + l[3].y = y; + l[4].x = x; + l[4].y = y-sizey; + gfxline_append(b, l); + } + if(do_centerpiece) + for(t=0;t<5;t++) { + gfxline_t*l = gfxline_makerectangle(-9*spacing,-10,9*spacing,10); + gfxmatrix_t matrix; + memset(&matrix, 0, sizeof(gfxmatrix_t)); + double ua=t*0.43; + matrix.m00=cos(ua);matrix.m10=sin(ua); + matrix.m01=-sin(ua);matrix.m11=cos(ua); + gfxline_transform(l, &matrix); + gfxline_append(b, l); + } + return b; +} + +int test0() +{ + gfxline_t* b = mkchessboard(); + + gfxmatrix_t m; + memset(&m, 0, sizeof(gfxmatrix_t)); + int t = 28; + m.m00 = cos(t*M_PI/180.0); + m.m01 = sin(t*M_PI/180.0); + m.m10 = -sin(t*M_PI/180.0); + m.m11 = cos(t*M_PI/180.0); + m.tx = 400*1.41/2; + m.ty = 400*1.41/2; + gfxline_transform(b, &m); + + gfxpoly_t*poly = gfxpoly_from_gfxline(b, 0.05); + gfxpoly_t*poly2 = gfxpoly_process(poly, &windrule_evenodd); + gfxpoly_destroy(poly2); + gfxpoly_destroy(poly); +} + int test1() { gfxline_t*box1 = gfxline_makerectangle(50,50,150,150); @@ -46,14 +158,17 @@ int test1() matrix.m01=-sin(ua);matrix.m11=cos(ua); //gfxline_transform(b, &matrix); - gfxpoly_t*poly = gfxpoly_fillToPoly(b, 0.05); + + gfxpoly_t*poly = gfxpoly_from_gfxline(b, 0.05); gfxline_free(box1); gfxline_free(box2); gfxline_free(box3); gfxline_free(star); gfxpoly_dump(poly); - gfxpoly_process(poly, &windrule_evenodd); + gfxpoly_t*poly2 = gfxpoly_process(poly, &windrule_evenodd); + gfxpoly_destroy(poly2); + gfxpoly_destroy(poly); } int test_square(int width, int height, int num, double gridsize, char bitmaptest) @@ -70,7 +185,7 @@ int test_square(int width, int height, int num, double gridsize, char bitmaptest line[num-1].y = line[0].y; line[num-1].next = 0; - gfxpoly_t*poly = gfxpoly_fillToPoly(line, gridsize); + gfxpoly_t*poly = gfxpoly_from_gfxline(line, gridsize); gfxline_free(line); windrule_t*rule = &windrule_circular; @@ -109,23 +224,10 @@ void test3() #define N 100 #define RANGE 400 - int i; - gfxline_t* line = malloc(sizeof(gfxline_t)*N*2); - for(i=0;i<N;i++) { - line[i].type = i?gfx_lineTo:gfx_moveTo; - line[i].x = lrand48()%RANGE - RANGE/2; - line[i].y = lrand48()%RANGE - RANGE/2; - line[i].next = &line[i+1]; - line[N*2-i-1].type = gfx_lineTo; - line[N*2-i-1].x = line[i].x; - line[N*2-i-1].y = line[i].y; - line[N*2-i-1].next = &line[N*2-i]; - } - line[N*2-1].next = 0; - - line[N-1].x = line[0].x; - line[N-1].y = line[0].y; - line[N-1].next = 0; + //gfxline_t*line = mkrandomshape(RANGE, N); + //windrule_t*rule = &windrule_circular; + gfxline_t*line = mkchessboard(); + windrule_t*rule = &windrule_evenodd; gfxmatrix_t m; memset(&m, 0, sizeof(m)); @@ -149,11 +251,13 @@ void test3() m.m11 = cos(t*M_PI/180.0); m.tx = RANGE*1.41/2; m.ty = RANGE*1.41/2; + printf("%d\n", t); + gfxline_t*l = gfxline_clone(line); gfxline_transform(l, &m); - gfxpoly_t*poly = gfxpoly_fillToPoly(l, 0.05); - gfxpoly_t*poly2 = gfxpoly_process(poly, &windrule_circular); + gfxpoly_t*poly = gfxpoly_from_gfxline(l, 0.05); + gfxpoly_t*poly2 = gfxpoly_process(poly, rule); tag = swf_InsertTag(tag, ST_DEFINESHAPE); SHAPE* s; @@ -259,7 +363,129 @@ void test4() } } +#include "../gfxdevice.h" +#include "../pdf/pdf.h" + +void extract_polygons_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color) +{ + gfxpoly_t*poly = gfxpoly_from_gfxline(line, 0.05); + printf("%d segments\n", gfxpoly_size(poly)); + + if(!gfxpoly_check(poly)) { + gfxpoly_destroy(poly); + printf("bad polygon\n"); + return; + } + + windrule_t*rule = &windrule_evenodd; + gfxpoly_t*poly2 = gfxpoly_process(poly, rule); + + double zoom = 1.0; + intbbox_t bbox = intbbox_from_polygon(poly, zoom); + unsigned char*bitmap1 = render_polygon(poly, &bbox, zoom, rule); + unsigned char*bitmap2 = render_polygon(poly2, &bbox, zoom, &windrule_evenodd); + if(!bitmap_ok(&bbox, bitmap1)) { + printf("bad polygon or error in renderer\n"); + return; + } + if(!bitmap_ok(&bbox, bitmap2)) { + save_two_bitmaps(&bbox, bitmap1, bitmap2, "error.png"); + assert(!"error in bitmap"); + } + if(!compare_bitmaps(&bbox, bitmap1, bitmap2)) { + save_two_bitmaps(&bbox, bitmap1, bitmap2, "error.png"); + assert(!"bitmaps don't match"); + } + + gfxpoly_destroy(poly); + gfxpoly_destroy(poly2); +} +int extract_polygons_setparameter(gfxdevice_t*dev, const char*key, const char*value) { + return 0; +} +void extract_polygons_startclip(gfxdevice_t*dev, gfxline_t*line) +{ + extract_polygons_fill(dev, line, 0); +} +void extract_polygons_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*imgcoord2devcoord, gfxcxform_t*cxform) +{ + extract_polygons_fill(dev, line, 0); +} +void extract_polygons_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*gradcoord2devcoord) +{ + extract_polygons_fill(dev, line, 0); +} +void extract_polygons_drawlink(gfxdevice_t*dev, gfxline_t*line, const char*action) +{ + extract_polygons_fill(dev, line, 0); +} +void extract_polygons_addfont(gfxdevice_t*dev, gfxfont_t*font) +{ + int t; + for(t=0;t<font->num_glyphs;t++) { + //extract_polygons_fill(dev, font->glyphs[t].line, 0); + } +} +void extract_polygons_endclip(gfxdevice_t*dev) +{ +} +void extract_polygons_stroke(gfxdevice_t*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit) +{ +} +void extract_polygons_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix) +{ +} + +gfxdevice_t extract_polygons = +{ +name: "extract polygons", +setparameter:extract_polygons_setparameter, +startclip: extract_polygons_startclip, +endclip: extract_polygons_endclip, +stroke: extract_polygons_stroke, +fill: extract_polygons_fill, +fillbitmap: extract_polygons_fillbitmap, +fillgradient: extract_polygons_fillgradient, +addfont: extract_polygons_addfont, +drawchar: extract_polygons_drawchar, +drawlink: extract_polygons_drawlink, +startpage: 0, +endpage: 0, +geterror: 0, +finish: 0, +internal: 0 +}; + +void test5() +{ + char*dir = "pdfs"; + DIR*_dir = opendir(dir); + if(!_dir) return; + struct dirent*file; + while(1) { + file = readdir(_dir); + if (!file) + break; + if(!strstr(file->d_name, ".pdf")) + continue; + char* filename = allocprintf("%s/%s", dir, file->d_name); + + gfxsource_t*driver = gfxsource_pdf_create(); + gfxdocument_t*doc = driver->open(driver, filename); + gfxdevice_t*out = &extract_polygons; + int t; + for(t=1;t<=doc->num_pages;t++) { + printf("%s (page %d)\n", filename, t); + gfxpage_t* page = doc->getpage(doc, t); + page->render(page, out); + page->destroy(page); + break; + } + free(filename); + } +} + int main() { - test3(); + test0(); } |