Java/2D Graphics GUI/GIF

Материал из Java эксперт
Перейти к: навигация, поиск

Animated Gif Encoder

   
import java.io.*;
import java.awt.*;
import java.awt.image.*;
/**
 * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
 * frames.
 * 
 * <pre>
 *  Example:
 *     AnimatedGifEncoder e = new AnimatedGifEncoder();
 *     e.start(outputFileName);
 *     e.setDelay(1000);   // 1 frame per sec
 *     e.addFrame(image1);
 *     e.addFrame(image2);
 *     e.finish();
 * </pre>
 * 
 * No copyright asserted on the source code of this class. May be used for any
 * purpose, however, refer to the Unisys LZW patent for restrictions on use of
 * the associated LZWEncoder class. Please forward any corrections to
 * kweiner@fmsware.ru.
 * 
 * @author Kevin Weiner, FM Software
 * @version 1.03 November 2003
 * 
 */
public class AnimatedGifEncoder {
  protected int width; // image size
  protected int height;
  protected Color transparent = null; // transparent color if given
  protected int transIndex; // transparent index in color table
  protected int repeat = -1; // no repeat
  protected int delay = 0; // frame delay (hundredths)
  protected boolean started = false; // ready to output frames
  protected OutputStream out;
  protected BufferedImage image; // current frame
  protected byte[] pixels; // BGR byte array from frame
  protected byte[] indexedPixels; // converted frame indexed to palette
  protected int colorDepth; // number of bit planes
  protected byte[] colorTab; // RGB palette
  protected boolean[] usedEntry = new boolean[256]; // active palette entries
  protected int palSize = 7; // color table size (bits-1)
  protected int dispose = -1; // disposal code (-1 = use default)
  protected boolean closeStream = false; // close stream when finished
  protected boolean firstFrame = true;
  protected boolean sizeSet = false; // if false, get size from first frame
  protected int sample = 10; // default sample interval for quantizer
  /**
   * Sets the delay time between each frame, or changes it for subsequent frames
   * (applies to last frame added).
   * 
   * @param ms
   *          int delay time in milliseconds
   */
  public void setDelay(int ms) {
    delay = Math.round(ms / 10.0f);
  }
  /**
   * Sets the GIF frame disposal code for the last added frame and any
   * subsequent frames. Default is 0 if no transparent color has been set,
   * otherwise 2.
   * 
   * @param code
   *          int disposal code.
   */
  public void setDispose(int code) {
    if (code >= 0) {
      dispose = code;
    }
  }
  /**
   * Sets the number of times the set of GIF frames should be played. Default is
   * 1; 0 means play indefinitely. Must be invoked before the first image is
   * added.
   * 
   * @param iter
   *          int number of iterations.
   * @return
   */
  public void setRepeat(int iter) {
    if (iter >= 0) {
      repeat = iter;
    }
  }
  /**
   * Sets the transparent color for the last added frame and any subsequent
   * frames. Since all colors are subject to modification in the quantization
   * process, the color in the final palette for each frame closest to the given
   * color becomes the transparent color for that frame. May be set to null to
   * indicate no transparent color.
   * 
   * @param c
   *          Color to be treated as transparent on display.
   */
  public void setTransparent(Color c) {
    transparent = c;
  }
  /**
   * Adds next GIF frame. The frame is not written immediately, but is actually
   * deferred until the next frame is received so that timing data can be
   * inserted. Invoking <code>finish()</code> flushes all frames. If
   * <code>setSize</code> was not invoked, the size of the first image is used
   * for all subsequent frames.
   * 
   * @param im
   *          BufferedImage containing frame to write.
   * @return true if successful.
   */
  public boolean addFrame(BufferedImage im) {
    if ((im == null) || !started) {
      return false;
    }
    boolean ok = true;
    try {
      if (!sizeSet) {
        // use first frame"s size
        setSize(im.getWidth(), im.getHeight());
      }
      image = im;
      getImagePixels(); // convert to correct format if necessary
      analyzePixels(); // build color table & map pixels
      if (firstFrame) {
        writeLSD(); // logical screen descriptior
        writePalette(); // global color table
        if (repeat >= 0) {
          // use NS app extension to indicate reps
          writeNetscapeExt();
        }
      }
      writeGraphicCtrlExt(); // write graphic control extension
      writeImageDesc(); // image descriptor
      if (!firstFrame) {
        writePalette(); // local color table
      }
      writePixels(); // encode and write pixel data
      firstFrame = false;
    } catch (IOException e) {
      ok = false;
    }
    return ok;
  }
  /**
   * Flushes any pending data and closes output file. If writing to an
   * OutputStream, the stream is not closed.
   */
  public boolean finish() {
    if (!started)
      return false;
    boolean ok = true;
    started = false;
    try {
      out.write(0x3b); // gif trailer
      out.flush();
      if (closeStream) {
        out.close();
      }
    } catch (IOException e) {
      ok = false;
    }
    // reset for subsequent use
    transIndex = 0;
    out = null;
    image = null;
    pixels = null;
    indexedPixels = null;
    colorTab = null;
    closeStream = false;
    firstFrame = true;
    return ok;
  }
  /**
   * Sets frame rate in frames per second. Equivalent to
   * <code>setDelay(1000/fps)</code>.
   * 
   * @param fps
   *          float frame rate (frames per second)
   */
  public void setFrameRate(float fps) {
    if (fps != 0f) {
      delay = Math.round(100f / fps);
    }
  }
  /**
   * Sets quality of color quantization (conversion of images to the maximum 256
   * colors allowed by the GIF specification). Lower values (minimum = 1)
   * produce better colors, but slow processing significantly. 10 is the
   * default, and produces good color mapping at reasonable speeds. Values
   * greater than 20 do not yield significant improvements in speed.
   * 
   * @param quality
   *          int greater than 0.
   * @return
   */
  public void setQuality(int quality) {
    if (quality < 1)
      quality = 1;
    sample = quality;
  }
  /**
   * Sets the GIF frame size. The default size is the size of the first frame
   * added if this method is not invoked.
   * 
   * @param w
   *          int frame width.
   * @param h
   *          int frame width.
   */
  public void setSize(int w, int h) {
    if (started && !firstFrame)
      return;
    width = w;
    height = h;
    if (width < 1)
      width = 320;
    if (height < 1)
      height = 240;
    sizeSet = true;
  }
  /**
   * Initiates GIF file creation on the given stream. The stream is not closed
   * automatically.
   * 
   * @param os
   *          OutputStream on which GIF images are written.
   * @return false if initial write failed.
   */
  public boolean start(OutputStream os) {
    if (os == null)
      return false;
    boolean ok = true;
    closeStream = false;
    out = os;
    try {
      writeString("GIF89a"); // header
    } catch (IOException e) {
      ok = false;
    }
    return started = ok;
  }
  /**
   * Initiates writing of a GIF file with the specified name.
   * 
   * @param file
   *          String containing output file name.
   * @return false if open or initial write failed.
   */
  public boolean start(String file) {
    boolean ok = true;
    try {
      out = new BufferedOutputStream(new FileOutputStream(file));
      ok = start(out);
      closeStream = true;
    } catch (IOException e) {
      ok = false;
    }
    return started = ok;
  }
  /**
   * Analyzes image colors and creates color map.
   */
  protected void analyzePixels() {
    int len = pixels.length;
    int nPix = len / 3;
    indexedPixels = new byte[nPix];
    NeuQuant nq = new NeuQuant(pixels, len, sample);
    // initialize quantizer
    colorTab = nq.process(); // create reduced palette
    // convert map from BGR to RGB
    for (int i = 0; i < colorTab.length; i += 3) {
      byte temp = colorTab[i];
      colorTab[i] = colorTab[i + 2];
      colorTab[i + 2] = temp;
      usedEntry[i / 3] = false;
    }
    // map image pixels to new palette
    int k = 0;
    for (int i = 0; i < nPix; i++) {
      int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
      usedEntry[index] = true;
      indexedPixels[i] = (byte) index;
    }
    pixels = null;
    colorDepth = 8;
    palSize = 7;
    // get closest match to transparent color if specified
    if (transparent != null) {
      transIndex = findClosest(transparent);
    }
  }
  /**
   * Returns index of palette color closest to c
   * 
   */
  protected int findClosest(Color c) {
    if (colorTab == null)
      return -1;
    int r = c.getRed();
    int g = c.getGreen();
    int b = c.getBlue();
    int minpos = 0;
    int dmin = 256 * 256 * 256;
    int len = colorTab.length;
    for (int i = 0; i < len;) {
      int dr = r - (colorTab[i++] & 0xff);
      int dg = g - (colorTab[i++] & 0xff);
      int db = b - (colorTab[i] & 0xff);
      int d = dr * dr + dg * dg + db * db;
      int index = i / 3;
      if (usedEntry[index] && (d < dmin)) {
        dmin = d;
        minpos = index;
      }
      i++;
    }
    return minpos;
  }
  /**
   * Extracts image pixels into byte array "pixels"
   */
  protected void getImagePixels() {
    int w = image.getWidth();
    int h = image.getHeight();
    int type = image.getType();
    if ((w != width) || (h != height) || (type != BufferedImage.TYPE_3BYTE_BGR)) {
      // create new image with right size/format
      BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
      Graphics2D g = temp.createGraphics();
      g.drawImage(image, 0, 0, null);
      image = temp;
    }
    pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
  }
  /**
   * Writes Graphic Control Extension
   */
  protected void writeGraphicCtrlExt() throws IOException {
    out.write(0x21); // extension introducer
    out.write(0xf9); // GCE label
    out.write(4); // data block size
    int transp, disp;
    if (transparent == null) {
      transp = 0;
      disp = 0; // dispose = no action
    } else {
      transp = 1;
      disp = 2; // force clear if using transparent color
    }
    if (dispose >= 0) {
      disp = dispose & 7; // user override
    }
    disp <<= 2;
    // packed fields
    out.write(0 | // 1:3 reserved
        disp | // 4:6 disposal
        0 | // 7 user input - 0 = none
        transp); // 8 transparency flag
    writeShort(delay); // delay x 1/100 sec
    out.write(transIndex); // transparent color index
    out.write(0); // block terminator
  }
  /**
   * Writes Image Descriptor
   */
  protected void writeImageDesc() throws IOException {
    out.write(0x2c); // image separator
    writeShort(0); // image position x,y = 0,0
    writeShort(0);
    writeShort(width); // image size
    writeShort(height);
    // packed fields
    if (firstFrame) {
      // no LCT - GCT is used for first (or only) frame
      out.write(0);
    } else {
      // specify normal LCT
      out.write(0x80 | // 1 local color table 1=yes
          0 | // 2 interlace - 0=no
          0 | // 3 sorted - 0=no
          0 | // 4-5 reserved
          palSize); // 6-8 size of color table
    }
  }
  /**
   * Writes Logical Screen Descriptor
   */
  protected void writeLSD() throws IOException {
    // logical screen size
    writeShort(width);
    writeShort(height);
    // packed fields
    out.write((0x80 | // 1 : global color table flag = 1 (gct used)
        0x70 | // 2-4 : color resolution = 7
        0x00 | // 5 : gct sort flag = 0
        palSize)); // 6-8 : gct size
    out.write(0); // background color index
    out.write(0); // pixel aspect ratio - assume 1:1
  }
  /**
   * Writes Netscape application extension to define repeat count.
   */
  protected void writeNetscapeExt() throws IOException {
    out.write(0x21); // extension introducer
    out.write(0xff); // app extension label
    out.write(11); // block size
    writeString("NETSCAPE" + "2.0"); // app id + auth code
    out.write(3); // sub-block size
    out.write(1); // loop sub-block id
    writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
    out.write(0); // block terminator
  }
  /**
   * Writes color table
   */
  protected void writePalette() throws IOException {
    out.write(colorTab, 0, colorTab.length);
    int n = (3 * 256) - colorTab.length;
    for (int i = 0; i < n; i++) {
      out.write(0);
    }
  }
  /**
   * Encodes and writes pixel data
   */
  protected void writePixels() throws IOException {
    LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
    encoder.encode(out);
  }
  /**
   * Write 16-bit value to output stream, LSB first
   */
  protected void writeShort(int value) throws IOException {
    out.write(value & 0xff);
    out.write((value >> 8) & 0xff);
  }
  /**
   * Writes string to output stream
   */
  protected void writeString(String s) throws IOException {
    for (int i = 0; i < s.length(); i++) {
      out.write((byte) s.charAt(i));
    }
  }
}
/*
 * NeuQuant Neural-Net Quantization Algorithm
 * ------------------------------------------
 * 
 * Copyright (c) 1994 Anthony Dekker
 * 
 * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
 * "Kohonen neural networks for optimal colour quantization" in "Network:
 * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
 * the algorithm.
 * 
 * Any party obtaining a copy of these files from the author, directly or
 * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
 * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
 * this software and documentation files (the "Software"), including without
 * limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons who
 * receive copies from any such party to do so, with the only requirement being
 * that this copyright notice remain intact.
 */
