音视频入门-17-GIF文件格式详解

* 音视频入门文章目录 *

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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容