added new perturbation, various bugfixes, and postscript output
[swftools.git] / lib / gfxpoly.c
1 /* gfxpoly.c 
2
3    Various boolean polygon functions.
4
5    Part of the swftools package.
6
7    Copyright (c) 2005 Matthias Kramm <kramm@quiss.org> 
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
22
23 #include "../config.h"
24 #include "gfxdevice.h"
25 #include "gfxtools.h"
26 #include "gfxpoly.h"
27 #include "mem.h"
28 #include "art/libart.h"
29 #include "art/art_svp_intersect.h"
30 #include "art/art_svp_ops.h"
31 #include "log.h"
32 #include <assert.h>
33 #include <memory.h>
34 #include <math.h>
35
36 #define PERTURBATE
37 //#define SHEAR
38 //#define DEBUG
39
40 static ArtVpath* gfxline_to_ArtVpath(gfxline_t*line, char fill)
41 {
42     ArtVpath *vec = NULL;
43     int pos=0,len=0;
44     gfxline_t*l2;
45     double x=0,y=0;
46
47     /* factor which determines into how many line fragments a spline is converted */
48     double subfraction = 2.4;//0.3
49
50     l2 = line;
51     while(l2) {
52         if(l2->type == gfx_moveTo) {
53             pos ++;
54         } else if(l2->type == gfx_lineTo) {
55             pos ++;
56         } else if(l2->type == gfx_splineTo) {
57             int parts = (int)(sqrt(fabs(l2->x-2*l2->sx+x) + fabs(l2->y-2*l2->sy+y))*subfraction);
58             if(!parts) parts = 1;
59             pos += parts + 1;
60         }
61         x = l2->x;
62         y = l2->y;
63         l2 = l2->next;
64     }
65     pos++;
66     len = pos;
67
68     vec = art_new (ArtVpath, len+1);
69
70     pos = 0;
71     l2 = line;
72     while(l2) {
73         if(l2->type == gfx_moveTo) {
74             vec[pos].code = ART_MOVETO_OPEN;
75             vec[pos].x = l2->x;
76             vec[pos].y = l2->y;
77             pos++; 
78             assert(pos<=len);
79         } else if(l2->type == gfx_lineTo) {
80             vec[pos].code = ART_LINETO;
81             vec[pos].x = l2->x;
82             vec[pos].y = l2->y;
83             pos++; 
84             assert(pos<=len);
85         } else if(l2->type == gfx_splineTo) {
86             int i;
87             int parts = (int)(sqrt(fabs(l2->x-2*l2->sx+x) + fabs(l2->y-2*l2->sy+y))*subfraction);
88             if(!parts) parts = 1;
89             double stepsize = 1.0/parts;
90             for(i=0;i<=parts;i++) {
91                 double t = (double)i*stepsize;
92                 vec[pos].code = ART_LINETO;
93                 vec[pos].x = l2->x*t*t + 2*l2->sx*t*(1-t) + x*(1-t)*(1-t);
94                 vec[pos].y = l2->y*t*t + 2*l2->sy*t*(1-t) + y*(1-t)*(1-t);
95                 pos++;
96                 assert(pos<=len);
97             }
98         }
99         x = l2->x;
100         y = l2->y;
101         l2 = l2->next;
102     }
103     vec[pos++].code = ART_END;
104     assert(pos == len);
105
106     if(!fill) {
107         /* Fix "dotted" lines. Those are lines where singular points are created
108            by a moveto x,y lineto x,y combination. We "fix" these by shifting the
109            point in the lineto a little bit to the right 
110            These should only occur in strokes, not in fills, so do this only
111            when we know we're not filling.
112          */
113         int t;
114         for(t=0;vec[t].code!=ART_END;t++) {
115             if(t>0 && (vec[t-1].code==ART_MOVETO_OPEN || vec[t-1].code==ART_MOVETO) 
116                    && vec[t].code==ART_LINETO && vec[t+1].code!=ART_LINETO &&
117                 vec[t-1].x == vec[t].x && 
118                 vec[t-1].y == vec[t].y) {
119                 vec[t].x += 0.01;
120             }
121         }
122     }
123
124     /* Find adjacent identical points. If an ajdacent pair of identical
125        points is found, the second is removed.
126        So moveto x,y lineto x,y becomes moveto x,y
127           lineto x,y lineto x,y becomes lineto x,y
128           lineto x,y moveto x,y becomes lineto x,y
129           moveto x,y moveto x,y becomes moveto x,y
130           lineto x,y lineto x2,y2 becomes lineto x2,y2 (if dir(x,y) ~= dir(x2,y2))
131      */
132     pos = 0;
133     int outpos = 0;
134     while(1)
135     {
136         if(vec[pos].code == ART_END) {
137             vec[outpos++] = vec[pos++];
138             break;
139         }
140
141         char samedir = 0, samepoint = 0;
142         if(outpos) {
143             double dx = vec[pos].x-vec[pos-1].x;
144             double dy = vec[pos].y-vec[pos-1].y;
145             /*if(pos<len-1 && vec[pos].code == ART_LINETO && vec[pos+1].code == ART_LINETO) {
146                 double dx2 = vec[pos+1].x-vec[pos].x;
147                 double dy2 = vec[pos+1].y-vec[pos].y;
148                 if(fabs(dx*dy2 - dy*dx2) < 0.0001 && dx*dx2 + dy*dy2 >= 0) {
149                     samedir=1;
150                 }
151             }*/
152             if(fabs(dx) + fabs(dy) < 0.0001) {
153                 samepoint=1;
154             }
155         }
156         if(!samepoint && !samedir) {
157             vec[outpos++] = vec[pos++];
158         } else {
159             pos++; // skip
160         }
161     }
162
163     return vec;
164 }
165
166 static void shear_svp(ArtSVP*svp, double v)
167 {
168     /* do a "shearing on the polygon. We do this to eliminate all
169        horizontal lines (which libart can't handle properly, even though
170        it tries). */
171
172     int t;
173     for(t=0;t<svp->n_segs;t++) {
174         ArtSVPSeg* seg = &svp->segs[t];
175         int s;
176         for(s=0;s<seg->n_points;s++) {
177             ArtPoint* point = &seg->points[s];
178             point->y += point->x*v;
179         }
180     }
181 }
182
183 static double find_shear_value(ArtSVP*svp)
184 {
185     /* We try random values until we find one
186        where there's no slope below a given value, or if that fails,
187        at least no slope of 0 */
188
189     double v = 0;
190     int tries = 0;
191     while(1) {
192         char fail = 0;
193         int t;
194         for(t=0;t<svp->n_segs;t++) {
195             ArtSVPSeg* seg = &svp->segs[t];
196             int s;
197             for(s=0;s<seg->n_points-1;s++) {
198                 ArtPoint* p1 = &seg->points[s];
199                 ArtPoint* p2 = &seg->points[s+1];
200                 double dx = p2->x - p1->x;
201                 double dy = p2->y - p1->y;
202                 dy += dx*v;
203                 if(dy==0) {
204                     fail = 1;
205                     break;
206                 }
207                 if(tries<100 && dx && fabs(dy / dx) < 1e-5) {
208                     fail = 1;
209                     break;
210                 }
211             }
212             if(fail) 
213                 break;
214         }
215         if(!fail) 
216             break;
217         v = lrand48() / 2000000000.0;
218         tries++;
219     }
220     return v;
221 }
222
223 void show_path(ArtSVP*path)
224 {
225     int t;
226     printf("Segments: %d\n", path->n_segs);
227     for(t=0;t<path->n_segs;t++) {
228         ArtSVPSeg* seg = &path->segs[t];
229         printf("Segment %d: %d points, %s, BBox: (%f,%f,%f,%f)\n", 
230                 t, seg->n_points, seg->dir==0?"UP  ":"DOWN",
231                 seg->bbox.x0, seg->bbox.y0, seg->bbox.x1, seg->bbox.y1);
232         int p;
233         for(p=0;p<seg->n_points;p++) {
234             ArtPoint* point = &seg->points[p];
235             printf("        (%f,%f)\n", point->x, point->y);
236         }
237     }
238     printf("\n");
239 }
240
241 void check_svp(ArtSVP*svp)
242 {
243     int t;
244     for(t=0;t<svp->n_segs;t++) {
245         ArtSVPSeg* seg = &svp->segs[t];
246         int p;
247         for(p=0;p<seg->n_points-1;p++) {
248             ArtPoint* p1 = &seg->points[p];
249             ArtPoint* p2 = &seg->points[p+1];
250             if(p1->y > p2->y) {
251                 fprintf(stderr, "bad svp, y in seg %08x is non-increasing\n");
252                 exit(0);
253             }
254         }
255     }
256 }
257
258 void write_svp_postscript(const char*filename, ArtSVP*svp)
259 {
260     printf("writing %s\n", filename);
261     FILE*fi = fopen(filename, "wb");
262     int i, j;
263     double xmin=0,ymin=0,xmax=0,ymax=0;
264     fprintf(fi, "%% begin\n");
265     for (i = 0; i < svp->n_segs; i++) {
266         for (j = 0; j < svp->segs[i].n_points; j++) {
267             double x = svp->segs[i].points[j].x;
268             double y = svp->segs[i].points[j].y;
269             if(i==0 && j==0) {
270                 xmin = xmax = x;
271                 ymin = ymax = y;
272             } else {
273                 if(x < xmin) xmin = x;
274                 if(x > xmax) xmax = x;
275                 if(y < ymin) ymin = y;
276                 if(y > ymax) ymax = y;
277             }
278         }
279     }
280     if(xmax == xmin) xmax=xmin+1;
281     if(ymax == ymin) ymax=ymin+1;
282
283     for (i = 0; i < svp->n_segs; i++)
284       {
285         fprintf(fi, "%g setgray\n", svp->segs[i].dir ? 0.7 : 0);
286         for (j = 0; j < svp->segs[i].n_points; j++)
287           {
288             //fprintf(fi, "%g %g %s\n",
289             //        20 + 550*(svp->segs[i].points[j].x-xmin)/(xmax-xmin),
290             //        820 - 800*(svp->segs[i].points[j].y-ymin)/(ymax-ymin),
291             //        j ? "lineto" : "moveto");
292             fprintf(fi, "%.32f %.32f %s\n",
293                     svp->segs[i].points[j].x,
294                     svp->segs[i].points[j].y,
295                     j ? "lineto" : "moveto");
296           }
297         fprintf(fi, "stroke\n");
298       }
299
300     fprintf(fi, "showpage\n");
301     fclose(fi);
302 }
303
304 void write_vpath_postscript(const char*filename, ArtVpath*path)
305 {
306     FILE*fi = fopen(filename, "wb");
307     int i, j;
308     double xmin=0,ymin=0,xmax=0,ymax=0;
309     fprintf(fi, "%% begin\n");
310     ArtVpath*p = path;
311     char first = 1;
312     while(p->code != ART_END) {
313         if(p->code == ART_MOVETO || p->code == ART_MOVETO_OPEN) {
314             if(!first)
315                 fprintf(fi, "stroke\n");
316             first = 0;
317             fprintf(fi, "0.0 setgray\n");
318             fprintf(fi, "%.32f %.32f moveto\n", p->x, p->y);
319         } else {
320             fprintf(fi, "%.32f %.32f lineto\n", p->x, p->y);
321         }
322         p++;
323     }
324     if(!first)
325         fprintf(fi, "stroke\n");
326     fprintf(fi, "showpage\n");
327     fclose(fi);
328 }
329
330 void write_gfxline_postscript(const char*filename, gfxline_t*line)
331 {
332     FILE*fi = fopen(filename, "wb");
333     int i, j;
334     fprintf(fi, "%% begin\n");
335     char first = 1;
336     while(line) {
337         if(line->type == gfx_moveTo) {
338             if(!first)
339                 fprintf(fi, "stroke\n");
340             first = 0;
341             fprintf(fi, "0.0 setgray\n");
342             fprintf(fi, "%.32f %.32f moveto\n", line->x, line->y);
343         } else {
344             fprintf(fi, "%.32f %.32f lineto\n", line->x, line->y);
345         }
346         line = line->next;
347     }
348     if(!first)
349         fprintf(fi, "stroke\n");
350     fprintf(fi, "showpage\n");
351     fclose(fi);
352 }
353
354 static int vpath_len(ArtVpath*svp)
355 {
356     int len = 0;
357     while(svp->code != ART_END) {
358         svp ++;
359         len ++;
360     }
361     return len;
362 }
363
364 int gfxline_len(gfxline_t*line)
365 {
366     gfxline_t*i = line;
367     int len = 0;
368     while(i) {
369         len ++;
370         i = i->next;
371     }
372     return len;
373 }
374
375 static ArtVpath*pvpath= 0;
376 static int cmppos(const void*_p1, const void*_p2)
377 {
378     int*p1 = (int*)_p1;
379     int*p2 = (int*)_p2;
380     ArtVpath*v1 = &pvpath[*p1]; 
381     ArtVpath*v2 = &pvpath[*p2]; 
382     if(v1->y < v2->y)
383         return -1;
384     else if(v1->y > v2->y)
385         return 1;
386     else if(v1->x < v2->x)
387         return -2;
388     else if(v1->x > v2->x)
389         return 2;
390     else 
391         return 0;
392 }
393
394 #define PERTURBATION 2e-3
395 static void my_perturb(ArtVpath*vpath)
396 {
397     int t;
398     int len = vpath_len(vpath);
399     int*pos = (int*)malloc(len*sizeof(int));
400     for(t=0;t<len;t++)
401         pos[t] = t;
402     pvpath = vpath;
403     qsort(pos, len, sizeof(int), cmppos);
404     t=0;
405     while(t<len) {
406         int t2=t+1;
407         while(t2<len && !cmppos(&pos[t],&pos[t2])) {
408             t2++;
409         }
410
411         double dx = (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
412         double dy = (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
413         int s;
414         for(s=t;s<t2;s++) {
415             vpath[pos[s]].x += dx;
416             vpath[pos[s]].y += dy;
417         }
418         t = t2;
419     }
420     free(pos);
421     pvpath = 0;
422 }
423
424 static ArtSVP* gfxfillToSVP(gfxline_t*line, int perturb)
425 {
426     ArtVpath* vec = gfxline_to_ArtVpath(line, 1);
427     msg("<verbose> Casting gfxline of %d segments (%d line segments) to a gfxpoly", gfxline_len(line), vpath_len(vec));
428
429     if(perturb) {
430         //ArtVpath* vec2 = art_vpath_perturb(vec);
431         //free(vec);
432         //vec = vec2;
433         my_perturb(vec);
434     }
435     ArtSVP *svp = art_svp_from_vpath(vec);
436     free(vec);
437
438     return svp;
439 }
440
441 ArtSVP* run_intersector(ArtSVP*svp, ArtWindRule rule)
442 {
443     ArtSvpWriter * swr = art_svp_writer_rewind_new(rule);
444
445 #ifdef SHEAR
446     double shear = find_shear_value(svp);
447     gfxline_t*line =  gfxpoly_to_gfxline((gfxpoly_t*)svp);
448     gfxline_t*l = line;
449     while(l) {
450         l->y += l->x*shear;
451         l->sy += l->sx*shear;
452         l= l->next;
453     }
454     svp = (ArtSVP*)gfxpoly_fillToPoly(line);
455     printf("shearing svp by %.16f\n", shear);
456 #endif
457
458     art_svp_intersector(svp, swr);
459     ArtSVP*result = art_svp_writer_rewind_reap(swr);
460     check_svp(result);
461
462 #ifdef SHEAR
463     art_svp_free(svp);
464     shear_svp(result, -shear);
465 #endif
466
467     return result;
468 }
469
470
471 gfxline_t* gfxpoly_to_gfxline(gfxpoly_t*poly)
472 {
473     ArtSVP*svp = (ArtSVP*)poly;
474     int size = 0;
475     int t;
476     int pos = 0;
477
478     msg("<verbose> Casting polygon of %d segments back to gfxline", svp->n_segs);
479     
480     for(t=0;t<svp->n_segs;t++) {
481         size += svp->segs[t].n_points;
482     }
483     size = size + 1;
484     gfxline_t* lines = (gfxline_t*)rfx_alloc(sizeof(gfxline_t)*size);
485
486     for(t=0;t<svp->n_segs;t++) {
487         ArtSVPSeg* seg = &svp->segs[t];
488         int p;
489         for(p=0;p<seg->n_points;p++) {
490             lines[pos].type = p==0?gfx_moveTo:gfx_lineTo;
491             ArtPoint* point = &seg->points[p];
492             lines[pos].x = point->x;
493             lines[pos].y = point->y;
494             lines[pos].next = &lines[pos+1];
495             pos++;
496         }
497     }
498     if(pos) {
499         lines[pos-1].next = 0;
500         return lines;
501     } else {
502         return 0;
503     }
504 }
505
506 gfxpoly_t* gfxpoly_fillToPoly(gfxline_t*line)
507 {
508     /* I'm not sure whether doing perturbation here is actually
509        a good idea- if that line has been run through the machine
510        several times already, it might be safer to leave it alone,
511        since it should already be in a format libart can handle */
512 #ifdef PERTURBATE
513     ArtSVP* svp = gfxfillToSVP(line, 1);
514 #else
515     ArtSVP* svp = gfxfillToSVP(line, 0);
516 #endif
517
518 #ifdef DEBUG
519     char filename[80];
520     static int counter = 0;
521     sprintf(filename, "svp%d.ps", counter);
522     write_svp_postscript(filename, svp);
523     sprintf(filename, "gfxline%d.ps", counter);
524     write_gfxline_postscript(filename, line);
525 #endif
526
527     /* we do xor-filling by default, so dir is always 1 
528        (actually for oddeven rewinding it makes no difference, but
529         it's "cleaner")
530      */
531     int t;
532     for(t=0; t<svp->n_segs; t++) {
533         svp->segs[t].dir = 1;
534     }
535             
536     /* for some reason, we need to rewind / self-intersect the polygons that gfxfillToSVP
537        returns- art probably uses a different fill mode (circular?) for vpaths */
538     ArtSVP*svp_uncrossed=0;
539    
540 #ifdef DEBUG
541     sprintf(filename, "svp%d_in.ps", counter);
542     write_svp_postscript(filename, svp);
543     counter++;
544 #endif
545
546     svp_uncrossed = run_intersector(svp, ART_WIND_RULE_ODDEVEN);
547
548     art_svp_free(svp);
549     svp=svp_uncrossed;
550
551     return (gfxpoly_t*)svp;
552 }
553
554 gfxpoly_t* gfxpoly_intersect(gfxpoly_t*poly1, gfxpoly_t*poly2)
555 {
556     ArtSvpWriter *swr;
557     
558     static int counter = 0;
559     
560     ArtSVP* svp1 = (ArtSVP*)poly1;
561     ArtSVP* svp2 = (ArtSVP*)poly2;
562     msg("<verbose> Intersecting two polygons of %d and %d segments", svp1->n_segs, svp2->n_segs);
563     
564 #ifdef DEBUG
565     char filename[80];
566     sprintf(filename, "isvp%d_src1.ps", counter);
567     write_svp_postscript(filename, svp1);
568     sprintf(filename, "isvp%d_src2.ps", counter);
569     write_svp_postscript(filename, svp2);
570 #endif
571     
572     ArtSVP* svp3 = art_svp_merge (svp1, svp2);
573
574 #ifdef DEBUG
575     sprintf(filename, "isvp%d_src.ps", counter);
576     write_svp_postscript(filename, svp3);
577 #endif
578
579     //write_svp_postscript("svp.ps", svp3);
580     ArtSVP*svp_new = run_intersector(svp3, ART_WIND_RULE_INTERSECT);
581
582     art_free (svp3); /* shallow free because svp3 contains shared segments */
583
584 #ifdef DEBUG
585     sprintf(filename, "isvp%d.ps", counter);
586     write_svp_postscript(filename, svp_new);
587 #endif
588
589     counter++;
590     
591     //write_svp_postscript("svp_new.ps", svp_new);
592         
593     return (gfxpoly_t*)svp_new;
594 }
595
596 gfxpoly_t* gfxpoly_union(gfxpoly_t*poly1, gfxpoly_t*poly2)
597 {
598     ArtSVP* svp1 = (ArtSVP*)poly1;
599     ArtSVP* svp2 = (ArtSVP*)poly2;
600     msg("<verbose> Unifying two polygons of %d and %d segments", svp1->n_segs, svp2->n_segs);
601         
602     ArtSVP* svp = art_svp_union(svp1, svp2);
603     return (gfxpoly_t*)svp;
604 }
605
606 gfxpoly_t* gfxpoly_strokeToPoly(gfxline_t*line, gfxcoord_t width, gfx_capType cap_style, gfx_joinType joint_style, double miterLimit)
607 {
608     ArtVpath* vec = gfxline_to_ArtVpath(line, 0);
609     msg("<verbose> Casting gfxline of %d segments to a stroke-polygon", gfxline_len(line));
610
611     ArtVpath* vec2 = art_vpath_perturb(vec);
612     free(vec);
613     vec = vec2;
614
615     ArtSVP *svp = art_svp_vpath_stroke (vec,
616                         (joint_style==gfx_joinMiter)?ART_PATH_STROKE_JOIN_MITER:
617                         ((joint_style==gfx_joinRound)?ART_PATH_STROKE_JOIN_ROUND:
618                          ((joint_style==gfx_joinBevel)?ART_PATH_STROKE_JOIN_BEVEL:ART_PATH_STROKE_JOIN_BEVEL)),
619                         (cap_style==gfx_capButt)?ART_PATH_STROKE_CAP_BUTT:
620                         ((cap_style==gfx_capRound)?ART_PATH_STROKE_CAP_ROUND:
621                          ((cap_style==gfx_capSquare)?ART_PATH_STROKE_CAP_SQUARE:ART_PATH_STROKE_CAP_SQUARE)),
622                         width, //line_width
623                         miterLimit, //miter_limit
624                         0.05 //flatness
625                         );
626     free(vec);
627     return (gfxpoly_t*)svp;
628 }
629
630 gfxline_t* gfxline_circularToEvenOdd(gfxline_t*line)
631 {
632     msg("<verbose> Converting circular-filled gfxline of %d segments to even-odd filled gfxline", gfxline_len(line));
633     ArtSVP* svp = gfxfillToSVP(line, 1);
634
635     /* TODO: ART_WIND_RULE_POSITIVE means that a shape is visible if
636              positive and negative line segments add up to something positive.
637              I *think* that clockwise fill in PDF is defined in a way, however,
638              that the *last* shape's direction will determine whether something
639              is filled */
640     ArtSVP* svp_rewinded;
641     
642     svp_rewinded = run_intersector(svp, ART_WIND_RULE_POSITIVE);
643
644     gfxline_t* result = gfxpoly_to_gfxline((gfxpoly_t*)svp_rewinded);
645     art_svp_free(svp);
646     art_svp_free(svp_rewinded);
647     return result;
648 }
649
650 gfxpoly_t* gfxpoly_createbox(double x1, double y1,double x2, double y2)
651 {
652     ArtVpath *vec = art_new (ArtVpath, 5+1);
653     vec[0].code = ART_MOVETO;
654     vec[0].x = x1;
655     vec[0].y = y1;
656     vec[1].code = ART_LINETO;
657     vec[1].x = x1;
658     vec[1].y = y2;
659     vec[2].code = ART_LINETO;
660     vec[2].x = x2;
661     vec[2].y = y2;
662     vec[3].code = ART_LINETO;
663     vec[3].x = x2;
664     vec[3].y = y1;
665     vec[4].code = ART_LINETO;
666     vec[4].x = x1;
667     vec[4].y = y1;
668     vec[5].code = ART_END;
669     vec[5].x = 0;
670     vec[5].y = 0;
671     ArtSVP *svp = art_svp_from_vpath(vec);
672     free(vec);
673     return (gfxpoly_t*)svp;
674 }
675
676 void gfxpoly_free(gfxpoly_t*poly)
677 {
678     ArtSVP*svp = (ArtSVP*)poly;
679     art_svp_free(svp);
680 }