# `Image.Plug.Pipeline.Encoder`
[🔗](https://github.com/elixir-image/image_plug/blob/v0.1.0/lib/image/plug/pipeline/encoder.ex#L1)

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](https://github.com/kipcole9/image/blob/main/test/stream_image_test.exs):

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

# `body`

```elixir
@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`

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

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

# `encode`

```elixir
@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

* `image` is the transformed `Vix.Vips.Image`.

* `format` is the pipeline's `Image.Plug.Pipeline.Ops.Format`
  struct. The encoder reads `:type`, `:quality`, and `:metadata`.

* `encode_options` is a keyword list:

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
