1. 程式人生 > >音視訊入門-18-手動生成一張GIF圖片

音視訊入門-18-手動生成一張GIF圖片

[* 音視訊入門文章目錄 *](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/) -----------