Serialises a transformed Vix.Vips.Image into bytes for the HTTP
response.
Returns {:ok, body, content_type} where body is one of:
{:stream, Enumerable.t()}— preferred. Backed byImage.stream!/2, which wrapsVix.Vips.Image.write_to_stream/2so libvips emits encoded bytes chunk-by-chunk.{:bytes, iodata()}— fallback for callers that need the response fully buffered (HEAD requests, hosts that disable chunked transfer, theformat=jsonoutput).
Canonical streaming pipeline
The full source-to-client chain mirrors the canonical shape
documented in the
Image library's stream test suite:
path
|> File.stream!(2048, []) # Image.Plug.SourceResolver.File
|> Image.open() # ditto
|> ...transforms... # Image.Plug.Pipeline.Interpreter
|> Image.stream!(suffix: ext) # this module's `:stream` body
|> Enum.reduce_while(conn, fn chunk, conn ->
case Plug.Conn.chunk(conn, chunk) do
{:ok, conn} -> {:cont, conn}
{:error, :closed} -> {:halt, conn}
end
end) # Image.Plug.Plug.send_body/4Image.write(image, conn, suffix: ext) does the chunked-write
loop internally and is functionally identical to that
reduce_while block. We keep the loop in our own plug so we
retain explicit control over the conn for header manipulation,
error fallbacks, and telemetry.
Format support
Supports JPEG (baseline + progressive), PNG, WebP, AVIF, TIFF,
JP2, GIF, PDF, the Accept-driven :auto selection, and the
small :json metadata endpoint.
AVIF fallback
If libvips lacks AVIF write support, requests for format=avif
encode to WebP and the response is tagged with the
x-image-plug-format-fallback: avif->webp header. The plug
forwards the header set by the encoder. Detection runs once at
application boot via Image.Plug.Capabilities.probe/0.
Summary
Types
The encoded body. Streaming form is preferred; the bytes form is used when buffering is requested or required.
Optional response headers the plug should add. Keys are lowercase binaries.
Functions
Encodes the working image according to the pipeline's Ops.Format.
Types
@type body() :: {:stream, Enumerable.t()} | {:bytes, iodata()}
The encoded body. Streaming form is preferred; the bytes form is used when buffering is requested or required.
Optional response headers the plug should add. Keys are lowercase binaries.
Functions
@spec encode(Vix.Vips.Image.t(), Image.Plug.Pipeline.Ops.Format.t(), keyword()) :: {:ok, body(), String.t()} | {:ok, body(), String.t(), extra_headers()} | {:error, Image.Plug.Error.t()}
Encodes the working image according to the pipeline's Ops.Format.
Arguments
imageis the transformedVix.Vips.Image.formatis the pipeline'sImage.Plug.Pipeline.Ops.Formatstruct. The encoder reads:type,:quality, and:metadata.encode_optionsis a keyword list:
Options
:buffer—:stream(default) or:bytes. Controls whether the body is returned as a stream or as buffered iodata. The:jsonoutput is always buffered regardless of this setting.:source_content_type— the source image's MIME type. Used by the:autoselector as the final fallback when neither AVIF nor WebP is acceptable.:accept— the request'sAcceptheader value (a single string ornil). Used by the:autoselector to negotiate the output format.
Returns
{:ok, body, content_type}on success.{:ok, body, content_type, extra_headers}when the encoder needs to add response headers (currently only the AVIF fallback).{:error, %Image.Plug.Error{}}on encode failure.