From b55c623b875556efb4d61bde9d136c0303001285 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Tue, 7 Sep 2021 09:29:13 +0200 Subject: [PATCH] #623: TGAImageReader, PCXImageReader and SGIImageReader now return more standard image types as default, for better AffineTransformOp compatibility. Added tests. Bonus: Subsampling fix for TGAImageReader and BMPImageReader. (cherry picked from commit 812e12acb0cdcf68574f10b384ef8d6240ec3e27) --- .../imageio/plugins/bmp/BMPImageReader.java | 79 ++++----- .../imageio/util/ImageReaderAbstractTest.java | 43 ++++- .../plugins/hdr/HDRImageReaderTest.java | 12 +- .../imageio/plugins/pcx/PCXImageReader.java | 15 +- .../plugins/pcx/PCXImageReaderTest.java | 2 +- .../plugins/pnm/PNMImageReaderTest.java | 14 ++ .../imageio/plugins/psd/PSDImageReader.java | 2 +- .../plugins/psd/PSDImageReaderTest.java | 43 ++++- .../imageio/plugins/sgi/SGIImageReader.java | 28 +++- .../imageio/plugins/tga/TGAImageReader.java | 151 ++++++++++++------ .../plugins/tiff/TIFFImageReaderTest.java | 48 +++++- 11 files changed, 325 insertions(+), 112 deletions(-) diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java index 290a07be..7e3f9e85 100755 --- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java +++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReader.java @@ -30,6 +30,22 @@ package com.twelvemonkeys.imageio.plugins.bmp; +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; +import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import com.twelvemonkeys.imageio.util.ProgressListenerBase; +import com.twelvemonkeys.io.LittleEndianDataInputStream; +import com.twelvemonkeys.io.enc.DecoderStream; +import com.twelvemonkeys.xml.XMLSerializer; + +import javax.imageio.*; +import javax.imageio.event.IIOReadUpdateListener; +import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.*; @@ -40,27 +56,6 @@ import java.nio.ByteOrder; import java.util.Collections; import java.util.Iterator; -import javax.imageio.IIOException; -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.event.IIOReadUpdateListener; -import javax.imageio.event.IIOReadWarningListener; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; - -import com.twelvemonkeys.imageio.ImageReaderBase; -import com.twelvemonkeys.imageio.stream.SubImageInputStream; -import com.twelvemonkeys.imageio.util.IIOUtil; -import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; -import com.twelvemonkeys.imageio.util.ProgressListenerBase; -import com.twelvemonkeys.io.LittleEndianDataInputStream; -import com.twelvemonkeys.io.enc.DecoderStream; -import com.twelvemonkeys.xml.XMLSerializer; - /** * ImageReader for Microsoft Windows Bitmap (BMP) format. * @@ -482,8 +477,12 @@ public final class BMPImageReader extends ImageReaderBase { private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub, final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException { + // Flip into position? + int srcY = !header.topDown ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + // If subsampled or outside source region, skip entire row - if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) { + if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { input.skipBytes(rowDataByte.length); return; @@ -498,19 +497,17 @@ public final class BMPImageReader extends ImageReaderBase { } } - if (header.topDown) { - destChannel.setDataElements(0, y, srcChannel); - } else { - // Flip into position - int dstY = (height - 1 - y - srcRegion.y) / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - } + destChannel.setDataElements(0, dstY, srcChannel); } private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub, final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException { + // Flip into position? + int srcY = !header.topDown ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + // If subsampled or outside source region, skip entire row - if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) { + if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2); return; @@ -530,19 +527,17 @@ public final class BMPImageReader extends ImageReaderBase { } } - if (header.topDown) { - destChannel.setDataElements(0, y, srcChannel); - } else { - // Flip into position - int dstY = (height - 1 - y - srcRegion.y) / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - } + destChannel.setDataElements(0, dstY, srcChannel); } private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub, final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException { + // Flip into position? + int srcY = !header.topDown ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + // If subsampled or outside source region, skip entire row - if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) { + if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { input.skipBytes(rowDataInt.length * 4); return; @@ -557,13 +552,7 @@ public final class BMPImageReader extends ImageReaderBase { } } - if (header.topDown) { - destChannel.setDataElements(0, y, srcChannel); - } else { - // Flip into position - int dstY = (height - 1 - y - srcRegion.y) / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - } + destChannel.setDataElements(0, dstY, srcChannel); } // TODO: Candidate util method diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java index 78de748e..ea383dfb 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java @@ -45,9 +45,8 @@ import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; -import java.awt.image.SampleModel; +import java.awt.geom.AffineTransform; +import java.awt.image.*; import java.io.File; import java.io.IOException; import java.lang.reflect.ParameterizedType; @@ -57,6 +56,7 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import static java.lang.Math.min; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -1738,6 +1738,43 @@ public abstract class ImageReaderAbstractTest { reader.dispose(); } + protected List getTestDataForAffineTransformOpCompatibility() { + // Allow subclasses to filter out test data that can't be converted to a compatible image without data loss + return getTestData(); + } + + @Test + public void testAffineTransformOpCompatibility() throws IOException { + // Test that the output of normal images are compatible with AffineTransformOp. Is unlikely to work on all test data + ImageReader reader = createReader(); + + for (TestData testData : getTestDataForAffineTransformOpCompatibility()) { + try (ImageInputStream input = testData.getInputStream()) { + reader.setInput(input); + + ImageReadParam param = reader.getDefaultReadParam(); + param.setSourceRegion(new Rectangle(min(reader.getWidth(0), 64), min(reader.getHeight(0), 64))); + + BufferedImage originalImage = reader.read(0, param); + + AffineTransform transform = AffineTransform.getTranslateInstance(10, 10); + AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + + try { + BufferedImage resultImage = op.filter(originalImage, null); // The exception happens here + assertNotNull(resultImage); + } + catch (ImagingOpException e) { + fail(e.getMessage() + ".\n\t" + + originalImage + "\n\t" + + testData); + } + } + } + + reader.dispose(); + } + @Ignore("TODO: Implement") @Test public void testSetDestinationBands() { diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java index dbe686f7..5c2be286 100755 --- a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java +++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java @@ -38,12 +38,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import static java.util.Collections.emptyList; + /** - * TGAImageReaderTest + * HDRImageReaderTest * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$ + * @version $Id: HDRImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$ */ public class HDRImageReaderTest extends ImageReaderAbstractTest { @Override @@ -58,6 +60,12 @@ public class HDRImageReaderTest extends ImageReaderAbstractTest ); } + @Override + protected List getTestDataForAffineTransformOpCompatibility() { + // HDR images uses floating point buffers... + return emptyList(); + } + @Override protected List getFormatNames() { return Arrays.asList("HDR", "hdr", "RGBE", "rgbe"); diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java index 1befd3f1..ca321f83 100755 --- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java +++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java @@ -104,8 +104,15 @@ public final class PCXImageReader extends ImageReaderBase { ImageTypeSpecifier rawType = getRawImageType(imageIndex); List specifiers = new ArrayList<>(); - - // TODO: Implement + if (rawType.getSampleModel() instanceof BandedSampleModel) { + if (rawType.getNumBands() == 3) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); + } + else if (rawType.getNumBands() == 4) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)); + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)); + } + } specifiers.add(rawType); return specifiers.iterator(); @@ -142,10 +149,10 @@ public final class PCXImageReader extends ImageReaderBase { // PCX RGB has channels for 24 bit RGB, will be validated by ImageTypeSpecifier return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false); case 24: - // Some sources says this is possible... + // Some sources say this is possible... return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); case 32: - // Some sources says this is possible... + // Some sources say this is possible... return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); default: throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel()); diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java index 0a99f961..769ee15a 100755 --- a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java +++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java @@ -78,7 +78,7 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel) new TestData(getClassLoaderResource("/pcx/MARBLES.PCX"), new Dimension(1419, 1001)), // RLE encoded RGB new TestData(getClassLoaderResource("/pcx/no-palette-monochrome.pcx"), new Dimension(128, 152)), // RLE encoded monochrome (1 bps/1 channel) - // See cga-pcx.txt, however, the text seems to be in error, the bits can not not as described + // See cga-pcx.txt, however, the text seems to be in error, I don't see how the bits can be as described new TestData(getClassLoaderResource("/pcx/CGA_BW.PCX"), new Dimension(640, 200)), // RLE encoded indexed (CGA mode) new TestData(getClassLoaderResource("/pcx/CGA_FSD.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode) new TestData(getClassLoaderResource("/pcx/CGA_RGBI.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode) diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java index 967285ec..de104f78 100755 --- a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java +++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java @@ -61,6 +61,20 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest ); } + @Override + protected List getTestDataForAffineTransformOpCompatibility() { + return Arrays.asList( + new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW) + new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)), // P3 (PPM PLAIN) + new TestData(getClassLoaderResource("/pbm/j.pbm"), new Dimension(6, 10)), // P1 (PBM PLAIN) + new TestData(getClassLoaderResource("/pgm/feep.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN) + new TestData(getClassLoaderResource("/pgm/feep16.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN, 16 bits/sample) + new TestData(getClassLoaderResource("/pgm/house.l.pgm"), new Dimension(367, 241)), // P5 (PGM RAW) + new TestData(getClassLoaderResource("/ppm/lighthouse_rgb48.ppm"), new Dimension(768, 512)) // P6 (PPM RAW, 16 bits/sample) + // "/pfm/memorial.pfm" uses floating point + ); + } + @Override protected List getFormatNames() { return Arrays.asList( diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 5bf608e0..61c22027 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -331,7 +331,7 @@ public final class PSDImageReader extends ImageReaderBase { // Just stick to the raw type } - // Finally add the raw type + // Finally, add the raw type types.add(rawType); return types.iterator(); diff --git a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java index 879efdbd..c21ca4e4 100755 --- a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java +++ b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java @@ -86,8 +86,6 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)), // 4 channel, CMYK, 16 bit samples new TestData(getClassLoaderResource("/psd/cmyk_16bits.psd"), new Dimension(1000, 275)), - // 3 channel, RGB, 32 bit samples - new TestData(getClassLoaderResource("/psd/32bit5x5.psd"), new Dimension(5, 5)), // 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB) new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)), // From http://telegraphics.com.au/svn/psdparse/trunk/psd/ @@ -104,11 +102,48 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)), new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)), // CMYK, uncompressed + contains some uncommon MeSa (instead of 8BIM) resource blocks - new TestData(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"), new Dimension(400, 191)) + new TestData(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"), new Dimension(400, 191)), + // 3 channel, RGB, 32 bit samples + new TestData(getClassLoaderResource("/psd/32bit5x5.psd"), new Dimension(5, 5)) // TODO: Need more recent ZIP compressed PSD files from CS2/CS3+ ); } + @Override + protected List getTestDataForAffineTransformOpCompatibility() { + return Arrays.asList( + // 5 channel, RGB + new TestData(getClassLoaderResource("/psd/photoshopping.psd"), new Dimension(300, 225)), + // 1 channel, gray, 8 bit samples + new TestData(getClassLoaderResource("/psd/buttons.psd"), new Dimension(20, 20)), + // 3 channel RGB, "no composite layer" + new TestData(getClassLoaderResource("/psd/jugware-icon.psd"), new Dimension(128, 128)), + // 3 channel RGB, old data, no layer info/mask + new TestData(getClassLoaderResource("/psd/MARBLES.PSD"), new Dimension(1419, 1001)), + // 1 channel, indexed color + new TestData(getClassLoaderResource("/psd/coral_fish.psd"), new Dimension(800, 800)), + // 1 channel, bitmap, 1 bit samples + new TestData(getClassLoaderResource("/psd/test_bitmap.psd"), new Dimension(710, 512)), + // 1 channel, gray, 16 bit samples + new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)), + // 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB) + new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)), + // From http://telegraphics.com.au/svn/psdparse/trunk/psd/ + new TestData(getClassLoaderResource("/psd/adobehq.psd"), new Dimension(341, 512)), + new TestData(getClassLoaderResource("/psd/adobehq_ind.psd"), new Dimension(341, 512)), + // Contains a shorter than normal PrintFlags chunk + new TestData(getClassLoaderResource("/psd/adobehq-2.5.psd"), new Dimension(341, 512)), + new TestData(getClassLoaderResource("/psd/adobehq-3.0.psd"), new Dimension(341, 512)), + new TestData(getClassLoaderResource("/psd/adobehq-5.5.psd"), new Dimension(341, 512)), + new TestData(getClassLoaderResource("/psd/adobehq-7.0.psd"), new Dimension(341, 512)), + // From https://github.com/kmike/psd-tools/tree/master/tests/psd_files + new TestData(getClassLoaderResource("/psd/masks2.psd"), new Dimension(640, 1136)), + // RGB, multiple alpha channels, no transparency + new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)), + new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)) + ); + } + @Override protected List getFormatNames() { return Collections.singletonList("psd"); @@ -472,7 +507,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest public void testMultiChannelNoTransparencyPSB() throws IOException { PSDImageReader imageReader = createReader(); - // The following PSB is RGB, has 4 channels (1 alpha/auxillary channel), but should be treated as opaque + // The following PSB is RGB, has 4 channels (1 alpha/auxiliary channel), but should be treated as opaque try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"))) { imageReader.setInput(stream); diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java index 738666e8..b173e910 100755 --- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java +++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java @@ -59,7 +59,7 @@ public final class SGIImageReader extends ImageReaderBase { private SGIHeader header; - protected SGIImageReader(final ImageReaderSpi provider) { + SGIImageReader(final ImageReaderSpi provider) { super(provider); } @@ -88,9 +88,31 @@ public final class SGIImageReader extends ImageReaderBase { public Iterator getImageTypes(final int imageIndex) throws IOException { ImageTypeSpecifier rawType = getRawImageType(imageIndex); - List specifiers = new ArrayList(); + List specifiers = new ArrayList<>(); + + int channels = header.getChannels(); + + switch (header.getBytesPerPixel()) { + case 1: + if (channels == 1) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)); + } + else if (channels == 3) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); + } + else if (channels == 4) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)); + } + + break; + case 2: + if (channels == 1) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY)); + } + + break; + } - // TODO: Implement specifiers.add(rawType); return specifiers.iterator(); diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java index 596c99eb..5ccdefad 100755 --- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java +++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java @@ -96,10 +96,18 @@ final class TGAImageReader extends ImageReaderBase { ImageTypeSpecifier rawType = getRawImageType(imageIndex); List specifiers = new ArrayList<>(); - - // TODO: Implement specifiers.add(rawType); + if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_RGB) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)); + } + else if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_ARGB) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)); + } + else if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_ARGB_PRE) { + specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)); + } + return specifiers.iterator(); } @@ -121,7 +129,8 @@ final class TGAImageReader extends ImageReaderBase { return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); case 16: return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY); - default: throw new IIOException("Unknown pixel depth for monochrome: " + header.getPixelDepth()); + default: + throw new IIOException("Unknown pixel depth for monochrome: " + header.getPixelDepth()); } case TGA.IMAGETYPE_TRUECOLOR: case TGA.IMAGETYPE_TRUECOLOR_RLE: @@ -141,12 +150,14 @@ final class TGAImageReader extends ImageReaderBase { case 24: return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); case 32: - // 4BYTE_BGRX... - // Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead, - // if hasAlpha is false - return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied); + // NOTE: We'll read using little endian byte order, thus the file layout is BGRA/BGRx + if (hasAlpha) { + return ImageTypeSpecifier.createFromBufferedImageType(isAlphaPremultiplied ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB); + } + + return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); default: - throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth()); + throw new IIOException("Unknown pixel depth for true color: " + header.getPixelDepth()); } default: throw new IIOException("Unknown image type: " + header.getImageType()); @@ -193,20 +204,26 @@ final class TGAImageReader extends ImageReaderBase { input = imageInput; } + int pixelDepth = header.getPixelDepth(); + boolean flipped = isOriginLowerLeft(header.getOrigin()); + for (int y = 0; y < height; y++) { - switch (header.getPixelDepth()) { + switch (pixelDepth) { case 8: case 24: - case 32: byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y); + readRowByte(input, height, srcRegion, flipped, xSub, ySub, rowDataByte, destRaster, clippedRow, y); break; case 16: short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); - readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y); + readRowUShort(input, height, srcRegion, flipped, xSub, ySub, rowDataUShort, destRaster, clippedRow, y); + break; + case 32: + int[] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); + readRowInt(input, height, srcRegion, flipped, xSub, ySub, rowDataInt, destRaster, clippedRow, y); break; default: - throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth()); + throw new AssertionError("Unsupported pixel depth: " + pixelDepth); } processImageProgress(100f * y / height); @@ -226,10 +243,26 @@ final class TGAImageReader extends ImageReaderBase { return destination; } - private void readRowByte(final DataInput input, int height, Rectangle srcRegion, int origin, int xSub, int ySub, + private boolean isOriginLowerLeft(final int origin) throws IIOException { + switch (origin) { + case TGA.ORIGIN_LOWER_LEFT: + return true; + case TGA.ORIGIN_UPPER_LEFT: + return false; + default: + // Other orientations are not supported + throw new IIOException("Unsupported origin: " + origin); + } + } + + private void readRowByte(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub, byte[] rowDataByte, WritableRaster destChannel, Raster srcChannel, int y) throws IOException { + // Flip into position? + int srcY = flip ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + // If subsampled or outside source region, skip entire row - if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) { + if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { input.skipBytes(rowDataByte.length); return; @@ -250,19 +283,7 @@ final class TGAImageReader extends ImageReaderBase { } } - switch (origin) { - case TGA.ORIGIN_LOWER_LEFT: - // Flip into position - int dstY = (height - 1 - y - srcRegion.y) / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - break; - case TGA.ORIGIN_UPPER_LEFT: - dstY = y / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - break; - default: - throw new IIOException("Unsupported origin: " + origin); - } + destChannel.setDataElements(0, dstY, srcChannel); } private void removeAlpha32(final byte[] rowData) { @@ -271,10 +292,14 @@ final class TGAImageReader extends ImageReaderBase { } } - private void readRowUShort(final DataInput input, int height, Rectangle srcRegion, int origin, int xSub, int ySub, + private void readRowUShort(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub, short[] rowDataUShort, WritableRaster destChannel, Raster srcChannel, int y) throws IOException { + // Flip into position? + int srcY = flip ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + // If subsampled or outside source region, skip entire row - if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) { + if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { input.skipBytes(rowDataUShort.length * 2); return; @@ -289,19 +314,32 @@ final class TGAImageReader extends ImageReaderBase { } } - switch (origin) { - case TGA.ORIGIN_LOWER_LEFT: - // Flip into position - int dstY = (height - 1 - y - srcRegion.y) / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - break; - case TGA.ORIGIN_UPPER_LEFT: - dstY = y / ySub; - destChannel.setDataElements(0, dstY, srcChannel); - break; - default: - throw new IIOException("Unsupported origin: " + origin); + destChannel.setDataElements(0, dstY, srcChannel); + } + + private void readRowInt(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub, + int[] rowDataInt, WritableRaster destChannel, Raster srcChannel, int y) throws IOException { + // Flip into position? + int srcY = flip ? height - 1 - y : y; + int dstY = (srcY - srcRegion.y) / ySub; + + // If subsampled or outside source region, skip entire row + if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) { + input.skipBytes(rowDataInt.length * 4); + + return; } + + readFully(input, rowDataInt); + + // Subsample horizontal + if (xSub != 1) { + for (int x = srcRegion.x / xSub; x < ((srcRegion.x + srcRegion.width) / xSub); x++) { + rowDataInt[x] = rowDataInt[x * xSub]; + } + } + + destChannel.setDataElements(0, dstY, srcChannel); } // TODO: Candidate util method @@ -317,6 +355,19 @@ final class TGAImageReader extends ImageReaderBase { } } + // TODO: Candidate util method + private static void readFully(final DataInput input, final int[] ints) throws IOException { + if (input instanceof ImageInputStream) { + // Optimization for ImageInputStreams, read all in one go + ((ImageInputStream) input).readFully(ints, 0, ints.length); + } + else { + for (int i = 0; i < ints.length; i++) { + ints[i] = input.readInt(); + } + } + } + private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) { if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1) && xSub == 1 @@ -452,20 +503,26 @@ final class TGAImageReader extends ImageReaderBase { // Thumbnail is always stored non-compressed, no need for RLE support imageInput.seek(extensions.getThumbnailOffset() + 2); + int pixelDepth = header.getPixelDepth(); + boolean flipped = isOriginLowerLeft(header.getOrigin()); + for (int y = 0; y < height; y++) { - switch (header.getPixelDepth()) { + switch (pixelDepth) { case 8: case 24: - case 32: byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y); + readRowByte(imageInput, height, srcRegion, flipped, 1, 1, rowDataByte, destRaster, rowRaster, y); break; case 16: short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); - readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y); + readRowUShort(imageInput, height, srcRegion, flipped, 1, 1, rowDataUShort, destRaster, rowRaster, y); + break; + case 32: + int[] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); + readRowInt(imageInput, height, srcRegion, flipped, 1, 1, rowDataInt, destRaster, rowRaster, y); break; default: - throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth()); + throw new AssertionError("Unsupported pixel depth: " + pixelDepth); } processThumbnailProgress(100f * y / height); diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 988b6296..fad5e6c4 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -97,7 +97,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest getTestDataForAffineTransformOpCompatibility() { + return Arrays.asList( + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-02.tif"), new Dimension(73, 43)), // Gray 2 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-04.tif"), new Dimension(73, 43)), // Gray 4 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-06.tif"), new Dimension(73, 43)), // Gray 6 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-08.tif"), new Dimension(73, 43)), // Gray 8 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-10.tif"), new Dimension(73, 43)), // Gray 10 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-12.tif"), new Dimension(73, 43)), // Gray 12 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-14.tif"), new Dimension(73, 43)), // Gray 14 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-16.tif"), new Dimension(73, 43)), // Gray 16 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-24.tif"), new Dimension(73, 43)), // Gray 24 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-32.tif"), new Dimension(73, 43)), // Gray 32 bit/sample + // Palette + new TestData(getClassLoaderResource("/tiff/depth/flower-palette-02.tif"), new Dimension(73, 43)), // Palette 2 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-palette-04.tif"), new Dimension(73, 43)), // Palette 4 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample + // RGB Interleaved (PlanarConfiguration: 1) + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-14.tif"), new Dimension(73, 43)), // RGB 14 bit/sample + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-16.tif"), new Dimension(73, 43)), // RGB 16 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-24.tif"), new Dimension(73, 43)), // RGB 24 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-32.tif"), new Dimension(73, 43)), // RGB 32 bit/sample + // RGB Planar (PlanarConfiguration: 2) + new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-08.tif"), new Dimension(73, 43)) // RGB 8 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-14.tif"), new Dimension(73, 43)), // RGB 14 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-16.tif"), new Dimension(73, 43)), // RGB 16 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-24.tif"), new Dimension(73, 43)), // RGB 24 bit/sample +// // RGB Interleaved Floating point..!? We can read this one, but the samples are not normalized, so colors are way too bright... +// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-32.tif"), new Dimension(73, 43)), // RGB 32 bit FP samples (!) +// // Separated (CMYK) Interleaved (PlanarConfiguration: 1) +// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-contig-08.tif"), new Dimension(73, 43)), // CMYK 8 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-contig-16.tif"), new Dimension(73, 43)), // CMYK 16 bit/sample +// // Separated (CMYK) Planar (PlanarConfiguration: 2) +// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-08.tif"), new Dimension(73, 43)), // CMYK 8 bit/sample +// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-16.tif"), new Dimension(73, 43)) // CMYK 16 bit/sample + ); + } + private List getUnsupportedTestData() { return Arrays.asList( // RGB Interleaved (PlanarConfiguration: 1)