Skip to content

Formats

Sources are identified by magic bytes (extensions are never trusted). By default the output format is the source’s own; any decode column combines with any encode column:

Format Decode Encode
JPEG baseline & progressive, grayscale; streaming, full-size decode (shrink-on-load opt-in) jpegli progressive (default), mozjpeg profiles via PRESET
PNG palette / grayscale / 16-bit, normalized to RGB(A)8 lossless RGB(A); opt-in palette quantization (OXIMG_PNG_QUANTIZE)
WebP lossy & lossless, alpha lossy (OXIMG_WEBP_QUALITY, 75), alpha; output is scaled to fit WebP’s 16383 px limit
AVIF (--features avif) dav1d: 8/10/12-bit, all subsamplings, alpha SVT-AV1: 10-bit 4:2:0, tune=ssim, alpha as auxiliary image
GIF GIF87a/89a, every frame composited onto the logical screen (frame sub-rectangle, transparent index, all four disposal methods) none — see below

GIF is the one decode-only format, so it is also the one source whose output format is not its own: with no @{fmt} and no negotiation, a GIF becomes WebP, and an animated GIF becomes an animated WebP (see Animation for the budgets that decide whether the animation is kept). That is a deliberate choice, not a missing encoder — on a 15-file real-world corpus, lossless GIF→GIF saved nothing on 9 of them (median 100% of the source bytes, which is also what imgproxy’s GIF→GIF measured), and at native size the smallest GIF variant measured still landed at 80.7% where WebP reached 25.2% at the same visual score. @gif and format=gif are rejected with a 400 instead of quietly answering with different bytes under the name the client asked for. See docs/gif-evaluation.md for the measurements.

Cross-format output: append an imgproxy-style @{fmt} token to the filename — /resize/300/200/photo.jpg@webp (jpg/jpeg, png, webp, avif; jxl is reserved, gif permanently so). Only exact tokens count, so photo@2x.jpg is still a filename. Precedence: explicit @{fmt} > Accept negotiation > source format. Negotiation is opt-in: set OXIMG_AUTO_FORMAT to a preference list (e.g. avif,webp) and bare-URL responses follow the request’s Accept header; every response then carries Vary: Accept (make sure your CDN honors it or normalizes Accept into the cache key — explicit @{fmt} URLs avoid the issue entirely, which is what signed deployments should prefer since headers are outside the signature). Alpha sources encoded to JPEG are flattened in linear light onto OXIMG_FLATTEN_BG (hex RRGGBB, default white). Encode settings are keyed by the output format, using the same knobs as same-format requests.

Choose the preference order by your goal: the AVIF defaults target fidelity, not minimum bytes — at default quality settings AVIF output measures 10–28% larger than WebP on photographic sources, and costs the more expensive encode. If the deployment’s goal is byte reduction, prefer webp,avif (or webp alone), or lower OXIMG_AVIF_QUALITY until AVIF earns its slot; put avif first only after comparing sizes on your own corpus at your own settings. Also note what negotiation does not cover: when it doesn’t fire (client sends Accept: */* — link-preview scrapers, social-card fetchers, curl integrations), the source format is kept, and PNG output defaults to lossless RGB(A) — a large photographic PNG stays large unless OXIMG_PNG_QUANTIZE=1 is set. Deployments that care about those clients should enable quantization or prefer explicit @{fmt} URLs over relying on negotiation. On flat graphics (charts, screenshots, text-heavy panels), a quantized PNG is often both smaller and truer to the source than any WebP quality setting — worth remembering when tuning OXIMG_AUTO_FORMAT for mixed content.

Orientation: every source format auto-rotates — JPEG EXIF, PNG eXIf, WebP EXIF chunks, and AVIF irot/imir transforms. The target box applies to the displayed frame and the pixels come out upright in every output format (the metadata itself is not forwarded, so nothing double-rotates). OXIMG_AUTO_ROTATE=0 restores the raw stored orientation.

ICC profiles: a source’s color profile (JPEG APP2 chain, PNG iCCP, WebP ICCP, AVIF colr) passes through byte-for-byte into any output format, across format conversion included. RGB pixels are never color-converted. This matters for wide-gamut sources: the common proxy default is to normalize pixels to sRGB and strip the profile, which permanently clips every color outside the sRGB gamut — a Display P3 phone photo loses exactly the saturated reds and greens that made it worth shooting in P3. oximg keeps the pixels and the profile as they were, so wide-gamut images render on a wide-gamut display the way the original did (and identically everywhere else). OXIMG_ICC=0 opts into stripping instead.

CMYK/YCCK JPEG sources (print-workflow assets) are the one exception, since no browser renders CMYK pixels: they are converted to sRGB — through the embedded CMYK profile (moxcms, relative colorimetric, like imgproxy/libvips) when one is present, with the naive composite browsers use otherwise — and the CMYK profile is consumed, never passed through. OXIMG_ICC=0 skips profile extraction entirely, so it also selects the naive conversion.