GIFEncoder is a class which takes an image and saves it to a stream using the GIF file format : GIF « 2D Graphics GUI « Java






GIFEncoder is a class which takes an image and saves it to a stream using the GIF file format

  
/*
 * @(#)GIFEncoder.java    0.90 4/21/96 Adam Doppelt
 */


import java.io.*;
import java.awt.*;
import java.awt.image.*;

/**
 * GIFEncoder is a class which takes an image and saves it to a stream
 * using the GIF file format (<A
 * HREF="http://www.dcs.ed.ac.uk/%7Emxr/gfx/">Graphics Interchange
 * Format</A>). A GIFEncoder
 * is constructed with either an AWT Image (which must be fully
 * loaded) or a set of RGB arrays. The image can be written out with a
 * call to <CODE>Write</CODE>.<P>
 *
 * Three caveats:
 * <UL>
 *   <LI>GIFEncoder will convert the image to indexed color upon
 *   construction. This will take some time, depending on the size of
 *   the image. Also, actually writing the image out (Write) will take
 *   time.<P>
 *
 *   <LI>The image cannot have more than 256 colors, since GIF is an 8
 *   bit format. For a 24 bit to 8 bit quantization algorithm, see
 *   Graphics Gems II III.2 by Xialoin Wu. Or check out his <A
 *   HREF="http://www.csd.uwo.ca/faculty/wu/cq.c">C source</A>.<P>
 *
 *   <LI>Since the image must be completely loaded into memory,
 *   GIFEncoder may have problems with large images. Attempting to
 *   encode an image which will not fit into memory will probably
 *   result in the following exception:<P>
 *   <CODE>java.awt.AWTException: Grabber returned false: 192</CODE><P>
 * </UL><P>
 *
 * GIFEncoder is based upon gifsave.c, which was written and released
 * by:<P>
 * <CENTER>
 *                                  Sverre H. Huseby<BR>
 *                                   Bjoelsengt. 17<BR>
 *                                     N-0468 Oslo<BR>
 *                                       Norway<P>
 *
 *                                 Phone: +47 2 230539<BR>
 *                                 sverrehu@ifi.uio.no<P>
 * </CENTER>
 * @version 0.90 21 Apr 1996
 * @author <A HREF="http://www.cs.brown.edu/people/amd/">Adam Doppelt</A> */
public class GIFEncoder {
    short width_, height_;
    int numColors_;
    byte pixels_[], colors_[];
    
