libvips bindings for Java and JVM projects, using JDK 22's FFM and Class-File APIs to generate complete, safe, and fast APIs for image manipulation
libvips bindings for JVM projects, using the “Foreign Function & Memory API”
(JEP 454), and the “Class-File API” (JEP 457) released in JDK 22. The combination
of libvips, FFM, and auto-generated helpers means these bindings are complete (supporting all libvips operations), safe,
and faster than AWT or JNI-based alternatives.
Supports a vast range of image formats, including HEIC, JXL, WebP, PNG, JPEG, and more. Pronounced “vips (like zips)
eff-eff-emm”. The project is relatively new, but aims to be production ready. Tested on macOS 14, Windows 11, and Linux
(Ubuntu 24.04, Debian 12.1). Should work on any architecture you can use libvips and Java on (arm64/amd64/etc).
Used the library? I’d love to hear from more users - let me know in Discussions.
Please also give the repo a star 🌟️!
vips-ffm
is available on Maven Central. To get set up with Gradle:
repositories {
mavenCentral()
}
dependencies {
implementation("app.photofox.vips-ffm:vips-ffm-core:1.3.0")
}
When running your project you must add --enable-native-access=ALL-UNNAMED
to your JVM runtime arguments. If you
don’t, you’ll get a warning about “Restricted methods”. In the future, the JVM will throw an error if you don’t
explicitly include this flag.
As the project uses the Java FFM API, it must target JDK 22+. Bindings are currently generated from libvips 8.16.0
,
however they use the underlying libvips operation API. Most operations do not use the C API directly (as described
in the bindings docs) - they should be safe to use with different
libvips versions, assuming there haven’t been major changes.
[!NOTE]
This library does not includelibvips
in the download, you must add it to the system/container you’re building
for. See details in native library loading.
All libvips operations are exposed via helper classes, like VImage
. You must provide an Arena to operations like
VImage.newFromFile
, which constrains the lifetime of objects generated during usage. You can get an appropriate arena
by using Vips.run
as shown in the sample below. VImage
and associated enums have extensive
Javadocs included, which are automatically generated from the same source that the libvips website uses, for ease of use.
These helper objects expose their raw pointers as a last resort via functions like VTarget.getUnsafeStructAddress
- if
you need to use these raw pointers and can’t find an alternative, please file a GitHub Issue.
Helper enums are generated for the version of libvips shown above. If you need to use an enum from another version,
which isn’t present in vips-ffm
, you can use VipsOption.Enum(rawValue)
or VEnum.Raw(rawValue)
.
[!CAUTION]
Bindings generated byjextract
are available inVipsRaw
, and wrapped with validation inVipsHelper
. These
functions are difficult to use without accidentally causing memory leaks, or even segfaults! If what you want to do is
available inVImage
and otherV
-prefixed classes, use those instead. If you notice something missing, please open
a GitHub Issue.
To get a feeling for the bindings, here’s an indicative sample written in Kotlin (using the Java bindings) that:
import app.photofox.vipsffm.Vips
import app.photofox.vipsffm.VImage
import app.photofox.vipsffm.VipsOption
import app.photofox.vipsffm.enums.VipsAccess
// ...
// Call once to initialise libvips when your program starts, from any thread
Vips.init()
// Use `Vips.run` to wrap your usage of the API, and get an arena with an appropriate lifetime to use
// Usage of the API, arena, and resulting V-Objects must be done from the thread that called `Vips.run`
Vips.run { arena ->
val sourceImage = VImage.newFromFile(
arena,
"sample/src/main/resources/sample_images/rabbit.jpg"
)
val sourceWidth = sourceImage.width
val sourceHeight = sourceImage.height
logger.info("source image size: $sourceWidth x $sourceHeight")
val outputPath = workingDirectory.resolve("rabbit_copy.jpg")
sourceImage.writeToFile(outputPath.absolutePathString())
val thumbnail = sourceImage.thumbnailImage(
400,
VipsOption.Boolean("auto-rotate", true) // example of an option
)
val thumbnailWidth = thumbnail.width
val thumbnailHeight = thumbnail.height
logger.info("thumbnail image size: $thumbnailWidth x $thumbnailHeight")
}
// Optionally call at the end of your program, for memory leak detection, from any thread
Vips.shutdown()
Samples are included that show various usages of these bindings. They include validations, and run on GitHub Actions as
“end-to-end tests” during development. You can find them all listed here.
To get set up to run samples (on macOS):
brew install vips
sdk use java 22-open
./run_samples.sh
in your terminalRun samples
profile in IntelliJ[main] INFO vipsffm.SampleRunner - clearing sample run directory at path "sample_run"
[main] INFO vipsffm.SampleRunner - running sample "RawGetVersionSample"...
[main] INFO vipsffm.sample.RawGetVersionSample - libvips version: "8.15.5"
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "HelperGetVersionSample"...
[main] INFO vipsffm.sample.HelperGetVersionSample - libvips version: "8.15.5"
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageCreateThumbnailSample"...
[main] INFO vipsffm.sample.VImageCreateThumbnailSample - source image size: 2490 x 3084
[main] INFO vipsffm.sample.VImageCreateThumbnailSample - thumbnail image size: 323 x 400
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageChainSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VSourceTargetSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageCopyWriteSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VOptionHyphenSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageCachingSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageBlobSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageArrayJoinSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VBlobByteBufferSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VTargetToFileSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageJoinSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageFromBytesSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageStreamSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - shutting down vips to check for memory leaks...
memory: high-water mark 151.24 MB
[main] INFO vipsffm.SampleRunner - all samples ran successfully 🎉
These samples are also run in Docker containers, to verify behaviour on specific Linux distributions. They’re useful to
look at if you’re deploying libvips
and vips-ffm
workloads using containers.
You can find them in the docker_tests
folder.
This library requires the libvips
, glib
, and gobject
native libraries to be present in your library path:
DYLD_LIBRARY_PATH
(installed with brew install vips
)LD_LIBRARY_PATH
(installed with apt install libvips-dev
on Debian / Ubuntu)PATH
The naming conventions of these libraries are not consistent across operating systems, so vips-ffm attempts to load each
in the following order:
vips
, vips.{abiNumber}
, libvips-{abiNumber}
glib-2.0
, glib-2.0.{abiNumber}
, libglib-2.0-{abiNumber}
gobject-2.0
, gobject-2.0.{abiNumber}
, libgobject-2.0-{abiNumber}
Override properties are provided to set your own “ABI number”, but note that vips-ffm might not support that version
yet (which could manifest as crashes/segfaults):
vipsffm.abinumber.vips.override
, default: 42
vipsffm.abinumber.glib.override
, default: 0
vipsffm.abinumber.gobject.override
, default: 0
If you want to manually override the library lookup path for any of the above (for example, if you’re using a platform
like Android where it’s hard to set the system library path), you can do so using these system properties:
vipsffm.libpath.vips.override
(eg /opt/homebrew/lib/libvips.dylib
)vipsffm.libpath.glib.override
vipsffm.libpath.gobject.override
Ideas and suggestions are welcome, but please make sure they fit in to these goals, or you have a good argument about
why a goal should change!
VipsHelper
), so users aren’t blocked by helper bugs or API annoyances.I’m not currently looking for external code contributions. If you’d like to help the project:
Thank you for being enthusiastic about the project!
./publish_release_to_maven_central.sh <version matching github release version, including v prefix>