Patch from Moriyoshi Koizumi:
[swftools.git] / src / jpeg2swf.c
1 /* jpeg2swf.c
2
3    JPEG to SWF converter tool
4
5    Part of the swftools package.
6
7    Copyright (c) 2001 Rainer Böhme <rfxswf@reflex-studio.de>
8    Copyright (c) 2002,2003 Matthias Kramm <kramm@quiss.org>
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 #include <stdio.h>
25 #include <math.h>
26 #include <fcntl.h>
27 #include <jpeglib.h>
28 #include "../lib/rfxswf.h"
29 #include "../lib/args.h"        // not really a header ;-)
30
31 #define MAX_INPUT_FILES 1024
32 #define VERBOSE(x) (global.verbose>=x)
33
34 struct {
35     int quality;
36     float framerate;
37     int max_image_width;
38     int max_image_height;
39     int force_width;
40     int force_height;
41     int nfiles;
42     int verbose;
43     char *outfile;
44     int mx;
45     int next_id;
46     char *asset_name;
47     int version;
48     int xoffset;
49     int yoffset;
50     int fit_to_movie;
51 } global;
52
53 typedef struct _image {
54     char *filename;
55     int quality;
56     int width;
57     int height;
58 } image_t;
59 image_t image[MAX_INPUT_FILES];
60
61 VIDEOSTREAM stream;
62
63 TAG *MovieStart(SWF * swf, float framerate, int dx, int dy)
64 {
65     TAG *t;
66     RGBA rgb;
67
68     memset(swf, 0x00, sizeof(SWF));
69
70     swf->fileVersion = global.version;
71     swf->frameRate = (int)(256.0 * framerate);
72     swf->movieSize.xmin = global.xoffset * 20;
73     swf->movieSize.ymin = global.yoffset * 20;
74     swf->movieSize.xmax = swf->movieSize.xmin + dx * 20;
75     swf->movieSize.ymax = swf->movieSize.ymin + dy * 20;
76
77     t = swf->firstTag = swf_InsertTag(NULL, ST_SETBACKGROUNDCOLOR);
78
79     rgb.r = rgb.g = rgb.b = rgb.a = 0x00;
80     swf_SetRGB(t, &rgb);
81
82     if (global.mx) {
83         t = swf_InsertTag(t, ST_DEFINEVIDEOSTREAM);
84         swf_SetU16(t, 0xf00d);
85         swf_SetVideoStreamDefine(t, &stream, 65535, dx, dy);
86     } else if (global.asset_name) {
87         t = swf_InsertTag(t, ST_DEFINESPRITE);
88         swf_SetU16(t, 1);
89         swf_SetU16(t, global.next_id++);
90     }
91
92     return t;
93 }
94
95 int MovieFinish(SWF * swf, TAG * t, char *sname)
96 {
97     int handle, so = fileno(stdout);
98
99     if (global.asset_name) {
100         SWFPLACEOBJECT obj;
101
102         t = swf_InsertTag(t, ST_END);
103         t = swf_InsertTag(t, ST_EXPORTASSETS);
104         swf_SetU16(t, 1);
105         swf_SetU16(t, 1);
106         swf_SetString(t, global.asset_name);
107
108         t = swf_InsertTag(t, ST_PLACEOBJECT2);
109         swf_GetPlaceObject(0, &obj);
110         obj.depth = 1;
111         obj.id = 1;
112         swf_SetPlaceObject(t, &obj);
113
114         t = swf_InsertTag(t, ST_SHOWFRAME);
115     }
116
117     t = swf_InsertTag(t, ST_END);
118
119     if ((!isatty(so)) && (!sname))
120         handle = so;
121     else {
122         if (!sname)
123             sname = "output.swf";
124         handle = open(sname, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, 0666);
125     }
126     if(global.version >= 6) {
127         if (swf_WriteSWC(handle, swf)<0) 
128                 fprintf(stderr, "Unable to write output file: %s\n", sname);
129     } else {
130         if (swf_WriteSWF(handle, swf)<0) 
131                 fprintf(stderr, "Unable to write output file: %s\n", sname);
132     }
133
134     if (handle != so)
135         close(handle);
136
137     swf_FreeTags(swf);
138     return 0;
139 }
140
141 int getJPEG(char*filename, int* width, int* height, RGBA**pic2)
142 {
143     struct jpeg_decompress_struct cinfo;
144     struct jpeg_error_mgr jerr;
145     struct jpeg_source_mgr mgr;
146     int x,y;
147     FILE*f;
148     RGBA*pic,*js;
149     U8*buf;
150
151     if ((f=fopen(filename,"rb"))==NULL) {
152         fprintf(stderr, "rfxswf: file open error\n");
153         return 0;
154     }
155
156     cinfo.err = jpeg_std_error(&jerr);
157     jpeg_create_decompress(&cinfo); 
158     jpeg_stdio_src(&cinfo, f);
159     jpeg_read_header(&cinfo, TRUE);
160     jpeg_start_decompress(&cinfo);
161
162     pic = malloc(cinfo.output_width*cinfo.output_height*sizeof(RGBA));
163     buf = malloc(cinfo.output_width*4);
164     memset(pic, 255, cinfo.output_width*cinfo.output_height*sizeof(RGBA));
165     js = pic;
166
167     *width = cinfo.output_width;
168     *height = cinfo.output_height;
169     
170     for (y=0;y<cinfo.output_height;y++) {
171         int x;
172         jpeg_read_scanlines(&cinfo,&buf,1);
173
174         if(cinfo.out_color_space == JCS_GRAYSCALE) {
175             for(x=0;x<cinfo.output_width;x++) {
176                 js[x].r = js[x].g = js[x].b = buf[x];
177             }
178         } else if(cinfo.out_color_space == JCS_RGB) {
179             for (x=0;x<cinfo.output_width;x++)
180             { 
181                 js[x].r = buf[x*3+0];
182                 js[x].g = buf[x*3+1];
183                 js[x].b = buf[x*3+2];
184             }
185         } else if(cinfo.out_color_space == JCS_YCCK) {
186             //FIXME
187             fprintf(stderr, "Error: Can't convert YCCK to RGB.\n");
188             return -1;
189         } else if(cinfo.out_color_space == JCS_YCbCr) {
190             for(x=0;x<cinfo.output_width;x++) {
191                 int y = buf[x*3+0];
192                 int u = buf[x*3+1];
193                 int v = buf[x*3+1];
194                 js[x].r = y + ((360*(v-128))>>8);
195                 js[x].g = y - ((88*(u-128)+183*(v-128))>>8);
196                 js[x].b = y + ((455 * (u-128))>>8);
197             }
198         }
199         else if(cinfo.out_color_space == JCS_CMYK) 
200         { 
201             for(x=0;x<cinfo.output_width;x++) {
202                   int white = 255 - buf[x*4+3];
203                   js[x].r = white - ((buf[x*4]*white)>>8);
204                   js[x].g = white - ((buf[x*4+1]*white)>>8);
205                   js[x].b = white - ((buf[x*4+2]*white)>>8);
206             }
207         }
208         js += cinfo.output_width;
209     }
210
211     jpeg_finish_decompress(&cinfo);
212     jpeg_destroy_decompress(&cinfo);
213     
214     free(buf);
215     *pic2 = pic;
216     return 1;
217 }
218
219
220 int frame = 0;
221 TAG *MovieAddFrame(SWF * swf, TAG * t, char *sname, int quality, 
222                    int width, int height)
223 {
224     SHAPE *s;
225     SRECT r;
226     MATRIX m;
227     int fs;
228     int movie_width = swf->movieSize.xmax - swf->movieSize.xmin;
229     int movie_height = swf->movieSize.ymax - swf->movieSize.ymin;
230
231     if(global.mx) {
232         int sizex, sizey;
233         RGBA * pic2;
234         SWFPLACEOBJECT obj;
235         int quant=0;
236         getJPEG(sname, &sizex, &sizey, &pic2);
237         if(sizex != stream.owidth || sizey != stream.oheight) {
238             fprintf(stderr, "All images must have the same dimensions if using -m!");
239             exit(1);
240         }
241
242         t = swf_InsertTag(t, ST_VIDEOFRAME);
243         swf_SetU16(t, 0xf00d);
244         quant = 1+(30-(30*quality)/100);
245         if(!(frame%20)) {
246             swf_SetVideoStreamIFrame(t, &stream, pic2, quant);
247         } else {
248             swf_SetVideoStreamPFrame(t, &stream, pic2, quant);
249         }
250
251         t = swf_InsertTag(t, ST_PLACEOBJECT2);
252         swf_GetPlaceObject(0, &obj);
253         if(frame==0) {
254             obj.depth = 1;
255             obj.id = 0xf00d;
256         } else {
257             obj.depth = 1;
258             obj.move = 1;
259             obj.ratio = frame;
260         }
261         swf_SetPlaceObject(t,&obj);
262
263         t = swf_InsertTag(t, ST_SHOWFRAME);
264     } else {
265         t = swf_InsertTag(t, ST_DEFINEBITSJPEG2);
266         swf_SetU16(t, global.next_id);          // id
267         swf_SetJPEGBits(t,sname,quality);
268
269         t = swf_InsertTag(t, ST_DEFINESHAPE);
270         swf_ShapeNew(&s);
271         swf_GetMatrix(NULL, &m);
272         if (global.fit_to_movie) {
273             m.sx = 0x10000 * movie_width / width;
274             m.sy = 0x10000 * movie_height / height;
275             width = movie_width / 20;
276             height = movie_height / 20;
277         } else {
278                 m.sx = 20 * 0x10000;
279                 m.sy = 20 * 0x10000;
280         }
281         m.tx = global.xoffset * 20;
282         m.ty = global.yoffset * 20;
283         fs = swf_ShapeAddBitmapFillStyle(s, &m, global.next_id, 1);
284         global.next_id++;
285         swf_SetU16(t, global.next_id);  // id
286         r.xmin = global.xoffset * 20;
287         r.ymin = global.yoffset * 20;
288         r.xmax = r.xmin + width * 20;
289         r.ymax = r.ymin + height * 20;
290         swf_SetRect(t, &r);
291         swf_SetShapeHeader(t, s);
292         swf_ShapeSetAll(t, s, r.xmin, r.ymin, 0, fs, 0);
293         swf_ShapeSetLine(t, s, r.xmax - r.xmin, 0);
294         swf_ShapeSetLine(t, s, 0, r.ymax - r.ymin);
295         swf_ShapeSetLine(t, s, -r.xmax + r.xmin, 0);
296         swf_ShapeSetLine(t, s, 0, -r.ymax + r.ymin);
297         swf_ShapeSetEnd(t);
298
299         if(frame) {
300             t = swf_InsertTag(t, ST_REMOVEOBJECT2);
301             swf_SetU16(t, 1);           // depth
302         }
303
304         t = swf_InsertTag(t, ST_PLACEOBJECT2);
305         swf_GetMatrix(NULL, &m);
306         m.tx = (movie_width - width * 20) / 2;
307         m.ty = (movie_height - height * 20) / 2;
308         swf_ObjectPlace(t, global.next_id, 1, &m, NULL, NULL);
309         global.next_id++;
310         t = swf_InsertTag(t, ST_SHOWFRAME);
311     }
312     frame++;
313
314     return t;
315 }
316
317 int CheckInputFile(image_t* i, char *fname, char **realname)
318 {
319     struct jpeg_decompress_struct cinfo;
320     struct jpeg_error_mgr jerr;
321     FILE *f;
322     char *s = malloc(strlen(fname) + 5);
323     int width, height;
324
325     if (!s)
326         exit(2);
327     (*realname) = s;
328     strcpy(s, fname);
329
330     // Check whether file exists (with typical extensions)
331
332     if ((f = fopen(s, "rb")) == NULL) {
333         sprintf(s, "%s.jpg", fname);
334         if ((f = fopen(s, "rb")) == NULL) {
335             sprintf(s, "%s.jpeg", fname);
336             if ((f = fopen(s, "rb")) == NULL) {
337                 sprintf(s, "%s.JPG", fname);
338                 if ((f = fopen(s, "rb")) == NULL) {
339                     sprintf(s, "%s.JPEG", fname);
340                     if ((f = fopen(s, "rb")) == NULL)
341                         return -1;
342                 }
343             }
344         }
345     }
346
347     cinfo.err = jpeg_std_error(&jerr);
348     jpeg_create_decompress(&cinfo);
349     jpeg_stdio_src(&cinfo, f);
350     jpeg_read_header(&cinfo, TRUE);
351
352     width = cinfo.image_width;
353     height = cinfo.image_height;
354
355     i->width = width;
356     i->height = height;
357
358     // Get image dimensions
359
360     if (global.max_image_width < width)
361         global.max_image_width = width;
362     if (global.max_image_height < height)
363         global.max_image_height = height;
364
365     jpeg_destroy_decompress(&cinfo);
366     fclose(f);
367
368     return 0;
369 }
370
371 int args_callback_option(char *arg, char *val)
372 {
373     int res = 0;
374     if (arg[1])
375         res = -1;
376     else
377         switch (arg[0]) {
378         case 'q':
379             if (val)
380                 global.quality = atoi(val);
381             if ((global.quality < 1) ||(global.quality > 100)) {
382                 if (VERBOSE(1))
383                     fprintf(stderr,
384                             "Error: You must specify a valid quality between 1 and 100.\n");
385                 exit(1);
386             }
387             res = 1;
388             break;
389
390         case 'r':
391             if (val)
392                 global.framerate = atof(val);
393             if ((global.framerate < 1.0/256) || (global.framerate >= 256.0)) {
394                 if (VERBOSE(1))
395                     fprintf(stderr,
396                             "Error: You must specify a valid framerate between 1 and 10000.\n");
397                 exit(1);
398             }
399             res = 1;
400             break;
401
402         case 'o':
403             if (val)
404                 global.outfile = val;
405             res = 1;
406             break;
407
408         case 'v':
409             if (val)
410                 global.verbose = atoi(val);
411             res = 1;
412             break;
413
414         case 'X':
415             if (val)
416                 global.force_width = atoi(val);
417             res = 1;
418             break;
419
420         case 'm':
421             global.mx = 1;
422             global.version = 6;
423             return 0;
424
425         case 'z':
426             global.version = 6;
427             return 0;
428
429         case 'Y':
430             if (val)
431                 global.force_height = atoi(val);
432             res = 1;
433             break;
434
435         case 'V':
436             printf("jpeg2swf - part of %s %s\n", PACKAGE, VERSION);
437             exit(0);
438
439         case 'e':
440             if (val)
441                 global.asset_name = val;
442             res = 1;
443             break;
444
445         case 'x':
446             if (val) {
447                 global.xoffset = atoi(val);
448             }
449             res = 1;
450             break;
451
452         case 'y':
453             if (val) {
454                 global.yoffset = atoi(val);
455             }
456             res = 1;
457             break;
458
459         case 'f':
460             global.fit_to_movie = 1;
461             res = 0;
462             break;
463
464         default:
465             res = -1;
466             break;
467         }
468
469     if (res < 0) {
470         if (VERBOSE(1))
471             fprintf(stderr, "Unknown option: -%s\n", arg);
472         exit(1);
473         return 0;
474     }
475     return res;
476 }
477
478 static struct options_t options[] = {
479 {"o", "output"},
480 {"m", "mx"},
481 {"q", "quality"},
482 {"r", "rate"},
483 {"z", "zlib"},
484 {"X", "width"},
485 {"Y", "height"},
486 {"x", "xoffset"},
487 {"y", "yoffset"},
488 {"e", "export"},
489 {"f", "fit-to-movie"},
490 {"v", "verbose"},
491 {"V", "version"},
492 {0,0}
493 };
494
495 int args_callback_longoption(char *name, char *val)
496 {
497     return args_long2shortoption(options, name, val);
498 }
499
500 int args_callback_command(char *arg, char *next)        // actually used as filename
501 {
502     char *s;
503     image_t* i = &image[global.nfiles];
504     if (CheckInputFile(i, arg, &s) < 0) {
505         if (VERBOSE(1))
506             fprintf(stderr, "Unable to open input file: %s\n", arg);
507         free(s);
508     } else {
509         i->filename = s;
510         i->quality = global.quality;
511         global.nfiles++;
512         if (global.nfiles >= MAX_INPUT_FILES) {
513             if (VERBOSE(1))
514                 fprintf(stderr, "Error: Too many input files.\n");
515             exit(1);
516         }
517     }
518     return 0;
519 }
520
521 void args_callback_usage(char *name)
522 {
523     printf("\n");
524     printf("Usage: %s [-options [value]] imagefiles[.jpg]|[.jpeg] [...]\n", name);
525     printf("\n");
526     printf("-o , --output <outputfile>     Explicitly specify output file. (otherwise, output.swf will be used)\n");
527     printf("-m , --mx                      Use Flash MX H.263 compression (use for correlated images)\n");
528     printf("-q , --quality <quality>       Set compression quality (1-100, 1=worst, 100=best)\n");
529     printf("-r , --rate <framerate>         Set movie framerate (frames per second)\n");
530     printf("-z , --zlib <zlib>             Enable Flash 6 (MX) Zlib Compression\n");
531     printf("-x , --xoffset <offset>        horizontally offset images by <offset>\n");
532     printf("-y , --yoffset <offset>        vertically offset images by <offset>\n");
533     printf("-X , --width <width>           Force movie width to <width> (default: autodetect)\n");
534     printf("-Y , --height <height>         Force movie height to <height> (default: autodetect)\n");
535     printf("-f , --fit-to-movie            Fit images to movie size\n");
536     printf("-e , --export <assetname>      Make importable as asset with <assetname>\n");
537     printf("-v , --verbose <level>         Set verbose level to <level> (0=quiet, 1=default, 2=debug)\n");
538     printf("-V , --version                 Print version information and exit\n");
539     printf("\n");
540 }
541
542
543 int main(int argc, char **argv)
544 {
545     SWF swf;
546     TAG *t;
547
548     memset(&global, 0x00, sizeof(global));
549
550     global.quality = 60;
551     global.framerate = 1.0;
552     global.verbose = 1;
553     global.version = 4;
554     global.asset_name = NULL;
555     global.next_id = 1;
556     global.xoffset = 0;
557     global.yoffset = 0;
558     global.fit_to_movie = 0;
559         
560     processargs(argc, argv);
561
562     if (VERBOSE(2))
563         fprintf(stderr, "Processing %i file(s)...\n", global.nfiles);
564
565     t = MovieStart(&swf, global.framerate,
566                    global.force_width ? global.force_width : global.
567                    max_image_width,
568                    global.force_height ? global.force_height : global.
569                    max_image_height);
570
571     {
572         int i;
573         for (i = 0; i < global.nfiles; i++) {
574             if (VERBOSE(3))
575                 fprintf(stderr, "[%03i] %s (%i%%, 1/%i)\n", i,
576                         image[i].filename, image[i].quality);
577             t = MovieAddFrame(&swf, t, image[i].filename, image[i].quality,
578                               image[i].width, image[i].height);
579             free(image[i].filename);
580         }
581     }
582
583     MovieFinish(&swf, t, global.outfile);
584
585     return 0;
586 }