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