# Image Pipeline

This page walks through `GET /c/img/*path`: deciding whether to transform, the libvips processing order, and the streamed response.

## Routing Decisions

`GetFromPath` checks these in order and responds on the first match:

| Order | Condition | Behavior |
|---|---|---|
| 1 | Path ends in `.pdf` | Stream the original as `application/pdf` |
| 2 | Path ends in `.svg` | Stream the original as `image/svg+xml` |
| 3 | `o=1` | Infer MIME from the extension (`GetMimeType`) and stream the original |
| 4 | Local cache file exists | Set Content-Type from `t` and stream the cache file |
| 5 | Otherwise | Transform with libvips |

Every branch first sets 7-day `Cache-Control` and `Expires` headers.

## Transform Steps

The transform runs in a goroutine under a 30-second timeout:

| Step | Call | Notes |
|---|---|---|
| 1 | `os.MkdirAll` | Create the cache file's directory |
| 2 | `os.ReadFile` → `vips.NewImageFromBuffer` | Read and decode the original |
| 3 | `GetNewSize` → `Resize(scale, KernelLanczos3)` | Scale proportionally with Lanczos3 when the size changes (rules in [Sizing Rules](/api-reference-sizing)) |
| 4 | `GaussianBlur(GetBlur(b))` | Gaussian blur, sigma clamped to 0–100 |
| 5 | `Linear([B], [0])` | Brightness: multiply every band by `B`; a non-numeric `B` fails the transform |
| 6 | `ExportJpeg` / `ExportPng` / `ExportAvif` / `ExportWebp` | Export by `t`; PNG ignores quality |
| 7 | `os.WriteFile` | Write the local cache file |

Any failed step returns the 404 placeholder SVG instead (dark variant for `d=1` or `d=dark`) with `Cache-Control: no-cache`.

## Streamed Output

`streamImage` (in-memory buffer) and `streamFile` (original file) both respond with `Transfer-Encoding: chunked`, flushing after each buffer fills:

| File size | Buffer |
|---|---|
| ≤ 50 KiB | 2 KiB |
| ≤ 500 KiB | 4 KiB |
| ≤ 2 MiB | 8 KiB |
| Larger | 16 KiB |

`streamFile` also sends `Content-Length`; when the ResponseWriter does not implement `http.Flusher`, it falls back to a single `c.File`.
