图形图像基础 之 gif介绍

一、GIF基础概念

gif—一种压缩的位图图形文件格式

GIF:Graphics Interchange Format,以8位色(即256种颜色)重现真彩色的图像。它实际上是一种压缩文档,采用LZW压缩算法进行编码,有效地减少了图像文件在网络上传输的时间。它是目前万维网广泛应用的网络传输图像格式之一,由Compu Serve公司推出。gif早期由于专利问题,引起部分开放源代码社群发起“Burn all GIFs”的运动抵制使用GIF格式,进而产生PNG文件标准,(当前gif的专利已经过期)PNG文件格式凭着其技术上的优势,已然跻身于网络上第三广泛应用格式。

gif图片的典型特性—压缩/多帧动画/支持alpha/256色

1、优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小;2、可插入多帧,从而实现动画效果。3、可设置透明色以产生对象浮现于背景之上的效果。4、由于采用了8位压缩,最多只能处理256种颜色,故不宜应用于真彩色图片。

gif与其他典型图形文件bmp、jpeg、png对比

bmp常见是非压缩的size最大但是显示简单,主要是最后用作本地存储 和 显示阶段-无需解压, 传输都会选择gif、png和jpeg。jpeg图片的优势是压缩比例大,而且支持每个颜色分量8bit甚至以上存储保证画面色彩还原,它由于存在转换到yuv域并进行下采样420,通过有损压缩算法 能得到比png更高的压缩效率,常见的大图都会选择jpeg而非png。png相对jpeg它的优势是包含alpha通道(可以做半透明效果,而jpeg是不存在的alpha通道),并且常用无损压缩,常见用在一些精细小图标,或者必须带上aplha透明度的图片的使用场景。png设计最初目标是替代gif,gif相对前面几种,它和png一样 带alpha通道并压缩,优势是支持多帧动画,缺点是最大256色用于画面质量要求低的场景,比如各种动态表情包。

libgif—一个广泛使用的C语言实现的gif读写库

giflib是一个使用最广泛的gif图片库,它用C语言实现,效率很高,功能很全;

二、GIF文件格式

GIF是按块划分存储的,包括控制块 Control Block(包含控制参数控制数据块行为)和数据块Data Sub-blocks(8-bit的字符流,大小从0到255个字节,第一个字节标识数据块内容size字节大小)两种。
GIF文件主要由以下几部分组成文件头File Header、GIF数据流GIF Data Stream和文件尾部Trailer三个部分。文件头包含GIF文件署名Signature和版本号Version;GIF数据流由控制标识符、图象块Image Block和其他的一些扩展块组成;文件终结器只有一个值为0x3B的字符(’;’)表示文件结束。
我们用下面来这个图片举例。
请添加图片描述
在这里插入图片描述

3.1 gif文件头File Header—署名和版本号,6字节,确认文件类型

文件头包含:1、GIF文件署名,3字节(就是GIF的ASCII码);2、版本号,3字节(87a-1987/5;89a-1989/7)。
在这里插入图片描述

3.2 gif数据流Data Stream

gif数据流由各种类型的块组成,介绍如下,其中部分块是可选的,主要在89a版本上得以支持。

3.2.1 逻辑屏幕标识符Logical Screen Descriptor—7字节

逻辑屏幕标识符:第一个gif数据块,定义了GIF分辨率、颜色深度、背景色以及有无全局颜色列表(Global Color Table)和颜色列表的索引数(Index Count)。
在这里插入图片描述
在这里插入图片描述
可见此图片,宽高都是0xc8=200;0xF7 有全局颜色表,色深8bit,没有排序,全局颜色列表数量256色;

3.2.2 全局颜色列表Global Color Table—任意字节,取决列表数目

在一张连续动态GIF里,每一帧之间信息差异不大,颜色是被大量重复使用的。全局颜色列表就是把图片中用到的颜色提取出来,组成一个调色盘,这样,在存储真正的图片点阵时,只需要存储每个点在调色盘里的索引值。如果调色盘放在文件头,作为所有帧公用的信息,就是全局调色盘,如果放在每一帧的帧信息中,就是局部调色盘。GIF格式允许两种调色盘同时存在,在没有局部调色盘的情况下,使用公共调色盘来渲染。
全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色列表索引条目由三个字节组成,按R、G、B的顺序排列
在这里插入图片描述
第一个点R、G、B各自8bit都是0,表示黑色。

3.2.3 图象标识符Image Descriptor—10字节

一个GIF文件内可以包含多幅图象,一幅图象结束之后紧接着下是一幅图象的标识符,图象标识符以0x2C(’,’)字符开始,定义紧接着它的图象的性质,包括图象相对于逻辑屏幕边界的偏移量、图象大小以及有无局部颜色列表和颜色列表大小。
在这里插入图片描述
在这里插入图片描述
没有任何x、y方向偏移,0xc8=200宽高都是200,没有局部颜色列表。

3.2.4 局部颜色列表Local Color Table

如果上面的局部颜色列表标志置位的话,则需要在这里(紧跟在图象标识符之后)定义一个局部颜色列表以供紧接着它的图象使用。局部颜色列表的排列方式和全局颜色列表一样。

3.2.5 基于颜色列表的图象数据Table-Based Image Data

图象数据由两部分组成:1、第一个字节表示 LZW编码长度LZW Minimum Code Size;2、后续内容 表示 图象数据Image Data,由一个或几个数据块Data Sub-blocks组成。

3.2.6 (可选)图形控制扩展Graphic Control Extension

89a版本,放在一个图象块(图象标识符)或文本扩展块的前面,用来控制跟在它后面的第一个图象(或文本)的渲染(Render)形式。

3.2.6 (可选)注释扩展Comment Extension

89a版本,可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据(7-bit ASCII字符),注释扩展并不影响对图象数据流的处理,解码器可以忽略。推荐放在数据流的开始或结尾。

3.2.6 (可选)图形控制扩展Graphic Control Extension

89a版本,用来绘制一个简单的文本图象,这一部分由用来绘制的纯文本数据(7-bit ASCII字符)和控制绘制的参数等组成。

3.2.7 (可选)应用程序扩展Application Extension

89a版本,应用程序可以在这里定义自己的标识、信息等,组成。

3.3 gif文件尾Trailer

这一部分0x3B的字符(’;’),块大小0字节,表示文件结束。
在这里插入图片描述

三、GIF压缩算法简介(待更新)

四、libgif库使用解码用例

4.1 源码下载与安装

1、源码下载:https://sourceforge.net/projects/giflib/;我下载的最新giflib-5.2.1版本。
2、源码编译安装:阅读源码包build.adoc确认安装流程:make check;make install;
install结果如下,关键部分:1、静态库到/ibgif.a和动态库libgif.so 2、安装了mannul手册 3、安装如gif2rgb等工具

