#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)
This commit is contained in:
Harald Kuhr 2021-09-07 09:29:13 +02:00
parent 09573b52ac
commit b55c623b87
11 changed files with 325 additions and 112 deletions

View File

@ -30,6 +30,22 @@
package com.twelvemonkeys.imageio.plugins.bmp; 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.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.*;
@ -40,27 +56,6 @@ import java.nio.ByteOrder;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; 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. * 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, 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 { 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 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); input.skipBytes(rowDataByte.length);
return; return;
@ -498,19 +497,17 @@ public final class BMPImageReader extends ImageReaderBase {
} }
} }
if (header.topDown) { destChannel.setDataElements(0, dstY, srcChannel);
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
} }
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub, 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 { 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 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); input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
return; return;
@ -530,19 +527,17 @@ public final class BMPImageReader extends ImageReaderBase {
} }
} }
if (header.topDown) { destChannel.setDataElements(0, dstY, srcChannel);
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
} }
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub, 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 { 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 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); input.skipBytes(rowDataInt.length * 4);
return; return;
@ -557,13 +552,7 @@ public final class BMPImageReader extends ImageReaderBase {
} }
} }
if (header.topDown) { destChannel.setDataElements(0, dstY, srcChannel);
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
} }
// TODO: Candidate util method // TODO: Candidate util method

View File

