动图只剩第一帧或变成纵向长图
动图只剩第一帧、变成纵向长图或出现帧错位时,先分别检查加载页数、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)}1. 转换动图前先加载全部页
Section titled “1. 转换动图前先加载全部页”任何“按整图操作”的算子(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,不要假定它们会随源自动保留。
受影响版本与边界
Section titled “受影响版本与边界”- 动图只取第一帧但确实是主动需求(例如只想生成预览缩略图),此时不要误以为动画是 bug。
toilet-roll帧错位在特定 scale 下有官方修复版本;若版本较旧,先升级 govips 再排查自身代码。- 本页的 API 与边界按 govips v2.19.0 复核;具体格式是否支持动画仍取决于运行时 libvips 构建。
- govips #445 — GIF 转 WebP 动画丢失
- govips #449 — ArrayJoin + SetPageHeight 得到长图
- govips #350 — animated resize toilet-roll
- govips #500 — fix animated resize producing toilet-roll
- govips #554 — rotate/flip 破坏帧序与 bad grid geometry
- govips #381 / #503 — loop 与 delay 元数据
- govips v2.19.0 README
pyvips、sharp 与 CLI 对动图页数的默认行为仍需逐一验证;本页不把 govips 的默认值推广到其他 binding。