动画图像(Animated GIF 与 WebP)与多帧垂直条带模型
libvips 没有专门的三维“视频帧数组”结构。它在处理 Animated GIF 或 Animated WebP 时,采用了一种极具特色的设计——垂直长卷模型(Toilet-roll image):当你在加载时指定 n=-1(或 NumPages: -1 / animated: true),libvips 会把所有动画帧沿垂直方向(Y 轴)拼接成一张超高的二维图像。
1. 理解垂直长卷(Toilet-roll)的尺寸与元数据
Section titled “1. 理解垂直长卷(Toilet-roll)的尺寸与元数据”假设一个 GIF 动图的单帧分辨率为 400 × 300,共包含 12 帧:
- 默认加载(
n=1):只解码第 1 帧,Width = 400,Height = 300。 - 全帧加载(
n=-1):全部 12 帧沿 Y 轴拼接:Width = 400Height = 3600(即300 × 12)PageHeight = 300(存储在图像元数据page-height中,标识单帧高度)Pages = 12(原文件的总帧数)PageDelay(每帧停留时长数组,单位毫秒ms)与Loop(循环次数,0表示无限循环)
| 操作或属性 | govips (v2.20.0) |
sharp (0.35.5) |
pyvips (3.2.0) |
vips CLI (8.17.3) |
|---|---|---|---|---|
| 加载全部动画帧 | ImportParams.NumPages.Set(-1) |
sharp(input, { animated: true }) |
new_from_file("in.gif", n=-1) |
'in.gif[n=-1]' |
| 获取单帧高度 | image.PageHeight() |
meta.pageHeight |
image.get("page-height") |
vipsheader -f page-height |
| 获取/设置帧延迟 | image.PageDelay() / SetPageDelay |
meta.delay / webp({ delay }) |
image.get("delay") |
— |
| 拆分为帧列表与重组 | 多页感知方法(自动拆合) | 自动按 pageHeight 处理 |
image.pagesplit() / Image.pagejoin(frames) |
— |
2. 将 Animated GIF 缩放并转换为 Animated WebP
Section titled “2. 将 Animated GIF 缩放并转换为 Animated WebP”Animated WebP 支持 24-bit RGB + 8-bit 半透明 Alpha,通常比 256 色的 GIF 体积小 50% ~ 80% 且没有颜色抖动噪点。
在 govips v2.20.0 中,ExtractArea、Resize / ResizeWithVScale、Embed、Zoom、Flip 与 90/180/270 度 Rotate 会保持多页长卷的帧序和 page-height;2.20 修复了 Rotate、Flip、Zoom 与非整数单帧缩放可能破坏帧几何的问题。Similarity、SmartCrop 与 Gravity 仍不能据此视为逐帧安全。在 pyvips 中,可以使用 pagesplit() 与 pagejoin() 显式逐帧处理:
package main
import ( "fmt" "os"
"github.com/davidbyttow/govips/v2/vips")
func main() { if err := vips.Startup(nil); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer vips.Shutdown()
// 1. 设置 NumPages = -1 加载全部动画帧 importParams := vips.NewImportParams() importParams.NumPages.Set(-1)
anim, err := vips.LoadImageFromFile("animated.gif", importParams) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } defer anim.Close()
fmt.Printf("Total strip: %dx%d, single frame PageHeight=%d, Pages=%d\n", anim.Width(), anim.Height(), anim.PageHeight(), anim.Pages())
// 2. 缩放到 50%:govips 的 Resize 会自动感知多页并同步更新 PageHeight if err := anim.Resize(0.5, vips.KernelLanczos3); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }
// 3. 导出为 Animated WebP(保留原有的帧延迟与循环次数) webpParams := vips.NewWebpExportParams() webpParams.Quality = 80 webpParams.ReductionEffort = 4
data, _, err := anim.ExportWebp(webpParams) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } if err := os.WriteFile("animated.webp", data, 0o644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) }}import sharp from 'sharp';
// animated: true(或 pages: -1)加载所有动画帧;resize 与 webp 会自动维护 pageHeight 与帧延迟await sharp('animated.gif', { animated: true }) .resize({ width: 320 }) .webp({ quality: 80, effort: 4, loop: 0, // 0 表示无限循环 }) .toFile('animated.webp');import pyvips
# 方法 A:直接使用 thumbnail(libvips 的 thumbnail 原生感知 page-height)anim = pyvips.Image.thumbnail("animated.gif[n=-1]", 320)anim.write_to_file("animated.webp", Q=80, effort=4)
# 方法 B:使用 pyvips 3.2.0 的 pagesplit() / pagejoin() 对每一帧做独立裁剪或变换full = pyvips.Image.new_from_file("animated.gif", n=-1)frames = [frame.crop(10, 10, 200, 150) for frame in full.pagesplit()]rejoined = pyvips.Image.pagejoin(frames)rejoined.write_to_file("cropped-animated.webp", Q=80)#!/usr/bin/env bashset -euo pipefail
# thumbnail 原生支持 [n=-1] 多页动图并自动更新 page-height 元数据vips thumbnail 'animated.gif[n=-1]' 'animated.webp[Q=80,effort=4]' 3203. 动图处理的三大高频事故与防御
Section titled “3. 动图处理的三大高频事故与防御”- 直接对整条长卷调底层
crop或resize导致帧错位:- 在
pyvips或vips CLI中,如果你对n=-1的图像直接调用image.crop(left, top, width, height),它只会从第 1 帧切出一块并丢失其余帧(或者忘记更新page-height导致导出时报错Image height is not a multiple of page-height)。在pyvips中请使用pagesplit()逐帧操作后再pagejoin();在govips v2.20.0中,ExtractArea已内置逐页裁剪支持。
- 在
- 用总高度
Height()判断单帧尺寸超限:- 一个
500 × 500、共100帧的动图,加载后Height()高达50,000像素!如果你的网关在加载后用Height() > 10000拦截大图,所有稍长的 GIF 都会被误杀;反之,如果不限制总像素数(Width × PageHeight × Pages),恶意构造的千帧 GIF 会瞬间耗尽内存(详见不可信输入防护)。
- 一个
- GIF 导出颜色量化开销:
- 将真彩色图像或 WebP 动图导出回
.gif(ExportGIF/.gif()/gifsave)需要调用cgif与调色板量化,CPU 与内存开销显著高于导出 Animated WebP。
- 将真彩色图像或 WebP 动图导出回