Introduction
ol.vips wraps libvips’s image graph in
Closeable
Clojure values. Loader functions such as v/from-file, v/from-buffer,
and v/from-stream return image handles, and operations such as
ops/resize, ops/colourspace, and v/call return new handles rather
than mutating the original image.
This model is immutable and pipeline-oriented. In the common case, libvips reads only enough of the input to discover image metadata and build an operation graph. Calling loaders and transforms usually does not decode the whole image up front.
libvips is demand-driven, so the pixel work happens when something
downstream asks for image data. In ol.vips, that usually means a sink
such as v/write-to-file, v/write-to-buffer, or v/write-to-stream.
At that point libvips pulls pixels through the pipeline, processing
small regions with a horizontally threaded execution model instead of
materializing every intermediate image in memory.
Some operations that need actual pixel values can trigger evaluation
earlier, but metadata queries such as v/width, v/height, and
v/metadata can often be satisfied from loader metadata alone. Because
the image graph is immutable and operations are generally side-effect
free, libvips can also reuse and cache operation results internally.
ol.vips adds a small amount of Clojure-specific structure on top of
that model. The ol.vips.operations namespace is generated from libvips
introspection data, ol.vips.enums exposes the enum keywords used by
many options, and the top-level ol.vips namespace provides the file,
buffer, stream, metadata, and convenience helpers you use most often.
Why libvips?
The typical libvips workflow is exactly what many applications need:
load a large source image, resize or transform it, and write a smaller
web-friendly JPEG, PNG, WebP, AVIF, or TIFF result. ol.vips gives
Clojure access to that model without shelling out to external image
tools or spawning child processes.
libvips is blazing fast and memory efficient because it usually works on small regions of uncompressed image data at a time, takes advantage of multiple CPU cores, and avoids materializing every intermediate result in memory. That execution model is a strong fit for image pipelines, thumbnails, upload processing, preprocessing for vision workloads, and other server-side image tasks.
It also brings broad format and operation support: 300 operations covering arithmetic, histograms, convolution, morphology, colour, resampling, statistics, and more, with correct handling of colour spaces, embedded ICC profiles, and alpha channels.
libvips was originally created at Birkbeck College and is currently maintained by John Cupitt and other contributors.
Bindings Generation
libvips itself is very amenable to automatic bindings generation. The
core of ol.vips is small introspection layer that loads the native
library then introspects it to generate ol.vips.operations and
ol.vips.enums, and then a small runtime for loading/reading/writing.
As a convenience for Clojure users there are separate jars available
with the native libraries per-platform, but ol.vips it self does not
depend on them directly. You can depend on one (or more) of those native
deps, or provide your own libvips build with -Dol.vips.native.preload.
You’ll need to choose one.
This means that you can even use a custom libvips build or a newer
version of libvips without having to wait for ol.vips to catch up
(though I expect to keep it up to date). Any new or updated operations
in libvips can be used with this library using the lower level
ol.vips/call function. You can even generate the bindings yourself if
you need to, simply use ol.vips.codegen.
Going to Production
See Going to Production
for production guidance on pipeline shape, sequential access, untrusted
inputs, sanity checks, allocator tuning, and cache settings in ol.vips.
See Examples for runnable snippets that cover file, buffer, stream, metadata, thumbnailing, transforms, composition, and animated-image workflows.
Loading the native library
ol.vips uses this procedure to load the native components:
-
Load any explicit libraries listed in
-Dol.vips.native.preload. -
Load the extracted libraries from the platform native jar on the classpath.
-
If that fails, fall back to the system library loader, which can resolve
libvipsfromLD_LIBRARY_PATH.
1. Preload
-Dol.vips.native.preload is for exact native library file paths. Use
it when you want to point ol.vips at a specific custom build in a
non-standard location, or when you need to preload dependency libraries
before libvips itself. On Nix-like systems that can include things like
libstdc++.so.6 as well as your libvips library. The value
is a single string containing one or more full library file paths
separated by the OS path separator, which is : on Linux and macOS and
; on Windows.
If ol.vips.native.preload is set, those entries are always loaded
first. If any preload entry fails to load, the packaged native-jar path
is abandoned for that initialization attempt and ol.vips falls back to
the system library loader instead of continuing with the native jar.
2. Classpath native bundle
ol.vips detects the current platform, builds a resource path like
ol/vips/native/<os>-<arch> on macOS and Windows or
ol/vips/native/<os>-<arch>-<libc> on Linux, and then looks
for manifest.edn under that path.
For example, Linux x86-64 glibc resolves to
ol/vips/native/linux-x86-64-gnu/manifest.edn. If that resource is
present, ol.vips reads the manifest, extracts the bundled native
libraries into the local cache, and loads those extracted files.
You can override parts of that platform detection with
-Dol.vips.native.platform-id, -Dol.vips.native.os,
-Dol.vips.native.arch, and -Dol.vips.native.libc.
In practice, this means the companion jar for the current runtime
platform needs to be on the classpath, meaning the current OS, CPU
architecture, and Linux libc when applicable. This path is attempted
after any explicit ol.vips.native.preload entries.
In the successful packaged case, v/init! will report
:native-load-source :packaged and expose the extracted primary library
path in :primary-library-path, which is useful when debugging exactly
what got loaded.
3. System fallback
The system-library fallback loads by library name rather than full file
path. Use this when you want the OS or JVM loader to resolve libvips
from LD_LIBRARY_PATH, standard system locations, or other
platform-specific loader configuration. A platform native jar is not
required for this path; if the packaged load fails, including because no
matching native jar is present on the classpath, ol.vips can still
fall back to the system loader.
By default the system fallback tries vips-cpp and then vips. Most
users should not need to change this. -Dol.vips.native.system-libs
exists for the uncommon case where your environment exposes libvips
under different system library names. Like ol.vips.native.preload, it
accepts multiple entries separated by the OS path separator.
The fallback only applies to native library loading. If the packaged
libraries load successfully but vips_init fails afterwards,
ol.vips does not automatically retry via the system loader. For
debugging, v/init! exposes runtime state including whether
initialization came from the packaged path or the system fallback.