Create Multi-Page Tiff with Java

jonD02 picture jonD02 · Dec 8, 2016 · Viewed 8.5k times · Source

I'm interested in taking a tif image and adding a layer to it that contains text with Java, preferably with the Twelve Monkeys image library if possible.

I can tweak the code from here to either add text to a tif or create a new tif of the same size with only text, but not save them as a multi-page tif. For example:

import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.*;
import java.io.*;

public class ImageUtil {

public static void main(String[] args) throws Exception {

    BufferedImage src = ImageIO.read(new File("/path/to/main.tif"));
    BufferedImage text = createTextLayer(src);
    BufferedImage[] images = new BufferedImage[]{src, text};
    createMultiPage(images);

}

private static BufferedImage createTextLayer(BufferedImage src) {
    int w = src.getWidth();
    int h = src.getHeight();
    BufferedImage img = new BufferedImage(
            w, h, BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2d = img.createGraphics();
    g2d.drawImage(img, 0, 0, null);

    g2d.setPaint(Color.red);
    g2d.setFont(new Font("Serif", Font.BOLD, 200));
    String s = "Hello, world!";
    FontMetrics fm = g2d.getFontMetrics();
    int x = img.getWidth() - fm.stringWidth(s) - 5;
    int y = fm.getHeight() * 5;
    g2d.drawString(s, x, y);
    g2d.dispose();
    return img;
}

private static void createMultiPage(BufferedImage[] images) throws IOException {

    File tempFile = new File("/new/file/path.tif");
    //I also tried passing in stream var below to the try, but also receive java.lang.UnsupportedOperationException: Unsupported write variant!
    //OutputStream stream = new FileOutputStream(tempFile);

    // Obtain a TIFF writer
    ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();

    try (ImageOutputStream output = ImageIO.createImageOutputStream(tempFile)) {
        writer.setOutput(output);

        ImageWriteParam params = writer.getDefaultWriteParam();
        params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        params.setCompressionType("None");

        //error here: java.lang.UnsupportedOperationException: Unsupported write variant!
        writer.prepareWriteSequence(null);

        for (int i = 0; i < images.length; i++){
            writer.writeToSequence(new IIOImage(images[i], null, null), params);
        }

        // We're done
        writer.endWriteSequence();
    }

}
}

Maven:

<dependency>
    <groupId>com.twelvemonkeys.imageio</groupId>
    <artifactId>imageio-tiff</artifactId>
    <version>3.2.1</version>
</dependency>

How can I create a multi-page tif from an image and the generated text-image?

I was able to get the following code to run for jpgs, but jpgs don't have layers.

public static void testWriteSequence() throws IOException {
    BufferedImage[] images = new BufferedImage[] {
            new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
            new BufferedImage(110, 100, BufferedImage.TYPE_INT_RGB),
            new BufferedImage(120, 100, BufferedImage.TYPE_INT_RGB),
            new BufferedImage(130, 100, BufferedImage.TYPE_INT_RGB)
    };

    Color[] colors = {Color.BLUE, Color.GREEN, Color.RED, Color.ORANGE};

    for (int i = 0; i < images.length; i++) {
        BufferedImage image = images[i];
        Graphics2D g2d = image.createGraphics();
        try {
            g2d.setColor(colors[i]);
            g2d.fillRect(0, 0, 100, 100);
        }
        finally {
            g2d.dispose();
        }
    }

    //ImageWriter writer = createImageWriter();
    ImageWriter writer = ImageIO.getImageWritersByFormatName("JPEG").next();

    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    try (ImageOutputStream output = ImageIO.createImageOutputStream(buffer)) {
        writer.setOutput(output);

        ImageWriteParam params = writer.getDefaultWriteParam();
        params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

        writer.prepareWriteSequence(null);

        params.setCompressionType("JPEG");
        writer.writeToSequence(new IIOImage(images[0], null, null), params);

        params.setCompressionType("JPEG");
        writer.writeToSequence(new IIOImage(images[1], null, null), params);

        params.setCompressionType("JPEG");
        writer.writeToSequence(new IIOImage(images[2], null, null), params);

        params.setCompressionType("JPEG");
        writer.writeToSequence(new IIOImage(images[3], null, null), params);

        writer.endWriteSequence();

        File tempFile = new File("/path/to/new/file.jpg");
        OutputStream out = new FileOutputStream(tempFile);

        buffer.writeTo(out);
    }
}

Thank you.

Answer

Harald K picture Harald K · Dec 8, 2016

You can write multi-page images (in formats that supports it, like TIFF), using the standard ImageIO API. Now that Java ImageIO comes with a TIFF plugin bundled, starting from Java 9, the below should just work, with no extra dependencies. For Java 8 and earlier, you still need a TIFF plugin, like JAI or TwelveMonkeys as mentioned.

See for example the TIFFImageWriterTest.testWriteSequence method from the TwelveMonkeys ImageIO project's test cases, for an example of how to do it.

The important part:

BufferedImage[] images = ...
OutputStream stream = ... // May also use File here, as output

// Obtain a TIFF writer
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();

try (ImageOutputStream output = ImageIO.createImageOutputStream(stream)) {
    writer.setOutput(output);

    ImageWriteParam params = writer.getDefaultWriteParam();
    params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);

    // Compression: None, PackBits, ZLib, Deflate, LZW, JPEG and CCITT variants allowed
    // (different plugins may use a different set of compression type names)
    params.setCompressionType("Deflate");

    writer.prepareWriteSequence(null);

    for (BufferedImage image : images) {
        writer.writeToSequence(new IIOImage(image, null, null), params);
    }

    // We're done
    writer.endWriteSequence();
}

writer.dispose();