解决gif导出后显示异常的现象

news2024/11/14 2:11:05

解决gif导出后显示异常的现象

背景:

上次gif支持透明度后,https://blog.csdn.net/c553110519/article/details/127757148?spm=1001.2014.3001.5501, 发现当输入是动态的时候,会出现异常现象

如下所示:

 

现象原因分析

刚开始怀疑是是输入的调色盘有问题

后来经过写测试代码把输入调色盘,转化成rgba数据后,发现输入调色盘数据并没有异常问题,从而可以断定是gif编码或者解码过程出现的问题

由于我们用的ffmpeg版本是3,仔细看了关于解码的问题,发现了一些代码比较可疑,如下所示:

C++

    if (avpkt->size >= 6) {
        s->keyframe = memcmp(avpkt->data, gif87a_sig, 6) == 0 ||
                      memcmp(avpkt->data, gif89a_sig, 6) == 0;
    } else {
        s->keyframe = 0;
    }
   
   
        if (s->keyframe) {
       .......//这里代码先删除,方便理解
        } else {
            if ((ret = ff_reget_buffer(avctx, s->frame)) < 0)
                return ret;
        ........
        }
 

从上边 截取的源码可以看到,当第一帧gif会带有标志的,所以s->keyframe=1,而接下来的帧不带上边标志位,所以s->keyframe= 0,紧接着下边的s->keyframe的判断,当s->keyframe=0的时候

可以看到ff_reget_buffer(avctx, s->frame), 这个获取s->frame是上次解码gif的frame,所以没有清屏,接下来看下具体解码的过程

C++
  for (y = 0; y < height; y++) {
        int count = ff_lzw_decode(s->lzw, s->idx_line, width);
        if (count != width) {
            if (count)
                av_log(s->avctx, AV_LOG_ERROR, "LZW decode failed\n");
            goto decode_tail;
        }

        pr = ptr + pw;

        for (px = ptr, idx = s->idx_line; px < pr; px++, idx++) {
            if (*idx != s->transparent_color_index)
                *px = pal[*idx];
        }
}

重点看下红色加深的地方,这里会判断索引是不是透明度,如果当前不是透明度的索引,就赋值,否则就用frame里边的,刚刚上边frame是上一帧的数据,并没有清掉,因此可以得出结论,当前叠加上一帧最终导致开始那种现象。

知道了原因,就找下下边解决过程

解决过程:

首先还是阅读源码试下是否有可以规避的地方,找到了如下代码

C++
static int gif_read_image(GifState *s, AVFrame *frame)
{
    .....................
    /* process disposal method */
    if (s->gce_prev_disposal == GCE_DISPOSAL_BACKGROUND) {
        gif_fill_rect(frame, s->stored_bg_color, s->gce_l, s->gce_t, s->gce_w, s->gce_h);
    } else if (s->gce_prev_disposal == GCE_DISPOSAL_RESTORE) {
        gif_copy_img_rect(s->stored_img, (uint32_t *)frame->data[0],
            frame->linesize[0] / sizeof(uint32_t), s->gce_l, s->gce_t, s->gce_w, s->gce_h);
    }
    .....................
}

在gif 解码中看到了s->gce_prev_disposal == GCE_DISPOSAL_BACKGROUND 的时候,会把frame中数据全部设置成s->stored_bg_color(0x00fffffff),这个透明度是0,这不就是我想要的吗,接着就看下gce_prev_disposal 值来源,在下边代码中

C++
static int gif_read_extension(GifState *s)
{
   .......
    switch(ext_code) {
    case GIF_GCE_EXT_LABEL:
  .......
        gce_flags    = bytestream2_get_byteu(&s->gb);
        bytestream2_skipu(&s->gb, 2);    // delay during which the frame is shown
   .......
 
        s->gce_disposal = (gce_flags >> 2) & 0x7;
      
        break;
    }
   .......

    return 0;
}

可以看到gce_prev_disposal  来自于s->gce_disposal,是从gif文件中读取的,而刚刚那个gif的值是1,但是我们需要它等于GCE_DISPOSAL_BACKGROUND(2),也就是说要想办法让s->gce_disposal = 2,那就要去找gif 写文件那块代码了,看下这个值是怎么填进去的

C++
static int flush_packet(AVFormatContext *s, AVPacket *new)
{
    ........
    /* graphic control extension block */
    avio_w8(pb, 0x21);
    avio_w8(pb, 0xf9);
    avio_w8(pb, 0x04); /* block size */
    avio_w8(pb, 1<<2 | (bcid >= 0));
    avio_wl16(pb, gif->duration);
    avio_w8(pb, bcid < 0 ? DEFAULT_TRANSPARENCY_INDEX : bcid);
    avio_w8(pb, 0x00);
    .......

    return 0;
}
 

黄色的阴影的代码,写死了是1,也就是gif写文件的时候写死是,那是不是把它改成2就可以了,抱着试试的心态,改成2,导出gif是下边的样子

 

这就比较气人了,图像指令中间变成了透明了,但是残影没有了,说明思路是对的,接下来就找下为什么出现这个现象。

经过一番折腾后,找到了gif编码里边的可疑代码

C++
static int gif_image_write_image(AVCodecContext *avctx,
                                 uint8_t **bytestream, uint8_t *end,
                                 const uint32_t *palette,
                                 const uint8_t *buf, const int linesize,
                                 AVPacket *pkt)
{
    GIFContext *s = avctx->priv_data;
    int honor_transparency = (s->flags & GF_TRANSDIFF) && s->last_frame;


    /* Crop image */
    if ((s->flags & GF_OFFSETTING) && s->last_frame && !palette) {
        const uint8_t *ref = s->last_frame->data[0];
        const int ref_linesize = s->last_frame->linesize[0];
        int x_end = avctx->width  - 1,
            y_end = avctx->height - 1;

        /* skip common lines */
        while (y_start < y_end) {
            if (memcmp(ref + y_start*ref_linesize, buf + y_start*linesize, width))
                break;
            y_start++;
        }
        while (y_end > y_start) {
            if (memcmp(ref + y_end*ref_linesize, buf + y_end*linesize, width))
                break;
            y_end--;
        }
        height = y_end + 1 - y_start;

        /* skip common columns */
        while (x_start < x_end) {
            int same_column = 1;
            for (y = y_start; y <= y_end; y++) {
                if (ref[y*ref_linesize + x_start] != buf[y*linesize + x_start]) {
                    same_column = 0;
                    break;
                }
            }
            if (!same_column)
                break;
            x_start++;
        }
        while (x_end > x_start) {
            int same_column = 1;
            for (y = y_start; y <= y_end; y++) {
                if (ref[y*ref_linesize + x_end] != buf[y*linesize + x_end]) {
                    same_column = 0;
                    break;
                }
            }
            if (!same_column)
                break;
            x_end--;
        }
        width = x_end + 1 - x_start;

    }
 

可疑看到s->flag 如果是GF_TRANSDIFF 或者GF_OFFSETTING,会进入下边的代码,会比较与上一帧的差异,那就看下s->flag是在哪里赋值的,找了整个文件都没看到哪里有初始化它,那说明调用它的方式在其他地方

C++
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
{

    if (codec->priv_data_size > 0) {
        if (!avctx->priv_data) {
            avctx->priv_data = av_mallocz(codec->priv_data_size);
            if (!avctx->priv_data) {
                ret = AVERROR(ENOMEM);
                goto end;
            }
            if (codec->priv_class) {
                *(const AVClass **)avctx->priv_data = codec->priv_class;
                av_opt_set_defaults(avctx->priv_data);
            }
        }
        if (codec->priv_class && (ret = av_opt_set_dict(avctx->priv_data, &tmp)) < 0)
            goto free_and_end;
    } else {
        avctx->priv_data = NULL;
    }
    if ((ret = av_opt_set_dict(avctx, &tmp)) < 0)
        goto free_and_end;
}

C++
#define OFFSET(x) offsetof(GIFContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
static const AVOption gif_options[] = {
    { "gifflags", "set GIF flags", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = GF_OFFSETTING|GF_TRANSDIFF}, 0, INT_MAX, FLAGS, "flags" },
        { "offsetting", "enable picture offsetting", 0, AV_OPT_TYPE_CONST, {.i64=GF_OFFSETTING}, INT_MIN, INT_MAX, FLAGS, "flags" },
        { "transdiff", "enable transparency detection between frames", 0, AV_OPT_TYPE_CONST, {.i64=GF_TRANSDIFF}, INT_MIN, INT_MAX, FLAGS, "flags" },
    { NULL }
};
 

 原来在codec open的时候会调用av_opt_set_dict 把gif编码器的gif_options 循环一遍,去设置对应的默认值,看下flag默认值{.i64 = GF_OFFSETTING|GF_TRANSDIFF},正好与上边分析一致,那我们知道这个字典外部是可以通过调用ffmpeg接口改掉的,我就把他改成0试下,

gif导出如下所示:

 

完美,最终解决了这个问题,前后花了三四个小时, 不过还是值得的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]Python计算机毕业设计Django校园招聘微信小程序

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

原理说明书艾美捷魔力红组织蛋白酶B活性分析试剂盒

艾美捷ICT魔力红组织蛋白酶B活性分析试剂盒被研究人员用来定量和监测培养细胞和组织中的组织蛋白酶活性。 Quantitate and monitor intracellular cathepsin-B activity over time in vitro. The Magic Red substrate in this assay fluoresces red upon cleavage by active ca…

torch.nn.functional

非线性激活函数 torch.nn.functional.threshold(input, threshold, value, inplaceFalse)torch.nn.functional.relu(input, inplaceFalse)torch.nn.functional.relu6(input, inplaceFalse)torch.nn.functional.elu(input, alpha1.0, inplaceFalse) torch.nn.functional.leaky_…

智云通CRM:如何使用万能的“三问”,提升业绩?

我们在销售产品时&#xff0c;客户往往不知道自己需要什么。如果在这样的情况下贸然推销产品&#xff0c;很容易遭到客户的厌烦。 因此&#xff0c;销售要做的就是尽可能地帮助客户整理头绪&#xff0c;激发他们的购买欲。要做到这一点&#xff0c;只需要三句话&#xff1a; …

led护眼灯有蓝光吗?双十二选led护眼灯的好处有哪些

现在的人造灯光&#xff0c;任何产品都不可能完全避免蓝光&#xff0c;市面上宣传的所谓完全无蓝光其实就是虚假宣传&#xff0c;一个是技术上几乎做不到完全无蓝光&#xff0c;另一个就是这也完全没必要&#xff0c;因为要达到比较好的光线显色效果&#xff0c;就必须要保证全…

Elasticsearch的高级查询

目录 一、条件查询 1、单条件 1&#xff09;路由查询 2&#xff09;body体查询 2、多条件查询 1&#xff09;and---must 2&#xff09;or---should 3&#xff09;范围---filter 3、全文检索、完全匹配、高亮显示 二、聚合查询 1、分组 2、求平均值 一、条件查询 1、单…

通过虚拟机搭建个人NAS

通过虚拟机搭建个人NAS 1 搭建黑群辉NAS 前期资料&#xff1a; 黑群晖系统 天翼云&#xff1a;https://cloud.189.cn/t/Vj2QRbFzq6Nn&#xff08;访问码&#xff1a;i94s&#xff09; 百度网盘&#xff1a;链接:https://pan.baidu.com/s/1MqimGhZnOTIcYfZhY5Z4lw 提取码:75n…

JSP 视频点播系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 视频点播系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发&#xff0c;数据库为Mysql&#xff0c;使用ja…

【数据集NO.5】小目标检测数据集汇总

文章目录前言一、TinyPerson数据集二、密集行人检测数据集三、加州理工学院行人检测数据集前言 数据集对应应用场景&#xff0c;不同的应用场景有不同的检测难点以及对应改进方法&#xff0c;本系列整理汇总领域内的数据集&#xff0c;方便大家下载数据集&#xff0c;若无法下…

大数据-Hadoop部署模式

一、JDK安装与配置 1、下载JDK压缩包 2、上传到master虚拟机 将JDK压缩包上传到master虚拟机/opt目录 查看上传的JDK压缩包 3、在master虚拟机上安装配置JDK 执行命令&#xff1a;tar -zxvf jdk-8u231-linux-x64.tar.gz -C /usr/local&#xff0c;将JDK压缩包解压到指定目…

【VUE3】保姆级基础讲解(一):初体验与指令

目录 安装和引入 CDN 本地引入 VUE初体验 MVVM模型 data属性 与 methods属性 VUE基础-模板语法 Mustache双大括号语法 v-once指令 v-html v-pre v-cloak v-memo v-bind&#xff08;重要&#xff09; 对象语法 v-bind直接绑定对象 v-on 条件渲染 v-for 基础使…

antd级联选择器(a-cascader)动态加载和动态回显效果实现

文章目录1、介绍2、效果图如下图所示&#xff1a;&#xff08;只实现3层的&#xff09;3、实现方法&#xff08;1&#xff09;层级可单独选择&#xff08;2&#xff09;组件使用&#xff08;3&#xff09;data数据&#xff08;4&#xff09;实现动态加载数据1、 提示&#xff1…

模糊预测|RFIS与ANFIS模糊模型预测的比较(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

vue2中实现右键菜单

vue2中实现右键菜单 效果图-右键菜单 1、安装 npm install vue-contextmenujsyarn add vue-contextmenujsCDN <script src"https://unpkg.com/vue-contextmenujs/dist/contextmenu.umd.js">2、使用 2.1、引入 src/main.js import Contextmenu from "…

python sklearn knn快速实现,保姆级教学

目录介绍KNN实战加载模块读取数据训练、测试数据分割关键环节&#xff1a;训练预测sklearn官方代码实例介绍 首先上链接 https://www.sklearncn.cn/ scikit-learn是基于Python语言的机器学习库&#xff0c;具有&#xff1a; 简单高效的数据分析工具 可在多种环境中重复使用 …

一套Altair Feko复杂结构模型散射和天线辐射仿真建模攻略

导读&#xff1a;Feko软件广泛应用于电磁散射、电磁辐射仿真&#xff0c;例如&#xff1a;天线、天线布局、天线罩、屏蔽效能、电磁散射、频选结构、线束EMC等方面。问题种类繁多&#xff0c;但是无论仿真哪一类问题&#xff0c;其仿真流程是相同的&#xff0c;我们只需掌握了这…

VirtualBox Ubuntu 16.04 磁盘不相邻分区扩容解决方案

前言 博主做期末大作业时用到 VirtualBox 6.1 安装的 Ubuntu 16.04 LTS 虚拟机&#xff0c;开始只分配了 20GB 硬盘&#xff0c;跑实验时发现空间不够&#xff0c;需要对磁盘扩容&#xff0c;折腾了半天&#xff0c;在此做一个小记录。 警告&#xff1a;博主并不精通 Linux&am…

风险评估具体操作流程

概述 风险评估应贯穿于评估对象生命周期 各阶段中。评估对象生命周期各阶段中涉及的风险评估原则和方法昆一致的&#xff0c;但由干各阶段实施内容对象、安全需求不同.使得风险评估的对象、目的、要求等各方面也有所不同。在规划设计阶段&#xff0c;通过风险评估以确定评估对…

摄影师接单小程序开发,自由交易平台

在网红直播经济的强势发展下&#xff0c;年轻一代对于摄影方面的需求急速增长&#xff0c;但是年轻人群体在摄影方面的要求更趋向于个性化&#xff0c;普通的影楼不仅拍摄价格高&#xff0c;在拍摄风格上也比较单调&#xff0c;缺乏创新&#xff0c;难以满足用户消费需求。对于…

cubeIDE开发, stm32的ADC(模数转换器) 开发要点

一、ADC模数转换简介 ADC(Analog-to-Digital Converter&#xff0c;模数转换器) 是将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号&#xff0c;例如温度、压力、声音或者图像等&#xff0c;需要转换成更容易储存、处理和发射的数字形式。模数转换器可以实…