libvips Parity Roadmap
The core is done. libviprs already implements the full core image-processing surface of libvips — arithmetic, conversion, morphology, convolution, colour, resample, histogram, create, draw, mosaicing, frequency filtering, band and matrix operations — as ~168 CLI commands / 247 API operations, every one pinned differential- or golden-tested against a libvips 8.18.4 oracle. What remains on the road to full libvips parity is not core processing: it is missing file formats, a short tail of niche operations, and deeper numeric pixel carriers (signed, 32-bit, double, and complex band formats). This page tracks exactly that remainder, phased by dependency and demand.
A small set of libvips operations is excluded by design and never planned: system (external command execution), sequential / tilecache / linecache (libvips demand-cache plumbing, replaced by the viprs planner and streaming engine), and the thumbnail_buffer / thumbnail_source streaming variants. libvips load-vs-save asymmetries also pin the target honestly: SVG, OpenSlide, camera RAW, and OpenEXR are load-only in libvips itself, so libviprs owes no encoder there.
Phase 1 — Near-term
Formats
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| WebP — still decode + lossless encode | webpload / webpsave (libwebp) |
Typed-Unsupported stub. Pure-Rust path: image-webp via the existing image 0.25 dep. Lossy encode has no mature pure-Rust encoder (libwebp FFI later, or ship lossless-first); animated WebP moves to Phase 2 behind the multi-page model. |
M | P0 | #497 |
| GIF — still load + save | gifload / gifsave (libnsgif / cgif in vips) |
Stub. Pure-Rust gif crate covers still decode/encode incl. palette quantisation; animated GIF moves to Phase 2 behind the multi-page model. |
M | P0 | #499 |
| SVG — load (rasterise) | svgload (librsvg in vips); load-only — vips has no svgsave |
Absent. Pure-Rust resvg reaches parity without C; no encoder owed. |
M | P0 | #502 |
| Radiance HDR (.hdr/.pic) load + save | radload / radsave (native vips code, RGBE coding); includes the rad2float / float2rad coding pair |
Stub. Pure-Rust reachable; f32 carrier already exists. | S | P1 | #506 |
Operations
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| sobel / scharr / prewitt named edge operators | VipsSobel / VipsScharr / VipsPrewitt (native vips VipsEdge subclasses; fixed separable kernel pairs + magnitude combine) |
Missing — but conv / convsep / compass (9 base convolution ops) exist to build on; uchar path can be EXACT-oracle. Three ops share one kernel/magnitude core — do first. |
M | P1 | #512 |
| canny edge detector | canny (native vips: gaussian blur → gradient → non-max suppression) |
Missing; builds directly on the #512 kernel/magnitude core. | M | P1 | #511 |
| matrixmultiply | matrixmultiply (native vips) |
Missing; matrix container (.mat text) and matrix ops already shipped. |
S | P2 | #513 |
| join + 2-image remainder | join / remainder (native vips) |
Missing as generic 2-image forms; constant-remainder and insert/embed primitives exist. | S | P2 | #514 |
| Oklab / Oklch — residual only | colourspace Oklab/Oklch routes (native vips, Ottosson matrices) |
~80% already done: viprs colourspace … oklab and oklch verified bit-exact against vips (max-abs-diff 0.000000). Only the packed LabQ coding route fails; issue to be re-scoped to that residual (full LabQ exactness lands with the Phase 3 carriers). |
S | P2 | #515 |
Pixel carriers — kickoff
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| uint (u32) carrier — starts alongside Phase 1 | VipsBandFormat uint (native vips band format + promotion rules) |
Absent; cast rejects the target. Pure-Rust enum/codec/op work; first of the four foundational carriers (see Phase 3) and biggest oracle-class payoff together with #516. |
L | P0 | #517 |
Phase 2 — Mid-term
Formats
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| Multi-page / animation raster model (shared infrastructure) | vips n-pages "toilet-roll" representation (page-height metadata over a tall raster) |
Absent; prerequisite for animated GIF/WebP and HEIF sequences. Not a codec — lands once, unblocks three formats. | L | P0 | #497 / #499 |
| Animated GIF + animated WebP (completes #497/#499) | gifload/gifsave, webpload/webpsave with n/page args |
Still halves land in Phase 1; animation follows the multi-page model. | M | P1 | #497 / #499 |
| HEIF / AVIF load + save | heifload / heifsave (libheif + libde265/x265 or libaom) |
Stub. No mature pure-Rust HEVC path — libheif FFI behind an off-by-default feature; AVIF decode has partial pure-Rust options. HEIF sequences need the multi-page model. | L | P1 | #498 |
| JPEG-XL load + save | jxlload / jxlsave (libjxl) |
Stub. Pure-Rust decode reachable (jxl-oxide); encode needs libjxl FFI or lossless-only first. |
L | P2 | #500 |
| OpenEXR load | openexrload (OpenEXR C++ lib); load-only in vips — no save op exists |
Stub. Pure-Rust exr crate reaches parity; f32 carrier exists. |
M | P2 | #504 |
Operations — unfiled gaps (to be filed as issues)
Nine further op workstreams were found in the 8.18.4 diff with no tracking issue yet. They are recorded here so nothing silently drops; each will get its own issue before work starts.
| Gap | vips reference | Notes | Tracking |
|---|---|---|---|
| invert (photographic negative) | invert | Explicitly documented as absent in OP_MAP.md; simple on existing carriers. | unfiled |
| case (image-array form) | case | Scalar/two-image forms shipped; N-image-array selector missing. | unfiled |
| fill_nearest | fill_nearest | Morphology-adjacent; existing distance-transform infra to build on. | unfiled |
| conva / convasep | conva / convasep | Approximate convolution variants; exact conv/convsep already shipped. | unfiled |
| quadratic | quadratic | Polynomial resample transform. | unfiled |
| transpose3d | transpose3d | Toilet-roll page transpose; pairs naturally with the Phase 2 multi-page model. | unfiled |
| match / mosaic1 | match / mosaic1 | First-order mosaicing extras; core mosaic/merge shipped. | unfiled |
| profile_load | profile_load | Named ICC profile loader. | unfiled |
| complex2 and remosaic | complex2 / remosaic | Complete only after Phase 3: complex2 needs the native complex carrier (#519); remosaic needs the join-tree metadata channel. | unfiled |
Phase 3 — Foundational carriers & long tail
cast rejects unsupported targets with a typed error rather than deferring ops). But they are what upgrades ~43 OP_MAP.md rows from EXACT-AFTER-CAST / FOURIER / GOLDEN-ONLY oracle classes to fully EXACT, and they gate the scientific formats below. Recommended landing order: #516 and #517 first (independent, biggest payoff; #517 starts back in Phase 1), then #518, then #519. All four are pure-Rust; no C bindings needed.Pixel carriers
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| Signed integer carriers (char / short / int) | VipsBandFormat char/short/int + full promotion lattice |
Absent — source of most of the 29 EXACT-AFTER-CAST oracle rows. Blast radius: pixel.rs exhaustive matches, stride math, .v codec, round-half-even save-cast rules, every promotion table, test-harness oracle classes. |
L | P0 | #516 |
| uint (u32) carrier — completion | VipsBandFormat uint |
Kickoff in Phase 1; full-surface promotion/oracle work completes here (e.g. the hist_match uchar-vs-uint LUT GOLDEN-ONLY row). |
L | P0 | #517 |
| double (f64) carrier | VipsBandFormat double |
Absent; prerequisite for the dpcomplex half of #519 and for FITS/scientific formats. | L | P1 | #518 |
| Native complex carriers (complex / dpcomplex) | VipsBandFormat complex/dpcomplex (vips FFT side uses fftw3; viprs uses rustfft) |
Worked around today as float-pair Fourier bands in the .v container — the cause of all 12 FOURIER oracle rows. dpcomplex half hard-depends on #518. |
XL | P1 | #519 |
Formats — niche & scientific
| Gap | vips reference / C lib | libviprs today | Effort | Priority | Tracking |
|---|---|---|---|---|---|
| JPEG 2000 load + save | jp2kload / jp2ksave (OpenJPEG) |
Stub; pure-Rust JP2K options are immature — likely optional OpenJPEG FFI. | L | P2 | #501 |
| OpenSlide load (whole-slide images) | openslideload (libopenslide); load-only in vips |
Absent; C FFI behind an optional feature. No encoder owed. | L | P2 | #503 |
| FITS load + save (astronomy) | fitsload / fitssave (cfitsio) |
Stub; gated on the signed (#516) and double (#518) carriers. | M | P2 | #505 |
| Camera RAW load | rawload-style dcraw path (libraw/dcraw); load-only in vips |
Absent; no encoder owed. Pure-Rust rawloader-class crates partially reach parity. |
L | P2 | #507 |
| Ultra HDR (gain-map JPEG) load + save | uhdrload-family (libultrahdr); includes the uhdr2scRGB coding op |
Absent; builds on the shipped JPEG codec plus gain-map metadata. | M | P2 | #508 |
| Scientific volumes — MAT / NIfTI / Analyze load | matload / niftiload / analyzeload (matio / niftiio) |
Absent; gated on the #516/#518 carriers. Caveat: NIfTI is not compiled into the local vips reference build, so that half has no local differential oracle (golden-only). | M | P2 | #510 |
| ImageMagick fallback loader | magickload / magicksave (ImageMagick/GraphicsMagick) |
Deliberate strategic fork: ImageMagick FFI contradicts the pure-Rust / untrusted-input posture. Recommendation on the issue: formally scope out, or gate behind an off-by-default FFI feature. | — | scope decision | #509 |
How this roadmap was grounded
- Oracle — every number was reproduced against live surfaces:
vips -lon libvips 8.18.4 (404 lines → 328 leaf classes → ~262 distinct callable operations after collapsing_buffer/_source/_target/_file/_mimeloader/saver variants and deduplicating aliases), diffed againstviprs __dump-commands(168 commands, 163 exact vips nicknames) and the 247-rowOP_MAP.mdparity ledger. - Honest crediting — "subsumed" ops (the 21 pairwise colour transforms folded into
colourspace --source-space,convf/convifolded intoconv --precision) are counted as covered only where the fold is behaviour-complete; format I/O is credited only for codecs that really ship. - Verification classes — shipped ops are pinned as EXACT, EXACT-AFTER-CAST, FOURIER, or GOLDEN-ONLY against the 8.18.4 differential oracle; the Phase 3 carriers exist precisely to promote the ~43 non-EXACT rows.
- Posture — the default build stays pure Rust. Any C codec (libwebp-lossy, libheif, OpenJPEG, libopenslide) lands behind an off-by-default Cargo feature, mirroring the existing
pdfiumprecedent.
Tracking: all open issues · formats #497–#510 · operations #511–#515 · pixel carriers #516–#519.