All notable changes to this project will be documented in this file. See Keep a Changelog.
[v0.1.0] — 2026-05-23
Adds
A canonical, provider-neutral pipeline IR with
Trim,Background,Resize,Rotate,Flip,Border,Adjust,Colorspace,IccTransform,Sepia,Tint,ReplaceColor,Posterize,Pixelate,Blur,Sharpen,Draw,Segment,Vignette,Fade,Rounded,DropShadow,Opacity,Orientation,Enhanceops, plus aFormatstruct for output configuration. The pipeline normaliser enforces a Sharp-style canonical order and per-op no-op detection.A pluggable
Image.Plug.Providerbehaviour with four implementations out of the box:- Cloudflare Images — recognises
/cdn-cgi/image/<options>/<source>andimagedelivery.net/<account>/<image-id>/<variant-or-options>; parses the documented option set including adraw=URL grammar for overlays. - imgix — query-string format with the documented option set.
- Cloudinary —
<account>/image/upload/<options>/<source>with multi-stage chained transforms (flattened to one comma-joined option set in v0.1). - ImageKit — both URL forms (path-prefix
tr:...and query-string?tr=...). - IIIF Image API 3.0 provider — targets Compliance Level 2. Parses the standard
<prefix>/<id>/<region>/<size>/<rotation>/<quality>.<format>form plus the<prefix>/<id>/info.jsondiscovery document. Mount withforward "/iiif/3", Image.Plug, provider: {Image.Plug.Provider.IIIF, []}. Seeguides/iiif_conformance.mdfor the per-segment compliance matrix.
- Cloudflare Images — recognises
Each adapter ships with a documented per-option conformance matrix (
✅/⚠️/❌) underguides/.
Pipeline operations
Resize family —
:contain,:cover,:crop,:pad,:scale_down,:squeezefit modes; gravity (named, compass, focal-point); DPR; aspect-ratio shortcuts (ImageKitar-W-H).Geometry — rotate (multiples of 90°), flip, trim (border-aware and explicit), border, EXIF orientation override (
Image.set_orientation/2).Colour — brightness, contrast, saturation, gamma; sepia (
Image.sepia/2); single-colour tint (Image.tint/2); colourspace conversion (Image.to_colorspace/2); ICC-profile-driven conversion viaOps.IccTransform(Image.to_colorspace/3); colour replace (Image.replace_color/2); content-aware enhance (Image.enhance/2).Pixel-domain effects — pixelate (
Image.pixelate/2); posterize / cartoonify (Image.posterize/2); blur and sharpen.Mask & alpha — vignette (
Image.vignette/2); fade (Image.fade/2); rounded corners via SVG mask (Image.rounded/2); drop shadow (Image.drop_shadow/2); mid-pipeline opacity (Image.opacity/2).Face-aware crop & pixelation —
Resize{gravity: :face}(Cloudflareg=face, imgixfit=facearea/crop=faces, Cloudinaryg_face, ImageKitfo-face) pre-crops to the most prominent detected face when the optional:image_visiondependency is loaded.face_zoom(Cloudflareface-zoom, ImageKitz-) controls how much context surrounds the face.Ops.PixelateFaces(Cloudinarye_pixelate_faces) pixelates only the face regions. Without:image_vision, face-aware ops fall back to libvips':attentionsaliency crop or no-op silently — the wire-up never errors on missing dependency.Overlays —
Drawop with multi-layer composition; per-layer source resolution, sizing, rotation, and positioning.Output format — JPEG (baseline + progressive), PNG, WebP, AVIF, JSON (metadata endpoint),
:auto(Accept-driven negotiation). Per-format encoder flags::lossy,:progressive,:chroma_subsampling. Selective EXIF preservation viametadata=:copyright(preserves copyright + orientation through the strip).
Streaming & performance
Streaming decode —
Image.open/2for files,Image.from_req_stream/2for HTTP via Req. Source bytes are progressively decoded by libvips.Streaming encode —
Image.stream!/2piped throughPlug.Conn.send_chunked/2so the encoded body never materialises in BEAM memory.AVIF soft fallback — requests for
format=avifon libvips builds without libheif/AV1 support encode as WebP and tag the response withx-image-plug-format-fallback: avif->webp. Detected once at boot viaImage.Plug.Capabilities.probe/0.
Cache & HTTP semantics
Strong ETag derived from the source's
etag_seedand the normalised pipeline's fingerprint. ConditionalIf-None-MatchGETs return304 Not Modifiedwithout invoking libvips.Sensible defaults for
Cache-ControlandVary: Accept(the latter onformat=auto).
Variants
Image.Plug.VariantStorebehaviour with an ETS-backed default; the implicit"public"variant is always seeded.Image.Plug.VariantStore.Persistencebehaviour withImage.Plug.VariantStore.Persistence.File(JSON-on-disk with atomic writes). Variants hydrate on boot; write-through on every CRUD.Image.Plug.Adminexposes the variant CRUD over HTTP/JSON.
Security
HMAC-SHA256 signed URLs via
Image.Plug.Signingand the plug's:signingconfig; supports key rotation and optional?exp=<unix-seconds>expiry. URL format wire-compatible with Cloudflare's hosted signed URLs (samesig/expparameter names, same algorithm).Provider-specific signing for imgix (HMAC-SHA256
?s=), Cloudinary (SHA-256s--<sig>--in-path segment, 32 url-safe-base64 chars), and ImageKit (HMAC-SHA1?ik-s=+?ik-t=). All wire-format-compatible with the hosted services.
Telemetry & error handling
Per-request telemetry under
[:image_plug, :request, :start | :stop | :exception].Friendly error policy: a placeholder PNG in dev (so broken URLs render visibly in the browser) and a stream of the original source bytes in prod (so a transform bug doesn't break the page).
Companion library
- For Phoenix LiveView markup that builds against the same URL grammar, see
image_components—<.image>and<.picture>components with responsivesrcset, lazy loading, blurhash placeholders, and art-direction.
See the README and the user guide for setup, configuration, and security guidance. The four *_conformance.md guides under guides/ document per-option support for each provider.