added swf_GetJPEGSize() function.
[swftools.git] / lib / modules / swfbits.c
1 /* swfbits.c
2
3    Bitmap functions (needs libjpeg) 
4
5    Extension module for the rfxswf library.
6    Part of the swftools package.
7
8    Copyright (c) 2000, 2001 Rainer Böhme <rfxswf@reflex-studio.de>
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 #define OUTBUFFER_SIZE 0x8000
25
26 #ifdef HAVE_JPEGLIB
27
28 typedef struct _JPEGDESTMGR
29 { struct jpeg_destination_mgr mgr;
30   TAG *  t;
31   JOCTET * buffer;
32   struct jpeg_compress_struct cinfo;
33   struct jpeg_error_mgr jerr;
34 } JPEGDESTMGR, * LPJPEGDESTMGR;
35
36 // Destination manager callbacks
37
38 void RFXSWF_init_destination(j_compress_ptr cinfo) 
39 { JPEGDESTMGR * dmgr = (JPEGDESTMGR *)cinfo->dest;
40   dmgr->buffer = (JOCTET*)malloc(OUTBUFFER_SIZE);
41   dmgr->mgr.next_output_byte = dmgr->buffer;
42   dmgr->mgr.free_in_buffer = OUTBUFFER_SIZE;
43 }
44
45 boolean RFXSWF_empty_output_buffer(j_compress_ptr cinfo)
46 { JPEGDESTMGR * dmgr = (JPEGDESTMGR *)cinfo->dest;
47   swf_SetBlock(dmgr->t,(U8*)dmgr->buffer,OUTBUFFER_SIZE);
48   dmgr->mgr.next_output_byte = dmgr->buffer;
49   dmgr->mgr.free_in_buffer = OUTBUFFER_SIZE;
50   return TRUE;
51 }
52
53 void RFXSWF_term_destination(j_compress_ptr cinfo) 
54 { JPEGDESTMGR * dmgr = (JPEGDESTMGR *)cinfo->dest;
55   swf_SetBlock(dmgr->t,(U8*)dmgr->buffer,OUTBUFFER_SIZE-dmgr->mgr.free_in_buffer);
56   free(dmgr->buffer);
57   dmgr->mgr.free_in_buffer = 0;
58 }
59
60 JPEGBITS * swf_SetJPEGBitsStart(TAG * t,int width,int height,int quality)
61 {
62   JPEGDESTMGR * jpeg;
63         
64   // redirect compression lib output to local SWF Tag structure
65   
66   jpeg = (JPEGDESTMGR *)malloc(sizeof(JPEGDESTMGR));
67   if (!jpeg) return NULL;
68   
69   memset(jpeg,0x00,sizeof(JPEGDESTMGR));
70   jpeg->cinfo.err = jpeg_std_error(&jpeg->jerr);
71
72   jpeg_create_compress(&jpeg->cinfo);
73
74   jpeg->mgr.init_destination = RFXSWF_init_destination;
75   jpeg->mgr.empty_output_buffer = RFXSWF_empty_output_buffer;
76   jpeg->mgr.term_destination = RFXSWF_term_destination;
77       
78   jpeg->t = t;
79
80   jpeg->cinfo.dest = (struct jpeg_destination_mgr *)jpeg;
81
82   // init compression
83   
84   jpeg->cinfo.image_width  = width;
85   jpeg->cinfo.image_height = height;
86   jpeg->cinfo.input_components = 3;
87   jpeg->cinfo.in_color_space = JCS_RGB;
88
89   jpeg_set_defaults(&jpeg->cinfo);
90   jpeg_set_quality(&jpeg->cinfo,quality,TRUE);
91
92   // write tables to SWF
93   
94   jpeg_write_tables(&jpeg->cinfo);
95
96   // compess image to SWF
97    
98   jpeg_suppress_tables(&jpeg->cinfo, TRUE);
99   jpeg_start_compress(&jpeg->cinfo, FALSE);
100
101   return (JPEGBITS *)jpeg;
102 }
103
104 int swf_SetJPEGBitsLines(JPEGBITS * jpegbits,U8 ** data,int n)
105 { JPEGDESTMGR * jpeg = (JPEGDESTMGR *)jpegbits;
106   if (!jpeg) return -1;
107   jpeg_write_scanlines(&jpeg->cinfo,data,n);
108   return 0;
109 }
110
111 int swf_SetJPEGBitsLine(JPEGBITS * jpegbits,U8 * data)
112 { return swf_SetJPEGBitsLines(jpegbits,&data,1);
113 }
114
115 int swf_SetJPEGBitsFinish(JPEGBITS * jpegbits)
116 { JPEGDESTMGR * jpeg = (JPEGDESTMGR *)jpegbits;
117   if (!jpeg) return -1;
118   jpeg_finish_compress(&jpeg->cinfo);
119   free(jpeg);
120   return 0;
121 }
122
123 void swf_SetJPEGBits2(TAG * tag,U16 width,U16 height,RGBA* bitmap, int quality)
124 {
125   JPEGBITS* jpeg;
126   int y;
127   jpeg = swf_SetJPEGBitsStart(tag,width,height,quality);
128   for (y=0;y<height;y++)
129   { U8 scanline[3*width];
130     int x,p = 0;
131     for (x=0;x<width;x++) 
132     { scanline[p++] = bitmap[width*y+x].r;
133       scanline[p++] = bitmap[width*y+x].g;
134       scanline[p++] = bitmap[width*y+x].b;
135     }
136     swf_SetJPEGBitsLine(jpeg,scanline);
137   }
138   swf_SetJPEGBitsFinish(jpeg);
139 }
140
141 void swf_GetJPEGSize(char * fname, int*width, int*height)
142 { struct jpeg_decompress_struct cinfo;
143   struct jpeg_error_mgr jerr;
144   FILE * fi;
145   *width = 0;
146   *height = 0;
147   cinfo.err = jpeg_std_error(&jerr);
148   jpeg_create_decompress(&cinfo); 
149   if ((fi=fopen(fname,"rb"))==NULL) {
150       fprintf(stderr, "rfxswf: file open error\n");
151       return;
152   }
153   jpeg_stdio_src(&cinfo, fi);
154   jpeg_read_header(&cinfo, TRUE);
155   *width = cinfo.image_width;
156   *height = cinfo.image_height;
157   jpeg_destroy_decompress(&cinfo);
158   fclose(fi);
159 }
160
161 int swf_SetJPEGBits(TAG * t,char * fname,int quality)
162 { struct jpeg_decompress_struct cinfo;
163   struct jpeg_error_mgr jerr;
164   JPEGBITS * out;
165   FILE * f;
166   U8 * scanline;
167   
168   cinfo.err = jpeg_std_error(&jerr);
169   jpeg_create_decompress(&cinfo); 
170
171   if ((f=fopen(fname,"rb"))==NULL) {
172       fprintf(stderr, "rfxswf: file open error\n");
173       return -1;
174   }
175
176   jpeg_stdio_src(&cinfo,f);
177   jpeg_read_header(&cinfo, TRUE);
178   jpeg_start_decompress(&cinfo);
179
180   out = swf_SetJPEGBitsStart(t,cinfo.output_width,cinfo.output_height,quality);
181   scanline = (U8*)malloc(4*cinfo.output_width);
182   
183   if (scanline) 
184   { int y;
185     U8 * js = scanline;
186     if(cinfo.out_color_space == JCS_GRAYSCALE) {
187         for (y=0;y<cinfo.output_height;y++)
188         { int x;
189           jpeg_read_scanlines(&cinfo,&js,1);
190           for(x=cinfo.output_width-1;x>=0;x--) {
191               js[x*3] = js[x*3+1] = js[x*3+2] = js[x];
192           }
193           swf_SetJPEGBitsLines(out,(U8**)&js,1);
194         }
195     }
196     else if(cinfo.out_color_space == JCS_RGB) 
197     {
198         for (y=0;y<cinfo.output_height;y++)
199         { jpeg_read_scanlines(&cinfo,&js,1);
200           swf_SetJPEGBitsLines(out,(U8**)&js,1);
201         }
202     }
203     else if(cinfo.out_color_space == JCS_YCCK) 
204     {
205         //FIXME
206         fprintf(stderr, "Error: Can't convert YCCK to RGB.\n");
207         return -1;
208     }
209     else if(cinfo.out_color_space == JCS_YCbCr) 
210     {
211         for (y=0;y<cinfo.output_height;y++) {
212           int x;
213           for(x=0;x<cinfo.output_width;x++) {
214               int y = js[x*3+0];
215               int u = js[x*3+1];
216               int v = js[x*3+1];
217               js[x*3+0] = y + ((360*(v-128))>>8);
218               js[x*3+1] = y - ((88*(u-128)+183*(v-128))>>8);
219               js[x*3+2] = y + ((455 * (u-128))>>8);
220           }
221         }
222     }
223     else if(cinfo.out_color_space == JCS_CMYK) 
224     { 
225         for (y=0;y<cinfo.output_height;y++)
226         { int x;
227           jpeg_read_scanlines(&cinfo,&js,1);
228           /* This routine seems to work for now-
229              It's a mixture of 3 different
230              CMYK->RGB conversion routines I found in the
231              web. (which all produced garbage)
232              I'm happily accepting suggestions. (mk)*/
233           for(x=0;x<cinfo.output_width;x++) {
234                 int white = 255 - js[x*4+3];
235                 js[x*3+0] = white - ((js[x*4]*white)>>8);
236                 js[x*3+1] = white - ((js[x*4+1]*white)>>8);
237                 js[x*3+2] = white - ((js[x*4+2]*white)>>8);
238           }
239           swf_SetJPEGBitsLines(out,(U8**)&js,1);
240         }
241     }
242   }
243
244   swf_SetJPEGBitsFinish(out);
245   jpeg_finish_decompress(&cinfo);
246   fclose(f);
247   
248   return 0;
249 }
250
251 #endif // HAVE_JPEGLIB
252
253 // Lossless compression texture based on zlib
254
255 #ifdef HAVE_ZLIB
256
257 int RFXSWF_deflate_wraper(TAG * t,z_stream * zs,boolean finish)
258
259   U8*data=malloc(OUTBUFFER_SIZE);
260   zs->next_out = data;
261   zs->avail_out = OUTBUFFER_SIZE;
262   while (1)
263   { int status = deflate(zs,Z_NO_FLUSH);
264
265     if (status!=Z_OK)
266     {
267 #ifdef DEBUG_RFXSWF
268       fprintf(stderr,"rfxswf: zlib compression error (%i)\n",status);
269 #endif
270       free(data);
271       return status;
272     }
273
274     if (zs->next_out!=data)
275     { swf_SetBlock(t,data,zs->next_out - data);
276       zs->next_out = data;
277       zs->avail_out = OUTBUFFER_SIZE;
278     }
279     
280     if (zs->avail_in==0)
281       break;
282   }
283
284   if(!finish) {
285       free(data);
286       return 0;
287   }
288
289   while(1) {
290     int status = deflate(zs,Z_FINISH);
291     if (status!=Z_OK && status!=Z_STREAM_END)
292     {
293 #ifdef DEBUG_RFXSWF
294       fprintf(stderr,"rfxswf: zlib compression error (%i)\n",status);
295 #endif
296       free(data);
297       return status;
298     }
299
300     if (zs->next_out!=data)
301     { 
302       swf_SetBlock(t,data,zs->next_out - data);
303       zs->next_out = data;
304       zs->avail_out = OUTBUFFER_SIZE;
305     }
306
307     if(status == Z_STREAM_END)
308         break;
309   }
310   free(data);
311   return 0;
312 }
313
314
315 int swf_SetLosslessBits(TAG * t,U16 width,U16 height,void * bitmap,U8 bitmap_flags)
316 { int res = 0;
317   int bps;
318   
319   switch (bitmap_flags)
320   { case BMF_8BIT:
321       return swf_SetLosslessBitsIndexed(t,width,height,bitmap,NULL,256);
322     case BMF_16BIT:
323       bps = BYTES_PER_SCANLINE(sizeof(U16)*width);
324       break;
325     case BMF_32BIT:
326       bps = width*4;
327       break;
328     default:
329       fprintf(stderr, "rfxswf: unknown bitmap type %d\n", bitmap_flags);
330       return -1;
331   }
332
333   swf_SetU8(t,bitmap_flags);
334   swf_SetU16(t,width);
335   swf_SetU16(t,height);
336
337   { z_stream zs;
338       
339     memset(&zs,0x00,sizeof(z_stream));
340     zs.zalloc = Z_NULL;
341     zs.zfree  = Z_NULL;
342
343     if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)==Z_OK)
344     { zs.avail_in         = bps*height;
345       zs.next_in          = bitmap;
346
347       if (RFXSWF_deflate_wraper(t,&zs,TRUE)<0) res = -3;
348       deflateEnd(&zs);
349       
350     } else res = -3; // zlib error
351   }
352
353   while(t->len < 64) { /* actually, 63 and above is o.k., but let's stay on the safe side */
354
355       /* Flash players up to MX crash or do strange things if they encounter a 
356          DefineLossless Tag with a payload of less than 63 bytes. They also
357          substitute the whole bitmap by a red rectangle.
358
359          This loop fills up the tag with zeroes so that this doesn't happen.
360       */
361       swf_SetU8(t, 0);
362   }
363   
364   return res;
365 }
366
367 int swf_SetLosslessBitsIndexed(TAG * t,U16 width,U16 height,U8 * bitmap,RGBA * palette,U16 ncolors)
368 { RGBA * pal = palette;
369   int bps = BYTES_PER_SCANLINE(width);
370   int res = 0;
371     
372   if (!pal)     // create default palette for grayscale images
373   { int i;
374     pal = malloc(256*sizeof(RGBA));
375     for (i=0;i<256;i++) { pal[i].r = pal[i].g = pal[i].b = i; pal[i].a = 0xff;}
376     ncolors = 256;
377   }
378   
379   if ((ncolors<2)||(ncolors>256)||(!t)) {
380       fprintf(stderr, "rfxswf: unsupported number of colors: %d\n", ncolors);
381       return -1; // parameter error
382   }
383
384   swf_SetU8(t,BMF_8BIT);
385   swf_SetU16(t,width);
386   swf_SetU16(t,height);
387   swf_SetU8(t,ncolors-1); // number of pal entries
388
389   { z_stream zs;
390
391     memset(&zs,0x00,sizeof(z_stream));
392     zs.zalloc = Z_NULL;
393     zs.zfree  = Z_NULL;
394
395     if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)==Z_OK)
396     { U8 * zpal;                    // compress palette
397       if ((zpal = malloc(ncolors*4)))
398       { U8 * pp = zpal;
399         int i;
400
401     /* be careful with ST_DEFINEBITSLOSSLESS2, because
402        the Flash player produces great bugs if you use too many
403        alpha colors in your palette. The only sensible result that
404        can be archeived is setting one color to r=0,b=0,g=0,a=0 to
405        make transparent parts in sprites. That's the cause why alpha
406        handling is implemented in lossless routines of rfxswf.
407
408        Indeed: I haven't understood yet how flash player handles
409        alpha values different from 0 and 0xff in lossless bitmaps...
410     */
411
412         if (swf_GetTagID(t)==ST_DEFINEBITSLOSSLESS2)  // have alpha channel?
413         { for (i=0;i<ncolors;i++)         
414           { pp[0] = pal[i].r;
415             pp[1] = pal[i].g;
416             pp[2] = pal[i].b;
417             pp[3] = pal[i].a;
418             pp+=4; 
419           }
420           zs.avail_in = 4*ncolors;
421         }
422         else
423         { for (i=0;i<ncolors;i++)         // pack RGBA structures to RGB 
424           { pp[0] = pal[i].r;
425             pp[1] = pal[i].g;
426             pp[2] = pal[i].b;
427             pp+=3;
428           }
429           zs.avail_in         = 3*ncolors;
430         }
431
432         zs.next_in          = zpal;
433
434         if (RFXSWF_deflate_wraper(t,&zs,FALSE)<0) res = -3;
435
436                                     // compress bitmap
437         zs.next_in = bitmap;
438         zs.avail_in = (bps*height*sizeof(U8));
439
440         if (RFXSWF_deflate_wraper(t,&zs,TRUE)<0) res = -3;
441
442         deflateEnd(&zs);
443
444         free(zpal);
445       } else res = -2; // memory error
446     } else res = -3; // zlib error
447   }
448   
449   if (!palette) free(pal);
450
451   while(t->len < 64) { /* actually, 63 and above is o.k., but let's stay on the safe side */
452
453       /* Flash players up to MX crash or do strange things if they encounter a 
454          DefineLossless(2) Tag with a payload of less than 63 bytes. They also
455          substitute the whole bitmap by a red rectangle.
456
457          This loop fills up the tag with zeroes so that this doesn't happen.
458       */
459       swf_SetU8(t, 0);
460   }
461   
462   return res;
463 }
464
465 int swf_SetLosslessBitsGrayscale(TAG * t,U16 width,U16 height,U8 * bitmap)
466 { return swf_SetLosslessBitsIndexed(t,width,height,bitmap,NULL,256);
467 }
468
469
470 #endif // HAVE_ZLIB
471
472 #if defined(HAVE_ZLIB) && defined(HAVE_JPEGLIB)
473 int swf_SetJPEGBits3(TAG * tag,U16 width,U16 height,RGBA* bitmap, int quality)
474 {
475   JPEGBITS* jpeg;
476   int y;
477   int pos;
478   int res = 0;
479   U8 * data;
480   z_stream zs;
481   
482   pos = tag->len;
483   swf_SetU32(tag, 0); //placeholder
484   jpeg = swf_SetJPEGBitsStart(tag,width,height,quality);
485   for (y=0;y<height;y++)
486   { U8 scanline[3*width];
487     int x,p = 0;
488     for (x=0;x<width;x++) 
489     { scanline[p++] = bitmap[width*y+x].r;
490       scanline[p++] = bitmap[width*y+x].g;
491       scanline[p++] = bitmap[width*y+x].b;
492     }
493     swf_SetJPEGBitsLine(jpeg,scanline);
494   }
495   swf_SetJPEGBitsFinish(jpeg);
496   PUT32(&tag->data[pos], tag->len - pos - 4);
497
498   data=malloc(OUTBUFFER_SIZE);
499   memset(&zs,0x00,sizeof(z_stream));
500
501   if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)!=Z_OK) {
502       fprintf(stderr, "rfxswf: zlib compression failed");
503       return -3;
504   }
505     
506   zs.next_out         = data;
507   zs.avail_out        = OUTBUFFER_SIZE;
508
509   for (y=0;y<height;y++)
510   { U8 scanline[width];
511     int x,p = 0;
512     for (x=0;x<width;x++) {
513       scanline[p++] = bitmap[width*y+x].a;
514     }
515     zs.avail_in         = width;
516     zs.next_in          = scanline;
517
518     while(1) {
519         if(deflate(&zs, Z_NO_FLUSH) != Z_OK) {
520             fprintf(stderr, "rfxswf: zlib compression failed");
521             return -4;
522         }
523         if(zs.next_out != data) {
524             swf_SetBlock(tag, data, zs.next_out - data);
525             zs.next_out = data;
526             zs.avail_out = OUTBUFFER_SIZE;
527         }
528         if(!zs.avail_in) {
529             break;
530         }
531     }
532   }
533
534   while(1) {
535       int ret = deflate(&zs, Z_FINISH);
536       if (ret != Z_OK &&
537           ret != Z_STREAM_END)  {
538           fprintf(stderr, "rfxswf: zlib compression failed");
539           return -5;
540       }
541       if(zs.next_out != data) {
542           swf_SetBlock(tag, data, zs.next_out - data);
543           zs.next_out = data;
544           zs.avail_out = OUTBUFFER_SIZE;
545       }
546       if (ret == Z_STREAM_END) {
547           break;
548       }
549   }
550
551   deflateEnd(&zs);
552   free(data);
553   return 0;
554 }
555 #endif
556
557 #undef OUTBUFFER_SIZE
558