// Ported to Java 12/00 K Weiner
class NeuQuant {
  protected static final int netsize = 256; /* number of colours used */
  /* four primes near 500 - assume no image has a length so large */
  /* that it is divisible by all four primes */
  protected static final int prime1 = 499;
  protected static final int prime2 = 491;
  protected static final int prime3 = 487;
  protected static final int prime4 = 503;
  protected static final int minpicturebytes = (3 * prime4);
  /* minimum size for input image */
  /*
   * Program Skeleton ---------------- [select samplefac in range 1..30] [read
   * image from input file] pic = (unsigned char*) malloc(3*width*height);
   * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
   * image header, using writecolourmap(f)] inxbuild(); write output image using
   * inxsearch(b,g,r)
   */
  /*
   * Network Definitions -------------------
   */
  protected static final int maxnetpos = (netsize - 1);
  protected static final int netbiasshift = 4; /* bias for colour values */
  protected static final int ncycles = 100; /* no. of learning cycles */
  /* defs for freq and bias */
  protected static final int intbiasshift = 16; /* bias for fractions */
  protected static final int intbias = (((int) 1) << intbiasshift);
  protected static final int gammashift = 10; /* gamma = 1024 */
  protected static final int gamma = (((int) 1) << gammashift);
  protected static final int betashift = 10;
  protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
  protected static final int betagamma = (intbias << (gammashift - betashift));
  /* defs for decreasing radius factor */
  protected static final int initrad = (netsize >> 3); /*
                                                         * for 256 cols, radius
                                                         * starts
                                                         */
  protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
  protected static final int radiusbias = (((int) 1) << radiusbiasshift);
  protected static final int initradius = (initrad * radiusbias); /*
                                                                   * and
                                                                   * decreases
                                                                   * by a
                                                                   */
  protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
  /* defs for decreasing alpha factor */
  protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
  protected static final int initalpha = (((int) 1) << alphabiasshift);
  protected int alphadec; /* biased by 10 bits */
  /* radbias and alpharadbias used for radpower calculation */
  protected static final int radbiasshift = 8;
  protected static final int radbias = (((int) 1) << radbiasshift);
  protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
  protected static final int alpharadbias = (((int) 1) << alpharadbshift);
  /*
   * Types and Global Variables --------------------------
   */
  protected byte[] thepicture; /* the input image itself */
  protected int lengthcount; /* lengthcount = H*W*3 */
  protected int samplefac; /* sampling factor 1..30 */
  // typedef int pixel[4]; /* BGRc */
  protected int[][] network; /* the network itself - [netsize][4] */
  protected int[] netindex = new int[256];
  /* for network lookup - really 256 */
  protected int[] bias = new int[netsize];
  /* bias and freq arrays for learning */
  protected int[] freq = new int[netsize];
  protected int[] radpower = new int[initrad];
  /* radpower for precomputation */
  /*
   * Initialise network in range (0,0,0) to (255,255,255) and set parameters
   * -----------------------------------------------------------------------
   */
  public NeuQuant(byte[] thepic, int len, int sample) {
    int i;
    int[] p;
    thepicture = thepic;
    lengthcount = len;
    samplefac = sample;
    network = new int[netsize][];
    for (i = 0; i < netsize; i++) {
      network[i] = new int[4];
      p = network[i];
      p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
      freq[i] = intbias / netsize; /* 1/netsize */
      bias[i] = 0;
    }
  }
  public byte[] colorMap() {
    byte[] map = new byte[3 * netsize];
    int[] index = new int[netsize];
    for (int i = 0; i < netsize; i++)
      index[network[i][3]] = i;
    int k = 0;
    for (int i = 0; i < netsize; i++) {
      int j = index[i];
      map[k++] = (byte) (network[j][0]);
      map[k++] = (byte) (network[j][1]);
      map[k++] = (byte) (network[j][2]);
    }
    return map;
  }
  /*
   * Insertion sort of network and building of netindex[0..255] (to do after
   * unbias)
   * -------------------------------------------------------------------------------
   */
  public void inxbuild() {
    int i, j, smallpos, smallval;
    int[] p;
    int[] q;
    int previouscol, startpos;
    previouscol = 0;
    startpos = 0;
    for (i = 0; i < netsize; i++) {
      p = network[i];
      smallpos = i;
      smallval = p[1]; /* index on g */
      /* find smallest in i..netsize-1 */
      for (j = i + 1; j < netsize; j++) {
        q = network[j];
        if (q[1] < smallval) { /* index on g */
          smallpos = j;
          smallval = q[1]; /* index on g */
        }
      }
      q = network[smallpos];
      /* swap p (i) and q (smallpos) entries */
      if (i != smallpos) {
        j = q[0];
        q[0] = p[0];
        p[0] = j;
        j = q[1];
        q[1] = p[1];
        p[1] = j;
        j = q[2];
        q[2] = p[2];
        p[2] = j;
        j = q[3];
        q[3] = p[3];
        p[3] = j;
      }
      /* smallval entry is now in position i */
      if (smallval != previouscol) {
        netindex[previouscol] = (startpos + i) >> 1;
        for (j = previouscol + 1; j < smallval; j++)
          netindex[j] = i;
        previouscol = smallval;
        startpos = i;
      }
    }
    netindex[previouscol] = (startpos + maxnetpos) >> 1;
    for (j = previouscol + 1; j < 256; j++)
      netindex[j] = maxnetpos; /* really 256 */
  }
  /*
   * Main Learning Loop ------------------
   */
  public void learn() {
    int i, j, b, g, r;
    int radius, rad, alpha, step, delta, samplepixels;
    byte[] p;
    int pix, lim;
    if (lengthcount < minpicturebytes)
      samplefac = 1;
    alphadec = 30 + ((samplefac - 1) / 3);
    p = thepicture;
    pix = 0;
    lim = lengthcount;
    samplepixels = lengthcount / (3 * samplefac);
    delta = samplepixels / ncycles;
    alpha = initalpha;
    radius = initradius;
    rad = radius >> radiusbiasshift;
    if (rad <= 1)
      rad = 0;
    for (i = 0; i < rad; i++)
      radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
    // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
    if (lengthcount < minpicturebytes)
      step = 3;
    else if ((lengthcount % prime1) != 0)
      step = 3 * prime1;
    else {
      if ((lengthcount % prime2) != 0)
        step = 3 * prime2;
      else {
        if ((lengthcount % prime3) != 0)
          step = 3 * prime3;
        else
          step = 3 * prime4;
      }
    }
    i = 0;
    while (i < samplepixels) {
      b = (p[pix + 0] & 0xff) << netbiasshift;
      g = (p[pix + 1] & 0xff) << netbiasshift;
      r = (p[pix + 2] & 0xff) << netbiasshift;
      j = contest(b, g, r);
      altersingle(alpha, j, b, g, r);
      if (rad != 0)
        alterneigh(rad, j, b, g, r); /* alter neighbours */
      pix += step;
      if (pix >= lim)
        pix -= lengthcount;
      i++;
      if (delta == 0)
        delta = 1;
      if (i % delta == 0) {
        alpha -= alpha / alphadec;
        radius -= radius / radiusdec;
        rad = radius >> radiusbiasshift;
        if (rad <= 1)
          rad = 0;
        for (j = 0; j < rad; j++)
          radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
      }
    }
    // fprintf(stderr,"finished 1D learning: final alpha=%f
    // !\n",((float)alpha)/initalpha);
  }
  /*
   * Search for BGR values 0..255 (after net is unbiased) and return colour
   * index
   * ----------------------------------------------------------------------------
   */
  public int map(int b, int g, int r) {
    int i, j, dist, a, bestd;
    int[] p;
    int best;
    bestd = 1000; /* biggest possible dist is 256*3 */
    best = -1;
    i = netindex[g]; /* index on g */
    j = i - 1; /* start at netindex[g] and work outwards */
    while ((i < netsize) || (j >= 0)) {
      if (i < netsize) {
        p = network[i];
        dist = p[1] - g; /* inx key */
        if (dist >= bestd)
          i = netsize; /* stop iter */
        else {
          i++;
          if (dist < 0)
            dist = -dist;
          a = p[0] - b;
          if (a < 0)
            a = -a;
          dist += a;
          if (dist < bestd) {
            a = p[2] - r;
            if (a < 0)
              a = -a;
            dist += a;
            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }
      if (j >= 0) {
        p = network[j];
        dist = g - p[1]; /* inx key - reverse dif */
        if (dist >= bestd)
          j = -1; /* stop iter */
        else {
          j--;
          if (dist < 0)
            dist = -dist;
          a = p[0] - b;
          if (a < 0)
            a = -a;
          dist += a;
          if (dist < bestd) {
            a = p[2] - r;
            if (a < 0)
              a = -a;
            dist += a;
            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }
    }
    return (best);
  }
  public byte[] process() {
    learn();
    unbiasnet();
    inxbuild();
    return colorMap();
  }
  /*
   * Unbias network to give byte values 0..255 and record position i to prepare
   * for sort
   * -----------------------------------------------------------------------------------
   */
  public void unbiasnet() {
    int i, j;
    for (i = 0; i < netsize; i++) {
      network[i][0] >>= netbiasshift;
      network[i][1] >>= netbiasshift;
      network[i][2] >>= netbiasshift;
      network[i][3] = i; /* record colour no */
    }
  }
  /*
   * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
   * radpower[|i-j|]
   * ---------------------------------------------------------------------------------
   */
  protected void alterneigh(int rad, int i, int b, int g, int r) {
    int j, k, lo, hi, a, m;
    int[] p;
    lo = i - rad;
    if (lo < -1)
      lo = -1;
    hi = i + rad;
    if (hi > netsize)
      hi = netsize;
    j = i + 1;
    k = i - 1;
    m = 1;
    while ((j < hi) || (k > lo)) {
      a = radpower[m++];
      if (j < hi) {
        p = network[j++];
        try {
          p[0] -= (a * (p[0] - b)) / alpharadbias;
          p[1] -= (a * (p[1] - g)) / alpharadbias;
          p[2] -= (a * (p[2] - r)) / alpharadbias;
        } catch (Exception e) {
        } // prevents 1.3 miscompilation
      }
      if (k > lo) {
        p = network[k--];
        try {
          p[0] -= (a * (p[0] - b)) / alpharadbias;
          p[1] -= (a * (p[1] - g)) / alpharadbias;
          p[2] -= (a * (p[2] - r)) / alpharadbias;
        } catch (Exception e) {
        }
      }
    }
  }
  /*
   * Move neuron i towards biased (b,g,r) by factor alpha
   * ----------------------------------------------------
   */
  protected void altersingle(int alpha, int i, int b, int g, int r) {
    /* alter hit neuron */
    int[] n = network[i];
    n[0] -= (alpha * (n[0] - b)) / initalpha;
    n[1] -= (alpha * (n[1] - g)) / initalpha;
    n[2] -= (alpha * (n[2] - r)) / initalpha;
  }
  /*
   * Search for biased BGR values ----------------------------
   */
  protected int contest(int b, int g, int r) {
    /* finds closest neuron (min dist) and updates freq */
    /* finds best neuron (min dist-bias) and returns position */
    /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
    /* bias[i] = gamma*((1/netsize)-freq[i]) */
    int i, dist, a, biasdist, betafreq;
    int bestpos, bestbiaspos, bestd, bestbiasd;
    int[] n;
    bestd = ~(((int) 1) << 31);
    bestbiasd = bestd;
    bestpos = -1;
    bestbiaspos = bestpos;
    for (i = 0; i < netsize; i++) {
      n = network[i];
      dist = n[0] - b;
      if (dist < 0)
        dist = -dist;
      a = n[1] - g;
      if (a < 0)
        a = -a;
      dist += a;
      a = n[2] - r;
      if (a < 0)
        a = -a;
      dist += a;
      if (dist < bestd) {
        bestd = dist;
        bestpos = i;
      }
      biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
      if (biasdist < bestbiasd) {
        bestbiasd = biasdist;
        bestbiaspos = i;
      }
      betafreq = (freq[i] >> betashift);
      freq[i] -= betafreq;
      bias[i] += (betafreq << gammashift);
    }
    freq[bestpos] += beta;
    bias[bestpos] -= betagamma;
    return (bestbiaspos);
  }
}
// ==============================================================================
// Adapted from Jef Poskanzer"s Java port by way of J. M. G. Elliott.
// K Weiner 12/00
class LZWEncoder {
  private static final int EOF = -1;
  private int imgW, imgH;
  private byte[] pixAry;
  private int initCodeSize;
  private int remaining;
  private int curPixel;
  // GIFCOMPR.C - GIF Image compression routines
  //
  // Lempel-Ziv compression based on "compress". GIF modifications by
  // David Rowley (mgardi@watdcsu.waterloo.edu)
  // General DEFINEs
  static final int BITS = 12;
  static final int HSIZE = 5003; // 80% occupancy
  // GIF Image compression - modified "compress"
  //
  // Based on: compress.c - File compression ala IEEE Computer, June 1984.
  //
  // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
  // Jim McKie (decvax!mcvax!jim)
  // Steve Davies (decvax!vax135!petsd!peora!srd)
  // Ken Turkowski (decvax!decwrl!turtlevax!ken)
  // James A. Woods (decvax!ihnp4!ames!jaw)
  // Joe Orost (decvax!vax135!petsd!joe)
  int n_bits; // number of bits/code
  int maxbits = BITS; // user settable max # bits/code
  int maxcode; // maximum code, given n_bits
  int maxmaxcode = 1 << BITS; // should NEVER generate this code
  int[] htab = new int[HSIZE];
  int[] codetab = new int[HSIZE];
  int hsize = HSIZE; // for dynamic table sizing
  int free_ent = 0; // first unused entry
  // block compression parameters -- after all codes are used up,
  // and compression rate changes, start over.
  boolean clear_flg = false;
  // Algorithm: use open addressing double hashing (no chaining) on the
  // prefix code / next character combination. We do a variant of Knuth"s
  // algorithm D (vol. 3, sec. 6.4) along with G. Knott"s relatively-prime
  // secondary probe. Here, the modular division first probe is gives way
  // to a faster exclusive-or manipulation. Also do block compression with
  // an adaptive reset, whereby the code table is cleared when the compression
  // ratio decreases, but after the table fills. The variable-length output
  // codes are re-sized at this point, and a special CLEAR code is generated
  // for the decompressor. Late addition: construct the table according to
  // file size for noticeable speed improvement on small files. Please direct
  // questions about this implementation to ames!jaw.
  int g_init_bits;
  int ClearCode;
  int EOFCode;
  // output
  //
  // Output the given code.
  // Inputs:
  // code: A n_bits-bit integer. If == -1, then EOF. This assumes
  // that n_bits =< wordsize - 1.
  // Outputs:
  // Outputs code to the file.
  // Assumptions:
  // Chars are 8 bits long.
  // Algorithm:
  // Maintain a BITS character long buffer (so that 8 codes will
  // fit in it exactly). Use the VAX insv instruction to insert each
  // code in turn. When the buffer fills up empty it and start over.
  int cur_accum = 0;
  int cur_bits = 0;
  int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
      0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
  // Number of characters so far in this "packet"
  int a_count;
  // Define the storage for the packet accumulator
  byte[] accum = new byte[256];
  // ----------------------------------------------------------------------------
  LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
    imgW = width;
    imgH = height;
    pixAry = pixels;
    initCodeSize = Math.max(2, color_depth);
  }
  // Add a character to the end of the current packet, and if it is 254
  // characters, flush the packet to disk.
  void char_out(byte c, OutputStream outs) throws IOException {
    accum[a_count++] = c;
    if (a_count >= 254)
      flush_char(outs);
  }
  // Clear out the hash table
  // table clear for block compress
  void cl_block(OutputStream outs) throws IOException {
    cl_hash(hsize);
    free_ent = ClearCode + 2;
    clear_flg = true;
    output(ClearCode, outs);
  }
  // reset code table
  void cl_hash(int hsize) {
    for (int i = 0; i < hsize; ++i)
      htab[i] = -1;
  }
  void compress(int init_bits, OutputStream outs) throws IOException {
    int fcode;
    int i /* = 0 */;
    int c;
    int ent;
    int disp;
    int hsize_reg;
    int hshift;
    // Set up the globals: g_init_bits - initial number of bits
    g_init_bits = init_bits;
    // Set up the necessary values
    clear_flg = false;
    n_bits = g_init_bits;
    maxcode = MAXCODE(n_bits);
    ClearCode = 1 << (init_bits - 1);
    EOFCode = ClearCode + 1;
    free_ent = ClearCode + 2;
    a_count = 0; // clear packet
    ent = nextPixel();
    hshift = 0;
    for (fcode = hsize; fcode < 65536; fcode *= 2)
      ++hshift;
    hshift = 8 - hshift; // set hash code range bound
    hsize_reg = hsize;
    cl_hash(hsize_reg); // clear hash table
    output(ClearCode, outs);
    outer_loop: while ((c = nextPixel()) != EOF) {
      fcode = (c << maxbits) + ent;
      i = (c << hshift) ^ ent; // xor hashing
      if (htab[i] == fcode) {
        ent = codetab[i];
        continue;
      } else if (htab[i] >= 0) // non-empty slot
      {
        disp = hsize_reg - i; // secondary hash (after G. Knott)
        if (i == 0)
          disp = 1;
        do {
          if ((i -= disp) < 0)
            i += hsize_reg;
          if (htab[i] == fcode) {
            ent = codetab[i];
            continue outer_loop;
          }
        } while (htab[i] >= 0);
      }
      output(ent, outs);
      ent = c;
      if (free_ent < maxmaxcode) {
        codetab[i] = free_ent++; // code -> hashtable
        htab[i] = fcode;
      } else
        cl_block(outs);
    }
    // Put out the final code.
    output(ent, outs);
    output(EOFCode, outs);
  }
  // ----------------------------------------------------------------------------
  void encode(OutputStream os) throws IOException {
    os.write(initCodeSize); // write "initial code size" byte
    remaining = imgW * imgH; // reset navigation variables
    curPixel = 0;
    compress(initCodeSize + 1, os); // compress and write the pixel data
    os.write(0); // write block terminator
  }
  // Flush the packet to disk, and reset the accumulator
  void flush_char(OutputStream outs) throws IOException {
    if (a_count > 0) {
      outs.write(a_count);
      outs.write(accum, 0, a_count);
      a_count = 0;
    }
  }
  final int MAXCODE(int n_bits) {
    return (1 << n_bits) - 1;
  }
  // ----------------------------------------------------------------------------
  // Return the next pixel from the image
  // ----------------------------------------------------------------------------
  private int nextPixel() {
    if (remaining == 0)
      return EOF;
    --remaining;
    byte pix = pixAry[curPixel++];
    return pix & 0xff;
  }
  void output(int code, OutputStream outs) throws IOException {
    cur_accum &= masks[cur_bits];
    if (cur_bits > 0)
      cur_accum |= (code << cur_bits);
    else
      cur_accum = code;
    cur_bits += n_bits;
    while (cur_bits >= 8) {
      char_out((byte) (cur_accum & 0xff), outs);
      cur_accum >>= 8;
      cur_bits -= 8;
    }
    // If the next entry is going to be too big for the code size,
    // then increase it, if possible.
    if (free_ent > maxcode || clear_flg) {
      if (clear_flg) {
        maxcode = MAXCODE(n_bits = g_init_bits);
        clear_flg = false;
      } else {
        ++n_bits;
        if (n_bits == maxbits)
          maxcode = maxmaxcode;
        else
          maxcode = MAXCODE(n_bits);
      }
    }
    if (code == EOFCode) {
      // At EOF, write the rest of the buffer.
      while (cur_bits > 0) {
        char_out((byte) (cur_accum & 0xff), outs);
        cur_accum >>= 8;
        cur_bits -= 8;
      }
      flush_char(outs);
    }
  }
}



AnimatedGifEncoder - Encodes a GIF file consisting of one or more frames

  
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
 * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
 * frames.
 * 
 * <pre>
 *  Example:
 *     AnimatedGifEncoder e = new AnimatedGifEncoder();
 *     e.start(outputFileName);
 *     e.setDelay(1000);   // 1 frame per sec
 *     e.addFrame(image1);
 *     e.addFrame(image2);
 *     e.finish();
 * </pre>
 * 
 * No copyright asserted on the source code of this class. May be used for any
 * purpose, however, refer to the Unisys LZW patent for restrictions on use of
 * the associated LZWEncoder class. Please forward any corrections to
 * kweiner@fmsware.ru.
 * 
 * @author Kevin Weiner, FM Software
 * @version 1.03 November 2003
 * 
 */
public class AnimatedGifEncoder {
  protected int width; // image size
  protected int height;
  protected Color transparent = null; // transparent color if given
  protected int transIndex; // transparent index in color table
  protected int repeat = -1; // no repeat
  protected int delay = 0; // frame delay (hundredths)
  protected boolean started = false; // ready to output frames
  protected OutputStream out;
  protected BufferedImage image; // current frame
  protected byte[] pixels; // BGR byte array from frame
  protected byte[] indexedPixels; // converted frame indexed to palette
  protected int colorDepth; // number of bit planes
  protected byte[] colorTab; // RGB palette
  protected boolean[] usedEntry = new boolean[256]; // active palette entries
  protected int palSize = 7; // color table size (bits-1)
  protected int dispose = -1; // disposal code (-1 = use default)
  protected boolean closeStream = false; // close stream when finished
  protected boolean firstFrame = true;
  protected boolean sizeSet = false; // if false, get size from first frame
  protected int sample = 10; // default sample interval for quantizer
  /**
   * Sets the delay time between each frame, or changes it for subsequent frames
   * (applies to last frame added).
   * 
   * @param ms
   *          int delay time in milliseconds
   */
  public void setDelay(int ms) {
    delay = Math.round(ms / 10.0f);
  }
  /**
   * Sets the GIF frame disposal code for the last added frame and any
   * subsequent frames. Default is 0 if no transparent color has been set,
   * otherwise 2.
   * 
   * @param code
   *          int disposal code.
   */
  public void setDispose(int code) {
    if (code >= 0) {
      dispose = code;
    }
  }
  /**
   * Sets the number of times the set of GIF frames should be played. Default is
   * 1; 0 means play indefinitely. Must be invoked before the first image is
   * added.
   * 
   * @param iter
   *          int number of iterations.
   * @return
   */
  public void setRepeat(int iter) {
    if (iter >= 0) {
      repeat = iter;
    }
  }
  /**
   * Sets the transparent color for the last added frame and any subsequent
   * frames. Since all colors are subject to modification in the quantization
   * process, the color in the final palette for each frame closest to the given
   * color becomes the transparent color for that frame. May be set to null to
   * indicate no transparent color.
   * 
   * @param c
   *          Color to be treated as transparent on display.
   */
  public void setTransparent(Color c) {
    transparent = c;
  }
  /**
   * Adds next GIF frame. The frame is not written immediately, but is actually
   * deferred until the next frame is received so that timing data can be
   * inserted. Invoking <code>finish()</code> flushes all frames. If
   * <code>setSize</code> was not invoked, the size of the first image is used
   * for all subsequent frames.
   * 
   * @param im
   *          BufferedImage containing frame to write.
   * @return true if successful.
   */
  public boolean addFrame(BufferedImage im) {
    if ((im == null) || !started) {
      return false;
    }
    boolean ok = true;
    try {
      if (!sizeSet) {
        // use first frame"s size
        setSize(im.getWidth(), im.getHeight());
      }
      image = im;
      getImagePixels(); // convert to correct format if necessary
      analyzePixels(); // build color table & map pixels
      if (firstFrame) {
        writeLSD(); // logical screen descriptior
        writePalette(); // global color table
        if (repeat >= 0) {
          // use NS app extension to indicate reps
          writeNetscapeExt();
        }
      }
      writeGraphicCtrlExt(); // write graphic control extension
      writeImageDesc(); // image descriptor
      if (!firstFrame) {
        writePalette(); // local color table
      }
      writePixels(); // encode and write pixel data
      firstFrame = false;
    } catch (IOException e) {
      ok = false;
    }
    return ok;
  }
  /**
   * Flushes any pending data and closes output file. If writing to an
   * OutputStream, the stream is not closed.
   */
  public boolean finish() {
    if (!started)
      return false;
    boolean ok = true;
    started = false;
    try {
      out.write(0x3b); // gif trailer
      out.flush();
      if (closeStream) {
        out.close();
      }
    } catch (IOException e) {
      ok = false;
    }
    // reset for subsequent use
    transIndex = 0;
    out = null;
    image = null;
    pixels = null;
    indexedPixels = null;
    colorTab = null;
    closeStream = false;
    firstFrame = true;
    return ok;
  }
  /**
   * Sets frame rate in frames per second. Equivalent to
   * <code>setDelay(1000/fps)</code>.
   * 
   * @param fps
   *          float frame rate (frames per second)
   */
  public void setFrameRate(float fps) {
    if (fps != 0f) {
      delay = Math.round(100f / fps);
    }
  }
  /**
   * Sets quality of color quantization (conversion of images to the maximum 256
   * colors allowed by the GIF specification). Lower values (minimum = 1)
   * produce better colors, but slow processing significantly. 10 is the
   * default, and produces good color mapping at reasonable speeds. Values
   * greater than 20 do not yield significant improvements in speed.
   * 
   * @param quality
   *          int greater than 0.
   * @return
   */
  public void setQuality(int quality) {
    if (quality < 1)
      quality = 1;
    sample = quality;
  }
  /**
   * Sets the GIF frame size. The default size is the size of the first frame
   * added if this method is not invoked.
   * 
   * @param w
   *          int frame width.
   * @param h
   *          int frame width.
   */
  public void setSize(int w, int h) {
    if (started && !firstFrame)
      return;
    width = w;
    height = h;
    if (width < 1)
      width = 320;
    if (height < 1)
      height = 240;
    sizeSet = true;
  }
  /**
   * Initiates GIF file creation on the given stream. The stream is not closed
   * automatically.
   * 
   * @param os
   *          OutputStream on which GIF images are written.
   * @return false if initial write failed.
   */
  public boolean start(OutputStream os) {
    if (os == null)
      return false;
    boolean ok = true;
    closeStream = false;
    out = os;
    try {
      writeString("GIF89a"); // header
    } catch (IOException e) {
      ok = false;
    }
    return started = ok;
  }
  /**
   * Initiates writing of a GIF file with the specified name.
   * 
   * @param file
   *          String containing output file name.
   * @return false if open or initial write failed.
   */
  public boolean start(String file) {
    boolean ok = true;
    try {
      out = new BufferedOutputStream(new FileOutputStream(file));
      ok = start(out);
      closeStream = true;
    } catch (IOException e) {
      ok = false;
    }
    return started = ok;
  }
  /**
   * Analyzes image colors and creates color map.
   */
  protected void analyzePixels() {
    int len = pixels.length;
    int nPix = len / 3;
    indexedPixels = new byte[nPix];
    NeuQuant nq = new NeuQuant(pixels, len, sample);
    // initialize quantizer
    colorTab = nq.process(); // create reduced palette
    // convert map from BGR to RGB
    for (int i = 0; i < colorTab.length; i += 3) {
      byte temp = colorTab[i];
      colorTab[i] = colorTab[i + 2];
      colorTab[i + 2] = temp;
      usedEntry[i / 3] = false;
    }
    // map image pixels to new palette
    int k = 0;
    for (int i = 0; i < nPix; i++) {
      int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
      usedEntry[index] = true;
      indexedPixels[i] = (byte) index;
    }
    pixels = null;
    colorDepth = 8;
    palSize = 7;
    // get closest match to transparent color if specified
    if (transparent != null) {
      transIndex = findClosest(transparent);
    }
  }
  /**
   * Returns index of palette color closest to c
   * 
   */
  protected int findClosest(Color c) {
    if (colorTab == null)
      return -1;
    int r = c.getRed();
    int g = c.getGreen();
    int b = c.getBlue();
    int minpos = 0;
    int dmin = 256 * 256 * 256;
    int len = colorTab.length;
    for (int i = 0; i < len;) {
      int dr = r - (colorTab[i++] & 0xff);
      int dg = g - (colorTab[i++] & 0xff);
      int db = b - (colorTab[i] & 0xff);
      int d = dr * dr + dg * dg + db * db;
      int index = i / 3;
      if (usedEntry[index] && (d < dmin)) {
        dmin = d;
        minpos = index;
      }
      i++;
    }
    return minpos;
  }
  /**
   * Extracts image pixels into byte array "pixels"
   */
  protected void getImagePixels() {
    int w = image.getWidth();
    int h = image.getHeight();
    int type = image.getType();
    if ((w != width) || (h != height) || (type != BufferedImage.TYPE_3BYTE_BGR)) {
      // create new image with right size/format
      BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
      Graphics2D g = temp.createGraphics();
      g.drawImage(image, 0, 0, null);
      image = temp;
    }
    pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
  }
  /**
   * Writes Graphic Control Extension
   */
  protected void writeGraphicCtrlExt() throws IOException {
    out.write(0x21); // extension introducer
    out.write(0xf9); // GCE label
    out.write(4); // data block size
    int transp, disp;
    if (transparent == null) {
      transp = 0;
      disp = 0; // dispose = no action
    } else {
      transp = 1;
      disp = 2; // force clear if using transparent color
    }
    if (dispose >= 0) {
      disp = dispose & 7; // user override
    }
    disp <<= 2;
    // packed fields
    out.write(0 | // 1:3 reserved
        disp | // 4:6 disposal
        0 | // 7 user input - 0 = none
        transp); // 8 transparency flag
    writeShort(delay); // delay x 1/100 sec
    out.write(transIndex); // transparent color index
    out.write(0); // block terminator
  }
  /**
   * Writes Image Descriptor
   */
  protected void writeImageDesc() throws IOException {
    out.write(0x2c); // image separator
    writeShort(0); // image position x,y = 0,0
    writeShort(0);
    writeShort(width); // image size
    writeShort(height);
    // packed fields
    if (firstFrame) {
      // no LCT - GCT is used for first (or only) frame
      out.write(0);
    } else {
      // specify normal LCT
      out.write(0x80 | // 1 local color table 1=yes
          0 | // 2 interlace - 0=no
          0 | // 3 sorted - 0=no
          0 | // 4-5 reserved
          palSize); // 6-8 size of color table
    }
  }
  /**
   * Writes Logical Screen Descriptor
   */
  protected void writeLSD() throws IOException {
    // logical screen size
    writeShort(width);
    writeShort(height);
    // packed fields
    out.write((0x80 | // 1 : global color table flag = 1 (gct used)
        0x70 | // 2-4 : color resolution = 7
        0x00 | // 5 : gct sort flag = 0
        palSize)); // 6-8 : gct size
    out.write(0); // background color index
    out.write(0); // pixel aspect ratio - assume 1:1
  }
  /**
   * Writes Netscape application extension to define repeat count.
   */
  protected void writeNetscapeExt() throws IOException {
    out.write(0x21); // extension introducer
    out.write(0xff); // app extension label
    out.write(11); // block size
    writeString("NETSCAPE" + "2.0"); // app id + auth code
    out.write(3); // sub-block size
    out.write(1); // loop sub-block id
    writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
    out.write(0); // block terminator
  }
  /**
   * Writes color table
   */
  protected void writePalette() throws IOException {
    out.write(colorTab, 0, colorTab.length);
    int n = (3 * 256) - colorTab.length;
    for (int i = 0; i < n; i++) {
      out.write(0);
    }
  }
  /**
   * Encodes and writes pixel data
   */
  protected void writePixels() throws IOException {
    LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
    encoder.encode(out);
  }
  /**
   * Write 16-bit value to output stream, LSB first
   */
  protected void writeShort(int value) throws IOException {
    out.write(value & 0xff);
    out.write((value >> 8) & 0xff);
  }
  /**
   * Writes string to output stream
   */
  protected void writeString(String s) throws IOException {
    for (int i = 0; i < s.length(); i++) {
      out.write((byte) s.charAt(i));
    }
  }
}
// 
// Adapted from Jef Poskanzer"s Java port by way of J. M. G. Elliott.
// K Weiner 12/00
class LZWEncoder {
  private static final int EOF = -1;
  private int imgW, imgH;
  private byte[] pixAry;
  private int initCodeSize;
  private int remaining;
  private int curPixel;
  // GIFCOMPR.C - GIF Image compression routines
  //
  // Lempel-Ziv compression based on "compress". GIF modifications by
  // David Rowley (mgardi@watdcsu.waterloo.edu)
  // General DEFINEs
  static final int BITS = 12;
  static final int HSIZE = 5003; // 80% occupancy
  // GIF Image compression - modified "compress"
  //
  // Based on: compress.c - File compression ala IEEE Computer, June 1984.
  //
  // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
  // Jim McKie (decvax!mcvax!jim)
  // Steve Davies (decvax!vax135!petsd!peora!srd)
  // Ken Turkowski (decvax!decwrl!turtlevax!ken)
  // James A. Woods (decvax!ihnp4!ames!jaw)
  // Joe Orost (decvax!vax135!petsd!joe)
  int n_bits; // number of bits/code
  int maxbits = BITS; // user settable max # bits/code
  int maxcode; // maximum code, given n_bits
  int maxmaxcode = 1 << BITS; // should NEVER generate this code
  int[] htab = new int[HSIZE];
  int[] codetab = new int[HSIZE];
  int hsize = HSIZE; // for dynamic table sizing
  int free_ent = 0; // first unused entry
  // block compression parameters -- after all codes are used up,
  // and compression rate changes, start over.
  boolean clear_flg = false;
  // Algorithm: use open addressing double hashing (no chaining) on the
  // prefix code / next character combination. We do a variant of Knuth"s
  // algorithm D (vol. 3, sec. 6.4) along with G. Knott"s relatively-prime
  // secondary probe. Here, the modular division first probe is gives way
  // to a faster exclusive-or manipulation. Also do block compression with
  // an adaptive reset, whereby the code table is cleared when the compression
  // ratio decreases, but after the table fills. The variable-length output
  // codes are re-sized at this point, and a special CLEAR code is generated
  // for the decompressor. Late addition: construct the table according to
  // file size for noticeable speed improvement on small files. Please direct
  // questions about this implementation to ames!jaw.
  int g_init_bits;
  int ClearCode;
  int EOFCode;
  // output
  //
  // Output the given code.
  // Inputs:
  // code: A n_bits-bit integer. If == -1, then EOF. This assumes
  // that n_bits =< wordsize - 1.
  // Outputs:
  // Outputs code to the file.
  // Assumptions:
  // Chars are 8 bits long.
  // Algorithm:
  // Maintain a BITS character long buffer (so that 8 codes will
  // fit in it exactly). Use the VAX insv instruction to insert each
  // code in turn. When the buffer fills up empty it and start over.
  int cur_accum = 0;
  int cur_bits = 0;
  int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
      0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
  // Number of characters so far in this "packet"
  int a_count;
  // Define the storage for the packet accumulator
  byte[] accum = new byte[256];
  // ----------------------------------------------------------------------------
  LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
    imgW = width;
    imgH = height;
    pixAry = pixels;
    initCodeSize = Math.max(2, color_depth);
  }
  // Add a character to the end of the current packet, and if it is 254
  // characters, flush the packet to disk.
  void char_out(byte c, OutputStream outs) throws IOException {
    accum[a_count++] = c;
    if (a_count >= 254)
      flush_char(outs);
  }
  // Clear out the hash table
  // table clear for block compress
  void cl_block(OutputStream outs) throws IOException {
    cl_hash(hsize);
    free_ent = ClearCode + 2;
    clear_flg = true;
    output(ClearCode, outs);
  }
  // reset code table
  void cl_hash(int hsize) {
    for (int i = 0; i < hsize; ++i)
      htab[i] = -1;
  }
  void compress(int init_bits, OutputStream outs) throws IOException {
    int fcode;
    int i /* = 0 */;
    int c;
    int ent;
    int disp;
    int hsize_reg;
    int hshift;
    // Set up the globals: g_init_bits - initial number of bits
    g_init_bits = init_bits;
    // Set up the necessary values
    clear_flg = false;
    n_bits = g_init_bits;
    maxcode = MAXCODE(n_bits);
    ClearCode = 1 << (init_bits - 1);
    EOFCode = ClearCode + 1;
    free_ent = ClearCode + 2;
    a_count = 0; // clear packet
    ent = nextPixel();
    hshift = 0;
    for (fcode = hsize; fcode < 65536; fcode *= 2)
      ++hshift;
    hshift = 8 - hshift; // set hash code range bound
    hsize_reg = hsize;
    cl_hash(hsize_reg); // clear hash table
    output(ClearCode, outs);
    outer_loop: while ((c = nextPixel()) != EOF) {
      fcode = (c << maxbits) + ent;
      i = (c << hshift) ^ ent; // xor hashing
      if (htab[i] == fcode) {
        ent = codetab[i];
        continue;
      } else if (htab[i] >= 0) // non-empty slot
      {
        disp = hsize_reg - i; // secondary hash (after G. Knott)
        if (i == 0)
          disp = 1;
        do {
          if ((i -= disp) < 0)
            i += hsize_reg;
          if (htab[i] == fcode) {
            ent = codetab[i];
            continue outer_loop;
          }
        } while (htab[i] >= 0);
      }
      output(ent, outs);
      ent = c;
      if (free_ent < maxmaxcode) {
        codetab[i] = free_ent++; // code -> hashtable
        htab[i] = fcode;
      } else
        cl_block(outs);
    }
    // Put out the final code.
    output(ent, outs);
    output(EOFCode, outs);
  }
  // ----------------------------------------------------------------------------
  void encode(OutputStream os) throws IOException {
    os.write(initCodeSize); // write "initial code size" byte
    remaining = imgW * imgH; // reset navigation variables
    curPixel = 0;
    compress(initCodeSize + 1, os); // compress and write the pixel data
    os.write(0); // write block terminator
  }
  // Flush the packet to disk, and reset the accumulator
  void flush_char(OutputStream outs) throws IOException {
    if (a_count > 0) {
      outs.write(a_count);
      outs.write(accum, 0, a_count);
      a_count = 0;
    }
  }
  final int MAXCODE(int n_bits) {
    return (1 << n_bits) - 1;
  }
  // ----------------------------------------------------------------------------
  // Return the next pixel from the image
  // ----------------------------------------------------------------------------
  private int nextPixel() {
    if (remaining == 0)
      return EOF;
    --remaining;
    byte pix = pixAry[curPixel++];
    return pix & 0xff;
  }
  void output(int code, OutputStream outs) throws IOException {
    cur_accum &= masks[cur_bits];
    if (cur_bits > 0)
      cur_accum |= (code << cur_bits);
    else
      cur_accum = code;
    cur_bits += n_bits;
    while (cur_bits >= 8) {
      char_out((byte) (cur_accum & 0xff), outs);
      cur_accum >>= 8;
      cur_bits -= 8;
    }
    // If the next entry is going to be too big for the code size,
    // then increase it, if possible.
    if (free_ent > maxcode || clear_flg) {
      if (clear_flg) {
        maxcode = MAXCODE(n_bits = g_init_bits);
        clear_flg = false;
      } else {
        ++n_bits;
        if (n_bits == maxbits)
          maxcode = maxmaxcode;
        else
          maxcode = MAXCODE(n_bits);
      }
    }
    if (code == EOFCode) {
      // At EOF, write the rest of the buffer.
      while (cur_bits > 0) {
        char_out((byte) (cur_accum & 0xff), outs);
        cur_accum >>= 8;
        cur_bits -= 8;
      }
      flush_char(outs);
    }
  }
}
/*
 * NeuQuant Neural-Net Quantization Algorithm
 * ------------------------------------------
 * 
 * Copyright (c) 1994 Anthony Dekker
 * 
 * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
 * "Kohonen neural networks for optimal colour quantization" in "Network:
 * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
 * the algorithm.
 * 
 * Any party obtaining a copy of these files from the author, directly or
 * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
 * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
 * this software and documentation files (the "Software"), including without
 * limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons who
 * receive copies from any such party to do so, with the only requirement being
 * that this copyright notice remain intact.
 */
// Ported to Java 12/00 K Weiner
class NeuQuant {
  protected static final int netsize = 256; /* number of colours used */
  /* four primes near 500 - assume no image has a length so large */
  /* that it is divisible by all four primes */
  protected static final int prime1 = 499;
  protected static final int prime2 = 491;
  protected static final int prime3 = 487;
  protected static final int prime4 = 503;
  protected static final int minpicturebytes = (3 * prime4);
  /* minimum size for input image */
  /*
   * Program Skeleton ---------------- [select samplefac in range 1..30] [read
   * image from input file] pic = (unsigned char*) malloc(3*width*height);
   * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
   * image header, using writecolourmap(f)] inxbuild(); write output image using
   * inxsearch(b,g,r)
   */
  /*
   * Network Definitions -------------------
   */
  protected static final int maxnetpos = (netsize - 1);
  protected static final int netbiasshift = 4; /* bias for colour values */
  protected static final int ncycles = 100; /* no. of learning cycles */
  /* defs for freq and bias */
  protected static final int intbiasshift = 16; /* bias for fractions */
  protected static final int intbias = (((int) 1) << intbiasshift);
  protected static final int gammashift = 10; /* gamma = 1024 */
  protected static final int gamma = (((int) 1) << gammashift);
  protected static final int betashift = 10;
  protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
  protected static final int betagamma = (intbias << (gammashift - betashift));
  /* defs for decreasing radius factor */
  protected static final int initrad = (netsize >> 3); /*
                                                         * for 256 cols, radius
                                                         * starts
                                                         */
  protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
  protected static final int radiusbias = (((int) 1) << radiusbiasshift);
  protected static final int initradius = (initrad * radiusbias); /*
                                                                   * and
                                                                   * decreases
                                                                   * by a
                                                                   */
  protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
  /* defs for decreasing alpha factor */
  protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
  protected static final int initalpha = (((int) 1) << alphabiasshift);
  protected int alphadec; /* biased by 10 bits */
  /* radbias and alpharadbias used for radpower calculation */
  protected static final int radbiasshift = 8;
  protected static final int radbias = (((int) 1) << radbiasshift);
  protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
  protected static final int alpharadbias = (((int) 1) << alpharadbshift);
  /*
   * Types and Global Variables --------------------------
   */
  protected byte[] thepicture; /* the input image itself */
  protected int lengthcount; /* lengthcount = H*W*3 */
  protected int samplefac; /* sampling factor 1..30 */
  // typedef int pixel[4]; /* BGRc */
  protected int[][] network; /* the network itself - [netsize][4] */
  protected int[] netindex = new int[256];
  /* for network lookup - really 256 */
  protected int[] bias = new int[netsize];
  /* bias and freq arrays for learning */
  protected int[] freq = new int[netsize];
  protected int[] radpower = new int[initrad];
  /* radpower for precomputation */
  /*
   * Initialise network in range (0,0,0) to (255,255,255) and set parameters
   * -----------------------------------------------------------------------
   */
  public NeuQuant(byte[] thepic, int len, int sample) {
    int i;
    int[] p;
    thepicture = thepic;
    lengthcount = len;
    samplefac = sample;
    network = new int[netsize][];
    for (i = 0; i < netsize; i++) {
      network[i] = new int[4];
      p = network[i];
      p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
      freq[i] = intbias / netsize; /* 1/netsize */
      bias[i] = 0;
    }
  }
  public byte[] colorMap() {
    byte[] map = new byte[3 * netsize];
    int[] index = new int[netsize];
    for (int i = 0; i < netsize; i++)
      index[network[i][3]] = i;
    int k = 0;
    for (int i = 0; i < netsize; i++) {
      int j = index[i];
      map[k++] = (byte) (network[j][0]);
      map[k++] = (byte) (network[j][1]);
      map[k++] = (byte) (network[j][2]);
    }
    return map;
  }
  /*
   * Insertion sort of network and building of netindex[0..255] (to do after
   * unbias)
   * -------------------------------------------------------------------------------
   */
  public void inxbuild() {
    int i, j, smallpos, smallval;
    int[] p;
    int[] q;
    int previouscol, startpos;
    previouscol = 0;
    startpos = 0;
    for (i = 0; i < netsize; i++) {
      p = network[i];
      smallpos = i;
      smallval = p[1]; /* index on g */
      /* find smallest in i..netsize-1 */
      for (j = i + 1; j < netsize; j++) {
        q = network[j];
        if (q[1] < smallval) { /* index on g */
          smallpos = j;
          smallval = q[1]; /* index on g */
        }
      }
      q = network[smallpos];
      /* swap p (i) and q (smallpos) entries */
      if (i != smallpos) {
        j = q[0];
        q[0] = p[0];
        p[0] = j;
        j = q[1];
        q[1] = p[1];
        p[1] = j;
        j = q[2];
        q[2] = p[2];
        p[2] = j;
        j = q[3];
        q[3] = p[3];
        p[3] = j;
      }
      /* smallval entry is now in position i */
      if (smallval != previouscol) {
        netindex[previouscol] = (startpos + i) >> 1;
        for (j = previouscol + 1; j < smallval; j++)
          netindex[j] = i;
        previouscol = smallval;
        startpos = i;
      }
    }
    netindex[previouscol] = (startpos + maxnetpos) >> 1;
    for (j = previouscol + 1; j < 256; j++)
      netindex[j] = maxnetpos; /* really 256 */
  }
  /*
   * Main Learning Loop ------------------
   */
  public void learn() {
    int i, j, b, g, r;
    int radius, rad, alpha, step, delta, samplepixels;
    byte[] p;
    int pix, lim;
    if (lengthcount < minpicturebytes)
      samplefac = 1;
    alphadec = 30 + ((samplefac - 1) / 3);
    p = thepicture;
    pix = 0;
    lim = lengthcount;
    samplepixels = lengthcount / (3 * samplefac);
    delta = samplepixels / ncycles;
    alpha = initalpha;
    radius = initradius;
    rad = radius >> radiusbiasshift;
    if (rad <= 1)
      rad = 0;
    for (i = 0; i < rad; i++)
      radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
    // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
    if (lengthcount < minpicturebytes)
      step = 3;
    else if ((lengthcount % prime1) != 0)
      step = 3 * prime1;
    else {
      if ((lengthcount % prime2) != 0)
        step = 3 * prime2;
      else {
        if ((lengthcount % prime3) != 0)
          step = 3 * prime3;
        else
          step = 3 * prime4;
      }
    }
    i = 0;
    while (i < samplepixels) {
      b = (p[pix + 0] & 0xff) << netbiasshift;
      g = (p[pix + 1] & 0xff) << netbiasshift;
      r = (p[pix + 2] & 0xff) << netbiasshift;
      j = contest(b, g, r);
      altersingle(alpha, j, b, g, r);
      if (rad != 0)
        alterneigh(rad, j, b, g, r); /* alter neighbours */
      pix += step;
      if (pix >= lim)
        pix -= lengthcount;
      i++;
      if (delta == 0)
        delta = 1;
      if (i % delta == 0) {
        alpha -= alpha / alphadec;
        radius -= radius / radiusdec;
        rad = radius >> radiusbiasshift;
        if (rad <= 1)
          rad = 0;
        for (j = 0; j < rad; j++)
          radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
      }
    }
    // fprintf(stderr,"finished 1D learning: final alpha=%f
    // !\n",((float)alpha)/initalpha);
  }
  /*
   * Search for BGR values 0..255 (after net is unbiased) and return colour
   * index
   * ----------------------------------------------------------------------------
   */
  public int map(int b, int g, int r) {
    int i, j, dist, a, bestd;
    int[] p;
    int best;
    bestd = 1000; /* biggest possible dist is 256*3 */
    best = -1;
    i = netindex[g]; /* index on g */
    j = i - 1; /* start at netindex[g] and work outwards */
    while ((i < netsize) || (j >= 0)) {
      if (i < netsize) {
        p = network[i];
        dist = p[1] - g; /* inx key */
        if (dist >= bestd)
          i = netsize; /* stop iter */
        else {
          i++;
          if (dist < 0)
            dist = -dist;
          a = p[0] - b;
          if (a < 0)
            a = -a;
          dist += a;
          if (dist < bestd) {
            a = p[2] - r;
            if (a < 0)
              a = -a;
            dist += a;
            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }
      if (j >= 0) {
        p = network[j];
        dist = g - p[1]; /* inx key - reverse dif */
        if (dist >= bestd)
          j = -1; /* stop iter */
        else {
          j--;
          if (dist < 0)
            dist = -dist;
          a = p[0] - b;
          if (a < 0)
            a = -a;
          dist += a;
          if (dist < bestd) {
            a = p[2] - r;
            if (a < 0)
              a = -a;
            dist += a;
            if (dist < bestd) {
              bestd = dist;
              best = p[3];
            }
          }
        }
      }
    }
    return (best);
  }
  public byte[] process() {
    learn();
    unbiasnet();
    inxbuild();
    return colorMap();
  }
  /*
   * Unbias network to give byte values 0..255 and record position i to prepare
   * for sort
   * -----------------------------------------------------------------------------------
   */
  public void unbiasnet() {
    for (int i = 0; i < netsize; i++) {
      network[i][0] >>= netbiasshift;
      network[i][1] >>= netbiasshift;
      network[i][2] >>= netbiasshift;
      network[i][3] = i; /* record colour no */
    }
  }
  /*
   * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
   * radpower[|i-j|]
   * ---------------------------------------------------------------------------------
   */
  protected void alterneigh(int rad, int i, int b, int g, int r) {
    int j, k, lo, hi, a, m;
    int[] p;
    lo = i - rad;
    if (lo < -1)
      lo = -1;
    hi = i + rad;
    if (hi > netsize)
      hi = netsize;
    j = i + 1;
    k = i - 1;
    m = 1;
    while ((j < hi) || (k > lo)) {
      a = radpower[m++];
      if (j < hi) {
        p = network[j++];
        try {
          p[0] -= (a * (p[0] - b)) / alpharadbias;
          p[1] -= (a * (p[1] - g)) / alpharadbias;
          p[2] -= (a * (p[2] - r)) / alpharadbias;
        } catch (Exception e) {
        } // prevents 1.3 miscompilation
      }
      if (k > lo) {
        p = network[k--];
        try {
          p[0] -= (a * (p[0] - b)) / alpharadbias;
          p[1] -= (a * (p[1] - g)) / alpharadbias;
          p[2] -= (a * (p[2] - r)) / alpharadbias;
        } catch (Exception e) {
        }
      }
    }
  }
  /*
   * Move neuron i towards biased (b,g,r) by factor alpha
   * ----------------------------------------------------
   */
  protected void altersingle(int alpha, int i, int b, int g, int r) {
    /* alter hit neuron */
    int[] n = network[i];
    n[0] -= (alpha * (n[0] - b)) / initalpha;
    n[1] -= (alpha * (n[1] - g)) / initalpha;
    n[2] -= (alpha * (n[2] - r)) / initalpha;
  }
  /*
   * Search for biased BGR values ----------------------------
   */
  protected int contest(int b, int g, int r) {
    /* finds closest neuron (min dist) and updates freq */
    /* finds best neuron (min dist-bias) and returns position */
    /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
    /* bias[i] = gamma*((1/netsize)-freq[i]) */
    int i, dist, a, biasdist, betafreq;
    int bestpos, bestbiaspos, bestd, bestbiasd;
    int[] n;
    bestd = ~(((int) 1) << 31);
    bestbiasd = bestd;
    bestpos = -1;
    bestbiaspos = bestpos;
    for (i = 0; i < netsize; i++) {
      n = network[i];
      dist = n[0] - b;
      if (dist < 0)
        dist = -dist;
      a = n[1] - g;
      if (a < 0)
        a = -a;
      dist += a;
      a = n[2] - r;
      if (a < 0)
        a = -a;
      dist += a;
      if (dist < bestd) {
        bestd = dist;
        bestpos = i;
      }
      biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
      if (biasdist < bestbiasd) {
        bestbiasd = biasdist;
        bestbiaspos = i;
      }
      betafreq = (freq[i] >> betashift);
      freq[i] -= betafreq;
      bias[i] += (betafreq << gammashift);
    }
    freq[bestpos] += beta;
    bias[bestpos] -= betagamma;
    return (bestbiaspos);
  }
}



Class for converting images to GIF files

   
/*
 * (C) 2004 - Geotechnical Software Services
 * 
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this program; if not, write to the Free 
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA  02111-1307, USA.
 */
package no.geosoft.cc.io;

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

/**
 * Class for converting images to GIF files.
 *
 * <p>
 * Contribution:
 * <ul>
 *   <li>Sverre H. Huseby (gifsave.c on which this is based)</li>
 *   <li>Adam Doppelt (Initial Java port)</li>
 *   <li>Greg Faron (Initial java port)</li>
 * </ul>
 * 
 * @author 
 */   
public class GifEncoder
{
  private short   imageWidth_, imageHeight_;
  private int     nColors_;
  private byte[]  pixels_ = null;
  private byte[]  colors_ = null;

  /**
   * Constructing a GIF encoder.
   *
   * @param image  The image to encode. The image must be
   *               completely loaded.
   * @throws AWTException  If memory is exhausted or image contains
   *                       more than 256 colors.
   */
  public GifEncoder (Image image)
    throws AWTException
  {
    imageWidth_  = (short) image.getWidth (null);
    imageHeight_ = (short) image.getHeight (null);
    int values[] = new int[imageWidth_ * imageHeight_];
    PixelGrabber grabber = new PixelGrabber (image, 0, 0,
                                             imageWidth_, imageHeight_,
                                             values, 0, imageWidth_);
    try {
      if (grabber.grabPixels() != true)
        throw new AWTException("Grabber returned false: " + grabber.status());
    }
    catch (InterruptedException exception) {
    }
    byte[][] r = new byte[imageWidth_][imageHeight_];
    byte[][] g = new byte[imageWidth_][imageHeight_];
    byte[][] b = new byte[imageWidth_][imageHeight_];
    int index = 0;
    for (int y = 0; y < imageHeight_; y ++) {
      for (int x = 0; x < imageWidth_; x ++, index ++) {
        r[x][y] = (byte) ((values[index] >> 16) & 0xFF);
        g[x][y] = (byte) ((values[index] >> 8)  & 0xFF);
        b[x][y] = (byte) ((values[index] >> 0)  & 0xFF);
      }
    }
    toIndexColor (r, g, b);
  }

  
  /**
   * Create a GIF encoder. r[i][j] refers to the pixel at
   * column i, row j.
   *
   * @param r  Red intensity values.
   * @param g  Green intensity values.
   * @param b  Blue intensity values.
   * @throws AWTException  If memory is exhausted or image contains
   *                       more than 256 colors.
   */
  public GifEncoder (byte[][] r, byte[][] g, byte[][] b)
    throws AWTException
  {
    imageWidth_  = (short) (r.length);
    imageHeight_ = (short) (r[0].length);
    toIndexColor(r, g, b);
  }

  
  /**
   * Write image to GIF file.
   * 
   * @param image  Image to write.
   * @param file   File to erite to.
   */
  public static void writeFile (Image image, File file)
    throws AWTException, IOException
  {
    GifEncoder gifEncoder = new GifEncoder (image);
    gifEncoder.write (new FileOutputStream (file));
  }
  
  
  /**
   * Write AWT/Swing component to GIF file.
   * 
   * @param image  Image to write.
   * @param file   File to erite to.
   */
  public static void writeFile (Component component, File file)
    throws AWTException, IOException
  {
    Image image  = component.createImage (component.getWidth(),
                                          component.getHeight());
    Graphics graphics = image.getGraphics();
    component.printAll (graphics);
    GifEncoder.writeFile (image, file);
  }
  
  
  
  /**
   * Writes the image out to a stream in GIF format.
   * This will be a single GIF87a image, non-interlaced, with no
   * background color.
   *
   * @param  stream       The stream to which to output.
   * @throws IOException  Thrown if a write operation fails.
   */
  public void write (OutputStream stream)
    throws IOException
  {
    writeString (stream, "GIF87a");
    writeScreenDescriptor (stream);
    stream.write (colors_, 0, colors_.length);
    writeImageDescriptor (stream, imageWidth_, imageHeight_, ",");
    byte codeSize = bitsNeeded (nColors_);
    if (codeSize == 1) codeSize++;
    stream.write (codeSize);
    writeLzwCompressed (stream, codeSize, pixels_);
    stream.write(0);
    writeImageDescriptor (stream, (short) 0, (short) 0, ";");
    stream.flush();
    stream.close();
  }

  
  /**
   * Converts rgb desrcription of image to colour
   * number description used by GIF.
   *
   * @param r  Red array of pixels.
   * @param g  Green array of pixels.
   * @param b  Blue array of pixels.
   * @throws   AWTException
   *           Thrown if too many different colours in image.
   */
  private void toIndexColor (byte[][] r, byte[][] g, byte[][] b)
    throws AWTException
  {
    pixels_ = new byte[imageWidth_ * imageHeight_];
    colors_ = new byte[256 * 3];
    int colornum = 0;
    for (int x = 0; x < imageWidth_; x++) {
      for (int y = 0; y < imageHeight_; y++ ) {
        int search;
        for (search = 0; search < colornum; search ++ ) {
          if (colors_[search * 3 + 0] == 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.");
        // Row major order y=row x=col
        pixels_[y * imageWidth_ + x] = (byte) search;
        
        if (search == colornum) {
          colors_[search * 3 + 0] = r[x][y]; // [col][row]
          colors_[search * 3 + 1] = g[x][y];
          colors_[search * 3 + 2] = b[x][y];
          colornum++;
        }
      }
    }
    
    nColors_ = 1 << bitsNeeded (colornum);
    byte copy[] = new byte[nColors_ * 3];
    System.arraycopy (colors_, 0, copy, 0, nColors_ * 3);
    colors_ = copy;
  }

  private byte bitsNeeded (int n)
  {
    if (n-- == 0) return 0;
    byte nBitsNeeded = 1;
    while ((n >>= 1) != 0)
      nBitsNeeded++;
    return nBitsNeeded;
  }
  
  private void writeWord (OutputStream stream, short w)
    throws IOException
  {
    stream.write (w & 0xFF);
    stream.write ((w >> 8) & 0xFF);
  }

  
  private void writeString (OutputStream stream, String string)
    throws IOException
  {
    for (int i = 0; i < string.length(); i ++ )
      stream.write ((byte) (string.charAt (i)));
  }

  private void writeScreenDescriptor (OutputStream stream)
    throws IOException
  {
    writeWord (stream, imageWidth_);
    writeWord (stream, imageHeight_);
    byte flag = 0;
    // Global color table size
    byte globalColorTableSize = (byte) (bitsNeeded (nColors_) - 1);
    flag |= globalColorTableSize & 7;
    // Global color table flag
    byte globalColorTableFlag = 1;
    flag |= (globalColorTableFlag & 1) << 7;
    // Sort flag
    byte sortFlag = 0;
    flag |= (sortFlag & 1) << 3;
    // Color resolution
    byte colorResolution = 7;
    flag |= (colorResolution & 7) << 4;    
    
    byte backgroundColorIndex = 0;
    byte pixelAspectRatio     = 0;
    stream.write (flag);
    stream.write (backgroundColorIndex);
    stream.write (pixelAspectRatio);
  }
  

  private void writeImageDescriptor (OutputStream stream,
                                     short width, short height, char separator)
    throws IOException
  {
    stream.write (separator);
    short leftPosition = 0;
    short topPosition  = 0;
    
    writeWord (stream, leftPosition);
    writeWord (stream, topPosition);
    writeWord (stream, width);
    writeWord (stream, height);
    byte flag = 0;
    
    // Local color table size
    byte localColorTableSize = 0;
    flag |= (localColorTableSize & 7);    
    // Reserved
    byte reserved = 0;
    flag |= (reserved & 3) << 3;    
    // Sort flag
    byte sortFlag = 0;
    flag |= (sortFlag & 1) << 5;    
    // Interlace flag
    byte interlaceFlag = 0;
    flag |= (interlaceFlag & 1) << 6;    
    // Local color table flag
    byte localColorTableFlag = 0;
    flag |= (localColorTableFlag & 1) << 7;    
    stream.write (flag);
  }

  
  private void writeLzwCompressed (OutputStream stream, int codeSize,
                                   byte toCompress[])
    throws IOException
  {
    byte c;
    short index;
    int clearcode, endofinfo, numbits, limit, errcode;
    short prefix = (short) 0xFFFF;
    BitFile bitFile = new BitFile (stream);
    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();
  }
  

  /**
   * Used to compress the image by looking for repeating
   * elements.
   */
  private 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;
    private byte   strChr_[];
    private short  strNxt_[];
    private short  strHsh_[];
    private short  nStrings_; 

  
    LzwStringTable()
    {
      strChr_ = new byte[MAXSTR];
      strNxt_ = new short[MAXSTR];
      strHsh_ = new short[HASHSIZE];
    }
  
    int addCharString (short index, byte b)
    {
      int hshidx;
      if ( nStrings_ >= MAXSTR )
        return 0xFFFF;
      hshidx = hash (index, b);
      while ( strHsh_[hshidx] != HASH_FREE )
        hshidx = (hshidx + HASHSTEP) % HASHSIZE;
      strHsh_[hshidx] = nStrings_;
      strChr_[nStrings_] = b;
      strNxt_[nStrings_] = (index != HASH_FREE)?index:NEXT_FIRST;
      return nStrings_++;
    }

    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;
    }

  
    void clearTable (int codesize)
    {
      nStrings_ = 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 ++ )
        this.addCharString((short) 0xFFFF, (byte) q);
    }

  
    int hash (short index, byte lastbyte)
    {
      return ((int)((short) (lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE;
    }
  }

  private class BitFile
  {
    private OutputStream stream_ = null;
    private byte[]       buffer_;
    private int          streamIndex_, bitsLeft_;

  
    BitFile(OutputStream stream)
    {
      stream_      = stream;
      buffer_      = new byte[256];
      streamIndex_ = 0;
      bitsLeft_    = 8;
    }

  
    void flush()
      throws IOException
    {
      int nBytes = streamIndex_ + ((bitsLeft_ == 8) ? 0 : 1);
                                                  
      if (nBytes > 0) {
        stream_.write (nBytes);
        stream_.write (buffer_, 0, nBytes);
        buffer_[0]   = 0;
        streamIndex_ = 0;
        bitsLeft_    = 8;
      }
    }

  
    void writeBits (int bits, int nBits)
      throws IOException
    {
      int nBitsWritten = 0;
      int nBytes       = 255;
      do {
        if ((streamIndex_ == 254 && bitsLeft_ == 0) || streamIndex_ > 254) {
          stream_.write (nBytes);
          stream_.write (buffer_, 0, nBytes);
        
          buffer_[0]   = 0;
          streamIndex_ = 0;
          bitsLeft_    = 8;
        }
      
        if (nBits <= bitsLeft_) {
          buffer_[streamIndex_] |= (bits & ((1 << nBits) - 1)) << (8 - bitsLeft_);
                                                             
          nBitsWritten += nBits;
          bitsLeft_    -= nBits;
          nBits         = 0;
        }
        else {
          buffer_[streamIndex_] |= (bits & ((1 << bitsLeft_) - 1)) <<
                                   (8 - bitsLeft_);
                                                             
          nBitsWritten += bitsLeft_;
          bits >>= bitsLeft_;
          nBits -= bitsLeft_;
          buffer_[++streamIndex_] = 0;
          bitsLeft_ = 8;
        }
      
      } while (nBits != 0);
    }
  }
}



Converting GIF to PostScript

   
import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.SimpleDoc;
import javax.print.StreamPrintService;
import javax.print.StreamPrintServiceFactory;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
public class StreamOneFour {
  public static void main(String args[]) throws Exception {
    String infile = "StreamOneFour.java";
    DocFlavor flavor = DocFlavor.INPUT_STREAM.GIF;
    String mimeType = DocFlavor.INPUT_STREAM.POSTSCRIPT.getMimeType();
    StreamPrintServiceFactory[] factories = StreamPrintServiceFactory
        .lookupStreamPrintServiceFactories(flavor, mimeType);
    String filename = "out.ps";
    FileOutputStream fos = new FileOutputStream(filename);
    StreamPrintService sps = factories[0].getPrintService(fos);
    FileInputStream fis = new FileInputStream(infile);
    DocPrintJob dpj = sps.createPrintJob();
    PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
    Doc doc = new SimpleDoc(fis, flavor, null);
    dpj.print(doc, pras);
    fos.close();
  }
}



Decodes a GIF file into one or more frames

  

import java.net.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
/**
 * Class GifDecoder - Decodes a GIF file into one or more frames.
 * <br><pre>
 * Example:
 *    GifDecoder d = new GifDecoder();
 *    d.read("sample.gif");
 *    int n = d.getFrameCount();
 *    for (int i = 0; i < n; i++) {
 *       BufferedImage frame = d.getFrame(i);  // frame i
 *       int t = d.getDelay(i);  // display duration of frame in milliseconds
 *       // do something with frame
 *    }
 * </pre>
 * No copyright asserted on the source code of this class.  May be used for
 * any purpose, however, refer to the Unisys LZW patent for any additional
 * restrictions.  Please forward any corrections to kweiner@fmsware.ru.
 *
 * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy"s ImageMagick.
 * @version 1.03 November 2003
 *
 */
public class GifDecoder {
  /**
   * File read status: No errors.
   */
  public static final int STATUS_OK = 0;
  /**
   * File read status: Error decoding file (may be partially decoded)
   */
  public static final int STATUS_FORMAT_ERROR = 1;
  /**
   * File read status: Unable to open source.
   */
  public static final int STATUS_OPEN_ERROR = 2;
  protected BufferedInputStream in;
  protected int status;
  protected int width; // full image width
  protected int height; // full image height
  protected boolean gctFlag; // global color table used
  protected int gctSize; // size of global color table
  protected int loopCount = 1; // iterations; 0 = repeat forever
  protected int[] gct; // global color table
  protected int[] lct; // local color table
  protected int[] act; // active color table
  protected int bgIndex; // background color index
  protected int bgColor; // background color
  protected int lastBgColor; // previous bg color
  protected int pixelAspect; // pixel aspect ratio
  protected boolean lctFlag; // local color table flag
  protected boolean interlace; // interlace flag
  protected int lctSize; // local color table size
  protected int ix, iy, iw, ih; // current image rectangle
  protected Rectangle lastRect; // last image rect
  protected BufferedImage image; // current frame
  protected BufferedImage lastImage; // previous frame
  protected byte[] block = new byte[256]; // current data block
  protected int blockSize = 0; // block size
  // last graphic control extension info
  protected int dispose = 0;
  // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
  protected int lastDispose = 0;
  protected boolean transparency = false; // use transparent color
  protected int delay = 0; // delay in milliseconds
  protected int transIndex; // transparent color index
  protected static final int MaxStackSize = 4096;
  // max decoder pixel stack size
  // LZW decoder working arrays
  protected short[] prefix;
  protected byte[] suffix;
  protected byte[] pixelStack;
  protected byte[] pixels;
  protected ArrayList frames; // frames read from current file
  protected int frameCount;
  static class GifFrame {
    public GifFrame(BufferedImage im, int del) {
      image = im;
      delay = del;
    }
    public BufferedImage image;
    public int delay;
  }
  /**
   * Gets display duration for specified frame.
   *
   * @param n int index of frame
   * @return delay in milliseconds
   */
  public int getDelay(int n) {
    //
    delay = -1;
    if ((n >= 0) && (n < frameCount)) {
      delay = ((GifFrame) frames.get(n)).delay;
    }
    return delay;
  }
  /**
   * Gets the number of frames read from file.
   * @return frame count
   */
  public int getFrameCount() {
    return frameCount;
  }
  /**
   * Gets the first (or only) image read.
   *
   * @return BufferedImage containing first frame, or null if none.
   */
  public BufferedImage getImage() {
    return getFrame(0);
  }
  /**
   * Gets the "Netscape" iteration count, if any.
   * A count of 0 means repeat indefinitiely.
   *
   * @return iteration count if one was specified, else 1.
   */
  public int getLoopCount() {
    return loopCount;
  }
  /**
   * Creates new frame image from current data (and previous
   * frames as specified by their disposition codes).
   */
  protected void setPixels() {
    // expose destination image"s pixels as int array
    int[] dest =
      ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    // fill in starting image contents based on last image"s dispose code
    if (lastDispose > 0) {
      if (lastDispose == 3) {
        // use image before last
        int n = frameCount - 2;
        if (n > 0) {
          lastImage = getFrame(n - 1);
        } else {
          lastImage = null;
        }
      }
      if (lastImage != null) {
        int[] prev =
          ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
        System.arraycopy(prev, 0, dest, 0, width * height);
        // copy pixels
        if (lastDispose == 2) {
          // fill last image rect area with background color
          Graphics2D g = image.createGraphics();
          Color c = null;
          if (transparency) {
            c = new Color(0, 0, 0, 0);  // assume background is transparent
          } else {
            c = new Color(lastBgColor); // use given background color
          }
          g.setColor(c);
          g.setComposite(AlphaComposite.Src); // replace area
          g.fill(lastRect);
          g.dispose();
        }
      }
    }
    // copy each source line to the appropriate place in the destination
    int pass = 1;
    int inc = 8;
    int iline = 0;
    for (int i = 0; i < ih; i++) {
      int line = i;
      if (interlace) {
        if (iline >= ih) {
          pass++;
          switch (pass) {
            case 2 :
              iline = 4;
              break;
            case 3 :
              iline = 2;
              inc = 4;
              break;
            case 4 :
              iline = 1;
              inc = 2;
          }
        }
        line = iline;
        iline += inc;
      }
      line += iy;
      if (line < height) {
        int k = line * width;
        int dx = k + ix; // start of line in dest
        int dlim = dx + iw; // end of dest line
        if ((k + width) < dlim) {
          dlim = k + width; // past dest edge
        }
        int sx = i * iw; // start of line in source
        while (dx < dlim) {
          // map color and insert in destination
          int index = ((int) pixels[sx++]) & 0xff;
          int c = act[index];
          if (c != 0) {
            dest[dx] = c;
          }
          dx++;
        }
      }
    }
  }
  /**
   * Gets the image contents of frame n.
   *
   * @return BufferedImage representation of frame, or null if n is invalid.
   */
  public BufferedImage getFrame(int n) {
    BufferedImage im = null;
    if ((n >= 0) && (n < frameCount)) {
      im = ((GifFrame) frames.get(n)).image;
    }
    return im;
  }
  /**
   * Gets image size.
   *
   * @return GIF image dimensions
   */
  public Dimension getFrameSize() {
    return new Dimension(width, height);
  }
  /**
   * Reads GIF image from stream
   *
   * @param BufferedInputStream containing GIF file.
   * @return read status code (0 = no errors)
   */
  public int read(BufferedInputStream is) {
    init();
    if (is != null) {
      in = is;
      readHeader();
      if (!err()) {
        readContents();
        if (frameCount < 0) {
          status = STATUS_FORMAT_ERROR;
        }
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }
    try {
      is.close();
    } catch (IOException e) {
    }
    return status;
  }
  /**
   * Reads GIF image from stream
   *
   * @param InputStream containing GIF file.
   * @return read status code (0 = no errors)
   */
  public int read(InputStream is) {
    init();
    if (is != null) {
      if (!(is instanceof BufferedInputStream))
        is = new BufferedInputStream(is);
      in = (BufferedInputStream) is;
      readHeader();
      if (!err()) {
        readContents();
        if (frameCount < 0) {
          status = STATUS_FORMAT_ERROR;
        }
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }
    try {
      is.close();
    } catch (IOException e) {
    }
    return status;
  }
  /**
   * Reads GIF file from specified file/URL source  
   * (URL assumed if name contains ":/" or "file:")
   *
   * @param name String containing source
   * @return read status code (0 = no errors)
   */
  public int read(String name) {
    status = STATUS_OK;
    try {
      name = name.trim().toLowerCase();
      if ((name.indexOf("file:") >= 0) ||
        (name.indexOf(":/") > 0)) {
        URL url = new URL(name);
        in = new BufferedInputStream(url.openStream());
      } else {
        in = new BufferedInputStream(new FileInputStream(name));
      }
      status = read(in);
    } catch (IOException e) {
      status = STATUS_OPEN_ERROR;
    }
    return status;
  }
  /**
   * Decodes LZW image data into pixel array.
   * Adapted from John Cristy"s ImageMagick.
   */
  protected void decodeImageData() {
    int NullCode = -1;
    int npix = iw * ih;
    int available, 
      clear,
      code_mask,
      code_size,
      end_of_information,
      in_code,
      old_code,
      bits,
      code,
      count,
      i,
      datum,
      data_size,
      first,
      top,
      bi,
      pi;
    if ((pixels == null) || (pixels.length < npix)) {
      pixels = new byte[npix]; // allocate new pixel array
    }
    if (prefix == null) prefix = new short[MaxStackSize];
    if (suffix == null) suffix = new byte[MaxStackSize];
    if (pixelStack == null) pixelStack = new byte[MaxStackSize + 1];
    //  Initialize GIF data stream decoder.
    data_size = read();
    clear = 1 << data_size;
    end_of_information = clear + 1;
    available = clear + 2;
    old_code = NullCode;
    code_size = data_size + 1;
    code_mask = (1 << code_size) - 1;
    for (code = 0; code < clear; code++) {
      prefix[code] = 0;
      suffix[code] = (byte) code;
    }
    //  Decode GIF pixel stream.
    datum = bits = count = first = top = pi = bi = 0;
    for (i = 0; i < npix;) {
      if (top == 0) {
        if (bits < code_size) {
          //  Load bytes until there are enough bits for a code.
          if (count == 0) {
            // Read a new data block.
            count = readBlock();
            if (count <= 0)
              break;
            bi = 0;
          }
          datum += (((int) block[bi]) & 0xff) << bits;
          bits += 8;
          bi++;
          count--;
          continue;
        }
        //  Get the next code.
        code = datum & code_mask;
        datum >>= code_size;
        bits -= code_size;
        //  Interpret the code
        if ((code > available) || (code == end_of_information))
          break;
        if (code == clear) {
          //  Reset decoder.
          code_size = data_size + 1;
          code_mask = (1 << code_size) - 1;
          available = clear + 2;
          old_code = NullCode;
          continue;
        }
        if (old_code == NullCode) {
          pixelStack[top++] = suffix[code];
          old_code = code;
          first = code;
          continue;
        }
        in_code = code;
        if (code == available) {
          pixelStack[top++] = (byte) first;
          code = old_code;
        }
        while (code > clear) {
          pixelStack[top++] = suffix[code];
          code = prefix[code];
        }
        first = ((int) suffix[code]) & 0xff;
        //  Add a new string to the string table,
        if (available >= MaxStackSize)
          break;
        pixelStack[top++] = (byte) first;
        prefix[available] = (short) old_code;
        suffix[available] = (byte) first;
        available++;
        if (((available & code_mask) == 0)
          && (available < MaxStackSize)) {
          code_size++;
          code_mask += available;
        }
        old_code = in_code;
      }
      //  Pop a pixel off the pixel stack.
      top--;
      pixels[pi++] = pixelStack[top];
      i++;
    }
    for (i = pi; i < npix; i++) {
      pixels[i] = 0; // clear missing pixels
    }
  }
  /**
   * Returns true if an error was encountered during reading/decoding
   */
  protected boolean err() {
    return status != STATUS_OK;
  }
  /**
   * Initializes or re-initializes reader
   */
  protected void init() {
    status = STATUS_OK;
    frameCount = 0;
    frames = new ArrayList();
    gct = null;
    lct = null;
  }
  /**
   * Reads a single byte from the input stream.
   */
  protected int read() {
    int curByte = 0;
    try {
      curByte = in.read();
    } catch (IOException e) {
      status = STATUS_FORMAT_ERROR;
    }
    return curByte;
  }
  /**
   * Reads next variable length block from input.
   *
   * @return number of bytes stored in "buffer"
   */
  protected int readBlock() {
    blockSize = read();
    int n = 0;
    if (blockSize > 0) {
      try {
        int count = 0;
        while (n < blockSize) {
          count = in.read(block, n, blockSize - n);
          if (count == -1) 
            break;
          n += count;
        }
      } catch (IOException e) {
      }
      if (n < blockSize) {
        status = STATUS_FORMAT_ERROR;
      }
    }
    return n;
  }
  /**
   * Reads color table as 256 RGB integer values
   *
   * @param ncolors int number of colors to read
   * @return int array containing 256 colors (packed ARGB with full alpha)
   */
  protected int[] readColorTable(int ncolors) {
    int nbytes = 3 * ncolors;
    int[] tab = null;
    byte[] c = new byte[nbytes];
    int n = 0;
    try {
      n = in.read(c);
    } catch (IOException e) {
    }
    if (n < nbytes) {
      status = STATUS_FORMAT_ERROR;
    } else {
      tab = new int[256]; // max size to avoid bounds checks
      int i = 0;
      int j = 0;
      while (i < ncolors) {
        int r = ((int) c[j++]) & 0xff;
        int g = ((int) c[j++]) & 0xff;
        int b = ((int) c[j++]) & 0xff;
        tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
      }
    }
    return tab;
  }
  /**
   * Main file parser.  Reads GIF content blocks.
   */
  protected void readContents() {
    // read GIF file content blocks
    boolean done = false;
    while (!(done || err())) {
      int code = read();
      switch (code) {
        case 0x2C : // image separator
          readImage();
          break;
        case 0x21 : // extension
          code = read();
          switch (code) {
            case 0xf9 : // graphics control extension
              readGraphicControlExt();
              break;
            case 0xff : // application extension
              readBlock();
              String app = "";
              for (int i = 0; i < 11; i++) {
                app += (char) block[i];
              }
              if (app.equals("NETSCAPE2.0")) {
                readNetscapeExt();
              }
              else
                skip(); // don"t care
              break;
            default : // uninteresting extension
              skip();
          }
          break;
        case 0x3b : // terminator
          done = true;
          break;
        case 0x00 : // bad byte, but keep going and see what happens
          break;
        default :
          status = STATUS_FORMAT_ERROR;
      }
    }
  }
  /**
   * Reads Graphics Control Extension values
   */
  protected void readGraphicControlExt() {
    read(); // block size
    int packed = read(); // packed fields
    dispose = (packed & 0x1c) >> 2; // disposal method
    if (dispose == 0) {
      dispose = 1; // elect to keep old image if discretionary
    }
    transparency = (packed & 1) != 0;
    delay = readShort() * 10; // delay in milliseconds
    transIndex = read(); // transparent color index
    read(); // block terminator
  }
  /**
   * Reads GIF file header information.
   */
  protected void readHeader() {
    String id = "";
    for (int i = 0; i < 6; i++) {
      id += (char) read();
    }
    if (!id.startsWith("GIF")) {
      status = STATUS_FORMAT_ERROR;
      return;
    }
    readLSD();
    if (gctFlag && !err()) {
      gct = readColorTable(gctSize);
      bgColor = gct[bgIndex];
    }
  }
  /**
   * Reads next frame image
   */
  protected void readImage() {
    ix = readShort(); // (sub)image position & size
    iy = readShort();
    iw = readShort();
    ih = readShort();
    int packed = read();
    lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
    interlace = (packed & 0x40) != 0; // 2 - interlace flag
    // 3 - sort flag
    // 4-5 - reserved
    lctSize = 2 << (packed & 7); // 6-8 - local color table size
    if (lctFlag) {
      lct = readColorTable(lctSize); // read table
      act = lct; // make local table active
    } else {
      act = gct; // make global table active
      if (bgIndex == transIndex)
        bgColor = 0;
    }
    int save = 0;
    if (transparency) {
      save = act[transIndex];
      act[transIndex] = 0; // set transparent color if specified
    }
    if (act == null) {
      status = STATUS_FORMAT_ERROR; // no color table defined
    }
    if (err()) return;
    decodeImageData(); // decode pixel data
    skip();
    if (err()) return;
    frameCount++;
    // create new image to receive frame data
    image =
      new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
    setPixels(); // transfer pixel data to image
    frames.add(new GifFrame(image, delay)); // add image to frame list
    if (transparency) {
      act[transIndex] = save;
    }
    resetFrame();
  }
  /**
   * Reads Logical Screen Descriptor
   */
  protected void readLSD() {
    // logical screen size
    width = readShort();
    height = readShort();
    // packed fields
    int packed = read();
    gctFlag = (packed & 0x80) != 0; // 1   : global color table flag
    // 2-4 : color resolution
    // 5   : gct sort flag
    gctSize = 2 << (packed & 7); // 6-8 : gct size
    bgIndex = read(); // background color index
    pixelAspect = read(); // pixel aspect ratio
  }
  /**
   * Reads Netscape extenstion to obtain iteration count
   */
  protected void readNetscapeExt() {
    do {
      readBlock();
      if (block[0] == 1) {
        // loop count sub-block
        int b1 = ((int) block[1]) & 0xff;
        int b2 = ((int) block[2]) & 0xff;
        loopCount = (b2 << 8) | b1;
      }
    } while ((blockSize > 0) && !err());
  }
  /**
   * Reads next 16-bit value, LSB first
   */
  protected int readShort() {
    // read 16-bit value, LSB first
    return read() | (read() << 8);
  }
  /**
   * Resets frame state for reading next image.
   */
  protected void resetFrame() {
    lastDispose = dispose;
    lastRect = new Rectangle(ix, iy, iw, ih);
    lastImage = image;
    lastBgColor = bgColor;
    dispose = 0;
    transparency = false;
    delay = 0;
    lct = null;
  }
  /**
   * Skips variable length blocks up to and including
   * next zero length block.
   */
  protected void skip() {
    do {
      readBlock();
    } while ((blockSize > 0) && !err());
  }
}



Get Gif Properties

 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// Revised from apache cocoon
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
/**
 * @version $Id: ImageUtils.java 587751 2007-10-24 02:41:36Z vgritsenko $
 */
final public class ImageUtils {

    final public static ImageProperties getJpegProperties(File file) throws FileNotFoundException, IOException{
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(file));
            // check for "magic" header
            byte[] buf = new byte[2];
            int count = in.read(buf, 0, 2);
            if (count < 2) {
                throw new RuntimeException("Not a valid Jpeg file!");
            }
            if ((buf[0]) != (byte) 0xFF || (buf[1]) != (byte) 0xD8) {
                throw new RuntimeException("Not a valid Jpeg file!");
            }
            int width = 0;
            int height = 0;
            char[] comment = null;
            boolean hasDims = false;
            boolean hasComment = false;
            int ch = 0;
            while (ch != 0xDA && !(hasDims && hasComment)) {
                /* Find next marker (JPEG markers begin with 0xFF) */
                while (ch != 0xFF) {
                    ch = in.read();
                }
                /* JPEG markers can be padded with unlimited 0xFF"s */
                while (ch == 0xFF) {
                    ch = in.read();
                }
                /* Now, ch contains the value of the marker. */
                int length = 256 * in.read();
                length += in.read();
                if (length < 2) {
                    throw new RuntimeException("Not a valid Jpeg file!");
                }
                /* Now, length contains the length of the marker. */
                if (ch >= 0xC0 && ch <= 0xC3) {
                    in.read();
                    height = 256 * in.read();
                    height += in.read();
                    width = 256 * in.read();
                    width += in.read();
                    for (int foo = 0; foo < length - 2 - 5; foo++) {
                        in.read();
                    }
                    hasDims = true;
                }
                else if (ch == 0xFE) {
                    // that"s the comment marker
                    comment = new char[length-2];
                    for (int foo = 0; foo < length - 2; foo++)
                        comment[foo] = (char) in.read();
                    hasComment = true;
                }
                else {
                    // just skip marker
                    for (int foo = 0; foo < length - 2; foo++) {
                        in.read();
                    }
                }
            }
            return (new ImageProperties(width, height, comment, "jpeg"));
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                }
                catch (IOException e) {
                }
            }
        }
    }
    final public static ImageProperties getGifProperties(File file) throws FileNotFoundException, IOException{
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(file));
            byte[] buf = new byte[10];
            int count = in.read(buf, 0, 10);
            if (count < 10) {
                throw new RuntimeException("Not a valid Gif file!");
            }
            if ((buf[0]) != (byte) "G" || (buf[1]) != (byte) "I" || (buf[2]) != (byte) "F") {
                throw new RuntimeException("Not a valid Gif file!");
            }
            int w1 = (buf[6] & 0xff) | (buf[6] & 0x80);
            int w2 = (buf[7] & 0xff) | (buf[7] & 0x80);
            int h1 = (buf[8] & 0xff) | (buf[8] & 0x80);
            int h2 = (buf[9] & 0xff) | (buf[9] & 0x80);
            int width = w1 + (w2 << 8);
            int height = h1 + (h2 << 8);
            return (new ImageProperties(width, height, null,"gif"));
        }
        finally {
            if (in != null) {
                try {
                    in.close();
                }
                catch (IOException e) {
                }
            }
        }
    }
}
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @version $Id: ImageProperties.java 587751 2007-10-24 02:41:36Z vgritsenko $
 */
final  class ImageProperties {
    final public int width;
    final public int height;
    final public char[] comment;
    final public String type;
    public ImageProperties(int width, int height, char[] comment, String type) {
        this.width = width;
        this.height = height;
        this.rument = comment;
        this.type = type;
    }
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(type).append(" ").append(width).append("x").append(height);
        if (comment != null) {
            sb.append(" (").append(comment).append(")");
        }
        return (sb.toString());
    }
}



Gif Encoder

  
/* 
 *
 * Part of the InfoGlue Content Management Platform (www.infoglue.org)
 *
 * 
 *
 *  Copyright (C)
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 2, as published by the
 * Free Software Foundation. See the file LICENSE.html for more information.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
 * Place, Suite 330 / Boston, MA 02111-1307 / USA.
 *
 * 
 */

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
public class GifEncoder
{
    String b;
    int g;
    int a;
    int h;
    int f;
    int c[];
    int i[];
    int e[];
    TreeSet d;
    private void a(int ai[], int j, DataOutputStream dataoutputstream) throws Exception
    {
        try
        {
            boolean flag2 = false;
            int l;
            int j3 = (1 << (l = j + 1)) - 1;
            int i2 = (1 << j) + 2;
            byte abyte0[] = new byte[255];
            int ai1[] = new int[4096];
            int ai2[] = new int[4096];
            int ai3[] = new int[4096];
            int ai4[] = new int[i2];
            int k;
            for(k = 0; k < i2; k++)
            {
                ai4[k] = 0xffffffff | k;
                ai3[k] = -1;
            }
            for(; k < 4096; k++)
            {
                ai3[k] = -1;
            }
            System.arraycopy(ai3, 0, ai1, 0, 4096);
            System.arraycopy(ai3, 0, ai2, 0, 4096);
            System.arraycopy(ai4, 0, ai1, 0, i2);
            int j1 = ai[0];
            k = 1;
            boolean flag1 = false;
            int j2 = 0;
            int k2 = (1 << l) - 1;
            boolean flag = true;
            int i3 = 0;
            int i1 = 0;
            j2 |= 1 << j + i3;
            for(i3 += l; i3 >= 8;)
            {
                try
                {
                    abyte0[i1++] = (byte)j2;
                }
                catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception)
                {
                    dataoutputstream.writeByte(255);
                    dataoutputstream.write(abyte0);
                    abyte0[i1 = 0] = (byte)j2;
                    i1++;
                }
                i3 -= 8;
                j2 >>= 8;
            }
            try
            {
                do
                {
                    int k1;
                    int l1 = j1 << 16 | (k1 = ai[k++]);
                    int k3;
                    for(k3 = j1; ai1[k3] != l1 && ai2[k3] >= 0; k3 = ai2[k3]) { }
                    if(ai1[k3] != l1)
                    {
                        j2 |= j1 << i3;
                        for(i3 += l; i3 >= 8;)
                        {
                            try
                            {
                                abyte0[i1++] = (byte)j2;
                            }
                            catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception1)
                            {
                                dataoutputstream.writeByte(255);
                                dataoutputstream.write(abyte0);
                                abyte0[i1 = 0] = (byte)j2;
                                i1++;
                            }
                            i3 -= 8;
                            j2 >>= 8;
                        }
                        if(i2 > j3)
                        {
                            l++;
                            j3 = (j3 << 1) + 1;
                        }
                        try
                        {
                            ai2[k3] = i2;
                            ai1[i2++] = j1 << 16 | k1;
                            j1 = k1;
                        }
                        catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception2)
                        {
                            j1 = k1;
                            l--;
                            j2 |= 1 << j + i3;
                            for(i3 += l; i3 >= 8;)
                            {
                                try
                                {
                                    abyte0[i1++] = (byte)j2;
                                }
                                catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception5)
                                {
                                    dataoutputstream.writeByte(255);
                                    dataoutputstream.write(abyte0);
                                    abyte0[i1 = 0] = (byte)j2;
                                    i1++;
                                }
                                i3 -= 8;
                                j2 >>= 8;
                            }
                            j3 = (1 << (l = j + 1)) - 1;
                            i2 = (1 << j) + 2;
                            int l2 = (1 << l) - 1;
                            System.arraycopy(ai3, 0, ai1, 0, 4096);
                            System.arraycopy(ai3, 0, ai2, 0, 4096);
                            System.arraycopy(ai4, 0, ai1, 0, i2);
                        }
                    }
                    else
                    {
                        j1 = k3;
                    }
                }
                while(true);
            }
            catch(Exception exception)
            {
                j2 |= j1 << i3;
            }
            for(i3 += l; i3 >= 8;)
            {
                try
                {
                    abyte0[i1++] = (byte)j2;
                }
                catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception3)
                {
                    dataoutputstream.writeByte(255);
                    dataoutputstream.write(abyte0);
                    abyte0[i1 = 0] = (byte)j2;
                    i1++;
                }
                i3 -= 8;
                j2 >>= 8;
            }
            j2 |= (1 << j) + 1 << i3;
            for(i3 += l; i3 > 0;)
            {
                try
                {
                    abyte0[i1++] = (byte)j2;
                }
                catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception4)
                {
                    dataoutputstream.writeByte(255);
                    dataoutputstream.write(abyte0);
                    abyte0[i1 = 0] = (byte)j2;
                    i1++;
                }
                i3 -= 8;
                j2 >>= 8;
            }
            dataoutputstream.writeByte(i1);
            dataoutputstream.write(abyte0, 0, i1);
            dataoutputstream.writeByte(0);
            return;
        }
        catch(Exception e) { }
    }
    public void addTransparentColor(Color color)
    {
        try
        {
            if(f < 256)
            {
                c[f++] = color.getRGB();
            }
            return;
        }
        catch(Exception e) { }
    }
    public void setTransparentColors(Vector vector)
    {
        try
        {
            Iterator iterator = vector.iterator();
            while(iterator.hasNext()) 
            {
                Color color = (Color)iterator.next();
                addTransparentColor(color);
            }
            return;
        }
        catch(Exception e) { }
    }
    public void encode(BufferedImage bufferedimage, DataOutputStream dataoutputstream, Hashtable hashtable) throws Exception
    {
        try
        {
            a = bufferedimage.getWidth();
            g = bufferedimage.getHeight();
            e = bufferedimage.getRGB(0, 0, a, g, null, 0, a);
            int i4 = 0;
            b = hashtable.get("encoding").toString();
            if(b.equals("websafe"))
            {
                int ai[] = new int[256];
                i = new int[256];
                h = 8;
                int k1 = 0;
                int j;
                int j1 = j = 0;
                for(; j <= 255; j += 51)
                {
                    for(int l = 0; l <= 255; l += 51)
                    {
                        for(int i1 = 0; i1 <= 255;)
                        {
                            i[j1] = (j << 16) + (l << 8) + i1;
                            ai[k1++] = j1;
                            i1 += 51;
                            j1++;
                        }
                    }
                }
                if(f > 0)
                {
                    int j4 = c[0];
                    int l1 = ((c[0] >> 16 & 0xff) + 25) / 51;
                    int k2 = ((c[0] >> 8 & 0xff) + 25) / 51;
                    int j3 = ((c[0] & 0xff) + 25) / 51;
                    i4 = l1 * 36 + k2 * 6 + j3;
                    for(j = 1; j < f; j++)
                    {
                        int i2 = ((c[j] >> 16 & 0xff) + 25) / 51;
                        int l2 = ((c[j] >> 8 & 0xff) + 25) / 51;
                        int k3 = ((c[j] & 0xff) + 25) / 51;
                        ai[i2 * 36 + l2 * 6 + k3] = i4;
                    }
                }
                j = 0;
                try
                {
                    do
                    {
                        int i5 = e[j];
                        int j2 = ((i5 >> 16 & 0xff) + 25) / 51;
                        int i3 = ((i5 >> 8 & 0xff) + 25) / 51;
                        int l3 = ((i5 & 0xff) + 25) / 51;
                        e[j++] = ai[j2 * 36 + i3 * 6 + l3];
                    }
                    while(true);
                }
                catch(Exception exception1) { }
            }
            /*else
            if(b.equals("optimized"))
            {
                try
                {
                    int k4 = Integer.parseInt(hashtable.get("colors").toString());
                    for(h = 1; k4 - 1 >> h > 0; h++) { }
                    i = new int[1 << h];
                    CSelectiveQuant cselectivequant = new CSelectiveQuant();
                    for(int j5 = 0; j5 < e.length; j5++)
                    {
                        cselectivequant.addPixel(e[j5]);
                    }
                    boolean flag = f > 0;
                    int k5 = flag ? 1 : 0;
                    int ai1[] = cselectivequant.createPalette(k4 - k5);
                    for(int l5 = 0; l5 < i.length; l5++)
                    {
                        try
                        {
                            i[l5] = ai1[l5 - k5];
                        }
                        catch(ArrayIndexOutOfBoundsException arrayindexoutofboundsexception)
                        {
                            i[l5] = 0;
                        }
                    }
                    if(flag)
                    {
                        i4 = 0;
                        for(int i6 = 0; i6 < f; i6++)
                        {
                            cselectivequant.setIndex(c[i6], -1);
                        }
                    }
                    for(int j6 = 0; j6 < e.length; j6++)
                    {
                        e[j6] = cselectivequant.getIndex(e[j6]) + k5;
                    }
                }
                catch(NumberFormatException numberformatexception)
                {
                    CmsLogger.logInfo("Parameter: "colors" is malformated...");
                    return;
                }
            }
            */
            dataoutputstream.write("GIF89a".getBytes());
            dataoutputstream.writeByte(a);
            dataoutputstream.writeByte(a >> 8);
            dataoutputstream.writeByte(g);
            dataoutputstream.writeByte(g >> 8);
            dataoutputstream.writeByte(0xf0 | h - 1);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(0);
            int k = 0;
            try
            {
                do
                {
                    int l4 = i[k++];
                    dataoutputstream.writeByte(l4 >> 16 & 0xff);
                    dataoutputstream.writeByte(l4 >> 8 & 0xff);
                    dataoutputstream.writeByte(l4 & 0xff);
                }
                while(true);
            }
            catch(Exception exception) { }
            if(f > 0)
            {
                dataoutputstream.writeByte(33);
                dataoutputstream.writeByte(249);
                dataoutputstream.writeByte(4);
                dataoutputstream.writeByte(1);
                dataoutputstream.writeByte(0);
                dataoutputstream.writeByte(0);
                dataoutputstream.writeByte(i4);
                dataoutputstream.writeByte(0);
            }
            dataoutputstream.writeByte(44);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(a);
            dataoutputstream.writeByte(a >> 8);
            dataoutputstream.writeByte(g);
            dataoutputstream.writeByte(g >> 8);
            dataoutputstream.writeByte(0);
            dataoutputstream.writeByte(h);
            a(e, h, dataoutputstream);
            dataoutputstream.writeByte(59);
            dataoutputstream.flush();
            return;
        }
        catch(Exception e) { }
    }
    public GifEncoder()
    {
        f = 0;
        c = new int[256];
    }
}