    ScreenDescriptor sd_;
    ImageDescriptor id_;
    
/**
 * Construct a GIFEncoder. The constructor will convert the image to
 * an indexed color array. <B>This may take some time.</B><P>
 * 
 * @param image The image to encode. The image <B>must</B> be
 * completely loaded.
 * @exception AWTException Will be thrown if the pixel grab fails. This
 * can happen if Java runs out of memory. It may also indicate that the image
 * contains more than 256 colors.
 * */
    public GIFEncoder(Image image) throws AWTException {
  width_ = (short)image.getWidth(null);
  height_ = (short)image.getHeight(null);

  int values[] = new int[width_ * height_];
  PixelGrabber grabber = new PixelGrabber(
      image, 0, 0, width_, height_, values, 0, width_);
  
  try {
      if(grabber.grabPixels() != true)
    throw new AWTException("Grabber returned false: " +
               grabber.status());
  }
  catch (InterruptedException e) { ; }
  
  byte r[][] = new byte[width_][height_];
  byte g[][] = new byte[width_][height_];
  byte b[][] = new byte[width_][height_];
  int index = 0;
  for (int y = 0; y < height_; ++y)
      for (int x = 0; x < width_; ++x) {
    r[x][y] = (byte)((values[index] >> 16) & 0xFF);
    g[x][y] = (byte)((values[index] >> 8) & 0xFF);
    b[x][y] = (byte)((values[index]) & 0xFF);  
    ++index;
      }
  ToIndexedColor(r, g, b);
    }

/**
 * Construct a GIFEncoder. The constructor will convert the image to
 * an indexed color array. <B>This may take some time.</B><P>
 *
 * Each array stores intensity values for the image. In other words,
 * r[x][y] refers to the red intensity of the pixel at column x, row
 * y.<P>
 *
 * @param r An array containing the red intensity values.
 * @param g An array containing the green intensity values.
 * @param b An array containing the blue intensity values.
 *
 * @exception AWTException Will be thrown if the image contains more than
 * 256 colors.
 * */
    public GIFEncoder(byte r[][], byte g[][], byte b[][]) throws AWTException {
  width_ = (short)(r.length);
  height_ = (short)(r[0].length);

  ToIndexedColor(r, g, b);
    }

/**
 * Writes the image out to a stream in the GIF file format. This will
 * be a single GIF87a image, non-interlaced, with no background color.
 * <B>This may take some time.</B><P>
 *
 * @param output The stream to output to. This should probably be a
 * buffered stream.
 *
 * @exception IOException Will be thrown if a write operation fails.
 * */
    public void Write(OutputStream output) throws IOException {
  BitUtils.WriteString(output, "GIF87a");
  
  ScreenDescriptor sd = new ScreenDescriptor(width_, height_,
               numColors_);
  sd.Write(output);

  output.write(colors_, 0, colors_.length);

  ImageDescriptor id = new ImageDescriptor(width_, height_, ',');
  id.Write(output);

  byte codesize = BitUtils.BitsNeeded(numColors_);
  if (codesize == 1)
      ++codesize;
  output.write(codesize);

  LZWCompressor.LZWCompress(output, codesize, pixels_);
  output.write(0);

  id = new ImageDescriptor((byte)0, (byte)0, ';');
  id.Write(output);
  output.flush();
    }

    void ToIndexedColor(byte r[][], byte g[][],
      byte b[][]) throws AWTException {
  pixels_ = new byte[width_ * height_];
  colors_ = new byte[256 * 3];
  int colornum = 0;
  for (int x = 0; x < width_; ++x) {
      for (int y = 0; y < height_; ++y) {
    int search;
    for (search = 0; search < colornum; ++search)
        if (colors_[search * 3]     == r[x][y] &&
      colors_[search * 3 + 1] == g[x][y] &&
      colors_[search * 3 + 2] == b[x][y])
      break;
    
    if (search > 255)
        throw new AWTException("Too many colors.");

    pixels_[y * width_ + x] = (byte)search;
    
    if (search == colornum) {
        colors_[search * 3]     = r[x][y];
        colors_[search * 3 + 1] = g[x][y];
        colors_[search * 3 + 2] = b[x][y];
        ++colornum;
    }
      }
  }
  numColors_ = 1 << BitUtils.BitsNeeded(colornum);
  byte copy[] = new byte[numColors_ * 3];
  System.arraycopy(colors_, 0, copy, 0, numColors_ * 3);
  colors_ = copy;
    }
    
}

class BitFile {
    OutputStream output_;
    byte buffer_[];
    int index_, bitsLeft_;

    public BitFile(OutputStream output) {
  output_ = output;
  buffer_ = new byte[256];
  index_ = 0;
  bitsLeft_ = 8;
    }

    public void Flush() throws IOException {
  int numBytes = index_ + (bitsLeft_ == 8 ? 0 : 1);
  if (numBytes > 0) {
      output_.write(numBytes);
      output_.write(buffer_, 0, numBytes);
      buffer_[0] = 0;
      index_ = 0;
      bitsLeft_ = 8;
  }
    }

