gif图片介绍和LZW算法说明

                                     GIF图片介绍和LZW算法说明

1. GIF格式

 

GIF图像的文件格式如下:


gif图片介绍和LZW算法说明

 

 

重点字段介绍:

逻辑屏幕标志符:包含了逻辑屏幕的长宽,是否包含全局颜色列表,背景色等。

全局颜色列表:包含了图片中使用的所有颜色,而图片的数据使用全局颜色列表中的索引代替。

图像块:gif的数据是由数据块组成,每个数据块的长度最大为255

 

2. LZW算法

全称为“串表压缩算法”,LZW代表了研发该算法的三个人(坑爹啊,还以为是英文缩写)。属于字典编码的一种,最大的优点在于压缩后的数据不需要额外携带字典就可以解压。GIF采用LZW算法进行编码的。

 

2.1 首先介绍几个相关术语:

数据流:需要进行编码的数据串,GIF中指像素的颜色值。LZW编码时的输入

编码流:数据流编码的结果。LZW编码时的输出

编译表:编码时候的字典表,是动态建立的。

字符:数据流中最基本的单位,GIF中一个像素的颜色在颜色列表中的序号。

字符串:由连续字符组成的,前缀和后缀一起代表了字符串

前缀:也是字符串,但可以为空。具体解释见下文

后缀:也是字符串,一个字符就是后缀,可以为空。具体解释见下文

 

2.2 编码流程

 

首先举一个简单的例子,有一个数据流为:abcbabbac

       我们为了和数据流的字符进行区别,我们采用1,2,3这样的数字进行编码。数据流中可能出现的字符为,a~z,所以我们初始化编译表,用1~26,代表a~z单个字符。所以对于其它的字符串只能从27进行编码。初始化前缀为空,后缀也为空,编码流为空,编译表为1~26和a~z的对应。

       1.2.1 从数据流取字符a作为后缀,前缀为空,字符串为a,在编译表中存在,为1,字符串变前缀。前缀:a;后缀:空;字符串:a;编码流:空;编译表:单个字符的对应(后面不再显示)

       1.2.2 取字符b作为后缀,字符串为:ab,编译表中不存在,定义ab为27,前缀输出,后缀变前缀。前缀:b;后缀:空;字符串:b;编码流:1;编译表:27=ab

       1.2.3 取字符c作为后缀,字符串为:bc,编译表不存在,定义bc为28,前缀输出,后缀变前缀。前缀:c;后缀:空;字符串:c;编码流:1-2;编译表:27=ab   28=bc

       1.2.4 取字符b作为后缀,字符串为:cb,编译表不存在,定义cb为29,前缀输出,后缀变前缀。前缀:b;后缀:空;字符串:b;编码流:1-2-3;编译表:27=ab   28=bc 29=cb

       1.2.5 取字符a作为后缀,字符串为:ba,编译表不存在,定义ba为30,前缀输出,后缀变前缀。前缀:a;后缀:空;字符串:a;编码流:1-2-3-2;编译表:27=ab   28=bc 29=cb 30=ba

       1.2.6 取字符b作为后缀,字符串为:ab,编译表存在,为27,字符串变前缀。前缀:ab;后缀:空;字符串:ab;编码流:1-2-3-2;编译表:27=ab   28=bc 29=cb 30=ba

       1.2.7 取字符b作为后缀,字符串为:abb,编译表不存在,定义abb为31,前缀输出,后缀变前缀。前缀:b;后缀:空;字符串:b;编码流:1-2-3-2-27;编译表:27=ab   28=bc 29=cb 30=ba 31=abb

       1.2.8 取字符a作为后缀,字符串为:ba,编译表存在,为30,字符串变前缀。前缀:ba;后缀:空;字符串:ba;编码流:1-2-3-2-27;编译表:27=ab   28=bc 29=cb 30=ba 31=abb

       1.2.9 取字符c作为后缀,字符串为:bac,编译表不存在,定义bac为32,前缀输出,后缀变前缀。前缀:c;后缀:空;字符串:c;编码流:1-2-3-2-27-30;编译表:27=ab   28=bc 29=cb 30=ba 31=abb 32=bac

       1.2.10 输入流没有字符,将前缀c输出,最终编码流为:1-2-3-2-27-30-3

能够看到原来的9个字符被压缩成7个字符。

 

GIF是基于颜色列表的图片结构,GIF的数据对应的是每个点的颜色在颜色列表的索引,LZW编码是对该索引进行的编码。在GIF格式的图片中,对LZW做了一些改变:

       1. 首先定义编码长度,gif中最大编码长度为12位,能表示的最大数字为4096,因此,编译表中序号不能超过4096.

        2. gif最大支持256色,因此gif编码长度为9~12位。采用变长编码,到1023,再编码就改为10位,以此类推。

        3. gif定义了clear code,清除标记为原始数据长度+1,比如gif最大值为255,那么清除标记为256. 当一个编译的时候,如果编译表达到了最大值,比如4096,就要插入一个clear code,即可以清空编译表,重新开始编译。

        4. gif定义了结束标记end,end为清除标记+1, gif中为257,当gif的一张图片编码数据结束后插入end,表示图片结束。

 

