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