基于 GD32F303ZET6 苹果派开发板
简介
前面的实验中介绍了 BMP、JPEG 和 PNG 的解码显示,本章中将介绍如何解码 GIF 图片。
GIF 图片,即为常说的动图,可以显示简单的动画效果。至于为什么脱了那么久,主要是没有找到一个好的 GIF 解码库。我在网上搜索了很久,那些 GIF 解码库都是基于 PC 平台的,基本不能在 Keil 上编译通过。在这期间我还尝试过原子哥的 GIF 驱动,不过他家的驱动,只能显示文件系统中的 GIF,后来我将所有文件操作相关的函数都修改成内存操纵,但是后边又发现了新问题。原子哥提供的 GIF 驱动,不能正常显示带透明度的 GIF 图片,然而我对 GIF 的解码不是很熟,不想修改底层代码。于是我便到 Github 上搜寻,终于找到一个比较合适的 GIF 解码库,即 gifdec。该解码库原本是用文件操作函数,显示文件系统中的 GIF 的,被我魔改成可以解码内存中的 GIF。这个解码库相对于原子哥的,速度慢了些,消耗的内存比较多,而且应用在 FatFS 上时,容易卡死(原因未知),但是它可以显示带透明度的 GIF。
虽然无法在 FatFS 上使用,但是我们可以将 GIF 直接固化到 Flash,相当于在内存中访问,解码显示小的 GIF 图片还是可以的。
源码头文件
gifdec 的头文件如下所示。这当中的 GIF_MAX_WIDTH 和 GIF_MAX_HEIGHT 为 GIF 最大尺寸,越大,解码时所消耗的内存越大,所以在单片机中可以适当改小一些。打开(预解码)GIF 时,需要提供两个函数,一个是读取数据函数,另一个是移动读写指针函数,稍后会详细介绍。
#ifndef GIFDEC_H
#define GIFDEC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define GIF_MAX_WIDTH 800
#define GIF_MAX_HEIGHT 480
typedef void (*gd_READ)(void* param, void* buf, unsigned int num);
typedef int (*gd_LSEEK)(void* param, int offset, int whence);
typedef struct gd_Palette {
int size;
uint8_t colors[0x100 * 3];
} gd_Palette;
typedef struct gd_GCE {
uint16_t delay;
uint8_t tindex;
uint8_t disposal;
int input;
int transparency;
} gd_GCE;
typedef struct gd_GIF {
long anim_start;
uint16_t width, height;
uint16_t depth;
uint16_t loop_count;
gd_GCE gce;
gd_Palette *palette;
gd_Palette lct, gct;
void (*plain_text)(
struct gd_GIF *gif, uint16_t tx, uint16_t ty,
uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
uint8_t fg, uint8_t bg
);
void (*comment)(struct gd_GIF *gif);
void (*application)(struct gd_GIF *gif, char id[8], char auth[3]);
uint16_t fx, fy, fw, fh;
uint8_t bgindex;
uint8_t *canvas;
uint8_t frame[4 * GIF_MAX_WIDTH * GIF_MAX_HEIGHT];
gd_READ read;
gd_LSEEK lseek;
void* param;
} gd_GIF;
int gd_open_gif(gd_GIF* gif, void* param, gd_READ read, gd_LSEEK lseek);
int gd_get_frame(gd_GIF *gif);
void gd_render_frame(gd_GIF *gif, uint8_t *buffer);
int gd_is_bgcolor(gd_GIF *gif, uint8_t color[3]);
void gd_rewind(gd_GIF *gif);
#ifdef __cplusplus
}
#endif
#endif /* GIFDEC_H */
源文件
gifdec 的源文件如下所示。源文件中有涉及到动态内存分配操作,如果是应用到单片机中,可以替换成自己的动态内存分配函数。GIF 解码时,所需的内存很大,所以推荐使用外拓 SRAM。对于苹果派开发板,1MB 的外拓 SRAM 解码 GIF 是有点吃力的,但蓝莓派则不同,蓝莓派开发板上有 32MB 的 SDRAM,完全不用担心内存不够。
#include "gifdec.h"
#include <stdio.h>
#include <stdlib.h>
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define GIF_SEEK_CUR (1)
#define GIF_SEEK_SET (0)
typedef struct Entry {
uint16_t length;
uint16_t prefix;
uint8_t suffix;
} Entry;
typedef struct Table {
int bulk;
int nentries;
Entry* entries;
} Table;
static void
gif_mem_copy(void* des, void* src, unsigned int size)
{
unsigned char* source;
unsigned char* target;
unsigned int i;
source = src;
target = des;
for (i = 0; i < size; i++)
{
target[i] = source[i];
}
}
static int
gif_mem_cmp(void* buf1, void* buf2, unsigned int size)
{
unsigned char* source;
unsigned char* target;
unsigned int i;
source = buf1;
target = buf2;
for (i = 0; i < size; i++)
{
if (target[i] != source[i])
{
return 1;
}
}
return 0;
}
static void
gif_mem_set(void* buf, int value, unsigned int len)
{
unsigned char* buffer;
unsigned int i;
buffer = buf;
for (i = 0; i < len; i++)
{
buffer[i] = value;
}
}
static void*
gif_malloc(unsigned int size)
{
return malloc(size);
}
static void
gif_free(void* p)
{
free(p);
}
static uint16_t
read_num(gd_GIF* gif)
{
uint8_t bytes[2];
gif->read(gif->param, bytes, 2);
return bytes[0] + (((uint16_t)bytes[1]) << 8);
}
int
gd_open_gif(gd_GIF* gif, void* param, gd_READ read, gd_LSEEK lseek)
{
uint8_t sigver[3];
uint16_t width, height, depth;
uint8_t fdsz, bgidx, aspect;
int i;
uint8_t* bgcolor;
int gct_sz;
gif->read = read;
gif->lseek = lseek;
gif->param = param;
/* Header */
gif->read(gif->param, sigver, 3);
if (gif_mem_cmp(sigver, "GIF", 3) != 0) {
printf("invalid signature\n");
return -1;
}
/* Version */
gif->read(gif->param, sigver, 3);
if (gif_mem_cmp(sigver, "89a", 3) != 0) {
printf("invalid version\n");
return -1;
}
/* Width x Height */
width = read_num(gif);
height = read_num(gif);
if ((width > GIF_MAX_WIDTH) || (height > GIF_MAX_HEIGHT))
{
printf("invalid size\n");
return -1;
}
/* FDSZ */
gif->read(gif->param, &fdsz, 1);
/* Presence of GCT */
if (!(fdsz & 0x80)) {
printf("no global color table\n");
return -1;
}
/* Color Space's Depth */
depth = ((fdsz >> 4) & 7) + 1;
/* Ignore Sort Flag. */
/* GCT Size */
gct_sz = 1 << ((fdsz & 0x07) + 1);
/* Background Color Index */
gif->read(gif->param, &bgidx, 1);
/* Aspect Ratio */
gif->read(gif->param, &aspect, 1);
/* Init gd_GIF Structure. */
gif->width = width;
gif->height = height;
gif->depth = depth;
/* Read GCT */
gif->gct.size = gct_sz;
gif->read(gif->param, gif->gct.colors, 3 * gif->gct.size);
gif->palette = &gif->gct;
gif->bgindex = bgidx;
gif->canvas = &gif->frame[width * height];
if (gif->bgindex)
gif_mem_set(gif->frame, gif->bgindex, gif->width * gif->height);
bgcolor = &gif->palette->colors[gif->bgindex * 3];
if (bgcolor[0] || bgcolor[1] || bgcolor[2])
for (i = 0; i < gif->width * gif->height; i++)
gif_mem_copy(&gif->canvas[i * 3], bgcolor, 3);
gif->anim_start = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
return 0;
}
static void
discard_sub_blocks(gd_GIF* gif)
{
uint8_t size;
do {
gif->read(gif->param, &size, 1);
gif->lseek(gif->param, size, GIF_SEEK_CUR);
} while (size);
}
static void
read_plain_text_ext(gd_GIF* gif)
{
if (gif->plain_text) {
uint16_t tx, ty, tw, th;
uint8_t cw, ch, fg, bg;
long sub_block;
gif->lseek(gif->param, 1, GIF_SEEK_CUR); /* block size = 12 */
tx = read_num(gif);
ty = read_num(gif);
tw = read_num(gif);
th = read_num(gif);
gif->read(gif->param, &cw, 1);
gif->read(gif->param, &ch, 1);
gif->read(gif->param, &fg, 1);
gif->read(gif->param, &bg, 1);
sub_block = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
gif->lseek(gif->param, sub_block, GIF_SEEK_SET);
}
else {
/* Discard plain text metadata. */
gif->lseek(gif->param, 13, GIF_SEEK_CUR);
}
/* Discard plain text sub-blocks. */
discard_sub_blocks(gif);
}
static void
read_graphic_control_ext(gd_GIF* gif)
{
uint8_t rdit;
/* Discard block size (always 0x04). */
gif->lseek(gif->param, 1, GIF_SEEK_CUR);
gif->read(gif->param, &rdit, 1);
gif->gce.disposal = (rdit >> 2) & 3;
gif->gce.input = rdit & 2;
gif->gce.transparency = rdit & 1;
gif->gce.delay = read_num(gif);
gif->read(gif->param, &gif->gce.tindex, 1);
/* Skip block terminator. */
gif->lseek(gif->param, 1, GIF_SEEK_CUR);
}
static void
read_comment_ext(gd_GIF* gif)
{
if (gif->comment) {
long sub_block = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
gif->comment(gif);
gif->lseek(gif->param, sub_block, GIF_SEEK_SET);
}
/* Discard comment sub-blocks. */
discard_sub_blocks(gif);
}
static void
read_application_ext(gd_GIF* gif)
{
char app_id[8];
char app_auth_code[3];
/* Discard block size (always 0x0B). */
gif->lseek(gif->param, 1, GIF_SEEK_CUR);
/* Application Identifier. */
gif->read(gif->param, app_id, 8);
/* Application Authentication Code. */
gif->read(gif->param, app_auth_code, 3);
if (!gif_mem_cmp(app_id, "NETSCAPE", sizeof(app_id))) {
/* Discard block size (0x03) and constant byte (0x01). */
gif->lseek(gif->param, 2, GIF_SEEK_CUR);
gif->loop_count = read_num(gif);
/* Skip block terminator. */
gif->lseek(gif->param, 1, GIF_SEEK_CUR);
}
else if (gif->application) {
long sub_block = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
gif->application(gif, app_id, app_auth_code);
gif->lseek(gif->param, sub_block, GIF_SEEK_SET);
discard_sub_blocks(gif);
}
else {
discard_sub_blocks(gif);
}
}
static void
read_ext(gd_GIF* gif)
{
uint8_t label;
gif->read(gif->param, &label, 1);
switch (label) {
case 0x01:
read_plain_text_ext(gif);
break;
case 0xF9:
read_graphic_control_ext(gif);
break;
case 0xFE:
read_comment_ext(gif);
break;
case 0xFF:
read_application_ext(gif);
break;
default:
printf("unknown extension: %02X\n", label);
}
}
static Table*
new_table(int key_size)
{
int key;
int init_bulk = MAX(1 << (key_size + 1), 0x100);
Table* table = gif_malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
if (table) {
table->bulk = init_bulk;
table->nentries = (1 << key_size) + 2;
table->entries = (Entry*)&table[1];
for (key = 0; key < (1 << key_size); key++) {
table->entries[key].length = 1;
table->entries[key].prefix = 0xFFF;
table->entries[key].suffix = key;
}
}
return table;
}
/* Add table entry. Return value:
* 0 on success
* +1 if key size must be incremented after this addition
* -1 if could not realloc table */
static int
add_entry(Table** tablep, uint16_t length, uint16_t prefix, uint8_t suffix)
{
Table* table = *tablep;
if (table->nentries == table->bulk) {
table->bulk *= 2;
table = realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
if (!table) return -1;
table->entries = (Entry*)&table[1];
*tablep = table;
}
table->entries[table->nentries].length = length;
table->entries[table->nentries].prefix = prefix;
table->entries[table->nentries].suffix = suffix;
table->nentries++;
if ((table->nentries & (table->nentries - 1)) == 0)
return 1;
return 0;
}
static uint16_t
get_key(gd_GIF* gif, int key_size, uint8_t* sub_len, uint8_t* shift, uint8_t* byte)
{
int bits_read;
int rpad;
int frag_size;
uint16_t key;
key = 0;
for (bits_read = 0; bits_read < key_size; bits_read += frag_size) {
rpad = (*shift + bits_read) % 8;
if (rpad == 0) {
/* Update byte. */
if (*sub_len == 0) {
gif->read(gif->param, sub_len, 1); /* Must be nonzero! */
if (*sub_len == 0)
return 0x1000;
}
gif->read(gif->param, byte, 1);
(*sub_len)--;
}
frag_size = MIN(key_size - bits_read, 8 - rpad);
key |= ((uint16_t)((*byte) >> rpad)) << bits_read;
}
/* Clear extra bits to the left. */
key &= (1 << key_size) - 1;
*shift = (*shift + key_size) % 8;
return key;
}
/* Compute output index of y-th input line, in frame of height h. */
static int
interlaced_line_index(int h, int y)
{
int p; /* number of lines in current pass */
p = (h - 1) / 8 + 1;
if (y < p) /* pass 1 */
return y * 8;
y -= p;
p = (h - 5) / 8 + 1;
if (y < p) /* pass 2 */
return y * 8 + 4;
y -= p;
p = (h - 3) / 4 + 1;
if (y < p) /* pass 3 */
return y * 4 + 2;
y -= p;
/* pass 4 */
return y * 2 + 1;
}
/* Decompress image pixels.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int
read_image_data(gd_GIF* gif, int interlace)
{
uint8_t sub_len, shift, byte;
int init_key_size, key_size, table_is_full;
int frm_off, frm_size, str_len, i, p, x, y;
uint16_t key, clear, stop;
int ret;
Table* table;
Entry entry;
long start, end;
gif->read(gif->param, &byte, 1);
key_size = (int)byte;
if (key_size < 2 || key_size > 8)
return -1;
start = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
discard_sub_blocks(gif);
end = gif->lseek(gif->param, 0, GIF_SEEK_CUR);
gif->lseek(gif->param, start, GIF_SEEK_SET);
clear = 1 << key_size;
stop = clear + 1;
table = new_table(key_size);
key_size++;
init_key_size = key_size;
sub_len = shift = 0;
key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
frm_off = 0;
ret = 0;
frm_size = gif->fw * gif->fh;
while (frm_off < frm_size) {
if (key == clear) {
key_size = init_key_size;
table->nentries = (1 << (key_size - 1)) + 2;
table_is_full = 0;
}
else if (!table_is_full) {
ret = add_entry(&table, str_len + 1, key, entry.suffix);
if (ret == -1) {
gif_free(table);
return -1;
}
if (table->nentries == 0x1000) {
ret = 0;
table_is_full = 1;
}
}
key = get_key(gif, key_size, &sub_len, &shift, &byte);
if (key == clear) continue;
if (key == stop || key == 0x1000) break;
if (ret == 1) key_size++;
entry = table->entries[key];
str_len = entry.length;
for (i = 0; i < str_len; i++) {
p = frm_off + entry.length - 1;
x = p % gif->fw;
y = p / gif->fw;
if (interlace)
y = interlaced_line_index((int)gif->fh, y);
gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
if (entry.prefix == 0xFFF)
break;
else
entry = table->entries[entry.prefix];
}
frm_off += str_len;
if (key < table->nentries - 1 && !table_is_full)
table->entries[table->nentries - 1].suffix = entry.suffix;
}
gif_free(table);
if (key == stop)
gif->read(gif->param, &sub_len, 1); /* Must be zero! */
gif->lseek(gif->param, end, GIF_SEEK_SET);
return 0;
}
/* Read image.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */
static int
read_image(gd_GIF* gif)
{
uint8_t fisrz;
int interlace;
/* Image Descriptor. */
gif->fx = read_num(gif);
gif->fy = read_num(gif);
if (gif->fx >= gif->width || gif->fy >= gif->height)
return -1;
gif->fw = read_num(gif);
gif->fh = read_num(gif);
gif->fw = MIN(gif->fw, gif->width - gif->fx);
gif->fh = MIN(gif->fh, gif->height - gif->fy);
gif->read(gif->param, &fisrz, 1);
interlace = fisrz & 0x40;
/* Ignore Sort Flag. */
/* Local Color Table? */
if (fisrz & 0x80) {
/* Read LCT */
gif->lct.size = 1 << ((fisrz & 0x07) + 1);
gif->read(gif->param, gif->lct.colors, 3 * gif->lct.size);
gif->palette = &gif->lct;
}
else
gif->palette = &gif->gct;
/* Image Data. */
return read_image_data(gif, interlace);
}
static void
render_frame_rect(gd_GIF* gif, uint8_t* buffer)
{
int i, j, k;
uint8_t index, * color;
i = gif->fy * gif->width + gif->fx;
for (j = 0; j < gif->fh; j++) {
for (k = 0; k < gif->fw; k++) {
index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
color = &gif->palette->colors[index * 3];
if (!gif->gce.transparency || index != gif->gce.tindex)
gif_mem_copy(&buffer[(i + k) * 3], color, 3);
}
i += gif->width;
}
}
static void
dispose(gd_GIF* gif)
{
int i, j, k;
uint8_t* bgcolor;
switch (gif->gce.disposal) {
case 2: /* Restore to background color. */
bgcolor = &gif->palette->colors[gif->bgindex * 3];
i = gif->fy * gif->width + gif->fx;
for (j = 0; j < gif->fh; j++) {
for (k = 0; k < gif->fw; k++)
gif_mem_copy(&gif->canvas[(i + k) * 3], bgcolor, 3);
i += gif->width;
}
break;
case 3: /* Restore to previous, i.e., don't update canvas.*/
break;
default:
/* Add frame non-transparent pixels to canvas. */
render_frame_rect(gif, gif->canvas);
}
}
/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
int
gd_get_frame(gd_GIF* gif)
{
char sep;
dispose(gif);
gif->read(gif->param, &sep, 1);
while (sep != ',') {
if (sep == ';')
return 0;
if (sep == '!')
read_ext(gif);
else return -1;
gif->read(gif->param, &sep, 1);
}
if (read_image(gif) == -1)
return -1;
return 1;
}
void
gd_render_frame(gd_GIF* gif, uint8_t* buffer)
{
gif_mem_copy(buffer, gif->canvas, gif->width * gif->height * 3);
render_frame_rect(gif, buffer);
}
int
gd_is_bgcolor(gd_GIF* gif, uint8_t color[3])
{
return !gif_mem_cmp(&gif->palette->colors[gif->bgindex * 3], color, 3);
}
void
gd_rewind(gd_GIF* gif)
{
gif->lseek(gif->param, gif->anim_start, GIF_SEEK_SET);
}
应用
接下来我们将介绍 gifdec 的应用,首先是添加包含头文件。
/*********************************************************************************************************
* 包含头文件
*********************************************************************************************************/
#include <stdio.h>
#include "./LCD/LCD.h"
#include <windows.h>
#include "./Picture/gifdec.h"
在本文章,显示 GIF 之前,已经预先将 GIF 转成 C 语言数组,具体实现可参考 AnythingToC-任意文件转 C 语言数组小工具。为了跟踪记录 GIF 文件的读写位置,可以定义一个结构体,如下所示。
/*********************************************************************************************************
* 枚举结构体定义
*********************************************************************************************************/
typedef struct
{
unsigned char* buf; //数据缓冲区首地址
unsigned int readCnt; //读取计数
unsigned int totalSize; //GIF 图片大小(字节)
}StructGIFParam;
gifdec 输出的是 RGB888 格式,而我们需要的是 RGB565,单片机中大多使用的是 RGB565 格式,所以会涉及到 RGB888 与 RGB565 之间的转换,具体如下所示。
/*********************************************************************************************************
* 函数名称: GifGetRgb565
* 函数功能: 将 RGB888 转为 RGB565
* 输入参数: ctb:RGB888 颜色数组首地址
* 输出参数: void
* 返 回 值: RGB565 颜色
* 创建日期: 2023年03月25日
* 注 意:
*********************************************************************************************************/
u16 GifGetRgb565(u8* ctb)
{
u16 r, g, b;
r = (ctb[0] >> 3) & 0x1F;
g = (ctb[1] >> 2) & 0x3F;
b = (ctb[2] >> 3) & 0x1F;
return b + (g << 5) + (r << 11);
}
接下来是定义一个函数,GIF 模块通过此函数获取 GIF 文件数据,具体如下所示。GIFRead 函数中,第一个参数是用户参数,即用户打开 GIF 图片时输入的参数,可以指向任意数据,在这里我们假定传入的是一个指针参数,且指向一个 StructGIFParam 类型的结构体。
对于显示文件系统中的 GIF 图片而言,只需要调用 fwrite 函数即可。
/*********************************************************************************************************
* 函数名称: GIFRead
* 函数功能: GIF 解码模块读取数据
* 输入参数: param:用户参数,buf:数据缓冲区,num:读取长度
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年03月25日
* 注 意:
*********************************************************************************************************/
void GIFRead(void* param, void* buf, unsigned int num)
{
unsigned int i;
unsigned char* buffer;
StructGIFParam* stream;
buffer = (unsigned char*)buf;
stream = (StructGIFParam*)param;
for (i = 0; i < num; i++)
{
buffer[i] = stream->buf[stream->readCnt++];
}
}
除了读取数据的函数,我们还要为 GIF 解码模块提供修改读写位置的接口函数,如下所示。
对于显示文件系统中的 GIF 图片而言,直接调用 fseek 函数即可。
/*********************************************************************************************************
* 函数名称: GIFLseek
* 函数功能: GIF 设置读写位置
* 输入参数: param:用户参数
* offset:偏移量,
* whence:0-从文件开始往后移动 offset 个字节
* 1-当前位置往前或往后移动 offset 个字节
* 输出参数: void
* 返 回 值: 成功:返回文件当前读写位置相对于文件开始位置的偏移量(字节数)
* 失败:返回 -1
* 创建日期: 2023年03月25日
* 注 意:
*********************************************************************************************************/
int GIFLseek(void* param, int offset, int whence)
{
//获取用户参数
StructGIFParam* stream;
stream = (StructGIFParam*)param;
//从文件开始往后移动 offset 个字节
if (0 == whence)
{
if (offset <= stream->totalSize)
{
stream->readCnt = offset;
return stream->readCnt;
}
else
{
return -1;
}
}
//当前位置往前或往后移动 offset 个字节
else if (1 == whence)
{
if ((stream->readCnt + offset) <= stream->totalSize)
{
stream->readCnt = stream->readCnt + offset;
return stream->readCnt;
}
else
{
return -1;
}
}
return -1;
}
定义好接口之后,就可以开始解码 GIF 图片了。开始解码 GIF 图片时,通过 gd_open_gif 函数打开 GIF 图片。GIF 图片分有带透明度吧不带透明度两种类型,如果是带透明度的,那么需要提前将 GIF 显示区域的像素点数据保存下来,方便解码时回填背景数据。同时,一段 GIF 图像可以重复显示多次,如果读者只需要显示一遍,那么可以去掉解码部分最外边的那个循环。
/*********************************************************************************************************
* 函数名称: main
* 函数功能: 主函数
* 输入参数: void
* 输出参数: void
* 返 回 值: void
* 创建日期: 2023年03月10日
* 注 意: 基于 EasyX 图形库
*********************************************************************************************************/
int main(void)
{
static gd_GIF s_structGIF;
int xoff, yoff;
//初始化
InitLCD();
//设定用户参数
extern const unsigned char g_arrGifImage3[17083];
static StructGIFParam s_structGIFParam;
s_structGIFParam.buf = g_arrGifImage3;
s_structGIFParam.readCnt = 0;
s_structGIFParam.totalSize = sizeof(g_arrGifImage3);
//解析文件
if (0 != gd_open_gif(&s_structGIF, &s_structGIFParam, GIFRead, GIFLseek))
{
return -1;
}
//计算起始坐标偏移量,使得 GIF 显示到屏幕正中央(屏幕尺寸:800x480)
xoff = (800 - s_structGIF.width) / 2;
yoff = (480 - s_structGIF.height) / 2;
//为一帧图像分配动态内存
unsigned char* buffer = malloc(s_structGIF.width * s_structGIF.height * 3);
unsigned short* background = malloc(s_structGIF.width * s_structGIF.height * sizeof(unsigned short));
if ((NULL == buffer) || (NULL == background))
{
printf("Fail to malloc\r\n");
while (1) {}
}
//保存背景
for (int y = 0; y < s_structGIF.height; y++)
{
for (int x = 0; x < s_structGIF.width; x++)
{
background[y * s_structGIF.width + x] = LCDReadPoint(x + xoff, y + yoff);
}
}
//解码并显示
for (unsigned looped = 1;; looped++)
{
//获取 1 帧图像
while (gd_get_frame(&s_structGIF))
{
//解码
gd_render_frame(&s_structGIF, buffer);
//带透明度处理
if (s_structGIF.gce.transparency)
{
char* color = buffer;
for (int y = 0; y < s_structGIF.height; y++)
{
for (int x = 0; x < s_structGIF.width; x++)
{
if (gd_is_bgcolor(&s_structGIF, color))
{
LCDFastDrawPoint(x + xoff, y + yoff, background[y * s_structGIF.width + x]);
}
else
{
LCDFastDrawPoint(x + xoff, y + yoff, GifGetRgb565(color));
}
color += 3;
}
}
}
//不带透明度处理
else
{
char* color = buffer;
for (int y = 0; y < s_structGIF.height; y++)
{
for (int x = 0; x < s_structGIF.width; x++)
{
LCDFastDrawPoint(x + xoff, y + yoff, GifGetRgb565(color));
color += 3;
}
}
}
//延时
Sleep(s_structGIF.gce.delay * 10);
}
//达到了预定的播放次数
if ((looped >= s_structGIF.loop_count) && (0 != s_structGIF.loop_count))
{
break;
}
//从头开始显示
gd_rewind(&s_structGIF);
}
free(buffer);
free(background);
//主循环
while (1)
{
//延时 25ms
Sleep(25);
}
return 0;
}
实验结果
实验结果如下所示。
-GIF-显示-实验结果-20230403.gif)
源码
本章节中的源码请参考《单片机 GUI 设计(零)- 大纲》