GIF图片编码举例:

输入流:10 21 22 26 10 21 22 10 26 

编译表:1~255,256(clear code),257(end)

 

输入 前缀 后缀 字符串 编译表 输出
10   10 ( ,10)    
21 10 21 (10, 21) 258=(10, 21) 10
22 21 22 (21, 22) 259=(21, 22) 21
26 22 26 (22, 26) 260=(22, 26) 22
10 26 10 (26, 10) 261=(26, 10) 26
21 10 21 (10, 21)    
22 258 22 (258, 22) 262=(258, 22) 258
10 22 10 (22. 10) 263=(22, 10) 22
26 10 26 (10, 26) 264=(10, 26) 10

 

最后的输出流为:10 21 22 26 258 22 10 26

具体流程图为:


gif图片介绍和LZW算法说明
 

1.2 解码流程

 

对于1.1中第一个例子,压缩后的编码为1-2-3-2-27-30-3。初始化后的编码表为1~26对应a~z。

        1.2.1 读入值1,编译表中存在,输出a,输入做前缀,字符串为a,也在编译表中,前缀为a

        1.2.2 读入值2,编译表中存在,输出b,,将b作为后缀,字符串为ab,不在编译表中,定义27=ab,输入做前缀,前缀为b

        1.2.3 读入值3,编译表中存在,输出c,将c作为后缀,字符串为bc,不在编译表中,定义28=bc,输入做前缀,前缀为c

        1.2.4 读入值2,编译表中存在,输出b,将b作为后缀,字符串为cb,不在编译表中,定义29=cb,输入做前缀,前缀为b

        1.2.5 读入值27,编译表中存在,输出ab,将a作为后缀,字符串为ba,不在编译表中,定义30=ba,输入做前缀,前缀为ab

        1.2.6 读入值30,编译表中存在,输出ba,将b作为后缀,字符串为abb,在编译表中,输入做前缀为ba

        1.2.7 读入值3,编译表中存在,输出c,将c作为后缀,字符串为bac,不在编译表中,定义32=bac,前缀为c

 

解码后的输出为:abcbabbac

 

上述第二个例子同理,不再赘述。

 

如果读入值30后,编译表中不存在,则将上次输入的值+该值的第一个字符输出,将该输出定义为30

 

 流程图为:


gif图片介绍和LZW算法说明
 

3. GraphicsMagic

 

在使用中发现GM对于有些gif文件无法解析

 

3.1 bug分析

 

GM解析gif图片的部分代码:

 

for (x=0; x < (long) image->columns; )
    {
      if (top_stack == pixel_stack)
        {
          if (bits < code_size)
            {
              /*
                Load bytes until there is enough bits for a code.
              */
              if (count == 0)
                {
                  /*
                    Read a new data block.
                  */
                  count=ReadBlobBlock(image,packet);
                  if (count == 0)
                    break;
                  c=packet;
                }
              datum+=(unsigned long) (*c) << bits;
              bits+=8;
              c++;
              count--;
              continue;
            }
          /*
            Get the next code.
          */
          code=(long) (datum & code_mask);
          datum>>=code_size;
          bits-=code_size;
          /*
            Interpret the code
          */
          if (code > available)
            {
              status=MagickFail;
              break;
            }
          if (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)
            {
              *top_stack++=suffix[code];
              old_code=code;
              first=(unsigned char) code;
              continue;
            }
          in_code=code;
          if (code >= available)
            {
              *top_stack++=first;
              code=old_code;
            }
          while (code >= clear)
          {
            if ((top_stack-pixel_stack) >= MaxStackSize)
              {
                status=MagickFail;
                break;
              }
            *top_stack++=suffix[code];
            code=prefix[code];
          }
          if (status == MagickFail)
            break;
          first=suffix[code];
          /*
            Add a new string to the string table,
          */
          if (available >= MaxStackSize)
            {
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Excessive LZW string data "
                                    "(string table overflow)");
              status=MagickFail;
              break;
            }

            *top_stack++=first;		
            prefix[available]=(short) old_code;
            suffix[available]=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_stack--;
      index=(*top_stack);
      VerifyColormapIndex(image,index);
      indexes[x]=index;
      *q=image->colormap[index];
      q->opacity=(Quantum)
        (index == opacity ? TransparentOpacity : OpaqueOpacity);
      x++;
      q++;
    }

 

结果发现对于有些gif文件,使用gm进行解析会出错,通过debug发现错误信息为:

 

Excessive LZW string data (string table overflow)

 可知当available>4096时会出现该错误。原因是对于有些gif文件没有严格遵守gif格式,导致解码表超过4096。

 

3.2 解决方案

将上述红色的代码改为:

if (available < MaxStackSize) 
{
		  		
		prefix[available]=(short) old_code;
                suffix[available]=first;
                available++;
		if (available >= code_mask + 1 && code_size < 12U)
		{
			code_size++;
		}
}

 将蓝色的代码去掉。

这样对于超出解码表的像素不再进行解析。