跳转到内容

Reader/Writer 流式 I/O

复制本页

从 govips v2.19.0 开始,程序可以直接将 io.Reader 连接到 libvips,并将编码结果写入 io.Writer。这避免了先用 io.ReadAll 把完整压缩文件复制到 Go 字节切片(byte slice),也让 HTTP 请求体、对象存储流和管道能够共用同一套接口。

API 作用 访问策略
LoadImageFromReader 从任意 io.Reader 创建 ImageRef 默认实体化缓冲;显式 AccessSequential 时按需延迟读取
SaveToWriter / 格式变体 把编码块直接写入 io.Writer 取决于编码器;TIFF 等格式可能需要完整缓冲
TranscodeStream 组合 Reader、可选自动旋转和 Writer 根据 EXIF 方向信息自动选择顺序或实体化路径

这些 API 在 govips v2.17.0 中不存在;本页与可执行示例均基于 govips v2.19.0 验证。

govips 显式设置 AccessSequential,并保持输入 Reader 打开直到 ImageRef.Close()。sharp、pyvips 与 CLI 使用各自的顺序访问开关读取文件,而非 Go 的 io.Reader / io.Writer 接口。

params := vips.NewImportParams()
params.Access.Set(vips.AccessSequential)
image, err := vips.LoadImageFromReader(input, params)
if err != nil { return err }
defer image.Close()
export := vips.NewWebpExportParams()
export.Quality = 82
export.StripMetadata = true
if err := image.SaveToWriterWebp(output, export); err != nil { return err }
通过基于描述符的流式管线无落地文件导出的 1938×1292 WebP 图像
WEBP1938 × 1292,398,898 字节,3 通道

由 govips v2.19.0 / libvips 8.17.3 实际生成。

  • 格式: WEBP
  • 尺寸: 1938 × 1292
  • 通道数: 3
  • 透明通道: 无
  • 文件大小: 398,898 字节
  • 生成环境: govips v2.19.0 / libvips 8.17.3

参考实现的 Reader 与 Writer 错误传播已通过自动化测试覆盖。顺序模式下,加载函数通常只读取足够的头部字节;文件截断或流中途读取失败可能直到 SaveToWriterWebp 首次完整消费像素时才返回。

顺序路径适合缩小、裁剪、背景展平(flatten)、色彩空间转换、水平翻转和多数行卷积操作。90°/180°/270° 旋转、垂直翻转、智能裁剪(Smart Crop)等需要乱序读取像素的操作必须先完成实体化缓冲,否则会触发 out of order read 错误。

params := vips.NewImportParams()
params.Access.Set(vips.AccessSequential)
image, err := vips.LoadImageFromReader(input, params)
if err != nil {
return fmt.Errorf("load stream: %w", err)
}
defer image.Close()
if err := image.SaveToWriterWebp(output, vips.NewWebpExportParams()); err != nil {
return fmt.Errorf("save stream: %w", err)
}

如果业务需要根据 EXIF 方向决定是否实体化,可使用 TranscodeStream:

err := vips.TranscodeStream(input, output, &vips.TranscodeOptions{
Format: vips.ImageTypeWEBP,
AutoRotate: true,
ExportParams: &vips.ExportParams{
Quality: 82,
Interlaced: false,
StripMetadata: true,
},
})
if err != nil {
return fmt.Errorf("transcode stream: %w", err)
}

TranscodeStream 会让 EXIF 方向 1 和 2 留在顺序路径;需要转置或反向读取行的方向会先实体化。

  • 在 HTTP 层限制请求体总长度;SetPipeReadLimit 只限制非可寻址(non-seekable)Reader 的头部缓冲,不等于上传体积上限。
  • 顺序模式必须让 Reader 存活到 ImageRef.Close()。
  • Reader 和 Writer 的原始错误会被包装保留,可以用 errors.Is 判断。
  • 渐进式(progressive)JPEG、隔行扫描(interlaced)PNG、HEIF 解码和 TIFF 输出可能需要额外缓冲,不能仅凭 API 名称推断内存上限。
  • 需要取消显式 Load/Save 流程时,可以从另一个 goroutine 调用 image.SetKill(true);完成后应清除 kill 标志。TranscodeStream 当前不接收 context.Context。

比较顺序与实体化路径时,应固定输入、libvips/govips 版本、并发度和编码参数,并同时记录峰值常驻内存(RSS)、退出码、输出尺寸与文件大小。单次本机结果只能描述该测试环境,不能推广为所有格式和机器的性能结论。

2026-10-12 在 Fedora Linux 43、govips v2.19.0、libvips 8.17.3 上,对这张 1938 × 1292 JPEG 执行了顺序 WebP 转码测试。自动化测试确认:Writer 中途返回的错误会从 SaveToWriterWebp 包装传出;Reader 在读完约三分之一后失败,错误同样传回调用方。调用 SetKill(true) 后再导出时,求值返回错误;导出结束后应将 kill 标志重置为 false。这些测试聚焦于生命周期与错误传播,未测量高并发下的服务内存上限。

libvips 的内部追踪内存(tracked memory)只统计其自身登记的分配,不等于进程 RSS。

入口 示例实际执行的操作 与 govips Reader/Writer 的差别
sharp 0.35.5 sequentialRead: true,再编码 WebP 顺序提示作用于 sharp 的文件加载,不是 Go 的 io.Reader / io.Writer
pyvips 3.2.0 access="sequential" 这是 libvips 访问枚举,文件仍由 pyvips 打开
vips CLI 8.17.3 [access=sequential] 加载提示,不提供取消标志,也不涉及 Go 的 errors.Is 包装

四份输出都是同一尺寸的 WebP 时,仅说明产品结果的格式和尺寸对齐;顺序开关并不会把它们变成同一种流式接口。