make check
make install
// 部分打印略
install gif2rgb gifbuild giffix giftext giftool gifclrmp "/usr/local/bin"
install -d "/usr/local/include"
install -m 644 gif_lib.h "/usr/local/include"
install -d "/usr/local/lib"
install -m 644 libgif.a "/usr/local/lib/libgif.a"
install -m 755 libgif.so "/usr/local/lib/libgif.so.7.2.0"
ln -sf libgif.so.7.2.0 "/usr/local/lib/libgif.so.7"
ln -sf libgif.so.7 "/usr/local/lib/libgif.so"
install -d "/usr/local/share/man/man1"
install -m 644 doc/*.1 "/usr/local/share/man/man1"

4.2 gif解码到rgb数据的示例用例

先通过man giflib 确认库提供的各种功能支持,其中gif2rgb是我们最需要的。但这里我不使用gif2rgb工具,而是参考一下写法,目的是通过库写一个自己的调试简单sample出来。(giflib不像libjpeg和libpng一样有专门的example示例文件,需要自己配合gif_lib.h头文件 和 小工具源码进行简单分析)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gif_lib.h"

void save_rgb_to_file(int file_num, GifFileType *GifFile, ColorMapObject *ColorMap, char *data, int size) {
	int i, j;
	char filename[100] = {0};
	sprintf(filename, "output_rgb_num%d_w%d_h%d" , file_num,
		GifFile->SWidth, GifFile->SHeight);
    FILE *outfile = fopen(filename, "w+");
	if (outfile == NULL) {
        printf("out file open failed!\n");
        return;
    }
	unsigned char *Buffer, *BufferP;
	// 申请一行内存,由于是RGB888,因此每个像素3字节
	if ((Buffer = (unsigned char *) malloc(GifFile->SWidth * 3)) == NULL) {
		printf("malloc failed\n");
		return;
	}
	for (i = 0; i < GifFile->SHeight; i++) {
		char *GifRow = data + i * GifFile->SWidth * sizeof(GifPixelType); // 取每行地址
		for (j = 0, BufferP = Buffer; j < GifFile->SWidth; j++) { // 遍历每一行每个点
			GifColorType *ColorMapEntry = &ColorMap->Colors[GifRow[j]];
			*BufferP++ = ColorMapEntry->Red;
			*BufferP++ = ColorMapEntry->Green;
			*BufferP++ = ColorMapEntry->Blue;
		}
		if (fwrite(Buffer, GifFile->SWidth * 3, 1, outfile) != 1) { // 写入一行RGB数据
			printf("fwrite failed!\n");
			return;			
		}
	}
	free((char *) Buffer);
	fclose(outfile);
	return;
}
#define TEST {printf("----%s %d\n", __FUNCTION__, __LINE__);}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("please intput like: ./a.out xxx.gif\n");
		return -1;
    }

	int	i, j, Size, Row, Col, Width, Height, ExtCode, Count;
    GifRecordType RecordType = UNDEFINED_RECORD_TYPE;
    GifByteType *Extension = NULL;;
    GifRowType *ScreenBuffer = NULL;;
    GifFileType *GifFile = NULL;
	int InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should. */
	int InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
    int ImageNum = 0;
    ColorMapObject *ColorMap;
    int Error;
	char *outbuf = NULL;
	
	// 打开输入文件
	if ((GifFile = DGifOpenFileName(argv[1], &Error)) == NULL) {
	    printf("out file open failed!\n");
	    return -1;
	}	
    if (GifFile->SHeight == 0 || GifFile->SWidth == 0) {
	    printf("resolution error!\n");
	    return -1;
    }	

	// 申请输出内存指针数组(类似png解析,每行一个指针)
    if ((ScreenBuffer = (GifRowType *)malloc(GifFile->SHeight * sizeof(GifRowType))) == NULL) {
	    printf("malloc failed!\n");
	    return -1;		
	}
	// 申请输出的整块buffer,并初始化指针数组
	Size = GifFile->SHeight * GifFile->SWidth * sizeof(GifPixelType);
	printf("size %d sw %d sh%d!\n", Size, GifFile->SWidth, GifFile->SHeight);
	if ((outbuf = (char *)malloc(Size)) == NULL){
		printf("malloc row failed!\n");
	    return -1;				
	}
    for (i = 0; i < GifFile->SHeight; i++) {
		ScreenBuffer[i] = (GifRowType)(outbuf + i * GifFile->SWidth * sizeof(GifPixelType));
		if (i == 0) {
		    for (j = 0; j < GifFile->SWidth; j++) // 设置第一行背景色
				ScreenBuffer[0][j] = GifFile->SBackGroundColor;	
		} else { // 他行的内存,复制第一行内容,均为背景色
			memcpy(ScreenBuffer[i], ScreenBuffer[0], GifFile->SWidth * sizeof(GifPixelType));			
		}
    }

	// 开始遍历文件
    do {
		if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
			printf("DGifGetRecordType failed!\n");
			return -1;
		}
		switch (RecordType) {
			case IMAGE_DESC_RECORD_TYPE:
				if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
					printf("DGifGetImageDesc failed!\n");
					return -1;
				}
				Row = GifFile->Image.Top; /* Image Position relative to Screen. */
				Col = GifFile->Image.Left;
				Width = GifFile->Image.Width;
				Height = GifFile->Image.Height;
				printf("buf %p Image %d at (%d, %d) [%dx%d]\n", outbuf, ++ImageNum, Col, Row, Width, Height);
				// 存文件内容到outbuf
				if (GifFile->Image.Interlace) {
					/* 交叉型数据 Need to perform 4 passes on the images: */
					for (Count = i = 0; i < 4; i++)
						for (j = Row + InterlacedOffset[i]; j < Row + Height; j += InterlacedJumps[i]) {
							if (DGifGetLine(GifFile, &ScreenBuffer[j][Col], Width) == GIF_ERROR) {
								printf("DGifGetLine failed!\n");
								return -1;
							}
						}
				} else {
					for (i = 0; i < Height; i++) {
						if (DGifGetLine(GifFile, &ScreenBuffer[Row++][Col], Width) == GIF_ERROR) {
							printf("DGifGetLine failed!\n");
							return -1;
						}
					}
				}
				ColorMap = (GifFile->Image.ColorMap ? GifFile->Image.ColorMap : GifFile->SColorMap);
				// outbuf存到输出文件
				save_rgb_to_file(ImageNum, GifFile, ColorMap, outbuf, Size);
				break;
			case EXTENSION_RECORD_TYPE:
				/* Skip any extension blocks in file: */
				if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
					printf("DGifGetExtension failed!\n");
					return -1;
				}
				while (Extension != NULL) {
					if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
						printf("DGifGetExtensionNext failed!\n");
						return -1;
					}
				}
				break;
			case TERMINATE_RECORD_TYPE:
				break;
			default: /* Should be trapped by DGifGetRecordType. */
				break;
		}
    } while (RecordType != TERMINATE_RECORD_TYPE);

	// 释放内存
	free(ScreenBuffer);
    free(outbuf);
	// 关闭全部文件
    DGifCloseFile(GifFile, &Error);
	return 0;
}

