Coverage Report - net.wotonomy.ui.swing.util.GIFEncoder
 
Classes in this File Line Coverage Branch Coverage Complexity
GIFEncoder
0% 
0% 
2.207
 
 1  
 /*
 2  
  The source code in this file, GIFEncoder.java,
 3  
  belongs to the public domain.
 4  
 */
 5  
 
 6  
 package net.wotonomy.ui.swing.util;
 7  
 
 8  
 import java.awt.AWTException;
 9  
 import java.awt.Image;
 10  
 import java.awt.image.PixelGrabber;
 11  
 import java.io.IOException;
 12  
 import java.io.OutputStream;
 13  
 
 14  
 /**
 15  
  * GIFEncoder is a class which takes an image and saves it to a stream
 16  
  * using the GIF file format. A GIFEncoder is constructed with either
 17  
  * an AWT Image (which must be fully loaded) or a set of RGB arrays.
 18  
  * The image can be written out with a call to <CODE>write</CODE>.<br><br>
 19  
  *
 20  
  * Three caveats:
 21  
  * <UL>
 22  
  *   <LI>GIFEncoder will convert the image to indexed color upon
 23  
  *   construction, and this is not fast.
 24  
  *
 25  
  *   <LI>The image cannot have more than 256 colors, since GIF is an 8
 26  
  *   bit format.
 27  
  *
 28  
  *   <LI>Since the image must be completely loaded into memory,
 29  
  *   there may be problems with large images.
 30  
  * </UL>
 31  
  *
 32  
  * This implementation is heavily based on code made available by
 33  
  * Adam Doppelt, which was based upon gifsave.c, which was written
 34  
  * and released by Sverre H. Huseby.  
 35  
  *
 36  
  * @author amd@brown.edu
 37  
  * @author sverrehu@ifi.uio.no
 38  
  * @author michael@mpowers.net
 39  
  */
 40  
 public class GIFEncoder
 41  
 {
 42  
     short width_, height_;
 43  
     int numColors_;
 44  
     byte pixels_[], colors_[];
 45  
 
 46  
     ScreenDescriptor sd_;
 47  
     ImageDescriptor id_;
 48  
 
 49  
 /**
 50  
  * Construct a GIFEncoder. The constructor will convert the image to
 51  
  * an indexed color array. This may take some time.  If more than 256
 52  
  * colors are encountered, all subsequent colors are mapped to the first
 53  
  * color encountered.
 54  
  * @param image The image to encode. The image must be completely loaded.
 55  
  * @exception AWTException Will be thrown if the pixel grab fails. This
 56  
  * can happen if Java runs out of memory. 
 57  
  * */
 58  0
     public GIFEncoder(Image image) throws AWTException
 59  0
     {
 60  0
         width_ = (short)image.getWidth(null);
 61  0
         height_ = (short)image.getHeight(null);
 62  
 
 63  0
         int values[] = new int[width_ * height_];
 64  0
         PixelGrabber grabber = new PixelGrabber(
 65  0
             image, 0, 0, width_, height_, values, 0, width_);
 66  
 
 67  
         try
 68  
         {
 69  0
             if(grabber.grabPixels() != true)
 70  0
             throw new AWTException("Grabber returned false: " +
 71  0
                            grabber.status());
 72  
         }
 73  0
         catch (InterruptedException e)
 74  
         {
 75  0
         }
 76  
 
 77  0
         byte r[][] = new byte[width_][height_];
 78  0
         byte g[][] = new byte[width_][height_];
 79  0
         byte b[][] = new byte[width_][height_];
 80  0
         int index = 0;
 81  0
         for (int y = 0; y < height_; ++y)
 82  
         {
 83  0
             for (int x = 0; x < width_; ++x)
 84  
             {
 85  0
                 r[x][y] = (byte)((values[index] >> 16) & 0xFF);
 86  0
                 g[x][y] = (byte)((values[index] >> 8) & 0xFF);
 87  0
                 b[x][y] = (byte)((values[index]) & 0xFF);
 88  0
                 ++index;
 89  
             }
 90  
         }
 91  0
         toIndexedColor(r, g, b);
 92  0
     }
 93  
 
 94  
 /**
 95  
  * Construct a GIFEncoder. The constructor will convert the image to
 96  
  * an indexed color array. This may take some time. <br><br>
 97  
  * Each array stores intensity values for the image. In other words,
 98  
  * r[x][y] refers to the red intensity of the pixel at column x, row y.
 99  
  * @param r An array containing the red intensity values.
 100  
  * @param g An array containing the green intensity values.
 101  
  * @param b An array containing the blue intensity values.
 102  
  *
 103  
  * @exception AWTException Will be thrown if the image contains more than
 104  
  * 256 colors.
 105  
  * */
 106  0
     public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException
 107  0
     {
 108  0
         width_ = (short)(r.length);
 109  0
         height_ = (short)(r[0].length);
 110  
 
 111  0
         toIndexedColor(r, g, b);
 112  0
     }
 113  
 
 114  
 /**
 115  
  * Writes the image out to a stream in the GIF file format. This will
 116  
  * be a single GIF87a image, non-interlaced, with no background color.
 117  
  * <B>This may take some time.</B><P>
 118  
  *
 119  
  * @param output The stream to output to. This should probably be a
 120  
  * buffered stream.
 121  
  *
 122  
  * @exception IOException Will be thrown if a write operation fails.
 123  
  * */
 124  
     public void write(OutputStream output) throws IOException
 125  
     {
 126  0
         BitUtils.writeString(output, "GIF87a");
 127  0
         ScreenDescriptor sd = new ScreenDescriptor(width_, height_,
 128  0
                                numColors_);
 129  0
         sd.write(output);
 130  
 
 131  0
         output.write(colors_, 0, colors_.length);
 132  
 
 133  0
         ImageDescriptor id = new ImageDescriptor(width_, height_, ',');
 134  0
         id.write(output);
 135  
 
 136  0
         byte codesize = BitUtils.bitsNeeded(numColors_);
 137  0
         if (codesize == 1)
 138  0
         ++codesize;
 139  0
         output.write(codesize);
 140  
 
 141  0
         LZWCompressor.LZWCompress(output, codesize, pixels_);
 142  0
         output.write(0);
 143  0
         id = new ImageDescriptor((byte)0, (byte)0, ';');
 144  0
         id.write(output);
 145  0
         output.flush();
 146  0
     }
 147  
 
 148  
     void toIndexedColor(byte r[][], byte g[][],
 149  
                         byte b[][]) throws AWTException
 150  
     {
 151  0
         pixels_ = new byte[width_ * height_];
 152  0
         colors_ = new byte[256 * 3];
 153  0
         int colornum = 0;
 154  0
         for (int x = 0; x < width_; ++x)
 155  
         {
 156  0
             for (int y = 0; y < height_; ++y)
 157  
             {
 158  
                 int search;
 159  0
                 for (search = 0; search < colornum; ++search)
 160  0
                     if (colors_[search * 3]     == r[x][y] &&
 161  0
                     colors_[search * 3 + 1] == g[x][y] &&
 162  0
                     colors_[search * 3 + 2] == b[x][y])
 163  0
                     break;
 164  
 
 165  0
                 if (search > 255)
 166  0
                     search = 0;
 167  
                     //throw new AWTException("Too many colors.");
 168  
 
 169  0
                 pixels_[y * width_ + x] = (byte)search;
 170  
 
 171  0
                 if (search == colornum) {
 172  0
                     colors_[search * 3]     = r[x][y];
 173  0
                     colors_[search * 3 + 1] = g[x][y];
 174  0
                     colors_[search * 3 + 2] = b[x][y];
 175  0
                     ++colornum;
 176  
                 }
 177  
             }
 178  
         }
 179  
 
 180  0
         numColors_ = 1 << BitUtils.bitsNeeded(colornum);
 181  0
         byte copy[] = new byte[numColors_ * 3];
 182  0
         System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
 183  0
         colors_ = copy;
 184  0
     }
 185  
 
 186  
 }
 187  
 
 188  
 class BitFile
 189  
 {
 190  
     OutputStream output_;
 191  
     byte buffer_[];
 192  
     int index_, bitsLeft_;
 193  
 
 194  
     public BitFile(OutputStream output)
 195  
     {
 196  
         output_ = output;
 197  
         buffer_ = new byte[256];
 198  
         index_ = 0;
 199  
         bitsLeft_ = 8;
 200  
     }
 201  
 
 202  
     public void flush() throws IOException
 203  
     {
 204  
         int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
 205  
         if (numBytes > 0)
 206  
         {
 207  
             output_.write(numBytes);
 208  
             output_.write(buffer_, 0, numBytes);
 209  
             buffer_[0] = 0;
 210  
             index_ = 0;
 211  
             bitsLeft_ = 8;
 212  
         }
 213  
     }
 214  
 
 215  
     public void writeBits(int bits, int numbits) throws IOException {
 216  
             int bitsWritten = 0;
 217  
             int numBytes = 255;
 218  
         do
 219  
         {
 220  
             if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254)
 221  
             {
 222  
                 output_.write(numBytes);
 223  
                 output_.write(buffer_, 0, numBytes);
 224  
 
 225  
                 buffer_[0] = 0;
 226  
                 index_ = 0;
 227  
                 bitsLeft_ = 8;
 228  
             }
 229  
 
 230  
             if (numbits <= bitsLeft_)
 231  
             {
 232  
                 buffer_[index_] |= (bits & ((1 << numbits) - 1)) <<
 233  
                     (8 - bitsLeft_);
 234  
                 bitsWritten += numbits;
 235  
                 bitsLeft_ -= numbits;
 236  
                 numbits = 0;
 237  
                 }
 238  
             else
 239  
             {
 240  
                 buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) <<
 241  
                 (8 - bitsLeft_);
 242  
                 bitsWritten += bitsLeft_;
 243  
                 bits >>= bitsLeft_;
 244  
                 numbits -= bitsLeft_;
 245  
                 buffer_[++index_] = 0;
 246  
                 bitsLeft_ = 8;
 247  
             }
 248  
 
 249  
             }
 250  
         while (numbits != 0);
 251  
     }
 252  
 }
 253  
 
 254  
 class LZWStringTable
 255  
 {
 256  
     private final static int RES_CODES = 2;
 257  
     private final static short HASH_FREE = (short)0xFFFF;
 258  
     private final static short NEXT_FIRST = (short)0xFFFF;
 259  
     private final static int MAXBITS = 12;
 260  
     private final static int MAXSTR = (1 << MAXBITS);
 261  
     private final static short HASHSIZE = 9973;
 262  
     private final static short HASHSTEP = 2039;
 263  
 
 264  
     byte strChr_[];
 265  
     short strNxt_[];
 266  
     short strHsh_[];
 267  
     short numStrings_;
 268  
 
 269  
     public LZWStringTable()
 270  
     {
 271  
         strChr_ = new byte[MAXSTR];
 272  
         strNxt_ = new short[MAXSTR];
 273  
         strHsh_ = new short[HASHSIZE];
 274  
     }
 275  
 
 276  
     public int addCharString(short index, byte b)
 277  
     {
 278  
         int hshidx;
 279  
 
 280  
         if (numStrings_ >= MAXSTR)
 281  
             return 0xFFFF;
 282  
 
 283  
         hshidx = Hash(index, b);
 284  
         while (strHsh_[hshidx] != HASH_FREE)
 285  
             hshidx = (hshidx + HASHSTEP) % HASHSIZE;
 286  
 
 287  
         strHsh_[hshidx] = numStrings_;
 288  
         strChr_[numStrings_] = b;
 289  
         strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST;
 290  
 
 291  
         return numStrings_++;
 292  
     }
 293  
 
 294  
     public short findCharString(short index, byte b)
 295  
     {
 296  
         int hshidx, nxtidx;
 297  
 
 298  
         if (index == HASH_FREE)
 299  
             return b;
 300  
 
 301  
         hshidx = Hash(index, b);
 302  
         while ((nxtidx = strHsh_[hshidx]) != HASH_FREE)
 303  
         {
 304  
             if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b)
 305  
             return (short)nxtidx;
 306  
             hshidx = (hshidx + HASHSTEP) % HASHSIZE;
 307  
         }
 308  
 
 309  
         return (short)0xFFFF;
 310  
     }
 311  
 
 312  
     public void clearTable(int codesize)
 313  
     {
 314  
         numStrings_ = 0;
 315  
 
 316  
         for (int q = 0; q < HASHSIZE; q++)
 317  
         {
 318  
             strHsh_[q] = HASH_FREE;
 319  
         }
 320  
 
 321  
         int w = (1 << codesize) + RES_CODES;
 322  
         for (int q = 0; q < w; q++)
 323  
         {
 324  
             addCharString((short)0xFFFF, (byte)q);
 325  
         }
 326  
     }
 327  
 
 328  
     static public int Hash(short index, byte lastbyte)
 329  
     {
 330  
             return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE;
 331  
     }
 332  
 }
 333  
 
 334  
 class LZWCompressor {
 335  
 
 336  
     public static void LZWCompress(OutputStream output, int codesize,
 337  
                                    byte toCompress[]) throws IOException
 338  
     {
 339  
         byte c;
 340  
         short index;
 341  
         int clearcode, endofinfo, numbits, limit, errcode;
 342  
         short prefix = (short)0xFFFF;
 343  
 
 344  
         BitFile bitFile = new BitFile(output);
 345  
         LZWStringTable strings = new LZWStringTable();
 346  
 
 347  
         clearcode = 1 << codesize;
 348  
         endofinfo = clearcode + 1;
 349  
 
 350  
         numbits = codesize + 1;
 351  
         limit = (1 << numbits) - 1;
 352  
         strings.clearTable(codesize);
 353  
         bitFile.writeBits(clearcode, numbits);
 354  
 
 355  
         for (int loop = 0; loop < toCompress.length; ++loop)
 356  
         {
 357  
             c = toCompress[loop];
 358  
             if ((index = strings.findCharString(prefix, c)) != -1)
 359  
             {
 360  
                 prefix = index;
 361  
             }
 362  
             else
 363  
             {
 364  
                 bitFile.writeBits(prefix, numbits);
 365  
                 if (strings.addCharString(prefix, c) > limit) {
 366  
                     if (++numbits > 12) {
 367  
                     bitFile.writeBits(clearcode, numbits - 1);
 368  
                     strings.clearTable(codesize);
 369  
                     numbits = codesize + 1;
 370  
                     }
 371  
                     limit = (1 << numbits) - 1;
 372  
                 }
 373  
 
 374  
                 prefix = (short)((short)c & 0xFF);
 375  
             }
 376  
         }
 377  
 
 378  
         if (prefix != -1)
 379  
             bitFile.writeBits(prefix, numbits);
 380  
 
 381  
         bitFile.writeBits(endofinfo, numbits);
 382  
         bitFile.flush();
 383  
     }
 384  
 }
 385  
 
 386  
 class ScreenDescriptor
 387  
 {
 388  
     public short localScreenWidth_, localScreenHeight_;
 389  
     private byte byte_;
 390  
     public byte backgroundColorIndex_, pixelAspectRatio_;
 391  
 
 392  
     public ScreenDescriptor(short width, short height, int numColors)
 393  
     {
 394  
         localScreenWidth_ = width;
 395  
         localScreenHeight_ = height;
 396  
         setGlobalColorTableSize((byte)(BitUtils.bitsNeeded(numColors) - 1));
 397  
         setGlobalColorTableFlag((byte)1);
 398  
         setSortFlag((byte)0);
 399  
         setColorResolution((byte)7);
 400  
         backgroundColorIndex_ = 0;
 401  
         pixelAspectRatio_ = 0;
 402  
     }
 403  
 
 404  
     public void write(OutputStream output) throws IOException
 405  
     {
 406  
         BitUtils.writeWord(output, localScreenWidth_);
 407  
         BitUtils.writeWord(output, localScreenHeight_);
 408  
         output.write(byte_);
 409  
         output.write(backgroundColorIndex_);
 410  
         output.write(pixelAspectRatio_);
 411  
     }
 412  
 
 413  
     public void setGlobalColorTableSize(byte num)
 414  
     {
 415  
             byte_ |= (num & 7);
 416  
     }
 417  
 
 418  
     public void setSortFlag(byte num)
 419  
     {
 420  
             byte_ |= (num & 1) << 3;
 421  
     }
 422  
 
 423  
     public void setColorResolution(byte num)
 424  
     {
 425  
             byte_ |= (num & 7) << 4;
 426  
     }
 427  
 
 428  
     public void setGlobalColorTableFlag(byte num)
 429  
     {
 430  
             byte_ |= (num & 1) << 7;
 431  
     }
 432  
 }
 433  
 
 434  
 class ImageDescriptor
 435  
 {
 436  
     public byte separator_;
 437  
     public short leftPosition_, topPosition_, width_, height_;
 438  
     private byte byte_;
 439  
 
 440  
     public ImageDescriptor(short width, short height, char separator)
 441  
     {
 442  
         separator_ = (byte)separator;
 443  
         leftPosition_ = 0;
 444  
         topPosition_ = 0;
 445  
         width_ = width;
 446  
         height_ = height;
 447  
         setLocalColorTableSize((byte)0);
 448  
         setReserved((byte)0);
 449  
         setSortFlag((byte)0);
 450  
         setInterlaceFlag((byte)0);
 451  
         setLocalColorTableFlag((byte)0);
 452  
     }
 453  
 
 454  
     public void write(OutputStream output) throws IOException
 455  
     {
 456  
         output.write(separator_);
 457  
         BitUtils.writeWord(output, leftPosition_);
 458  
         BitUtils.writeWord(output, topPosition_);
 459  
         BitUtils.writeWord(output, width_);
 460  
         BitUtils.writeWord(output, height_);
 461  
         output.write(byte_);
 462  
     }
 463  
 
 464  
     public void setLocalColorTableSize(byte num)
 465  
     {
 466  
             byte_ |= (num & 7);
 467  
     }
 468  
 
 469  
     public void setReserved(byte num)
 470  
     {
 471  
             byte_ |= (num & 3) << 3;
 472  
     }
 473  
 
 474  
     public void setSortFlag(byte num)
 475  
     {
 476  
             byte_ |= (num & 1) << 5;
 477  
     }
 478  
 
 479  
     public void setInterlaceFlag(byte num)
 480  
     {
 481  
             byte_ |= (num & 1) << 6;
 482  
     }
 483  
 
 484  
     public void setLocalColorTableFlag(byte num)
 485  
     {
 486  
         byte_ |= (num & 1) << 7;
 487  
     }
 488  
 }
 489  
 
 490  
 class BitUtils
 491  
 {
 492  
     public static byte bitsNeeded(int n)
 493  
     {
 494  
         byte ret = 1;
 495  
 
 496  
         if (n-- == 0)
 497  
             return 0;
 498  
 
 499  
         while ((n >>= 1) != 0)
 500  
             ++ret;
 501  
 
 502  
         return ret;
 503  
     }
 504  
 
 505  
     public static void writeWord(OutputStream output,
 506  
                                  short w) throws IOException
 507  
     {
 508  
         output.write(w & 0xFF);
 509  
         output.write((w >> 8) & 0xFF);
 510  
     }
 511  
 
 512  
     static void writeString(OutputStream output,
 513  
                             String string) throws IOException
 514  
     {
 515  
         for (int loop = 0; loop < string.length(); ++loop)
 516  
             output.write((byte)(string.charAt(loop)));
 517  
     }
 518  
 }
 519  
 
 520