⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 lzwdecoder.cs

📁 c#常用类库大全
💻 CS
字号:
using System.IO;

/// <summary>
/// Gif使用的可变长度的LZW压缩算法解码器
/// </summary>
public class LZWDecoder
{
    #region 变量
    /// <summary>
    /// GIF规定编码最大为12bit,最大值即为4096
    /// </summary>
    protected static readonly int MaxStackSize = 4096;
    protected Stream stream;
    #endregion

    #region 构造函数
    /// <summary>
    /// 构造函数
    /// </summary>
    public LZWDecoder(Stream stream)
    {
        this.stream = stream;
    }
    #endregion

    #region 私有方法
    /// <summary>
    /// 读取数据段
    /// </summary>
    byte[] ReadData()
    {
        int blockSize = Read();
        return ReadByte(blockSize);
    }

    /// <summary>
    /// 读取指定长度的字节字节
    /// </summary>
    byte[] ReadByte(int len)
    {
        byte[] buffer = new byte[len];
        stream.Read(buffer, 0, len);
        return buffer;
    }

    /// <summary>
    /// 读取一个字节
    /// </summary>
    int Read()
    {
        return stream.ReadByte();
    }
    #endregion

    #region 调用方法
    /// <summary>
    /// LZW压缩算法解码器
    /// </summary>
    /// <param name="width">长度</param>
    /// <param name="height">高度</param>
    /// <param name="stream">包含编码流的数据流</param>
    /// <returns>原始数据流</returns>
    public byte[] DecodeImageData(int width, int height, Stream stream)
    {
        int NullCode = -1;
        int pixelCount = width * height;//获取原图像的像素数,公式为 像素数 = 图像长度*图像高度
        byte[] pixels = new byte[pixelCount];
        int dataSize = Read();          //图像编码流的第一个字节(byte)存放的是数据位大小,在gif通常为1,4,8
        int codeSize = dataSize + 1;    //编码位大小,根据lzw算法的要求,编码位的大小 = 数据位大小+1 ,针对gif,有如下对应关系 1->3 4->5 ->9,而最大的codeSize为12
        int clearFlag = 1 << dataSize;  //在lzw算法有两个特殊标记,clearFlag为其中的清除标记,此后的编码将重头再来,这样做可以防止编码位无限增大
        int endFlag = clearFlag + 1;    //lzw算法两个特殊标记之一,endFlag为结束标记,表示一次编码的结束  endFlag=clearFlag+1
        int available = endFlag + 1;    //初始的可用编码大小,因为0-(clear-1)为元数据,所以均可用,不必研究,此处从能形成压缩的编码开始算起

        int code = NullCode;     //用于存储当前的编码值
        int old_code = NullCode; //用于存储上一次的编码值
        int code_mask = (1 << codeSize) - 1;//表示编码的最大值,如果codeSize=5,则code_mask=31
        int bits = 0;//在编码流中数据的保存形式为byte,而实际编码过程中是找实际编码位来存储的,比如当codeSize=5的时候,那么实际上5bit的数据就应该可以表示一个编码,这样取出来的1个字节就富余了3个bit,这3个bit用于和第二个字节的后两个bit进行组合,再次形成编码值,如此类推

        int[] prefix = new int[MaxStackSize];          //用于保存前缀的集合
        int[] suffix = new int[MaxStackSize];          //用于保存后缀
        int[] pixelStatck = new int[MaxStackSize + 1]; //用于临时保存数据流

        int top = 0;
        int count = 0; //在下面的循环中,每次会获取一定量的编码的字节数组,而处理这些数组的时候需要1个个字节来处理,count就是表示还要处理的字节数目
        int bi = 0;    //count表示还剩多少字节需要处理,而bi则表示本次已经处理的个数
        int i = 0;     //i代表当前处理得到像素数

        int data = 0;          //表示当前处理的数据的值
        int first = 0;         //一个字符串重的第一个字节
        int inCode = NullCode; //在lzw中,如果认识了一个编码所代表的数据entry,则将编码作为下一次的prefix,此处inCode代表传递给下一次作为前缀的编码值

        //先生成元数据的前缀集合和后缀集合,元数据的前缀均为0,而后缀与元数据相等,同时编码也与元数据相等
        for (code = 0; code < clearFlag; code++)
        {
            //前缀初始为0
            prefix[code] = 0;
            //后缀=元数据=编码
            suffix[code] = (byte)code;
        }

        byte[] buffer = null;
        while (i < pixelCount)
        {
            //最大像素数已经确定为pixelCount = width * width
            if (top == 0)
            {
                if (bits < codeSize)
                {
                    //如果当前的要处理的bit数小于编码位大小,则需要加载数据
                    if (count == 0)
                    {
                        //如果count为0,表示要从编码流中读一个数据段来进行分析
                        buffer = ReadData();
                        count = buffer.Length;
                        if (count == 0)
                        {
                            //再次想读取数据段,却没有读到数据,此时就表明已经处理完了
                            break;
                        }
                        //重新读取一个数据段后,应该将已经处理的个数置0
                        bi = 0;
                    }
                    //获取本次要处理的数据的值
                    data += buffer[bi] << bits;//此处为何要移位呢,比如第一次处理了1个字节为176,第一次只要处理5bit就够了,剩下3bit留给下个字节进行组合。也就是第二个字节的后两位+第一个字节的前三位组成第二次输出值
                    bits += 8; //本次又处理了一个字节,所以需要+8
                    bi++;      //将处理下一个字节
                    count--;   //已经处理过的字节数+1
                    continue;
                }

                //如果已经有足够的bit数可供处理,下面就是处理过程
                code = data & code_mask; //获取data数据的编码位大小bit的数据
                data >>= codeSize;       //将编码数据截取后,原来的数据就剩下几个bit了,此时将这些bit右移,为下次作准备
                bits -= codeSize;        //同时需要将当前数据的bit数减去编码位长,因为已经得到了处理。

                //下面根据获取的code值来进行处理
                if (code > available || code == endFlag)
                {
                    //当编码值大于最大编码值或者为结束标记的时候,停止处理
                    break;
                }
                if (code == clearFlag)
                {
                    //如果当前是清除标记,则重新初始化变量,好重新再来
                    codeSize = dataSize + 1;
                    //重新初始化最大编码值
                    code_mask = (1 << codeSize) - 1;
                    //初始化下一步应该处理得编码值
                    available = clearFlag + 2;
                    //将保存到old_code中的值清除,以便重头再来
                    old_code = NullCode;
                    continue;
                }

                //下面是code属于能压缩的编码范围内的的处理过程
                if (old_code == NullCode)
                {
                    //如果当前编码值为空,表示是第一次获取编码
                    pixelStatck[top++] = suffix[code];//获取到1个数据流的数据
                    //本次编码处理完成,将编码值保存到old_code中
                    old_code = code;
                    //第一个字符为当前编码
                    first = code;
                    continue;
                }
                inCode = code;
                if (code == available)
                {
                    //如果当前编码和本次应该生成的编码相同
                    pixelStatck[top++] = (byte)first;//那么下一个数据字节就等于当前处理字符串的第一个字节
                    code = old_code; //回溯到上一个编码
                }
                while (code > clearFlag)
                {
                    //如果当前编码大于清除标记,表示编码值是能压缩数据的
                    pixelStatck[top++] = suffix[code];
                    code = prefix[code];//回溯到上一个编码
                }
                first = suffix[code];
                if (available > MaxStackSize)
                {
                    //当编码最大值大于gif所允许的编码(4096)最大值的时候停止处理
                    break;
                }

                //获取下一个数据
                pixelStatck[top++] = suffix[code];
                //设置当前应该编码位置的前缀
                prefix[available] = old_code;
                //设置当前应该编码位置的后缀
                suffix[available] = first;
                //下次应该得到的编码值
                available++;
                if (available == code_mask + 1 && available < MaxStackSize)
                {
                    //增加编码位数
                    codeSize++;
                    //重设最大编码值
                    code_mask = (1 << codeSize) - 1;
                }
                //还原old_code
                old_code = inCode;
            }
            //回溯到上一个处理位置
            top--;
            //获取元数据
            pixels[i++] = (byte)pixelStatck[top];
        }
        return pixels;
    }
    #endregion
}

//LZW数据压缩算法的原理分析:http://www.cnblogs.com/jillzhang/archive/2006/11/06/551298.html

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -