diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java index 49eb041e..37f7e6d9 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java @@ -198,7 +198,7 @@ public final class TIFFImageWriter extends ImageWriterBase { int compression; if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA) && image.getMetadata() != null && metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION) != null) { - compression = (int) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue(); + compression = ((Number) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue()).intValue(); } else { compression = TIFFImageWriteParam.getCompressionType(param); @@ -243,8 +243,7 @@ public final class TIFFImageWriter extends ImageWriterBase { default: } - // TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support. - int photometric = compression == TIFFExtension.COMPRESSION_JPEG ? TIFFExtension.PHOTOMETRIC_YCBCR : getPhotometricInterpretation(colorModel); + int photometric = getPhotometricInterpretation(colorModel, compression); entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric)); if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) { @@ -463,7 +462,7 @@ public final class TIFFImageWriter extends ImageWriterBase { // Use predictor by default for LZW and ZLib/Deflate // TODO: Unless explicitly disabled in TIFFImageWriteParam - int compression = (int) entries.get(TIFF.TAG_COMPRESSION).getValue(); + int compression = ((Number) entries.get(TIFF.TAG_COMPRESSION).getValue()).intValue(); OutputStream stream; switch (compression) { @@ -519,7 +518,8 @@ public final class TIFFImageWriter extends ImageWriterBase { long option = 0L; if (compression != TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE) { - option = (long) entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4 ? TIFF.TAG_GROUP3OPTIONS : TIFF.TAG_GROUP4OPTIONS).getValue(); + Entry optionsEntry = entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4 ? TIFF.TAG_GROUP3OPTIONS : TIFF.TAG_GROUP4OPTIONS); + option = ((Number) optionsEntry.getValue()).longValue(); } Entry fillOrderEntry = entries.get(TIFF.TAG_FILL_ORDER); @@ -533,7 +533,7 @@ public final class TIFFImageWriter extends ImageWriterBase { throw new IllegalArgumentException(String.format("Unsupported TIFF compression: %d", compression)); } - private int getPhotometricInterpretation(final ColorModel colorModel) { + private int getPhotometricInterpretation(final ColorModel colorModel, int compression) { if (colorModel.getPixelSize() == 1) { if (colorModel instanceof IndexColorModel) { if (colorModel.getRGB(0) == 0xFFFFFFFF && colorModel.getRGB(1) == 0xFF000000) { @@ -555,7 +555,7 @@ public final class TIFFImageWriter extends ImageWriterBase { case ColorSpace.TYPE_GRAY: return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO; case ColorSpace.TYPE_RGB: - return TIFFBaseline.PHOTOMETRIC_RGB; + return compression == TIFFExtension.COMPRESSION_JPEG ? TIFFExtension.PHOTOMETRIC_YCBCR : TIFFBaseline.PHOTOMETRIC_RGB; case ColorSpace.TYPE_CMYK: return TIFFExtension.PHOTOMETRIC_SEPARATED; } @@ -862,8 +862,17 @@ public final class TIFFImageWriter extends ImageWriterBase { } } - // TODO: Set values from imageType - entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel()))); + int compression; + if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA) + && ifd != null && ifd.getEntryById(TIFF.TAG_COMPRESSION) != null) { + compression = ((Number) ifd.getEntryById(TIFF.TAG_COMPRESSION).getValue()).intValue(); + } + else { + compression = TIFFImageWriteParam.getCompressionType(param); + } + + int photometricInterpretation = getPhotometricInterpretation(imageType.getColorModel(), compression); + entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, photometricInterpretation)); // TODO: Set values from param if != null + combined values... diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java index 84b5da75..6a313950 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java @@ -55,6 +55,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.nio.ByteOrder; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -921,4 +922,111 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase { byte[] bytes = output.toByteArray(); assertArrayEquals(new byte[] {'I', 'I', 42, 0}, Arrays.copyOf(bytes, 4)); } + + @Test + public void testRewrite() throws IOException { + ImageWriter writer = createImageWriter(); + ImageReader reader = ImageIO.getImageReader(writer); + + List testData = Arrays.asList( + getClassLoaderResource("/tiff/pixtiff/17-tiff-binary-ccitt-group3.tif"), + getClassLoaderResource("/tiff/pixtiff/36-tiff-8-bit-gray-jpeg.tif"), + getClassLoaderResource("/tiff/pixtiff/51-tiff-24-bit-color-jpeg.tif"), + getClassLoaderResource("/tiff/pixtiff/58-plexustiff-binary-ccitt-group4.tif"), + getClassLoaderResource("/tiff/balloons.tif"), + getClassLoaderResource("/tiff/ColorCheckerCalculator.tif"), + getClassLoaderResource("/tiff/quad-jpeg.tif"), + getClassLoaderResource("/tiff/quad-lzw.tif"), + getClassLoaderResource("/tiff/old-style-jpeg-inconsistent-metadata.tif"), + getClassLoaderResource("/tiff/ccitt/group3_1d.tif"), + getClassLoaderResource("/tiff/ccitt/group3_2d.tif"), + getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"), + getClassLoaderResource("/tiff/ccitt/group3_2d_fill.tif"), + getClassLoaderResource("/tiff/ccitt/group4.tif") + ); + + for (URL url : testData) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + try (ImageInputStream input = ImageIO.createImageInputStream(url); + ImageOutputStream stream = ImageIO.createImageOutputStream(output)) { + reader.setInput(input); + writer.setOutput(stream); + + List infos = new ArrayList<>(20); + + writer.prepareWriteSequence(null); + + for (int i = 0; i < reader.getNumImages(true); i++) { + IIOImage image = reader.readAll(i, null); + + // If compression is Old JPEG, rewrite as JPEG + // Normally, use the getAsTree method, but we don't care here if we are tied to our impl + TIFFImageMetadata metadata = (TIFFImageMetadata) image.getMetadata(); + Directory ifd = metadata.getIFD(); + Entry compressionEntry = ifd.getEntryById(TIFF.TAG_COMPRESSION); + + int compression = compressionEntry != null ? ((Number) compressionEntry.getValue()).intValue() : TIFFBaseline.COMPRESSION_NONE; + + infos.add(new ImageInfo(image.getRenderedImage().getWidth(), image.getRenderedImage().getHeight(), compression)); + + ImageWriteParam param = writer.getDefaultWriteParam(); + + if (compression == TIFFExtension.COMPRESSION_OLD_JPEG) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Override the copy from metadata + param.setCompressionType("JPEG"); + } + + writer.writeToSequence(image, param); + } + + writer.endWriteSequence(); + +// File tempFile = File.createTempFile("foo-", ".tif"); +// System.err.println("open " + tempFile.getAbsolutePath()); +// FileUtil.write(tempFile, output.toByteArray()); + + try (ImageInputStream inputAfter = new ByteArrayImageInputStream(output.toByteArray())) { + reader.setInput(inputAfter); + + int numImages = reader.getNumImages(true); + + assertEquals("Number of pages differs from original", infos.size(), numImages); + + for (int i = 0; i < numImages; i++) { + IIOImage after = reader.readAll(i, null); + ImageInfo info = infos.get(i); + + TIFFImageMetadata afterMetadata = (TIFFImageMetadata) after.getMetadata(); + Directory afterIfd = afterMetadata.getIFD(); + Entry afterCompressionEntry = afterIfd.getEntryById(TIFF.TAG_COMPRESSION); + + if (info.compression == TIFFExtension.COMPRESSION_OLD_JPEG) { + // Should rewrite this from old-style to new style + assertEquals("Old JPEG compression not rewritten as JPEG", TIFFExtension.COMPRESSION_JPEG, afterCompressionEntry.getValue()); + } + else { + assertEquals("Compression differs from original", info.compression, ((Number) afterCompressionEntry.getValue()).intValue()); + } + + assertEquals("Image width differs from original", info.width, after.getRenderedImage().getWidth()); + assertEquals("Image height differs from original", info.height, after.getRenderedImage().getHeight()); + } + } + } + } + } + + private class ImageInfo { + final int width; + final int height; + + final int compression; + + private ImageInfo(int width, int height, int compression) { + this.width = width; + this.height = height; + this.compression = compression; + } + } } diff --git a/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/17-tiff-binary-ccitt-group3.tif b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/17-tiff-binary-ccitt-group3.tif new file mode 100644 index 00000000..83c0e4a1 Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/17-tiff-binary-ccitt-group3.tif differ diff --git a/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/36-tiff-8-bit-gray-jpeg.tif b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/36-tiff-8-bit-gray-jpeg.tif new file mode 100644 index 00000000..715cdb3a Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/36-tiff-8-bit-gray-jpeg.tif differ diff --git a/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/51-tiff-24-bit-color-jpeg.tif b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/51-tiff-24-bit-color-jpeg.tif new file mode 100644 index 00000000..66f7112b Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/51-tiff-24-bit-color-jpeg.tif differ diff --git a/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/58-plexustiff-binary-ccitt-group4.tif b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/58-plexustiff-binary-ccitt-group4.tif new file mode 100644 index 00000000..336dc2df Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/pixtiff/58-plexustiff-binary-ccitt-group4.tif differ