音視訊入門-18-手動生成一張GIF圖片
阿新 • • 發佈:2020-12-04
[* 音視訊入門文章目錄 *](https://www.ihubin.com/blog/audio-video-basic-catagory/)
# GIF 編碼知識
![GIF File Format](http://cloudstorage.ihubin.com/blog/audio-video/blog-17/gif_file_stream.png)
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 動畫。
![example-rainbow.gif](http://cloudstorage.ihubin.com/blog/audio-video/blog-18/example-rainbow.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檔案格式詳解](https://www.ihubin.com/blog/audio-video-basic-17-gif-file-format-detail/) 我們知道:
邏輯螢幕識別符號(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 ,表示資料結束了
![gif-block-image-data-hex.jpg](http://cloudstorage.ihubin.com/blog/audio-video/blog-18/gif-block-image-data-hex.jpg)
示例程式碼:
```
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](https://github.com/binglingziyu/audio-video-blog-demos) 可以獲取。
執行程式碼,生成 GIF 圖片:
![example-rainbow.gif](http://cloudstorage.ihubin.com/blog/audio-video/blog-18/example-rainbow.gif)
-----------
程式碼:
[audio-video-blog-demos](https://github.com/binglingziyu/audio-video-blog-demos)
參考資料:
[What's In A GIF](http://giflib.sourceforge.net/whatsinagif/index.html)
[Gif 89a specification](https://www.w3.org/Graphics/GIF/spec-gif89a.txt)
[GIF 格式解析](https://www.jianshu.com/p/38743ef278ac)
[GIF 圖片原理和儲存結構](https://www.techug.com/post/gif-image-structure-intro.html)
[Gif 圖片格式完全理解](https://www.techug.com/post/understand-spec-gif.html)
[GIF 檔案格式詳解](http://www.ks7.net/Program/cpp/276fb72618e35f0d.shtml)
[GIF 圖形檔案格式文件](https://www.cnblogs.com/think/archive/2006/04/12/372942.html)
[GIF 檔案格式詳解](https://blog.csdn.net/Swallow_he/article/details/76165202)
[LZW 壓縮演算法——簡明原理與實現](https://blog.csdn.net/abcjennifer/article/details/7995426)
[github.com/jefftime/lzw](https://github.com/jefftime/lzw)
[https://github.com/jcraveiro](https://github.com/jcraveiro/TI-Pl2)
[LZW compressor / decompressor](https://github.com/dbry/lzw-ab)
[ASCII Codes Table](https://ascii.cl/)
-----------