* Read the "frame delay time" defined in (animated) GIF header.
[swftools.git] / src / gif2swf.c
1 /* -*- mode: c; tab-width: 4; -*- ---------------------------[for (x)emacs]--
2
3    $Id: gif2swf.c,v 1.3 2005/04/17 17:35:07 dseg Exp $
4    GIF to SWF converter tool
5
6    Part of the swftools package.
7
8    Copyright (c) 2005 Daichi Shinozaki <dseg@shield.jp>
9
10    This program is free software; you can redistribute it and/or modify
11    it under the terms of the GNU General Public License as published by
12    the Free Software Foundation; either version 2 of the License, or
13    (at your option) any later version.
14
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License for more details.
19
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
23
24    This file is derived from png2swf.c */
25
26 #include <stdio.h>
27 #include <fcntl.h>
28 #include <gif_lib.h>
29 #include "../lib/rfxswf.h"
30 #include "../lib/args.h"
31
32 #define MAX_INPUT_FILES 1024
33 #define VERBOSE(x) (global.verbose>=x)
34 #define AS_FIRSTFRAME "if(n<1) n=1;"
35 #define AS_LASTFRAME "if(n<=%d-1){n=n+1;gotoAndPlay(1);}else stop();"
36
37 struct {
38     float framerate;
39     int max_image_width;
40     int max_image_height;
41     int force_width;
42     int force_height;
43     int nfiles;
44     int verbose;
45     int do_cgi;
46     int version;
47     char *outfile;
48     int imagecount;
49     int loopcount;
50 } global;
51
52 struct {
53     char *filename;
54 } image[MAX_INPUT_FILES];
55
56 struct gif_header {
57    int width;
58    int height;
59 };
60
61 enum disposal_method {
62     NONE,
63     DO_NOT_DISPOSE,
64     RESTORE_TO_BGCOLOR,
65     RESTORE_TO_PREVIOUS
66 }; 
67
68
69 void SetFrameAction(TAG** t, const char *src, int ver)
70 {
71    ActionTAG* as;
72    
73    as = swf_ActionCompile(src, ver);
74    if(!as)
75       fprintf(stderr, "Couldn't compile ActionScript\n");
76    else {
77       *t = swf_InsertTag(*t, ST_DOACTION);
78       swf_ActionSet(*t, as);
79       swf_ActionFree(as);
80    }
81 }
82
83 int getGifDisposalMethod(GifFileType * gft, int framenum) 
84 {
85    int i;
86    ExtensionBlock* ext = gft->SavedImages[0].ExtensionBlocks;
87
88    for (i=0; i < gft->SavedImages[framenum].ExtensionBlockCount; i++, ext++)
89      if (ext->Function == GRAPHICS_EXT_FUNC_CODE) 
90        return  ((ext->Bytes[0] & 0x1C) >> 2);
91    
92    return -1;
93 }
94
95 U16 getGifLoopCount(GifFileType * gft) {
96    int i;
97    char sig[11];
98    ExtensionBlock* ext = gft->SavedImages[0].ExtensionBlocks;
99
100    // info:  http://members.aol.com/royalef/gifabout.htm#net-extension
101    for (i=0; i < gft->SavedImages[0].ExtensionBlockCount; i++, ext++)
102      if (ext->Function == APPLICATION_EXT_FUNC_CODE) {
103 //       fprintf(stderr, "extension size: %d\n", ext->ByteCount);
104          memcpy(sig, &ext->Bytes[0], 11);
105 //       if(memcmp(sig, "NETSCAPE2.0", 11) == 0)
106 //            fprintf(stderr, "NETSCAPE2.0\n");
107 //      fprintf(stderr, "data: %d %d\n", ext->Bytes[13], ext->Bytes[14]);
108      }
109    
110    return 0;
111 }
112
113 U16 getGifDelayTime(GifFileType * gft, int framenum)
114 {  
115    int i;
116    ExtensionBlock* ext = gft->SavedImages[framenum].ExtensionBlocks;
117
118    for (i=0; i < gft->SavedImages[framenum].ExtensionBlockCount; i++, ext++)
119        if (ext->Function == GRAPHICS_EXT_FUNC_CODE)
120            return GET16(&ext->Bytes[1]);
121
122    return 0;
123 }
124
125 int getTransparentColor(GifFileType * gft, int framenum)
126 {
127     int i;
128     ExtensionBlock* ext = gft->SavedImages[framenum].ExtensionBlocks;
129
130     // Get transparency color from graphic extension block
131     for (i=0; i < gft->SavedImages[framenum].ExtensionBlockCount; i++, ext++)
132         if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) &&
133             (ext->Bytes[0] & 1)) { // there is a transparent color
134            return ext->Bytes[3] == 0 ?
135             255 :                  // exception
136             (U8)ext->Bytes[3];     // transparency color
137         }
138
139     return -1;
140 }
141
142 TAG *MovieStart(SWF * swf, float framerate, int dx, int dy)
143 {
144     TAG *t;
145     RGBA rgb;
146
147     memset(swf, 0x00, sizeof(SWF));
148
149     swf->fileVersion = global.version;
150     swf->frameRate = (int)(256.0 * framerate);
151     swf->movieSize.xmax = dx * 20;
152     swf->movieSize.ymax = dy * 20;
153
154     t = swf->firstTag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
155
156     rgb.r = rgb.g = rgb.b = rgb.a = 0x00;
157
158     //rgb.g = 0xff; //<--- handy for testing alpha conversion
159     swf_SetRGB(t, &rgb);
160
161     return t;
162 }
163
164 int MovieFinish(SWF * swf, TAG * t, char *sname)
165 {
166     int f, so = fileno(stdout);
167     t = swf_InsertTag(t, ST_END);
168
169     if ((!isatty(so)) && (!sname))
170         f = so;
171     else {
172         if (!sname)
173             sname = "output.swf";
174         f = open(sname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0644);
175     }
176
177     if (global.do_cgi) {
178         if FAILED(swf_WriteCGI(swf)) fprintf(stderr,"WriteCGI() failed.\n");
179     } else {
180         if (global.version >= 6) {
181             if (swf_WriteSWC(f, swf)<0)
182                 fprintf(stderr, "Unable to write output file: %s\n", sname);
183         } else {
184             if (swf_WriteSWF(f, swf)<0)
185                 fprintf(stderr, "Unable to write output file: %s\n", sname);
186         }
187         if (f != so)
188             close(f);
189     }
190
191     swf_FreeTags(swf);
192     return 0;
193 }
194
195 TAG *MovieAddFrame(SWF * swf, TAG * t, char *sname, int id, int imgidx)
196 {
197     SHAPE *s;
198     SRECT r;
199     MATRIX m;
200     int fs;
201
202     U8 *imagedata, *from, *to;
203     GifImageDesc *img;
204     RGBA* pal;
205
206     struct gif_header header;
207
208     int i, j, numcolors = 0, alphapalette = 0;
209     U8 bgcolor;
210     int bpp; // byte per pixel
211     int swf_width, padlen;
212
213     ColorMapObject* colormap;
214     GifColorType c;
215     int interlacedOffset[] = { 0, 4, 2, 1 };// The way Interlaced image should
216     int interlacedJumps[] = { 8, 8, 4, 2 }; // be read - offsets and jumps...
217     U16 delay, depth;
218     int disposal;
219     char framename[8] = { 0, 0, 0, 0, 0, 0, 0, '\0' };
220     char *as_lastframe;
221    
222     GifFileType* gft;
223     FILE *fi;
224
225     if ((fi = fopen(sname, "rb")) == NULL) {
226         if (VERBOSE(1))
227             fprintf(stderr, "Read access failed: %s\n", sname);
228         return t;
229     }
230     fclose(fi);
231
232     if ((gft = DGifOpenFileName(sname)) == NULL) {
233         fprintf(stderr, "%s is not a GIF file!\n", sname);
234         return t;
235     }
236     if (DGifSlurp(gft) != GIF_OK) {
237         PrintGifError(); 
238         return t;
239     }
240
241     header.width  = gft->SWidth;
242     header.height = gft->SHeight;
243
244     pal = (RGBA*)malloc(256 * sizeof(RGBA));
245     memset(pal, 0, 256 * sizeof(RGBA));
246
247     img = &gft->SavedImages[imgidx].ImageDesc;
248    
249     // Local colormap has precedence over Global colormap
250     colormap = img->ColorMap ? img->ColorMap : gft->SColorMap;
251     numcolors = colormap->ColorCount;
252     alphapalette = getTransparentColor(gft, imgidx);
253     bpp = (alphapalette >= 0 ? 4 : 3);
254
255     // bgcolor is the background color to fill the bitmap
256     // if DWORD-aligned imagebuffer is bigger than the actual image
257     if (gft->SColorMap)                     // There is a GlobalColorMap
258         bgcolor = (U8)gft->SBackGroundColor;// The SBGColor is meaningful
259     else
260         if (alphapalette >= 0)          // There is a transparency color
261             bgcolor = alphapalette;     // set the bgColor to tranparent
262         else
263             bgcolor = 0;                // Don't know what to do here.
264                                         // If this doesn't work, we could
265                                         // create a new color and set the
266                                         // alpha-channel to transparent
267                                         // (unless we are using all the 256
268                                         // colors, in which case either we
269                                         // give up, or move to 16-bits palette
270
271     if(VERBOSE(3))
272         fprintf(stderr, "BG palette index => %u\n", bgcolor);
273    
274     for (i=0; i<numcolors; i++) {
275         c = colormap->Colors[i];
276         if (i == alphapalette)
277             pal[i].r = pal[i].g = pal[i].b = pal[i].a = 0;// Fully transparent
278         else {
279             pal[i].r = c.Red;
280             pal[i].g = c.Green;
281             pal[i].b = c.Blue;
282             pal[i].a = 255; // Fully opaque
283         }
284     }
285
286     t = swf_InsertTag(t, bpp == 4 ? 
287                       ST_DEFINEBITSLOSSLESS2 : ST_DEFINEBITSLOSSLESS);
288     swf_SetU16(t, id); // id
289
290     // Ah! The Flash specs says scanlines must be DWORD ALIGNED!
291     // (but image width is the correct number of pixels)
292     swf_width = BYTES_PER_SCANLINE(header.width);
293
294     if ((imagedata = (U8 *)malloc(swf_width*header.height)) == NULL) {
295         fprintf(stderr, "Failed to allocate memory required, aborted.");
296         exit(2);
297     }
298
299     to = imagedata;
300     from = (U8 *)gft->SavedImages[imgidx].RasterBits;
301
302     if (swf_width == header.width) {
303         // we are all nicely aligned and don't need to move the bitmap around.
304         // Just copy the bits into the image buffer.
305         if (! gft->Image.Interlace)
306             if (header.width == img->Width && header.height == img->Height)
307                 memcpy(to, from, header.width*header.height);
308             else { //small screen
309                for (i = 0; i < header.height; i++, to += header.width) {
310                   memset(to, bgcolor, header.width);
311                   if (i >= img->Top && i < img->Top + img->Height) {
312                      memcpy(to + img->Left, from, img->Width);
313                      from += img->Width;
314                   }
315                }
316             }
317        
318         else // Need to perform 4 passes on the interlaced images
319             for (i = 0; i < 4; i++)
320                 for (j = interlacedOffset[i]; j < header.height;
321                      j += interlacedJumps[i], from += header.width)
322                     memcpy(to + header.width*j, from, header.width);
323     } else {
324         padlen = swf_width - header.width;
325
326         // here we need to pad the scanline
327         if (! gft->Image.Interlace) {
328            if (header.width == img->Width &&
329                header.height == img->Height) {
330               for (i=0; i < header.height; i++, from+=header.width, to+=swf_width) {
331                  memcpy(to, from, header.width);
332                  memset(to + header.width, bgcolor, padlen);
333               }
334            } else {  //small screen
335               for (i=0; i < header.height; i++, to += swf_width) {
336                  memset(to, bgcolor, swf_width);
337                  if (i >= img->Top && i < img->Top + img->Height) {
338                     memcpy(to + img->Left, from, img->Width);
339                     from += img->Width;
340                  }
341               }
342            }
343         } else { // Need to perform 4 passes on the interlaced images
344             for (i = 0; i < 4; i++)
345                 for (j = interlacedOffset[i]; j < header.height;
346                      j += interlacedJumps[i], from += header.width) {
347                     memcpy(to + swf_width*j, from, header.width);
348                     memset(to + swf_width*j, bgcolor, padlen);
349                 }
350         }
351     }
352     swf_SetLosslessBitsIndexed(t,header.width,header.height,imagedata,pal,256);
353
354     t = swf_InsertTag(t, ST_DEFINESHAPE);
355
356     swf_ShapeNew(&s);
357     swf_GetMatrix(NULL, &m);
358     m.sx = 20 * 0x10000;
359     m.sy = 20 * 0x10000;
360     fs = swf_ShapeAddBitmapFillStyle(s, &m, id, 0);
361
362     swf_SetU16(t, id + 1); // id
363
364     r.xmin = r.ymin = 0;
365     r.xmax = header.width * 20;
366     r.ymax = header.height * 20;
367     swf_SetRect(t, &r);
368
369     swf_SetShapeHeader(t, s);
370
371     swf_ShapeSetAll(t, s, 0, 0, 0, fs, 0);
372     swf_ShapeSetLine(t, s, r.xmax, 0);
373     swf_ShapeSetLine(t, s, 0, r.ymax);
374     swf_ShapeSetLine(t, s, -r.xmax, 0);
375     swf_ShapeSetLine(t, s, 0, -r.ymax);
376
377     swf_ShapeSetEnd(t);
378    
379     depth = imgidx + 1;
380     if ((imgidx > 0) && // REMOVEOBJECT2 not needed at frame 1(imgidx==0)
381         (global.imagecount > 1))
382      {
383         // check last frame's disposal method
384         if ((disposal = getGifDisposalMethod(gft, imgidx-1)) >= 0)
385           {
386              switch(disposal) {
387               case NONE:
388                 //fprintf(stdout, "  [none]\n");
389                 swf_SetU16(t, depth-1);
390                 t = swf_InsertTag(t, ST_REMOVEOBJECT2);
391                 break;          
392               case DO_NOT_DISPOSE:
393                 //fprintf(stdout, "  [don't dispose]\n");
394                 break;
395               case RESTORE_TO_BGCOLOR:
396                 swf_SetU16(t, depth-1);
397                 t = swf_InsertTag(t, ST_REMOVEOBJECT2);
398                 //fprintf(stdout, "  [restore to bg color]\n");
399                 break;
400               case RESTORE_TO_PREVIOUS:
401                 swf_SetU16(t, depth-1);
402                 t = swf_InsertTag(t, ST_REMOVEOBJECT2);
403                 //fprintf(stdout, "  [restore to previous]\n");
404                 break;
405              }
406           }
407      }
408    
409     swf_SetU16(t, depth);
410     t = swf_InsertTag(t, ST_PLACEOBJECT2);
411
412     swf_GetMatrix(NULL, &m);
413     m.tx = (swf->movieSize.xmax - (int) header.width * 20) / 2;
414     m.ty = (swf->movieSize.ymax - (int) header.height * 20) / 2;
415     swf_ObjectPlace(t, id + 1, depth, &m, NULL, NULL);
416
417     if ((global.imagecount > 1) &&
418         (global.loopcount > 0)) { // 0 means "infinite loop"
419         if (imgidx == 0)
420             SetFrameAction(&t, AS_FIRSTFRAME, global.version);
421     }
422    
423     t = swf_InsertTag(t, ST_SHOWFRAME);
424    
425     if (global.imagecount > 1) { // multi-frame GIF?
426        int framecnt;
427        delay = getGifDelayTime(gft, imgidx); // delay in 1/100 sec
428        framecnt = (int)(global.framerate * (delay/100.0));
429        if (framecnt > 1) {
430             if (VERBOSE(2))
431                fprintf(stderr, "at frame %d: pad %d frames(%.3f sec)\n",
432                        imgidx + 1, framecnt, delay/100.0);
433
434             framecnt -= 1; // already inserted a frame
435             while (framecnt--)
436                 t = swf_InsertTag(t, ST_SHOWFRAME);
437        }
438        if (imgidx == global.imagecount-1) {
439            as_lastframe = malloc(strlen(AS_LASTFRAME) + 5); // 0-99999
440            sprintf(as_lastframe, AS_LASTFRAME, global.loopcount);
441            SetFrameAction(&t, as_lastframe, global.version);
442            if (as_lastframe) 
443                free(as_lastframe);
444        }
445     }
446
447     free(pal);
448     free(imagedata);
449     DGifCloseFile(gft);
450
451     return t;
452 }
453
454 GifFileType * GifOpen(char *sname) {
455    GifFileType *gft = NULL;
456    
457    return gft;
458 }
459
460 int CheckInputFile(char *fname, char **realname)
461 {
462     FILE *fi;
463     char *s = malloc(strlen(fname) + 5);
464     GifFileType* gft;
465     GifRecordType rt;
466     GifByteType *extdata;
467     SavedImage tmp;
468     U8 buf[16];
469    
470     if (!s)
471         exit(2);
472     (*realname) = s;
473     strcpy(s, fname);
474
475     // Check whether file exists (with typical extensions)
476
477     if ((fi = fopen(s, "rb")) == NULL) {
478         sprintf(s, "%s.gif", fname);
479         if ((fi = fopen(s, "rb")) == NULL) {
480             sprintf(s, "%s.GIF", fname);
481             if ((fi = fopen(s, "rb")) == NULL) {
482                 sprintf(s, "%s.Gif", fname);
483                 if ((fi = fopen(s, "rb")) == NULL) {
484                     fprintf(stderr, "Couldn't open %s!\n", fname);
485                     return -1;
486                 }
487             }
488         }
489     }
490     fclose(fi);
491
492     if ((gft = DGifOpenFileName(s)) == NULL) {
493         fprintf(stderr, "%s is not a GIF file!\n", fname);
494         return -1;
495     }
496     if (global.max_image_width < gft->SWidth)
497         global.max_image_width = gft->SWidth;
498     if (global.max_image_height < gft->SHeight)
499         global.max_image_height = gft->SHeight;
500    
501    if (DGifSlurp(gft) != GIF_OK) { //gft->ImageCount be set
502         PrintGifError();
503         return -1;
504     }
505
506     global.imagecount = gft->ImageCount;
507     if(VERBOSE(3))
508         if(global.imagecount > 1)
509             fprintf(stderr, "Loops => %u\n", getGifLoopCount(gft));
510    
511     if(VERBOSE(2)) {
512         U8 i;
513         fprintf(stderr, "%d x %d, %d images total\n", 
514                 gft->SWidth, gft->SHeight, gft->ImageCount);
515        
516         for(i=0; i < gft->ImageCount; i++)
517             fprintf(stderr, "frame: %u, delay: %.3f sec\n",
518                     i+1, getGifDelayTime(gft, i) / 100.0);
519     }
520
521     DGifCloseFile(gft);
522
523     return 0;
524 }
525
526 int args_callback_option(char *arg, char *val)
527 {
528     int res = 0;
529     if (arg[1])
530         res = -1;
531     else
532         switch (arg[0]) {
533         case 'l':
534            if (val)
535              global.loopcount = atoi(val);
536            res = 1;
537            break;
538            
539         case 'r':
540             if (val)
541                 global.framerate = atof(val);
542             if ((global.framerate < 1.0/256) ||(global.framerate >= 256.0)) {
543                 if (VERBOSE(1))
544                     fprintf(stderr,
545                             "Error: You must specify a valid framerate between 1/256 and 255.\n");
546                 exit(1);
547             }
548             res = 1;
549             break;
550             
551         case 'o':
552             if (val)
553                 global.outfile = val;
554             res = 1;
555             break;
556             
557         case 'z':
558             global.version = 6;
559             res = 0;
560             break;
561
562         case 'C':
563             global.do_cgi = 1;
564             break;
565
566         case 'v':
567             if (val)
568                 global.verbose = atoi(val);
569             res = 1;
570             break;
571
572         case 'X':
573             if (val)
574                 global.force_width = atoi(val);
575             res = 1;
576             break;
577
578         case 'Y':
579             if (val)
580                 global.force_height = atoi(val);
581             res = 1;
582             break;
583
584         case 'V':
585             printf("gif2swf - part of %s %s\n", PACKAGE, VERSION);
586             exit(0);
587
588         default:
589             res = -1;
590             break;
591         }
592
593     if (res < 0) {
594         if (VERBOSE(1))
595             fprintf(stderr, "Unknown option: -%s\n", arg);
596         exit(1);
597         return 0;
598     }
599     return res;
600 }
601
602 static struct options_t options[] = {
603 {"l", "loop"},
604 {"r", "rate"},
605 {"o", "output"},
606 {"z", "zlib"},
607 {"X", "pixel"},
608 {"Y", "pixel"},
609 {"v", "verbose"},
610 {"C", "cgi"},
611 {"V", "version"},
612 {0,0}
613 };
614
615 int args_callback_longoption(char *name, char *val)
616 {
617     return args_long2shortoption(options, name, val);
618 }
619
620 int args_callback_command(char *arg, char *next) // actually used as filename
621 {
622     char *s;
623     if (CheckInputFile(arg, &s) < 0) {
624         if (VERBOSE(1))
625             fprintf(stderr, "Error opening input file: %s\n", arg);
626         free(s);
627
628     } else {
629         image[global.nfiles].filename = s;
630         global.nfiles++;
631         if (global.nfiles >= MAX_INPUT_FILES) {
632             if (VERBOSE(1))
633                 fprintf(stderr, "Error: Too many input files.\n");
634             exit(1);
635         }
636     }
637
638     return 0;
639 }
640
641 void args_callback_usage(char *name)
642 {
643     printf("\n");
644     printf("Usage: %s [-X width] [-Y height] [-o file.swf] [-r rate] file1.gif [file2.gif...]\n", name);
645     printf("\n");
646     printf("-l , --loop <loop count>       Set loop count. (default: 0 [= infinite loop])\n");
647     printf("-r , --rate <framerate>        Set movie framerate (frames per second)\n");
648     printf("-o , --output <filename>       Set name for SWF output file.\n");
649     printf("-z , --zlib <zlib>             Enable Flash 6 (MX) Zlib Compression\n");
650     printf("-X , --pixel <width>           Force movie width to <width> (default: autodetect)\n");
651     printf("-Y , --pixel <height>          Force movie height to <height> (default: autodetect)\n");
652     printf("-v , --verbose <level>         Set verbose level (0=quiet, 1=default, 2=debug)\n");
653     printf("-C , --cgi                     For use as CGI- prepend http header, write to stdout\n");
654     printf("-V , --version                 Print version information and exit\n");
655     printf("\n");
656 }
657
658 int main(int argc, char **argv)
659 {
660     SWF swf;
661     TAG *t;
662
663     memset(&global, 0x00, sizeof(global));
664
665     global.framerate = 1.0;
666     global.verbose = 1;
667     global.version = 5;
668     global.loopcount = 0;
669    
670     processargs(argc, argv);
671
672     if (VERBOSE(2)) 
673         fprintf(stderr, "loop count => %d\n", global.loopcount);
674
675     if (global.nfiles<=0) {
676         fprintf(stderr, "No gif files found in arguments\n");
677         return 1;
678     }
679
680     if (VERBOSE(2))
681         fprintf(stderr, "Processing %i file(s)...\n", global.nfiles);
682
683     if (global.imagecount > 1)       // multi-frame GIF?
684         if(global.framerate == 1.0)  // user not specified '-r' option?
685             global.framerate = 10.0;
686
687     t = MovieStart(&swf, global.framerate,
688                    global.force_width  ? global.force_width  : global.max_image_width,
689                    global.force_height ? global.force_height : global.max_image_height);
690     {
691         int i, j;
692         for (i = 0; i < global.nfiles; i++) {
693             if (VERBOSE(3))
694                 fprintf(stderr, "[%03i] %s\n", i,
695                         image[i].filename);
696             t = MovieAddFrame(&swf, t, image[i].filename, (i*2)+1, 0);
697             for (j = 2; j <= global.imagecount; j++)
698                 t = MovieAddFrame(&swf, t, image[i].filename, (j*2)-1, j-1);
699             free(image[i].filename);
700         }
701     }
702
703     MovieFinish(&swf, t, global.outfile);
704    
705     return 0;
706 }
707