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 fast.
Supports a vast range of image formats, including HEIC, JXL, WebP, PNG, JPEG, and more. Pronounced "vips (like zips) eff-eff-emm".
Used the library, or just like what you've read so far? Please give the repo a star 🌟️!
repositories {
mavenCentral()
}
dependencies {
implementation("app.photofox.vips-ffm:vips-ffm-core:0.4.0")
}
As the project uses the FFM API, it must target JDK 22+. The bindings are currently generated from libvips 8.15.3
.
All libvips operations are exposed via helper classes, like VImage
. You must provide an Arena to operations like
VImage.newFromFile
, and this constrains the lifetime of objects generated during usage. Be careful to only keep the
arena, and image, in scope for as long as you need to! If the arena doesn't close, your memory usage will grow forever.
Constructing the VImage
object is cheap, as it's just a wrapper, so make them as you need.
This library does not include libvips
in the download, you must add it to the system you're building on, then make
sure it's available in DYLD_LIBRARY_PATH
(on macOS) or LD_LIBRARY_PATH
(on Linux).
🚨 Bindings generated by jextract
are available in VipsRaw
, and wrapped with validation in VipsHelper
. It's
difficult to use VipsRaw
functions 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, 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
Vips.init()
// Use `Vips.run` to wrap your usage of the API, and get an arena with an appropriate lifetime to use
Vips.run { arena ->
val sourceImage = VImage.newFromFile(
arena,
"sample/src/main/resources/sample_images/rabbit.jpg",
VipsOption.Enum("access", VipsAccess.ACCESS_SEQUENTIAL)
)
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.thumbnail(
"sample/src/main/resources/sample_images/rabbit.jpg",
400
)
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
Vips.shutdown()
The project has several samples, described below.
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 GObject API for operations, 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.
Samples are included that show various usages of the libvips
bindings. They include validations, and run on GitHub
Actions as "end-to-end tests" during development.
To get set up to run samples (on macOS):
brew install vips
sdk use java 22-open
./setup_lib_symlinks.sh
- (Optional, to regenerate bindings)
./generate_ffm_bindings.sh
- Then either:
- Run
./run_samples.sh
in your terminal - Run
SampleRunner
in IntelliJ to run samples and validations
- Run
[main] INFO vipsffm.SampleRunner - clearing sample run directory at path "sample_run"
[main] INFO vipsffm.SampleRunner - running sample "RawGetVersionSample"...
[main] INFO vipsffm.RawGetVersionSample - libvips version: "8.15.3"
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "HelperGetVersionSample"...
[main] INFO vipsffm.HelperGetVersionSample - libvips version: "8.15.3"
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - running sample "VImageCreateThumbnailSample"...
[main] INFO vipsffm.RawGetVersionSample - source image size: 2490 x 3084
[main] INFO vipsffm.RawGetVersionSample - 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 "VImageWriteSample"...
[main] INFO vipsffm.SampleRunner - validation succeeded ✅
[main] INFO vipsffm.SampleRunner - shutting down vips to check for memory leaks...
memory: high-water mark 82.66 MB
[main] INFO vipsffm.SampleRunner - all samples ran successfully 🎉
- 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>