CODE会说话--代码讲解GIF标准

GIF(Graphics Interchange Format,图形交换格式)文件是由 CompuServe公司开发的图形文件格式,版权所有,任何商业目的使用均须 CompuServe公司授权。

GIF图像是基于颜色列表的(存储的数据是该点的颜色对应于颜色列表的索引值),最多只支持8位(256色)。GIF文件内部分成许多存储块,用来存储多幅图像或者是决定图像表现行为的控制块,用以实现动画和交互式应用。GIF文件还通过LZW压缩算法压缩图像数据来减少图像尺寸。

下面,我们直接通过代码来解析GIF标准。


1、GIF署名(Signature)和版本号(Version)

GIF署名用来确认一个文件是否是GIF格式的文件, 这一部分由三个字符组成:"GIF";文件版本号 也是由三个字节组成,可以为"87a"或"89a"


    /*###################GIF署名(Signature)和版本号(Version)##################*/
	/*GIF署名用来确认一个文件是否是GIF格式的文件,
	 * 这一部分由三个字符组成:"GIF";文件版本号
	 * 也是由三个字节组成,可以为"87a"或"89a"*/
    protected void writeSignatureVersion() throws IOException
    {
    	writeString("GIF89a");
    }

2、逻辑屏幕标识符(Logical Screen Descriptor)

这一部分由7个字节组成,定义了GIF图像的大小(Logical Screen Width & Height)、 颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列 表(Global Color Table)和颜色列表的索引数(Index Count)

    /*##################逻辑屏幕标识符########################*/
    /*这一部分由7个字节组成,定义了GIF图像的大小(Logical Screen Width & Height)、
     * 颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列
     * 表(Global Color Table)和颜色列表的索引数(Index Count)*/
    protected void writeLogicalScreenDescriptor() throws IOException {        
        writeShort(width);		//逻辑屏幕宽度,像素数,定义GIF图像的宽度
        writeShort(height);		//逻辑屏幕高度,像素数,定义GIF图像的宽度
        
        /*全局颜色列表标志(Global Color Table Flag),
         * 当置位时表示有全局颜色列表,pixel值有意义。*/
        out.write((0x80 |         		
        0x70 | // cr - 颜色深度(Color ResoluTion),cr+1确定图像的颜色深度
        0x00 | // s - 分类标志(Sort Flag),如果置位表示全局颜色列表分类排列
        palSize)); // 全局颜色列表大小,pixel+1确定颜色列表的索引数(2的pixel+1次方
        out.write(0); // 背景颜色(在全局颜色列表中的索引,如果没有全局颜色列表,该值没有意义)
        out.write(0); // 像素宽高比(Pixel Aspect Radio)
    }

3、全局颜色列表(Global Color Table)

全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色列表索引条目由三个字节组成,按R、G、B的顺序排列

    /*########################全局颜色列表(Global Color Table)#######################*/
    /*全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色
     * 列表索引条目由三个字节组成,按R、G、B的顺序排*/
    protected void writeGlobalColorTable() throws IOException {
        out.write(colorTab, 0, colorTab.length);
        int n = (3 * 256) - colorTab.length;
        for (int i = 0; i < n; i++) {
            out.write(0);
        }
    }

4、图像标识符(Image Descriptor)

一个GIF文件内可以包含多幅图像,一幅图像结束之后紧接着下是一幅图像的标识符,图像标识符以0x2C(",")字符开始,定义紧接着它的图像的性质,包括图像相对于逻辑屏幕边界的偏移量、图像大小以及有无局部颜色列表和颜色列表大小,由10个字节组成

    /*###################图像标识符(Image Descriptor)#######################*/
    /*一个GIF文件内可以包含多幅图像,一幅图像结束之后紧接着下是一幅图像的标识符,
     * 图像标识符以0x2C(",")字符开始,定义紧接着它的图像的性质,包括图像相对于
     * 逻辑屏幕边界的偏移量、图像大小以及有无局部颜色列表和颜色列表大小,由10个
     * 字节组成*/
    protected void writeImageDescriptor() throws IOException {
        out.write(0x2c); 	// 图像标识符开始,固定值为0x2c
        writeShort(0); 		// X方向偏移量,必须限定在逻辑屏幕尺寸范围内
        writeShort(0);		// Y方向偏移量,必须限定在逻辑屏幕尺寸范围内
        writeShort(width);  // 图像宽度
        writeShort(height);	// 图像高度
        
        if (firstFrame) {
            // 第一帧不需要局部颜色列表标志
            out.write(0);
        } else {
        	writeLocalColorTableFlag();	//局部颜色列表标志
        }
    }

