vips-rs
Rust bindings for libvips: fast, low-memory image processing with a safe, ergonomic API.
- Safe wrappers over common libvips APIs
- Process-wide initialization and RAII image ownership
- Practical helpers for reading, transforming, and writing images
- Lazy pipeline evaluation (libvips demand-driven processing)
Documentation: https://docs.rs/vips/
Requirements
- Current stable Rust (edition 2024)
- libvips installed on your system
- macOS:
brew install vips - Linux:
apt-get install -y pkg-config libvips-dev(or your distro equivalent)
- macOS:
Installation
Add to your Cargo.toml:
[]
= "0.2"
Quick start
use *;
0.2.0 additions and migration
This version uses the published vips-sys 0.3.0 dependency;
no sibling checkout is required.
write_to_memory_owned/write_to_buffer_ownedreturnVipsOwnedBuffer, which owns a GLib allocation and exposes immutable bytes without the extra full-buffer copy into a RustVec. Existing Vec APIs remain available.from_file_with_access/from_buffer_with_accessaccept aVipsAccesshint. Prefer sequential access for a one-pass pipeline and file/buffer thumbnail constructors for shrink-on-load.--features v8_18addstile_size,gainmap, camera RAW file/buffer loaders, UltraHDR file/buffer IO and scRGB conversion, Oklab/Oklch conversion helpers, andinterpretation_bands. Requires libvips 8.18+; RAW and HDR codecs remain optional build-time dependencies of the system library.VipsRegionnow has a buffer lifetime; lazy multi-image operations retain every input buffer's lifetime. Image handles may drop before their results, but borrowed pixel buffers must remain alive.- Drawing detaches borrowed/shared storage into writable memory on first use. Borrowed memory must be correctly aligned for its sample format.
VipsInterpolateMethod::callis unsafe because output size/alignment and a prepared interpolation window are native caller obligations.VipsInstanceuses the same initialization state asinitand no longer shuts the process-wide library down when its handle is dropped.
= { = "0.2", = ["v8_18"] }
Run cargo run --release --example buffer_bench for an alternating ABBA
comparison of copied and owned raw exports. See the review and roadmap.
Working with memory
- Own the pixel buffer (simple and recommended):
let pixels = vec!; // RGB
let img = from_memory?;
let thumb = img.thumbnail?;
thumb.write_to_file?;
- Borrow a pixel buffer (make sure the backing data outlives all derived images):
let pixels = vec!;
let img = from_memory_reference?;
let thumb = img.thumbnail?;
thumb.write_to_file?;
Lifetimes and common pitfalls
- Prefer owned constructors (
from_file,from_memory) when possible. - Borrowing constructors (
from_memory_reference) tie the image lifetime to the borrowed slice. Do not let the slice drop before all derived images are fully used. - Native image references are retained by derived pipelines; creator handles may drop earlier. Every borrowed input buffer must still outlive the result.
API highlights
| Area | APIs |
|---|---|
| IO | from_file, from_memory, from_memory_reference, from_buffer, write_to_file, write_to_memory, write_to_buffer, write_jpeg, find_load/find_save, black/xyz/grey/sines/zone/perlin/gaussnoise |
| Geometry | thumbnail_file, thumbnail_buffer, thumbnail (in-memory), resize, resize_reduce, reduce, shrink_box, crop, embed, flip/rot/rotate/autorot, zoom, insert, gravity, extract_band, bandjoin2/bandjoin_const, copy/copy_memory, smartcrop, ifthenelse |
| Arithmetic | add/subtract/multiply/divide, linear/linear1, invert, pow_const, abs/sign/clamp, floor/ceil/rint |
| Math | sin/cos/tan, ln/log10/exp/exp10 |
| Relational | less/lesseq/more/moreeq/equal_const/notequal_const, boolean/shift const, bandand/bandor/bando |
| Color | cast, colourspace, falsecolour, premultiply/unpremultiply |
| Filter | gaussblur, sharpen, conv, erode/dilate, median, sobel, canny, maplut |
| Hist | hist_find/hist_local/hist_equal/hist_entropy, labelregions |
| Stats | avg, min_value, max_value, deviate, percent, getpoint, find_trim |
| Properties | width, height, size, bands |
| Drawing | draw_rect, draw_line, draw_circle, draw_flood, … (in-place) |
| Stitching | merge, mosaic, match_, globalbalance, remosaic |
| Interpolation | VipsInterpolate nearest / bilinear / custom |
Performance tips
- Prefer
VipsImage::thumbnail_file/VipsImage::thumbnail_bufferoverfrom_file/from_buffer+thumbnailfor downscales. They callvips_thumbnail/vips_thumbnail_buffer, which combine load and resize so libvips can shrink-on-load (often ~3× faster and several times lower peak memory thanthumbnail_image). Use the instance methodthumbnailonly for already-decoded raw pixels. - Prefer
write_to_fileoverwrite_to_memorywhen the destination is a path (avoids a full in-memory copy). - For already-decoded large images, benchmark
resize_reduceagainstresizebefore choosing the extra box pre-shrink pass; it can be slower on small inputs. - Tune
vips::set_concurrency,set_max_operations, andset_max_mem_bytesfor your workload. - libvips pipelines are lazy: operations enqueue work until the image is written or materialized.
Notes
- Initialization:
initandVipsInstanceshare process-wide state. Native objects use RAII; global state remains available until process exit. - Side effects: most operations return new images; drawing operations modify
self. - If a higher-level wrapper is missing, you can still access lower-level bindings in
vips-sysor use unsafevips::vips_call(...)to invoke libvips operations directly.
Local development
Builds resolve vips-sys from crates.io. To develop both crates together, add this
optional override at the workspace/consumer root:
[]
= { = "../vips-sys" }
Docker-based check (no local libvips required):
License
Changelog
See CHANGELOG.md.