polygon intersector: improved test routines
[swftools.git] / lib / gfxpoly.c
index 8b914a8..76352af 100644 (file)
 #include "gfxtools.h"
 #include "gfxpoly.h"
 #include "mem.h"
 #include "gfxtools.h"
 #include "gfxpoly.h"
 #include "mem.h"
+#ifdef INTERNAL_LIBART
 #include "art/libart.h"
 #include "art/art_svp_intersect.h"
 #include "art/art_svp_ops.h"
 #include "art/libart.h"
 #include "art/art_svp_intersect.h"
 #include "art/art_svp_ops.h"
+#else
+#include <libart_lgpl/libart.h>
+#include <libart_lgpl/art_svp_intersect.h>
+#include <libart_lgpl/art_svp_ops.h>
+#endif
 #include "log.h"
 #include <assert.h>
 #include <memory.h>
 #include "log.h"
 #include <assert.h>
 #include <memory.h>
 //#define SHEAR
 //#define DEBUG
 
 //#define SHEAR
 //#define DEBUG
 
+//----------------------------------------- svp renderer ----------------------------------------
+
+typedef struct {
+    int xmin;
+    int ymin;
+    int xmax;
+    int ymax;
+    int width;
+    int height;
+} intbbox_t;
+
+typedef struct _renderpoint
+{
+    double x;
+    signed char direction;
+} renderpoint_t;
+
+typedef struct _renderline
+{
+    renderpoint_t*points;
+    int size;
+    int num;
+} renderline_t;
+
+typedef struct _renderbuf
+{
+    intbbox_t bbox;
+    int width;
+    int height;
+    double zoom;
+    renderline_t*lines;
+} renderbuf_t;
+
+static inline void add_pixel(renderbuf_t*buf, double x, int y, signed char direction)
+{
+    renderpoint_t p;
+    p.x = x;
+    p.direction = direction;
+
+    if(x >= buf->bbox.xmax || y >= buf->bbox.ymax || y < buf->bbox.ymin) 
+        return;
+    renderline_t*l = &buf->lines[y-buf->bbox.ymin];
+
+    if(l->num == l->size) {
+       l->size += 32;
+       l->points = (renderpoint_t*)rfx_realloc(l->points, l->size * sizeof(renderpoint_t));
+    }
+    l->points[l->num] = p;
+    l->num++;
+}
+#define CUT 0.5
+#define INT(x) ((int)((x)+16)-16)
+static void add_line(renderbuf_t*buf, double x1, double y1, double x2, double y2, signed char direction)
+{
+    x1 *= buf->zoom;
+    y1 *= buf->zoom;
+    x2 *= buf->zoom;
+    y2 *= buf->zoom;
+    double diffx, diffy;
+    double ny1, ny2, stepx;
+    if(y2 < y1) {
+        double x,y;
+       x = x1;x1 = x2;x2=x;
+       y = y1;y1 = y2;y2=y;
+    }
+    diffx = x2 - x1;
+    diffy = y2 - y1;
+    ny1 = INT(y1)+CUT;
+    ny2 = INT(y2)+CUT;
+
+    if(ny1 < y1) {
+        ny1 = INT(y1) + 1.0 + CUT;
+    }
+    if(ny2 >= y2) {
+        ny2 = INT(y2) - 1.0 + CUT;
+    }
+    if(ny1 > ny2)
+        return;
+
+    stepx = diffx/diffy;
+    x1 = x1 + (ny1-y1)*stepx;
+    x2 = x2 + (ny2-y2)*stepx;
+
+    int posy=INT(ny1);
+    int endy=INT(ny2);
+    double posx=0;
+    double startx = x1;
+
+    while(posy<=endy) {
+        double xx = startx + posx;
+        add_pixel(buf, xx, posy, direction);
+        posx+=stepx;
+        posy++;
+    }
+}
+
+static int compare_renderpoints(const void * _a, const void * _b)
+{
+    renderpoint_t*a = (renderpoint_t*)_a;
+    renderpoint_t*b = (renderpoint_t*)_b;
+    if(a->x < b->x) return -1;
+    if(a->x > b->x) return 1;
+    return 0;
+}
+
+static void fill_bitwise(unsigned char*line, int x1, int x2)
+{
+    int p1 = x1>>3;
+    int p2 = x2>>3;
+    int b1 = 0xff >> (x1&7);
+    int b2 = 0xff << (1+7-(x2&7));
+    if(p1==p2) {
+        line[p1] |= b1&b2;
+    } else {
+        line[p1] |= b1;
+        memset(&line[p1+1], 255, p2-(p1+1));
+        line[p2] = b2;
+    }
+}
+
+unsigned char* render_svp(ArtSVP*svp, intbbox_t*bbox, double zoom, ArtWindRule rule)
+{
+    renderbuf_t _buf, *buf=&_buf;
+    buf->width = (bbox->xmax - bbox->xmin);
+    buf->height = (bbox->ymax - bbox->ymin);
+    buf->bbox = *bbox;
+    buf->zoom = zoom;
+    int width8 = (buf->width+7) >> 3;
+    unsigned char* image = (unsigned char*)malloc(width8*buf->height);
+    memset(image, 0, width8*buf->height);
+    
+    buf->lines = (renderline_t*)rfx_alloc(buf->height*sizeof(renderline_t));
+    int y;
+    for(y=0;y<buf->height;y++) {
+       memset(&buf->lines[y], 0, sizeof(renderline_t));
+        buf->lines[y].points = 0;
+        buf->lines[y].num = 0;
+    }
+
+    int t;
+    for(t=0;t<svp->n_segs;t++) {
+        ArtSVPSeg* seg = &svp->segs[t];
+        int s;
+        for(s=0;s<seg->n_points-1;s++) {
+            int dir = seg->dir? 1 : -1;
+            add_line(buf, seg->points[s].x, seg->points[s].y, seg->points[s+1].x, seg->points[s+1].y, dir);
+        }
+    }
+    for(y=0;y<buf->height;y++) {
+       renderpoint_t*points = buf->lines[y].points;
+        unsigned char*line = &image[width8*y];
+       int n;
+       int num = buf->lines[y].num;
+        int wind = 0;
+        qsort(points, num, sizeof(renderpoint_t), compare_renderpoints);
+        int lastx = 0;
+        int fill = 0;
+
+        for(n=0;n<num;n++) {
+            renderpoint_t*p = &points[n];
+            int x = (int)(p->x - bbox->xmin);
+            
+            if(x < lastx)
+                x = lastx;
+            if(x > buf->width) {
+                break;
+            }
+            if(fill && x!=lastx) {
+                fill_bitwise(line, lastx, x);
+            }
+            wind += p->direction;
+            if(rule == ART_WIND_RULE_INTERSECT) {
+                fill = wind>=2;
+            } else if (rule == ART_WIND_RULE_NONZERO) {
+                fill = wind!=0;
+            } else if (rule == ART_WIND_RULE_ODDEVEN) {
+                fill = wind&1;
+            } else { // rule == ART_WIND_RULE_POSITIVE
+                fill = wind>=1;
+            }
+            lastx = x;
+        }
+        if(fill && lastx!=buf->width)
+            fill_bitwise(line, lastx, buf->width);
+    }
+    
+    for(y=0;y<buf->height;y++) {
+        if(buf->lines[y].points) {
+            free(buf->lines[y].points);
+        }
+        memset(&buf->lines[y], 0, sizeof(renderline_t));
+    }
+    free(buf->lines);buf->lines=0;
+    return image;
+}
+
+#define MAX_WIDTH 8192
+#define MAX_HEIGHT 4096
+
+intbbox_t get_svp_bbox(ArtSVP*svp, double zoom)
+{
+    int t;
+    intbbox_t b = {0,0,0,0};
+    if(svp->n_segs && svp->segs[0].n_points) {
+        b.xmin = svp->segs[0].points[0].x;
+        b.ymin = svp->segs[0].points[0].y;
+        b.xmax = svp->segs[0].points[0].x;
+        b.ymax = svp->segs[0].points[0].y;
+    }
+    for(t=0;t<svp->n_segs;t++) {
+        ArtSVPSeg* seg = &svp->segs[t];
+        int s;
+        for(s=0;s<seg->n_points;s++) {
+            double x = seg->points[s].x*zoom;
+            double y = seg->points[s].y*zoom;
+            int x1 = floor(x);
+            int x2 = ceil(x);
+            int y1 = floor(y);
+            int y2 = ceil(y);
+            if(x1 < b.xmin) b.xmin = x1;
+            if(y1 < b.ymin) b.ymin = y1;
+            if(x2 > b.xmax) b.xmax = x2;
+            if(y2 > b.ymax) b.ymax = y2;
+        }
+    }
+    if(b.xmax > (int)(MAX_WIDTH*zoom))
+       b.xmax = (int)(MAX_WIDTH*zoom);
+    if(b.ymax > (int)(MAX_HEIGHT*zoom))
+       b.ymax = (int)(MAX_HEIGHT*zoom);
+    if(b.xmin < -(int)(MAX_WIDTH*zoom))
+       b.xmin = -(int)(MAX_WIDTH*zoom);
+    if(b.ymin < -(int)(MAX_HEIGHT*zoom))
+       b.ymin = -(int)(MAX_HEIGHT*zoom);
+    
+    if(b.xmin > b.xmax) 
+       b.xmin = b.xmax;
+    if(b.ymin > b.ymax) 
+       b.ymin = b.ymax;
+
+    b.width = b.xmax - b.xmin;
+    b.height = b.ymax - b.ymin;
+    return b;
+}
+
+#define B11100000 0xe0
+#define B01110000 0x70
+#define B00111000 0x38
+#define B00011100 0x1c
+#define B00001110 0x0e
+#define B00000111 0x07
+#define B10000000 0x80
+#define B01000000 0x40
+#define B00100000 0x20
+#define B00010000 0x10
+#define B00001000 0x08
+#define B00000100 0x04
+#define B00000010 0x02
+#define B00000001 0x01
+
+int compare_bitmaps(intbbox_t*bbox, unsigned char*data1, unsigned char*data2)
+{
+    if(!data1 || !data2) 
+        return 0;
+    int x,y;
+    int height = bbox->height;
+    int width = bbox->width;
+    int width8 = (width+7) >> 3;
+    unsigned char*l1 = &data1[width8];
+    unsigned char*l2 = &data2[width8];
+    for(y=1;y<height-1;y++) {
+        for(x=0;x<width8;x++) {
+            unsigned a = l1[x-width8] & l1[x] & l1[x+width8];
+            unsigned b = l2[x-width8] & l2[x] & l2[x+width8];
+            
+            if((a&B11100000) && !(l2[x]&B01000000)) return 0;
+            if((a&B01110000) && !(l2[x]&B00100000)) return 0;
+            if((a&B00111000) && !(l2[x]&B00010000)) return 0;
+            if((a&B00011100) && !(l2[x]&B00001000)) return 0;
+            if((a&B00001110) && !(l2[x]&B00000100)) return 0;
+            if((a&B00000111) && !(l2[x]&B00000010)) return 0;
+
+            if((b&B11100000) && !(l1[x]&B01000000)) return 0;
+            if((b&B01110000) && !(l1[x]&B00100000)) return 0;
+            if((b&B00111000) && !(l1[x]&B00010000)) return 0;
+            if((b&B00011100) && !(l1[x]&B00001000)) return 0;
+            if((b&B00001110) && !(l1[x]&B00000100)) return 0;
+            if((b&B00000111) && !(l1[x]&B00000010)) return 0;
+        }
+        l1 += width8;
+        l2 += width8;
+    }
+    return 1;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+
 static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
 {
     ArtVpath *vec = NULL;
 static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
 {
     ArtVpath *vec = NULL;
@@ -69,11 +372,13 @@ static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
 
     pos = 0;
     l2 = line;
 
     pos = 0;
     l2 = line;
+    int lastmove=-1;
     while(l2) {
        if(l2->type == gfx_moveTo) {
            vec[pos].code = ART_MOVETO_OPEN;
            vec[pos].x = l2->x;
            vec[pos].y = l2->y;
     while(l2) {
        if(l2->type == gfx_moveTo) {
            vec[pos].code = ART_MOVETO_OPEN;
            vec[pos].x = l2->x;
            vec[pos].y = l2->y;
+            lastmove=pos;
            pos++; 
            assert(pos<=len);
        } else if(l2->type == gfx_lineTo) {
            pos++; 
            assert(pos<=len);
        } else if(l2->type == gfx_lineTo) {
@@ -98,6 +403,16 @@ static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
        }
        x = l2->x;
        y = l2->y;
        }
        x = l2->x;
        y = l2->y;
+       
+        /* let closed line segments start w/ MOVETO instead of MOVETO_OPEN */
+        if(lastmove>=0 && l2->type!=gfx_moveTo && (!l2->next || l2->next->type == gfx_moveTo)) {
+            if(vec[lastmove].x == l2->x &&
+               vec[lastmove].y == l2->y) {
+                assert(vec[lastmove].code == ART_MOVETO_OPEN);
+                vec[lastmove].code = ART_MOVETO;
+            }
+        }
+
        l2 = l2->next;
     }
     vec[pos++].code = ART_END;
        l2 = l2->next;
     }
     vec[pos++].code = ART_END;
@@ -122,7 +437,7 @@ static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
     }
 
     /* Find adjacent identical points. If an ajdacent pair of identical
     }
 
     /* Find adjacent identical points. If an ajdacent pair of identical
-       points is found, the second is removed.
+       points is found, the second one is removed.
        So moveto x,y lineto x,y becomes moveto x,y
           lineto x,y lineto x,y becomes lineto x,y
           lineto x,y moveto x,y becomes lineto x,y
        So moveto x,y lineto x,y becomes moveto x,y
           lineto x,y lineto x,y becomes lineto x,y
           lineto x,y moveto x,y becomes lineto x,y
@@ -165,7 +480,7 @@ static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
 
 static void shear_svp(ArtSVP*svp, double v)
 {
 
 static void shear_svp(ArtSVP*svp, double v)
 {
-    /* do a "shearing on the polygon. We do this to eliminate all
+    /* do a "shearing" on the polygon. We do this to eliminate all
        horizontal lines (which libart can't handle properly, even though
        it tries). */
 
        horizontal lines (which libart can't handle properly, even though
        it tries). */
 
@@ -214,7 +529,13 @@ static double find_shear_value(ArtSVP*svp)
         }
         if(!fail) 
             break;
         }
         if(!fail) 
             break;
