JAVA实现Gif动画

GIF是一种图像文件格式,可以将多幅图像保存到一个文件中,形成动画。网上常见的动画文件都是GIF文件格式。适当地使用动画,可以使我们的网站或应用显得更加活泼,从而更具有吸引力。
通过开发应用,我们可以根据自己的需要,定制实现GIF动画的生成方式,本文提供一个GIF编码类,通过将该类嵌入您的应用中,轻松几部就可以实现GIF动画的生成。

使用方式

我们先看看如何使用

import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;

public class GifEncoderTest {

    public static void main(String[] argv) throws Exception {
        BufferedImage[] imgs = new BufferedImage[4];
        imgs[0] = ImageIO.read(new File("a1.png"));
        imgs[1] = ImageIO.read(new File("a2.jpg"));
        imgs[2] = ImageIO.read(new File("a3.jpg"));
        imgs[3] = ImageIO.read(new File("a4.jpg"));

        GifEncoder gif = new GifEncoder(300, 200, new java.io.FileOutputStream("D:\\eclipse-workspace\\image-toolkit\\images\\bbbb.gif"), true, true);
        for (BufferedImage img : imgs) {
            gif.addImage(img);
        }
        gif.setDelay(100);  //帧之间的延时,单位毫秒
        gif.encodeMultiple();
    }
}

其中GifEncoder就是我们需要使用的类,通过给该类添加图片,设置延时,就可以生成我么需要的动画。

GifEncoder构造函数的参数如下:

GifEncoder(int width, int height, OutputStream out, boolean interlace)

Width:最终生成的图片的宽度
Height:最终生成的图片的高度
Out: 输出流
Interlace: 是否采用交错编码方式

GIF编码类实现方式

GIF编码包含四个文件Pixel.java,IntHashtable.java,TrueTo256.java和GifEncoder.java
Pixel.java 实现单点的颜色值的设置或读取
IntHashtable.java 实现整型数据哈希表的操作
TrueTo256.java 实现真彩色到256色的转换
GifEncoder.java 实现Gif文件编码

Pixel.java代码

import java.awt.image.*;

public class Pixel {
  public int red;
  public int green;
  public int blue;
  public int alpha=0xFF;
  public double hue;
  public double saturation;
  public double luminosity;
  private int rgb;

  public Pixel() {
  }

  public void setRGB(int rgb) {
    red = (rgb & 0x00FF0000) / 0x00010000;
    green = (rgb & 0x0000FF00) / 0x00000100;
    blue = rgb & 0x000000FF;
    alpha = (rgb >> 24)&0x0ff;
    this.rgb = rgb;
  }

  public int getRGB() {
    rgb =  alpha<<24 | 0x00010000 * red | 0x00000100 * green | blue;
    return this.rgb;
  }

  public static void setRgb(BufferedImage image, int x, int y, int red, int green, int blue) {
    int rgb = 0xFF000000 | red * 0x00010000 | green * 0x00000100 | blue;
    image.setRGB(x, y, rgb);
  }

  public static int pixelIntensity(int rgb) {
    int red = (rgb&0x00FF0000)/0x00010000;
    int green = (rgb&0x0000FF00)/0x00000100;
    int blue = rgb&0x000000FF;
    return (int) (0.299 * red + 0.587 * green + 0.114 * blue + 0.5);
  }
}

IntHashtable.java 代码

import java.util.*;

public class IntHashtable extends Dictionary implements Cloneable {
    /// The hash table data.
    private IntHashtableEntry table[];

    /// The total number of entries in the hash table.
    private int count;

    /// Rehashes the table when count exceeds this threshold.
    private int threshold;

    /// The load factor for the hashtable.
    private float loadFactor;

    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 IntHashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public IntHashtable() {
        this(101, 0.75f);
    }

    public int size() {
        return count;
    }

    public boolean isEmpty() {
        return count == 0;
    }

    public synchronized Enumeration keys() {
        return new IntHashtableEnumerator(table, true);
    }

    public synchronized Enumeration elements() {
        return new IntHashtableEnumerator(table, false);
    }

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

    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 put(int key, Object value) {
        // Make sure the value is not null.
        if (value == null)
            throw new NullPointerException();

        // Makes sure the key is not already in the hashtable.
        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 the table if the threshold is exceeded.
            rehash();
            return put(key, value);
        }

        // Creates the new entry.
        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);
    }

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

