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