    public void WriteBits(int bits, int numbits) throws IOException {
  int bitsWritten = 0;
  int numBytes = 255;
  do {
      if ((index_ == 254 && bitsLeft_ == 0) || index_ > 254) {
    output_.write(numBytes);
    output_.write(buffer_, 0, numBytes);

    buffer_[0] = 0;
    index_ = 0;
    bitsLeft_ = 8;
      }

      if (numbits <= bitsLeft_) {
    buffer_[index_] |= (bits & ((1 << numbits) - 1)) <<
        (8 - bitsLeft_);
    bitsWritten += numbits;
    bitsLeft_ -= numbits;
    numbits = 0;
      }
      else {
    buffer_[index_] |= (bits & ((1 << bitsLeft_) - 1)) <<
        (8 - bitsLeft_);
    bitsWritten += bitsLeft_;
    bits >>= bitsLeft_;
    numbits -= bitsLeft_;
    buffer_[++index_] = 0;
    bitsLeft_ = 8;
      }
  } while (numbits != 0);
    }
}

class LZWStringTable {
    private final static int RES_CODES = 2;
    private final static short HASH_FREE = (short)0xFFFF;
    private final static short NEXT_FIRST = (short)0xFFFF;
    private final static int MAXBITS = 12;
    private final static int MAXSTR = (1 << MAXBITS);
    private final static short HASHSIZE = 9973;
    private final static short HASHSTEP = 2039;

    byte strChr_[];
    short strNxt_[];
    short strHsh_[];
    short numStrings_;

    public LZWStringTable() {
  strChr_ = new byte[MAXSTR];
  strNxt_ = new short[MAXSTR];
  strHsh_ = new short[HASHSIZE];    
    }

    public int AddCharString(short index, byte b) {
  int hshidx;

  if (numStrings_ >= MAXSTR)
      return 0xFFFF;
  
  hshidx = Hash(index, b);
  while (strHsh_[hshidx] != HASH_FREE)
      hshidx = (hshidx + HASHSTEP) % HASHSIZE;
  
  strHsh_[hshidx] = numStrings_;
  strChr_[numStrings_] = b;
  strNxt_[numStrings_] = (index != HASH_FREE) ? index : NEXT_FIRST;

  return numStrings_++;
    }
    
    public short FindCharString(short index, byte b) {
  int hshidx, nxtidx;

  if (index == HASH_FREE)
      return b;

  hshidx = Hash(index, b);
  while ((nxtidx = strHsh_[hshidx]) != HASH_FREE) {
      if (strNxt_[nxtidx] == index && strChr_[nxtidx] == b)
    return (short)nxtidx;
      hshidx = (hshidx + HASHSTEP) % HASHSIZE;
  }

  return (short)0xFFFF;
    }

    public void ClearTable(int codesize) {
  numStrings_ = 0;
  
  for (int q = 0; q < HASHSIZE; q++) {
      strHsh_[q] = HASH_FREE;
  }

  int w = (1 << codesize) + RES_CODES;
  for (int q = 0; q < w; q++)
      AddCharString((short)0xFFFF, (byte)q);
    }
    
    static public int Hash(short index, byte lastbyte) {
  return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE;
    }
}

class LZWCompressor {

    public static void LZWCompress(OutputStream output, int codesize,
           byte toCompress[]) throws IOException {
  byte c;
  short index;
  int clearcode, endofinfo, numbits, limit, errcode;
  short prefix = (short)0xFFFF;

  BitFile bitFile = new BitFile(output);
  LZWStringTable strings = new LZWStringTable();

  clearcode = 1 << codesize;
  endofinfo = clearcode + 1;
    
  numbits = codesize + 1;
  limit = (1 << numbits) - 1;
  
  strings.ClearTable(codesize);
  bitFile.WriteBits(clearcode, numbits);

  for (int loop = 0; loop < toCompress.length; ++loop) {
      c = toCompress[loop];
      if ((index = strings.FindCharString(prefix, c)) != -1)
    prefix = index;
      else {
    bitFile.WriteBits(prefix, numbits);
    if (strings.AddCharString(prefix, c) > limit) {
        if (++numbits > 12) {
      bitFile.WriteBits(clearcode, numbits - 1);
      strings.ClearTable(codesize);
      numbits = codesize + 1;
        }
        limit = (1 << numbits) - 1;
    }
    
    prefix = (short)((short)c & 0xFF);
      }
  }
  
  if (prefix != -1)
      bitFile.WriteBits(prefix, numbits);
  
  bitFile.WriteBits(endofinfo, numbits);
  bitFile.Flush();
    }
}

