Image.Plug.Pipeline.Encoder (image_plug v0.1.0)

Copy Markdown View Source

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 by Image.stream!/2, which wraps Vix.Vips.Image.write_to_stream/2 so 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, the format=json output).

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/4

Image.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

body()

@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.

extra_headers()

@type extra_headers() :: [{String.t(), String.t()}]

Optional response headers the plug should add. Keys are lowercase binaries.

Functions

encode(image, format, encode_options \\ [])

@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

Options

  • :buffer:stream (default) or :bytes. Controls whether the body is returned as a stream or as buffered iodata. The :json output is always buffered regardless of this setting.

  • :source_content_type — the source image's MIME type. Used by the :auto selector as the final fallback when neither AVIF nor WebP is acceptable.

  • :accept — the request's Accept header value (a single string or nil). Used by the :auto selector 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.