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