diff --git a/README.md b/README.md
index 9daa559..42de00e 100644
--- a/README.md
+++ b/README.md
@@ -8,20 +8,27 @@ A Java interface to [llibvips](http://libvips.github.io/libvips/), the fast imag
+ * java.nio.Path path = image.tiff().save();
+ *
+ *
+ * vips_tiffsave()
+ *
+ * @return the {@link TiffSaveOperation}
+ */
+ public TiffSaveOperation tiff() {
+ return new TiffSaveOperation(this);
+ }
+
/**
* Get the width of this image.
*
diff --git a/src/main/java/org/jlibvips/VipsRegionShrink.java b/src/main/java/org/jlibvips/VipsRegionShrink.java
new file mode 100644
index 0000000..d670cb0
--- /dev/null
+++ b/src/main/java/org/jlibvips/VipsRegionShrink.java
@@ -0,0 +1,14 @@
+package org.jlibvips;
+
+/**
+ * https://libvips.github.io/libvips/API/current/VipsRegion.html#VipsRegionShrink
+ */
+public enum VipsRegionShrink {
+ VIPS_REGION_SHRINK_MEAN,
+ VIPS_REGION_SHRINK_MEDIAN,
+ VIPS_REGION_SHRINK_MODE,
+ VIPS_REGION_SHRINK_MAX,
+ VIPS_REGION_SHRINK_MIN,
+ VIPS_REGION_SHRINK_NEAREST,
+ VIPS_REGION_SHRINK_LAST
+}
diff --git a/src/main/java/org/jlibvips/jna/VipsBindings.java b/src/main/java/org/jlibvips/jna/VipsBindings.java
index 8a05cd6..69455dc 100644
--- a/src/main/java/org/jlibvips/jna/VipsBindings.java
+++ b/src/main/java/org/jlibvips/jna/VipsBindings.java
@@ -25,6 +25,7 @@ public interface VipsBindings extends Library {
int vips_jpegsave(Pointer in, String filename, Object...args);
int vips_webpsave(Pointer in, String filename, Object...args);
int vips_pngsave(Pointer in, String filename, Object...args);
+ int vips_tiffsave(Pointer in, String filename, Object...args);
int vips_insert(Pointer main, Pointer sub, Pointer[] out, int x, int y, Object...args);
int vips_join(Pointer in1, Pointer in2, Pointer[] out, int direction, Object...args);
diff --git a/src/main/java/org/jlibvips/jna/VipsBindingsSingleton.java b/src/main/java/org/jlibvips/jna/VipsBindingsSingleton.java
index b3cd256..b896623 100644
--- a/src/main/java/org/jlibvips/jna/VipsBindingsSingleton.java
+++ b/src/main/java/org/jlibvips/jna/VipsBindingsSingleton.java
@@ -4,8 +4,8 @@
public class VipsBindingsSingleton {
-
- private static String libraryPath = "vips";
+ private static final String ENV_LIB_PATH = "JLIBVIPS_LIB_PATH";
+ private static String libraryPath = System.getenv(ENV_LIB_PATH) == null ? "vips" : System.getenv(ENV_LIB_PATH);
public static void configure(String lp) {
libraryPath = lp;
@@ -16,9 +16,13 @@ public static void configure(String lp) {
public static VipsBindings instance() {
if(INSTANCE == null) {
if(libraryPath == null || libraryPath.isEmpty()) {
- throw new IllegalStateException("Please call VipsBindingsSingleton.configure(...) before getting the instance.");
+ throw new IllegalStateException("Please call VipsBindingsSingleton.configure(...) or set env var JLIBVIPS_LIB_PATH before getting the instance.");
+ }
+ try {
+ INSTANCE = Native.load(libraryPath, VipsBindings.class);
+ } catch (UnsatisfiedLinkError e) {
+ throw new IllegalStateException("Please call VipsBindingsSingleton.configure(...) or set env var JLIBVIPS_LIB_PATH before getting the instance.");
}
- INSTANCE = Native.load(libraryPath, VipsBindings.class);
}
return INSTANCE;
}
diff --git a/src/main/java/org/jlibvips/jna/glib/GLibBindingsSingleton.java b/src/main/java/org/jlibvips/jna/glib/GLibBindingsSingleton.java
index d955916..839ce52 100644
--- a/src/main/java/org/jlibvips/jna/glib/GLibBindingsSingleton.java
+++ b/src/main/java/org/jlibvips/jna/glib/GLibBindingsSingleton.java
@@ -4,7 +4,10 @@
public class GLibBindingsSingleton {
- private static String libraryPath = "/usr/local/opt/glib/lib/libglib-2.0.dylib";
+ private static final String ENV_GLIBC_PATH = "JLIBVIPS_GLIBC_PATH";
+ private static String libraryPath = System.getenv(ENV_GLIBC_PATH) == null
+ ? "libglib-2.0"
+ : System.getenv(ENV_GLIBC_PATH);
public static void configure(String lp) {
libraryPath = lp;
diff --git a/src/main/java/org/jlibvips/operations/TiffSaveOperation.java b/src/main/java/org/jlibvips/operations/TiffSaveOperation.java
new file mode 100644
index 0000000..6031741
--- /dev/null
+++ b/src/main/java/org/jlibvips/operations/TiffSaveOperation.java
@@ -0,0 +1,338 @@
+package org.jlibvips.operations;
+
+import org.jlibvips.*;
+import org.jlibvips.exceptions.VipsException;
+import org.jlibvips.jna.VipsBindingsSingleton;
+import org.jlibvips.util.Varargs;
+import org.jlibvips.util.VipsUtils;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * Write a VIPS image to a file as TIFF.
+ */
+public class TiffSaveOperation implements SaveOperation {
+
+ private final VipsImage image;
+
+ private Integer quality;
+ private VipsForeignTiffCompression tiffCompression;
+ private Boolean tile;
+ private Integer tileWidth;
+ private Integer tileHeight;
+ private Boolean pyramid;
+ private VipsForeignTiffPredictor predictor;
+ private String profile;
+ private Integer bitdepth;
+ private Boolean miniswhite;
+ private VipsForeignTiffResunit resunit;
+ private Double xres;
+ private Double yres;
+ private Boolean bigtiff;
+ private Boolean properties;
+ private VipsRegionShrink region_shrink;
+ private VipsForeignDzDepth depth;
+ private Integer level;
+ private Boolean lossless;
+ private Boolean subifd;
+
+ public TiffSaveOperation(VipsImage image) {
+ this.image = image;
+ }
+
+ @Override
+ public Path save() throws IOException, VipsException {
+ if (tileWidth % 128 != 0 || tileHeight % 128 != 0) {
+ throw new VipsException("Wrong value for tileWidth or TileHeight", 1);
+ }
+ Path path = Files.createTempFile("jlibvips", ".tif");
+ int ret = VipsBindingsSingleton.instance().vips_tiffsave(image.getPtr(), path.toString(),
+ new Varargs().add("Q", quality)
+ .add("compression", VipsUtils.toOrdinal(tiffCompression))
+ .add("tile", VipsUtils.booleanToInteger(tile))
+ .add("tile_width", tileWidth)
+ .add("tile_height", tileHeight)
+ .add("pyramid", VipsUtils.booleanToInteger(pyramid))
+ .add("predictor", VipsUtils.toOrdinal(predictor))
+ .add("profile", profile)
+ .add("bitdepth", bitdepth)
+ .add("miniswhite", VipsUtils.booleanToInteger(miniswhite))
+ .add("resunit", VipsUtils.toOrdinal(resunit))
+ .add("xres", xres)
+ .add("yres", yres)
+ .add("bigtiff", VipsUtils.booleanToInteger(bigtiff))
+ .add("properties", VipsUtils.booleanToInteger(properties))
+ .add("region_shrink", VipsUtils.toOrdinal(region_shrink))
+ .add("depth", VipsUtils.toOrdinal(depth))
+ .add("level", level)
+ .add("lossless", VipsUtils.booleanToInteger(lossless))
+ .add("subifd", VipsUtils.booleanToInteger(subifd))
+ .toArray());
+ if (ret != 0) {
+ throw new VipsException("vips_tiffsave", ret);
+ }
+ return path;
+ }
+
+ /**
+ * Set the JPEG compression factor. Default 75.
+ *
+ * @param q quality factor
+ * @return this
+ */
+ public TiffSaveOperation quality(Integer q) {
+ this.quality = q;
+ return this;
+ }
+
+ /**
+ * Use compression to set the tiff compression. Currently jpeg, packbits, fax4,
+ * lzw, none, deflate, webp and zstd are supported. The default is no compression.
+ * JPEG compression is a good lossy compressor for photographs, packbits is good
+ * for 1-bit images, and deflate is the best lossless compression TIFF can do.
+ *
+ * @param compression compression ENUM
+ * @return this
+ */
+ public TiffSaveOperation compression(VipsForeignTiffCompression compression) {
+ this.tiffCompression = compression;
+ return this;
+ }
+
+ /**
+ * Set true to write a tiled tiff
+ *
+ * @param tile true or false
+ * @return this
+ */
+ public TiffSaveOperation tile(boolean tile) {
+ this.tile = tile;
+ return this;
+ }
+
+ /**
+ * Set tile width, must be 2^N, i.e. 128, 256, 512 etc
+ * Default is 128.
+ *
+ * @param tileWidth width of tiles
+ * @return this
+ */
+ public TiffSaveOperation tileWidth(int tileWidth) {
+ this.tileWidth = tileWidth;
+ return this;
+ }
+
+ /**
+ * Set tile width, must be 2^N, i.e. 128, 256, 512 etc
+ * Default is 128.
+ *
+ * @param tileHeight height of tiles
+ * @return this
+ */
+ public TiffSaveOperation tileHeight(int tileHeight) {
+ this.tileHeight = tileHeight;
+ return this;
+ }
+
+ /**
+ * Set pyramid to write the image as a set of images, one per page, of decreasing size.
+ * By default, the pyramid stops when the image is small enough to fit in one tile.
+ * Use depth to stop when the image fits in one pixel, or to only write a single layer.
+ *
+ * @param pyramid true or false
+ * @return this
+ */
+ public TiffSaveOperation pyramid(boolean pyramid) {
+ this.pyramid = pyramid;
+ return this;
+ }
+
+ /**
+ * Use predictor to set the predictor for lzw and deflate compression.
+ * It defaults to VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL, meaning horizontal
+ * differencing. Please refer to the libtiff specifications for further
+ * discussion of various predictors.
+ *
+ * @param predictor enum
+ * @return this
+ */
+ public TiffSaveOperation predictor(VipsForeignTiffPredictor predictor) {
+ this.predictor = predictor;
+ return this;
+ }
+
+ /**
+ * Use profile to give the filename of a profile to be embedded in the TIFF.
+ * This does not affect the pixels which are written, just the way they are tagged.
+ * See vips_profile_load() for details on profile naming.
+ *
+ * If no profile is specified and the VIPS header contains an ICC profile named + * VIPS_META_ICC_NAME, the profile from the VIPS header will be attached. + * + * @param profile name of profile + * @return this + */ + public TiffSaveOperation profile(String profile) { + this.profile = profile; + return this; + } + + /** + * Set bitdepth to save 8-bit uchar images as 1, 2 or 4-bit TIFFs. In case of + * depth 1: Values >128 are written as white, values <=128 as black. Normally + * vips will write MINISBLACK TIFFs where black is a 0 bit, but if you set + * miniswhite, it will use 0 for a white bit. Many pre-press applications + * only work with images which use this sense. miniswhite only affects one-bit images, + * it does nothing for greyscale images. In case of depth 2: The same holds + * but values < 64 are written as black. For 64 <= values < 128 they are written + * as dark grey, for 128 <= values < 192 they are written as light gray and values + * above are written as white. In case miniswhite is set to true this behavior is + * inverted. In case of depth 4: values < 16 are written as black, and so on for + * the lighter shades. In case miniswhite is set to true this behavior is inverted. + * + * @param bitdepth the bit depth + * @return this + */ + public TiffSaveOperation bitdepth(Integer bitdepth) { + this.bitdepth = bitdepth; + return this; + } + + /** + * Normally vips will write MINISBLACK TIFFs where black is a 0 bit, but if you set + * miniswhite , it will use 0 for a white bit. Many pre-press applications only work + * with images which use this sense. miniswhite only affects one-bit images, it does + * nothing for greyscale images. + * + * @param miniswhite true or false + * @return this + */ + public TiffSaveOperation miniswhite(boolean miniswhite) { + this.miniswhite = miniswhite; + return this; + } + + /** + * Use xres and yres to override the default horizontal and vertical resolutions. + * By default these values are taken from the VIPS image header. libvips resolution + * is always in pixels per millimetre. + * + * @param xres horizontal resolution in pixels/mm + * @return this + */ + public TiffSaveOperation xres(Double xres) { + this.xres = xres; + return this; + } + + /** + * Use xres and yres to override the default horizontal and vertical resolutions. + * By default these values are taken from the VIPS image header. libvips resolution + * is always in pixels per millimetre. + * + * @param yres horizontal resolution in pixels/mm + * @return this + */ + public TiffSaveOperation yres(Double yres) { + this.yres = yres; + return this; + } + + /** + * Set bigtiff to attempt to write a bigtiff. Bigtiff is a variant of the + * TIFF format that allows more than 4GB in a file. + * + * @param bigtiff true or false + * @return this + */ + public TiffSaveOperation bigtiff(boolean bigtiff) { + this.bigtiff = bigtiff; + return this; + } + + /** + * Set properties to write all vips metadata to the IMAGEDESCRIPTION tag as xml. + * If properties is not set, the value of VIPS_META_IMAGEDESCRIPTION is used instead. + * + * @param properties set TRUE to write an IMAGEDESCRIPTION tag + * @return this + */ + public TiffSaveOperation properties(boolean properties) { + this.properties = properties; + return this; + } + + /** + * Use region_shrink to set how images will be shrunk when generating pyramid: + * by default each 2x2 block is just averaged, but you can set MODE or MEDIAN as well. + * + * @param region_shrink How to shrink each 2x2 region. + * @return this + */ + public TiffSaveOperation region_shrink(VipsRegionShrink region_shrink) { + this.region_shrink = region_shrink; + return this; + } + + /** + * By default, the pyramid stops when the image is small enough to fit in one tile. + * Use depth to stop when the image fits in one pixel, or to only write a single layer. + * + * @param depth how deep to make the pyramid + * @return this + */ + public TiffSaveOperation depth(VipsForeignDzDepth depth) { + this.depth = depth; + return this; + } + + /** + * Use level to set the ZSTD compression level. + * + * @param level Zstd compression level + * @return this + */ + public TiffSaveOperation level(Integer level) { + this.level = level; + return this; + } + + /** + * Use lossless to set WEBP lossless mode on + * + * @param lossless WebP losssless mode + * @return this + */ + public TiffSaveOperation lossless(boolean lossless) { + this.lossless = lossless; + return this; + } + + /** + * Set subifd to save pyramid layers as sub-directories of the main image. + * Setting this option can improve compatibility with formats like OME. + * + * @param subifd set TRUE to write pyr layers as sub-ifds + * @return this + */ + public TiffSaveOperation subifd(boolean subifd) { + this.subifd = subifd; + return this; + } + + /** + * Use resunit to override the default resolution unit. + * The default resolution unit is taken from the header field + * VIPS_META_RESOLUTION_UNIT. If this field is not set, then + * VIPS defaults to cm. + * + * @param resunit set resolution unit + * @return this + */ + public TiffSaveOperation resunit(VipsForeignTiffResunit resunit) { + this.resunit = resunit; + return this; + } +} diff --git a/src/test/groovy/org/jlibvips/Drawing.groovy b/src/test/groovy/org/jlibvips/Drawing.groovy index 924f100..c474ff6 100644 --- a/src/test/groovy/org/jlibvips/Drawing.groovy +++ b/src/test/groovy/org/jlibvips/Drawing.groovy @@ -4,7 +4,6 @@ package org.jlibvips import spock.lang.Specification import java.nio.file.Files -import java.nio.file.Paths import java.nio.file.StandardCopyOption import static org.jlibvips.TestUtils.copyResourceToFS @@ -65,6 +64,7 @@ class Drawing extends Specification { def image = VipsImage.fromFile imagePath def whiteRgb = color(255, 255, 255) def transparent = color(0, 0, 0, 0) + def dir = Files.createTempDirectory("tint") when: def lut = VipsImage.identity().create() lut = lut.moreEq(200).ifthenelse(lut.newFromImage(transparent), lut.newFromImage(tint)) @@ -77,7 +77,7 @@ class Drawing extends Specification { then: tinted != null println tinted - def dest = Paths.get("/Volumes/HD/backups/images/${colorName}.png") + def dest = dir.resolve( "${colorName}.png") Files.move(tinted.png().save(), dest, StandardCopyOption.REPLACE_EXISTING) cleanup: Files.deleteIfExists imagePath