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