使用libgif库解码全过程(C语言)-包括扩展块的处理

我看到的所有例程,都把扩展部分的处理跳过了,而我的动画是有透明度的,这就导致解码后的图像在有透明色的像素部分,呈现了很多的黑点,或者闪白的情况出现。经过调试,终于成功。

文件格式

先了解一下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;
                }

                // 透明色索引值,如果没有透明色,该值为-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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值