Gif Encoder implements ImageConsumer

  
//** Copyright Statement ***************************************************
//The Salmon Open Framework for Internet Applications (SOFIA)
// Copyright (C) 1999 - 2002, Salmon LLC
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation;
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// 
// For more information please visit http://www.salmonllc.ru
//** End Copyright Statement ***************************************************

// ImageEncoder - abstract class for writing out an image
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.ru>.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS"" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.ru/java/
import java.util.*;
import java.io.*;
import java.awt.Image;
import java.awt.image.*;
import java.awt.*;
/**
 * Class for writing out an image as a gif.
 */

public class GifEncoder implements ImageConsumer {
    protected OutputStream out;
    private ImageProducer producer;
    private int width = -1;
    private int height = -1;
    private int hintflags = 0;
    private boolean started = false;
    private boolean encoding;
    private IOException iox;
    private static final ColorModel rgbModel = ColorModel.getRGBdefault();
    private Hashtable props = null;
    private boolean accumulate = false;
    private int[] accumulator;
    private boolean interlace = false;
    int[][] rgbPixels;
    IntHashtable colorHash;
    // Adapted from ppmtogif, which is based on GIFENCOD by David
    // Rowley <mgardi@watdscu.waterloo.edu>.  Lempel-Zim compression
    // based on "compress".
    int Width, Height;
    boolean Interlace;
    int curx, cury;
    int CountDown;
    int Pass = 0;
    static final int EOF = -1;
    // GIFCOMPR.C       - GIF Image compression routines
    //
    // Lempel-Ziv compression based on "compress".  GIF modifications by
    // David Rowley (mgardi@watdcsu.waterloo.edu)
    // General DEFINEs
    static final int BITS = 12;
    static final int HSIZE = 5003;    // 80% occupancy
    // GIF Image compression - modified "compress"
    //
    // Based on: compress.c - File compression ala IEEE Computer, June 1984.
    //
    // By Authors:  Spencer W. Thomas      (decvax!harpo!utah-cs!utah-gr!thomas)
    //              Jim McKie              (decvax!mcvax!jim)
    //              Steve Davies           (decvax!vax135!petsd!peora!srd)
    //              Ken Turkowski          (decvax!decwrl!turtlevax!ken)
    //              James A. Woods         (decvax!ihnp4!ames!jaw)
    //              Joe Orost              (decvax!vax135!petsd!joe)
    int n_bits;       // number of bits/code
    int maxbits = BITS;     // user settable max # bits/code
    int maxcode;      // maximum code, given n_bits
    int maxmaxcode = 1 << BITS; // should NEVER generate this code
    int[] htab = new int[HSIZE];
    int[] codetab = new int[HSIZE];
    int hsize = HSIZE;    // for dynamic table sizing
    int free_ent = 0;     // first unused entry
    // block compression parameters -- after all codes are used up,
    // and compression rate changes, start over.
    boolean clear_flg = false;
    // Algorithm:  use open addressing double hashing (no chaining) on the
    // prefix code / next character combination.  We do a variant of Knuth"s
    // algorithm D (vol. 3, sec. 6.4) along with G. Knott"s relatively-prime
    // secondary probe.  Here, the modular division first probe is gives way
    // to a faster exclusive-or manipulation.  Also do block compression with
    // an adaptive reset, whereby the code table is cleared when the compression
    // ratio decreases, but after the table fills.  The variable-length output
    // codes are re-sized at this point, and a special CLEAR code is generated
    // for the decompressor.  Late addition:  construct the table according to
    // file size for noticeable speed improvement on small files.  Please direct
    // questions about this implementation to ames!jaw.
    int g_init_bits;
    int ClearCode;
    int EOFCode;
    // output
    //
    // Output the given code.
    // Inputs:
    //      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
    //              that n_bits =< wordsize - 1.
    // Outputs:
    //      Outputs code to the file.
    // Assumptions:
    //      Chars are 8 bits long.
    // Algorithm:
    //      Maintain a BITS character long buffer (so that 8 codes will
    // fit in it exactly).  Use the VAX insv instruction to insert each
    // code in turn.  When the buffer fills up empty it and start over.
    int cur_accum = 0;
    int cur_bits = 0;
    int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
                   0x001F, 0x003F, 0x007F, 0x00FF,
                   0x01FF, 0x03FF, 0x07FF, 0x0FFF,
                   0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
    // GIF Specific routines
    // Number of characters so far in this "packet"
    int a_count;
    // Define the storage for the packet accumulator
    byte[] accum = new byte[256];

