* 音视频入门文章目录 *
图像互换格式主要分为两个版本,即图像互换格式 87a 和图像互换格式 89a。
图像互换格式 87a:是在 1987 年制定的版本。
图像互换格式 89a:是在 1989 年制定的版本。在这个版本中,为图像互换格式文档扩充了图形控制区块、备注、说明、应用程序接口等四个区块,并提供了对透明色和多帧动画的支持。
现在我们一般所说的 GIF 动画都是指 89a 的格式。
GIF 包含的数据块:
文件头(Header)
逻辑屏幕标识符(Logical Screen Descriptor)
全局颜色表(Global Color Table)
图形控制扩展(Graphic Control Extension)
图像标识符(Image Descriptor)
局部颜色表(Local Color Table)
基于颜色表的图像数据(Image Data)
Plain Text Extension
Application Extension
Comment Extension
文件结尾(Trailer)
本文所有分析,都是基于下面这张 GIF 图片。
用来分析的样例图片
十六进制编辑器
GIF 的前 6 个字节内容是 GIF 的署名和版本号。
我们可以通过前 3 个字节判断文件是否为 GIF 格式,后 3 个字节判断 GIF 格式的版本:
逻辑屏幕标识符配置了 GIF 一些全局属性,我们通过读取解析它,获取 GIF 全局的一些配置。
逻辑屏幕标识符(7 个字节):
屏幕逻辑宽度:定义了 GIF 图像的像素宽度,大小为 2 字节;
屏幕逻辑高度:定义了 GIF 图像的像素高度,大小为 2 字节;
打包值,大小为 1 字节
背景颜色:背景颜色在全局颜色列表中的索引(PS:是索引而不是 RGB 值,所以如果没有全局颜色列表时,该值没有意义),大小为 1 字节;
像素宽高比:全局像素的宽度与高度的比值,大小为 1 字节;
从图中可以看出,这张 GIF 图片:
PS: Glide 中在读取了全局的宽高之后,忽略了颜色深度和分类标志,像素宽高比也只是读取,后续并没有使用到。
全局颜色表,在逻辑屏幕标识之后,每个颜色索引由三字节组成,按 RGB 顺序排列。
在 (2) 逻辑屏幕标识符(Logical Screen Descriptor)
中得到,全局颜色表大小是 8 个颜色,每个颜色占 3 字节(R、G、B)。
接下来出现的是 21
FF
,特定于应用程序的信息,这个并没有太大用处。唯一已知的公共文件是 Netscape 2.0 扩展(如下所述),用于循环动画GIF文件。
Netscape 2.0 循环块扩展必须立即出现在逻辑屏幕描述符的全局颜色表之后。它有19个字节长。
byte 1 : 33 (hex 0x21) GIF Extension code
byte 2 : 255 (hex 0xFF) Application Extension Label
byte 3 : 11 (hex 0x0B) Length of Application Block
(eleven bytes of data to follow)
bytes 4 to 11 : "NETSCAPE"
bytes 12 to 14 : "2.0"
byte 15 : 3 (hex 0x03) Length of Data Sub-Block
(three bytes of data to follow)
byte 16 : 1 (hex 0x01)
bytes 17 to 18 : 0 to 65535, an unsigned integer in
little-endian byte format. This specifies the
number of times the loop should
be executed.
byte 19 : 0 (hex 0x00) a Data Sub-Block Terminator.
Application Extension 这 19 个字节基本上目前所有 GIF 都一样。
在 89a 版本,GIF 添加了图形控制扩展块。放在一个图象块(图象标识符)的前面,用来控制它后面的第一个图象的显示。
处置方法(Disposal Method):指出处置图形的方法:
用户输入可以是按回车键、鼠标点击等,可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续。
透明颜色标志(Transparent Color Flag):置位表示使用透明颜色。
从图中可以看出,这张 GIF 图片:
接下来出现的是 21
FE
,这允许你将 ASCII 文本嵌入到 GIF 文件,有时被用来图像描述、图像信贷或其他人类可读的元数据,如图像捕获的 GPS 定位。
下面是这张图片的 Comment Extension:
一个 GIF 文件中可以有多个图像块,每个图像块就会有图像标识符,描述了当前帧的一些属性。下面我们来看看图像标识符中包含的一些信息。
图像标识符以 ‘,’ (0x2c) 作为开始标志。接着定义了当前帧的偏移量和宽高。
最后 5 个标志的意义分别为:
从图中可以看出,这张 GIF 图片:
如果有局部颜色表,则跟 (3) 全局颜色表(Global Color Table)
一样的格式。
接下来就是图像数据(已使用 LZW 算法压缩,解压后才是真正的 基于颜色表的图像数据
)。
数据的第一个字节表示 LZW 编码初始表大小的位数,用于使用 LZW 算法解压数据。
后面的是图像数据块:
0
,表示数据结束了如上图所示:
最后一步,我们将使用 LZW 算法解压图像数据块,并根据颜色表还原出整张图像(GIF 的一帧)的 RGB 文件。
需要删除标蓝色以外的所有字节,保存为rainbow-compressed.gif.frame
。
这个特性不起作用; 浏览器和图片处理应用程序,如 Photoshop 忽略它, GIFLIB 并不试图解释它。
标识 GIF 文件结束,固定值 0x3B。
当解析程序读到 0x3B 时,文件终结。
根据 (9) 基于颜色表的图像数据(Image Data)
,可以得到 GIF 一帧图像的数据(已使用 LZW 算法压缩)。
文 件 名:rainbow-compressed.gif.frame
文件大小:3428字节
这里直接使用 github.com/jefftime/lzw 这个库。
#include "stdio.h"
#include "stdlib.h"
#include "lzw/src/lzw.h"
int main () {
// LZW 编码初始表大小的位数:3
unsigned char code_size = 3;
// GIF 一帧图像的数据压缩文件(rainbow-compressed.gif.frame)大小
long total_bytes;
// GIF 一帧图像的数据压缩数据
unsigned char *img_compressed;
// GIF 一帧图像的数据解压后的数据
unsigned char *img;
// GIF 一帧图像的数据解压后大小
unsigned long decompressed_size;
FILE *gif_compressed_frame = fopen("/Users/staff/Desktop/rainbow-compressed.gif.frame", "rb+");
fseek(gif_compressed_frame, 0L, SEEK_END);
total_bytes = ftell(gif_compressed_frame);
fseek(gif_compressed_frame, 0L, SEEK_SET);
printf("Gif 一帧压缩文件大小:%li\n", total_bytes);
img_compressed = malloc((unsigned long) total_bytes);
fread(img_compressed, total_bytes, 1, gif_compressed_frame);
// 进行 LZW 解压
lzw_decompress(
code_size,
total_bytes,
img_compressed,
&decompressed_size,
&img
);
printf("Gif 一帧解压文件大小:%li\n", decompressed_size);
FILE *gif_decompressed_frame = fopen("/Users/staff/Desktop/rainbow-decompressed.gif.frame", "wb+");
fwrite(img, decompressed_size, 1, gif_decompressed_frame);
fflush(gif_decompressed_frame);
free(img_compressed);
free(img);
fclose(gif_compressed_frame);
fclose(gif_decompressed_frame);
return 0;
}
解压后得到解压文件:rainbow-decompressed.gif.frame
解压后文件大小:490000字节 (700x700x1)
解压后文件中每一个字节代表颜色表的一个颜色索引
#include "stdio.h"
#include "stdlib.h"
#include "lzw/src/lzw.h"
// 颜色表
uint32_t rainbowColors[] = {
0XFF0000, // 赤
0X00FF00, // 绿
0XFFA500, // 橙
0XFFFF00, // 黄
0X0000FF, // 蓝
0X007FFF, // 青
0X8B00FF, // 紫
0X000000 // 黑
};
int main () {
......
FILE *gif_frame_rgb = fopen("/Users/staff/Desktop/rainbow-decompressed.gif.frame.rgb", "wb+");
for(int i = 0; i < decompressed_size; i++) {
// 颜色索引值
unsigned char color_index = img[i];
// 根据颜色索引取出颜色表中的颜色
uint32_t color_rgb = rainbowColors[color_index];
// 当前颜色 R 分量
uint8_t R = (color_rgb & 0xFF0000) >> 16;
// 当前颜色 G 分量
uint8_t G = (color_rgb & 0x00FF00) >> 8;
// 当前颜色 B 分量
uint8_t B = color_rgb & 0x0000FF;
fputc(R, gif_frame_rgb);
fputc(G, gif_frame_rgb);
fputc(B, gif_frame_rgb);
}
fflush(gif_frame_rgb);
......
}
这一步,得到了 GIF 一帧图像的 RGB 文件:rainbow-decompressed.gif.frame.rgb
GIF 一帧图像的 RGB 文件大小为:1470000 字节(700x700x3)
ffplay -f rawvideo -pixel_format rgb24 -s 700x700 rainbow-decompressed.gif.frame.rgb
代码:
audio-video-blog-demos
参考资料:
What’s In A GIF
Gif 89a specification
GIF 格式解析
GIF 图片原理和储存结构
Gif 图片格式完全理解
GIF 文件格式详解
GIF 图形文件格式文档
GIF 文件格式详解
LZW 压缩算法——简明原理与实现
github.com/jefftime/lzw
https://github.com/jcraveiro
LZW compressor / decompressor
ASCII Codes Table
内容有误?联系作者: