GIF 編碼知識
GIF 包含的數(shù)據(jù)塊:
文件頭(Header)
邏輯屏幕標(biāo)識符(Logical Screen Descriptor)
全局顏色表(Global Color Table)
Application Extension
Comment Extension
圖形控制擴(kuò)展(Graphic Control Extension)
圖像標(biāo)識符(Image Descriptor)
局部顏色表(Local Color Table)
基于顏色表的圖像數(shù)據(jù)(Image Data)
Plain Text Extension
文件結(jié)尾(Trailer)
GIF 編碼步驟
今天的目標(biāo)是做出一張尺寸 700x700、7 個顏色畫面切換的 GIF 動畫叉寂。
文件頭(Header)
GIF 的前 6 個字節(jié)內(nèi)容是 GIF 的署名和版本號。有兩個版本 GIF87a
GIF89a
致开,GIF89a
版本才有多幀動畫,所有這里使用 89a
版本晒旅。
示例代碼:
// GIF 文件頭枪芒,6 個字節(jié)內(nèi)容是 GIF 的署名和版本號
uint8_t gif_header[] = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
fwrite(gif_header, 6, 1, gif_file);
邏輯屏幕標(biāo)識符(Logical Screen Descriptor)
從上一篇 音視頻入門-17-GIF文件格式詳解 我們知道:
邏輯屏幕標(biāo)識符(7 個字節(jié)):
屏幕邏輯寬度:2 字節(jié);
屏幕邏輯高度:2 字節(jié);
-
打包值,大小為 1 字節(jié)
- m - 全局顏色表標(biāo)志名扛,1 bit;
- cr - 顏色深度,3 bit;(x: 可忽略)
- s - 分類標(biāo)志茧痒, 1 bit; (x: 不使用肮韧,設(shè)為 0)
- pixel - 全局顏色列表大小,3 bit;
背景顏色索引: 1 字節(jié);
像素寬高比: 1 字節(jié);(x: 不使用旺订,設(shè)為 0)
示例代碼:
// 邏輯屏幕標(biāo)識符
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)
每個顏色索引由三字節(jié)組成弄企,按 RGB 順序排列。
由 【邏輯屏幕標(biāo)識符】可知区拳,顏色的索引數(shù)(2^(pixel+1))是 2 的倍數(shù)拘领,如果圖片顏色數(shù)目不夠要補(bǔ)足。
比如樱调,我們的圖片用了 7 個顏色约素,顏色索引數(shù)是 8届良,所以最后再加一個顏色(占位,不使用)圣猎。
示例代碼:
// 顏色表
uint32_t rainbowColors[] = {
0XFF0000, // 赤
0XFFA500, // 橙
0XFFFF00, // 黃
0X00FF00, // 綠
0X007FFF, // 青
0X0000FF, // 藍(lán)
0X8B00FF, // 紫
0X000000 // 黑
};
// 全局顏色表士葫、
for(int i = 0; i < 8; i++) {
// 根據(jù)顏色索引取出顏色表中的顏色
uint32_t color_rgb = rainbowColors[i];
// 當(dāng)前顏色 R 分量
uint8_t R = (color_rgb & 0xFF0000) >> 16;
// 當(dāng)前顏色 G 分量
uint8_t G = (color_rgb & 0x00FF00) >> 8;
// 當(dāng)前顏色 B 分量
uint8_t B = color_rgb & 0x0000FF;
fputc(R, gif_file);
fputc(G, gif_file);
fputc(B, gif_file);
}
Application Extension
Application Extension 這 19 個字節(jié)基本上 GIF 都一樣。
0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00
代表的內(nèi)容是 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 文件样漆,有時被用來圖像描述为障、圖像信貸或其他人類可讀的元數(shù)據(jù),如圖像捕獲的 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
代表的內(nèi)容是 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);
圖形控制擴(kuò)展(Graphic Control Extension)
我們的 GIF 不使用處置方法
不使用透明色
圖像延遲 50
放祟。
所以,這里就是 0x21, 0xF9, 0x04, 0x00, 0x32, 0x00, 0xFF, 0x00
呻右。
示例代碼:
// 圖形控制擴(kuò)展
uint8_t gif_graphic_control_extension[] = {0x21, 0xF9, 0x04, 0x00, 0x32, 0x00, 0xFF, 0x00};
fwrite(gif_graphic_control_extension, 8, 1, gif_file);
圖像標(biāo)識符(Image Descriptor)
我們的 GIF 沒有局部顏色表
順序排列
局部顏色表大小為 0
跪妥。
所以,這里就是 0x2C, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x02, 0xBC, 0x02, 0x00
声滥。
示例代碼:
// 圖像標(biāo)識符
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) 一樣的格式。
基于顏色表的圖像數(shù)據(jù)(Image Data)
這里是最關(guān)鍵的圖像數(shù)據(jù)落塑,生成步驟如下:
- 1.根據(jù)全局顏色表或者局部顏色表纽疟,生成一張圖像的顏色索引數(shù)據(jù)
- 2.使用 LZW 算法壓縮上一步生成的數(shù)據(jù)
- 3.將壓縮后的數(shù)據(jù)按照格式寫入文件
1.生成索引數(shù)據(jù)
我們要生成的 GIF 尺寸 700x700,有 7 張圖像憾赁,每張圖像一個顏色 赤
橙
黃
綠
青
藍(lán)
紫
污朽;
顏色已經(jīng)寫入全局顏色表中;
每個顏色索引 1 字節(jié)龙考;
示例代碼:
// 基于顏色表的圖像數(shù)據(jù)
uint8_t *gif_one_frame_raw = malloc(700 * 700);
memset(gif_one_frame_raw, i, 700*700);
2.LZW 壓縮數(shù)據(jù)
LZW 壓縮算法不在本次研究范圍蟆肆,直接用即可。
// GIF 一幀圖像的數(shù)據(jù)壓縮后大小
unsigned long compressed_size;
// GIF 一幀圖像的數(shù)據(jù)解壓后的數(shù)據(jù)
unsigned char *img;
lzw_compress_gif(
3,
700*700,
gif_one_frame_raw,
&compressed_size,
&img
);
3.按照格式寫入文件
第一個字節(jié)表示 LZW 編碼初始表大小的位數(shù)晦款,用于使用 LZW 算法解壓數(shù)據(jù)炎功。
后面的是圖像數(shù)據(jù)塊:
每個數(shù)據(jù)塊第一個字節(jié)表示數(shù)據(jù)塊大小(不包括這個字節(jié))
數(shù)據(jù)塊后面的一個字節(jié)表示后續(xù)數(shù)據(jù)塊大小
當(dāng)數(shù)據(jù)塊后面的一個字節(jié)是 0 缓溅,表示數(shù)據(jù)結(jié)束了
[圖片上傳失敗...(image-d75d16-1607049927310)]
示例代碼:
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
這個特性不起作用; 瀏覽器和圖片處理應(yīng)用程序,如 Photoshop 忽略它, GIFLIB 并不試圖解釋它蛇损。
所以直接忽略。
文件結(jié)尾(Trailer)
標(biāo)識 GIF 文件結(jié)束坛怪,固定值 0x3B淤齐。
當(dāng)解析程序讀到 0x3B 時,文件終結(jié)酝陈。
示例代碼:
// GIF 文件結(jié)束: 0x3B
fputc(0x3B, gif_file);
查看 GIF
以上完整代碼在 binglingziyu/audio-video-blog-demos 可以獲取床玻。
運(yùn)行代碼,生成 GIF 圖片:
參考資料: