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