added a new stroke->polygon conversion function
[swftools.git] / lib / gfxpoly / stroke.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <math.h>
4 #include <assert.h>
5 #include "../gfxdevice.h"
6 #include "../gfxtools.h"
7
8 /* notice: left/right for a coordinate system where y goes up, not down */
9 typedef enum {LEFT=0, RIGHT=1} leftright_t;
10
11 /* factor that determines into how many line fragments a spline is converted */
12 #define SUBFRACTION (2.4)
13
14 // spline equation:
15 // s(t) = t*t*x2 + 2*t*(1-t)*cx + (1-t)*(1-t)*x1
16 //
17 // s(0.5) = 0.25*x2 + 0.5*cx + 0.25*x1
18 // ds(t)/dt = 2*t*x2 + (2-2t)*cx + (2t-2)*x1
19 // ds(0) = 2*(cx-x1)
20
21 static void draw_arc(gfxdrawer_t*draw, double x, double y, double a1, double a2, double r)
22 {
23     if(a2<a1) a2+=M_PI*2;
24
25     double d = (a2-a1);
26     int steps = ceil(8*d/(M_PI*2)); // we use 8 splines for a full circle
27     if(!steps) return;
28
29     int t;
30     double step = (a2-a1)/steps;
31     double lastx = x+cos(a1)*r;
32     double lasty = y+sin(a1)*r;
33
34     /* we could probably build a table for this- there are only 8
35        possible values for step */
36     double r2 = r*(2-sqrt(0.5+0.5*cos(step)));
37
38     for(t=1;t<=steps;t++) {
39         double a = a1 + t*step;
40         double c = cos(a)*r;
41         double s = sin(a)*r;
42         double xx = c + x;
43         double yy = s + y;
44         //double dx = (s*step/2 + lastx);
45         //double dy = (-c*step/2 + lasty);
46         double dx = x + cos(a-step/2)*r2;
47         double dy = y + sin(a-step/2)*r2;
48         //draw->lineto(draw, xx, yy);
49         draw->splineTo(draw, dx, dy, xx, yy);
50         lastx = xx;
51         lasty = yy;
52     }
53 }
54
55 static void draw_single_stroke(gfxpoint_t*p, int num, gfxdrawer_t*draw, double width, gfx_capType cap, gfx_joinType join, double limit)
56 {
57     char do_draw=0;
58     leftright_t lastdir = LEFT;
59     int start = 0;
60     int end = num-1;
61     int incr = 1;
62     int pos = 0;
63
64     width/=2;
65     if(width<=0) 
66         width = 0.05;
67
68     /* remove duplicate points */
69     int s=1,t;
70     for(t=1;t<num;t++) {
71         p[s] = p[t];
72         if(p[t].x != p[t-1].x || p[t].y != p[t-1].y) {
73             s++;
74         } else {
75             num--;
76         }
77     }
78
79     double alimit = atan(limit / width);
80
81     /* iterate through the points two times: first forward, then backward,
82        adding a stroke outline to the right side and line caps after each
83        pass */
84     int pass;
85     for(pass=0;pass<2;pass++) {
86         int pos;
87         double lastw=0;
88         for(pos=start;pos!=end;pos+=incr) {
89             //printf("%d) %.2f %.2f\n", pos, p[pos].x, p[pos].y);
90             double dx = p[pos+incr].x - p[pos].x;
91             double dy = p[pos+incr].y - p[pos].y;
92             double l = sqrt(dx*dx+dy*dy);
93             double w = atan2(dy,dx);
94             if(w<0) w+=M_PI*2;
95             
96             if(pos!=start) {
97                 double d = w-lastw;
98                 leftright_t turn;
99                 if(d>=0 && d<M_PI) turn=LEFT;
100                 else if(d<0 && d>-M_PI) turn=RIGHT;
101                 else if(d>=M_PI) {turn=RIGHT;}
102                 else if(d<=-M_PI) {turn=LEFT;d+=M_PI*2;}
103                 else {assert(0);}
104                 if(turn!=LEFT || join==gfx_joinBevel) {
105                     /* TODO: does a bevel join extend beyond the segment (i.e.,
106                        is it like a square cap or like a butt cap? */
107                 } else if(join==gfx_joinRound) {
108                     draw_arc(draw, p[pos].x, p[pos].y, lastw-M_PI/2, w-M_PI/2, width);
109                 } else if(join==gfx_joinMiter) {
110                     if(d/2<alimit) {
111                         double r2 = width*(1-sin(d/2)+tan(d/2));
112                         double addx = cos(lastw-M_PI/2+d/2)*r2;
113                         double addy = sin(lastw-M_PI/2+d/2)*r2;
114                         draw->lineTo(draw, p[pos].x+addx, p[pos].y+addy);
115                     } else {
116                         /* convert to bevel join, which always looks the same (is
117                            independent of miterLimit TODO: verify this */
118                     }
119                 }
120             }
121
122             double addx = cos(w-M_PI/2)*width;
123             double addy = sin(w-M_PI/2)*width;
124             draw->lineTo(draw, p[pos].x+addx, p[pos].y+addy);
125             //printf("-- %.2f %.2f (angle:%d)\n", px1, py1, (int)(180*w/M_PI));
126             double px2 = p[pos+incr].x + addx;
127             double py2 = p[pos+incr].y + addy;
128             //printf("-- %.2f %.2f (angle:%d)\n", px2, py2, (int)(180*w/M_PI));
129             draw->lineTo(draw, p[pos+incr].x+addx, p[pos+incr].y+addy);
130
131             lastw = w;
132         }
133         /* draw stroke ends */
134         if(cap == gfx_capButt) {
135             double c = cos(lastw-M_PI/2)*width;
136             double s = sin(lastw-M_PI/2)*width;
137             draw->lineTo(draw, p[pos].x-c, p[pos].y-s);
138         } else if(cap == gfx_capRound) {
139             draw_arc(draw, p[pos].x, p[pos].y, lastw-M_PI/2, lastw+M_PI/2, width);
140         } else if(cap == gfx_capSquare) {
141             double c = cos(lastw-M_PI/2)*width;
142             double s = sin(lastw-M_PI/2)*width;
143             draw->lineTo(draw, p[pos].x+c-s, p[pos].y+s+c);
144             draw->lineTo(draw, p[pos].x-c-s, p[pos].y-s+c);
145             draw->lineTo(draw, p[pos].x-c, p[pos].y-s);
146         }
147         start=num-1;
148         end=0;
149         incr=-1;
150     }
151 }
152
153 static void draw_stroke(gfxline_t*start, gfxdrawer_t*draw, double width, gfx_capType cap, gfx_joinType join, double miterLimit)
154 {
155     if(!start) 
156         return;
157     assert(start->type == gfx_moveTo);
158     gfxline_t*line = start;
159     // measure array size
160     int size = 0;
161     int pos = 0;
162     double lastx,lasty;
163     while(line) {
164         if(line->type == gfx_moveTo) {
165             if(pos>size) size = pos;
166             pos++;
167         } else if(line->type == gfx_lineTo) {
168             pos++;
169         } else if(line->type == gfx_splineTo) {
170             int parts = (int)(sqrt(fabs(line->x-2*line->sx+lastx) + fabs(line->y-2*line->sy+lasty))*SUBFRACTION);
171             if(!parts) parts = 1;
172             pos+=parts+1;
173         }
174         lastx = line->x;
175         lasty = line->y;
176         line = line->next;
177     }
178     if(pos>size) size = pos;
179     if(!size) return;
180
181     gfxpoint_t* points = malloc(sizeof(gfxpoint_t)*size);
182     line = start;
183     pos = 0;
184     while(line) {
185         if(line->type == gfx_moveTo) {
186             if(pos) draw_single_stroke(points, pos, draw, width, cap, join, miterLimit);
187             pos = 0;
188         } else if(line->type == gfx_splineTo) {
189             int parts = (int)(sqrt(fabs(line->x-2*line->sx+lastx) + fabs(line->y-2*line->sy+lasty))*SUBFRACTION);
190             if(!parts) parts = 1;
191             double stepsize = 1.0/parts;
192             int i;
193             for(i=0;i<parts;i++) {
194                 double t = (double)i*stepsize;
195                 points[pos].x = (line->x*t*t + 2*line->sx*t*(1-t) + lastx*(1-t)*(1-t));
196                 points[pos].y = (line->y*t*t + 2*line->sy*t*(1-t) + lasty*(1-t)*(1-t));
197                 pos++;
198             }
199         }
200         lastx = points[pos].x = line->x;
201         lasty = points[pos].y = line->y;
202         pos++;
203         line = line->next;
204     }
205     if(pos) draw_single_stroke(points, pos, draw, width, cap, join, miterLimit);
206     free(points);
207 }
208
209 int main()
210 {
211     gfxline_t l[4];
212     l[0].type = gfx_moveTo;
213     l[0].x = 100;l[0].sx=2;
214     l[0].y = 100;l[0].sy=2;
215     l[0].next = &l[1];
216     l[1].type = gfx_lineTo;
217     l[1].x = 100;l[1].sx=2;
218     l[1].y = 200;l[1].sy=-2;
219     l[1].next = &l[2];
220     l[2].type = gfx_lineTo;
221     l[2].x = 250;l[2].sx=4;
222     l[2].y = 200;l[2].sy=0;
223     l[2].next = &l[3];
224     l[3].type = gfx_lineTo;
225     l[3].x = 200;l[3].sx=0;
226     l[3].y = 150;l[3].sy=4;
227     l[3].next = 0;
228
229
230     gfxdevice_t dev;
231     gfxdevice_swf_init(&dev);
232     dev.setparameter(&dev, "framerate", "25.0");
233     int t;
234     for(t=0;t<300;t++) {
235         dev.startpage(&dev, 700,700);
236         gfxline_t*g = l;
237         while(g) {
238             g->x += g->sx;
239             g->y += g->sy;
240             if(g->x<200) {g->x=400-g->x;g->sx=-g->sx;}
241             if(g->y<200) {g->y=400-g->y;g->sy=-g->sy;}
242             if(g->x>500) {g->x=1000-g->x;g->sx=-g->sx;}
243             if(g->y>500) {g->y=1000-g->y;g->sy=-g->sy;}
244             g = g->next;
245         }
246         gfxdrawer_t d;
247         gfxdrawer_target_gfxline(&d);
248         double width = t/3.0;
249         if(width>50) width=100-width;
250
251         draw_stroke(l, &d, width, gfx_capRound, gfx_joinBevel, 500);
252         gfxline_t*line = (gfxline_t*)d.result(&d);
253         //gfxline_dump(line, stdout, "");
254
255         gfxcolor_t black = {255,0,0,0};
256         gfxcolor_t cyan = {255,0,128,128};
257         dev.stroke(&dev, l, 2, &black, gfx_capRound, gfx_joinRound, 0);
258         dev.stroke(&dev, line, 2, &cyan, gfx_capRound, gfx_joinRound, 0);
259         gfxline_free(line);
260         dev.endpage(&dev);
261     }
262
263     gfxresult_t* result = dev.finish(&dev);
264     result->save(result, "test.swf");
265     result->destroy(result);
266 }