【工作筆記】將GIF拆解成序列幀給video_image控件使用

一报腔、前言

本文基于 AWTK 提供的自定義控件 video image 編寫蜈彼,該控件主要用來解決嵌入式開發(fā)板上 JPG、PNG总滩、GIF文件解析慢且內(nèi)存需求量高的問題纲堵。

備注:

  1. AWTK 是 ZLG 基于C語言開發(fā)的開源 GUI 引擎,前往官網(wǎng)咳秉,Github倉庫婉支。
  2. awtk-widget-video-image 是 AWTK 官方提供的自定義控件鸯隅,Gitee倉庫澜建。

二、video image 控件

在一般的嵌入式板子上面蝌以,解析 JPG炕舵、PNG、GIF文件速度是很慢的跟畅,尤其是在播放高分辨率的序列幀的時(shí)尤為明顯咽筋,為了解決該問題,AWTK 提供了 video image 控件徊件。

video_image 控件是采用幀間差異的圖像算法奸攻,把序列幀壓縮為一個自定義的視頻文件,這種方法的邏輯實(shí)際上就是空間換時(shí)間的策略虱痕,雖然整體視頻文件的大小會比 JPG 文件(PNG 文件)要大睹耐,但是會比位圖文件小很多。

控件原理:

  1. 先將一系列JPG/PNG格式的序列幀圖片解析成位圖部翘。
  2. 采用幀間差異算法計(jì)算并保存每一幀之間的臟矩形區(qū)域和數(shù)據(jù)硝训。
  3. 將這些差異數(shù)據(jù)壓縮為一個自定義的視頻文件。
  4. 采用 lz4 算法(一個第三方庫)再次對視頻文件進(jìn)行壓縮新思,進(jìn)一步縮小資源窖梁。
  5. 在程序運(yùn)行時(shí)候先用 lz4 算法解壓視頻文件,然后將幀間差異解析出來夹囚,組合成位圖纵刘,一幀幀播放。

三荸哟、為什么需要將GIF拆解成序列幀彰导?

在 AWTK 中,顯示 gif 圖片的過程時(shí)會先將 gif 文件解析成一幀幀的位圖敲茄,再將它們拼接起來加載到內(nèi)存中位谋,比如一個分辨率為 480 X 480 的 gif 圖片,其中包含 120 幀圖像堰燎,需要將其解析為 16 位色的 bitmap 顯示到 LCD 上掏父,那么就至少需要 480 X 480 X 120 X 2 大小的內(nèi)存,約為 52.7 M秆剪。

這么大的內(nèi)存需求量赊淑,一般的嵌入式開發(fā)板顯然是無法提供的爵政,這個時(shí)候就可以借助 git_to_video_gen 工具將 git 圖片轉(zhuǎn)成一張張的位圖序列幀,然后再使用 diff_image_to_video_gen 工具壓縮成 video_image 控件使用的視頻文件陶缺。

備注:git_to_video_gen 工具可以在 window 和 linux 上面運(yùn)行钾挟。

git_to_video_gen 工具的用法:git_to_video_gen.exe [空格] image_name [空格] save_file_path

參數(shù) 說明 備注
image_name gif文件路徑
save_file_path 序列幀文件的保存路徑

git_to_video_gen 工具默認(rèn)將 gif 圖片轉(zhuǎn)成一系列 RGBA8888 格式的 png 圖片幀,并且會在圖片幀目錄下生成 info.log 文件饱岸,該文件用于存放 gif 的相關(guān)信息掺出,比如:

image_name_format=frame%d
delays=30

其中參數(shù)的含義如下:

參數(shù) 說明 備注
image_name_format 序列幀的文件名格式 git_to_video_gen工具生成的序列幀默認(rèn)為frame%d
delays gif的幀間延遲時(shí)間 每一幀的時(shí)間間隔,單位為毫秒

本文主要是介紹 git_to_video_gen 工具的實(shí)現(xiàn)苫费,即將 gif 文件拆解成序列幀汤锨。