-        v = lrand48() / 2000000000.0;
+#ifdef HAVE_LRAND48
+       v = lrand48() / 2000000000.0;;
+#elif HAVE_RAND
+        v = rand() / 2000000000.0;
+#else
+#error "no lrand48()/rand() implementation found"
+#endif
         tries++;
     }
     return v;
         tries++;
     }
     return v;
@@ -347,7 +668,9 @@ int check_svp(ArtSVP*svp)
            ArtPoint* p2 = &seg->points[p+1];
             
            if(p1->y >= p2->y) {
            ArtPoint* p2 = &seg->points[p+1];
             
            if(p1->y >= p2->y) {
-                msg("<error> bad svp, y in seg is non-increasing %.16f -> %.16f\n", p1->y, p2->y);
+                if(p1->y > p2->y) {
+                    msg("<error> bad svp, y in seg is non-increasing %.16f -> %.16f\n", p1->y, p2->y);
+                }
             } else {
                segs[num_segs].y1 = p1->y;
                segs[num_segs].y2 = p2->y;
             } else {
                segs[num_segs].y1 = p1->y;
                segs[num_segs].y2 = p2->y;
@@ -363,7 +686,7 @@ int check_svp(ArtSVP*svp)
     qsort(seg_start, num_segs, sizeof(svp_segment_part_t*), compare_seg_start);
     qsort(seg_end, num_segs, sizeof(svp_segment_part_t*), compare_seg_end);
 
     qsort(seg_start, num_segs, sizeof(svp_segment_part_t*), compare_seg_start);
     qsort(seg_end, num_segs, sizeof(svp_segment_part_t*), compare_seg_end);
 
-    double lasty = y[0]+1;
+    double lasty = num_points?y[0]+1:0;
     int num_active = 0;
     int bleed = 0;
     double bleedy1=0,bleedy2 = 0;
     int num_active = 0;
     int bleed = 0;
     double bleedy1=0,bleedy2 = 0;
@@ -393,7 +716,7 @@ int check_svp(ArtSVP*svp)
        lasty = y[t];
     }
     if(bleed) {
        lasty = y[t];
     }
     if(bleed) {
-       msg("<warning> svp bleeds from y=%.16f to y=%.16f (%d/%d active segments)\n", 
+       msg("<verbose> svp bleeds from y=%.16f to y=%.16f (%d/%d active segments)\n", 
                bleedy1, bleedy2,
                bleed, num_segs);
        free(y);free(segs);free(seg_start);free(seg_end);
                bleedy1, bleedy2,
                bleed, num_segs);
        free(y);free(segs);free(seg_start);free(seg_end);
@@ -412,7 +735,6 @@ void write_svp_postscript(const char*filename, ArtSVP*svp)
 {
     if(!svp)
        return;
 {
     if(!svp)
        return;
-    printf("writing %s\n", filename);
     FILE*fi = fopen(filename, "wb");
     int i, j;
     double xmin=0,ymin=0,xmax=0,ymax=0;
     FILE*fi = fopen(filename, "wb");
     int i, j;
     double xmin=0,ymin=0,xmax=0,ymax=0;
@@ -612,24 +934,34 @@ static ArtSVP* gfxfillToSVP(gfxline_t*line, int perturb)
 //#endif
 
 
 //#endif
 
 
-
-extern const ArtSVP* current_svp;
-extern void art_report_error();
-extern int art_error_in_intersector;
-
 ArtSVP* run_intersector(ArtSVP*svp, ArtWindRule rule)
 {
     ArtSvpWriter * swr = art_svp_writer_rewind_new(rule);
 
 ArtSVP* run_intersector(ArtSVP*svp, ArtWindRule rule)
 {
     ArtSvpWriter * swr = art_svp_writer_rewind_new(rule);
 
+    double zoom = 1.0;
+    intbbox_t bbox = get_svp_bbox(svp, zoom);
+
     art_svp_intersector(svp, swr);
     ArtSVP*result = art_svp_writer_rewind_reap(swr);
     clean_svp(result);
     if(!check_svp(result)) {
     art_svp_intersector(svp, swr);
     ArtSVP*result = art_svp_writer_rewind_reap(swr);
     clean_svp(result);
     if(!check_svp(result)) {
-       msg("<warning> Error in polygon processing");
        current_svp = result;
        art_report_error(); // might set art_error_in_intersector
        current_svp = result;
        art_report_error(); // might set art_error_in_intersector
+    } else {
+        msg("<verbose> Comparing polygon renderings of size %dx%d and %dx%d", bbox.width, bbox.height, bbox.width, bbox.height);
+        unsigned char*data1 = render_svp(svp, &bbox, zoom, rule);
+        unsigned char*data2 = render_svp(result, &bbox, zoom, ART_WIND_RULE_ODDEVEN);
+        if(!compare_bitmaps(&bbox, data1, data2)) {
+            msg("<verbose> Bad SVP rewinding result- polygons don't match");
+            current_svp = result;
+            art_report_error(); // might set art_error_in_intersector
+        }
+        free(data1);
+        free(data2);
     }
     }
+
     if(art_error_in_intersector) {
     if(art_error_in_intersector) {
+       msg("<verbose> Error in polygon processing");
        art_svp_free(result);
        art_error_in_intersector=0;
        return 0;
        art_svp_free(result);
        art_error_in_intersector=0;
        return 0;
@@ -675,7 +1007,7 @@ gfxline_t* gfxpoly_to_gfxline(gfxpoly_t*poly)
 gfxpoly_t* gfxpoly_fillToPoly(gfxline_t*line)
 {
     /* I'm not sure whether doing perturbation here is actually
 gfxpoly_t* gfxpoly_fillToPoly(gfxline_t*line)
 {
     /* I'm not sure whether doing perturbation here is actually
-       a good idea- if that line has been run through the machine
+       a good idea- if that line has been run through the machinery
        several times already, it might be safer to leave it alone,
        since it should already be in a format libart can handle */
 #ifdef PERTURBATE
        several times already, it might be safer to leave it alone,
        since it should already be in a format libart can handle */
 #ifdef PERTURBATE
@@ -801,14 +1133,9 @@ gfxline_t* gfxline_circularToEvenOdd(gfxline_t*line)
     msg("<verbose> Converting circular-filled gfxline of %d segments to even-odd filled gfxline", gfxline_len(line));
     ArtSVP* svp = gfxfillToSVP(line, 1);
 
     msg("<verbose> Converting circular-filled gfxline of %d segments to even-odd filled gfxline", gfxline_len(line));
     ArtSVP* svp = gfxfillToSVP(line, 1);
 
-    /* TODO: ART_WIND_RULE_POSITIVE means that a shape is visible if
-             positive and negative line segments add up to something positive.
-             I *think* that clockwise fill in PDF is defined in a way, however,
-             that the *last* shape's direction will determine whether something
-             is filled */
     ArtSVP* svp_rewinded;
     
     ArtSVP* svp_rewinded;
     
-    svp_rewinded = run_intersector(svp, ART_WIND_RULE_POSITIVE);
+    svp_rewinded = run_intersector(svp, ART_WIND_RULE_NONZERO);
     if(!svp_rewinded) {
        art_svp_free(svp);
        return 0;
     if(!svp_rewinded) {
        art_svp_free(svp);
        return 0;