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 include libvips
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 by jextract
are available in VipsRaw
, and wrapped with validation in VipsHelper
. These
functions are difficult to use without accidentally causing memory leaks, or even segfaults! If what you want to do is
available in VImage
and other V
-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:
- Loads an original JPEG image from disk
- Writes a copy of it to disk
- Creates a 400px thumbnail from the original, and writes that to disk
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
- Then either:
- Run
./run_samples.sh
in your terminal - Use the included
Run samples
profile in IntelliJ
- Run
[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:
- On macOS:
DYLD_LIBRARY_PATH
(installed withbrew install vips
) - On Linux:
LD_LIBRARY_PATH
(installed withapt install libvips-dev
on Debian / Ubuntu) - On Windows:
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:
- libvips:
vipsffm.libpath.vips.override
(eg/opt/homebrew/lib/libvips.dylib
) - glib:
vipsffm.libpath.glib.override
- gobject:
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!
- Avoid manual work by automating as much as possible. This means upstream changes can be rapidly integrated.
- Use the libvips operations API, as described in the libvips documentation
- Provide access to the raw bindings (
VipsHelper
), so users aren't blocked by helper bugs or API annoyances. - Incubate in Photo Fox with some "real world" usage.
I'm not currently looking for external code contributions. If you'd like to help the project:
- Use the library and give your feedback in Discussions
- Or file an issue if you have a problem!
- Share the announcement post in your circles
- Star the repo π
Thank you for being enthusiastic about the project!
- GitHub Releases automatically result in a deployment to GitHub Packages
- Maven Central releases happen manually
- This can only be done by @lopcode
- And only after a GitHub Release is made
- Run
./publish_release_to_maven_central.sh <version matching github release version, including v prefix>