四、將GIF拆解成序列幀

首先百框,我們先編寫 git_to_video_gen 工具的 main 函數(shù):

/* 該工具僅在 Linux 平臺和 Windows 平臺使用 */
#if defined(LINUX) || defined(WIN32)
#include "tkc/fs.h"
#include "tkc/mem.h"
#include "tkc/path.h"
#include "tkc/utils.h"
#include "base/bitmap.h"
#include "base/types_def.h"
#include "image_loader/image_loader_stb.h"
#include <iostream>
using namespace std;

/* 導(dǎo)入stb_image_write庫闲礼,用于后續(xù)將序列幀保存到本地硬盤 */
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "../3rd/stb/stb_image_write.h"

#define GIF_INFO_FILE_NAME "info.log"    /* 保存序列幀的相關(guān)信息,比如文件名格式铐维、幀間延遲時(shí)間柬泽, */
#define FRAME_FILE_NAME_FORMAT "frame%d" /* 序列幀文件名格式 */
#define FRAME_FILE_NAME_MAX_LEN 16       /* 序列幀文件名最大長度 */

/**
 * 注意:本工具默認(rèn)將 GIF 圖片解析為RGBA8888格式的位圖序列幀,再將它們保存為 png 格式的文件 
 */

......

int main(int argc, char** argv) {
  bitmap_t image;
  const char* image_name = NULL;
  const char* save_file_path = NULL;
  memset(&image, 0x00, sizeof(bitmap_t));

  /* 用戶需傳入兩個參數(shù):gif文件路徑嫁蛇、序列幀文件的保存路徑 */
  if (argc < 3) {
    cout << "argvs: image_name, save_file_path" << endl;
    return -1;
  }

  image_name = argv[1];
  save_file_path = argv[2];

  /* 將GIF文件解析并拼接成包含所有幀的位圖 */
  if (!get_bitmap(image_name, &image)) {
    cout << "get bitmap failed!" << endl;
    return -1;
  }
  
  if (image.is_gif) {
    /* 將包含所有幀的位圖拆解成序列幀并保存到本地硬盤 */
    if (save_git_frame(&image, save_file_path)) {
      char buff[MAX_PATH] = {0};
      char info_path[MAX_PATH] = {0};
      tk_snprintf(buff, sizeof(buff), "image_name_format=%s\ndelays=%d\n", FRAME_FILE_NAME_FORMAT,
                  *image.gif_delays);
      path_build(info_path, sizeof(info_path), save_file_path, GIF_INFO_FILE_NAME, NULL);
      file_write(info_path, buff, tk_strlen(buff));
    }
  } else {
    cout << "image is not a gif!" << endl;
  }
  /* 銷毀位圖 */
  bitmap_destroy(&image);

  return 0;
}

#endif

4.1 先將GIF解析拼接成位圖

這里我們首先借助 AWTK 中的 stb_image 庫將 gif 文件解析并拼接成一幅包含所有幀的位圖锨并,代碼如下:

static bool get_bitmap(const char* image_name, bitmap_t* image) {
  ret_t ret = RET_FAIL;
  uint8_t* buff = NULL;
  uint32_t buff_length = 0;
  /* 確保GIF文件存在 */
  if (!file_exist(image_name)) {
    return false;
  }
  /* 讀取GIF文件的數(shù)據(jù) */
  buff = (uint8_t*)file_read(image_name, &buff_length);
  if (buff != NULL) {
    /* 調(diào)用stb_image庫中的函數(shù)將GIF解析成位圖 */
    ret = stb_load_image(ASSET_TYPE_IMAGE_GIF, buff, buff_length, image, false, false, false,
                         LCD_ORIENTATION_0);
    TKMEM_FREE(buff);
  }

  return ret == RET_OK;
}

4.2 將位圖拆解為序列幀

得到包含GIF文件所有幀的位圖對象后,再將這個位圖拆解成一幀一幀的數(shù)據(jù)保存到本地硬盤即可棠众,代碼如下:

