file_gif.htm
来自「GIF解码以下是gif图形显示的源代码以下是gif图形显示的源代码」· HTM 代码 · 共 1,068 行 · 第 1/5 页
HTM
1,068 行
</tr>
<tr>
<td align="center" colspan="8" bgcolor="#BDCB85"><br>
...<br>
</td>
</tr>
<tr>
<td align="right">N+1</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><small>块终结</small></td>
<td><small>Block Terminator - 标识注释块结束,固定值0</small></td>
</tr>
</table>
<p><small>推荐:1.由于文本的字体(Font)和尺寸(Size)没有定义,解码器应该根据情况选择最合适的;</small><br>
<small>2.如果一个字符的值小于0x20或大于0xF7,则这个字符被推荐显示为一个空格(0x20);</small><br>
<small>3.为了兼容性,最好定义字符单元格的大小为8x8或8x16(宽度x高度)</small>。</p>
<p><small>应用程序扩展(Application Extension)</small><br>
<font color="#FF0000"><small>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</small></font></p>
<p><small>这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等,组成:</small><br>
</p>
<table border="0" width="100%" cellpadding="0">
<tr>
<td align="right">BYTE</td>
<td align="center">7</td>
<td align="center">6</td>
<td align="center">5</td>
<td align="center">4</td>
<td align="center">3</td>
<td align="center">2</td>
<td align="center">1</td>
<td align="center">0</td>
<td>BIT</td>
</tr>
<tr>
<td align="right">1</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><small>扩展块标识</small></td>
<td><small>Extension Introducer - 标识这是一个扩展块,固定值0x21</small></td>
</tr>
<tr>
<td align="right">2</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><small>图形控制扩展标签</small></td>
<td><small>Application Extension Label -
标识这是一个应用程序扩展块,固定值0xFF</small></td>
</tr>
<tr>
<td align="right">3</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><small>块大小</small></td>
<td><small>Block Size - 块大小,固定值11</small></td>
</tr>
<tr>
<td align="right">4</td>
<td align="center" colspan="8" rowspan="8" bgcolor="#BDCB85"><small>应用程序标识符</small></td>
<td rowspan="8"><small>Application Identifier - 用来鉴别应用程序自身的标识(8个连续ASCII字符)</small></td>
</tr>
<tr>
<td align="right">5</td>
</tr>
<tr>
<td align="right">6</td>
</tr>
<tr>
<td align="right">7</td>
</tr>
<tr>
<td align="right">8</td>
</tr>
<tr>
<td align="right">9</td>
</tr>
<tr>
<td align="right">10</td>
</tr>
<tr>
<td align="right">11</td>
</tr>
<tr>
<td align="right">12</td>
<td align="center" colspan="8" rowspan="3" bgcolor="#BDCB85"><small>应用程序鉴别码</small></td>
<td rowspan="3"><small>Application Authentication Code -
应用程序定义的特殊标识码(3个连续ASCII字符)</small></td>
</tr>
<tr>
<td align="right">13</td>
</tr>
<tr>
<td align="right">14</td>
</tr>
<tr>
<td align="right" rowspan="3">N</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><br>
<small>...</small><br>
</td>
<td rowspan="3"><small>应用程序自定义数据块 - 一个或多个数据块(<a
href="#数据块">Data Sub-Blocks</a>)组成,保存应用程序自己定义的数据</small></td>
</tr>
<tr>
<td align="center" colspan="8" bgcolor="#BDCB85"><br>
<small>应用程序数据</small><br>
</td>
</tr>
<tr>
<td align="center" colspan="8" bgcolor="#BDCB85"><br>
<small>...</small><br>
</td>
</tr>
<tr>
<td align="right">N+1</td>
<td align="center" colspan="8" bgcolor="#BDCB85"><small>块终结器</small></td>
<td><small>lock Terminator - 标识注释块结束,固定值0</small></td>
</tr>
</table>
<p><strong><small>文件结尾部分</small><br>
<font color="#FF0000"><small>~~~~~~~~~~~</small></font></strong></p>
<p><small>文件终结器(Trailer)</small><br>
<font color="#FF0000"><small>~~~~~~~~~~~~~~~~</small></font></p>
<p><small>这一部分只有一个值为0的字节,标识一个GIF文件结束.</small><br>
</p>
<table border="0" width="100%" cellpadding="0">
<tr>
<td align="right">BYTE</td>
<td align="center">7</td>
<td align="center">6</td>
<td align="center">5</td>
<td align="center">4</td>
<td align="center">3</td>
<td align="center">2</td>
<td align="center">1</td>
<td align="center">0</td>
<td></td>
</tr>
<tr>
<td align="right">1</td>
<td colspan="8" align="center" bgcolor="#BDCB85"><p align="center"><small>文件终结</small></td>
<td><small>GIF Trailer - 标识GIF文件结束,固定值0x3B</small></td>
</tr>
</table>
<p align="center"><a name="2.LZW算法和GIF数据压缩"><strong>2.LZW算法和GIF数据压缩</strong><br>
</a><font color="#FF0000">~~~~~~~~~~~~~~~~~~~~~~~~~~~</font></p>
<p><small> GIF文件的图象数据使用了可变长度编码的LZW压缩算法(Variable-Length_Code
LZW Compression),这是从LZW(Lempel Ziv Compression)压缩算法演变过来的,通过压缩原始数据的重复部分来达到减少文件大小的目的。</small></p>
<p><small>标准的LZW压缩原理:</small><br>
<small><font color="#FF0000">~~~~~~~~~~~~~~~~~~</font></small><br>
<small>先来解释一下几个基本概念:</small><br>
<small> LZW压缩有三个重要的对象:数据流(CharStream)、编码流(CodeStream)和编译表(String
Table)。在编码时,数据流是输入对象(图象的光栅数据序列),编码流就是输出对象(经过压缩运算的编码数据);在解码时,编码流则是输入对象,数据流是输出对象;而编译表是在编码和解码时都须要用借助的对象。</small><br>
<br>
<small><strong>字符</strong>(Character):最基础的数据元素,在文本文件中就是一个字节,在光栅数据中就是一个像素的颜色在指定的颜色列表中的索引值</small>;<br>
<small><strong>字符串</strong>(String):由几个连续的字符组成;</small><br>
<small><strong>前缀</strong>(Prefix):也是一个字符串,不过通常用在另一个字符的前面,而且它的长度可以为0;</small><br>
<small><strong>根</strong>(Root):单个长度的字符串;</small><br>
<small><strong>编码</strong>(Code):一个数字,按照固定长度(编码长度)从编码流中取出,编译表的映射值;</small><br>
<small><strong>图案</strong>:一个字符串,按不定长度从数据流中读出,映射到编译表条目.</small></p>
<p> <small>LZW压缩的原理:提取原始图象数据中的不同图案,基于这些图案创建一个编译表,然后用编译表中的图案索引来替代原始光栅数据中的相应图案,减少原始数据大小。看起来和调色板图象的实现原理差不多,但是应该注意到的是,我们这里的编译表不是事先创建好的,而是根据原始图象数据动态创建的,解码时还要从已编码的数据中还原出原来的编译表(GIF文件中是不携带编译表信息的),为了更好理解编解码原理,我们来看看具体的处理过程:</small></p>
<p><small>编码器(Compressor)</small><br>
<small><font color="#FF0000">~~~~~~~~~~~~~~~~</font></small></p>
<p><small> 编码数据,第一步,初始化一个编译表,假设这个编译表的大小是12位的,也就是最多有4096个单位,另外假设我们有32个不同的字符(也可以认为图象的每个像素最多有32种颜色),表示为a,b,c,d,e...,初始化编译表:第0项为a,第1项为b,第2项为c...一直到第31项,我们把这32项就称为根。</small><br>
<small> 开始编译,先定义一个前缀对象Current Prefix,记为[.c.],现在它是空的,然后定义一个当前字符串Current
String,标记为[.c.]k,[.c.]就为Current Prefix,k就为当前读取字符。现在来读取数据流的第一个字符,假如为p,那么Current
String就等于[.c.]p(由于[.c.]为空,实际上值就等于p),现在在编译表中查找有没有Current
String的值,由于p就是一个根字符,我们已经初始了32个根索引,当然可以找到,把p设为Current
Prefix的值,不做任何事继续读取下一个字符,假设为q,Current String就等于[.c.]q(也就是pq),看看在编译表中有没有该值,当然。没有,这时我们要做下面的事情:将Current
String的值(也就是pq)添加到编译表的第32项,把Current Prefix的值(也就是p)在编译表中的索引输出到编码流,修改Current
Prefix为当前读取的字符(也就是q)。继续往下读,如果在编译表中可以查找到Current
String的值([.c.]k),则把Current String的值([.c.]k)赋予Current Prefix;如果查找不到,则添加Current
String的值([.c.]k)到编译表,把Current Prefix的值([.c.])在编译表中所对应的索引输出到编码流,同时修改Current
Prefix为k
,这样一直循环下去直到数据流结束。伪代码看起来就像下面这样:</small></p>
<table border="1" width="100%" cellspacing="3" cellpadding="0" bordercolor="#FFFFFF"
bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#C0C0C0">
<caption><small>编码器伪代码</small></caption>
<tr>
<td width="100%"><blockquote>
<blockquote>
<p><small>Initialize String Table;<br>
[.c.] = Empty;<br>
[.c.]k = First Character in CharStream;<br>
while ([.c.]k != EOF )<br>
{<br>
if ( [.c.]k is in the StringTable)<br>
{<br>
[.c.] = [.c.]k;<br>
}<br>
else<br>
{<br>
add [.c.]k to the StringTable;<br>
Output the Index of [.c.] in the StringTable to the CodeStream;<br>
[.c.] = k;<br>
}<br>
[.c.]k = Next Character in CharStream;<br>
}</small><br>
<small>Output the Index of [.c.] in the StringTable to the CodeStream;</small></p>
</blockquote>
</blockquote>
</td>
</tr>
</table>
<p><small>来看一个具体的例子,我们有一个字母表a,b,c,d.有一个输入的字符流abacaba。现在来初始化编译表:#0=a,#1=b,#2=c,#3=d.现在开始读取第一个字符a,[.c.]a=a,可以在在编译表中找到,修改[.c.]=a;不做任何事继续读取第二个字符b,[.c.]b=ab,在编译表中不能找,那么添加[.c.]b到编译表:#4=ab,同时输出[.c.](也就是a)的索引#0到编码流,修改[.c.]=b;读下一个字符a,[.c.]a=ba,在编译表中不能找到:添加编译表#5=ba,输出[.c.]的索引#1到编码流,修改[.c.]=a;读下一个字符c,[.c.]c=ac,在编译表中不能找到:添加编译表#6=ac,输出[.c.]的索引#0到编码流,修改[.c.]=c;读下一个字符a,[.c.]c=ca,在编译表中不能找到:添加编译表#7=ca,输出[.c.]的索引#2到编码流,修改[.c.]=a;读下一个字符b,[.c.]b=ab,编译表的#4=ab,修改[.c.]=ab;读取最后一个字符a,[.c.]a=aba,在编译表中不能找到:添加编译表#8=aba,输出[.c.]的索引#4到编码流,修改[.c.]=a;好了,现在没有数据了,输出[.c.]的值a的索引#0到编码流,这样最后的输出结果就是:#0#1#0#2#4#0.</small></p>
<p><small>解码器(Decompressor)</small><br>
<small><font color="#FF0000">~~~~~~~~~~~~~~~~~~</font></small></p>
<p><small> 好了,现在来看看解码数据。数据的解码,其实就是数据编码的逆向过程,要从已经编译的数据(编码流)中找出编译表,然后对照编译表还原图象的光栅数据。</small><br>
<small> 首先,还是要初始化编译表。GIF文件的图象数据的第一个字节存储的就是LZW编码的编码大小(一般等于图象的位数),根据编码大小,初始化编译表的根条目(从0到2的编码大小次方),然后定义一个当前编码Current
Code,记作[code],定义一个Old Code,记作[old]。读取第一个编码到[code],这是一个根编码,在编译表中可以找到,把该编码所对应的字符输出到数据流,[old]=[code];读取下一个编码到[code],这就有两种情况:在编译表中有或没有该编码,我们先来看第一种情况:先输出当前编码[code]所对应的字符串到数据流,然后把[old]所对应的字符(串)当成前缀prefix
[...],当前编码[code]所对应的字符串的第一个字符当成k,组合起来当前字符串Current
String就为[...]k,把[...]k添加到编译表,修改[old]=[code],读下一个编码;我们来看看在编译表中找不到该编码的情况,回想一下编码情况:如果数据流中有一个p[...]p[...]pq这样的字符串,p[...]在编译表中而p[...]p不在,编译器将输出p[...]的索引而添加p[...]p到编译表,下一个字符串p[...]p就可以在编译表中找到了,而p[...]pq不在编译表中,同样将输出p[...]p的索引值而添加p[...]pq到编译表,这样看来,解码器总比编码器<strong>『</strong>慢一步』,当我们遇到p[...]p所对应的索引时,我们不知到该索引对应的字符串(在解码器的编译表中还没有该索引,事实上,这个索引将在下一步添加),这时需要用猜测法:现在假设上面的p[...]所对应的索引值是#58,那么上面的字符串经过编译之后是#58#59,我们在解码器中读到#59时,编译表的最大索引只有#58,#59所对应的字符串就等于#58所对应的字符串(也就是p[...])加上这个字符串的第一个字符(也就是p),也就是p[...]p。事实上,这种猜测法是很准确(有点不好理解,仔细想一想吧)。上面的解码过程用伪代码表示就像下面这样:</small></p>
<table border="1" width="100%" cellspacing="3" cellpadding="0" bordercolor="#FFFFFF"
bordercolorlight="#000000" bordercolordark="#FFFFFF" bgcolor="#C0C0C0">
<caption><small>解码器伪代码</small></caption>
<tr>
<td width="100%"><blockquote>
<p><small>Initialize String Table;<br>
[code] = First Code in the CodeStream;<br>
Output the String for [code] to the CharStream;</small><br>
<small>[old] = [c
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?