Skip to content

Reader/Writer streaming I/O

Copy page

Starting in govips v2.19.0, applications can connect an io.Reader directly to libvips and write encoded chunks to an io.Writer. This avoids copying the complete compressed file into a Go byte slice with io.ReadAll, and gives HTTP bodies, object-storage streams, and pipes a common interface.

API Role Access strategy
LoadImageFromReader Create an ImageRef from any io.Reader Materialized by default; lazy with explicit AccessSequential
SaveToWriter / typed variants Write encoded chunks to an io.Writer Codec-dependent; formats such as TIFF may need full buffering
TranscodeStream Combine a Reader, optional auto-rotation, and a Writer Selects sequential or materialized loading from orientation metadata

These APIs are not present in govips v2.17.0. This page and its executable example are verified against govips v2.19.0.

govips selects AccessSequential and keeps the input Reader open until ImageRef.Close(). sharp, pyvips, and the CLI use their own sequential file hints. Those switches are not the same Reader/Writer API.

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 image decoded and re-encoded through a descriptor-backed streaming pipeline without temporary files
WEBP1938 × 1292, 398,898 bytes, 3 bands

Generated with govips v2.19.0 / libvips 8.17.3.

  • Format: WEBP
  • Dimensions: 1938 × 1292
  • Bands: 3
  • Alpha: No
  • File size: 398,898 bytes
  • Generated with: govips v2.19.0 / libvips 8.17.3

Automated tests cover propagation of Reader and Writer failures in the reference implementation. A sequential load normally consumes only enough bytes for the header. Truncation or a mid-stream failure can therefore surface later, when SaveToWriterWebp first consumes all pixels.

The sequential path works for shrinking, cropping, flattening, colourspace conversion, horizontal flips, and most line-kernel operations. Operations that read rows out of order—90°/180°/270° rotation, vertical flip, and smart crop, for example—must materialize first or fail with 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)
}

Use TranscodeStream when the pipeline must choose materialization from EXIF orientation:

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 keeps orientations 1 and 2 on the sequential path. Orientations that transpose or reverse row order are materialized first.

  • Limit the complete request body at the HTTP layer. SetPipeReadLimit only limits header buffering for non-seekable Readers; it is not an upload-size limit.
  • In sequential mode, keep the Reader alive until ImageRef.Close().
  • Original Reader and Writer errors are wrapped and remain detectable with errors.Is.
  • Progressive JPEG, interlaced PNG, HEIF decoding, and TIFF output can require additional buffering. Do not infer a memory ceiling from the API name alone.
  • For cancellation in an explicit Load/Save flow, another goroutine can call image.SetKill(true) and clear the flag after completion. TranscodeStream does not currently accept a context.Context.

When comparing sequential and materialized paths, hold the input, libvips/govips versions, concurrency, and encoding parameters constant. Record peak RSS, exit status, output dimensions, and file size. A single-machine observation describes that environment only; it is not a universal performance claim.

On 2026-10-12, on Fedora Linux 43 with govips v2.19.0 and libvips 8.17.3, one sequential WebP transcode of this 1938 × 1292 JPEG was covered by automated tests. A writer that fails mid-stream surfaces through SaveToWriterWebp. A reader that fails after about a third of the bytes surfaces to the caller as well. Calling SetKill(true) before export makes evaluation return an error; clear the flag back to false after the attempt. Those tests focus on lifecycle and error propagation rather than multi-request concurrency ceilings.

libvips tracked memory counts allocations it recorded. It is not process RSS.

Interface What this example actually did How that differs from govips Reader/Writer
sharp 0.35.5 sequentialRead: true, then WebP encode The sequential hint applies to sharp’s file load, not to io.Reader / io.Writer
pyvips 3.2.0 access="sequential" This is the libvips access enum. pyvips still opens the file
vips CLI 8.17.3 [access=sequential] A load hint. It has no kill flag and does not wrap errors for Go’s errors.Is

Matching WebP dimensions across the four outputs only align format and size. The sequential switch does not make them the same streaming interface.