class ScreenDescriptor {
    public short localScreenWidth_, localScreenHeight_;
    private byte byte_;
    public byte backgroundColorIndex_, pixelAspectRatio_;

    public ScreenDescriptor(short width, short height, int numColors) {
  localScreenWidth_ = width;
  localScreenHeight_ = height;
  SetGlobalColorTableSize((byte)(BitUtils.BitsNeeded(numColors) - 1));
  SetGlobalColorTableFlag((byte)1);
  SetSortFlag((byte)0);
  SetColorResolution((byte)7);
  backgroundColorIndex_ = 0;
  pixelAspectRatio_ = 0;
    }

    public void Write(OutputStream output) throws IOException {
  BitUtils.WriteWord(output, localScreenWidth_);
  BitUtils.WriteWord(output, localScreenHeight_);
  output.write(byte_);
  output.write(backgroundColorIndex_);
  output.write(pixelAspectRatio_);
    }

    public void SetGlobalColorTableSize(byte num) {
  byte_ |= (num & 7);
    }

    public void SetSortFlag(byte num) {
  byte_ |= (num & 1) << 3;
    }

    public void SetColorResolution(byte num) {
  byte_ |= (num & 7) << 4;
    }
    
    public void SetGlobalColorTableFlag(byte num) {
  byte_ |= (num & 1) << 7;
    }
}

class ImageDescriptor {
    public byte separator_;
    public short leftPosition_, topPosition_, width_, height_;
    private byte byte_;

    public ImageDescriptor(short width, short height, char separator) {
  separator_ = (byte)separator;
  leftPosition_ = 0;
  topPosition_ = 0;
  width_ = width;
  height_ = height;
  SetLocalColorTableSize((byte)0);
  SetReserved((byte)0);
  SetSortFlag((byte)0);
  SetInterlaceFlag((byte)0);
  SetLocalColorTableFlag((byte)0);
    }
    
    public void Write(OutputStream output) throws IOException {
  output.write(separator_);
  BitUtils.WriteWord(output, leftPosition_);
  BitUtils.WriteWord(output, topPosition_);
  BitUtils.WriteWord(output, width_);
  BitUtils.WriteWord(output, height_);    
  output.write(byte_);
    }

    public void SetLocalColorTableSize(byte num) {
  byte_ |= (num & 7);
    }

    public void SetReserved(byte num) {
  byte_ |= (num & 3) << 3;
    }

    public void SetSortFlag(byte num) {
  byte_ |= (num & 1) << 5;
    }
    
    public void SetInterlaceFlag(byte num) {
  byte_ |= (num & 1) << 6;
    }

    public void SetLocalColorTableFlag(byte num) {
  byte_ |= (num & 1) << 7;
    }
}

class BitUtils {
    public static byte BitsNeeded(int n) {
  byte ret = 1;

  if (n-- == 0)
      return 0;

  while ((n >>= 1) != 0)
      ++ret;
  
  return ret;
    }    

    public static void WriteWord(OutputStream output,
         short w) throws IOException {
  output.write(w & 0xFF);
  output.write((w >> 8) & 0xFF);
    }
    
    static void WriteString(OutputStream output,
          String string) throws IOException {
  for (int loop = 0; loop < string.length(); ++loop)
      output.write((byte)(string.charAt(loop)));
    }
}

   
    
  








Related examples in the same category

1.GIF Writer
2.Class for converting images to GIF files
3.Animated Gif Encoder
4.Gif file Encoder
5.Gif Encoder
6.AnimatedGifEncoder - Encodes a GIF file consisting of one or more frames
7.Converting GIF to PostScript
8.Decodes a GIF file into one or more frames
9.Gif Encoder implements ImageConsumer
10.Hide the mouse cursor: use a transparent GIF as the cursor
11.Get Gif Properties
12.Gif Encoder - writes out an image as a GIF.