implemented gradients
[swftools.git] / lib / devices / render.c
1 /* render.c
2
3    Part of the swftools package.
4
5    Copyright (c) 2005/2006/2007 Matthias Kramm <kramm@quiss.org> 
6  
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
20
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <math.h>
24 #include <memory.h>
25 #include "../gfxdevice.h"
26 #include "../gfxtools.h"
27 #include "../mem.h"
28 #define PNG_INLINE_EXPORTS
29 #include "../types.h"
30 #include "../png.c"
31 #include "../log.h"
32 #include "render.h"
33
34 typedef gfxcolor_t RGBA;
35
36 typedef struct _renderpoint
37 {
38     float x;
39 } renderpoint_t;
40
41 typedef struct _renderline
42 {
43     renderpoint_t*points;
44     int size;
45     int num;
46 } renderline_t;
47
48 typedef struct _internal_result {
49     gfximage_t img;
50     struct _internal_result*next;
51 } internal_result_t;
52
53 typedef struct _clipbuffer {
54     U32*data;
55     struct _clipbuffer*next;
56 } clipbuffer_t;
57
58 typedef struct _internal {
59     int width;
60     int height;
61     int width2;
62     int height2;
63     int bitwidth;
64     int multiply;
65     int antialize;
66     int zoom;
67     int ymin, ymax;
68     int fillwhite;
69
70     RGBA* img;
71
72     clipbuffer_t*clipbuf;
73
74     renderline_t*lines;
75
76     internal_result_t*results;
77     internal_result_t*result_next;
78 } internal_t;
79
80 typedef enum {filltype_solid,filltype_clip,filltype_bitmap,filltype_gradient} filltype_t;
81
82 typedef struct _fillinfo {
83     filltype_t type;
84     gfxcolor_t*color;
85     gfximage_t*image;
86     gfxmatrix_t*matrix;
87     gfxcxform_t*cxform;
88     RGBA*gradient;
89     char clip_or_radial;
90 } fillinfo_t;
91
92
93 static inline void add_pixel(internal_t*i, float x, int y)
94 {
95     renderpoint_t p;
96
97     if(x >= i->width2 || y >= i->height2 || y<0) return;
98     p.x = x;
99     if(y<i->ymin) i->ymin = y;
100     if(y>i->ymax) i->ymax = y;
101
102     renderline_t*l = &i->lines[y];
103
104     if(l->num == l->size) {
105         l->size += 32;
106         l->points = (renderpoint_t*)rfx_realloc(l->points, l->size * sizeof(renderpoint_t));
107     }
108     l->points[l->num] = p;
109     l->num++;
110 }
111
112 /* set this to 0.777777 or something if the "both fillstyles set while not inside shape"
113    problem appears to often */
114 #define CUT 0.5
115
116 #define INT(x) ((int)((x)+16)-16)
117
118 static void add_line(gfxdevice_t*dev , double x1, double y1, double x2, double y2)
119 {
120     internal_t*i = (internal_t*)dev->internal;
121     double diffx, diffy;
122     double ny1, ny2, stepx;
123 /*    if(DEBUG&4) {
124         int l = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
125         printf(" l[%d - %.2f/%.2f -> %.2f/%.2f]\n", l, x1/20.0, y1/20.0, x2/20.0, y2/20.0);
126     }*/
127
128     if(y2 < y1) {
129         double x;
130         double y;
131         x = x1;x1 = x2;x2=x;
132         y = y1;y1 = y2;y2=y;
133     }
134     
135     diffx = x2 - x1;
136     diffy = y2 - y1;
137     
138     ny1 = INT(y1)+CUT;
139     ny2 = INT(y2)+CUT;
140
141     if(ny1 < y1) {
142         ny1 = INT(y1) + 1.0 + CUT;
143     }
144     if(ny2 >= y2) {
145         ny2 = INT(y2) - 1.0 + CUT;
146     }
147
148     if(ny1 > ny2)
149         return;
150
151     stepx = diffx/diffy;
152     x1 = x1 + (ny1-y1)*stepx;
153     x2 = x2 + (ny2-y2)*stepx;
154
155     {
156         int posy=INT(ny1);
157         int endy=INT(ny2);
158         double posx=0;
159         double startx = x1;
160
161         while(posy<=endy) {
162             float xx = (float)(startx + posx);
163             add_pixel(i, xx ,posy);
164             posx+=stepx;
165             posy++;
166         }
167     }
168 }
169 #define PI 3.14159265358979
170 static void add_solidline(gfxdevice_t*dev, double x1, double y1, double x2, double y2, double width)
171 {
172     /* TODO: handle cap styles */
173
174     internal_t*i = (internal_t*)dev->internal;
175
176     double dx = x2-x1;
177     double dy = y2-y1;
178     double sd;
179     double d;
180
181     int t;
182     int segments;
183     double lastx,lasty;
184     double vx,vy;
185     double xx,yy;
186   
187     /* Make sure the line is always at least one pixel wide */
188 #ifdef LINEMODE1
189     /* That's what Macromedia's Player does at least at zoom level >= 1.  */
190     width += 1.0;
191 #else
192     /* That's what Macromedia's Player seems to do at zoom level 0.  */
193     /* TODO: needs testing */
194
195     /* TODO: how does this interact with scaling? */
196     if(width * i->multiply < 1.0)
197         width = 1.0 / i->multiply;
198 #endif
199
200     sd = (double)dx*(double)dx+(double)dy*(double)dy;
201     d = sqrt(sd);
202
203     if(!dx && !dy) {
204         vx = 1;
205         vy = 0;
206     } else {
207         vx = ( dy/d);
208         vy = (-dx/d);
209     }
210
211     segments = width/2;
212     if(segments < 2)
213         segments = 2;
214
215     segments = 8;
216
217     vx=vx*width*0.5;
218     vy=vy*width*0.5;
219
220     xx = x2+vx;
221     yy = y2+vy;
222     add_line(dev, x1+vx, y1+vy, xx, yy);
223     lastx = xx;
224     lasty = yy;
225     for(t=1;t<segments;t++) {
226         double s = sin(t*PI/segments);
227         double c = cos(t*PI/segments);
228         xx = (x2 + vx*c - vy*s);
229         yy = (y2 + vx*s + vy*c);
230         add_line(dev, lastx, lasty, xx, yy);
231         lastx = xx;
232         lasty = yy;
233     }
234     
235     xx = (x2-vx);
236     yy = (y2-vy);
237     add_line(dev, lastx, lasty, xx, yy);
238     lastx = xx;
239     lasty = yy;
240     xx = (x1-vx);
241     yy = (y1-vy);
242     add_line(dev, lastx, lasty, xx, yy);
243     lastx = xx;
244     lasty = yy;
245     for(t=1;t<segments;t++) {
246         double s = sin(t*PI/segments);
247         double c = cos(t*PI/segments);
248         xx = (x1 - vx*c + vy*s);
249         yy = (y1 - vx*s - vy*c);
250         add_line(dev, lastx, lasty, xx, yy);
251         lastx = xx;
252         lasty = yy;
253     }
254     add_line(dev, lastx, lasty, (x1+vx), (y1+vy));
255 }
256
257 static int compare_renderpoints(const void * _a, const void * _b)
258 {
259     renderpoint_t*a = (renderpoint_t*)_a;
260     renderpoint_t*b = (renderpoint_t*)_b;
261     if(a->x < b->x) return -1;
262     if(a->x > b->x) return 1;
263     return 0;
264 }
265
266 static void fill_line_solid(RGBA*line, U32*z, int y, int x1, int x2, RGBA col)
267 {
268     int x = x1;
269
270     U32 bit = 1<<(x1&31);
271     int bitpos = (x1/32);
272
273     if(col.a!=255) {
274         int ainv = 255-col.a;
275         col.r = (col.r*col.a)>>8;
276         col.g = (col.g*col.a)>>8;
277         col.b = (col.b*col.a)>>8;
278         do {
279             if(z[bitpos]&bit) {
280                 line[x].r = ((line[x].r*ainv)>>8)+col.r;
281                 line[x].g = ((line[x].g*ainv)>>8)+col.g;
282                 line[x].b = ((line[x].b*ainv)>>8)+col.b;
283                 //line[x].a = 255;
284                 line[x].a = ((line[x].a*ainv)>>8)+col.a;
285             }
286             bit <<= 1;
287             if(!bit) {
288                 bit = 1;bitpos++;
289             }
290         } while(++x<x2);
291     } else {
292         do {
293             if(z[bitpos]&bit) {
294                 line[x] = col;
295             }
296             bit <<= 1;
297             if(!bit) {
298                 bit = 1;bitpos++;
299             }
300         } while(++x<x2);
301     }
302 }
303
304 static void fill_line_bitmap(RGBA*line, U32*z, int y, int x1, int x2, fillinfo_t*info)
305 {
306     int x = x1;
307
308     gfxmatrix_t*m = info->matrix;
309     gfximage_t*b = info->image;
310     
311     if(!b->width || !b->height) {
312         gfxcolor_t red = {255,255,0,0};
313         fill_line_solid(line, z, y, x1, x2, red);
314         return;
315     }
316     
317     double det = m->m00*m->m11 - m->m01*m->m10;
318     if(fabs(det) < 0.0005) { 
319         /* x direction equals y direction- the image is invisible */
320         return;
321     }
322     det = 1.0/det;
323     double xx1 =  (  (-m->tx) * m->m11 - (y - m->ty) * m->m10) * det;
324     double yy1 =  (- (-m->tx) * m->m01 + (y - m->ty) * m->m00) * det;
325     double xinc1 = m->m11 * det;
326     double yinc1 = m->m01 * det;
327     
328     U32 bit = 1<<(x1&31);
329     int bitpos = (x1/32);
330
331     do {
332         if(z[bitpos]&bit) {
333             RGBA col;
334             int xx = (int)(xx1 + x * xinc1);
335             int yy = (int)(yy1 - x * yinc1);
336             int ainv;
337
338             if(info->clip_or_radial) {
339                 if(xx<0) xx=0;
340                 if(xx>=b->width) xx = b->width-1;
341                 if(yy<0) yy=0;
342                 if(yy>=b->height) yy = b->height-1;
343             } else {
344                 xx %= b->width;
345                 yy %= b->height;
346                 if(xx<0) xx += b->width;
347                 if(yy<0) yy += b->height;
348             }
349
350             col = b->data[yy*b->width+xx];
351             ainv = 255-col.a;
352
353             /* needs bitmap with premultiplied alpha */
354             line[x].r = ((line[x].r*ainv)>>8)+col.r;
355             line[x].g = ((line[x].g*ainv)>>8)+col.g;
356             line[x].b = ((line[x].b*ainv)>>8)+col.b;
357             line[x].a = 255;
358         }
359         bit <<= 1;
360         if(!bit) {
361             bit = 1;bitpos++;
362         }
363     } while(++x<x2);
364 }
365
366 static void fill_line_gradient(RGBA*line, U32*z, int y, int x1, int x2, fillinfo_t*info)
367 {
368     int x = x1;
369
370     gfxmatrix_t*m = info->matrix;
371     RGBA*g= info->gradient;
372     
373     double det = m->m00*m->m11 - m->m01*m->m10;
374     if(fabs(det) < 0.0005) { 
375         /* x direction equals y direction */
376         return;
377     }
378     det = 1.0/det;
379     double xx1 =  (  (-m->tx) * m->m11 - (y - m->ty) * m->m10) * det;
380     double yy1 =  (- (-m->tx) * m->m01 + (y - m->ty) * m->m00) * det;
381     double xinc1 = m->m11 * det;
382     double yinc1 = m->m01 * det;
383     
384     U32 bit = 1<<(x1&31);
385     int bitpos = (x1/32);
386
387     do {
388         if(z[bitpos]&bit) {
389             RGBA col;
390             int ainv;
391
392             int pos = 0;
393             if(info->clip_or_radial) {
394                 double xx = xx1 + x * xinc1;
395                 double yy = yy1 + y * yinc1;
396                 double r = sqrt(xx*xx + yy*yy);
397                 if(r>1) r = 1;
398                 pos = (int)(r*255.999);
399             } else {
400                 double r = xx1 + x * xinc1;
401                 if(r>1) r = 1;
402                 if(r<-1) r = -1;
403                 pos = (int)((r+1)*127.999);
404             }
405             col = g[pos];
406             ainv = 255-col.a;
407
408             /* needs bitmap with premultiplied alpha */
409             line[x].r = ((line[x].r*ainv)>>8)+col.r;
410             line[x].g = ((line[x].g*ainv)>>8)+col.g;
411             line[x].b = ((line[x].b*ainv)>>8)+col.b;
412             line[x].a = 255;
413         }
414         bit <<= 1;
415         if(!bit) {
416             bit = 1;bitpos++;
417         }
418     } while(++x<x2);
419 }
420
421 static void fill_line_clip(RGBA*line, U32*z, int y, int x1, int x2)
422 {
423     int x = x1;
424
425     U32 bit = 1<<(x1&31);
426     int bitpos = (x1/32);
427
428     do {
429         z[bitpos]|=bit;
430         bit <<= 1;
431         if(!bit) {
432             bit = 1;bitpos++;
433         }
434     } while(++x<x2);
435 }
436
437 void fill_line(gfxdevice_t*dev, RGBA*line, U32*zline, int y, int startx, int endx, fillinfo_t*fill)
438 {
439     if(fill->type == filltype_solid)
440         fill_line_solid(line, zline, y, startx, endx, *fill->color);
441     else if(fill->type == filltype_clip)
442         fill_line_clip(line, zline, y, startx, endx);
443     else if(fill->type == filltype_bitmap)
444         fill_line_bitmap(line, zline, y, startx, endx, fill);
445     else if(fill->type == filltype_gradient)
446         fill_line_gradient(line, zline, y, startx, endx, fill);
447 }
448
449 void fill(gfxdevice_t*dev, fillinfo_t*fill)
450 {
451     internal_t*i = (internal_t*)dev->internal;
452     int y;
453     U32 clipdepth = 0;
454     for(y=i->ymin;y<=i->ymax;y++) {
455         renderpoint_t*points = i->lines[y].points;
456         RGBA*line = &i->img[i->width2*y];
457         U32*zline = &i->clipbuf->data[i->bitwidth*y];
458
459         int n;
460         int num = i->lines[y].num;
461         int lastx;
462         qsort(points, num, sizeof(renderpoint_t), compare_renderpoints);
463
464         for(n=0;n<num;n++) {
465             renderpoint_t*p = &points[n];
466             renderpoint_t*next= n<num-1?&points[n+1]:0;
467             int startx = p->x;
468             int endx = next?next->x:i->width2;
469             if(endx > i->width2)
470                 endx = i->width2;
471             if(startx < 0)
472                 startx = 0;
473             if(endx < 0)
474                 endx = 0;
475
476             if(!(n&1))
477                 fill_line(dev, line, zline, y, startx, endx, fill);
478
479             lastx = endx;
480             if(endx == i->width2)
481                 break;
482         }
483         if(fill->type == filltype_clip) {
484             if(i->clipbuf->next) {
485                 U32*line2 = &i->clipbuf->next->data[i->bitwidth*y];
486                 int x;
487                 for(x=0;x<i->bitwidth;x++)
488                     zline[x] &= line2[x];
489             }
490         }
491
492         i->lines[y].num = 0;
493     }
494 }
495
496 void fill_solid(gfxdevice_t*dev, gfxcolor_t* color)
497 {
498     fillinfo_t info;
499     memset(&info, 0, sizeof(info));
500     info.type = filltype_solid;
501     info.color = color;
502     fill(dev, &info);
503 }
504
505 int render_setparameter(struct _gfxdevice*dev, const char*key, const char*value)
506 {
507     internal_t*i = (internal_t*)dev->internal;
508     if(!strcmp(key, "antialize") || !strcmp(key, "antialise")) {
509         i->antialize = atoi(value);
510         i->zoom = i->antialize * i->multiply;
511         return 1;
512     } else if(!strcmp(key, "multiply")) {
513         i->multiply = atoi(value);
514         i->zoom = i->antialize * i->multiply;
515         fprintf(stderr, "Warning: multiply not implemented yet\n");
516         return 1;
517     } else if(!strcmp(key, "fillwhite")) {
518         i->fillwhite = atoi(value);
519         return 1;
520     }
521     return 0;
522 }
523
524 void newclip(struct _gfxdevice*dev)
525 {
526     internal_t*i = (internal_t*)dev->internal;
527     
528     clipbuffer_t*c = (clipbuffer_t*)rfx_calloc(sizeof(clipbuffer_t));
529     c->data = (U32*)rfx_calloc(sizeof(U32) * i->bitwidth * i->height2);
530     c->next = i->clipbuf;
531     i->clipbuf = c;
532     if(c->next)
533         memcpy(c->data, c->next->data, i->bitwidth*i->height2);
534     else
535         memset(c->data, 0, sizeof(U32)*i->bitwidth*i->height2);
536 }
537
538 void endclip(struct _gfxdevice*dev, char removelast)
539 {
540     internal_t*i = (internal_t*)dev->internal;
541    
542     /* test for at least one cliplevel (the one we created ourselves) */
543     if(!i->clipbuf || (!i->clipbuf->next && !removelast)) {
544         fprintf(stderr, "endclip without any active clip buffers\n");
545         return;
546     }
547
548     clipbuffer_t*c = i->clipbuf;
549     i->clipbuf = i->clipbuf->next;
550     c->next = 0;
551     free(c->data);c->data = 0;
552     free(c);
553 }
554
555 void render_stroke(struct _gfxdevice*dev, gfxline_t*line, gfxcoord_t width, gfxcolor_t*color, gfx_capType cap_style, gfx_joinType joint_style, gfxcoord_t miterLimit)
556 {
557     internal_t*i = (internal_t*)dev->internal;
558     double x,y;
559     
560     /*if(cap_style != gfx_capRound || joint_style != gfx_joinRound) {
561         fprintf(stderr, "Warning: cap/joint style != round not yet supported\n");
562     }*/
563
564     while(line) {
565         if(line->type == gfx_moveTo) {
566         } else if(line->type == gfx_lineTo) {
567             double x1=x*i->zoom,y1=y*i->zoom;
568             double x3=line->x*i->zoom,y3=line->y*i->zoom;
569             add_solidline(dev, x1, y1, x3, y3, width * i->multiply);
570             fill_solid(dev, color);
571         } else if(line->type == gfx_splineTo) {
572             int t,parts;
573             double xx,yy;
574            
575             double x1=x*i->zoom,y1=y*i->zoom;
576             double x2=line->sx*i->zoom,y2=line->sy*i->zoom;
577             double x3=line->x*i->zoom,y3=line->y*i->zoom;
578             
579             double c = abs(x3-2*x2+x1) + abs(y3-2*y2+y1);
580             xx=x1;
581             yy=y1;
582
583             parts = (int)(sqrt(c)/3);
584             if(!parts) parts = 1;
585
586             for(t=1;t<=parts;t++) {
587                 double nx = (double)(t*t*x3 + 2*t*(parts-t)*x2 + (parts-t)*(parts-t)*x1)/(double)(parts*parts);
588                 double ny = (double)(t*t*y3 + 2*t*(parts-t)*y2 + (parts-t)*(parts-t)*y1)/(double)(parts*parts);
589                 
590                 add_solidline(dev, xx, yy, nx, ny, width * i->multiply);
591                 fill_solid(dev, color);
592                 xx = nx;
593                 yy = ny;
594             }
595         }
596         x = line->x;
597         y = line->y;
598         line = line->next;
599     }
600 }
601
602 static void draw_line(gfxdevice_t*dev, gfxline_t*line)
603 {
604     internal_t*i = (internal_t*)dev->internal;
605     double x,y;
606
607     while(line)
608     {
609         int x1,y1,x2,y2,x3,y3;
610
611         if(line->type == gfx_moveTo) {
612         } else if(line->type == gfx_lineTo) {
613             double x1=x*i->zoom,y1=y*i->zoom;
614             double x3=line->x*i->zoom,y3=line->y*i->zoom;
615             
616             add_line(dev, x1, y1, x3, y3);
617         } else if(line->type == gfx_splineTo) {
618             int c,t,parts,qparts;
619             double xx,yy;
620             
621             double x1=x*i->zoom,y1=y*i->zoom;
622             double x2=line->sx*i->zoom,y2=line->sy*i->zoom;
623             double x3=line->x*i->zoom,y3=line->y*i->zoom;
624             
625             c = abs(x3-2*x2+x1) + abs(y3-2*y2+y1);
626             xx=x1;
627             yy=y1;
628
629             parts = (int)(sqrt(c));
630             if(!parts) parts = 1;
631
632             for(t=1;t<=parts;t++) {
633                 double nx = (double)(t*t*x3 + 2*t*(parts-t)*x2 + (parts-t)*(parts-t)*x1)/(double)(parts*parts);
634                 double ny = (double)(t*t*y3 + 2*t*(parts-t)*y2 + (parts-t)*(parts-t)*y1)/(double)(parts*parts);
635                 
636                 add_line(dev, xx, yy, nx, ny);
637                 xx = nx;
638                 yy = ny;
639             }
640         }
641         x = line->x;
642         y = line->y;
643         line = line->next;
644     }
645 }
646
647 void render_startclip(struct _gfxdevice*dev, gfxline_t*line)
648 {
649     internal_t*i = (internal_t*)dev->internal;
650     fillinfo_t info;
651     memset(&info, 0, sizeof(info));
652     newclip(dev);
653     info.type = filltype_clip;
654     draw_line(dev, line);
655     fill(dev, &info);
656 }
657
658 void render_endclip(struct _gfxdevice*dev)
659 {
660     internal_t*i = (internal_t*)dev->internal;
661     endclip(dev, 0);
662 }
663
664 void render_fill(struct _gfxdevice*dev, gfxline_t*line, gfxcolor_t*color)
665 {
666     internal_t*i = (internal_t*)dev->internal;
667
668     draw_line(dev, line);
669     fill_solid(dev, color);
670 }
671
672 void render_fillbitmap(struct _gfxdevice*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
673 {
674     internal_t*i = (internal_t*)dev->internal;
675
676     gfxmatrix_t m2 = *matrix;
677
678     draw_line(dev, line);
679
680     fillinfo_t info;
681     memset(&info, 0, sizeof(info));
682     info.type = filltype_bitmap;
683     info.image = img;
684     info.matrix = &m2;
685     info.cxform = cxform;
686
687     m2.m00 *= i->zoom; m2.m01 *= i->zoom; m2.tx *= i->zoom;
688     m2.m10 *= i->zoom; m2.m11 *= i->zoom; m2.ty *= i->zoom;
689
690     fill(dev, &info);
691 }
692
693 void render_fillgradient(struct _gfxdevice*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
694 {
695     internal_t*i = (internal_t*)dev->internal;
696     
697     gfxmatrix_t m2 = *matrix;
698
699     draw_line(dev, line);
700
701     RGBA g[256];
702     fillinfo_t info;
703     memset(&info, 0, sizeof(info));
704     info.type = filltype_gradient;
705     info.gradient = g;
706     info.matrix = &m2;
707
708     m2.m00 *= i->zoom; m2.m01 *= i->zoom; m2.tx *= i->zoom;
709     m2.m10 *= i->zoom; m2.m11 *= i->zoom; m2.ty *= i->zoom;
710
711     info.clip_or_radial = type == gfxgradient_radial;
712
713     int pos = 0;
714     gfxcolor_t color = {0,0,0,0};
715     pos=0;
716     while(gradient) {
717         int nextpos = gradient->pos*256;
718         int t;
719         if(nextpos>256) {
720             msg("<error> Invalid gradient- contains values > 1.0");
721             return;
722         }
723         
724         gfxcolor_t nextcolor = gradient->color;
725         if(nextpos!=pos) {
726             double p0 = 1.0;
727             double p1 = 0.0;
728             double step = 1.0/(nextpos-pos);
729             int t;
730             for(t=pos;t<nextpos;t++) {
731                 g[t].r = color.r*p0 + nextcolor.r*p1;
732                 g[t].g = color.g*p0 + nextcolor.g*p1;
733                 g[t].b = color.b*p0 + nextcolor.b*p1;
734                 g[t].a = color.a*p0 + nextcolor.a*p1;
735                 p0 -= step;
736                 p1 += step;
737             }
738         }
739         color=nextcolor;
740
741         pos = nextpos;
742         gradient = gradient->next;
743     }
744     if(pos!=256) {
745         msg("<error> Invalid gradient- doesn't end with 1.0");
746     }
747
748     fill(dev, &info);
749 }
750
751 void render_addfont(struct _gfxdevice*dev, gfxfont_t*font)
752 {
753 }
754
755 void render_drawchar(struct _gfxdevice*dev, gfxfont_t*font, int glyphnr, gfxcolor_t*color, gfxmatrix_t*matrix)
756 {
757     internal_t*i = (internal_t*)dev->internal;
758     if(!font)
759         return;
760
761     /* align characters to whole pixels */
762     matrix->tx = (int)(matrix->tx * i->antialize) / i->antialize;
763     matrix->ty = (int)(matrix->ty * i->antialize) / i->antialize;
764
765     gfxglyph_t*glyph = &font->glyphs[glyphnr];
766     gfxline_t*line2 = gfxline_clone(glyph->line);
767     gfxline_transform(line2, matrix);
768     draw_line(dev, line2);
769     fill_solid(dev, color);
770     gfxline_free(line2);
771     
772     return;
773 }
774
775 void render_result_write(gfxresult_t*r, int filedesc)
776 {
777     internal_result_t*i= (internal_result_t*)r->internal;
778 }
779 int render_result_save(gfxresult_t*r, const char*filename)
780 {
781     internal_result_t*i= (internal_result_t*)r->internal;
782     if(!i) {
783         return 0; // no pages drawn
784     }
785     if(i->next) {
786         int nr=0;
787         char filenamebuf[256];
788         char*origname = strdup(filename);
789         int l = strlen(origname);
790         if(l>3 && strchr("gG",origname[l-1]) && strchr("nN",filename[l-2]) &&
791                 strchr("pP",origname[l-3]) && filename[l-4]=='.') {
792             origname[l-4] = 0;
793         }
794         while(i->next) {
795             sprintf(filenamebuf, "%s.%d.png", origname, nr);
796             writePNG(filename, (unsigned char*)i->img.data, i->img.width, i->img.height);
797             nr++;
798         }
799         free(origname);
800     } else {
801         writePNG(filename, (unsigned char*)i->img.data, i->img.width, i->img.height);
802     }
803     return 1;
804 }
805 char*gfximage_asXPM(gfximage_t*img, int depth)
806 {
807     int d= 256/depth;
808     char*str = (char*)malloc(img->width*img->height*4 + 500 + 16*depth*depth*depth);
809     char*p = str;
810     p+= sprintf(p, "static char *noname[] = {\n\"%d %d 262144 3\",\n");
811     int r,g,b;
812     for(r=0;r<depth;r++)
813     for(g=0;g<depth;g++)
814     for(b=0;b<depth;b++) {
815         p += sprintf(p, "\"%c%c%c c #%02x%02x%02x\",\n", r+32,g+32,b+32, r*d,g*d,b*d);
816     }
817     int y;
818     for(y=0;y<img->height;y++)  {
819         p+=sprintf(p, "\"");
820         gfxcolor_t*col = &img->data[y*img->height];
821         int x;
822         for(x=0;x<img->width;x++) {
823             p+=sprintf(p, "%c%c%c", 32+(col->r/d), 32+(col->g/d), 32+(col->b/d));
824         }
825         p+=sprintf(p, "\",\n");
826     }
827     *p = 0;
828     return p;
829 }
830 void*render_result_get(gfxresult_t*r, const char*name)
831 {
832     internal_result_t*i= (internal_result_t*)r->internal;
833     if(!strncmp(name,"xpm",3)) {
834         int pagenr = atoi(&name[3]);
835         if(pagenr<0)
836             pagenr=0;
837         while(pagenr>0) {
838             i = i->next;
839             if(!i)
840                 return 0;
841             pagenr--;
842         }
843         return gfximage_asXPM(&i->img, 64);
844     } else if(!strncmp(name,"page",4)) {
845         int pagenr = atoi(&name[4]);
846         if(pagenr<0)
847             pagenr=0;
848         while(pagenr>0) {
849             i = i->next;
850             if(!i)
851                 return 0;
852             pagenr--;
853         }
854         return &i->img;
855     }
856     return 0;
857 }
858 void render_result_destroy(gfxresult_t*r)
859 {
860     internal_result_t*i= (internal_result_t*)r->internal;
861     r->internal = 0;
862     while(i) {
863         internal_result_t*next = i->next;
864         free(i->img.data);i->img.data = 0;
865
866         /* FIXME memleak
867            the following rfx_free causes a segfault on WIN32 machines,
868            if executed */
869         //rfx_free(i);
870
871         i = next;
872     }
873     rfx_free(r);
874 }
875
876 gfxresult_t* render_finish(struct _gfxdevice*dev)
877 {
878     internal_t*i = (internal_t*)dev->internal;
879     
880     gfxresult_t* res = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
881     
882     res->internal = i->results;i->results = 0;
883     res->write = render_result_write;
884     res->save = render_result_save;
885     res->get = render_result_get;
886     res->destroy = render_result_destroy;
887
888     free(dev->internal); dev->internal = 0; i = 0;
889
890     return res;
891 }
892
893 void render_startpage(struct _gfxdevice*dev, int width, int height)
894 {
895     internal_t*i = (internal_t*)dev->internal;
896     int y;
897
898     if(i->width2 || i->height2) {
899         fprintf(stderr, "Error: startpage() called twice (no endpage()?)\n");
900         exit(1);
901     }
902     
903     i->width = width*i->multiply;
904     i->height = height*i->multiply;
905     i->width2 = width*i->zoom;
906     i->height2 = height*i->zoom;
907     i->bitwidth = (i->width2+31)/32;
908
909     i->lines = (renderline_t*)rfx_alloc(i->height2*sizeof(renderline_t));
910     for(y=0;y<i->height2;y++) {
911         memset(&i->lines[y], 0, sizeof(renderline_t));
912         i->lines[y].points = 0;
913         i->lines[y].num = 0;
914     }
915     i->img = (RGBA*)rfx_calloc(sizeof(RGBA)*i->width2*i->height2);
916     if(i->fillwhite) {
917         memset(i->img, 0xff, sizeof(RGBA)*i->width2*i->height2);
918     }
919
920     i->ymin = 0x7fffffff;
921     i->ymax = -0x80000000;
922
923
924     /* initialize initial clipping field, which doesn't clip anything yet */
925     newclip(dev);
926     memset(i->clipbuf->data, 255, sizeof(U32)*i->bitwidth*i->height2);
927 }
928
929 static void store_image(internal_t*i, internal_result_t*ir)
930 {
931     ir->img.data = (gfxcolor_t*)malloc(i->width*i->height*sizeof(gfxcolor_t));
932     ir->img.width = i->width;
933     ir->img.height = i->height;
934
935     gfxcolor_t*dest = ir->img.data;
936
937     if(i->antialize <= 1) /* no antializing */ {
938         int y;
939         for(y=0;y<i->height;y++) {
940             RGBA*line = &i->img[y*i->width];
941             memcpy(&dest[y*i->width], line, sizeof(RGBA)*i->width);
942         }
943     } else {
944         RGBA**lines = (RGBA**)rfx_calloc(sizeof(RGBA*)*i->antialize);
945         int q = i->antialize*i->antialize;
946         int ypos = 0;
947         int y;
948         int y2=0;
949         for(y=0;y<i->height2;y++) {
950             int n;
951             ypos = y % i->antialize;
952             lines[ypos] = &i->img[y*i->width2];
953             if(ypos == i->antialize-1) {
954                 RGBA*out = &dest[(y2++)*i->width];
955                 int x;
956                 int r,g,b,a;
957                 for(x=0;x<i->width;x++) {
958                     int xpos = x*i->antialize;
959                     int yp;
960                     U32 r=0,g=0,b=0,a=0;
961                     for(yp=0;yp<i->antialize;yp++) {
962                         RGBA*lp = &lines[yp][xpos];
963                         int xp;
964                         for(xp=0;xp<i->antialize;xp++) {
965                             RGBA*p = &lp[xp];
966                             r += p->r;
967                             g += p->g;
968                             b += p->b;
969                             a += p->a;
970                         }
971                     }
972                     out[x].r = r / q;
973                     out[x].g = g / q;
974                     out[x].b = b / q;
975                     out[x].a = a / q;
976                 }
977             }
978         }
979         rfx_free(lines);
980     }
981 }
982
983 void render_endpage(struct _gfxdevice*dev)
984 {
985     internal_t*i = (internal_t*)dev->internal;
986     
987     if(!i->width2 || !i->height2) {
988         fprintf(stderr, "Error: endpage() called without corresponding startpage()\n");
989         exit(1);
990     }
991
992     endclip(dev, 1);
993     while(i->clipbuf) {
994         fprintf(stderr, "Warning: unclosed clip while processing endpage()\n");
995         endclip(dev, 1);
996     }
997     
998     internal_result_t*ir= (internal_result_t*)rfx_calloc(sizeof(internal_result_t));
999
1000     int y,x;
1001
1002     store_image(i, ir);
1003
1004     ir->next = 0;
1005     if(i->result_next) {
1006         i->result_next->next = ir;
1007     }
1008     if(!i->results) {
1009         i->results = ir;
1010     }
1011     i->result_next = ir;
1012
1013     for(y=0;y<i->height2;y++) {
1014         rfx_free(i->lines[y].points); i->lines[y].points = 0;
1015     }
1016     rfx_free(i->lines);i->lines=0;
1017
1018     if(i->img) {rfx_free(i->img);i->img = 0;}
1019
1020     i->width2 = 0;
1021     i->height2 = 0;
1022 }
1023
1024 void render_drawlink(struct _gfxdevice*dev, gfxline_t*line, const char*action)
1025 {
1026     /* not supported for this output device */
1027 }
1028
1029 void gfxdevice_render_init(gfxdevice_t*dev)
1030 {
1031     internal_t*i = (internal_t*)rfx_calloc(sizeof(internal_t));
1032     memset(dev, 0, sizeof(gfxdevice_t));
1033     
1034     dev->name = "render";
1035
1036     dev->internal = i;
1037     
1038     i->width = 0;
1039     i->width2 = 0;
1040     i->height = 0;
1041     i->height2 = 0;
1042     i->antialize = 1;
1043     i->multiply = 1;
1044     i->zoom = 1;
1045
1046     dev->setparameter = render_setparameter;
1047     dev->startpage = render_startpage;
1048     dev->startclip = render_startclip;
1049     dev->endclip = render_endclip;
1050     dev->stroke = render_stroke;
1051     dev->fill = render_fill;
1052     dev->fillbitmap = render_fillbitmap;
1053     dev->fillgradient = render_fillgradient;
1054     dev->addfont = render_addfont;
1055     dev->drawchar = render_drawchar;
1056     dev->drawlink = render_drawlink;
1057     dev->endpage = render_endpage;
1058     dev->finish = render_finish;
1059 }
1060