5、局部颜色列表标志(Local Color Table Flag)

    /*###################局部颜色列表标志(Local Color Table Flag)#######################*/
    protected void writeLocalColorTableFlag() throws IOException {
        out.write(0x80 | // 置位时标识紧接在图像标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图像使用;值为0时使用全局颜色列表,忽略pixel值。
                0 | // 交织标志(Interlace Flag),置位时图像数据使用交织方式排列,否则使用顺序排
                0 | // 分类标志(Sort Flag),如果置位表示紧跟着的局部颜色列表分类排列
                0 | // 保留,必须初始化为0
                palSize); // 局部颜色列表大小(Size of Local Color Table),pixel+1就为颜色列表的位数    	
    }


6、图形控制扩展(Graphic Control Extension)

这一部分是可选的(需要89a版本),可以放在一个图像块(图像标识符)或文本扩展块的前面,用来控制跟在它后面的第一个图像(或文本)的渲染(Render)形式

    /*###################图形控制扩展(Graphic Control Extension)##################*/
    /*这一部分是可选的(需要89a版本),可以放在一个图像块(图像标识符)或文本扩展块的前面,
     * 用来控制跟在它后面的第一个图像(或文本)的渲染(Render)形式*/
    protected void writeGraphicControlExtension() throws IOException {
        out.write(0x21); // 标识这是一个扩展块,固定值0x21
        out.write(0xf9); // 标识这是一个图形控制扩展块,固定值0xF9
        out.write(4); 	 // 块大小 - 不包括块终结器,固定值4
        int transp, disp;
        if (transparent == 0) {
            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 | // 处置方法(Disposal Method):指出处置图形的方法,当值为:  0 - 不使用处置方法; 1 - 不处置图形,把图形从当前位置移去;2 - 回复到背景色;  3 - 回复到先前状态; 4-7 - 自定义
                0 | // 用户输入标志(Use Input Flag):指出是否期待用户有输入之后才继续进行下去,置位表示期待,值否表示不期待。用户输入可以是按回车键、鼠标点击等,可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续
                transp); // 透明颜色标志(Transparent Color Flag):置位表示使用透明颜色
        writeShort(delay); // Delay Time - 单位1/100秒,如果值不为1,表示暂停规定的时间后再继续往下处理数据流
        out.write(transIndex); // 透明色索引值
        out.write(0); // 标识块终结,固定值0
    }

7、应用程序扩展(Application Extension)

这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等

    /*########################应用程序扩展(Application Extension)###########################*/
    /*这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等*/
    protected void writeApplicationExtension() throws IOException {
        out.write(0x21); // 标识这是一个扩展块,固定值0x21
        out.write(0xff); // 标识这是一个应用程序扩展块,固定值0xFF
        out.write(11); 	 // 块大小,固定值11
        writeString("ELONGGIF"); // Application Identifier - 用来鉴别应用程序自身的标识(8个连续ASCII字符)
        writeString("1.0"); //Application Authentication Code - 应用程序定义的特殊标识码(3个连续ASCII字符)
        
        /*应用程序自定义数据块 - 一个或多个数据块(Data Sub-Blocks)组成,保存应用程序自己定义的数据*/
        out.write(3); // sub-block size
        out.write(1); // loop sub-block id        
        writeShort(0); // loop count (extra iterations, 0=repeat forever)
        
        out.write(0); // 标识注释块结束,固定值0
    }
    
    

8、文件终结器(Trailer)

这一部分只有一个值为0的字节,标识一个GIF文件结束

    /*##############文件终结器(Trailer)###############*/
    /*这一部分只有一个值为0的字节,标识一个GIF文件结束*/
	protected void writeTrailer() throws IOException {
		out.write(0x3b); // 标识GIF文件结束,固定值0x3B
	}





最后贴出完整的代码:

public class GifEncoder {
    protected boolean closeStream;

    protected int colorDepth;

    protected byte[] colorTab;

