diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java old mode 100755 new mode 100644 index 5f36ff01..0b6b23ff --- a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -31,9 +31,7 @@ package com.twelvemonkeys.imageio; import com.twelvemonkeys.image.BufferedImageIcon; import com.twelvemonkeys.imageio.util.IIOUtil; -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; +import javax.imageio.*; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; @@ -46,6 +44,7 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; /** * ImageReaderBase @@ -191,6 +190,79 @@ public abstract class ImageReaderBase extends ImageReader { } } + /** + * Returns the {@code BufferedImage} to which decoded pixel + * data should be written. + *

+ * As {@link javax.imageio.ImageReader#getDestination} but tests if the explicit destination + * image (if set) is valid according to the {@code ImageTypeSpecifier}s given in {@code pTypes} + * + * + * @param pParam an {@code ImageReadParam} to be used to get + * the destination image or image type, or {@code null}. + * @param pTypes an {@code Iterator} of + * {@code ImageTypeSpecifier}s indicating the legal image + * types, with the default first. + * @param pWidth the true width of the image or tile begin decoded. + * @param pHeight the true width of the image or tile being decoded. + * + * @return the {@code BufferedImage} to which decoded pixel + * data should be written. + * + * @exception IIOException if the {@code ImageTypeSpecifier} or {@code BufferedImage} + * specified by {@code pParam} does not match any of the legal + * ones from {@code pTypes}. + * @throws IllegalArgumentException if {@code pTypes} + * is {@code null} or empty, or if an object not of type + * {@code ImageTypeSpecifier} is retrieved from it. + * Or, if the resulting image would + * have a width or height less than 1, + * or if the product of + * {@code pWidth} and {@code pHeight} is greater than + * {@code Integer.MAX_VALUE}. + */ + public static BufferedImage getDestination(final ImageReadParam pParam, final Iterator pTypes, + final int pWidth, final int pHeight) throws IIOException { + BufferedImage image = ImageReader.getDestination(pParam, pTypes, pWidth, pHeight); + + if (pParam != null) { + BufferedImage dest = pParam.getDestination(); + if (dest != null) { + boolean found = false; + + // NOTE: This is bad, as it relies on implementation details of super method... + // We know that the iterator has not been touched if explicit destination.. + while (pTypes.hasNext()) { + ImageTypeSpecifier specifier = pTypes.next(); + int imageType = specifier.getBufferedImageType(); + + if (imageType != 0 && imageType == dest.getType()) { + // Known types equal, perfect match + found = true; + break; + } + else { + // If types are different, or TYPE_CUSTOM, test if + // - transferType is ok + // - bands are ok + // TODO: Test if color model is ok? + if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() && + specifier.getNumBands() <= dest.getSampleModel().getNumBands()) { + found = true; + break; + } + } + } + + if (!found) { + throw new IIOException(String.format("Illegal explicit destination image %s", dest)); + } + } + } + + return image; + } + /** * Utility method for getting the area of interest (AOI) of an image. * The AOI is defined by the {@link javax.imageio.IIOParam#setSourceRegion(java.awt.Rectangle)} diff --git a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java index 84909fe7..776c768d 100644 --- a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java +++ b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java @@ -540,14 +540,14 @@ public abstract class ImageReaderAbstractTestCase extends } - public void readAsRenderedImageIndexNegative() { + public void testReadAsRenderedImageIndexNegative() { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); - BufferedImage image = null; + RenderedImage image = null; try { - image = reader.read(-1, reader.getDefaultReadParam()); + image = reader.readAsRenderedImage(-1, reader.getDefaultReadParam()); fail("Read image with illegal index"); } catch (IndexOutOfBoundsException expected) { @@ -559,14 +559,14 @@ public abstract class ImageReaderAbstractTestCase extends assertNull(image); } - public void readAsRenderedImageIndexOutOfBounds() { + public void testReadAsRenderedImageIndexOutOfBounds() { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); - BufferedImage image = null; + RenderedImage image = null; try { - image = reader.read(11, reader.getDefaultReadParam()); + image = reader.readAsRenderedImage(reader.getNumImages(true), reader.getDefaultReadParam()); fail("Read image with index out of bounds"); } catch (IndexOutOfBoundsException expected) { @@ -578,7 +578,7 @@ public abstract class ImageReaderAbstractTestCase extends assertNull(image); } - public void readAsRenderedImageNoInput() { + public void testReadAsRenderedImageNoInput() { ImageReader reader = createReader(); // Do not set input @@ -596,7 +596,7 @@ public abstract class ImageReaderAbstractTestCase extends assertNull(image); } - public void readAsRenderedImage() { + public void testReadAsRenderedImage() { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -615,7 +615,7 @@ public abstract class ImageReaderAbstractTestCase extends data.getDimension(0).height, image.getHeight()); } - public void readAsRenderedImageWithDefaultParam() { + public void testReadAsRenderedImageWithDefaultParam() { ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); @@ -1181,28 +1181,93 @@ public abstract class ImageReaderAbstractTestCase extends assertSame(destination, result); } - // TODO: This test is foobar.. public void testSetDestinationIllegal() throws IOException { - // TODO: Test that the reader throws IIOException if given an illegal destination final ImageReader reader = createReader(); TestData data = getTestData().get(0); reader.setInput(data.getInputStream()); - Iterator types = reader.getImageTypes(0); + + List illegalTypes = createIllegalTypes(reader.getImageTypes(0)); ImageReadParam param = reader.getDefaultReadParam(); - // TODO: Should either be a type from image type specifiers or throw IIOException in read - BufferedImage destination = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB); - param.setDestination(destination); + for (ImageTypeSpecifier illegalType : illegalTypes) { + BufferedImage destination = illegalType.createBufferedImage(50, 50); + param.setDestination(destination); - try { - reader.read(0, param); - fail("Expected to throw exception with wrong type specifier"); + try { + reader.read(0, param); + + // NOTE: We allow the reader to read, as it's inconvenient to test all possible cases. + // However, it may NOT fail with any other exception in that case. + System.err.println("WARNING: Reader does not throw exception with non-declared destination: " + destination); + } + catch (IIOException expected) { + // TODO: This is thrown by ImageReader.getDestination. But are we happy with that? + // The problem is that the checkReadParamBandSettings throws IllegalArgumentException, which seems more appropriate... + String message = expected.getMessage(); + assertTrue("Wrong message: " + message, message.toLowerCase().contains("destination")); + } + catch (IllegalArgumentException expected) { + String message = expected.getMessage(); + assertTrue("Wrong message: " + message, message.toLowerCase().contains("dest")); + } } - catch (IIOException e) { - assertTrue(e.getMessage().toLowerCase().contains("type")); + } + + public void testSetDestinationTypeIllegal() throws IOException { + final ImageReader reader = createReader(); + TestData data = getTestData().get(0); + reader.setInput(data.getInputStream()); + + List illegalTypes = createIllegalTypes(reader.getImageTypes(0)); + + ImageReadParam param = reader.getDefaultReadParam(); + for (ImageTypeSpecifier illegalType : illegalTypes) { + param.setDestinationType(illegalType); + + try { + reader.read(0, param); + fail("Expected to throw exception with illegal type specifier"); + } + catch (IIOException expected) { + // TODO: This is thrown by ImageReader.getDestination. But are we happy with that? + String message = expected.getMessage(); + assertTrue(message.toLowerCase().contains("destination")); + assertTrue(message.toLowerCase().contains("type")); + } + catch (IllegalArgumentException expected) { + String message = expected.getMessage(); + assertTrue(message.toLowerCase().contains("destination")); + assertTrue(message.toLowerCase().contains("type")); + } } } + private List createIllegalTypes(Iterator pValidTypes) { + List allTypes = new ArrayList(); + for (int i = BufferedImage.TYPE_INT_RGB; i < BufferedImage.TYPE_BYTE_INDEXED; i++) { + allTypes.add(ImageTypeSpecifier.createFromBufferedImageType(i)); + } + + List illegalTypes = new ArrayList(allTypes); + while (pValidTypes.hasNext()) { + ImageTypeSpecifier valid = pValidTypes.next(); + boolean removed = illegalTypes.remove(valid); + + // TODO: 4BYTE_ABGR (6) and 4BYTE_ABGR_PRE (7) is essentially the same type... + // !#$#§%$! ImageTypeSpecifier.equals is not well-defined + if (!removed) { + for (Iterator iterator = illegalTypes.iterator(); iterator.hasNext();) { + ImageTypeSpecifier illegalType = iterator.next(); + if (illegalType.getBufferedImageType() == valid.getBufferedImageType()) { + iterator.remove(); + } + } + } + } + + return illegalTypes; + } + // TODO: Test dest offset + destination set? public void testSetDestinationOffset() throws IOException { final ImageReader reader = createReader(); @@ -1266,18 +1331,17 @@ public abstract class ImageReaderAbstractTestCase extends } } - public void testSetDestinationTypeIllegal() throws IOException { - throw new UnsupportedOperationException("Method testSetDestinationTypeIllegal not implemented"); // TODO: Implement - } - - public void testSetDestinationBands() throws IOException { - throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement - } - - public void testSetSourceBands() throws IOException { - throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement - } - +// public void testSetDestinationTypeIllegal() throws IOException { +// throw new UnsupportedOperationException("Method testSetDestinationTypeIllegal not implemented"); // TODO: Implement +// } +// +// public void testSetDestinationBands() throws IOException { +// throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement +// } +// +// public void testSetSourceBands() throws IOException { +// throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement +// } protected URL getClassLoaderResource(final String pName) { return getClass().getResource(pName); diff --git a/twelvemonkeys-imageio/ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java b/twelvemonkeys-imageio/ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java old mode 100755 new mode 100644 index 9e3503fb..7be3ab71 --- a/twelvemonkeys-imageio/ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java +++ b/twelvemonkeys-imageio/ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java @@ -292,10 +292,9 @@ public class ICOImageReader extends ImageReaderBase { } private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException { - // TODO: Currently, we have a memory leak, as the values refer to the keys... BitmapDescriptor descriptor = mDescriptors.get(pEntry); - if (!mDescriptors.containsKey(pEntry)) { + if (descriptor == null || !mDescriptors.containsKey(pEntry)) { DIBHeader header = getHeader(pEntry); int offset = pEntry.getOffset() + header.getSize(); diff --git a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java index 4e246ef2..bce32188 100644 --- a/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java +++ b/twelvemonkeys-imageio/pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java @@ -2604,6 +2604,7 @@ public class PICTImageReader extends ImageReaderBase { processImageStarted(pIndex); // TODO: Param handling + // TODO: Real subsampling for bit/pixmap/QT stills final int subX, subY; if (pParam != null) { subX = pParam.getSourceXSubsampling(); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 1c81c24b..98c6e01d 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -276,11 +276,9 @@ public class PSDImageReader extends ImageReaderBase { readImageResources(false); readLayerAndMaskInfo(false); - // TODO: Test if explicit destination is compatible or throw IllegalArgumentException BufferedImage image = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight); ImageTypeSpecifier rawType = getRawImageType(pIndex); - - processImageStarted(pIndex); + checkReadParamBandSettings(pParam, rawType.getNumBands(), image.getSampleModel().getNumBands()); final Rectangle source = new Rectangle(); final Rectangle dest = new Rectangle(); @@ -293,7 +291,7 @@ public class PSDImageReader extends ImageReaderBase { // TODO: Create temp raster in native format w * 1 // Read (sub-sampled) row into temp raster (skip other rows) // If color model (color space) is not RGB, do color convert op - // Otherwise, copy "through" ColorMode?l + // Otherwise, copy "through" ColorModel? // Copy pixels from temp raster // If possible, leave the destination image "untouched" (accelerated) @@ -322,6 +320,8 @@ public class PSDImageReader extends ImageReaderBase { ySub = pParam.getSourceYSubsampling(); } + processImageStarted(pIndex); + int[] offsets = null; int compression = mImageInput.readShort(); @@ -342,7 +342,12 @@ public class PSDImageReader extends ImageReaderBase { // Could be same as PNG prediction? Read up... throw new IIOException("ZIP compression not supported yet"); default: - throw new IIOException("Unknown compression type: " + compression); + throw new IIOException( + String.format( + "Unknown PSD compression: %d. Expected 0 (none), 1 (RLE), 2 (ZIP) or 3 (ZIP w/prediction).", + compression + ) + ); } // What we read here is the "composite layer" of the PSD file @@ -532,7 +537,8 @@ public class PSDImageReader extends ImageReaderBase { final byte[] pRow, final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub, final int[] pRowOffsets, boolean pRLECompressed) throws IOException { - + // NOTE: 1 bit channels only occurs once + final int destWidth = (pDest.width + 7) / 8; for (int y = 0; y < mHeader.mHeight; y++) { @@ -878,7 +884,7 @@ public class PSDImageReader extends ImageReaderBase { @Override public BufferedImage readThumbnail(int pImageIndex, int pThumbnailIndex) throws IOException { - // TODO: Thumbnail listeners... + // TODO: Thumbnail progress listeners... PSDThumbnail thumbnail = getThumbnailResource(pImageIndex, pThumbnailIndex); // TODO: Defer decoding