@ -45,9 +45,8 @@ import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage; import java.awt.image.*;
import java.awt.image.SampleModel;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
@ -57,6 +56,7 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static java.lang.Math.min;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -1738,6 +1738,43 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
reader.dispose(); reader.dispose();
} }
protected List<TestData> 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") @Ignore("TODO: Implement")
@Test @Test
public void testSetDestinationBands() { public void testSetDestinationBands() {

View File

@ -38,12 +38,14 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static java.util.Collections.emptyList;
/** /**
* TGAImageReaderTest * HDRImageReaderTest
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$ * @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<HDRImageReader> { public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
@Override @Override
@ -58,6 +60,12 @@ public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader>
); );
} }
@Override
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
// HDR images uses floating point buffers...
return emptyList();
}
@Override @Override
protected List<String> getFormatNames() { protected List<String> getFormatNames() {
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe"); return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");

View File

@ -104,8 +104,15 @@ public final class PCXImageReader extends ImageReaderBase {
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<>(); List<ImageTypeSpecifier> specifiers = new ArrayList<>();
if (rawType.getSampleModel() instanceof BandedSampleModel) {
// TODO: Implement 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); specifiers.add(rawType);
return specifiers.iterator(); 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 // 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); return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false);
case 24: case 24:
// Some sources says this is possible... // Some sources say this is possible...
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
case 32: case 32:
// Some sources says this is possible... // Some sources say this is possible...
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
default: default:
throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel()); throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel());

View File

@ -78,7 +78,7 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest<PCXImageReader>
new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel) 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/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) 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_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_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) new TestData(getClassLoaderResource("/pcx/CGA_RGBI.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)

View File

@ -61,6 +61,20 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
); );
} }
@Override
protected List<TestData> 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 @Override
protected List<String> getFormatNames() { protected List<String> getFormatNames() {
return Arrays.asList( return Arrays.asList(

View File

@ -331,7 +331,7 @@ public final class PSDImageReader extends ImageReaderBase {
// Just stick to the raw type // Just stick to the raw type
} }
// Finally add the raw type // Finally, add the raw type
types.add(rawType); types.add(rawType);
return types.iterator(); return types.iterator();

View File

@ -86,8 +86,6 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)), new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)),
// 4 channel, CMYK, 16 bit samples // 4 channel, CMYK, 16 bit samples
new TestData(getClassLoaderResource("/psd/cmyk_16bits.psd"), new Dimension(1000, 275)), 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) // 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB)
new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)), new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)),
// From http://telegraphics.com.au/svn/psdparse/trunk/psd/ // From http://telegraphics.com.au/svn/psdparse/trunk/psd/
@ -104,11 +102,48 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)), 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)), new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)),
// CMYK, uncompressed + contains some uncommon MeSa (instead of 8BIM) resource blocks // 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+ // TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
); );
} }
@Override
protected List<TestData> 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 @Override
protected List<String> getFormatNames() { protected List<String> getFormatNames() {
return Collections.singletonList("psd"); return Collections.singletonList("psd");
@ -472,7 +507,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
public void testMultiChannelNoTransparencyPSB() throws IOException { public void testMultiChannelNoTransparencyPSB() throws IOException {
PSDImageReader imageReader = createReader(); 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"))) { try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"))) {
imageReader.setInput(stream); imageReader.setInput(stream);

View File

@ -59,7 +59,7 @@ public final class SGIImageReader extends ImageReaderBase {
private SGIHeader header; private SGIHeader header;
protected SGIImageReader(final ImageReaderSpi provider) { SGIImageReader(final ImageReaderSpi provider) {
super(provider); super(provider);
} }
@ -88,9 +88,31 @@ public final class SGIImageReader extends ImageReaderBase {
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException { public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>(); List<ImageTypeSpecifier> 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); specifiers.add(rawType);
return specifiers.iterator(); return specifiers.iterator();

View File

@ -96,10 +96,18 @@ final class TGAImageReader extends ImageReaderBase {
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<>(); List<ImageTypeSpecifier> specifiers = new ArrayList<>();
// TODO: Implement
specifiers.add(rawType); 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(); return specifiers.iterator();
} }
@ -121,7 +129,8 @@ final class TGAImageReader extends ImageReaderBase {
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
case 16: case 16:
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY); 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:
case TGA.IMAGETYPE_TRUECOLOR_RLE: case TGA.IMAGETYPE_TRUECOLOR_RLE:
@ -141,12 +150,14 @@ final class TGAImageReader extends ImageReaderBase {
case 24: case 24:
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
case 32: case 32:
// 4BYTE_BGRX... // NOTE: We'll read using little endian byte order, thus the file layout is BGRA/BGRx
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead, if (hasAlpha) {
// if hasAlpha is false return ImageTypeSpecifier.createFromBufferedImageType(isAlphaPremultiplied ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB);
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied); }
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
default: default:
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth()); throw new IIOException("Unknown pixel depth for true color: " + header.getPixelDepth());
} }
default: default:
throw new IIOException("Unknown image type: " + header.getImageType()); throw new IIOException("Unknown image type: " + header.getImageType());
@ -193,20 +204,26 @@ final class TGAImageReader extends ImageReaderBase {
input = imageInput; input = imageInput;
} }
int pixelDepth = header.getPixelDepth();
boolean flipped = isOriginLowerLeft(header.getOrigin());
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
switch (header.getPixelDepth()) { switch (pixelDepth) {
case 8: case 8:
case 24: case 24:
case 32:
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); 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; break;
case 16: case 16:
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); 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; break;
default: default:
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth()); throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
} }
processImageProgress(100f * y / height); processImageProgress(100f * y / height);
@ -226,10 +243,26 @@ final class TGAImageReader extends ImageReaderBase {
return destination; 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 { 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 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); input.skipBytes(rowDataByte.length);
return; return;
@ -250,19 +283,7 @@ final class TGAImageReader extends ImageReaderBase {
} }
} }
switch (origin) { destChannel.setDataElements(0, dstY, srcChannel);
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);
}
} }
private void removeAlpha32(final byte[] rowData) { 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 { 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 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); input.skipBytes(rowDataUShort.length * 2);
return; return;
@ -289,19 +314,32 @@ final class TGAImageReader extends ImageReaderBase {
} }
} }
switch (origin) { destChannel.setDataElements(0, dstY, srcChannel);
case TGA.ORIGIN_LOWER_LEFT: }
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub; private void readRowInt(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub,
destChannel.setDataElements(0, dstY, srcChannel); int[] rowDataInt, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
break; // Flip into position?
case TGA.ORIGIN_UPPER_LEFT: int srcY = flip ? height - 1 - y : y;
dstY = y / ySub; int dstY = (srcY - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
break; // If subsampled or outside source region, skip entire row
default: if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
throw new IIOException("Unsupported origin: " + origin); 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 // 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) { private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1) if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
&& xSub == 1 && xSub == 1
@ -452,20 +503,26 @@ final class TGAImageReader extends ImageReaderBase {
// Thumbnail is always stored non-compressed, no need for RLE support // Thumbnail is always stored non-compressed, no need for RLE support
imageInput.seek(extensions.getThumbnailOffset() + 2); imageInput.seek(extensions.getThumbnailOffset() + 2);
int pixelDepth = header.getPixelDepth();
boolean flipped = isOriginLowerLeft(header.getOrigin());
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
switch (header.getPixelDepth()) { switch (pixelDepth) {
case 8: case 8:
case 24: case 24:
case 32:
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); 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; break;
case 16: case 16:
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); 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; break;
default: default:
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth()); throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
} }
processThumbnailProgress(100f * y / height); processThumbnailProgress(100f * y / height);

View File

@ -97,7 +97,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"), new Dimension(1728, 1146)), // Gray, CCITT type 2 compressed new TestData(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"), new Dimension(1728, 1146)), // Gray, CCITT type 2 compressed
new TestData(getClassLoaderResource("/tiff/cramps-tile.tif"), new Dimension(800, 607)), // Gray/WhiteIsZero, uncompressed, striped & tiled... new TestData(getClassLoaderResource("/tiff/cramps-tile.tif"), new Dimension(800, 607)), // Gray/WhiteIsZero, uncompressed, striped & tiled...
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg_no_profile.tif"), new Dimension(150, 63)), // CMYK, JPEG compressed, no ICC profile new TestData(getClassLoaderResource("/tiff/cmyk_jpeg_no_profile.tif"), new Dimension(150, 63)), // CMYK, JPEG compressed, no ICC profile
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile
new TestData(getClassLoaderResource("/tiff/grayscale-alpha.tiff"), new Dimension(248, 351)), // Gray + unassociated alpha new TestData(getClassLoaderResource("/tiff/grayscale-alpha.tiff"), new Dimension(248, 351)), // Gray + unassociated alpha
@ -172,10 +171,55 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/jpeg-lossless-16bit-gray.tif"), new Dimension(512, 512)), // Lossless JPEG Gray, 16 bit/sample new TestData(getClassLoaderResource("/tiff/jpeg-lossless-16bit-gray.tif"), new Dimension(512, 512)), // Lossless JPEG Gray, 16 bit/sample
new TestData(getClassLoaderResource("/tiff/jpeg-lossless-24bit-rgb.tif"), new Dimension(512, 512)), // Lossless JPEG RGB, 8 bit/sample new TestData(getClassLoaderResource("/tiff/jpeg-lossless-24bit-rgb.tif"), new Dimension(512, 512)), // Lossless JPEG RGB, 8 bit/sample
// Custom PIXTIFF ZIP (Compression: 50013) // Custom PIXTIFF ZIP (Compression: 50013)
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)) // ZIP Gray, 8 bit/sample new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)), // ZIP Gray, 8 bit/sample
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)) // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
); );
} }
@Override
protected List<TestData> 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<TestData> getUnsupportedTestData() { private List<TestData> getUnsupportedTestData() {
return Arrays.asList( return Arrays.asList(
// RGB Interleaved (PlanarConfiguration: 1) // RGB Interleaved (PlanarConfiguration: 1)