zlib fixes, workaround for buggy(?) flash players
[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
309   /* fix for buggy flash players which can't handle plain-color bitmaps 
310      TODO: is there a better solution?
311   */
312   { int s;
313     int l=32;
314     for(s=0;s<height*width*4;s+=4) {
315         ((U8*)bitmap)[s+0] = s;
316         if(s>l)
317             break;
318     }
319   }
320
321   { z_stream zs;
322       
323     memset(&zs,0x00,sizeof(z_stream));
324     zs.zalloc = Z_NULL;
325     zs.zfree  = Z_NULL;
326
327     if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)==Z_OK)
328     { zs.avail_in         = bps*height;
329       zs.next_in          = bitmap;
330
331       if (RFXSWF_deflate_wraper(t,&zs,TRUE)<0) res = -3;
332       deflateEnd(&zs);
333       
334     } else res = -3; // zlib error
335   }
336   
337   return res;
338 }
339
340 int swf_SetLosslessBitsIndexed(TAG * t,U16 width,U16 height,U8 * bitmap,RGBA * palette,U16 ncolors)
341 { RGBA * pal = palette;
342   int bps = BYTES_PER_SCANLINE(width);
343   U8 * data;
344   int res = 0;
345     
346   if (!pal)     // create default palette for grayscale images
347   { int i;
348     pal = malloc(256*sizeof(RGBA));
349     for (i=0;i<256;i++) { pal[i].r = pal[i].g = pal[i].b = i; pal[i].a = 0xff;}
350     ncolors = 256;
351   }
352   
353   if ((ncolors<2)||(ncolors>256)||(!t)) {
354       fprintf(stderr, "rfxswf: unsupported number of colors: %d\n", ncolors);
355       return -1; // parameter error
356   }
357
358   swf_SetU8(t,BMF_8BIT);
359   swf_SetU16(t,width);
360   swf_SetU16(t,height);
361   swf_SetU8(t,ncolors-1); // number of pal entries
362
363   { z_stream zs;
364
365     memset(&zs,0x00,sizeof(z_stream));
366     zs.zalloc = Z_NULL;
367     zs.zfree  = Z_NULL;
368
369     if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)==Z_OK)
370     { U8 * zpal;                    // compress palette
371       if ((zpal = malloc(ncolors*4)))
372       { U8 * pp = zpal;
373         int i;
374
375     /* be careful with ST_DEFINEBITSLOSSLESS2, because
376        the Flash player produces great bugs if you use too many
377        alpha colors in your palette. The only sensible result that
378        can be archeived is setting one color to r=0,b=0,g=0,a=0 to
379        make transparent parts in sprites. That's the cause why alpha
380        handling is implemented in lossless routines of rfxswf.
381
382        Indeed: I haven't understood yet how flash player handles
383        alpha values different from 0 and 0xff in lossless bitmaps...
384     */
385
386         if (swf_GetTagID(t)==ST_DEFINEBITSLOSSLESS2)  // have alpha channel?
387         { for (i=0;i<ncolors;i++)         
388           { pp[0] = pal[i].r;
389             pp[1] = pal[i].g;
390             pp[2] = pal[i].b;
391             pp[3] = pal[i].a;
392             pp+=4; 
393           }
394           zs.avail_in = 4*ncolors;
395         }
396         else
397         { for (i=0;i<ncolors;i++)         // pack RGBA structures to RGB 
398           { pp[0] = pal[i].r;
399             pp[1] = pal[i].g;
400             pp[2] = pal[i].b;
401             pp+=3;
402           }
403           zs.avail_in         = 3*ncolors;
404         }
405
406         zs.next_in          = zpal;
407
408         if (RFXSWF_deflate_wraper(t,&zs,FALSE)<0) res = -3;
409
410                                     // compress bitmap
411         zs.next_in = bitmap;
412         zs.avail_in = (bps*height*sizeof(U8));
413
414         if (RFXSWF_deflate_wraper(t,&zs,TRUE)<0) res = -3;
415
416         deflateEnd(&zs);
417
418         if (zs.next_out>data) swf_SetBlock(t,data,zs.next_out-data);
419
420         free(zpal);
421       } else res = -2; // memory error
422     } else res = -3; // zlib error
423     free(data);
424   }
425   
426   if (!palette) free(pal);
427
428   return res;
429 }
430
431 int swf_SetLosslessBitsGrayscale(TAG * t,U16 width,U16 height,U8 * bitmap)
432 { return swf_SetLosslessBitsIndexed(t,width,height,bitmap,NULL,256);
433 }
434
435
436 #endif // HAVE_ZLIB
437
438 #if defined(HAVE_ZLIB) && defined(HAVE_JPEGLIB)
439 int swf_SetJPEGBits3(TAG * tag,U16 width,U16 height,RGBA* bitmap, int quality)
440 {
441   JPEGBITS* jpeg;
442   int y;
443   int pos;
444   int res = 0;
445   U8 * data;
446   z_stream zs;
447   
448   pos = tag->len;
449   swf_SetU32(tag, 0); //placeholder
450   jpeg = swf_SetJPEGBitsStart(tag,width,height,quality);
451   for (y=0;y<height;y++)
452   { U8 scanline[3*width];
453     int x,p = 0;
454     for (x=0;x<width;x++) 
455     { scanline[p++] = bitmap[width*y+x].r;
456       scanline[p++] = bitmap[width*y+x].g;
457       scanline[p++] = bitmap[width*y+x].b;
458     }
459     swf_SetJPEGBitsLine(jpeg,scanline);
460   }
461   swf_SetJPEGBitsFinish(jpeg);
462   PUT32(&tag->data[pos], tag->len - pos - 4);
463
464   data=malloc(OUTBUFFER_SIZE);
465   memset(&zs,0x00,sizeof(z_stream));
466
467   if (deflateInit(&zs,Z_DEFAULT_COMPRESSION)!=Z_OK) {
468       fprintf(stderr, "rfxswf: zlib compression failed");
469       return -3;
470   }
471     
472   zs.next_out         = data;
473   zs.avail_out        = OUTBUFFER_SIZE;
474
475   for (y=0;y<height;y++)
476   { U8 scanline[width];
477     int x,p = 0;
478     for (x=0;x<width;x++) {
479       scanline[p++] = bitmap[width*y+x].a;
480     }
481     zs.avail_in         = width;
482     zs.next_in          = scanline;
483
484     while(1) {
485         if(deflate(&zs, Z_NO_FLUSH) != Z_OK) {
486             fprintf(stderr, "rfxswf: zlib compression failed");
487             return -4;
488         }
489         if(zs.next_out != data) {
490             swf_SetBlock(tag, data, zs.next_out - data);
491             zs.next_out = data;
492             zs.avail_out = OUTBUFFER_SIZE;
493         }
494         if(!zs.avail_in) {
495             break;
496         }
497     }
498   }
499
500   while(1) {
501       int ret = deflate(&zs, Z_FINISH);
502       if (ret != Z_OK &&
503           ret != Z_STREAM_END)  {
504           fprintf(stderr, "rfxswf: zlib compression failed");
505           return -5;
506       }
507       if(zs.next_out != data) {
508           swf_SetBlock(tag, data, zs.next_out - data);
509           zs.next_out = data;
510           zs.avail_out = OUTBUFFER_SIZE;
511       }
512       if (ret == Z_STREAM_END) {
513           break;
514       }
515   }
516
517   deflateEnd(&zs);
518   free(data);
519   return 0;
520 }
521 #endif
522
523 #undef OUTBUFFER_SIZE
524