Skip to content

Commit

Permalink
#312, #313, #314 Fixed ClassCastException when rewriting compressed T…
Browse files Browse the repository at this point in the history
…IFF images.

(cherry picked from commit 6ea1ea8)
  • Loading branch information
haraldk committed Feb 2, 2017
1 parent b0108fe commit aa3e2cc
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 9 deletions.
Expand Up @@ -264,7 +264,7 @@ else if (sampleModel instanceof MultiPixelPackedSampleModel) {
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);
Expand Down Expand Up @@ -309,8 +309,7 @@ else if (sampleModel instanceof MultiPixelPackedSampleModel) {
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) {
Expand Down Expand Up @@ -529,7 +528,7 @@ Deflate, BEST_COMPRESSION (9):

// 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) {
Expand Down Expand Up @@ -585,7 +584,8 @@ Deflate, BEST_COMPRESSION (9):
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);
Expand All @@ -599,7 +599,7 @@ Deflate, BEST_COMPRESSION (9):
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) {
Expand All @@ -621,7 +621,7 @@ else if (colorModel instanceof IndexColorModel) {
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;
}
Expand Down Expand Up @@ -928,8 +928,17 @@ private TIFFImageMetadata initMeta(final Directory ifd, final ImageTypeSpecifier
}
}

// 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...

Expand Down
Expand Up @@ -54,6 +54,8 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

Expand Down Expand Up @@ -856,4 +858,143 @@ private void assertImageEquals(final String message, final BufferedImage expecte
}
}
}

@Test
public void testWriteStreamMetadataDefaultMM() throws IOException {
ImageWriter writer = createImageWriter();

ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN); // Should pass through
writer.setOutput(stream);

writer.write(null, new IIOImage(getTestData(0), null, null), null);
}

byte[] bytes = output.toByteArray();
assertArrayEquals(new byte[] {'M', 'M', 0, 42}, Arrays.copyOf(bytes, 4));
}

@Test
public void testWriteStreamMetadataDefaultII() throws IOException {
ImageWriter writer = createImageWriter();

ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // Should pass through
writer.setOutput(stream);

writer.write(null, new IIOImage(getTestData(0), null, null), null);
}

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<URL> 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<ImageInfo> 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;
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit aa3e2cc

Please sign in to comment.