    protected int delay = 0;

    protected int dispose;

    protected boolean firstFrame;

    protected int height;

    protected Bitmap image;

    protected byte[] indexedPixels;

    protected OutputStream out;

    protected int palSize;

    protected byte[] pixels;

    protected int repeat = -1;

    protected int sample;

    protected boolean sizeSet;

    protected boolean started;

    protected int transIndex;

    protected int transparent = 0;

    protected boolean[] usedEntry;

    protected int width;

    public GifEncoder() {
        boolean[] arrayOfBoolean = new boolean[256];
        this.usedEntry = arrayOfBoolean;
        this.palSize = 7;
        this.dispose = -1;
        this.closeStream = false;
        this.firstFrame = true;
        this.sizeSet = false;
        this.sample = 10;
    }

    public boolean addFrame(Bitmap paramBitmap, int index) {
        boolean isOk = true;
        if (paramBitmap == null || !started) {
            return false;
        }

        try {
            this.image = paramBitmap;
            if (!sizeSet) {
                setSize();
            }                
            long time01 = System.currentTimeMillis();
            getImagePixels();
            TimeUtil.logTime(time01, "getImagePixels");
            long time02 = System.currentTimeMillis();
            analyzePixels();
            TimeUtil.logTime(time02, "analyzePixels");
            if (firstFrame) {
                writeLogicalScreenDescriptor();   	//写逻辑屏幕标识符
                writeGlobalColorTable();		  	//全局颜色列表(Global Color Table)
                if (repeat >= 0)
                    writeApplicationExtension();  	//应用程序扩展(Application Extension)
            }
            writeGraphicControlExtension();			//图形控制扩展(Graphic Control Extension)
            writeImageDescriptor();					//图像标识符(Image Descriptor)

            if (!firstFrame)
                writeGlobalColorTable();			//全局颜色列表(Global Color Table)
            writePixels();
            this.firstFrame = false;

        } catch (IOException localIOException1) {
            isOk = false;

        }
        return isOk;

    }