    class gifHashitem {
        public int rgb;
        public int count;
        public int index;
        public boolean isTransparent;
        public gifHashitem(int rgb, int count, int index, boolean isTransparent) {
            this.rgb = rgb;
            this.count = count;
            this.index = index;
            this.isTransparent = isTransparent;
        }
    }
    class IntHashtableEntry {
        int hash;
        int key;
        Object value;
        IntHashtableEntry next;
        protected Object clone() {
            IntHashtableEntry entry = new IntHashtableEntry();
            entry.hash = hash;
            entry.key = key;
            entry.value = value;
            entry.next = (next != null) ? (IntHashtableEntry) next.clone() : null;
            return entry;
        }
    }
    class IntHashtable extends Dictionary implements Cloneable {
        private IntHashtableEntry table[];
        private int count;
        private int threshold;
        private float loadFactor;
        public IntHashtable() {
            this(101, 0.75f);
        }
        public IntHashtable(int initialCapacity) {
            this(initialCapacity, 0.75f);
        }
        public IntHashtable(int initialCapacity, float loadFactor) {
            if (initialCapacity <= 0 || loadFactor <= 0.0)
                throw new IllegalArgumentException();
            this.loadFactor = loadFactor;
            table = new IntHashtableEntry[initialCapacity];
            threshold = (int) (initialCapacity * loadFactor);
        }
        public synchronized void clear() {
            IntHashtableEntry tab[] = table;
            for (int index = tab.length; --index >= 0;)
                tab[index] = null;
            count = 0;
        }
        public synchronized Object clone() {
            try {
                IntHashtable t = (IntHashtable) super.clone();
                t.table = new IntHashtableEntry[table.length];
                for (int i = table.length; i-- > 0;)
                    t.table[i] = (table[i] != null) ?  (IntHashtableEntry) table[i].clone() : null;
                return t;
            } catch (CloneNotSupportedException e) {
                throw new InternalError();
            }
        }
        public synchronized boolean contains(Object value) {
            if (value == null)
                throw new NullPointerException();
            IntHashtableEntry tab[] = table;
            for (int i = tab.length; i-- > 0;) {
                for (IntHashtableEntry e = tab[i]; e != null; e = e.next) {
                    if (e.value.equals(value))
                        return true;
                }
            }
            return false;
        }
        public synchronized boolean containsKey(int key) {
            IntHashtableEntry tab[] = table;
            int hash = key;
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
                if (e.hash == hash && e.key == key)
                    return true;
            }
            return false;
        }
        public synchronized Enumeration elements() {
            return new IntHashtableEnumerator(table, false);
        }
        public synchronized Object get(int key) {
            IntHashtableEntry tab[] = table;
            int hash = key;
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
                if (e.hash == hash && e.key == key)
                    return e.value;
            }
            return null;
        }
        public Object get(Object okey) {
            if (!(okey instanceof Integer))
                throw new InternalError("key is not an Integer");
            Integer ikey = (Integer) okey;
            int key = ikey.intValue();
            return get(key);
        }
        public boolean isEmpty() {
            return count == 0;
        }
        public synchronized Enumeration keys() {
            return new IntHashtableEnumerator(table, true);
        }
        public synchronized Object put(int key, Object value) {
            if (value == null)
                throw new NullPointerException();
            IntHashtableEntry tab[] = table;
            int hash = key;
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (IntHashtableEntry e = tab[index]; e != null; e = e.next) {
                if (e.hash == hash && e.key == key) {
                    Object old = e.value;
                    e.value = value;
                    return old;
                }
            }
            if (count >= threshold) {
                rehash();
                return put(key, value);
            }
            IntHashtableEntry e = new IntHashtableEntry();
            e.hash = hash;
            e.key = key;
            e.value = value;
            e.next = tab[index];
            tab[index] = e;
            ++count;
            return null;
        }
        public Object put(Object okey, Object value) {
            if (!(okey instanceof Integer))
                throw new InternalError("key is not an Integer");
            Integer ikey = (Integer) okey;
            int key = ikey.intValue();
            return put(key, value);
        }
        protected void rehash() {
            int oldCapacity = table.length;
            IntHashtableEntry oldTable[] = table;
            int newCapacity = oldCapacity * 2 + 1;
            IntHashtableEntry newTable[] = new IntHashtableEntry[newCapacity];
            threshold = (int) (newCapacity * loadFactor);
            table = newTable;
            for (int i = oldCapacity; i-- > 0;) {
                for (IntHashtableEntry old = oldTable[i]; old != null;) {
                    IntHashtableEntry e = old;
                    old = old.next;
                    int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                    e.next = newTable[index];
                    newTable[index] = e;
                }
            }
        }
        public synchronized Object remove(int key) {
            IntHashtableEntry tab[] = table;
            int hash = key;
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (IntHashtableEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) {
                if (e.hash == hash && e.key == key) {
                    if (prev != null)
                        prev.next = e.next;
                    else
                        tab[index] = e.next;
                    --count;
                    return e.value;
                }
            }
            return null;
        }
        public Object remove(Object okey) {
            if (!(okey instanceof Integer))
                throw new InternalError("key is not an Integer");
            Integer ikey = (Integer) okey;
            int key = ikey.intValue();
            return remove(key);
        }
        public int size() {
            return count;
        }
        public synchronized String toString() {
            int max = size() - 1;
            StringBuffer buf = new StringBuffer();
            Enumeration k = keys();
            Enumeration e = elements();
            buf.append("{");
            for (int i = 0; i <= max; ++i) {
                String s1 = k.nextElement().toString();
                String s2 = e.nextElement().toString();
                buf.append(s1 + "=" + s2);
                if (i < max)
                    buf.append(", ");
            }
            buf.append("}");
            return buf.toString();
        }
    }
    class IntHashtableEnumerator implements Enumeration {
        boolean keys;
        int index;
        IntHashtableEntry table[];
        IntHashtableEntry entry;
        IntHashtableEnumerator(IntHashtableEntry table[], boolean keys) {
            this.table = table;
            this.keys = keys;
            this.index = table.length;
        }
        public boolean hasMoreElements() {
            if (entry != null)
                return true;
            while (index-- > 0)
                if ((entry = table[index]) != null)
                    return true;
            return false;
        }
        public Object nextElement() {
            if (entry == null)
                while ((index-- > 0) && ((entry = table[index]) == null)) ;
            if (entry != null) {
                IntHashtableEntry e = entry;
                entry = e.next;
                return keys ? new Integer(e.key) : e.value;
            }
            throw new NoSuchElementException("IntHashtableEnumerator");
        }
    }
    class TransparentFilter extends RGBImageFilter {
        int transparentRGB;
        public TransparentFilter(Color color) {
            transparentRGB = color.getRGB() & 0xFFFFFF;
            canFilterIndexColorModel = true;
        }
        public int filterRGB(int x, int y, int rgb) {
            if ((rgb & 0xFFFFFF) == transparentRGB)
                return 0;
            return rgb;
        }
    }
    /// Constructor.
    // @param producer The ImageProducer to encode.
    // @param out The stream to write the bytes to.
    private GifEncoder(ImageProducer producer, OutputStream out) throws IOException {
        this.producer = producer;
        this.out = out;
    }
    /**
     * Constructor
     *  @param img The image to encode.
     *  @param out The stream to write the bytes to.
     */
    public GifEncoder(Image img, OutputStream out) throws IOException {
        this(img.getSource(), out);
    }
    /**
     * Constructor from Image with interlace setting.
     * @param img The image to encode.
     * @param out The stream to write the GIF to.
     * @param interlace Whether to interlace.
     */
    public GifEncoder(Image img, OutputStream out, boolean interlace) throws IOException {
        this(img, out);
        this.interlace = interlace;
    }
    /** Constructor from Image with interlace setting.
     * @param img The image to encode.
     * @param out The stream to write the GIF to.
     * @param interlace Whether to interlace.
     * @param transparentColor The color to use for transparency
     */
    public GifEncoder(Image img, OutputStream out, boolean interlace, Color transparentColor) throws IOException {
        RGBImageFilter f = new TransparentFilter(transparentColor);
        this.producer = new FilteredImageSource(img.getSource(), f);
        this.out = out;
        this.interlace = interlace;
    }
    // Bump the "curx" and "cury" to point to the next pixel
    void BumpPixel() {
        // Bump the current X position
        ++curx;
        // If we are at the end of a scan line, set curx back to the beginning
        // If we are interlaced, bump the cury to the appropriate spot,
        // otherwise, just increment it.
        if (curx == Width) {
            curx = 0;
            if (!Interlace)
                ++cury;
            else {
                switch (Pass) {
                    case 0:
                        cury += 8;
                        if (cury >= Height) {
                            ++Pass;
                            cury = 4;
                        }
                        break;
                    case 1:
                        cury += 8;
                        if (cury >= Height) {
                            ++Pass;
                            cury = 2;
                        }
                        break;
                    case 2:
                        cury += 4;
                        if (cury >= Height) {
                            ++Pass;
                            cury = 1;
                        }
                        break;
                    case 3:
                        cury += 2;
                        break;
                }
            }
        }
    }
    // Set up the "byte output" routine
    void char_init() {
        a_count = 0;
    }
    // Add a character to the end of the current packet, and if it is 254
    // characters, flush the packet to disk.
    void char_out(byte c, OutputStream outs) throws IOException {
        accum[a_count++] = c;
        if (a_count >= 254)
            flush_char(outs);
    }
    // Clear out the hash table
    // table clear for block compress
    void cl_block(OutputStream outs) throws IOException {
        cl_hash(hsize);
        free_ent = ClearCode + 2;
        clear_flg = true;
        output(ClearCode, outs);
    }
    // reset code table
    void cl_hash(int hsize) {
        for (int i = 0; i < hsize; ++i)
            htab[i] = -1;
    }
    void compress(int init_bits, OutputStream outs) throws IOException {
        int fcode;
        int i /* = 0 */;
        int c;
        int ent;
        int disp;
        int hsize_reg;
        int hshift;
        // Set up the globals:  g_init_bits - initial number of bits
        g_init_bits = init_bits;
        // Set up the necessary values
        clear_flg = false;
        n_bits = g_init_bits;
        maxcode = MAXCODE(n_bits);
        ClearCode = 1 << (init_bits - 1);
        EOFCode = ClearCode + 1;
        free_ent = ClearCode + 2;
        char_init();
        ent = GIFNextPixel();
        hshift = 0;
        for (fcode = hsize; fcode < 65536; fcode *= 2)
            ++hshift;
        hshift = 8 - hshift;      // set hash code range bound
        hsize_reg = hsize;
        cl_hash(hsize_reg); // clear hash table
        output(ClearCode, outs);
        outer_loop:
        while ((c = GIFNextPixel()) != EOF) {
            fcode = (c << maxbits) + ent;
            i = (c << hshift) ^ ent;    // xor hashing
            if (htab[i] == fcode) {
                ent = codetab[i];
                continue;
            } else if (htab[i] >= 0)  // non-empty slot
            {
                disp = hsize_reg - i; // secondary hash (after G. Knott)
                if (i == 0)
                    disp = 1;
                do {
                    if ((i -= disp) < 0)
                        i += hsize_reg;
                    if (htab[i] == fcode) {
                        ent = codetab[i];
                        continue outer_loop;
                    }
                } while (htab[i] >= 0);
            }
            output(ent, outs);
            ent = c;
            if (free_ent < maxmaxcode) {
                codetab[i] = free_ent++;  // code -> hashtable
                htab[i] = fcode;
            } else
                cl_block(outs);
        }
        // Put out the final code.
        output(ent, outs);
        output(EOFCode, outs);
    }
    // Our own methods.
    /**
     *  Call this method after initialization to do the encoding
     */
    public synchronized void encode() throws IOException {
        encoding = true;
        iox = null;
        producer.startProduction(this);
        while (encoding)
            try {
                wait();
            } catch (InterruptedException e) {
            }
        if (iox != null)
            throw iox;
    }
    void encodeDone() throws IOException {
        int transparentIndex = -1;
        int transparentRgb = -1;
        // Put all the pixels into a hash table.
        colorHash = new IntHashtable();
        int index = 0;
        for (int row = 0; row < height; ++row) {
             for (int col = 0; col < width; ++col) {
                int rgb = rgbPixels[row][col];
                boolean isTransparent = ((rgb >>> 24) < 0x80);
                if (isTransparent) {
                    if (transparentIndex < 0) {
                        // First transparent color; remember it.
                        transparentIndex = index;
                        transparentRgb = rgb;
                    } else if (rgb != transparentRgb) {
                        // A second transparent color; replace it with
                        // the first one.
                        rgbPixels[row][col] = rgb = transparentRgb;
                    }
                }
                gifHashitem item =
                        (gifHashitem) colorHash.get(rgb);
                if (item == null) {
                    if (index >= 256)
                        throw new IOException("too many colors for a GIF");
                    item = new gifHashitem(
                            rgb, 1, index, isTransparent);
                    ++index;
                    colorHash.put(rgb, item);
                } else
                    ++item.count;
            }
        }
        // Figure out how many bits to use.
        int logColors;
        if (index <= 2)
            logColors = 1;
        else if (index <= 4)
            logColors = 2;
        else if (index <= 16)
            logColors = 4;
        else
            logColors = 8;
        // Turn colors into colormap entries.
        int mapSize = 1 << logColors;
        byte[] reds = new byte[mapSize];
        byte[] grns = new byte[mapSize];
        byte[] blus = new byte[mapSize];
        for (Enumeration e = colorHash.elements(); e.hasMoreElements();) {
            gifHashitem item = (gifHashitem) e.nextElement();
            reds[item.index] = (byte) ((item.rgb >> 16) & 0xff);
            grns[item.index] = (byte) ((item.rgb >> 8) & 0xff);
            blus[item.index] = (byte) (item.rgb & 0xff);
        }
        GIFEncode(
                out, width, height, interlace, (byte) 0, transparentIndex,
                logColors, reds, grns, blus);
    }
    private void encodeFinish() throws IOException {
        if (accumulate) {
            encodePixels(0, 0, width, height, accumulator, 0, width);
            accumulator = null;
            accumulate = false;
        }
    }
    void encodePixels(
            int x, int y, int w, int h, int[] rgbPixels, int off, int scansize)
            throws IOException {
        // Save the pixels.
        for (int row = 0; row < h; ++row)
            System.arraycopy(
                    rgbPixels, row * scansize + off,
                    this.rgbPixels[y + row], x, w);
    }
    private void encodePixelsWrapper(
            int x, int y, int w, int h, int[] rgbPixels, int off, int scansize)
            throws IOException {
        if (!started) {
            started = true;
            encodeStart(width, height);
            if ((hintflags & TOPDOWNLEFTRIGHT) == 0) {
                accumulate = true;
                accumulator = new int[width * height];
            }
        }
        if (accumulate)
            for (int row = 0; row < h; ++row)
                System.arraycopy(
                        rgbPixels, row * scansize + off,
                        accumulator, (y + row) * width + x,
                        w);
        else
            encodePixels(x, y, w, h, rgbPixels, off, scansize);
    }
    void encodeStart(int width, int height) throws IOException {
        this.width = width;
        this.height = height;
        rgbPixels = new int[height][width];
    }
    // Flush the packet to disk, and reset the accumulator
    void flush_char(OutputStream outs) throws IOException {
        if (a_count > 0) {
            outs.write(a_count);
            outs.write(accum, 0, a_count);
            a_count = 0;
        }
    }
    byte GetPixel(int x, int y) throws IOException {
        gifHashitem item =
                (gifHashitem) colorHash.get(rgbPixels[y][x]);
        if (item == null)
            throw new IOException("color not found");
        return (byte) item.index;
    }
    void GIFEncode(
            OutputStream outs, int Width, int Height, boolean Interlace, byte Background, int Transparent, int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue)
            throws IOException {
        byte B;
        int LeftOfs, TopOfs;
        int ColorMapSize;
        int InitCodeSize;
        int i;
        this.Width = Width;
        this.Height = Height;
        this.Interlace = Interlace;
        ColorMapSize = 1 << BitsPerPixel;
        LeftOfs = TopOfs = 0;
        // Calculate number of bits we are expecting
        CountDown = Width * Height;
        // Indicate which pass we are on (if interlace)
        Pass = 0;
        // The initial code size
        if (BitsPerPixel <= 1)
            InitCodeSize = 2;
        else
            InitCodeSize = BitsPerPixel;
        // Set up the current x and y position
        curx = 0;
        cury = 0;
        // Write the Magic header
        writeString(outs, "GIF89a");
        // Write out the screen width and height
        Putword(Width, outs);
        Putword(Height, outs);
        // Indicate that there is a global colour map
        B = (byte) 0x80;    // Yes, there is a color map
        // OR in the resolution
        B |= (byte) ((8 - 1) << 4);
        // Not sorted
        // OR in the Bits per Pixel
        B |= (byte) ((BitsPerPixel - 1));
        // Write it out
        Putbyte(B, outs);
        // Write out the Background colour
        Putbyte(Background, outs);
        // Pixel aspect ratio - 1:1.
        //Putbyte( (byte) 49, outs );
        // Java"s GIF reader currently has a bug, if the aspect ratio byte is
        // not zero it throws an ImageFormatException.  It doesn"t know that
        // 49 means a 1:1 aspect ratio.  Well, whatever, zero works with all
        // the other decoders I"ve tried so it probably doesn"t hurt.
        Putbyte((byte) 0, outs);
        // Write out the Global Colour Map
        for (i = 0; i < ColorMapSize; ++i) {
            Putbyte(Red[i], outs);
            Putbyte(Green[i], outs);
            Putbyte(Blue[i], outs);
        }
        // Write out extension for transparent colour index, if necessary.
        if (Transparent != -1) {
            Putbyte((byte) "!", outs);
            Putbyte((byte) 0xf9, outs);
            Putbyte((byte) 4, outs);
            Putbyte((byte) 1, outs);
            Putbyte((byte) 0, outs);
            Putbyte((byte) 0, outs);
            Putbyte((byte) Transparent, outs);
            Putbyte((byte) 0, outs);
        }
        // Write an Image separator
        Putbyte((byte) ",", outs);
        // Write the Image header
        Putword(LeftOfs, outs);
        Putword(TopOfs, outs);
        Putword(Width, outs);
        Putword(Height, outs);
        // Write out whether or not the image is interlaced
        if (Interlace)
            Putbyte((byte) 0x40, outs);
        else
            Putbyte((byte) 0x00, outs);
        // Write out the initial code size
        Putbyte((byte) InitCodeSize, outs);
        // Go and actually compress the data
        compress(InitCodeSize + 1, outs);
        // Write out a Zero-length packet (to end the series)
        Putbyte((byte) 0, outs);
        // Write the GIF file terminator
        Putbyte((byte) ";", outs);
    }
    // Return the next pixel from the image
    int GIFNextPixel() throws IOException {
        byte r;
        if (CountDown == 0)
            return EOF;
        --CountDown;
        r = GetPixel(curx, cury);
        BumpPixel();
        return r & 0xff;
    }
    public void imageComplete(int status) {
        producer.removeConsumer(this);
        if (status == ImageConsumer.IMAGEABORTED)
            iox = new IOException("image aborted");
        else {
            try {
                encodeFinish();
                encodeDone();
            } catch (IOException e) {
                iox = e;
            }
        }
        stop();
    }
    final int MAXCODE(int n_bits) {
        return (1 << n_bits) - 1;
    }
    void output(int code, OutputStream outs) throws IOException {
        cur_accum &= masks[cur_bits];
        if (cur_bits > 0)
            cur_accum |= (code << cur_bits);
        else
            cur_accum = code;
        cur_bits += n_bits;
        while (cur_bits >= 8) {
            char_out((byte) (cur_accum & 0xff), outs);
            cur_accum >>= 8;
            cur_bits -= 8;
        }
        // If the next entry is going to be too big for the code size,
        // then increase it, if possible.
        if (free_ent > maxcode || clear_flg) {
            if (clear_flg) {
                maxcode = MAXCODE(n_bits = g_init_bits);
                clear_flg = false;
            } else {
                ++n_bits;
                if (n_bits == maxbits)
                    maxcode = maxmaxcode;
                else
                    maxcode = MAXCODE(n_bits);
            }
        }
        if (code == EOFCode) {
            // At EOF, write the rest of the buffer.
            while (cur_bits > 0) {
                char_out((byte) (cur_accum & 0xff), outs);
                cur_accum >>= 8;
                cur_bits -= 8;
            }
            flush_char(outs);
        }
    }
    // Write out a byte to the GIF file
    void Putbyte(byte b, OutputStream outs) throws IOException {
        outs.write(b);
    }
    // Write out a word to the GIF file
    void Putword(int w, OutputStream outs) throws IOException {
        Putbyte((byte) (w & 0xff), outs);
        Putbyte((byte) ((w >> 8) & 0xff), outs);
    }
    public void setColorModel(ColorModel model) {
        // Ignore.
    }
    // Methods from ImageConsumer.
    public void setDimensions(int width, int height) {
        this.width = width;
        this.height = height;
    }
    public void setHints(int hintflags) {
        this.hintflags = hintflags;
    }
    public void setPixels(
            int x, int y, int w, int h, ColorModel model, byte[] pixels,
            int off, int scansize) {
        int[] rgbPixels = new int[w];
        for (int row = 0; row < h; ++row) {
            int rowOff = off + row * scansize;
            for (int col = 0; col < w; ++col)
                rgbPixels[col] = model.getRGB(pixels[rowOff + col] & 0xff);
            try {
                encodePixelsWrapper(x, y + row, w, 1, rgbPixels, 0, w);
            } catch (IOException e) {
                iox = e;
                stop();
                return;
            }
        }
    }
    public void setPixels(
            int x, int y, int w, int h, ColorModel model, int[] pixels,
            int off, int scansize) {
        if (model == rgbModel) {
            try {
                encodePixelsWrapper(x, y, w, h, pixels, off, scansize);
            } catch (IOException e) {
                iox = e;
                stop();
                return;
            }
        } else {
            int[] rgbPixels = new int[w];
            for (int row = 0; row < h; ++row) {
                int rowOff = off + row * scansize;
                for (int col = 0; col < w; ++col)
                    rgbPixels[col] = model.getRGB(pixels[rowOff + col]);
                try {
                    encodePixelsWrapper(x, y + row, w, 1, rgbPixels, 0, w);
                } catch (IOException e) {
                    iox = e;
                    stop();
                    return;
                }
            }
        }
    }
    public void setProperties(Hashtable props) {
        this.props = props;
    }
    private synchronized void stop() {
        encoding = false;
        notifyAll();
    }
    static void writeString(OutputStream out, String str) throws IOException {
        byte[] buf = str.getBytes();
        out.write(buf);
    }
}



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 ( */
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)));
    }
}



Gif Encoder - writes out an image as a GIF.

 

import java.awt.Image;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.PixelGrabber;
import java.io.IOException;
import java.io.OutputStream;

/**  GifEncoder - writes out an image as a GIF.
 *
 * Transparency handling and variable bit size courtesy of Jack Palevich.
 *
 * Copyright (C) 1996 by Jef Poskanzer <jef@acme.ru>.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS"" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * Visit the ACME Labs Java page for up-to-date versions of this and other
 * fine Java utilities: http://www.acme.ru/java/
 */
public class GifEncoder {
  private boolean interlace = false;
  private int width, height;
  private byte[] pixels;
  private byte[] r, g, b; // the color look-up table
  private int pixelIndex;
  private int numPixels;
  private int transparentPixel = -1; // hpm
  /**
   *  Constructs a new GifEncoder. 
   * @param width The image width.
   * @param height  The image height.
   * @param pixels  The pixel data.
   * @param r   The red look-up table.
   * @param g   The green look-up table.
   * @param b   The blue look-up table.
   */
  public GifEncoder(int width, int height, byte[] pixels, byte[] r, byte[] g, byte[] b) {
    this.width = width;
    this.height = height;
    this.pixels = pixels;
    this.r = r;
    this.g = g;
    this.b = b;
    interlace = false;
    pixelIndex = 0;
    numPixels = width * height;
  }
  /** Constructs a new GifEncoder using an 8-bit AWT Image.
   The image is assumed to be fully loaded. */
  public GifEncoder(Image img) {
    width = img.getWidth(null);
    height = img.getHeight(null);
    pixels = new byte[width * height];
    PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, false);
    try {
      pg.grabPixels();
    } catch (InterruptedException e) {
      System.err.println(e);
    }
    ColorModel cm = pg.getColorModel();
    if (cm instanceof IndexColorModel) {
      pixels = (byte[]) (pg.getPixels());
      // hpm
      IndexColorModel icm = (IndexColorModel) cm;
      setTransparentPixel(icm.getTransparentPixel());
    } else
      throw new IllegalArgumentException("IMAGE_ERROR");
    IndexColorModel m = (IndexColorModel) cm;
    int mapSize = m.getMapSize();
    r = new byte[mapSize];
    g = new byte[mapSize];
    b = new byte[mapSize];
    m.getReds(r);
    m.getGreens(g);
    m.getBlues(b);
    interlace = false;
    pixelIndex = 0;
    numPixels = width * height;
  }
  /** Saves the image as a GIF file. */
  public void write(OutputStream out) throws IOException {
    // Figure out how many bits to use.
    int numColors = r.length;
    int BitsPerPixel;
    if (numColors <= 2)
      BitsPerPixel = 1;
    else if (numColors <= 4)
      BitsPerPixel = 2;
    else if (numColors <= 16)
      BitsPerPixel = 4;
    else
      BitsPerPixel = 8;
    int ColorMapSize = 1 << BitsPerPixel;
    byte[] reds = new byte[ColorMapSize];
    byte[] grns = new byte[ColorMapSize];
    byte[] blus = new byte[ColorMapSize];
    for (int i = 0; i < numColors; i++) {
      reds[i] = r[i];
      grns[i] = g[i];
      blus[i] = b[i];
    }
    // hpm
    GIFEncode(out, width, height, interlace, (byte) 0, getTransparentPixel(), BitsPerPixel, reds, grns, blus);
  }
  // hpm
  public void setTransparentPixel(int pixel) {
    transparentPixel = pixel;
  }
  // hpm
  public int getTransparentPixel() {
    return transparentPixel;
  }
  static void writeString(OutputStream out, String str) throws IOException {
    byte[] buf = str.getBytes();
    out.write(buf);
  }
  // Adapted from ppmtogif, which is based on GIFENCOD by David
  // Rowley <mgardi@watdscu.waterloo.edu>.  Lempel-Zim compression
  // based on "compress".
  int Width, Height;
  boolean Interlace;
  void GIFEncode(OutputStream outs, int Width, int Height, boolean Interlace,
      byte Background, int Transparent, int BitsPerPixel, byte[] Red,
      byte[] Green, byte[] Blue) throws IOException {
    byte B;
    int LeftOfs, TopOfs;
    int ColorMapSize;
    int InitCodeSize;
    int i;
    this.Width = Width;
    this.Height = Height;
    this.Interlace = Interlace;
    ColorMapSize = 1 << BitsPerPixel;
    LeftOfs = TopOfs = 0;
    // The initial code size
    if (BitsPerPixel <= 1)
      InitCodeSize = 2;
    else
      InitCodeSize = BitsPerPixel;
    // Write the Magic header
    writeString(outs, "GIF89a");
    // Write out the screen width and height
    Putword(Width, outs);
    Putword(Height, outs);
    // Indicate that there is a global colour map
    B = (byte) 0x80; // Yes, there is a color map
    // OR in the resolution
    B |= (byte) ((8 - 1) << 4);
    // Not sorted
    // OR in the Bits per Pixel
    B |= (byte) ((BitsPerPixel - 1));
    // Write it out
    Putbyte(B, outs);
    // Write out the Background colour
    Putbyte(Background, outs);
    // Pixel aspect ratio - 1:1.
    //Putbyte( (byte) 49, outs );
    // Java"s GIF reader currently has a bug, if the aspect ratio byte is
    // not zero it throws an ImageFormatException.  It doesn"t know that
    // 49 means a 1:1 aspect ratio.  Well, whatever, zero works with all
    // the other decoders I"ve tried so it probably doesn"t hurt.
    Putbyte((byte) 0, outs);
    // Write out the Global Colour Map
    for (i = 0; i < ColorMapSize; ++i) {
      Putbyte(Red[i], outs);
      Putbyte(Green[i], outs);
      Putbyte(Blue[i], outs);
    }
    // Write out extension for transparent colour index, if necessary.
    if (Transparent != -1) {
      Putbyte((byte) "!", outs);
      Putbyte((byte) 0xf9, outs);
      Putbyte((byte) 4, outs);
      Putbyte((byte) 1, outs);
      Putbyte((byte) 0, outs);
      Putbyte((byte) 0, outs);
      Putbyte((byte) Transparent, outs);
      Putbyte((byte) 0, outs);
    }
    // Write an Image separator
    Putbyte((byte) ",", outs);
    // Write the Image header
    Putword(LeftOfs, outs);
    Putword(TopOfs, outs);
    Putword(Width, outs);
    Putword(Height, outs);
    // Write out whether or not the image is interlaced
    if (Interlace)
      Putbyte((byte) 0x40, outs);
    else
      Putbyte((byte) 0x00, outs);
    // Write out the initial code size
    Putbyte((byte) InitCodeSize, outs);
    // Go and actually compress the data
    compress(InitCodeSize + 1, outs);
    // Write out a Zero-length packet (to end the series)
    Putbyte((byte) 0, outs);
    // Write the GIF file terminator
    Putbyte((byte) ";", outs);
  }
  static final int EOF = -1;
  // Return the next pixel from the image
  int GIFNextPixel() throws IOException {
    if (pixelIndex == numPixels)
      return EOF;
    else
      return ((byte[]) pixels)[pixelIndex++] & 0xff;
  }
  // Write out a word to the GIF file
  void Putword(int w, OutputStream outs) throws IOException {
    Putbyte((byte) (w & 0xff), outs);
    Putbyte((byte) ((w >> 8) & 0xff), outs);
  }
  // Write out a byte to the GIF file
  void Putbyte(byte b, OutputStream outs) throws IOException {
    outs.write(b);
  }
  // GIFCOMPR.C       - GIF Image compression routines
  //
  // Lempel-Ziv compression based on "compress".  GIF modifications by
  // David Rowley (mgardi@watdcsu.waterloo.edu)
  // General DEFINEs
  static final int BITS = 12;
  static final int HSIZE = 5003; // 80% occupancy
  // GIF Image compression - modified "compress"
  //
  // Based on: compress.c - File compression ala IEEE Computer, June 1984.
  //
  // By Authors:  Spencer W. Thomas      (decvax!harpo!utah-cs!utah-gr!thomas)
  //              Jim McKie              (decvax!mcvax!jim)
  //              Steve Davies           (decvax!vax135!petsd!peora!srd)
  //              Ken Turkowski          (decvax!decwrl!turtlevax!ken)
  //              James A. Woods         (decvax!ihnp4!ames!jaw)
  //              Joe Orost              (decvax!vax135!petsd!joe)
  int n_bits; // number of bits/code
  int maxbits = BITS; // user settable max # bits/code
  int maxcode; // maximum code, given n_bits
  int maxmaxcode = 1 << BITS; // should NEVER generate this code
  final int MAXCODE(int n_bits) {
    return (1 << n_bits) - 1;
  }
  int[] htab = new int[HSIZE];
  int[] codetab = new int[HSIZE];
  int hsize = HSIZE; // for dynamic table sizing
  int free_ent = 0; // first unused entry
  // block compression parameters -- after all codes are used up,
  // and compression rate changes, start over.
  boolean clear_flg = false;
  // Algorithm:  use open addressing double hashing (no chaining) on the
  // prefix code / next character combination.  We do a variant of Knuth"s
  // algorithm D (vol. 3, sec. 6.4) along with G. Knott"s relatively-prime
  // secondary probe.  Here, the modular division first probe is gives way
  // to a faster exclusive-or manipulation.  Also do block compression with
  // an adaptive reset, whereby the code table is cleared when the compression
  // ratio decreases, but after the table fills.  The variable-length output
  // codes are re-sized at this point, and a special CLEAR code is generated
  // for the decompressor.  Late addition:  construct the table according to
  // file size for noticeable speed improvement on small files.  Please direct
  // questions about this implementation to ames!jaw.
  int g_init_bits;
  int ClearCode;
  int EOFCode;
  void compress(int init_bits, OutputStream outs) throws IOException {
    int fcode;
    int i /* = 0 */;
    int c;
    int ent;
    int disp;
    int hsize_reg;
    int hshift;
    // Set up the globals:  g_init_bits - initial number of bits
    g_init_bits = init_bits;
    // Set up the necessary values
    clear_flg = false;
    n_bits = g_init_bits;
    maxcode = MAXCODE(n_bits);
    ClearCode = 1 << (init_bits - 1);
    EOFCode = ClearCode + 1;
    free_ent = ClearCode + 2;
    char_init();
    ent = GIFNextPixel();
    hshift = 0;
    for (fcode = hsize; fcode < 65536; fcode *= 2)
      ++hshift;
    hshift = 8 - hshift; // set hash code range bound
    hsize_reg = hsize;
    cl_hash(hsize_reg); // clear hash table
    output(ClearCode, outs);
    outer_loop: while ((c = GIFNextPixel()) != EOF) {
      fcode = (c << maxbits) + ent;
      i = (c << hshift) ^ ent; // xor hashing
      if (htab[i] == fcode) {
        ent = codetab[i];
        continue;
      } else if (htab[i] >= 0) // non-empty slot
      {
        disp = hsize_reg - i; // secondary hash (after G. Knott)
        if (i == 0)
          disp = 1;
        do {
          if ((i -= disp) < 0)
            i += hsize_reg;
          if (htab[i] == fcode) {
            ent = codetab[i];
            continue outer_loop;
          }
        } while (htab[i] >= 0);
      }
      output(ent, outs);
      ent = c;
      if (free_ent < maxmaxcode) {
        codetab[i] = free_ent++; // code -> hashtable
        htab[i] = fcode;
      } else
        cl_block(outs);
    }
    // Put out the final code.
    output(ent, outs);
    output(EOFCode, outs);
  }
  // output
  //
  // Output the given code.
  // Inputs:
  //      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
  //              that n_bits =< wordsize - 1.
  // Outputs:
  //      Outputs code to the file.
  // Assumptions:
  //      Chars are 8 bits long.
  // Algorithm:
  //      Maintain a BITS character long buffer (so that 8 codes will
  // fit in it exactly).  Use the VAX insv instruction to insert each
  // code in turn.  When the buffer fills up empty it and start over.
  int cur_accum = 0;
  int cur_bits = 0;
  int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F,
      0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF,
      0x7FFF, 0xFFFF };
  void output(int code, OutputStream outs) throws IOException {
    cur_accum &= masks[cur_bits];
    if (cur_bits > 0)
      cur_accum |= (code << cur_bits);
    else
      cur_accum = code;
    cur_bits += n_bits;
    while (cur_bits >= 8) {
      char_out((byte) (cur_accum & 0xff), outs);
      cur_accum >>= 8;
      cur_bits -= 8;
    }
    // If the next entry is going to be too big for the code size,
    // then increase it, if possible.
    if (free_ent > maxcode || clear_flg) {
      if (clear_flg) {
        maxcode = MAXCODE(n_bits = g_init_bits);
        clear_flg = false;
      } else {
        ++n_bits;
        if (n_bits == maxbits)
          maxcode = maxmaxcode;
        else
          maxcode = MAXCODE(n_bits);
      }
    }
    if (code == EOFCode) {
      // At EOF, write the rest of the buffer.
      while (cur_bits > 0) {
        char_out((byte) (cur_accum & 0xff), outs);
        cur_accum >>= 8;
        cur_bits -= 8;
      }
      flush_char(outs);
    }
  }
  // Clear out the hash table
  // table clear for block compress
  void cl_block(OutputStream outs) throws IOException {
    cl_hash(hsize);
    free_ent = ClearCode + 2;
    clear_flg = true;
    output(ClearCode, outs);
  }
  // reset code table
  void cl_hash(int hsize) {
    for (int i = 0; i < hsize; ++i)
      htab[i] = -1;
  }
  // GIF Specific routines
  // Number of characters so far in this "packet"
  int a_count;
  // Set up the "byte output" routine
  void char_init() {
    a_count = 0;
  }
  // Define the storage for the packet accumulator
  byte[] accum = new byte[256];
  // Add a character to the end of the current packet, and if it is 254
  // characters, flush the packet to disk.
  void char_out(byte c, OutputStream outs) throws IOException {
    accum[a_count++] = c;
    if (a_count >= 254)
      flush_char(outs);
  }
  // Flush the packet to disk, and reset the accumulator
  void flush_char(OutputStream outs) throws IOException {
    if (a_count > 0) {
      outs.write(a_count);
      outs.write(accum, 0, a_count);
      a_count = 0;
    }
  }
}
class GifEncoderHashitem {
  public int rgb;
  public int count;
  public int index;
  public boolean isTransparent;
  public GifEncoderHashitem(int rgb, int count, int index,
      boolean isTransparent) {
    this.rgb = rgb;
    this.count = count;
    this.index = index;
    this.isTransparent = isTransparent;
  }
}



