实现一个GIF译码器 使用C 解码和显示GIF图像外文翻译资料

 2022-12-25 14:30:51

英语原文共 15 页,剩余内容已隐藏,支付完成后下载完整资料


实现一个GIF译码器

使用C 解码和显示GIF图像

Andrew S.Downs

andrew@downs.ws

摘要:许多客户端应用程序需要显示图像,尽管GIF格式已经过时,但是GIF格式仍然很受欢迎。可以解码gif的各种库,但有时对于定制应用程序,可能需要更小的内存空间,或者更好地控制进程。在本文中,我讨论了解码算法,以及在C 中设计和实现的方法,它可以插入到一个更大的图像处理架构中。

介绍:

GIF(图形交换格式)使用LZW(lempel-ziv-威尔士)压缩方案。编码和解码过程都构建了一个代码字典,表示图像数据中唯一的RGB颜色序列。字典没有显式地包含在图像数据中。一个编码器在读取未压缩数据时生成字典,而一个解码器在读取来自压缩流的代码时重新生成字典。当解析器检查数据流时,当它遇到一个新的序列时,它会将下一个代码写入字典并输入相应的颜色信息。如果使用树结构来保存代码,这可能是为了节省空间而需要的,那么解析器还需要保存对父代码的引用。

当库存在时,为什么还要编写一个解码器呢?一个原因是许多库都很大。您可能不希望加载或加载包含大量未使用功能的应用程序。或者你可能想要绕过一个特定的解码器实现,并以更快或更小的速度替代。或者,您可能只需要显示gif,但目标系统已知的内存或存储空间很少(可能是嵌入式系统)。无论什么原因,您可能会发现需要实现一个定制的GIF解码器。

文件格式

GIF文件格式是灵活的,允许多个图像以及可能是特定应用程序的扩展。但核心结构很简单。一个GIF文件从一个固定大小的区域开始,其中包含一个头部(带有GIF版本标识符)、屏幕描述符和全局颜色表数据。图像数据遵循颜色表。

头文件

图像类型由文件中的前6个字节标识,要么是“GIF87a”,要么是“GIF89b”。下面是c/c 的结构如何被声明为一个结构;

struct Header {

Byte signature[3];

Byte version[3];

} ;

注意,数据类型字节被定义为:

#define Byte unsigned char

在Mac OS工具箱中,类型字节已经被使用了很多年,但是它不是ANSI C标准的一部分。这个宏将字节等同于一个无符号字符,在ANSI C中定义为8位类型。因此,字节为引用这个应用程序中的无符号字符数据类型提供了一种简便的方法。

该标识符是逻辑屏幕描述符,它包含关于文件中图像所占用的整个区域的维度的信息(它们可能占据屏幕的不同部分)、各种标志和颜色信息。

struct LogicalScreenDescriptor {

Byte logicalScreenWidth[ 2 ];

Byte logicalScreenHeight[2];

Byte bitField;

Byte backgroundColor;

Byte pixelAspectRatio;

} ;

颜色是在一个颜色表中指定的,最多可达256色。图像可以分别定义单独的表或共享全局表。本文讨论的代码假设有一个全局颜色表。这里的颜色都是3字节的RGB格式。

struct ColorTableEntry {

Byte red;

Byte green;

Byte blue;

} ;

GIF87a定义了两个特定的标识符,它们标记了全局颜色表之后的各个部分。图像的开始由0x2c标记,图像的结束由0x3b。中间是压缩的数据流。

const Byte gTypeImageBlock = 0x2c;

const Byte gTypeTerminator = 0x3b;

GIF89b添加了扩展块,允许文本覆盖、动画帧和通用的和特定于应用程序的数据。每个块类型都是由0x21加上特定类型的值来标识的。这些扩展是在附带的代码中处理的,但是内容没有完成。

const Byte gTypeExtension = 0x21;

一个图像头在文件的每个图像前面。这为图像的左上角和图像的宽度和高度定义了x-y坐标。单个字节指定是否使用了本地颜色表(及其大小),以及图像数据是否相互交错。

struct ImageHeader {

Byte leftPosition[2];

Byte rightPosition[2];

Byte imageWidth[2];

Byte imageHeight[2];

Byte bitField;

} ;

代码字典

代码存储在物理数组中,但是逻辑结构是一棵树,每个节点可以通过一个父节点的父节点来跟踪它的沿袭。代码实际上是一个惟一的树条目序列。每个树条目都包含单独的RGB值和父索引。以下是一个条目的结构:

parentrsquo;s index. Here is the structure for an entry:

struct DictionaryTreeEntry {

Byte red;

Byte green;

Byte blue;

unsigned int parent;

} ;

数组被声明为:

struct DictionaryTreeEntry dictionaryTree[ 4096 ];

字典使用颜色表的颜色值进行初始化(在本例中是全局颜色表)。对于一个8位的代码大小,字典中第一个256个值是颜色表条目。有关设置字典的示例,请参见清单1。

