Skip to content
Contributor documentation: how the project is built and maintained. Not needed to run xtandard-image.

Tech-stack research: self-hosted image proxy

Researched 2026-09-26 against live sources. Summarised; see links per claim.

1. Go + libvips vs Rust + libvips vs pure Rust

Section titled “1. Go + libvips vs Rust + libvips vs pure Rust”

Go + libvips (bindings are mature and used in production)

Rust + libvips (usable, bindings young)

  • rs-vips 0.7.0 (MIT) released 24 Sep 2026, fork of olxgroup bindings; VipsImage became Send/Sync only in 0.7.0; ~2.6k total downloads. https://docs.rs/rs-vips/latest/rs_vips/
  • No production image server at imgproxy/imagor scale. Same perf (libvips does the work), same unsafe C boundary.

Pure Rust (not viable for this format list)

  • zune-image, image-rs, fast_image_resize, jxl-oxide are fast but none covers HEIC, CR3/RAW, PDF, PSD, animated WebP/AVIF encode. FFI still required. No pure-Rust demand-driven streaming pipeline equivalent to libvips.

Recommendation: Go + libvips, thin cgo layer generated by vipsgen plus a hand-written shim for hot paths.

Stable: libvips 8.18.6 (25 Aug 2026). 8.18 added UltraHDR (libultrahdr), camera RAW via libraw (dcrawload), Oklab/Oklch. https://github.com/libvips/libvips/releases https://www.libvips.org/2025/12/04/What's-new-in-8.18.html

Lib Enables License
libvips core LGPL-2.1+
libheif HEIC/AVIF LGPL-3
libde265 HEIC decode LGPL
x265 HEIC encode GPL → use kvazaar (BSD)
aom / dav1d AV1 enc / dec BSD + AOM patent / BSD
libjxl JPEG XL BSD-3
libspng / libpng PNG BSD-2 / libpng
cgif + quantizr GIF save MIT / MIT (libimagequant newer versions are GPL-3, avoid)
libwebp WebP incl. animated BSD
librsvg SVG LGPL
poppler-glib PDF GPL-2+ → avoid
PDFium PDF BSD-3/Apache-2 (https://github.com/bblanchon/pdfium-binaries)
libraw RAW LGPL-2.1 or CDDL-1.0
ImageMagick PSD/BMP/ICO fallback ImageMagick (Apache-style); security caveat with untrusted input
OpenJPEG, OpenEXR, libultrahdr, highway JP2, EXR, UltraHDR, SIMD BSD/MIT/Apache

Shipping LGPL .so files in an image is fine (dynamic linking + source offer). Do not ship x265 or poppler. imgproxy-docker-base uses kvazaar + dav1d, no x265, -Dmodules=disabled (no libraw/poppler/magick). https://github.com/imgproxy/imgproxy-docker-base/blob/master/build-deps.sh

libvips has no native loader for BMP, ICO or PSD: magick, or own loaders like imgproxy.

Strategy: cgo shim tries unpack_thumb first; if the embedded JPEG ≥ requested size, feed to vips_jpegload_buffer and apply RAW EXIF orientation; else fall back to vips_dcrawload half-size.

imgproxy Pro paid features ($49/mo): advanced smart crop (faces), object-oriented crop/blur/draw, auto-quality by DSSIM and by file size, best-format, GIF→MP4, video thumbnails, PDF/PSD thumbnails, advanced watermarks (per-request URL, text, shadow, rotation), chained pipelines, colour adjustments, advanced compression, and (since v4.0.0, 2026-05-13) camera RAW loading behind IMGPROXY_ENABLE_RAW_FORMATS plus an internal result cache. https://imgproxy.net/features/ (verified in .refs/imgproxy/CHANGELOG.md and imagetype/raw.go)

Image Size (amd64/arm64) Contents Missing
darthsim/imgproxy (base ghcr.io/imgproxy/imgproxy-base, MIT, Noble) 75 / 72 MB vips 8.18.6, libheif 1.23.4 (kvazaar+dav1d+libde265, aom), libjxl 0.12, cgif 0.5.4, librsvg 2.63, quantizr, highway libraw, PDF, magick
ghcr.io/cshum/imagor-base (MIT) / shumc/imagor 106/100 MB vips 8.18.6, libraw 0.22.1, libjxl, libheif, x265 (GPL), poppler (GPL), optional magick/mozjpeg/ffmpeg 7.1.1 GPL bits → don’t reuse as-is
lovell/sharp-libvips static prebuilt, Apache-compatible, heif AVIF only jxl, libraw, PDF
weserv/images (BSD-3) C++ nginx module not a base image

Strategy: fork imgproxy-docker-base versions.sh/build-deps.sh (MIT), add libraw 0.22.x, pdfium-binaries, optional restricted ImageMagick. Use imagor-base as reference for libraw.

  • go-astiav: idiomatic cgo, targets FFmpeg n8.0 only. https://github.com/asticode/go-astiav
  • ffmpeg-statigo: static GPL build → avoid. ffmpeg-go: CLI wrapper.
  • imagorvideo does best-frame selection by RMSE histogram. https://docs.studio.imagor.net/ecosystem/
  • Size: imagorvideo 148 MB vs imagor 106 MB → FFmpeg full adds ~40 MB compressed. Decoder-only LGPL build (--disable-gpl, h264/hevc/vp9/av1 decoders, mp4/webm/mov demuxers) estimated 10–15 MB.
  • Language: Go 1.27 (1.27.1 current as of 2026-09-26; pin go 1.27.0, toolchain go1.27.1), CGO_ENABLED=1, buildx matrix, native arm64 runners preferred.
  • Binding: vipsgen-generated bindings + own small cgo shim for hot paths (load from buffer/source, thumbnail, save to target, libraw thumbnail loader). Pin libvips 8.18.x. VIPS_CONCURRENCY, request semaphore sized to cores, vips_cache_set_max(0), MALLOC_ARENA_MAX=2 + jemalloc.
  • Base image: in-house deps on ubuntu:noble → /opt/xtandard/lib, multi-stage copy to slim runtime. Publish ghcr.io/xantiagoma/xtandard-image-base:vips8.18.6-<flavor>-rN.
  • License policy: LGPL/BSD/MIT/Apache only. No x265, poppler, exiv2, GPL ffmpeg, AGPL weights.
Format Library
JPEG libjpeg-turbo 3.2 (optional mozjpeg encode)
PNG libspng decode, libpng encode, quantizr palette
GIF vips gifload (libnsgif) + cgif + quantizr
WebP libwebp 1.6
AVIF libheif + dav1d + aom (or SVT-AV1)
HEIC libheif + libde265 + kvazaar
JXL libjxl 0.12
TIFF libtiff 4.7
BMP/ICO own loaders (as imgproxy), magick fallback
SVG librsvg 2.63 with own sanitisation
PDF PDFium via vips pdfload
PSD/PSB ImageMagick 7 restricted by policy.xml, or embedded composite preview
RAW libraw 0.22.1: unpack_thumb first, dcrawload fallback
UltraHDR libultrahdr
Smart crop vips attention default; faces/objects via ONNX Runtime Go + permissive model, lazy-loaded
Video go-astiav + FFmpeg 8.0 LGPL decoder-only; best frame by histogram
Auto-quality DSSIM/SSIMULACRA2 + binary search on quality