KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > Acme > JPM > Encoders > GifEncoder


1 // GifEncoder - write out an image as a GIF
2
//
3
// Transparency handling and variable bit size courtesy of Jack Palevich.
4
//
5
// Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or without
8
// modification, are permitted provided that the following conditions
9
// are met:
10
// 1. Redistributions of source code must retain the above copyright
11
// notice, this list of conditions and the following disclaimer.
12
// 2. Redistributions in binary form must reproduce the above copyright
13
// notice, this list of conditions and the following disclaimer in the
14
// documentation and/or other materials provided with the distribution.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
// SUCH DAMAGE.
27
//
28
// Visit the ACME Labs Java page for up-to-date versions of this and other
29
// fine Java utilities: http://www.acme.com/java/
30

31 package Acme.JPM.Encoders;
32
33 import java.awt.*;
34 import java.awt.image.ImageProducer JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.io.OutputStream JavaDoc;
37 import java.util.Enumeration JavaDoc;
38
39 /// Write out an image as a GIF.
40
// <P>
41
// <A HREF="../../../../resources/classes/Acme/JPM/Encoders/GifEncoder.java">Fetch the software.</A><BR>
42
// <A HREF="../../../../resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
43
// <P>
44
// @see ToGif
45

46 public class GifEncoder extends ImageEncoder {
47
48     private boolean interlace = false;
49
50     /// Constructor from Image.
51
// @param img The image to encode.
52
// @param out The stream to write the GIF to.
53
public GifEncoder(Image img, OutputStream JavaDoc out) throws IOException JavaDoc {
54         super(img, out);
55     }
56
57     /// Constructor from Image with interlace setting.
58
// @param img The image to encode.
59
// @param out The stream to write the GIF to.
60
// @param interlace Whether to interlace.
61
public GifEncoder(Image img, OutputStream JavaDoc out, boolean interlace) throws IOException JavaDoc {
62         super(img, out);
63         this.interlace = interlace;
64     }
65
66     /// Constructor from ImageProducer.
67
// @param prod The ImageProducer to encode.
68
// @param out The stream to write the GIF to.
69
public GifEncoder(ImageProducer JavaDoc prod, OutputStream JavaDoc out) throws IOException JavaDoc {
70         super(prod, out);
71     }
72
73     /// Constructor from ImageProducer with interlace setting.
74
// @param prod The ImageProducer to encode.
75
// @param out The stream to write the GIF to.
76
public GifEncoder(ImageProducer JavaDoc prod, OutputStream JavaDoc out, boolean interlace) throws IOException JavaDoc {
77         super(prod, out);
78         this.interlace = interlace;
79     }
80
81
82     int width, height;
83     int[][] rgbPixels;
84
85     void encodeStart(int width, int height) throws IOException JavaDoc {
86         this.width = width;
87         this.height = height;
88         rgbPixels = new int[height][width];
89     }
90
91     void encodePixels(int x, int y, int w, int h, int[] rgbPixels, int off, int scansize)
92             throws IOException JavaDoc {
93         // Save the pixels.
94
for (int row = 0; row < h; ++row)
95             System.arraycopy(rgbPixels, row * scansize + off,
96                     this.rgbPixels[y + row], x, w);
97
98     }
99
100     Acme.IntHashtable colorHash;
101
102     void encodeDone() throws IOException JavaDoc {
103         int transparentIndex = -1;
104         int transparentRgb = -1;
105         // Put all the pixels into a hash table.
106
colorHash = new Acme.IntHashtable();
107         int index = 0;
108         for (int row = 0; row < height; ++row) {
109             int rowOffset = row * width;
110             for (int col = 0; col < width; ++col) {
111                 int rgb = rgbPixels[row][col];
112                 boolean isTransparent = ((rgb >>> 24) < 0x80);
113                 if (isTransparent) {
114                     if (transparentIndex < 0) {
115                         // First transparent color; remember it.
116
transparentIndex = index;
117                         transparentRgb = rgb;
118                     } else if (rgb != transparentRgb) {
119                         // A second transparent color; replace it with
120
// the first one.
121
rgbPixels[row][col] = rgb = transparentRgb;
122                     }
123                 }
124                 GifEncoderHashitem item =
125                         (GifEncoderHashitem) colorHash.get(rgb);
126                 if (item == null) {
127                     if (index >= 256)
128                         throw new IOException JavaDoc("too many colors for a GIF");
129                     item = new GifEncoderHashitem(rgb, 1, index, isTransparent);
130                     ++index;
131                     colorHash.put(rgb, item);
132                 } else
133                     ++item.count;
134             }
135         }
136
137         // Figure out how many bits to use.
138
int logColors;
139         if (index <= 2)
140             logColors = 1;
141         else if (index <= 4)
142             logColors = 2;
143         else if (index <= 16)
144             logColors = 4;
145         else
146             logColors = 8;
147
148         // Turn colors into colormap entries.
149
int mapSize = 1 << logColors;
150         byte[] reds = new byte[mapSize];
151         byte[] grns = new byte[mapSize];
152         byte[] blus = new byte[mapSize];
153         for (Enumeration JavaDoc e = colorHash.elements(); e.hasMoreElements();) {
154             GifEncoderHashitem item = (GifEncoderHashitem) e.nextElement();
155             reds[item.index] = (byte) ((item.rgb >> 16) & 0xff);
156             grns[item.index] = (byte) ((item.rgb >> 8) & 0xff);
157             blus[item.index] = (byte) (item.rgb & 0xff);
158         }
159
160         GIFEncode(out, width, height, interlace, (byte) 0, transparentIndex,
161                 logColors, reds, grns, blus);
162     }
163
164     byte GetPixel(int x, int y) throws IOException JavaDoc {
165         GifEncoderHashitem item =
166                 (GifEncoderHashitem) colorHash.get(rgbPixels[y][x]);
167         if (item == null)
168             throw new IOException JavaDoc("color not found");
169         return (byte) item.index;
170     }
171
172     static void writeString(OutputStream JavaDoc out, String JavaDoc str) throws IOException JavaDoc {
173         byte[] buf = str.getBytes();
174         out.write(buf);
175     }
176
177     // Adapted from ppmtogif, which is based on GIFENCOD by David
178
// Rowley <mgardi@watdscu.waterloo.edu>. Lempel-Zim compression
179
// based on "compress".
180

181     int Width, Height;
182     boolean Interlace;
183     int curx, cury;
184     int CountDown;
185     int Pass = 0;
186
187     void GIFEncode(OutputStream JavaDoc outs, int Width, int Height, boolean Interlace, byte Background, int Transparent, int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue)
188             throws IOException JavaDoc {
189         byte B;
190         int LeftOfs, TopOfs;
191         int ColorMapSize;
192         int InitCodeSize;
193         int i;
194
195         this.Width = Width;
196         this.Height = Height;
197         this.Interlace = Interlace;
198         ColorMapSize = 1 << BitsPerPixel;
199         LeftOfs = TopOfs = 0;
200
201         // Calculate number of bits we are expecting
202
CountDown = Width * Height;
203
204         // Indicate which pass we are on (if interlace)
205
Pass = 0;
206
207         // The initial code size
208
if (BitsPerPixel <= 1)
209             InitCodeSize = 2;
210         else
211             InitCodeSize = BitsPerPixel;
212
213         // Set up the current x and y position
214
curx = 0;
215         cury = 0;
216
217         // Write the Magic header
218
writeString(outs, "GIF89a");
219
220         // Write out the screen width and height
221
Putword(Width, outs);
222         Putword(Height, outs);
223
224         // Indicate that there is a global colour map
225
B = (byte) 0x80; // Yes, there is a color map
226
// OR in the resolution
227
B |= (byte) ((8 - 1) << 4);
228         // Not sorted
229
// OR in the Bits per Pixel
230
B |= (byte) ((BitsPerPixel - 1));
231
232         // Write it out
233
Putbyte(B, outs);
234
235         // Write out the Background colour
236
Putbyte(Background, outs);
237
238         // Pixel aspect ratio - 1:1.
239
//Putbyte( (byte) 49, outs );
240
// Java's GIF reader currently has a bug, if the aspect ratio byte is
241
// not zero it throws an ImageFormatException. It doesn't know that
242
// 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
243
// the other decoders I've tried so it probably doesn't hurt.
244
Putbyte((byte) 0, outs);
245
246         // Write out the Global Colour Map
247
for (i = 0; i < ColorMapSize; ++i) {
248             Putbyte(Red[i], outs);
249             Putbyte(Green[i], outs);
250             Putbyte(Blue[i], outs);
251         }
252
253         // Write out extension for transparent colour index, if necessary.
254
if (Transparent != -1) {
255             Putbyte((byte) '!', outs);
256             Putbyte((byte) 0xf9, outs);
257             Putbyte((byte) 4, outs);
258             Putbyte((byte) 1, outs);
259             Putbyte((byte) 0, outs);
260             Putbyte((byte) 0, outs);
261             Putbyte((byte) Transparent, outs);
262             Putbyte((byte) 0, outs);
263         }
264
265         // Write an Image separator
266
Putbyte((byte) ',', outs);
267
268         // Write the Image header
269
Putword(LeftOfs, outs);
270         Putword(TopOfs, outs);
271         Putword(Width, outs);
272         Putword(Height, outs);
273
274         // Write out whether or not the image is interlaced
275
if (Interlace)
276             Putbyte((byte) 0x40, outs);
277         else
278             Putbyte((byte) 0x00, outs);
279
280         // Write out the initial code size
281
Putbyte((byte) InitCodeSize, outs);
282
283         // Go and actually compress the data
284
compress(InitCodeSize + 1, outs);
285
286         // Write out a Zero-length packet (to end the series)
287
Putbyte((byte) 0, outs);
288
289         // Write the GIF file terminator
290
Putbyte((byte) ';', outs);
291     }
292
293     // Bump the 'curx' and 'cury' to point to the next pixel
294
void BumpPixel() {
295         // Bump the current X position
296
++curx;
297
298         // If we are at the end of a scan line, set curx back to the beginning
299
// If we are interlaced, bump the cury to the appropriate spot,
300
// otherwise, just increment it.
301
if (curx == Width) {
302             curx = 0;
303
304             if (!Interlace)
305                 ++cury;
306             else {
307                 switch (Pass) {
308                     case 0:
309                         cury += 8;
310                         if (cury >= Height) {
311                             ++Pass;
312                             cury = 4;
313                         }
314                         break;
315
316                     case 1:
317                         cury += 8;
318                         if (cury >= Height) {
319                             ++Pass;
320                             cury = 2;
321                         }
322                         break;
323
324                     case 2:
325                         cury += 4;
326                         if (cury >= Height) {
327                             ++Pass;
328                             cury = 1;
329                         }
330                         break;
331
332                     case 3:
333                         cury += 2;
334                         break;
335                 }
336             }
337         }
338     }
339
340     static final int EOF = -1;
341
342     // Return the next pixel from the image
343
int GIFNextPixel() throws IOException JavaDoc {
344         byte r;
345
346         if (CountDown == 0)
347             return EOF;
348
349         --CountDown;
350
351         r = GetPixel(curx, cury);
352
353         BumpPixel();
354
355         return r & 0xff;
356     }
357
358     // Write out a word to the GIF file
359
void Putword(int w, OutputStream JavaDoc outs) throws IOException JavaDoc {
360         Putbyte((byte) (w & 0xff), outs);
361         Putbyte((byte) ((w >> 8) & 0xff), outs);
362     }
363
364     // Write out a byte to the GIF file
365
void Putbyte(byte b, OutputStream JavaDoc outs) throws IOException JavaDoc {
366         outs.write(b);
367     }
368
369
370     // GIFCOMPR.C - GIF Image compression routines
371
//
372
// Lempel-Ziv compression based on 'compress'. GIF modifications by
373
// David Rowley (mgardi@watdcsu.waterloo.edu)
374

375     // General DEFINEs
376

377     static final int BITS = 12;
378
379     static final int HSIZE = 5003; // 80% occupancy
380

381     // GIF Image compression - modified 'compress'
382
//
383
// Based on: compress.c - File compression ala IEEE Computer, June 1984.
384
//
385
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
386
// Jim McKie (decvax!mcvax!jim)
387
// Steve Davies (decvax!vax135!petsd!peora!srd)
388
// Ken Turkowski (decvax!decwrl!turtlevax!ken)
389
// James A. Woods (decvax!ihnp4!ames!jaw)
390
// Joe Orost (decvax!vax135!petsd!joe)
391

392     int n_bits; // number of bits/code
393
int maxbits = BITS; // user settable max # bits/code
394
int maxcode; // maximum code, given n_bits
395
int maxmaxcode = 1 << BITS; // should NEVER generate this code
396

397     final int MAXCODE(int n_bits) {
398         return (1 << n_bits) - 1;
399     }
400
401     int[] htab = new int[HSIZE];
402     int[] codetab = new int[HSIZE];
403
404     int hsize = HSIZE; // for dynamic table sizing
405

406     int free_ent = 0; // first unused entry
407

408     // block compression parameters -- after all codes are used up,
409
// and compression rate changes, start over.
410
boolean clear_flg = false;
411
412     // Algorithm: use open addressing double hashing (no chaining) on the
413
// prefix code / next character combination. We do a variant of Knuth's
414
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
415
// secondary probe. Here, the modular division first probe is gives way
416
// to a faster exclusive-or manipulation. Also do block compression with
417
// an adaptive reset, whereby the code table is cleared when the compression
418
// ratio decreases, but after the table fills. The variable-length output
419
// codes are re-sized at this point, and a special CLEAR code is generated
420
// for the decompressor. Late addition: construct the table according to
421
// file size for noticeable speed improvement on small files. Please direct
422
// questions about this implementation to ames!jaw.
423

424     int g_init_bits;
425
426     int ClearCode;
427     int EOFCode;
428
429     void compress(int init_bits, OutputStream JavaDoc outs) throws IOException JavaDoc {
430         int fcode;
431         int i /* = 0 */;
432         int c;
433         int ent;
434         int disp;
435         int hsize_reg;
436         int hshift;
437
438         // Set up the globals: g_init_bits - initial number of bits
439
g_init_bits = init_bits;
440
441         // Set up the necessary values
442
clear_flg = false;
443         n_bits = g_init_bits;
444         maxcode = MAXCODE(n_bits);
445
446         ClearCode = 1 << (init_bits - 1);
447         EOFCode = ClearCode + 1;
448         free_ent = ClearCode + 2;
449
450         char_init();
451
452         ent = GIFNextPixel();
453
454         hshift = 0;
455         for (fcode = hsize; fcode < 65536; fcode *= 2)
456             ++hshift;
457         hshift = 8 - hshift; // set hash code range bound
458

459         hsize_reg = hsize;
460         cl_hash(hsize_reg); // clear hash table
461

462         output(ClearCode, outs);
463
464         outer_loop:
465         while ((c = GIFNextPixel()) != EOF) {
466             fcode = (c << maxbits) + ent;
467             i = (c << hshift) ^ ent; // xor hashing
468

469             if (htab[i] == fcode) {
470                 ent = codetab[i];
471                 continue;
472             } else if (htab[i] >= 0) // non-empty slot
473
{
474                 disp = hsize_reg - i; // secondary hash (after G. Knott)
475
if (i == 0)
476                     disp = 1;
477                 do {
478                     if ((i -= disp) < 0)
479                         i += hsize_reg;
480
481                     if (htab[i] == fcode) {
482                         ent = codetab[i];
483                         continue outer_loop;
484                     }
485                 } while (htab[i] >= 0);
486             }
487             output(ent, outs);
488             ent = c;
489             if (free_ent < maxmaxcode) {
490                 codetab[i] = free_ent++; // code -> hashtable
491
htab[i] = fcode;
492             } else
493                 cl_block(outs);
494         }
495         // Put out the final code.
496
output(ent, outs);
497         output(EOFCode, outs);
498     }
499
500     // output
501
//
502
// Output the given code.
503
// Inputs:
504
// code: A n_bits-bit integer. If == -1, then EOF. This assumes
505
// that n_bits =< wordsize - 1.
506
// Outputs:
507
// Outputs code to the file.
508
// Assumptions:
509
// Chars are 8 bits long.
510
// Algorithm:
511
// Maintain a BITS character long buffer (so that 8 codes will
512
// fit in it exactly). Use the VAX insv instruction to insert each
513
// code in turn. When the buffer fills up empty it and start over.
514

515     int cur_accum = 0;
516     int cur_bits = 0;
517
518     int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
519                    0x001F, 0x003F, 0x007F, 0x00FF,
520                    0x01FF, 0x03FF, 0x07FF, 0x0FFF,
521                    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
522
523     void output(int code, OutputStream JavaDoc outs) throws IOException JavaDoc {
524         cur_accum &= masks[cur_bits];
525
526         if (cur_bits > 0)
527             cur_accum |= (code << cur_bits);
528         else
529             cur_accum = code;
530
531         cur_bits += n_bits;
532
533         while (cur_bits >= 8) {
534             char_out((byte) (cur_accum & 0xff), outs);
535             cur_accum >>= 8;
536             cur_bits -= 8;
537         }
538
539         // If the next entry is going to be too big for the code size,
540
// then increase it, if possible.
541
if (free_ent > maxcode || clear_flg) {
542             if (clear_flg) {
543                 maxcode = MAXCODE(n_bits = g_init_bits);
544                 clear_flg = false;
545             } else {
546                 ++n_bits;
547                 if (n_bits == maxbits)
548                     maxcode = maxmaxcode;
549                 else
550                     maxcode = MAXCODE(n_bits);
551             }
552         }
553
554         if (code == EOFCode) {
555             // At EOF, write the rest of the buffer.
556
while (cur_bits > 0) {
557                 char_out((byte) (cur_accum & 0xff), outs);
558                 cur_accum >>= 8;
559                 cur_bits -= 8;
560             }
561
562             flush_char(outs);
563         }
564     }
565
566     // Clear out the hash table
567

568     // table clear for block compress
569
void cl_block(OutputStream JavaDoc outs) throws IOException JavaDoc {
570         cl_hash(hsize);
571         free_ent = ClearCode + 2;
572         clear_flg = true;
573
574         output(ClearCode, outs);
575     }
576
577     // reset code table
578
void cl_hash(int hsize) {
579         for (int i = 0; i < hsize; ++i)
580             htab[i] = -1;
581     }
582
583     // GIF Specific routines
584

585     // Number of characters so far in this 'packet'
586
int a_count;
587
588     // Set up the 'byte output' routine
589
void char_init() {
590         a_count = 0;
591     }
592
593     // Define the storage for the packet accumulator
594
byte[] accum = new byte[256];
595
596     // Add a character to the end of the current packet, and if it is 254
597
// characters, flush the packet to disk.
598
void char_out(byte c, OutputStream JavaDoc outs) throws IOException JavaDoc {
599         accum[a_count++] = c;
600         if (a_count >= 254)
601             flush_char(outs);
602     }
603
604     // Flush the packet to disk, and reset the accumulator
605
void flush_char(OutputStream JavaDoc outs) throws IOException JavaDoc {
606         if (a_count > 0) {
607             outs.write(a_count);
608             outs.write(accum, 0, a_count);
609             a_count = 0;
610         }
611     }
612
613 }
614
615 class GifEncoderHashitem {
616
617     public int rgb;
618     public int count;
619     public int index;
620     public boolean isTransparent;
621
622     public GifEncoderHashitem(int rgb, int count, int index, boolean isTransparent) {
623         this.rgb = rgb;
624         this.count = count;
625         this.index = index;
626         this.isTransparent = isTransparent;
627     }
628
629 }
630
Popular Tags