static bool save_git_frame(bitmap_t* image, const char* save_file_path) {
  fs_t* fs = os_fs();
  ret_t ret = RET_OK;
  int x = image->w;
  int y = image->gif_frame_h;
  int stride_bytes = image->line_length;
  char file_path[MAX_PATH];
  char file_name[FRAME_FILE_NAME_MAX_LEN];
  /* 確保存放序列幀文件的目標(biāo)路徑存在 */
  if (!fs_dir_exist(fs, save_file_path)) {
    ret = fs_create_dir_r(fs, save_file_path);
  }

  if (ret == RET_OK) {
    /* 獲取位圖數(shù)據(jù) */
    uint8_t* image_data = bitmap_lock_buffer_for_read(image);
    /* 循環(huán)遍歷每一幀位圖數(shù)據(jù) */
    for (int i = 0; i < image->gif_frames_nr; i++) {
      int32_t len = 0;
      uint8_t* png_data = NULL;
      uint8_t* bmp_data = image_data + (stride_bytes * y * i);
      memset(file_path, 0x00, sizeof(file_path));
      memset(file_name, 0x00, sizeof(file_name));
  
      tk_snprintf(file_name, sizeof(file_name), FRAME_FILE_NAME_FORMAT ".png", i);
      path_build(file_path, sizeof(file_path), save_file_path, file_name, NULL);
      /* 調(diào)用stb_image_write庫中的接口將每幀位圖數(shù)據(jù)寫入本地硬盤(保存為png文件)  */
      png_data = stbi_write_png_to_mem(bmp_data, stride_bytes, x, y, 4, &len);
      if (png_data != NULL) {
        file_write(file_path, png_data, len);
        STBIW_FREE(png_data);
      }
    }
    bitmap_unlock_buffer(image);
  }

  return ret == RET_OK;
}

4.3 示例效果

比如這里我將 awtk-widget-video-image/design/default/images/video/gif_35.gif 文件拆解成序列幀琳疏,命令如下:

[圖片上傳失敗...(image-660897-1653914653187)]

.\bin\gif_to_frame_gen.exe design\default\images\video\gif_35.gif design\default\images\video\gif_35

即可得到 design\default\images\video\gif_35目錄,其中存放著 gif_35.gif 文件生成的序列幀闸拿,如下圖所示:

[圖片上傳失敗...(image-18136-1653914653187)]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末空盼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子新荤,更是在濱河造成了極大的恐慌揽趾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苛骨,死亡現(xiàn)場離奇詭異篱瞎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)痒芝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門俐筋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人严衬,你說我怎么就攤上這事澄者。” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵粱挡,是天一觀的道長赠幕。 經(jīng)常有香客問我,道長询筏,這世上最難降的妖魔是什么榕堰? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮嫌套,結(jié)果婚禮上逆屡,老公的妹妹穿的比我還像新娘。我一直安慰自己灌危,他們只是感情好康二,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布碳胳。 她就那樣靜靜地躺著勇蝙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挨约。 梳的紋絲不亂的頭發(fā)上味混,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音诫惭,去河邊找鬼翁锡。 笑死,一個胖子當(dāng)著我的面吹牛夕土,可吹牛的內(nèi)容都是我干的馆衔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼怨绣,長吁一口氣:“原來是場噩夢啊……” “哼角溃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篮撑,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤减细,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赢笨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體未蝌,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年茧妒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萧吠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡桐筏,死狀恐怖纸型,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤绊袋,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布毕匀,位于F島的核電站,受9級特大地震影響癌别,放射性物質(zhì)發(fā)生泄漏皂岔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一展姐、第九天 我趴在偏房一處隱蔽的房頂上張望躁垛。 院中可真熱鬧,春花似錦圾笨、人聲如沸教馆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽土铺。三九已至,卻和暖如春板鬓,著一層夾襖步出監(jiān)牢的瞬間悲敷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工俭令, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留后德,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓抄腔,卻偏偏與公主長得像瓢湃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子赫蛇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

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