建网站培训学校营销软文写作
我看到的所有例程,都把扩展部分的处理跳过了,而我的动画是有透明度的,这就导致解码后的图像在有透明色的像素部分,呈现了很多的黑点,或者闪白的情况出现。经过调试,终于成功。
文件格式
先了解一下GIF的文件格式:
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;}// 透明色索引值,如果没有透明色,该值为-1transparent_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;}// 透明色索引值,如果没有透明色,该值为-1transparent_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);
#endifif (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;
}