Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: Acme/JPM/Encoders/GifEncoder.java


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  package Acme.JPM.Encoders;
31  
32  import java.awt.Image;
33  import java.awt.image.*;
34  
35  import java.io.*;
36  
37  import java.util.*;
38  
39  
40  /**
41   * Write out an image as a GIF.
42   * <P>
43   * <A HREF="/resources/classes/Acme/JPM/Encoders/GifEncoder.java">Fetch the software.</A><BR>
44   * <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
45   * <P>
46   * @see ToGif
47   * @cvsversion    $Revision: 1.3 $, $Date: 2003/10/27 10:10:19 $
48   */
49  public class GifEncoder extends ImageEncoder
50  {
51      //~ Static fields/initializers /////////////////////////////////////////////
52  
53      static final int EOF = -1;
54  
55      // GIFCOMPR.C       - GIF Image compression routines
56      //
57      // Lempel-Ziv compression based on 'compress'.  GIF modifications by
58      // David Rowley (mgardi@watdcsu.waterloo.edu)
59      // General DEFINEs
60      static final int BITS = 12;
61      static final int HSIZE = 5003; // 80% occupancy
62  
63      //~ Instance fields ////////////////////////////////////////////////////////
64  
65      Acme.IntHashtable colorHash;
66  
67      // Define the storage for the packet accumulator
68      byte[] accum = new byte[256];
69      int[] codetab = new int[HSIZE];
70      int[] htab = new int[HSIZE];
71      int[] masks = 
72      {
73          0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
74          0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
75      };
76      int[][] rgbPixels;
77      boolean Interlace;
78      boolean clear_flg = false;
79      int ClearCode;
80      int CountDown;
81      int EOFCode;
82  
83      // Adapted from ppmtogif, which is based on GIFENCOD by David
84      // Rowley <mgardi@watdscu.waterloo.edu>.  Lempel-Zim compression
85      // based on "compress".
86      int Height;
87      int Pass = 0;
88  
89      // Adapted from ppmtogif, which is based on GIFENCOD by David
90      // Rowley <mgardi@watdscu.waterloo.edu>.  Lempel-Zim compression
91      // based on "compress".
92      int Width;
93  
94      // GIF Specific routines
95      // Number of characters so far in this 'packet'
96      int a_count;
97  
98      // output
99      //
100     // Output the given code.
101     // Inputs:
102     //      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
103     //              that n_bits =< wordsize - 1.
104     // Outputs:
105     //      Outputs code to the file.
106     // Assumptions:
107     //      Chars are 8 bits long.
108     // Algorithm:
109     //      Maintain a BITS character long buffer (so that 8 codes will
110     // fit in it exactly).  Use the VAX insv instruction to insert each
111     // code in turn.  When the buffer fills up empty it and start over.
112     int cur_accum = 0;
113     int cur_bits = 0;
114     int curx;
115     int cury;
116     int free_ent = 0; // first unused entry
117     int g_init_bits;
118     int height;
119     int hsize = HSIZE; // for dynamic table sizing
120     int maxbits = BITS; // user settable max # bits/code
121     int maxcode; // maximum code, given n_bits
122     int maxmaxcode = 1 << BITS; // should NEVER generate this code
123     int n_bits; // number of bits/code
124     int width;
125     private boolean interlace = false;
126 
127     //~ Constructors ///////////////////////////////////////////////////////////
128 
129     /// Constructor from Image.
130     // @param img The image to encode.
131     // @param out The stream to write the GIF to.
132     public GifEncoder(Image img, OutputStream out) throws IOException
133     {
134         super(img, out);
135     }
136 
137     /// Constructor from Image with interlace setting.
138     // @param img The image to encode.
139     // @param out The stream to write the GIF to.
140     // @param interlace Whether to interlace.
141     public GifEncoder(Image img, OutputStream out, boolean interlace)
142         throws IOException
143     {
144         super(img, out);
145         this.interlace = interlace;
146     }
147 
148     /// Constructor from ImageProducer.
149     // @param prod The ImageProducer to encode.
150     // @param out The stream to write the GIF to.
151     public GifEncoder(ImageProducer prod, OutputStream out)
152         throws IOException
153     {
154         super(prod, out);
155     }
156 
157     /// Constructor from ImageProducer with interlace setting.
158     // @param prod The ImageProducer to encode.
159     // @param out The stream to write the GIF to.
160     public GifEncoder(ImageProducer prod, OutputStream out, boolean interlace)
161         throws IOException
162     {
163         super(prod, out);
164         this.interlace = interlace;
165     }
166 
167     //~ Methods ////////////////////////////////////////////////////////////////
168 
169     byte GetPixel(int x, int y) throws IOException
170     {
171         GifEncoderHashitem item = (GifEncoderHashitem) colorHash.get(rgbPixels[y][x]);
172 
173         if (item == null)
174         {
175             throw new IOException("color not found");
176         }
177 
178         return (byte) item.index;
179     }
180 
181     final int MAXCODE(int n_bits)
182     {
183         return (1 << n_bits) - 1;
184     }
185 
186     // Write out a byte to the GIF file
187     void Putbyte(byte b, OutputStream outs) throws IOException
188     {
189         outs.write(b);
190     }
191 
192     // Set up the 'byte output' routine
193     void char_init()
194     {
195         a_count = 0;
196     }
197 
198     // Add a character to the end of the current packet, and if it is 254
199     // characters, flush the packet to disk.
200     void char_out(byte c, OutputStream outs) throws IOException
201     {
202         accum[a_count++] = c;
203 
204         if (a_count >= 254)
205         {
206             flush_char(outs);
207         }
208     }
209 
210     // Clear out the hash table
211     // table clear for block compress
212     void cl_block(OutputStream outs) throws IOException
213     {
214         cl_hash(hsize);
215         free_ent = ClearCode + 2;
216         clear_flg = true;
217 
218         output(ClearCode, outs);
219     }
220 
221     // reset code table
222     void cl_hash(int hsize)
223     {
224         for (int i = 0; i < hsize; ++i)
225         {
226             htab[i] = -1;
227         }
228     }
229 
230     void compress(int init_bits, OutputStream outs) throws IOException
231     {
232         int fcode;
233         int i /* = 0 */;
234         int c;
235         int ent;
236         int disp;
237         int hsize_reg;
238         int hshift;
239 
240         // Set up the globals:  g_init_bits - initial number of bits
241         g_init_bits = init_bits;
242 
243         // Set up the necessary values
244         clear_flg = false;
245         n_bits = g_init_bits;
246         maxcode = MAXCODE(n_bits);
247 
248         ClearCode = 1 << (init_bits - 1);
249         EOFCode = ClearCode + 1;
250         free_ent = ClearCode + 2;
251 
252         char_init();
253 
254         ent = GIFNextPixel();
255 
256         hshift = 0;
257 
258         for (fcode = hsize; fcode < 65536; fcode *= 2)
259         {
260             ++hshift;
261         }
262 
263         hshift = 8 - hshift; // set hash code range bound
264 
265         hsize_reg = hsize;
266         cl_hash(hsize_reg); // clear hash table
267 
268         output(ClearCode, outs);
269 
270 outer_loop: 
271         while ((c = GIFNextPixel()) != EOF)
272         {
273             fcode = (c << maxbits) + ent;
274             i = (c << hshift) ^ ent; // xor hashing
275 
276             if (htab[i] == fcode)
277             {
278                 ent = codetab[i];
279 
280                 continue;
281             }
282             else if (htab[i] >= 0) // non-empty slot
283             {
284                 disp = hsize_reg - i; // secondary hash (after G. Knott)
285 
286                 if (i == 0)
287                 {
288                     disp = 1;
289                 }
290 
291                 do
292                 {
293                     if ((i -= disp) < 0)
294                     {
295                         i += hsize_reg;
296                     }
297 
298                     if (htab[i] == fcode)
299                     {
300                         ent = codetab[i];
301 
302                         continue outer_loop;
303                     }
304                 }
305                  while (htab[i] >= 0);
306             }
307 
308             output(ent, outs);
309             ent = c;
310 
311             if (free_ent < maxmaxcode)
312             {
313                 codetab[i] = free_ent++; // code -> hashtable
314                 htab[i] = fcode;
315             }
316             else
317             {
318                 cl_block(outs);
319             }
320         }
321 
322         // Put out the final code.
323         output(ent, outs);
324         output(EOFCode, outs);
325     }
326 
327     void encodeDone() throws IOException
328     {
329         int transparentIndex = -1;
330         int transparentRgb = -1;
331 
332         // Put all the pixels into a hash table.
333         colorHash = new Acme.IntHashtable();
334 
335         int index = 0;
336 
337         for (int row = 0; row < height; ++row)
338         {
339             int rowOffset = row * width;
340 
341             for (int col = 0; col < width; ++col)
342             {
343                 int rgb = rgbPixels[row][col];
344                 boolean isTransparent = ((rgb >>> 24) < 0x80);
345 
346                 if (isTransparent)
347                 {
348                     if (transparentIndex < 0)
349                     {
350                         // First transparent color; remember it.
351                         transparentIndex = index;
352                         transparentRgb = rgb;
353                     }
354                     else if (rgb != transparentRgb)
355                     {
356                         // A second transparent color; replace it with
357                         // the first one.
358                         rgbPixels[row][col] = rgb = transparentRgb;
359                     }
360                 }
361 
362                 GifEncoderHashitem item = (GifEncoderHashitem) colorHash.get(rgb);
363 
364                 if (item == null)
365                 {
366                     if (index >= 256)
367                     {
368                         throw new IOException("too many colors for a GIF");
369                     }
370 
371                     item = new GifEncoderHashitem(rgb, 1, index, isTransparent);
372                     ++index;
373                     colorHash.put(rgb, item);
374                 }
375                 else
376                 {
377                     ++item.count;
378                 }
379             }
380         }
381 
382         // Figure out how many bits to use.
383         int logColors;
384 
385         if (index <= 2)
386         {
387             logColors = 1;
388         }
389         else if (index <= 4)
390         {
391             logColors = 2;
392         }
393         else if (index <= 16)
394         {
395             logColors = 4;
396         }
397         else
398         {
399             logColors = 8;
400         }
401 
402         // Turn colors into colormap entries.
403         int mapSize = 1 << logColors;
404         byte[] reds = new byte[mapSize];
405         byte[] grns = new byte[mapSize];
406         byte[] blus = new byte[mapSize];
407 
408         for (Enumeration e = colorHash.elements(); e.hasMoreElements();)
409         {
410             GifEncoderHashitem item = (GifEncoderHashitem) e.nextElement();
411             reds[item.index] = (byte) ((item.rgb >> 16) & 0xff);
412             grns[item.index] = (byte) ((item.rgb >> 8) & 0xff);
413             blus[item.index] = (byte) (item.rgb & 0xff);
414         }
415 
416         GIFEncode(out, width, height, interlace, (byte) 0, transparentIndex,
417             logColors, reds, grns, blus);
418     }
419 
420     void encodePixels(int x, int y, int w, int h, int[] rgbPixels, int off,
421         int scansize) throws IOException
422     {
423         // Save the pixels.
424         for (int row = 0; row < h; ++row)
425         {
426             System.arraycopy(rgbPixels, (row * scansize) + off,
427                 this.rgbPixels[y + row], x, w);
428         }
429     }
430 
431     void encodeStart(int width, int height) throws IOException
432     {
433         this.width = width;
434         this.height = height;
435         rgbPixels = new int[height][width];
436     }
437 
438     static void writeString(OutputStream out, String str)
439         throws IOException
440     {
441         byte[] buf = str.getBytes();
442         out.write(buf);
443     }
444 
445     // Bump the 'curx' and 'cury' to point to the next pixel
446     void BumpPixel()
447     {
448         ++curx;
449 
450         // If we are at the end of a scan line, set curx back to the beginning
451         // If we are interlaced, bump the cury to the appropriate spot,
452         // otherwise, just increment it.
453         if (curx == Width)
454         {
455             curx = 0;
456 
457             if (!Interlace)
458             {
459                 ++cury;
460             }
461             else
462             {
463                 switch (Pass)
464                 {
465                 case 0:
466                     cury += 8;
467 
468                     if (cury >= Height)
469                     {
470                         ++Pass;
471                         cury = 4;
472                     }
473 
474                     break;
475 
476                 case 1:
477                     cury += 8;
478 
479                     if (cury >= Height)
480                     {
481                         ++Pass;
482                         cury = 2;
483                     }
484 
485                     break;
486 
487                 case 2:
488                     cury += 4;
489 
490                     if (cury >= Height)
491                     {
492                         ++Pass;
493                         cury = 1;
494                     }
495 
496                     break;
497 
498                 case 3:
499                     cury += 2;
500 
501                     break;
502                 }
503             }
504         }
505     }
506 
507     void GIFEncode(OutputStream outs, int Width, int Height, boolean Interlace,
508         byte Background, int Transparent, int BitsPerPixel, byte[] Red,
509         byte[] Green, byte[] Blue) throws IOException
510     {
511         byte B;
512         int LeftOfs;
513         int TopOfs;
514         int ColorMapSize;
515         int InitCodeSize;
516         int i;
517 
518         this.Width = Width;
519         this.Height = Height;
520         this.Interlace = Interlace;
521         ColorMapSize = 1 << BitsPerPixel;
522         LeftOfs = TopOfs = 0;
523 
524         // Calculate number of bits we are expecting
525         CountDown = Width * Height;
526 
527         // Indicate which pass we are on (if interlace)
528         Pass = 0;
529 
530         // The initial code size
531         if (BitsPerPixel <= 1)
532         {
533             InitCodeSize = 2;
534         }
535         else
536         {
537             InitCodeSize = BitsPerPixel;
538         }
539 
540         // Set up the current x and y position
541         curx = 0;
542         cury = 0;
543 
544         // Write the Magic header
545         writeString(outs, "GIF89a");
546 
547         // Write out the screen width and height
548         Putword(Width, outs);
549         Putword(Height, outs);
550 
551         // Indicate that there is a global colour map
552         B = (byte) 0x80; // Yes, there is a color map
553 
554         // OR in the resolution
555         B |= (byte) ((8 - 1) << 4);
556 
557         // Not sorted
558         // OR in the Bits per Pixel
559         B |= (byte) ((BitsPerPixel - 1));
560 
561         // Write it out
562         Putbyte(B, outs);
563 
564         // Write out the Background colour
565         Putbyte(Background, outs);
566 
567         // Pixel aspect ratio - 1:1.
568         //Putbyte( (byte) 49, outs );
569         // Java's GIF reader currently has a bug, if the aspect ratio byte is
570         // not zero it throws an ImageFormatException.  It doesn't know that
571         // 49 means a 1:1 aspect ratio.  Well, whatever, zero works with all
572         // the other decoders I've tried so it probably doesn't hurt.
573         Putbyte((byte) 0, outs);
574 
575         // Write out the Global Colour Map
576         for (i = 0; i < ColorMapSize; ++i)
577         {
578             Putbyte(Red[i], outs);
579             Putbyte(Green[i], outs);
580             Putbyte(Blue[i], outs);
581         }
582 
583         // Write out extension for transparent colour index, if necessary.
584         if (Transparent != -1)
585         {
586             Putbyte((byte) '!', outs);
587             Putbyte((byte) 0xf9, outs);
588             Putbyte((byte) 4, outs);
589             Putbyte((byte) 1, outs);
590             Putbyte((byte) 0, outs);
591             Putbyte((byte) 0, outs);
592             Putbyte((byte) Transparent, outs);
593             Putbyte((byte) 0, outs);
594         }
595 
596         // Write an Image separator
597         Putbyte((byte) ',', outs);
598 
599         // Write the Image header
600         Putword(LeftOfs, outs);
601         Putword(TopOfs, outs);
602         Putword(Width, outs);
603         Putword(Height, outs);
604 
605         // Write out whether or not the image is interlaced
606         if (Interlace)
607         {
608             Putbyte((byte) 0x40, outs);
609         }
610         else
611         {
612             Putbyte((byte) 0x00, outs);
613         }
614 
615         // Write out the initial code size
616         Putbyte((byte) InitCodeSize, outs);
617 
618         // Go and actually compress the data
619         compress(InitCodeSize + 1, outs);
620 
621         // Write out a Zero-length packet (to end the series)
622         Putbyte((byte) 0, outs);
623 
624         // Write the GIF file terminator
625         Putbyte((byte) ';', outs);
626     }
627 
628     // Return the next pixel from the image
629     int GIFNextPixel() throws IOException
630     {
631         byte r;
632 
633         if (CountDown == 0)
634         {
635             return EOF;
636         }
637 
638         --CountDown;
639 
640         r = GetPixel(curx, cury);
641 
642         BumpPixel();
643 
644         return r & 0xff;
645     }
646 
647     // Write out a word to the GIF file
648     void Putword(int w, OutputStream outs) throws IOException
649     {
650         Putbyte((byte) (w & 0xff), outs);
651         Putbyte((byte) ((w >> 8) & 0xff), outs);
652     }
653 
654     // Flush the packet to disk, and reset the accumulator
655     void flush_char(OutputStream outs) throws IOException
656     {
657         if (a_count > 0)
658         {
659             outs.write(a_count);
660             outs.write(accum, 0, a_count);
661             a_count = 0;
662         }
663     }
664 
665     void output(int code, OutputStream outs) throws IOException
666     {
667         cur_accum &= masks[cur_bits];
668 
669         if (cur_bits > 0)
670         {
671             cur_accum |= (code << cur_bits);
672         }
673         else
674         {
675             cur_accum = code;
676         }
677 
678         cur_bits += n_bits;
679 
680         while (cur_bits >= 8)
681         {
682             char_out((byte) (cur_accum & 0xff), outs);
683             cur_accum >>= 8;
684             cur_bits -= 8;
685         }
686 
687         // If the next entry is going to be too big for the code size,
688         // then increase it, if possible.
689         if ((free_ent > maxcode) || clear_flg)
690         {
691             if (clear_flg)
692             {
693                 maxcode = MAXCODE(n_bits = g_init_bits);
694                 clear_flg = false;
695             }
696             else
697             {
698                 ++n_bits;
699 
700                 if (n_bits == maxbits)
701                 {
702                     maxcode = maxmaxcode;
703                 }
704                 else
705                 {
706                     maxcode = MAXCODE(n_bits);
707                 }
708             }
709         }
710 
711         if (code == EOFCode)
712         {
713             // At EOF, write the rest of the buffer.
714             while (cur_bits > 0)
715             {
716                 char_out((byte) (cur_accum & 0xff), outs);
717                 cur_accum >>= 8;
718                 cur_bits -= 8;
719             }
720 
721             flush_char(outs);
722         }
723     }
724 }
725 
726 class GifEncoderHashitem
727 {
728     //~ Instance fields ////////////////////////////////////////////////////////
729 
730     public boolean isTransparent;
731     public int count;
732     public int index;
733     public int rgb;
734 
735     //~ Constructors ///////////////////////////////////////////////////////////
736 
737     public GifEncoderHashitem(int rgb, int count, int index,
738         boolean isTransparent)
739     {
740         this.rgb = rgb;
741         this.count = count;
742         this.index = index;
743         this.isTransparent = isTransparent;
744     }
745 }
746 ///////////////////////////////////////////////////////////////////////////////
747 //  END OF FILE.
748 ///////////////////////////////////////////////////////////////////////////////