跳转到内容

动图只剩第一帧或变成纵向长图

复制本页

动图只剩第一帧、变成纵向长图或出现帧错位时,先分别检查加载页数、page-height 与动画元数据。它们是三个相关但不能混为一谈的问题。

  • GIF/WebP/AVIF 动图导出后变成单一静态帧。
  • 缩放动图后出现帧错位,图像看起来像“卷纸”(toilet-roll),或报 bad grid geometry。
  • 想把多张帧拼成一个动画时,得到一条超长单图而不是多页动画。
  • 动图的 loop 次数与帧延迟在导出后丢失。

根因:libvips 的多页(multi-page/pages)模型

Section titled “根因:libvips 的多页(multi-page/pages)模型”

libvips 把 GIF、动图 WebP、AVIF、PDF、多页 TIFF 统一建模为“多页图像”。n(页数)参数决定加载多少页:

  • 默认 n=1:只取第一页(对动图而言就是第一帧)。
  • n=-1:加载全部页。

govips 通过 ImportParams.NumPages 暴露这一语义。如果不设置,动图只保留第一帧。 这是绝大多数“动画丢了”问题的根源。

import "github.com/davidbyttow/govips/v2/vips"
// loadAnimation 返回由调用方负责 Close 的多页图像。
func loadAnimation(buf []byte) (*vips.ImageRef, error) {
params := vips.NewImportParams()
params.NumPages.Set(-1)
return vips.LoadImageFromBuffer(buf, params)
}

任何“按整图操作”的算子(resize / rotate / flip / zoom)在动图上都必须先用 NumPages=-1 加载全部帧,否则只作用于第一帧或产生帧错位。

2. 用多页拼接生成动画时,记住 SetPageHeight

Section titled “2. 用多页拼接生成动画时,记住 SetPageHeight”

把多帧拼成动画的正确方式是纵向拼接后,用 SetPageHeight() 把长图切回等高的多页,并设置 SetPageDelay 控制帧间隔。若漏掉 SetPageHeight,导出结果是“一条超长单图”——这正是多个 issue 里“拼成了长图”的成因。

3. loop 与帧延迟元数据需要显式保留或设置

Section titled “3. loop 与帧延迟元数据需要显式保留或设置”

部分源(如 GIF87a)不带 loop 计数,导出后动画 loop 语义可能丢失。若依赖动画循环次数,请在导出或元数据处理时显式设置 loop 与 delay,不要假定它们会随源自动保留。

  • 动图只取第一帧但确实是主动需求(例如只想生成预览缩略图),此时不要误以为动画是 bug。
  • toilet-roll 帧错位在特定 scale 下有官方修复版本;若版本较旧,先升级 govips 再排查自身代码。
  • 本页的 API 与边界按 govips v2.19.0 复核;具体格式是否支持动画仍取决于运行时 libvips 构建。

pyvips、sharp 与 CLI 对动图页数的默认行为仍需逐一验证;本页不把 govips 的默认值推广到其他 binding。