TrueTo256.java 代码

import java.awt.image.BufferedImage;

public class TrueTo256 {
    public static int colorTransfer(int rgb) {
        int r = (rgb&0x0F00000)>>20;
        int g = (rgb&0x000F000)>>12;
        int b = (rgb&0x00000F0)>>4;
        return (r<<8|g<<4|b);
    }
    
    public static int colorRevert(int rgb) {
        int r = (rgb&0x0F00)<<12;
        int g = (rgb&0x000F0)<<8;
        int b = (rgb&0x00000F)<<4;
        return (r|g|b);
    }
    
    private static int getDouble(int a, int b) {
        int red = ((a&0x0F00)>>8) - ((b&0x0F00)>>8);
        int grn = ((a&0x00F0)>>4) - ((b&0x00F0)>>4);
        int blu = (a&0x000F) - (b&0x000F);
        return red*red + blu*blu + grn*grn;
    }
    public static int getSimulatorColor(int rgb, int[] rgbs, int m) {
        int r = 0;
        int lest = getDouble(rgb, rgbs[r]);
        for (int i=1; i < m; i++) {
            int d2 = getDouble(rgb, rgbs[i]);
            if (lest > d2) {
                lest = d2;
                r = i;
            }
        }
        return rgbs[r];
    }
    
    public static void transferTo256(int[][] rgbs) {
        int n = 4096;
        int m = 256;
        int[] colorV = new int[n];
        int[] colorIndex = new int[n];
        
        //初始化
        for (int i=0; i < n; i++) {
            colorV[i] = 0;
            colorIndex[i] = i;
        }
        
        //颜色转换
        for (int x = 0; x < rgbs.length; x++) {
            for (int y = 0; y < rgbs[x].length; y++) {
                rgbs[x][y] = colorTransfer(rgbs[x][y]);
                colorV[rgbs[x][y]]++;
            }
        }
        
        //出现频率排序
        boolean exchange;
        int r;
        for (int i=0; i < n; i++) {
            exchange = false;
            for (int j=n-2; j>=i; j--) {
                if (colorV[colorIndex[j+1]] > colorV[colorIndex[j]]) {
                    r = colorIndex[j];
                    colorIndex[j] = colorIndex[j+1];
                    colorIndex[j+1] = r;
                    exchange = true;
                }
            }
            if (!exchange) break;
        }
        
        //颜色排序位置
        for (int i=0; i < n; i++) {
            colorV[colorIndex[i]] = i;
        }
        
        for (int x = 0; x < rgbs.length; x++) {
            for (int y = 0; y < rgbs[x].length; y++) {
                if (colorV[rgbs[x][y]] >= m) {
                    rgbs[x][y] = colorRevert(getSimulatorColor(rgbs[x][y], colorIndex, m));
                } else {
                    rgbs[x][y] = colorRevert(rgbs[x][y]);
                }
            }
        }
    }
    
    public static void transferToRed(int[][] rgbs) {
        for (int x = 0; x < rgbs.length; x++) {
            for (int y = 0; y < rgbs[x].length; y++) {
                rgbs[x][y] = rgbs[x][y]&0x00FF0000;
            }
        }
    }
    
    public static void transferToGreen(int[][] rgbs) {
        for (int x = 0; x < rgbs.length; x++) {
            for (int y = 0; y < rgbs[x].length; y++) {
                rgbs[x][y] = rgbs[x][y]&0x00FF00;
            }
        }
    }
    
    public static void transferToBlue(int[][] rgbs) {
        for (int x = 0; x < rgbs.length; x++) {
            for (int y = 0; y < rgbs[x].length; y++) {
                rgbs[x][y] = rgbs[x][y]&0x00FF;
            }
        }
    }
    
    public static BufferedImage trueTo256(BufferedImage img) {
        int[][] rgbs = new int[img.getWidth()][img.getHeight()];
        BufferedImage cloneImg = new BufferedImage(img.getWidth(), img.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        for (int x=0; x < rgbs.length; x++) {
            for (int y=0; y < rgbs[x].length; y++) {
                rgbs[x][y] = img.getRGB(x, y);
            }
        }

        transferTo256(rgbs);
        
        for (int x=0; x < rgbs.length; x++) {
            for (int y=0; y < rgbs[x].length; y++) {
                cloneImg.setRGB(x, y, rgbs[x][y]);
            }
        }

        return cloneImg;
    }
}

GifEncoder.java 代码

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;

public class GifEncoder {
    private int width, height;
    private int[][] rgbPixels;
    private IntHashtable colorHash;
    private boolean interlace = false;
    private OutputStream out;
    private int sec = 50;
    private ArrayList images = new ArrayList();
    private boolean _init = false;

