View Javadoc

1   package org.freehep.graphicsio.gif;
2   
3   // GifEncoder - write out an image as a GIF
4   //
5   // Transparency handling and variable bit size courtesy of Jack Palevich.
6   //
7   // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
8   //
9   // Redistribution and use in source and binary forms, with or without
10  // modification, are permitted provided that the following conditions
11  // are met:
12  // 1. Redistributions of source code must retain the above copyright
13  //    notice, this list of conditions and the following disclaimer.
14  // 2. Redistributions in binary form must reproduce the above copyright
15  //    notice, this list of conditions and the following disclaimer in the
16  //    documentation and/or other materials provided with the distribution.
17  //
18  // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  // ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  // SUCH DAMAGE.
29  //
30  // Visit the ACME Labs Java page for up-to-date versions of this and other
31  // fine Java utilities: http://www.acme.com/java/
32  
33  //package Acme.JPM.Encoders;
34  
35  import java.awt.Image;
36  import java.awt.image.ImageProducer;
37  import java.io.DataOutput;
38  import java.io.DataOutputStream;
39  import java.io.IOException;
40  import java.io.OutputStream;
41  
42  //import org.freehep.graphicsio.ImageEncoder;
43  
44  /// Write out an image as a GIF.
45  // <P>
46  // <A HREF="/resources/classes/Acme/JPM/Encoders/GifEncoder.java">Fetch the software.</A><BR>
47  // <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
48  // <P>
49  // @see ToGif
50  
51  public class GIFEncoder extends ImageEncoder {
52  
53      private boolean interlace = false;
54  
55      private String comment = null;
56  
57      // / Constructor from Image.
58      // @param img The image to encode.
59      // @param out The stream to write the GIF to.
60      public GIFEncoder(Image img, OutputStream out) throws IOException {
61          this(img, out, false);
62      }
63  
64      public GIFEncoder(Image img, DataOutput dos) throws IOException {
65          this(img, dos, false);
66      }
67  
68      // / Constructor from Image with interlace setting.
69      // @param img The image to encode.
70      // @param out The stream to write the GIF to.
71      // @param interlace Whether to interlace.
72      public GIFEncoder(Image img, OutputStream out, boolean interlace)
73              throws IOException {
74          this(img, (DataOutput) new DataOutputStream(out), interlace);
75      }
76  
77      public GIFEncoder(Image img, DataOutput dos, boolean interlace)
78              throws IOException {
79          super(img, dos);
80          this.interlace = interlace;
81      }
82  
83      // / Constructor from ImageProducer.
84      // @param prod The ImageProducer to encode.
85      // @param out The stream to write the GIF to.
86      public GIFEncoder(ImageProducer prod, OutputStream out) throws IOException {
87          this(prod, out, false);
88      }
89  
90      public GIFEncoder(ImageProducer prod, DataOutput dos) throws IOException {
91          this(prod, dos, false);
92      }
93  
94      // / Constructor from ImageProducer with interlace setting.
95      // @param prod The ImageProducer to encode.
96      // @param out The stream to write the GIF to.
97      public GIFEncoder(ImageProducer prod, OutputStream out, boolean interlace)
98              throws IOException {
99          this(prod, (DataOutput) new DataOutputStream(out), interlace);
100     }
101 
102     public GIFEncoder(ImageProducer prod, DataOutput dos, boolean interlace)
103             throws IOException {
104         super(prod, dos);
105         this.interlace = interlace;
106     }
107 
108     public void setComment(String comment) {
109         this.comment = comment;
110     }
111 
112     int width, height;
113 
114     int[][] rgbPixels;
115 
116     protected void encodeStart(int width, int height) throws IOException {
117         this.width = width;
118         this.height = height;
119         rgbPixels = new int[height][width];
120     }
121 
122     protected void encodePixels(int x, int y, int w, int h, int[] rgbPixels,
123             int off, int scansize) throws IOException {
124         // Save the pixels.
125         for (int row = 0; row < h; ++row) {
126             System.arraycopy(rgbPixels, row * scansize + off, this.rgbPixels[y
127                     + row], x, w);
128         }
129     }
130 
131     protected void encodeDone() throws IOException {
132         // MD: added ColorMap for max color limitation
133         // returning a color palette (including one transparent color)
134         // and rgbPixels changes from colors into palette indices.
135 //        GIFColorMap colorMap = new GIFNearestColorMap();
136 //        GIFColorMap colorMap = new GIFPlainColorMap();
137         GIFColorMap colorMap = new GIFNeuralColorMap();
138         int[] palette = colorMap.create(rgbPixels, 255);
139         
140         // MD: look for first fully transparent color
141         int transparentIndex = -1;
142     	for (int i=0; i<palette.length; i++) { 
143             if ((palette[i] & 0xFF000000) == 0) {
144             	transparentIndex = i;
145             	break;
146             }
147         }
148         
149 /*
150     	for (int i=0; i<palette.length; i++) {
151             System.err.print(Integer.toHexString(palette[i])+", ");
152         }
153         System.err.println("\n n = "+palette.length+" "+ (transparentIndex >= 0 ? Integer.toHexString(palette[transparentIndex]) : "Not Transparent"));
154 */
155         // Figure out how many bits to use.
156         int logColors;
157         int nColors = palette.length;
158         if (nColors <= 2)
159             logColors = 1;
160         else if (nColors <= 4)
161             logColors = 2;
162         else if (nColors <= 16)
163             logColors = 4;
164         else
165             logColors = 8;
166 
167         GIFEncode(width, height, interlace, (byte) 0, transparentIndex,
168                 logColors, palette);
169     }
170 
171     byte GetPixel(int x, int y) throws IOException {
172         return (byte)rgbPixels[y][x];
173     }
174 
175     void writeString(String str) throws IOException {
176         byte[] buf = str.getBytes();
177         out.write(buf);
178     }
179 
180     // Adapted from ppmtogif, which is based on GIFENCOD by David
181     // Rowley <mgardi@watdscu.waterloo.edu>. Lempel-Zim compression
182     // based on "compress".
183 
184     int Width, Height;
185 
186     boolean Interlace;
187 
188     int curx, cury;
189 
190     int CountDown;
191 
192     int Pass = 0;
193 
194     void GIFEncode(int Width, int Height, boolean Interlace, byte Background,
195             int Transparent, int BitsPerPixel, int[] palette) throws IOException {
196 
197         byte B;
198         int LeftOfs, TopOfs;
199         int InitCodeSize;
200         int i;
201 
202         this.Width = Width;
203         this.Height = Height;
204         this.Interlace = Interlace;
205         LeftOfs = TopOfs = 0;
206 
207         // Calculate number of bits we are expecting
208         CountDown = Width * Height;
209 
210         // Indicate which pass we are on (if interlace)
211         Pass = 0;
212 
213         // The initial code size
214         if (BitsPerPixel <= 1)
215             InitCodeSize = 2;
216         else
217             InitCodeSize = BitsPerPixel;
218 
219         // Set up the current x and y position
220         curx = 0;
221         cury = 0;
222 
223         // Write the Magic header
224         writeString("GIF89a");
225 
226         // Write out the screen width and height
227         Putword(Width);
228         Putword(Height);
229 
230         // Indicate that there is a global colour map
231         B = (byte) 0x80; // Yes, there is a color map
232         // OR in the resolution
233         B |= (byte) ((8 - 1) << 4);
234         // Not sorted
235         // OR in the Bits per Pixel
236         B |= (byte) ((BitsPerPixel - 1));
237 
238         // Write it out
239         Putbyte(B);
240 
241         // Write out the Background colour
242         Putbyte(Background);
243 
244         // Pixel aspect ratio - 1:1.
245         // Putbyte( (byte) 49);
246         // Java's GIF reader currently has a bug, if the aspect ratio byte is
247         // not zero it throws an ImageFormatException. It doesn't know that
248         // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
249         // the other decoders I've tried so it probably doesn't hurt.
250         Putbyte((byte) 0);
251 
252         // Write out the Global Colour Map
253         int colorMapSize = 1 << BitsPerPixel;
254         for (i = 0; i < colorMapSize; ++i) {
255             if (i < palette.length) {
256                Putbyte((byte)((palette[i] >> 16) & 0xFF));
257                Putbyte((byte)((palette[i] >> 8) & 0xFF));
258                Putbyte((byte)((palette[i]) & 0xFF));
259             } else {
260                 Putbyte((byte)0);
261                 Putbyte((byte)0);
262                 Putbyte((byte)0);
263             }
264         }
265 
266         // Write out extension for transparent colour index, if necessary.
267         if (Transparent != -1) {
268             Putbyte((byte) '!');
269             Putbyte((byte) 0xf9);
270             Putbyte((byte) 4);
271             Putbyte((byte) 1);
272             Putbyte((byte) 0);
273             Putbyte((byte) 0);
274             Putbyte((byte) Transparent);
275             Putbyte((byte) 0);
276         }
277 
278         // Write an Image separator
279         Putbyte((byte) ',');
280 
281         // Write the Image header
282         Putword(LeftOfs);
283         Putword(TopOfs);
284         Putword(Width);
285         Putword(Height);
286 
287         // Write out whether or not the image is interlaced
288         if (Interlace)
289             Putbyte((byte) 0x40);
290         else
291             Putbyte((byte) 0x00);
292 
293         // Write out the initial code size
294         Putbyte((byte) InitCodeSize);
295 
296         // Go and actually compress the data
297         compress(InitCodeSize + 1);
298 
299         // Write out a Zero-length packet (to end the series)
300         Putbyte((byte) 0);
301 
302         // Write out the comment
303         if ((comment != null) && (comment.length() > 0)) {
304             Putbyte((byte) 0x21);
305             Putbyte((byte) 0xFE);
306             Putbyte((byte) comment.length());
307             writeString(comment);
308             Putbyte((byte) 0);
309         }
310 
311         // Write the GIF file terminator
312         Putbyte((byte) ';');
313     }
314 
315     // Bump the 'curx' and 'cury' to point to the next pixel
316     void BumpPixel() {
317         // Bump the current X position
318         ++curx;
319 
320         // If we are at the end of a scan line, set curx back to the beginning
321         // If we are interlaced, bump the cury to the appropriate spot,
322         // otherwise, just increment it.
323         if (curx == Width) {
324             curx = 0;
325 
326             if (!Interlace) {
327                 ++cury;
328             } else {
329                 switch (Pass) {
330                 case 0:
331                     cury += 8;
332                     if (cury >= Height) {
333                         ++Pass;
334                         cury = 4;
335                     }
336                     break;
337 
338                 case 1:
339                     cury += 8;
340                     if (cury >= Height) {
341                         ++Pass;
342                         cury = 2;
343                     }
344                     break;
345 
346                 case 2:
347                     cury += 4;
348                     if (cury >= Height) {
349                         ++Pass;
350                         cury = 1;
351                     }
352                     break;
353 
354                 case 3:
355                     cury += 2;
356                     break;
357                 }
358             }
359         }
360     }
361 
362     static final int EOF = -1;
363 
364     // Return the next pixel from the image
365     int GIFNextPixel() throws IOException {
366         byte r;
367 
368         if (CountDown == 0)
369             return EOF;
370 
371         --CountDown;
372 
373         r = GetPixel(curx, cury);
374 
375         BumpPixel();
376 
377         return r & 0xff;
378     }
379 
380     // Write out a word to the GIF file
381     void Putword(int w) throws IOException {
382         Putbyte((byte) (w & 0xff));
383         Putbyte((byte) ((w >> 8) & 0xff));
384     }
385 
386     // Write out a byte to the GIF file
387     void Putbyte(byte b) throws IOException {
388         out.write(b);
389     }
390 
391     // GIFCOMPR.C - GIF Image compression routines
392     //
393     // Lempel-Ziv compression based on 'compress'. GIF modifications by
394     // David Rowley (mgardi@watdcsu.waterloo.edu)
395 
396     // General DEFINEs
397 
398     static final int BITS = 12;
399 
400     static final int HSIZE = 5003; // 80% occupancy
401 
402     // GIF Image compression - modified 'compress'
403     //
404     // Based on: compress.c - File compression ala IEEE Computer, June 1984.
405     //
406     // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
407     // Jim McKie (decvax!mcvax!jim)
408     // Steve Davies (decvax!vax135!petsd!peora!srd)
409     // Ken Turkowski (decvax!decwrl!turtlevax!ken)
410     // James A. Woods (decvax!ihnp4!ames!jaw)
411     // Joe Orost (decvax!vax135!petsd!joe)
412 
413     int n_bits; // number of bits/code
414 
415     int maxbits = BITS; // user settable max # bits/code
416 
417     int maxcode; // maximum code, given n_bits
418 
419     int maxmaxcode = 1 << BITS; // should NEVER generate this code
420 
421     final int MAXCODE(int n_bits) {
422         return (1 << n_bits) - 1;
423     }
424 
425     int[] htab = new int[HSIZE];
426 
427     int[] codetab = new int[HSIZE];
428 
429     int hsize = HSIZE; // for dynamic table sizing
430 
431     int free_ent = 0; // first unused entry
432 
433     // block compression parameters -- after all codes are used up,
434     // and compression rate changes, start over.
435     boolean clear_flg = false;
436 
437     // Algorithm: use open addressing double hashing (no chaining) on the
438     // prefix code / next character combination. We do a variant of Knuth's
439     // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
440     // secondary probe. Here, the modular division first probe is gives way
441     // to a faster exclusive-or manipulation. Also do block compression with
442     // an adaptive reset, whereby the code table is cleared when the compression
443     // ratio decreases, but after the table fills. The variable-length output
444     // codes are re-sized at this point, and a special CLEAR code is generated
445     // for the decompressor. Late addition: construct the table according to
446     // file size for noticeable speed improvement on small files. Please direct
447     // questions about this implementation to ames!jaw.
448 
449     int g_init_bits;
450 
451     int ClearCode;
452 
453     int EOFCode;
454 
455     void compress(int init_bits) throws IOException {
456         int fcode;
457         int i /* = 0 */;
458         int c;
459         int ent;
460         int disp;
461         int hsize_reg;
462         int hshift;
463 
464         // Set up the globals: g_init_bits - initial number of bits
465         g_init_bits = init_bits;
466 
467         // Set up the necessary values
468         clear_flg = false;
469         n_bits = g_init_bits;
470         maxcode = MAXCODE(n_bits);
471 
472         ClearCode = 1 << (init_bits - 1);
473         EOFCode = ClearCode + 1;
474         free_ent = ClearCode + 2;
475 
476         char_init();
477 
478         ent = GIFNextPixel();
479 
480         hshift = 0;
481         for (fcode = hsize; fcode < 65536; fcode *= 2)
482             ++hshift;
483         hshift = 8 - hshift; // set hash code range bound
484 
485         hsize_reg = hsize;
486         cl_hash(hsize_reg); // clear hash table
487 
488         output(ClearCode);
489 
490         outer_loop: while ((c = GIFNextPixel()) != EOF) {
491             fcode = (c << maxbits) + ent;
492             i = (c << hshift) ^ ent; // xor hashing
493 
494             if (htab[i] == fcode) {
495                 ent = codetab[i];
496                 continue;
497             } else if (htab[i] >= 0) // non-empty slot
498             {
499                 disp = hsize_reg - i; // secondary hash (after G. Knott)
500                 if (i == 0)
501                     disp = 1;
502                 do {
503                     if ((i -= disp) < 0)
504                         i += hsize_reg;
505 
506                     if (htab[i] == fcode) {
507                         ent = codetab[i];
508                         continue outer_loop;
509                     }
510                 } while (htab[i] >= 0);
511             }
512             output(ent);
513             ent = c;
514             if (free_ent < maxmaxcode) {
515                 codetab[i] = free_ent++; // code -> hashtable
516                 htab[i] = fcode;
517             } else
518                 cl_block();
519         }
520         // Put out the final code.
521         output(ent);
522         output(EOFCode);
523     }
524 
525     // output
526     //
527     // Output the given code.
528     // Inputs:
529     // code: A n_bits-bit integer. If == -1, then EOF. This assumes
530     // that n_bits =< wordsize - 1.
531     // Outputs:
532     // Outputs code to the file.
533     // Assumptions:
534     // Chars are 8 bits long.
535     // Algorithm:
536     // Maintain a BITS character long buffer (so that 8 codes will
537     // fit in it exactly). Use the VAX insv instruction to insert each
538     // code in turn. When the buffer fills up empty it and start over.
539 
540     int cur_accum = 0;
541 
542     int cur_bits = 0;
543 
544     int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F,
545             0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF,
546             0x7FFF, 0xFFFF };
547 
548     void output(int code) throws IOException {
549         cur_accum &= masks[cur_bits];
550 
551         if (cur_bits > 0)
552             cur_accum |= (code << cur_bits);
553         else
554             cur_accum = code;
555 
556         cur_bits += n_bits;
557 
558         while (cur_bits >= 8) {
559             char_out((byte) (cur_accum & 0xff));
560             cur_accum >>= 8;
561             cur_bits -= 8;
562         }
563 
564         // If the next entry is going to be too big for the code size,
565         // then increase it, if possible.
566         if (free_ent > maxcode || clear_flg) {
567             if (clear_flg) {
568                 maxcode = MAXCODE(n_bits = g_init_bits);
569                 clear_flg = false;
570             } else {
571                 ++n_bits;
572                 if (n_bits == maxbits)
573                     maxcode = maxmaxcode;
574                 else
575                     maxcode = MAXCODE(n_bits);
576             }
577         }
578 
579         if (code == EOFCode) {
580             // At EOF, write the rest of the buffer.
581             while (cur_bits > 0) {
582                 char_out((byte) (cur_accum & 0xff));
583                 cur_accum >>= 8;
584                 cur_bits -= 8;
585             }
586 
587             flush_char();
588         }
589     }
590 
591     // Clear out the hash table
592 
593     // table clear for block compress
594     void cl_block() throws IOException {
595         cl_hash(hsize);
596         free_ent = ClearCode + 2;
597         clear_flg = true;
598 
599         output(ClearCode);
600     }
601 
602     // reset code table
603     void cl_hash(int hsize) {
604         for (int i = 0; i < hsize; ++i)
605             htab[i] = -1;
606     }
607 
608     // GIF Specific routines
609 
610     // Number of characters so far in this 'packet'
611     int a_count;
612 
613     // Set up the 'byte output' routine
614     void char_init() {
615         a_count = 0;
616     }
617 
618     // Define the storage for the packet accumulator
619     byte[] accum = new byte[256];
620 
621     // Add a character to the end of the current packet, and if it is 254
622     // characters, flush the packet to disk.
623     void char_out(byte c) throws IOException {
624         accum[a_count++] = c;
625         if (a_count >= 254)
626             flush_char();
627     }
628 
629     // Flush the packet to disk, and reset the accumulator
630     void flush_char() throws IOException {
631         if (a_count > 0) {
632             out.write(a_count);
633             out.write(accum, 0, a_count);
634             a_count = 0;
635         }
636     }
637 
638 }