v0.11 · experimental

Image resizing that's faster —
and sharper.

A Rust image server, CLI and library for JPEG, PNG, WebP and AVIF. It leads every format cell of imgproxy's own benchmark, resizing in linear light at measurably higher quality.

docker run -p 8081:8081 -v $PWD/images:/images:ro ghcr.io/oximg/oximg
oximg · request trace

GET /resize/512/512/0801.jpg@webp

  1. sniffformat by magic bytes, never the extension
  2. decodemozjpeg streaming, full size
  3. linearizesRGB u8 → linear u16, alpha premultiplied
  4. resizeLanczos3 · NEON / AVX2 row kernels
  5. encodelibwebp lossy, ICC passed through

200 image/webp·p95 33 msc7g.large, JPEG→WebP cell

benchmarks

Measured on their harness, on their hardware.

imgproxy publishes numbers from its own benchmark on AWS c7i and c7g instances. We ran the same harness, unmodified, on the same instance types — every server re-measured together in one run.

JPEG

  • oximg78.733 ms
  • imgproxy67.040 ms
  • imagor 1.9.258.744 ms
  • thumbor 7.x50.050 ms

PNG

  • oximg32.879 ms
  • imgproxy14.3187 ms
  • imagor 1.9.215.5174 ms
  • thumbor 7.x8.7304 ms

WebP

  • oximg30.992 ms
  • imgproxy20.3136 ms
  • imagor 1.9.217.7152 ms
  • thumbor 7.x14.0187 ms

AVIF

  • oximg15.6181 ms
  • imgproxy15.2190 ms
  • imagor 1.9.210.1283 ms
  • thumbor 7.x12.1225 ms

req/s (higher is better) · p95 latency · DIV2K corpus over nginx, fit into 512×512, k6 2 VUs × 5 min, all defaults · measured 2026-07-05 · source table ↗

JPEG

  • oximg91.228 ms
  • imgproxy68.039 ms
  • imagor 1.9.257.544 ms
  • thumbor 7.x63.241 ms

PNG

  • oximg39.066 ms
  • imgproxy21.0123 ms
  • imagor 1.9.222.1115 ms
  • thumbor 7.x12.5210 ms

WebP

  • oximg41.570 ms
  • imgproxy25.4110 ms
  • imagor 1.9.219.7133 ms
  • thumbor 7.x20.2129 ms

AVIF

  • oximg23.4124 ms
  • imgproxy20.3139 ms
  • imagor 1.9.213.7204 ms
  • thumbor 7.x14.7196 ms

req/s (higher is better) · p95 latency · DIV2K corpus over nginx, fit into 512×512, k6 2 VUs × 5 min, all defaults · measured 2026-07-05 · source table ↗

where we don't lead

  • JPEG → AVIF on c7i.large is a tie

    44.6 req/s against imgproxy's 44.9 — within run-to-run variance, not a lead.

  • AVIF defaults target fidelity, not bytes

    On photographic sources AVIF output measures 10–28% larger than WebP at default settings.

  • Request coalescing is per-process

    Scaled out behind round-robin, identical requests rarely meet: 0 followers across 6 Cloud Run instances in the field. Put a CDN in front.

quality

Speed that wasn't bought with pixels.

SSIMULACRA2 scores how close an output looks to its source. At the same settings, oximg's output scores higher — the throughput above is not a quality trade.

linear light
resampled on 16-bit linear samples, not gamma-encoded bytes
Lanczos3
full-size decode, so the whole reduction is the resampler's
premultiplied alpha
no dark fringes around transparent edges
ICC passthrough
Display P3 photos keep the colors sRGB would clip

End-to-end JPEG, q80

Kodak corpus; the gap widens with source size

oximg77.5
imgproxy71.2

Pure resize (lossless PNG path)

isolates the resampler from the encoder

oximg97.6
imgproxy81.9

SSIMULACRA2, 0–100, higher is better · QUALITY.md ↗

urls

Speaks the URLs you already have.

Moving off imgproxy or Cloudflare Images shouldn't mean rewriting every image URL in your templates. Sources come from a directory, any HTTP(S) origin, or a privategs:// bucket.

Positional

Fit within w×h, never enlarge. 0 frees an axis — what srcset and Next.js loaders emit.

GET /resize/750/0/photos/2026/a.jpg
GET /resize/300/200/photo.jpg@webp
GET /resize/1600/1600/photo.png@avif

Cloudflare Images

Mount OXIMG_OPTIONS_PREFIX and URLs built for Cloudflare survive the migration as-is.

GET /image/width=750,quality=80/albums/photo.png
GET /image/width=1200,format=auto/hero.jpg

Signed, imgproxy-style

Set OXIMG_KEY and OXIMG_SALT: every URL needs an HMAC-SHA256 prefix, or it answers 403.

GET /{base64url(HMAC(key, salt ‖ path))}/resize/750/0/a.jpg

use

One pipeline, four ways in.

The server, the CLI, the Rust crate and the Ruby gems all run the same decode → resize → encode path, so a number measured on one holds for the others.

server

An image service

Multi-arch Docker image, linux/amd64 + arm64, AVIF included. Put a CDN in front; responses carry a one-year Cache-Control.

docker run -p 8081:8081 \
  -v $PWD/images:/images:ro \
  ghcr.io/oximg/oximg
curl localhost:8081/resize/500/500/photo.jpg -o out.jpg

cli

A one-shot command

The same pipeline with no server, for CI asset builds. Prebuilt binaries for Linux and macOS, or Homebrew.

brew install oximg/tap/oximg
oximg resize photo.jpg 1600 1600 out.webp
oximg resize photo.jpg 750 0 out.jpg -q 70
oximg probe photo.webp

rust

A library

default-features = false drops the whole HTTP stack. Failures are typed: match on ErrorKind, not on messages.

use oximg::pipeline::{self, Params};

let params = Params {
    max_width: 300,
    max_height: 200,
    ..Default::default()
};
let (bytes, format) = pipeline::process_path(path, &params)?;

ruby

A gem — no libvips

Platform gems bundle the executable, so bundle install is the whole installation. oximg-rails signs URLs and wires into ActiveStorage.

gem "oximg"

Oximg.resize("in.jpg", "out.webp", width: 750, quality: 80)
Oximg.probe("in.jpg")
#=> {format: :jpeg, width: 4000, height: 3000, ...}

production

The details that page you at 3 a.m.

errors by fault

Status codes CDNs can trust

A client's bad key is 400, a missing object 404, a broken origin 502, a slow one 504. CDNs retry 5xx and cache 4xx, so misfiling turns crawlers into origin load.

/metrics

Queue wait, split from work

Opt-in Prometheus metrics separate queue wait from processing time — telling “needs more CPU” apart from “sources got bigger”.

SIGTERM

Graceful drain

Stops accepting, finishes in-flight encodes, exits 0 — what docker stop, Kubernetes and Cloud Run expect.

fail-closed

A typo'd knob refuses to boot

Every setting is an environment variable. Set but unparseable means the process exits naming it, not a silent default.

orientation

Upright in every format

EXIF, PNG eXIf, WebP EXIF and AVIF irot/imir are applied; pixels come out upright and nothing double-rotates.

GIF → WebP

Animated GIFs stay animated

Frames stream into an animated WebP, one canvas in memory. Measured: WebP reached 25% of the source bytes where the best GIF managed 81%.

status

Experimental, and saying so.

oximg is a proof of concept: the HTTP interface and the library API can change without notice. Pin a version tag in production. Here is what isn't there yet.