javajavax.imageiotwelvemonkeys

Strange exception when trying to save tif with dpi


Java-Version:
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)

TwelveMonkeys ImageIO Dependencies:

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

Case:
I'm trying to save an subimage as a tif with dpi information.
Sometimes it works, sometimes I get a strange exception.

First of all, here's my code how I create the subimage:

//Reading JPG as BufferedImage
BufferedImage bi = ImageIO.read(new FileImageInputStream(jpg.toFile()));
//Create Subimage (r is an java.awt.Rectangle)
BufferedImage subimage = bi.getSubimage(r.x - 10, r.y - 10, r.width + 20, r.height + 20);
saveImage(subimage, new File("destfile.tif"));

It is ensured, that this subimage is valid.

Now the method "saveImage", inspired by this post
https://github.com/haraldk/TwelveMonkeys/issues/439#issue-355278313

   public static void saveImage(BufferedImage image, File destFile) throws IOException {
        String format = "tif";

        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);

        if (!writers.hasNext()) {
            throw new IllegalArgumentException("No writer for: " + format);
        }

        ImageWriter writer = writers.next();

        try {
            // Create output stream (in try-with-resource block to avoid leaks)
            try (ImageOutputStream output = ImageIO.createImageOutputStream(destFile)) {
                writer.setOutput(output);

                // set the resolution of the target image to 200 dpi
                final List<Entry> entries = new ArrayList<Entry>();
                entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(200)));
                entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(200)));

                final IIOMetadata tiffImageMetadata = new TIFFImageMetadata(entries);

                writer.write(new IIOImage(image, null, tiffImageMetadata));
            }

        }
        finally {
            // Dispose writer in finally block to avoid memory leaks
            writer.dispose();
        }
    }

Sometimes, saving this TIF works like a charm without any problems. But in some cases, I receive the following exception. In this case, I have to restart the application and give it another try:

java.lang.StringIndexOutOfBoundsException: begin 0, end -1, length 3
    at java.base/java.lang.String.checkBoundsBeginEnd(String.java:3319)
    at java.base/java.lang.String.substring(String.java:1874)
    at com.github.jaiimageio.plugins.tiff.TIFFField.initData(TIFFField.java:406)
    at com.github.jaiimageio.plugins.tiff.TIFFField.createFromMetadataNode(TIFFField.java:486)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata.parseIFD(TIFFImageMetadata.java:1588)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata.mergeNativeTree(TIFFImageMetadata.java:1612)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageMetadata.mergeTree(TIFFImageMetadata.java:1636)
    at java.desktop/javax.imageio.metadata.IIOMetadata.setFromTree(IIOMetadata.java:752)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriter.convertNativeImageMetadata(TIFFImageWriter.java:515)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriter.write(TIFFImageWriter.java:2551)
    at com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriter.write(TIFFImageWriter.java:2383)
    at java.desktop/javax.imageio.ImageWriter.write(ImageWriter.java:595)
    at de.buerotex.util.BufferedImageUtil.saveImage(BufferedImageUtil.java:64)

I looked in the source and saw, that this class is trying to split a value at this point:
at com.github.jaiimageio.plugins.tiff.TIFFField.initData(TIFFField.java:406)

case TIFFTag.TIFF_RATIONAL:
   slashPos = value.indexOf("/");
   numerator = value.substring(0, slashPos);
   denominator = value.substring(slashPos + 1);

where "value" = 200.

I do not know where and why this constant "TIFF_RATIONAL" is beeing set.

Where does this error come from and why? When I disable setting the tiffImageMetadata in my save-method by setting the third parameter to null:

writer.write(new IIOImage(image, null, null));

everything is working fine. But my tif-images don't have any dpi-values being set.


Solution

  • I think I found the answer by myself after crawling through the code.

    When creating a new Object of TIFFEntry

    new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(200));
    

    I saw that in the constructor it is trying to guess the type. Because of I provided a Rational-object (like in the code-snippet I mentioned in the link in the question) it is setting the wrong type.
    After setting the value as integer

    new TIFFEntry(TIFF.TAG_X_RESOLUTION, 200)
    

    it seems so work properly.

    Update:

    After talking to the developer @HaraldK he gave me the hint I needed:
    I also using tess4j in my project, which has JAI Core in its dependencies.

    So I updated my code that the right Writer is picked up:

    Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
            if (!writers.hasNext()) {
                throw new IllegalArgumentException("No writer for: " + format);
            }
    
            ImageWriter writer = null;
            while (writers.hasNext()) {
                writer = writers.next();
                // Find the right writer
                if (writer.getClass().getName().contains("twelvemonkeys")) {
                    break;
                }
                writer = null;
            }
    
            if (Objects.isNull(writer)) {
                throw new IOException("Could not find twelvemonkeys Writer");
            }