音视频入门文章目录

GIF 文件格式解析

图像互换格式主要分为两个版本,即图像互换格式 87a 和图像互换格式 89a。
图像互换格式 87a:是在 1987 年制定的版本。
图像互换格式 89a:是在 1989 年制定的版本。在这个版本中,为图像互换格式文档扩充了图形控制区块、备注、说明、应用程序接口等四个区块,并提供了对透明色和多帧动画的支持。

现在我们一般所说的 GIF 动画都是指 89a 的格式。

GIF File Format

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)

(0) 准备 GIF 图片 & 十六进制查看工具

本文所有分析,都是基于下面这张 GIF 图片。

用来分析的样例图片

用来分析的样例图片

十六进制编辑器

(1) 文件头(Header)

GIF 的前 6 个字节内容是 GIF 的署名和版本号。
我们可以通过前 3 个字节判断文件是否为 GIF 格式,后 3 个字节判断 GIF 格式的版本:

  • 87a:是在 1987 年制定的版本
  • 89a:是在 1989 年制定的版本

文件头(Header)

(2) 逻辑屏幕标识符(Logical Screen Descriptor)

逻辑屏幕标识符配置了 GIF 一些全局属性,我们通过读取解析它,获取 GIF 全局的一些配置。

逻辑屏幕标识符(Logical Screen Descriptor)

逻辑屏幕标识符(7 个字节):

  • 屏幕逻辑宽度:定义了 GIF 图像的像素宽度,大小为 2 字节;
  • 屏幕逻辑高度:定义了 GIF 图像的像素高度,大小为 2 字节;
  • 打包值,大小为 1 字节

    • m - 全局颜色表标志(Global Color Table Flag),当置位时表示有全局颜色列表,pixel 值有意义;
    • cr - 颜色深度(Color ResoluTion),cr+1 确定图象的颜色深度;
    • s - 分类标志(Sort Flag),如果置位表示全局颜色列表分类排列;
    • pixel - 全局颜色列表大小,pixel+1 确定颜色列表的索引数(2^(pixel+1));
  • 背景颜色:背景颜色在全局颜色列表中的索引(PS:是索引而不是 RGB 值,所以如果没有全局颜色列表时,该值没有意义),大小为 1 字节;
  • 像素宽高比:全局像素的宽度与高度的比值,大小为 1 字节;

逻辑屏幕标识符(Logical Screen Descriptor

从图中可以看出,这张 GIF 图片:

  • 宽度:700
  • 高度:700
  • 有全局颜色表
  • 颜色深度 8
  • 全局颜色表大小 8

PS: Glide 中在读取了全局的宽高之后,忽略了颜色深度和分类标志,像素宽高比也只是读取,后续并没有使用到

(3) 全局颜色表(Global Color Table)

全局颜色表,在逻辑屏幕标识之后,每个颜色索引由三字节组成,按 RGB 顺序排列。

(2) 逻辑屏幕标识符(Logical Screen Descriptor) 中得到,全局颜色表大小是 8 个颜色,每个颜色占 3 字节(R、G、B)。

全局颜色表(Global Color Table)

全局颜色表-颜色

(4) Application Extension

接下来出现的是 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.

(4) Application Extension

Application Extension 这 19 个字节基本上目前所有 GIF 都一样。

(5) 图形控制扩展(Graphic Control Extension)

在 89a 版本,GIF 添加了图形控制扩展块。放在一个图象块(图象标识符)的前面,用来控制它后面的第一个图象的显示。

(5) 图形控制扩展(Graphic Control Extension)

处置方法(Disposal Method):指出处置图形的方法:

  • 0 - 不使用处置方法
  • 1 - 不处置图形,把图形从当前位置移去
  • 2 - 回复到背景色
  • 3 - 回复到先前状态
  • 4-7 - 自定义用户输入标志(Use Input Flag):指出是否期待用户有输入之后才继续进行下去,置位表示期待,值否表示不期待。

用户输入可以是按回车键、鼠标点击等,可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续。

透明颜色标志(Transparent Color Flag):置位表示使用透明颜色。

图形控制扩展-Hex

从图中可以看出,这张 GIF 图片:

  • 不使用处置方法
  • 不使用透明色
  • 后面的图像延迟 50 (单位:1/100 秒)

(6) Comment Extension

接下来出现的是 21 FE,这允许你将 ASCII 文本嵌入到 GIF 文件,有时被用来图像描述、图像信贷或其他人类可读的元数据,如图像捕获的 GPS 定位。

(6) Comment Extension

下面是这张图片的 Comment Extension:

Comment Extension Hex

(7) 图像标识符(Image Descriptor)

一个 GIF 文件中可以有多个图像块,每个图像块就会有图像标识符,描述了当前帧的一些属性。下面我们来看看图像标识符中包含的一些信息。

图像标识符(Image Descriptor)

图像标识符以 ',' (0x2c) 作为开始标志。接着定义了当前帧的偏移量和宽高。

最后 5 个标志的意义分别为:

  • m - 局部颜色表标志(Local Color Table Flag)

置位时标识紧接在图象标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图象使用;值否时使用全局颜色列表,忽略 pixel 值。

  • i - 交织标志(Interlace Flag),置位时图象数据使用交织方式排列,否则使用顺序排列。
  • s - 分类标志(Sort Flag),如果置位表示紧跟着的局部颜色列表分类排列.
  • r - 保留,必须初始化为 0.
  • pixel - 局部颜色表大小(Size of Local Color Table),pixel+1 就为颜色表的大小

Image Descriptor Hex

从图中可以看出,这张 GIF 图片:

  • 没有局部颜色表
  • 顺序排列
  • 局部颜色表大小为 0

(8) 局部颜色表(Local Color Table)

如果有局部颜色表,则跟 (3) 全局颜色表(Global Color Table) 一样的格式。

(9) 基于颜色表的图像数据(Image Data)

接下来就是图像数据(已使用 LZW 算法压缩,解压后才是真正的 基于颜色表的图像数据 )。

数据的第一个字节表示 LZW 编码初始表大小的位数,用于使用 LZW 算法解压数据。

后面的是图像数据块:

  • 每个数据块第一个字节表示数据块大小(不包括这个字节)
  • 数据块后面的一个字节表示后续数据块大小
  • 当数据块后面的一个字节是 0 ,表示数据结束了

基于颜色表的图像数据

如上图所示:

  • LZW 编码初始表大小的位数:3
  • 标蓝色的所有字节就是完整图像块数据
最后一步,我们将使用 LZW 算法解压图像数据块,并根据颜色表还原出整张图像(GIF 的一帧)的 RGB 文件。
需要删除标蓝色以外的所有字节,保存为 rainbow-compressed.gif.frame

(10) Plain Text Extension

这个特性不起作用; 浏览器和图片处理应用程序,如 Photoshop 忽略它, GIFLIB 并不试图解释它。

(11) 文件结尾(Trailer)

标识 GIF 文件结束,固定值 0x3B。

当解析程序读到 0x3B 时,文件终结。

根据图像数据块 & 颜色表还原图像

根据 (9) 基于颜色表的图像数据(Image Data) ,可以得到 GIF 一帧图像的数据(已使用 LZW 算法压缩)。

文 件 名:rainbow-compressed.gif.frame
文件大小:3428字节

LZW 解压

这里直接使用 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)
解压后文件中每一个字节代表颜色表的一个颜色索引

还原出 RGB 文件

#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)

查看还原出来的 RGB 文件

ffplay -f rawvideo -pixel_format rgb24 -s 700x700 rainbow-decompressed.gif.frame.rgb

gif-one-frame-rgb-file-preview.jpg


代码:
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

内容有误?联系作者:

联系作者



binglingziyu
3 声望4 粉丝