    private int count = 0;

    int Width, Height;
    boolean Interlace;
    int curx, cury;
    int CountDown;
    int Pass = 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;
        }
    }

    public GifEncoder() {

    }

    public int getDelay() {
        return sec;
    }

    public int getCount() {
        return count;
    }

    public ArrayList getImages() {
        return images;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public void init(int width, int height, OutputStream out, boolean interlace) throws IOException {
        this.out = out;
        this.interlace = interlace;
        this.width = width;
        this.height = height;
        this.Interlace = interlace;
        this.Width = width;
        this.Height = height;
        _init = true;
    }

    public GifEncoder(int width, int height, OutputStream out, boolean interlace) throws IOException {
        this.out = out;
        this.interlace = interlace;
        this.width = width;
        this.height = height;
        this.Interlace = interlace;
        this.Width = width;
        this.Height = height;
        _init = true;
    }

    public boolean isInit() {
        return this._init;
    }

    public void clearImage() {
        images.clear();
        this._init = false;
    }

    public void setOutputStream(OutputStream out) {
        this.out = out;
    }

    public void setDelay(int millisecond) {
        this.sec = millisecond/10;
    }

    public void setCount(int count) {
        this.count = count;
    }
    
    public BufferedImage scaleTo(BufferedImage image, int width, int height) {
        int pwidth = image.getWidth();
        int pheight = image.getHeight();

        BufferedImage bimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bimg.createGraphics();

        g2d.drawImage(image,  0, 0, width, height, 0, 0, pwidth, pheight, null);
        return bimg;
    }

    public void addImage(BufferedImage img) {
        if (img.getWidth() != width || img.getHeight() != height) {
            img = scaleTo(img, width, height);
        }
        images.add(TrueTo256.trueTo256(img));
    }

    public void encodeMultiple() throws IOException {
        encodeMultiple(images);
    }

    void GIFEncodeHeader(OutputStream outs, byte Background) throws IOException {
        // 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
        byte B = (byte) 0x00; // 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) ((8 - 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);
    }

    void GIFAppControl(OutputStream outs, int count) throws IOException {
        Putbyte((byte) 0x21, outs);
        Putbyte((byte) 0xff, outs);
        Putbyte((byte) 0x0b, outs);
        writeString(outs, "NETSCAPE2.0");
        Putbyte((byte) 0x03, outs);
        Putbyte((byte) 0x01, outs);
        Putbyte((byte) (0x00ff & count), outs);
        Putbyte((byte) (0xff00 & (count >> 4)), outs);
        Putbyte((byte) 0x00, outs);
    }

    void gifControl(OutputStream outs, int sec) throws IOException {
        Putbyte((byte) 0x21, outs);
        Putbyte((byte) 0xf9, outs);
        Putbyte((byte) 0x04, outs);
        Putbyte((byte) 0x08, outs);
        Putbyte((byte) (sec & 0x0ff), outs);
        Putbyte((byte) ((sec & 0x0ff00) >> 8), outs);
        Putbyte((byte) 0x40, outs);
        Putbyte((byte) 0, outs);
    }

    void gifEncodeBody(OutputStream outs, int Width, int Height, boolean Interlace, byte Background, int Transparent,
            int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue) throws IOException {
        int LeftOfs, TopOfs;
        int InitCodeSize;
        LeftOfs = TopOfs = 0;

        // The initial code size
        if (BitsPerPixel <= 1)
            InitCodeSize = 2;
        else
            InitCodeSize = BitsPerPixel;

        if (sec > 0) {
            gifControl(outs, sec);
        }

        // 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
        // Indicate that there is a global colour map
        byte 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);
        int ColorMapSize = 1 << BitsPerPixel;

        for (int 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) 0x0d, outs);
            Putbyte((byte) 0, outs);
            Putbyte((byte) 0, outs);
            Putbyte((byte) Transparent, outs);
            Putbyte((byte) 0, 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);
    }

    void GIFEncodeBottom(OutputStream outs) throws IOException {
        // Write the GIF file terminator
        Putbyte((byte) ';', outs);
    }

    private void encodeMultiple(ArrayList imgs) throws IOException {
        if (imgs.size() == 0)
            return;

        encodeHeader();
        for (int i = 0; i < imgs.size(); i++) {
            encodeInit((BufferedImage) imgs.get(i));
            encodeSingle();
        }
        GIFEncodeBottom(out);
    }

    public void encodeMultiple2(ArrayList imgs) throws IOException {
        if (imgs.size() == 0)
            return;
        BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = tmp.createGraphics();

        encodeHeader();
        for (int i = 0; i < imgs.size(); i++) {
            g2d.drawImage((BufferedImage) imgs.get(i), 0, 0, width, height, null);
            encodeInit(tmp);
            encodeSingle();
        }
        g2d.dispose();
        GIFEncodeBottom(out);
    }

    public void encodeMultiple(BufferedImage[] imgs) throws IOException {
        if (imgs.length == 0)
            return;

        encodeHeader();
        for (int i = 0; i < imgs.length; i++) {
            encodeInit(imgs[i]);
            encodeSingle();
        }
        GIFEncodeBottom(out);
    }

    public void encodeMultiple2(BufferedImage[] imgs) throws IOException {
        if (imgs.length == 0)
            return;
        BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = tmp.createGraphics();

        encodeHeader();
        for (int i = 0; i < imgs.length; i++) {
            g2d.drawImage(imgs[i], 0, 0, width, height, null);
            encodeInit(tmp);
            encodeSingle();
        }
        g2d.dispose();
        GIFEncodeBottom(out);
    }

    void encodeInit(BufferedImage img) throws IOException {
        cur_accum = 0;
        cur_bits = 0;
        Pass = 0;
        curx = 0;
        cury = 0;

        this.Interlace = true;

        CountDown = Width * Height;

        rgbPixels = new int[height][width];

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                rgbPixels[y][x] = img.getRGB(x, y);
            }
        }
    }

    void encodeHeader() throws IOException {
        GIFEncodeHeader(out, (byte) 0);
        GIFAppControl(out, count);
    }

    void encodeSingle() throws IOException {
        int transparentIndex = -1;
        int transparentRgb = -1;
        colorHash = new IntHashtable();
        int index = 0;
        for (int row = 0; row < height; ++row) {
            int rowOffset = row * width;
            for (int col = 0; col < width; ++col) {
                int rgb = rgbPixels[row][col];
                boolean isTransparent = ((rgb >>> 24) < 0x80);
                Pixel pixel = new Pixel();
                pixel.setRGB(rgb);
                /***
                 * int red = pixel.red/32; int green = pixel.green/16; int blue = pixel.blue/16;
                 * rgb = red * 32 | green * 5 | blue;
                 **/
                if (isTransparent) {
                    if (transparentIndex < 0) {
                        transparentIndex = index;
                        transparentRgb = rgb;
                    } else if (rgb != transparentRgb) {
                        rgbPixels[row][col] = rgb = transparentRgb;
                    }
                }
                rgbPixels[row][col] = rgb;
                GifEncoderHashitem item = (GifEncoderHashitem) colorHash.get(rgb);
                if (item == null) {
                    if (index >= 256)
                        throw new IOException("too many colors for a GIF");
                    item = new GifEncoderHashitem(pixel.getRGB(), 1, index, isTransparent);
                    ++index;
                    colorHash.put(rgb, item);
                } else
                    ++item.count;
            }
        }

        int logColors;
        if (index <= 2)
            logColors = 1;
        else if (index <= 4)
            logColors = 2;
        else if (index <= 16)
            logColors = 4;
        else
            logColors = 8;

        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();) {
            GifEncoderHashitem item = (GifEncoderHashitem) 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);
        }

        gifEncodeBody(out, width, height, interlace, (byte) 0, transparentIndex, logColors, reds, grns, blus);
    }

    byte GetPixel(int x, int y) throws IOException {
        GifEncoderHashitem item = (GifEncoderHashitem) colorHash.get(rgbPixels[y][x]);
        if (item == null)
            throw new IOException("color not found");
        return (byte) item.index;
    }

    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 . Lempel-Zim compression
    // based on "compress".

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

    static final int EOF = -1;

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

    // 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 ([email protected])

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

你可能感兴趣的:(JAVA实现Gif动画)