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