Reader/Writer 流式 I/O
从 govips v2.19.0 开始,程序可以直接将 io.Reader 连接到 libvips,并将编码结果写入 io.Writer。这避免了先用 io.ReadAll 把完整压缩文件复制到 Go 字节切片(byte slice),也让 HTTP 请求体、对象存储流和管道能够共用同一套接口。
三个 API 的职责
Section titled “三个 API 的职责”| API | 作用 | 访问策略 |
|---|---|---|
LoadImageFromReader |
从任意 io.Reader 创建 ImageRef |
默认实体化缓冲;显式 AccessSequential 时按需延迟读取 |
SaveToWriter / 格式变体 |
把编码块直接写入 io.Writer |
取决于编码器;TIFF 等格式可能需要完整缓冲 |
TranscodeStream |
组合 Reader、可选自动旋转和 Writer | 根据 EXIF 方向信息自动选择顺序或实体化路径 |
这些 API 在 govips v2.17.0 中不存在;本页与可执行示例均基于 govips v2.19.0 验证。
可执行的顺序流示例
Section titled “可执行的顺序流示例”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 = 82export.StripMetadata = trueif err := image.SaveToWriterWebp(output, export); err != nil { return err }await sharp('input.jpg', { sequentialRead: true }) .webp({ quality: 82 }) .toFile('streamed.webp');image = pyvips.Image.new_from_file("input.jpg", access="sequential")image.write_to_file("streamed.webp", Q=82, keep="none")vips copy 'input.jpg[access=sequential]' 'streamed.webp[Q=82,keep=none]'检查生成结果
Section titled “检查生成结果”
由 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 首次完整消费像素时才返回。
顺序路径与实体化路径
Section titled “顺序路径与实体化路径”顺序路径适合缩小、裁剪、背景展平(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 留在顺序路径;需要转置或反向读取行的方向会先实体化。
不可信输入和错误边界
Section titled “不可信输入和错误边界”- 在 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。
另外三个入口的边界
Section titled “另外三个入口的边界”| 入口 | 示例实际执行的操作 | 与 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 时,仅说明产品结果的格式和尺寸对齐;顺序开关并不会把它们变成同一种流式接口。
- 阅读按需执行模型,理解按需延迟求值机制。
- 阅读生产环境生命周期管理,检查
Startup、Shutdown和ImageRef.Close()。