    /*分析像素信息*/
    protected void analyzePixels() {
        int len = this.pixels.length;
        int nPix = len / 3;
        byte[] arrayOfByte1 = new byte[nPix];
        this.indexedPixels = arrayOfByte1;
        byte[] arrayOfByte2 = this.pixels;
        int k = this.sample;
        NeuQuant nq = new NeuQuant(arrayOfByte2, len, k);
        this.colorTab = nq.process();
        int l = 0;
        int i1 = this.colorTab.length;
        if (l >= i1) {
            l = 0;
        }

        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;
        }
        int k1 = 0;
        for (int i = 0; i < nPix; i++) {
            int index = nq.map(pixels[k1++] & 0xff, pixels[k1++] & 0xff, pixels[k1++] & 0xff);
            usedEntry[index] = true;
            indexedPixels[i] = (byte) index;
        }
        pixels = null;
        colorDepth = 8;
        palSize = 7;
        if (transparent != 0) {
            transIndex = findClosest(transparent);
        }

    }

    protected int findClosest(int paramInt) {

        if (colorTab == null) {
            return -1;
        }
        int r = Color.red(paramInt);
        int g = Color.green(paramInt);
        int b = Color.blue(paramInt);
        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;
    }

    public boolean finish() {
        if (!started)
            return false;
        boolean isOk = true;
        started = false;
        try {
        	writeTrailer();		//文件终结器(Trailer)            
            out.flush();		//将缓冲区清除,将数据写入到基础设备
            if (closeStream) {
                out.close();
            }
        } catch (IOException e) {
            isOk = false;
        }
        // reset for subsequent use
        transIndex = 0;
        out = null;
        image = null;
        pixels = null;
        indexedPixels = null;
        colorTab = null;
        closeStream = false;
        firstFrame = true;
        return isOk;
    }

    protected void getImagePixels() {
        int w = this.image.getWidth();
        int h = this.image.getHeight();
        this.pixels = new byte[w * h * 3];
        int[] arrayOfInt = new int[w * h];

        this.image.getPixels(arrayOfInt, 0, w, 0, 0, w, h);
        for(int i = 0; i < arrayOfInt.length; i++)
        {
            pixels[i * 3] = (byte) Color.blue(arrayOfInt[i]);
            pixels[i * 3 + 1] = (byte) Color.green(arrayOfInt[i]);
            pixels[i * 3 + 2] = (byte) Color.red(arrayOfInt[i]);
        }
    }

    public void setDelay(int ms) {
        delay = Math.round(ms / 10.0f);
    }

    public void setDispose(int code) {
        if (code >= 0) {
            dispose = code;
        }
    }

    public void setFrameRate(float fps) {
        if (fps != 0f) {
            delay = Math.round(100f / fps);
        }
    }

    public void setQuality(int quality) {
        if (quality < 1)
            quality = 1;
        sample = quality;
    }

    public void setRepeat(int iter) {
        if (iter >= 0) {
            repeat = iter;
        }
    }

    public void setSize() {
        if (started && !firstFrame)
            return;
        width = this.image.getWidth();
        height = this.image.getHeight();

        if (width < 1)
            width = 160;
        if (height < 1)
            height = 120;
        sizeSet = true;
    }

    public void setTransparent(int c) {
        this.transparent = c;
    }

    public boolean start(OutputStream os) {
        if (os == null)
            return false;
        boolean isOk = true;
        closeStream = false;
        out = os;

        try {
        	writeSignatureVersion(); // GIF署名(Signature)和版本号(Version)
        } catch (IOException e) {
            isOk = false;
        }
        return started = isOk;
    }

    public boolean start(String file) {
        boolean isOk = true;
        try {
            out = new BufferedOutputStream(new FileOutputStream(file));
            isOk = start(out);
            closeStream = true;
        } catch (IOException e) {
            isOk = false;
        }
        return started = isOk;
    }
    
    /*###################GIF署名(Signature)和版本号(Version)##################*/
	/*GIF署名用来确认一个文件是否是GIF格式的文件,
	 * 这一部分由三个字符组成:"GIF";文件版本号
	 * 也是由三个字节组成,可以为"87a"或"89a"*/
    protected void writeSignatureVersion() throws IOException
    {
    	writeString("GIF89a");
    }

    /*##################逻辑屏幕标识符########################*/
    /*这一部分由7个字节组成,定义了GIF图像的大小(Logical Screen Width & Height)、
     * 颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列
     * 表(Global Color Table)和颜色列表的索引数(Index Count)*/
    protected void writeLogicalScreenDescriptor() throws IOException {        
        writeShort(width);		//逻辑屏幕宽度,像素数,定义GIF图像的宽度
        writeShort(height);		//逻辑屏幕高度,像素数,定义GIF图像的宽度
        
        /*全局颜色列表标志(Global Color Table Flag),
         * 当置位时表示有全局颜色列表,pixel值有意义。*/
        out.write((0x80 |         		
        0x70 | // cr - 颜色深度(Color ResoluTion),cr+1确定图像的颜色深度
        0x00 | // s - 分类标志(Sort Flag),如果置位表示全局颜色列表分类排列
        palSize)); // 全局颜色列表大小,pixel+1确定颜色列表的索引数(2的pixel+1次方
        out.write(0); // 背景颜色(在全局颜色列表中的索引,如果没有全局颜色列表,该值没有意义)
        out.write(0); // 像素宽高比(Pixel Aspect Radio)
    }

    /*########################全局颜色列表(Global Color Table)#######################*/
    /*全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色
     * 列表索引条目由三个字节组成,按R、G、B的顺序排*/
    protected void writeGlobalColorTable() throws IOException {
        out.write(colorTab, 0, colorTab.length);
        int n = (3 * 256) - colorTab.length;
        for (int i = 0; i < n; i++) {
            out.write(0);
        }
    }

    /*###################图像标识符(Image Descriptor)#######################*/
    /*一个GIF文件内可以包含多幅图像,一幅图像结束之后紧接着下是一幅图像的标识符,
     * 图像标识符以0x2C(",")字符开始,定义紧接着它的图像的性质,包括图像相对于
     * 逻辑屏幕边界的偏移量、图像大小以及有无局部颜色列表和颜色列表大小,由10个
     * 字节组成*/
    protected void writeImageDescriptor() throws IOException {
        out.write(0x2c); 	// 图像标识符开始,固定值为0x2c
        writeShort(0); 		// X方向偏移量,必须限定在逻辑屏幕尺寸范围内
        writeShort(0);		// Y方向偏移量,必须限定在逻辑屏幕尺寸范围内
        writeShort(width);  // 图像宽度
        writeShort(height);	// 图像高度
        
        if (firstFrame) {
            // 第一帧不需要局部颜色列表标志
            out.write(0);
        } else {
        	writeLocalColorTableFlag();	//局部颜色列表标志
        }
    }
    
    /*###################局部颜色列表标志(Local Color Table Flag)#######################*/
    protected void writeLocalColorTableFlag() throws IOException {
        out.write(0x80 | // 置位时标识紧接在图像标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图像使用;值为0时使用全局颜色列表,忽略pixel值。
                0 | // 交织标志(Interlace Flag),置位时图像数据使用交织方式排列,否则使用顺序排
                0 | // 分类标志(Sort Flag),如果置位表示紧跟着的局部颜色列表分类排列
                0 | // 保留,必须初始化为0
                palSize); // 局部颜色列表大小(Size of Local Color Table),pixel+1就为颜色列表的位数    	
    }


    
    /*###################图形控制扩展(Graphic Control Extension)##################*/
    /*这一部分是可选的(需要89a版本),可以放在一个图像块(图像标识符)或文本扩展块的前面,
     * 用来控制跟在它后面的第一个图像(或文本)的渲染(Render)形式*/
    protected void writeGraphicControlExtension() throws IOException {
        out.write(0x21); // 标识这是一个扩展块,固定值0x21
        out.write(0xf9); // 标识这是一个图形控制扩展块,固定值0xF9
        out.write(4); 	 // 块大小 - 不包括块终结器,固定值4
        int transp, disp;
        if (transparent == 0) {
            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 | // 处置方法(Disposal Method):指出处置图形的方法,当值为:  0 - 不使用处置方法; 1 - 不处置图形,把图形从当前位置移去;2 - 回复到背景色;  3 - 回复到先前状态; 4-7 - 自定义
                0 | // 用户输入标志(Use Input Flag):指出是否期待用户有输入之后才继续进行下去,置位表示期待,值否表示不期待。用户输入可以是按回车键、鼠标点击等,可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续
                transp); // 透明颜色标志(Transparent Color Flag):置位表示使用透明颜色
        writeShort(delay); // Delay Time - 单位1/100秒,如果值不为1,表示暂停规定的时间后再继续往下处理数据流
        out.write(transIndex); // 透明色索引值
        out.write(0); // 标识块终结,固定值0
    }


    /*########################应用程序扩展(Application Extension)###########################*/
    /*这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等*/
    protected void writeApplicationExtension() throws IOException {
        out.write(0x21); // 标识这是一个扩展块,固定值0x21
        out.write(0xff); // 标识这是一个应用程序扩展块,固定值0xFF
        out.write(11); 	 // 块大小,固定值11
        writeString("ELONGGIF"); // Application Identifier - 用来鉴别应用程序自身的标识(8个连续ASCII字符)
        writeString("1.0"); //Application Authentication Code - 应用程序定义的特殊标识码(3个连续ASCII字符)
        
        /*应用程序自定义数据块 - 一个或多个数据块(Data Sub-Blocks)组成,保存应用程序自己定义的数据*/
        out.write(3); // sub-block size
        out.write(1); // loop sub-block id        
        writeShort(0); // loop count (extra iterations, 0=repeat forever)
        
        out.write(0); // 标识注释块结束,固定值0
    }
    
    
    /*##############文件终结器(Trailer)###############*/
    /*这一部分只有一个值为0的字节,标识一个GIF文件结束*/
	protected void writeTrailer() throws IOException {
		out.write(0x3b); // 标识GIF文件结束,固定值0x3B
	}

    /*################图像数据######################*/
    protected void writePixels() throws IOException {
        LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
        encoder.encode(out);
    }

    protected void writeShort(int value) throws IOException {
        out.write(value & 0xff);
        out.write((value >> 8) & 0xff);
    }

    protected void writeString(String s) throws IOException {
        for (int i = 0; i < s.length(); i++) {
            out.write((byte) s.charAt(i));
        }
    }
}






CODE会说话--代码讲解GIF标准

上一篇:Cocos2d: Get data from file(image.png) failed!


下一篇:hdu 1043 poj 1077 Eight Time (搜索&八数码)