编译与执行

gcc sample_gif.c -lgif
./a.out ./200px-Rotating_earth_large.gif
## 执行打印如下
buf 0xed0620 Image 1 at (0, 0) [200x200]
buf 0xed0620 Image 2 at (29, 15) [141x141]
buf 0xed0620 Image 3 at (28, 15) [142x141]
buf 0xed0620 Image 4 at (28, 15) [142x141]
buf 0xed0620 Image 5 at (29, 15) [141x141]
buf 0xed0620 Image 6 at (29, 15) [141x141]
buf 0xed0620 Image 7 at (29, 15) [141x141]
buf 0xed0620 Image 8 at (30, 15) [139x141]
buf 0xed0620 Image 9 at (29, 15) [140x141]
// 略
buf 0xed0620 Image 44 at (30, 15) [139x141]
## 执行结果如下
root@ubuntu:/home/study/giflib/gif_to_rgb# ls
200px-Rotating_earth_large.gif  output_rgb_num1_w200_h200   output_rgb_num30_w143_h142  output_rgb_num41_w141_h141
a.out                             output_rgb_num20_w140_h141  output_rgb_num31_w143_h142  output_rgb_num42_w141_h141
output_rgb_num10_w140_h141        output_rgb_num21_w142_h141  output_rgb_num32_w141_h141  output_rgb_num43_w140_h141
output_rgb_num11_w140_h141        output_rgb_num22_w142_h141  output_rgb_num33_w141_h141  output_rgb_num44_w139_h141
output_rgb_num12_w141_h141        output_rgb_num23_w141_h141  output_rgb_num34_w140_h141  output_rgb_num4_w142_h141
output_rgb_num13_w142_h141        output_rgb_num24_w140_h141  output_rgb_num35_w140_h141  output_rgb_num5_w141_h141
output_rgb_num14_w142_h141        output_rgb_num25_w140_h141  output_rgb_num36_w141_h141  output_rgb_num6_w141_h141
output_rgb_num15_w141_h141        output_rgb_num26_w140_h141  output_rgb_num37_w141_h141  output_rgb_num7_w141_h141
output_rgb_num16_w140_h141        output_rgb_num27_w140_h141  output_rgb_num38_w141_h141  output_rgb_num8_w139_h141
output_rgb_num17_w139_h141        output_rgb_num28_w141_h141  output_rgb_num39_w142_h142  output_rgb_num9_w140_h141
output_rgb_num18_w140_h141        output_rgb_num29_w142_h141  output_rgb_num3_w142_h141   sample_gif.c

结果查看,用工具随意打开两个图片,如下,说明解码正常:
在这里插入图片描述

五、GIF图片相关工具

ScreenToGif—一款很方便的gif录制编辑小工具

ScreenToGIf可以自己录制制作gif文件(点击录制,调整录制窗口,开始录制,录制完成,编辑删除无效帧,另存导出),也可以导入现有gif文件进行编辑或者帧导出;
在这里插入图片描述

参考

维基百科:https://chi.jinzhao.wiki/wiki/GIF
格式标准:https://www.w3.org/Graphics/GIF/spec-gif89a.txt
libgif源码:https://sourceforge.net/projects/giflib/
https://blog.csdn.net/wzy198852/article/details/17266507
浅析GIF格式图片的存储和压缩:https://www.cnblogs.com/qcloud1001/p/6647080.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值