一报腔、前言
本文基于 AWTK 提供的自定義控件 video image 編寫蜈彼,該控件主要用來解決嵌入式開發(fā)板上 JPG、PNG总滩、GIF文件解析慢且內(nèi)存需求量高的問題纲堵。
備注:
- AWTK 是 ZLG 基于C語言開發(fā)的開源 GUI 引擎,前往官網(wǎng)咳秉,Github倉庫婉支。
- awtk-widget-video-image 是 AWTK 官方提供的自定義控件鸯隅,Gitee倉庫澜建。
二、video image 控件
在一般的嵌入式板子上面蝌以,解析 JPG炕舵、PNG、GIF文件速度是很慢的跟畅,尤其是在播放高分辨率的序列幀的時(shí)尤為明顯咽筋,為了解決該問題,AWTK 提供了 video image 控件徊件。
video_image 控件是采用幀間差異的圖像算法奸攻,把序列幀壓縮為一個自定義的視頻文件,這種方法的邏輯實(shí)際上就是空間換時(shí)間的策略虱痕,雖然整體視頻文件的大小會比 JPG 文件(PNG 文件)要大睹耐,但是會比位圖文件小很多。
控件原理:
- 先將一系列JPG/PNG格式的序列幀圖片解析成位圖部翘。
- 采用幀間差異算法計(jì)算并保存每一幀之間的臟矩形區(qū)域和數(shù)據(jù)硝训。
- 將這些差異數(shù)據(jù)壓縮為一個自定義的視頻文件。
- 采用 lz4 算法(一個第三方庫)再次對視頻文件進(jìn)行壓縮新思,進(jìn)一步縮小資源窖梁。
- 在程序運(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)]