Gif file Encoder

   
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
/**
 * Class GifDecoder - Decodes a GIF file into one or more frames. <br>
 * 
 * <pre>
 *  Example:
 *     GifDecoder d = new GifDecoder();
 *     d.read(&quot;sample.gif&quot;);
 *     int n = d.getFrameCount();
 *     for (int i = 0; i &lt; n; i++) {
 *        BufferedImage frame = d.getFrame(i);  // frame i
 *        int t = d.getDelay(i);  // display duration of frame in milliseconds
 *        // do something with frame
 *     }
 * </pre>
 * 
 * No copyright asserted on the source code of this class. May be used for any
 * purpose, however, refer to the Unisys LZW patent for any additional
 * restrictions. Please forward any corrections to kweiner@fmsware.ru.
 * 
 * @author Kevin Weiner, FM Software; LZW decoder adapted from John Cristy"s
 *         ImageMagick.
 * @version 1.03 November 2003
 * 
 */
public class GifDecoder {
  /**
   * File read status: No errors.
   */
  public static final int STATUS_OK = 0;
  /**
   * File read status: Error decoding file (may be partially decoded)
   */
  public static final int STATUS_FORMAT_ERROR = 1;
  /**
   * File read status: Unable to open source.
   */
  public static final int STATUS_OPEN_ERROR = 2;
  protected BufferedInputStream in;
  protected int status;
  protected int width; // full image width
  protected int height; // full image height
  protected boolean gctFlag; // global color table used
  protected int gctSize; // size of global color table
  protected int loopCount = 1; // iterations; 0 = repeat forever
  protected int[] gct; // global color table
  protected int[] lct; // local color table
  protected int[] act; // active color table
  protected int bgIndex; // background color index
  protected int bgColor; // background color
  protected int lastBgColor; // previous bg color
  protected int pixelAspect; // pixel aspect ratio
  protected boolean lctFlag; // local color table flag
  protected boolean interlace; // interlace flag
  protected int lctSize; // local color table size
  protected int ix, iy, iw, ih; // current image rectangle
  protected Rectangle lastRect; // last image rect
  protected BufferedImage image; // current frame
  protected BufferedImage lastImage; // previous frame
  protected byte[] block = new byte[256]; // current data block
  protected int blockSize = 0; // block size
  // last graphic control extension info
  protected int dispose = 0;
  // 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
  protected int lastDispose = 0;
  protected boolean transparency = false; // use transparent color
  protected int delay = 0; // delay in milliseconds
  protected int transIndex; // transparent color index
  protected static final int MaxStackSize = 4096;
  // max decoder pixel stack size
  // LZW decoder working arrays
  protected short[] prefix;
  protected byte[] suffix;
  protected byte[] pixelStack;
  protected byte[] pixels;
  protected ArrayList frames; // frames read from current file
  protected int frameCount;
  static class GifFrame {
    public GifFrame(BufferedImage im, int del) {
      image = im;
      delay = del;
    }
    public BufferedImage image;
    public int delay;
  }
  /**
   * Gets display duration for specified frame.
   * 
   * @param n
   *          int index of frame
   * @return delay in milliseconds
   */
  public int getDelay(int n) {
    //
    delay = -1;
    if ((n >= 0) && (n < frameCount)) {
      delay = ((GifFrame) frames.get(n)).delay;
    }
    return delay;
  }
  /**
   * Gets the number of frames read from file.
   * 
   * @return frame count
   */
  public int getFrameCount() {
    return frameCount;
  }
  /**
   * Gets the first (or only) image read.
   * 
   * @return BufferedImage containing first frame, or null if none.
   */
  public BufferedImage getImage() {
    return getFrame(0);
  }
  /**
   * Gets the "Netscape" iteration count, if any. A count of 0 means repeat
   * indefinitiely.
   * 
   * @return iteration count if one was specified, else 1.
   */
  public int getLoopCount() {
    return loopCount;
  }
  /**
   * Creates new frame image from current data (and previous frames as specified
   * by their disposition codes).
   */
  protected void setPixels() {
    // expose destination image"s pixels as int array
    int[] dest = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    // fill in starting image contents based on last image"s dispose code
    if (lastDispose > 0) {
      if (lastDispose == 3) {
        // use image before last
        int n = frameCount - 2;
        if (n > 0) {
          lastImage = getFrame(n - 1);
        } else {
          lastImage = null;
        }
      }
      if (lastImage != null) {
        int[] prev = ((DataBufferInt) lastImage.getRaster().getDataBuffer()).getData();
        System.arraycopy(prev, 0, dest, 0, width * height);
        // copy pixels
        if (lastDispose == 2) {
          // fill last image rect area with background color
          Graphics2D g = image.createGraphics();
          Color c = null;
          if (transparency) {
            c = new Color(0, 0, 0, 0); // assume background is transparent
          } else {
            c = new Color(lastBgColor); // use given background color
          }
          g.setColor(c);
          g.setComposite(AlphaComposite.Src); // replace area
          g.fill(lastRect);
          g.dispose();
        }
      }
    }
    // copy each source line to the appropriate place in the destination
    int pass = 1;
    int inc = 8;
    int iline = 0;
    for (int i = 0; i < ih; i++) {
      int line = i;
      if (interlace) {
        if (iline >= ih) {
          pass++;
          switch (pass) {
          case 2:
            iline = 4;
            break;
          case 3:
            iline = 2;
            inc = 4;
            break;
          case 4:
            iline = 1;
            inc = 2;
          }
        }
        line = iline;
        iline += inc;
      }
      line += iy;
      if (line < height) {
        int k = line * width;
        int dx = k + ix; // start of line in dest
        int dlim = dx + iw; // end of dest line
        if ((k + width) < dlim) {
          dlim = k + width; // past dest edge
        }
        int sx = i * iw; // start of line in source
        while (dx < dlim) {
          // map color and insert in destination
          int index = ((int) pixels[sx++]) & 0xff;
          int c = act[index];
          if (c != 0) {
            dest[dx] = c;
          }
          dx++;
        }
      }
    }
  }
  /**
   * Gets the image contents of frame n.
   * 
   * @return BufferedImage representation of frame, or null if n is invalid.
   */
  public BufferedImage getFrame(int n) {
    BufferedImage im = null;
    if ((n >= 0) && (n < frameCount)) {
      im = ((GifFrame) frames.get(n)).image;
    }
    return im;
  }
  /**
   * Gets image size.
   * 
   * @return GIF image dimensions
   */
  public Dimension getFrameSize() {
    return new Dimension(width, height);
  }
  /**
   * Reads GIF image from stream
   * 
   * @param BufferedInputStream
   *          containing GIF file.
   * @return read status code (0 = no errors)
   */
  public int read(BufferedInputStream is) {
    init();
    if (is != null) {
      in = is;
      readHeader();
      if (!err()) {
        readContents();
        if (frameCount < 0) {
          status = STATUS_FORMAT_ERROR;
        }
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }
    try {
      is.close();
    } catch (IOException e) {
    }
    return status;
  }
  /**
   * Reads GIF image from stream
   * 
   * @param InputStream
   *          containing GIF file.
   * @return read status code (0 = no errors)
   */
  public int read(InputStream is) {
    init();
    if (is != null) {
      if (!(is instanceof BufferedInputStream))
        is = new BufferedInputStream(is);
      in = (BufferedInputStream) is;
      readHeader();
      if (!err()) {
        readContents();
        if (frameCount < 0) {
          status = STATUS_FORMAT_ERROR;
        }
      }
    } else {
      status = STATUS_OPEN_ERROR;
    }
    try {
      is.close();
    } catch (IOException e) {
    }
    return status;
  }
  /**
   * Reads GIF file from specified file/URL source (URL assumed if name contains
   * ":/" or "file:")
   * 
   * @param name
   *          String containing source
   * @return read status code (0 = no errors)
   */
  public int read(String name) {
    status = STATUS_OK;
    try {
      name = name.trim().toLowerCase();
      if ((name.indexOf("file:") >= 0) || (name.indexOf(":/") > 0)) {
        URL url = new URL(name);
        in = new BufferedInputStream(url.openStream());
      } else {
        in = new BufferedInputStream(new FileInputStream(name));
      }
      status = read(in);
    } catch (IOException e) {
      status = STATUS_OPEN_ERROR;
    }
    return status;
  }
  /**
   * Decodes LZW image data into pixel array. Adapted from John Cristy"s
   * ImageMagick.
   */
  protected void decodeImageData() {
    int NullCode = -1;
    int npix = iw * ih;
    int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
    if ((pixels == null) || (pixels.length < npix)) {
      pixels = new byte[npix]; // allocate new pixel array
    }
    if (prefix == null)
      prefix = new short[MaxStackSize];
    if (suffix == null)
      suffix = new byte[MaxStackSize];
    if (pixelStack == null)
      pixelStack = new byte[MaxStackSize + 1];
    // Initialize GIF data stream decoder.
    data_size = read();
    clear = 1 << data_size;
    end_of_information = clear + 1;
    available = clear + 2;
    old_code = NullCode;
    code_size = data_size + 1;
    code_mask = (1 << code_size) - 1;
    for (code = 0; code < clear; code++) {
      prefix[code] = 0;
      suffix[code] = (byte) code;
    }
    // Decode GIF pixel stream.
    datum = bits = count = first = top = pi = bi = 0;
    for (i = 0; i < npix;) {
      if (top == 0) {
        if (bits < code_size) {
          // Load bytes until there are enough bits for a code.
          if (count == 0) {
            // Read a new data block.
            count = readBlock();
            if (count <= 0)
              break;
            bi = 0;
          }
          datum += (((int) block[bi]) & 0xff) << bits;
          bits += 8;
          bi++;
          count--;
          continue;
        }
        // Get the next code.
        code = datum & code_mask;
        datum >>= code_size;
        bits -= code_size;
        // Interpret the code
        if ((code > available) || (code == end_of_information))
          break;
        if (code == clear) {
          // Reset decoder.
          code_size = data_size + 1;
          code_mask = (1 << code_size) - 1;
          available = clear + 2;
          old_code = NullCode;
          continue;
        }
        if (old_code == NullCode) {
          pixelStack[top++] = suffix[code];
          old_code = code;
          first = code;
          continue;
        }
        in_code = code;
        if (code == available) {
          pixelStack[top++] = (byte) first;
          code = old_code;
        }
        while (code > clear) {
          pixelStack[top++] = suffix[code];
          code = prefix[code];
        }
        first = ((int) suffix[code]) & 0xff;
        // Add a new string to the string table,
        if (available >= MaxStackSize)
          break;
        pixelStack[top++] = (byte) first;
        prefix[available] = (short) old_code;
        suffix[available] = (byte) first;
        available++;
        if (((available & code_mask) == 0) && (available < MaxStackSize)) {
          code_size++;
          code_mask += available;
        }
        old_code = in_code;
      }
      // Pop a pixel off the pixel stack.
      top--;
      pixels[pi++] = pixelStack[top];
      i++;
    }
    for (i = pi; i < npix; i++) {
      pixels[i] = 0; // clear missing pixels
    }
  }
  /**
   * Returns true if an error was encountered during reading/decoding
   */
  protected boolean err() {
    return status != STATUS_OK;
  }
  /**
   * Initializes or re-initializes reader
   */
  protected void init() {
    status = STATUS_OK;
    frameCount = 0;
    frames = new ArrayList();
    gct = null;
    lct = null;
  }
  /**
   * Reads a single byte from the input stream.
   */
  protected int read() {
    int curByte = 0;
    try {
      curByte = in.read();
    } catch (IOException e) {
      status = STATUS_FORMAT_ERROR;
    }
    return curByte;
  }
  /**
   * Reads next variable length block from input.
   * 
   * @return number of bytes stored in "buffer"
   */
  protected int readBlock() {
    blockSize = read();
    int n = 0;
    if (blockSize > 0) {
      try {
        int count = 0;
        while (n < blockSize) {
          count = in.read(block, n, blockSize - n);
          if (count == -1)
            break;
          n += count;
        }
      } catch (IOException e) {
      }
      if (n < blockSize) {
        status = STATUS_FORMAT_ERROR;
      }
    }
    return n;
  }
  /**
   * Reads color table as 256 RGB integer values
   * 
   * @param ncolors
   *          int number of colors to read
   * @return int array containing 256 colors (packed ARGB with full alpha)
   */
  protected int[] readColorTable(int ncolors) {
    int nbytes = 3 * ncolors;
    int[] tab = null;
    byte[] c = new byte[nbytes];
    int n = 0;
    try {
      n = in.read(c);
    } catch (IOException e) {
    }
    if (n < nbytes) {
      status = STATUS_FORMAT_ERROR;
    } else {
      tab = new int[256]; // max size to avoid bounds checks
      int i = 0;
      int j = 0;
      while (i < ncolors) {
        int r = ((int) c[j++]) & 0xff;
        int g = ((int) c[j++]) & 0xff;
        int b = ((int) c[j++]) & 0xff;
        tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
      }
    }
    return tab;
  }
  /**
   * Main file parser. Reads GIF content blocks.
   */
  protected void readContents() {
    // read GIF file content blocks
    boolean done = false;
    while (!(done || err())) {
      int code = read();
      switch (code) {
      case 0x2C: // image separator
        readImage();
        break;
      case 0x21: // extension
        code = read();
        switch (code) {
        case 0xf9: // graphics control extension
          readGraphicControlExt();
          break;
        case 0xff: // application extension
          readBlock();
          String app = "";
          for (int i = 0; i < 11; i++) {
            app += (char) block[i];
          }
          if (app.equals("NETSCAPE2.0")) {
            readNetscapeExt();
          } else
            skip(); // don"t care
          break;
        default: // uninteresting extension
          skip();
        }
        break;
      case 0x3b: // terminator
        done = true;
        break;
      case 0x00: // bad byte, but keep going and see what happens
        break;
      default:
        status = STATUS_FORMAT_ERROR;
      }
    }
  }
  /**
   * Reads Graphics Control Extension values
   */
  protected void readGraphicControlExt() {
    read(); // block size
    int packed = read(); // packed fields
    dispose = (packed & 0x1c) >> 2; // disposal method
    if (dispose == 0) {
      dispose = 1; // elect to keep old image if discretionary
    }
    transparency = (packed & 1) != 0;
    delay = readShort() * 10; // delay in milliseconds
    transIndex = read(); // transparent color index
    read(); // block terminator
  }
  /**
   * Reads GIF file header information.
   */
  protected void readHeader() {
    String id = "";
    for (int i = 0; i < 6; i++) {
      id += (char) read();
    }
    if (!id.startsWith("GIF")) {
      status = STATUS_FORMAT_ERROR;
      return;
    }
    readLSD();
    if (gctFlag && !err()) {
      gct = readColorTable(gctSize);
      bgColor = gct[bgIndex];
    }
  }
  /**
   * Reads next frame image
   */
  protected void readImage() {
    ix = readShort(); // (sub)image position & size
    iy = readShort();
    iw = readShort();
    ih = readShort();
    int packed = read();
    lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
    interlace = (packed & 0x40) != 0; // 2 - interlace flag
    // 3 - sort flag
    // 4-5 - reserved
    lctSize = 2 << (packed & 7); // 6-8 - local color table size
    if (lctFlag) {
      lct = readColorTable(lctSize); // read table
      act = lct; // make local table active
    } else {
      act = gct; // make global table active
      if (bgIndex == transIndex)
        bgColor = 0;
    }
    int save = 0;
    if (transparency) {
      save = act[transIndex];
      act[transIndex] = 0; // set transparent color if specified
    }
    if (act == null) {
      status = STATUS_FORMAT_ERROR; // no color table defined
    }
    if (err())
      return;
    decodeImageData(); // decode pixel data
    skip();
    if (err())
      return;
    frameCount++;
    // create new image to receive frame data
    image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
    setPixels(); // transfer pixel data to image
    frames.add(new GifFrame(image, delay)); // add image to frame list
    if (transparency) {
      act[transIndex] = save;
    }
    resetFrame();
  }
  /**
   * Reads Logical Screen Descriptor
   */
  protected void readLSD() {
    // logical screen size
    width = readShort();
    height = readShort();
    // packed fields
    int packed = read();
    gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
    // 2-4 : color resolution
    // 5 : gct sort flag
    gctSize = 2 << (packed & 7); // 6-8 : gct size
    bgIndex = read(); // background color index
    pixelAspect = read(); // pixel aspect ratio
  }
  /**
   * Reads Netscape extenstion to obtain iteration count
   */
  protected void readNetscapeExt() {
    do {
      readBlock();
      if (block[0] == 1) {
        // loop count sub-block
        int b1 = ((int) block[1]) & 0xff;
        int b2 = ((int) block[2]) & 0xff;
        loopCount = (b2 << 8) | b1;
      }
    } while ((blockSize > 0) && !err());
  }
  /**
   * Reads next 16-bit value, LSB first
   */
  protected int readShort() {
    // read 16-bit value, LSB first
    return read() | (read() << 8);
  }
  /**
   * Resets frame state for reading next image.
   */
  protected void resetFrame() {
    lastDispose = dispose;
    lastRect = new Rectangle(ix, iy, iw, ih);
    lastImage = image;
    lastBgColor = bgColor;
    int dispose = 0;
    boolean transparency = false;
    int delay = 0;
    lct = null;
  }
  /**
   * Skips variable length blocks up to and including next zero length block.
   */
  protected void skip() {
    do {
      readBlock();
    } while ((blockSize > 0) && !err());
  }
}



GIF Writer

    
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class ImageConverterGIF {
  public static void main(String[] args) throws Exception {
    String imageFilePath = "C:/myBmp.bmp";
    String gifFilePath = "C:/myPic.gif";
    File inputFile = new File(imageFilePath);
    BufferedImage image = ImageIO.read(inputFile);
    File outputFile = new File(gifFilePath);
    ImageIO.write(image, "GIF", outputFile);
  }
}



Hide the mouse cursor: use a transparent GIF as the cursor

   
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.image.MemoryImageSource;
public class Main {
  public static void main(String[] argv) throws Exception {
    int[] pixels = new int[16 * 16];
    Image image = Toolkit.getDefaultToolkit().createImage(
        new MemoryImageSource(16, 16, pixels, 0, 16));
    Cursor transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(
        image, new Point(0, 0), "invisibleCursor");
  }
}