我看到的所有例程,都把扩展部分的处理跳过了,而我的动画是有透明度的,这就导致解码后的图像在有透明色的像素部分,呈现了很多的黑点,或者闪白的情况出现。经过调试,终于成功。
文件格式
先了解一下GIF的文件格式:
扩展块的处理
// 扩展块解析
if (currentImage.ExtensionBlockCount > 0) // 存在扩展块
{
switch (currentImage.ExtensionBlocks[0].Function)
{
case CONTINUE_EXT_FUNC_CODE:
case COMMENT_EXT_FUNC_CODE: // 注释扩展块
case PLAINTEXT_EXT_FUNC_CODE: // 图形文本扩展块
case APPLICATION_EXT_FUNC_CODE: // 应用程序扩展块
break;
case GRAPHICS_EXT_FUNC_CODE: // 图形控制扩展块
{
if (DGifExtensionToGCB(currentImage.ExtensionBlocks[0].ByteCount, currentImage.ExtensionBlocks[0].Bytes, &gcb) == GIF_ERROR)
{
continue;
}
// 透明色索引值,如果没有透明色,该值为-1
transparent_color = gcb.TransparentColor;
// 延迟时间的单位为0.01秒,转换为微秒
delay_us = gcb.DelayTime * 10 * 1000;
}
default:
break;
}
}
//转换
//显示
if (gcb.DisposalMode == DISPOSE_BACKGROUND) // 回复到背景色
{
memset(gif_buffer, fp->SBackGroundColor, size);
}
else if (gcb.DisposalMode == DISPOSE_PREVIOUS) // 回复到先前状态(上一帧)
{
for (int i = 0; i < size; i++)
{
prev_gif_buffer[i] = gif_buffer[i];
}
}
透明度处理
在有透明的的情况下,GIF图像转为RGB需要进行以下判断:
// 颜色索引等于透明度索引,进行透明处理,这里直接跳过,保持原像素值(上一帧此索引位置的像素)不变
if (color_index == transparent_index)
{
rgb_index += 3;
}
else
{
// 将颜色项的红色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Red;
// 将颜色项的绿色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Green;
// 将颜色项的蓝色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Blue;
}
如果使用了透明色,要确定在解析时,解析到这一个像素时,不要对这个值进行任何操作,跳过即可。跳过后,你会发现上一帧这个位置的图像又出现在这个位置,符合预期,显示正常。
全部代码
全部代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <giflib/gif_lib.h>
int fd = 0;
/** GIF图像转换为RGB888图像字节处理)
* @name: Gif_To_Rgb888
* @param {ColorMapObject} *ColorMap 颜色表
* @param {uint8_t} *rgb 解码后的rgb图像(输出)
* @param {GifByteType} *gif_buf gif图像
* @param {int} gif_width gif图像宽度
* @param {int} gif_height gif图像高度
* @param {int} transparent_index gif透明度索引值
* @return {*}
*/
static void Gif_To_Rgb888(ColorMapObject *ColorMap, uint8_t *rgb, GifByteType *gif_buf, int gif_width, int gif_height, int transparent_index)
{
int index_h = 0; // 循环变量,用于遍历图像的高度方向
int index_w = 0; // 循环变量,用于遍历图像的宽度方向
// 外层循环遍历图像的每一行
for (index_h = 0; index_h < gif_height; index_h++)
{
// rgb_buf数据位置索引
int rgb_index = index_h * gif_width * 3;
// 内层循环遍历当前行的每一个像素
for (index_w = 0; index_w < gif_width; index_w++)
{
// 如果存在颜色表,进行转换操作
if (ColorMap)
{
// 获取当前像素在GIF缓冲区中的索引
int color_index = gif_buf[index_h * gif_width + index_w];
// 颜色索引等于透明度索引,进行透明处理,这里直接跳过,保持原像素值(上一帧此索引位置的像素)不变
if (color_index == transparent_index)
{
rgb_index += 3;
}
else
{
// 将颜色项的红色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Red;
// 将颜色项的绿色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Green;
// 将颜色项的蓝色分量写入输出缓冲区
rgb[rgb_index++] = ColorMap->Colors[color_index].Blue;
}
}
}
}
}
/**
* @name: Display_RGB_From_Buffer
* @param {uint8_t} *buffer
* @param {int} fb_fd
* @param {int} xres
* @param {int} yres
* @return {*}
*/
static void Display_RGB_From_Buffer(uint8_t *buffer, int gif_width, int gif_height)
{
fd = open("/dev/fb0", O_RDWR);
if (fd < 0)
{
perror("设备文件打开失败");
return;
}
// 屏幕分辨率,固定不变
int screen_size = 480 * 800 * 4;
char *fb_data = malloc(screen_size);
memset(fb_data, 0, screen_size);
for (int y = 0; y < gif_height; y++)
{
for (int x = 0; x < gif_width; x++)
{
uint8_t r = buffer[(y * gif_width + x) * 3 + 0];
uint8_t g = buffer[(y * gif_width + x) * 3 + 1];
uint8_t b = buffer[(y * gif_width + x) * 3 + 2];
// 这个索引值是显示位置的变化,现在的显示位置是在屏幕的(50,50)
int index = (y + 50) * 480 * 4 + (x + 50) * 4;
fb_data[index + 0] = b;
fb_data[index + 1] = g;
fb_data[index + 2] = r;
fb_data[index + 3] = 255;
}
}
ssize_t bytes_written = write(fd, fb_data, screen_size);
if (bytes_written != screen_size)
{
perror("Error writing to framebuffer");
}
free(fb_data);
close(fd);
}
int GIF_Decode(char *file)
{
int error; // 错误码
int size = 0; // GIF数据大小
int transparent_color = -1; // 透明色索引
int delay_us; // 延时时间,单位微秒
GifFileType *fp; // gif文件标识符
GifByteType *gif_buffer = NULL; // GIF数据
GifByteType *prev_gif_buffer = NULL; // GIF上一帧数据
GraphicsControlBlock gcb = { // GIF图像控制块
.DelayTime = 0,
.DisposalMode = 0,
.TransparentColor = -1,
.UserInputFlag = 0};
// 定义交错图像的读取偏移量和步长数组
int InterlacedOffset[] = {0, 4, 2, 1};
int InterlacedJumps[] = {8, 8, 4, 2};
// 打开GIF文件
fp = DGifOpenFileName(file, &error);
if (fp == NULL)
{
printf("打开GIF文件失败: %s\n", GifErrorString(error));
return -1;
}
// 读取GIF文件的完整信息(一次性读取所有GIF信息,存放在fp结构体内)
if (DGifSlurp(fp) == GIF_ERROR)
{
printf("解析GIF详细信息错误: %s\n", GifErrorString(fp->Error));
// 关闭GIF文件
DGifCloseFile(fp, &error);
return -1;
}
printf("GIF分辨率: %d*%d\n", fp->SWidth, fp->SHeight);
printf("总帧数: %d\n", fp->ImageCount);
// 内存空间申请、初始化
size = fp->SWidth * fp->SHeight;
uint8_t rgb[size * 3];
memset(rgb, 0, sizeof(rgb));
gif_buffer = (GifByteType *)malloc(size * sizeof(GifByteType));
if (gif_buffer == NULL)
{
printf("内存空间申请失败\n");
DGifCloseFile(fp, &error);
return -1;
}
prev_gif_buffer = (GifByteType *)malloc(size * sizeof(GifByteType));
if (prev_gif_buffer == NULL)
{
printf("内存空间申请失败\n");
DGifCloseFile(fp, &error);
return -1;
}
// 填充背景颜色
for (int i = 0; i < size; i++)
{
gif_buffer[i] = fp->SBackGroundColor;
prev_gif_buffer[i] = fp->SBackGroundColor;
}
// 遍历读取到的每一帧图像数据进行处理
for (int ImageNum = 0; ImageNum < fp->ImageCount; ImageNum++)
{
// 当前图像的信息
SavedImage currentImage = fp->SavedImages[ImageNum];
// 获取图像的位置和尺寸信息
int row = currentImage.ImageDesc.Top; // 图像位置-行
int col = currentImage.ImageDesc.Left; // 图像位置-列
int width = currentImage.ImageDesc.Width; // 图像数据宽度
int height = currentImage.ImageDesc.Height; // 图像数据高度
// 检测是否超过逻辑屏幕边界
if (currentImage.ImageDesc.Left + currentImage.ImageDesc.Width > fp->SWidth || currentImage.ImageDesc.Top + currentImage.ImageDesc.Height > fp->SHeight)
{
printf("第%d帧图像超过了屏幕边界,无法解码.\n", ImageNum);
continue;
}
// 如果图像是交错的
if (currentImage.ImageDesc.Interlace)
{
// 按照交错图像的读取方式逐行读取像素数据
for (int iH = 0; iH < 4; iH++)
{
for (int iW = row + InterlacedOffset[iH]; iW < row + height; iW += InterlacedJumps[iH])
{
// 计算当前像素在Buffer中的索引位置
int index = (iW * fp->SWidth + col);
for (int x = 0; x < width; x++)
{
gif_buffer[index + x] = currentImage.RasterBits[(iW - row) * width + x];
}
}
}
}
else
{
// 如果不是交错图像,则顺序读取像素数据
for (int iH = 0; iH < height; iH++)
{
// 计算当前像素在Buffer中的索引位置
int index = ((row + iH) * fp->SWidth + col);
for (int iW = 0; iW < width; iW++)
{
gif_buffer[index + iW] = currentImage.RasterBits[iH * width + iW];
}
}
}
// 扩展块解析
if (currentImage.ExtensionBlockCount > 0) // 存在扩展块
{
switch (currentImage.ExtensionBlocks[0].Function)
{
case CONTINUE_EXT_FUNC_CODE:
case COMMENT_EXT_FUNC_CODE: // 注释扩展块
case PLAINTEXT_EXT_FUNC_CODE: // 图形文本扩展块
case APPLICATION_EXT_FUNC_CODE: // 应用程序扩展块
break;
case GRAPHICS_EXT_FUNC_CODE: // 图形控制扩展块
{
if (DGifExtensionToGCB(currentImage.ExtensionBlocks[0].ByteCount, currentImage.ExtensionBlocks[0].Bytes, &gcb) == GIF_ERROR)
{
continue;
}
// 透明色索引值,如果没有透明色,该值为-1
transparent_color = gcb.TransparentColor;
// 延迟时间的单位为0.01秒,转换为微秒
delay_us = gcb.DelayTime * 10 * 1000;
}
default:
break;
}
}
// 获取颜色映射表,如果没有颜色映射表则退出,有局部颜色表先用局部颜色表,否则使用全局颜色表
ColorMapObject *colorMap = (currentImage.ImageDesc.ColorMap ? currentImage.ImageDesc.ColorMap : fp->SColorMap);
if (colorMap == NULL)
{
continue;
}
// 将 GIF 图像数据转换为 RGB888 格式
Gif_To_Rgb888(colorMap, rgb, gif_buffer, fp->SWidth, fp->SHeight, transparent_color);
#if 1
// LCD显示解码后的图像
Display_RGB_From_Buffer(rgb, fp->SWidth, fp->SHeight);
// 帧间隔时间
usleep(delay_us);
#endif
if (gcb.DisposalMode == DISPOSE_BACKGROUND) // 回复到背景色
{
memset(gif_buffer, fp->SBackGroundColor, size);
}
else if (gcb.DisposalMode == DISPOSE_PREVIOUS) // 回复到先前状态(上一帧)
{
for (int i = 0; i < size; i++)
{
prev_gif_buffer[i] = gif_buffer[i];
}
}
}
// 释放为Buffer分配的内存空间
free(gif_buffer);
free(prev_gif_buffer);
// 关闭GIF文件,释放资源
DGifCloseFile(fp, &error);
return 0;
}