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