void GifUtils::InitializeDictionary( int size ) {

// size determines the number of color table entries

int limit = ( int )pow( 2, size );

clearcode = ( int )pow( 2, size );

endcode = clearcode 1;

startcode = clearcode 2;

nextcode = startcode;

currCodeSize = size 1;

for ( int i = 0; i lt; limit; i ) {

// Copy the appropriate color table source byte to its

// dictionary counterpart.

dictionaryTree[ i ].blue =

*( ( globalColorTable ( i * 3 ) 2 ) );

dictionaryTree[ i ].green =

*( ( globalColorTable ( i * 3 ) 1 ) );

dictionaryTree[ i ].red =

*( ( globalColorTable ( i * 3 ) ) );

// Color table entries have no parent.

dictionaryTree[ i ].parent = 0;

}

stackPointer = 0;

}

图像解码

解码有几个步骤:读取下一个代码值,确定它是如何适应字典的,并在适当的时候编写一个RGB序列。

LZW中的图像数据由一系列用于构建字典的代码组成。字典中的每个代码条目或节点都映射到一个RGB值。除了颜色表项之外的每个节点都有一个父节点。颜色表条目是根节点。与特定节点相关联的RGB流不仅包括它的RGB值,而且包括每个父节点和包括根节点在内的每个父节点。

清单2所示的第一种方法处理LZW算法中比较棘手的部分。用于编码序列的比特数不是恒定的。它从初始代码大小字节中指定的值开始,该值遵循图像头部。随着越来越多的代码被添加到字典中,可用代码的数量逐渐减少到零,在这一点上,用来表示代码的位元的数量需要增加。阅读和字典的构建一直在继续,直到可用代码的数量再次耗尽,这时才会再次增加。对于没有太多颜色变化的小图像来说,这可能不是什么大问题,但是解码器必须准备好处理它。棘手的部分阅读变长值数据流包括记住剩下部分最后一个字节是未使用的,有多少位需要阅读来弥补当前代码之间的差异大小和数量的剩余部分,创建一个有效值的组合,并保存当前字节和未使用的比特数。

下面是代码构建的算法:

1。如果需要,请从图像文件中读取一个字节。加载一些或全部文件内容并从该流中读取会更快,但是这种方法更倾向于在速度上比临时存储大小更小。

numBytes = fread( amp;b1, 1, 1, inFile );

2。屏蔽掉所有未使用的部分,并将结果赋值为代码值。

retval = ( b1 amp; 0x03 );

3。保存任何剩余的碎片。这些需要添加到下一个迭代中读取的值。

prevValue = 0;

prevValue = ( b1 gt;gt; 2 );

4。还记得在阅读过程中留下了多少位元。

bitsRemaining = 6;

5。跟踪从文件读取的字节数。对于每个情况,这个值可能不会增加,这取决于是否可以在不读取文件的情况下构造下一个值。

bytesRead ;

下面的方法片段演示了该算法的一部分。这里的逻辑是作为一组开关结构被布局的。第一个内部案例包含与刚刚讨论的算法匹配的注释。这种方式更冗长,但希望更明显。

unsigned short GifUtils::ReadCode( FILE *inFile ) {

unsigned short retval = 0;

unsigned short tempShort = 0;

size_t numBytes = 0;

Byte b1 = 0, b2 = 0;

// Outer case depends on how many bits we need to construct a code.

switch ( currCodeSize ) {

case 2:

// Inner case depends on how many bits were left unused

// during the previous read.

switch ( bitsRemaining ) {

case 0:

// Read a byte from the image file.

numBytes = fread( amp;b1, 1, 1, inFile );

retval = ( b1 amp; 0x03 );

// Any remaining bits need to get added to the value read

// in the next iteration.

prevValue = 0;

prevValue = ( b1 gt;gt; 2 );

// Use this as the switch value.

bitsRemaining = 6;

// Are we at the end of the file?

bytesRead ;

break;

case 1:

numBytes = fread( amp;b1, 1, 1, inFile );

retval = ( ( b1 amp; 0x01 ) lt;lt; 1 ) | prevValue;

prevValue = 0;

prevValue = ( b1 gt;gt; 1 );

bitsRemaining = 7;

bytesRead ;

break;

case 2:

retval = prevValue;

prevValue = 0;

bitsRemaining = 0;

break;

case 3:

retval = ( prevValue amp; 0x03 );

prevValue = ( prevValue gt;gt; 2 );

bitsRemaining = 1;

break;

case 4:

retval = ( prevValue amp; 0x03 );

prevValue = ( prevValue gt;gt; 2 );

bitsRem

剩余内容已隐藏,支付完成后下载完整资料


资料编号:[28250],资料为PDF文档或Word文档,PDF文档可免费转换为Word

您需要先支付 30元 才能查看全部内容!立即支付

发小红书推广免费获取该资料资格。点击链接进入获取推广文案即可: Ai一键组稿 | 降AI率 | 降重复率 | 论文一键排版