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

* 音視頻入門文章目錄 *

GIF 編碼知識

1.png

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 動畫叉寂。

2.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 圖片:

2.gif

代碼:
audio-video-blog-demos

參考資料:

What's In A GIF

Gif 89a specification

GIF 格式解析

GIF 圖片原理和儲存結(jié)構(gòu)

Gif 圖片格式完全理解

GIF 文件格式詳解

GIF 圖形文件格式文檔

GIF 文件格式詳解

LZW 壓縮算法——簡明原理與實(shí)現(xiàn)

github.com/jefftime/lzw

https://github.com/jcraveiro

LZW compressor / decompressor

ASCII Codes Table


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沉帮,一起剝皮案震驚了整個濱河市锈死,隨后出現(xiàn)的幾起案子贫堰,更是在濱河造成了極大的恐慌,老刑警劉巖待牵,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件其屏,死亡現(xiàn)場離奇詭異,居然都是意外死亡缨该,警方通過查閱死者的電腦和手機(jī)偎行,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贰拿,“玉大人蛤袒,你說我怎么就攤上這事∨蚋” “怎么了妙真?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荚守。 經(jīng)常有香客問我珍德,道長,這世上最難降的妖魔是什么矗漾? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任锈候,我火速辦了婚禮,結(jié)果婚禮上敞贡,老公的妹妹穿的比我還像新娘泵琳。我一直安慰自己,他們只是感情好嫡锌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布虑稼。 她就那樣靜靜地躺著,像睡著了一般势木。 火紅的嫁衣襯著肌膚如雪蛛倦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天啦桌,我揣著相機(jī)與錄音溯壶,去河邊找鬼。 笑死甫男,一個胖子當(dāng)著我的面吹牛且改,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播板驳,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼又跛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了若治?” 一聲冷哼從身側(cè)響起慨蓝,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤感混,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后礼烈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弧满,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年此熬,在試婚紗的時候發(fā)現(xiàn)自己被綠了庭呜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡犀忱,死狀恐怖募谎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情峡碉,我是刑警寧澤近哟,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布驮审,位于F島的核電站鲫寄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疯淫。R本人自食惡果不足惜地来,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望熙掺。 院中可真熱鬧未斑,春花似錦、人聲如沸币绩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缆镣。三九已至芽突,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間董瞻,已是汗流浹背寞蚌。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钠糊,地道東北人挟秤。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像抄伍,于是被迫代替她去往敵國和親艘刚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容