GIF 編碼知識

GIF 包含的數據塊:
-
文件頭(Header)
-
邏輯屏幕標識符(Logical Screen Descriptor)
-
全局顏色表(Global Color Table)
-
Application Extension
-
Comment Extension
-
圖形控制擴展(Graphic Control Extension)
-
圖像標識符(Image Descriptor)
-
局部顏色表(Local Color Table)
-
基於顏色表的圖像數據(Image Data)
-
Plain Text Extension
-
文件結尾(Trailer)
GIF 編碼步驟
今天的目標是做出一張尺寸 700x700、7 個顏色畫面切換的 GIF 動畫。

文件頭(Header)
GIF 的前 6 個字節內容是 GIF 的署名和版本號。有兩個版本 GIF87a GIF89a,GIF89a 版本才有多幀動畫,所有這里使用 89a 版本。
示例代碼:
// GIF 文件頭,6 個字節內容是 GIF 的署名和版本號
uint8_t gif_header[] = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
fwrite(gif_header, 6, 1, gif_file);
邏輯屏幕標識符(Logical Screen Descriptor)
從上一篇 音視頻入門-17-GIF文件格式詳解 我們知道:
邏輯屏幕標識符(7 個字節):
-
屏幕邏輯寬度:2 字節;
-
屏幕邏輯高度:2 字節;
-
打包值,大小為 1 字節
- m - 全局顏色表標志,1 bit;
- cr - 顏色深度,3 bit;(x: 可忽略)
- s - 分類標志, 1 bit; (x: 不使用,設為 0)
- pixel - 全局顏色列表大小,3 bit;
-
背景顏色索引: 1 字節;
-
像素寬高比: 1 字節;(x: 不使用,設為 0)
示例代碼:
// 邏輯屏幕標識符
uint16_t gif_width = 700;
uint16_t gif_height = 700;
// 0xF2 = 1 1 1 1 0 0 1 0
uint8_t gif_logical_screen_pack_byte = 0xF2;
uint8_t gif_bg_color_index = 0;
uint8_t gif_pixel_aspect = 0;
fputc(gif_width >> 0, gif_file); // width low 8
fputc(gif_width >> 8, gif_file); // width high 8
fputc(gif_height >> 0, gif_file); // height low 8
fputc(gif_height >> 8, gif_file); // height high 8
fputc(gif_logical_screen_pack_byte, gif_file);
fputc(gif_bg_color_index, gif_file);
fputc(gif_pixel_aspect, gif_file);
全局顏色表(Global Color Table)
每個顏色索引由三字節組成,按 RGB 順序排列。
由 【邏輯屏幕標識符】可知,顏色的索引數(2^(pixel+1))是 2 的倍數,如果圖片顏色數目不夠要補足。
比如,我們的圖片用了 7 個顏色,顏色索引數是 8,所以最后再加一個顏色(占位,不使用)。
示例代碼:
// 顏色表
uint32_t rainbowColors[] = {
0XFF0000, // 赤
0XFFA500, // 橙
0XFFFF00, // 黃
0X00FF00, // 綠
0X007FFF, // 青
0X0000FF, // 藍
0X8B00FF, // 紫
0X000000 // 黑
};
// 全局顏色表、
for(int i = 0; i < 8; i++) {
// 根據顏色索引取出顏色表中的顏色
uint32_t color_rgb = rainbowColors[i];
// 當前顏色 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_file);
fputc(G, gif_file);
fputc(B, gif_file);
}
Application Extension
Application Extension 這 19 個字節基本上 GIF 都一樣。
0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00
代表的內容是 NETSCAPE2.0
示例代碼:
// Application Extension
uint8_t gif_application_extension[] = {0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00};
fwrite(gif_application_extension, 19, 1, gif_file);
Comment Extension
這里允許你將 ASCII 文本嵌入到 GIF 文件,有時被用來圖像描述、圖像信貸或其他人類可讀的元數據,如圖像捕獲的 GPS 定位。
0x21, 0xFE, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x7A, 0x67, 0x69, 0x66, 0x2E, 0x63, 0x6F, 0x6D, 0x20, 0x47, 0x49, 0x46, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x72, 0x00
代表的內容是 Created with ezgif.com GIF maker
示例代碼:
// Comment Extension
// Created with ezgif.com GIF maker
uint8_t gif_comment_extension[] = {0x21, 0xFE, 0x20, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x65, 0x7A, 0x67, 0x69, 0x66, 0x2E, 0x63, 0x6F, 0x6D, 0x20, 0x47, 0x49, 0x46, 0x20, 0x6D, 0x61, 0x6B, 0x65, 0x72, 0x00};
fwrite(gif_comment_extension, 36, 1, gif_file);
圖形控制擴展(Graphic Control Extension)
我們的 GIF 不使用處置方法 不使用透明色 圖像延遲 50。
所以,這里就是 0x21, 0xF9, 0x04, 0x00, 0x32, 0x00, 0xFF, 0x00。
示例代碼:
// 圖形控制擴展
uint8_t gif_graphic_control_extension[] = {0x21, 0xF9, 0x04, 0x00, 0x32, 0x00, 0xFF, 0x00};
fwrite(gif_graphic_control_extension, 8, 1, gif_file);
圖像標識符(Image Descriptor)
我們的 GIF 沒有局部顏色表 順序排列 局部顏色表大小為 0。
所以,這里就是 0x2C, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x02, 0xBC, 0x02, 0x00。
示例代碼:
// 圖像標識符
uint8_t gif_image_descriptor[] = {0x2C, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x02, 0xBC, 0x02, 0x00};
fwrite(gif_image_descriptor, 10, 1, gif_file);
局部顏色表(Local Color Table)
如果有局部顏色表,則跟 全局顏色表(Global Color Table) 一樣的格式。
基於顏色表的圖像數據(Image Data)
這里是最關鍵的圖像數據,生成步驟如下:
- 1.根據全局顏色表或者局部顏色表,生成一張圖像的顏色索引數據
- 2.使用 LZW 算法壓縮上一步生成的數據
- 3.將壓縮后的數據按照格式寫入文件
1.生成索引數據
我們要生成的 GIF 尺寸 700x700,有 7 張圖像,每張圖像一個顏色 赤 橙 黃 綠 青 藍 紫;
顏色已經寫入全局顏色表中;
每個顏色索引 1 字節;
示例代碼:
// 基於顏色表的圖像數據
uint8_t *gif_one_frame_raw = malloc(700 * 700);
memset(gif_one_frame_raw, i, 700*700);
2.LZW 壓縮數據
LZW 壓縮算法不在本次研究范圍,直接用即可。
// GIF 一幀圖像的數據壓縮后大小
unsigned long compressed_size;
// GIF 一幀圖像的數據解壓后的數據
unsigned char *img;
lzw_compress_gif(
3,
700*700,
gif_one_frame_raw,
&compressed_size,
&img
);
3.按照格式寫入文件
第一個字節表示 LZW 編碼初始表大小的位數,用於使用 LZW 算法解壓數據。
后面的是圖像數據塊:
每個數據塊第一個字節表示數據塊大小(不包括這個字節)
數據塊后面的一個字節表示后續數據塊大小
當數據塊后面的一個字節是 0 ,表示數據結束了

示例代碼:
fputc(0x03, gif_file);
unsigned long current_index = 0;
while (current_index < compressed_size) {
if((current_index + 0xFF) >= compressed_size) {
unsigned long diff = compressed_size - current_index;
fputc(diff, gif_file);
fwrite(img+current_index, diff, 1, gif_file);
fputc(0x00, gif_file);
current_index += diff;
} else {
fputc(0xFF, gif_file);
fwrite(img+current_index, 0xFF, 1, gif_file);
current_index += 0xFF;
}
}
Plain Text Extension
這個特性不起作用; 瀏覽器和圖片處理應用程序,如 Photoshop 忽略它, GIFLIB 並不試圖解釋它。
所以直接忽略。
文件結尾(Trailer)
標識 GIF 文件結束,固定值 0x3B。
當解析程序讀到 0x3B 時,文件終結。
示例代碼:
// GIF 文件結束: 0x3B
fputc(0x3B, gif_file);
查看 GIF
以上完整代碼在 binglingziyu/audio-video-blog-demos 可以獲取。
運行代碼,生成 GIF 圖片:

參考資料:
