GIF 太大怎么压?调帧率、尺寸、颜色数,10 组参数体积对照实测
当前位置:点晴教程→知识管理交流
→『 技术文档交流 』
同一段 60 帧的动画,在同一个页面里换参数重编,最小的 GIF 是 11372 字节,最大的是 95206 字节。 差了 8.4 倍。两者内容一模一样,只有输出宽度、帧率、颜色数和抖动开关不同。 我把整条链路做成了一个能点开的网页:GIF89a 编码、中位切分调色板、LZW 压缩、Floyd-Steinberg 抖动全部自己实现,10 组参数一键对照,跑完直接给体积、耗时和 PSNR。
一、为什么值得看:GIF 的体积是几个参数乘出来的GIF 的压缩能力其实很弱。 它只有一张最多 256 色的调色板,每个像素只存调色板下标,再叠一层 LZW 无损压缩。 所以体积基本由三件事决定:像素总量(宽 × 高 × 帧数)、颜色数、画面里有没有高频细节。 我拿同一段示例动画把这三件事各推了一遍。 颜色数从 16 加到 256,体积从 31461 字节涨到 90079 字节,2.9 倍。 帧率从 10 提到 20,同尺寸下从 16761 字节涨到 90079 字节,5.4 倍。 宽度从 128 换成 256,像素数翻 4 倍,16 色那组从 11372 字节到 31461 字节。 三个参数乘在一起,8.4 倍的落差就出来了。 顺带说一个反直觉的点:加抖动会把体积顶上去,同一组参数下 90079 字节变 95206 字节,涨了 5.7%。 抖动不省体积,它是拿体积换视觉上的平滑。 二、准备环境:打开码道 Web 就能用码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。 浏览器打开码道 Web 版:devcloud.cn-north-4.huaweicloud.com/chat?source… 登录之后直接在对话窗口里说需求,不需要装任何软件。 三、任务描述:把这段需求原文丢给码道下面是我实际发给码道的那段话,一字未改,复制过去就能复现同一个应用。 四、码道做了什么:一个页面把编码链路全摊开它交付了一个 39 KB 的单文件 左侧是参数区:输出宽度、帧率(抽帧)、颜色数(2~256 的 2 的幂)、抖动开关、循环次数。 中间是两幅对照:左边是原始帧序列的首帧,右边是刚编出来的 GIF 在页面里播放。 下面一排统计:原始帧数、原始尺寸、输出字节、输出帧数、实际用色数、编码耗时、压缩比、平均色差和 PSNR。 底部还有两块,一块是 10 组参数对照面板,一块是 12 项页内自检。
编码器是页面自己写的,三步:先把所有帧的像素采样出来做中位切分,切出目标数量的代表色当调色板;再把每个像素换成最近的颜色下标,开抖动时用 Floyd-Steinberg 把量化误差扩散给右边和下边的像素;最后把下标流交给 LZW 编码器,按 GIF89a 的结构(文件头、全局调色板、每帧的图形控制块和图像描述符、子块、结尾 0x3B)拼成字节流。 这段代码量不小,但它是这个应用真正值钱的部分——体积、颜色、耗时三个数字,全都是从这条链路里长出来的。 五、实测:10 组参数,体积差 8 倍页面上「运行 10 组参数对照」跑完,10 组结果一字不差抄在下面。
挑几条我读完之后的判断。 第 1 组体积最小,11372 字节,但 PSNR 只有 20.86 dB,颜色只剩 16 种,画面肉眼可见地发脏。它是最小的,不是最合适的。 第 9 组和第 10 组只差了抖动开关,体积从 90079 涨到 95206,耗时从 80 毫秒涨到 141 毫秒,PSNR 反而从 56.77 掉到 55.55。 也就是说抖动在这段素材上花了 5.7% 的体积和 76% 的额外时间,换来的不是更低的误差,而是更平滑的过渡。 如果你只是要压体积,先动颜色数和帧率,抖动是最后才考虑的选项。 颜色数的边际收益在 64 色之后就很小了:64 到 256 色,体积涨 2.3 倍,PSNR 只从 44.53 dB 提到 56.47 dB。 压缩比那栏要解释一句:它是相对未压缩的 RGBA 原始像素算的。60 帧 256×256 的原始像素是 15360 KB,编完 88.0 KB,所以写 174.61:1。这个数字说明不了「比原视频小多少」,别拿它当宣传。 六、验证与踩坑:自检 12 项全绿,生成的 GIF 却打不开页面自带 12 项自检,跑完 12 项全绿。
但第一版交付的时候,这 12 项里第 7 到 12 项是红的,而且红得很具体: 我做了三次验证,一次比一次狠。 第一遍把导出的 第二遍把同一个文件交给 Python 的 Pillow,报错 第三遍我把编码器和解码器分别抽出来,在 Node 里单独跑,构造一段 20 帧的合成图,结果稳定复现:编码出来的码流解不回去。 根因是 LZW 的码宽升级时机差了一个码。 GIF 的 LZW 里,编码器每往字典里加一项,都要判断当前码宽够不够放下所有可能的码号;这个判断早一位或者晚一位,就是「文件能打开」和「文件是坏的」的区别。 第一版把「下一个可用码号等于 2 的码宽次方」当成升级点,实际上应该在「刚加进去的那个码号等于 2 的码宽次方」时才升——也就是晚一个码。 更值得记的是中间那次「修好了」的假象。 修完第一版,页面自检 7 到 12 项变绿了,看起来一切正常;但导出的文件仍然是坏的。 原因是那次只改了页面里的解码器,把它改成和错的编码器同一个偏法,于是「自己编、自己解」能对上,「标准解码器」对不上。 不要用自己的解码器去验证自己的编码器。 这条规则在压缩、序列化、协议、加密里都成立。 正确的修法是两头都对齐标准:编码器按 失败路径我也点了一遍。 把输出宽度填成 颜色数不是 2 的幂会提示「颜色数必须是 2~256 之间 2 的幂(2/4/8/16/32/64/128/256)」,帧率填 0 会提示「帧率必须大于 0」,循环次数填负数会提示「循环次数不能为负」。
七、本地复现
我自己的验收动作是三步:先看默认结果是不是 88.0 KB 左右的 GIF、能不能在页面里动起来;再把颜色数从 256 调到 32、打开抖动,看体积和 PSNR 是不是按上表那样变化;最后把宽度清成 0,确认提示是「宽度必须大于 0」而且不清空上一次的结果。 八、使用码道体会
直接下载试玩这个 Demo 已经完整开源(源代码、本文档、全部截图),托管在公开仓库,可直接下载来玩:
打开上面的链接进入
|
关键字查询
相关文章
正在查询... |