From 4a1eb4b376459c0663f3a407786b8bd2076315f2 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 27 Oct 2017 19:51:09 +0200 Subject: [PATCH] #384 RGB PSB files with non-transparency alpha channels now rendered correctly --- .../plugins/psd/PSDAlphaChannelInfo.java | 2 +- .../imageio/plugins/psd/PSDHeader.java | 8 +- .../imageio/plugins/psd/PSDImageReader.java | 243 ++++++++---------- .../imageio/plugins/psd/PSDMetadata.java | 22 +- .../plugins/psd/PSDUnicodeAlphaNames.java | 9 +- .../plugins/psd/PSDImageReaderTest.java | 35 ++- .../psb/rgb-multichannel-no-transparency.psb | Bin 0 -> 44596 bytes .../resources/{psd => psb}/test_original.psb | Bin .../psd/rgb-multichannel-no-transparency.psd | Bin 0 -> 43770 bytes 9 files changed, 173 insertions(+), 146 deletions(-) create mode 100755 imageio/imageio-psd/src/test/resources/psb/rgb-multichannel-no-transparency.psb rename imageio/imageio-psd/src/test/resources/{psd => psb}/test_original.psb (100%) create mode 100755 imageio/imageio-psd/src/test/resources/psd/rgb-multichannel-no-transparency.psd diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java index 2d3ec3f6..f474cd36 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java @@ -49,7 +49,7 @@ final class PSDAlphaChannelInfo extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { - names = new ArrayList(); + names = new ArrayList<>(); long left = size; while (left > 0) { diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java index cdd31def..8892a6a8 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java @@ -40,8 +40,8 @@ import java.io.IOException; * @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$ */ final class PSDHeader { - static final int PSD_MAX_SIZE = 30000; - static final int PSB_MAX_SIZE = 300000; + private static final int PSD_MAX_SIZE = 30000; + private static final int PSB_MAX_SIZE = 300000; // The header is 26 bytes in length and is structured as follows: // // typedef struct _PSD_HEADER @@ -87,8 +87,8 @@ final class PSDHeader { pInput.readFully(reserved); // We don't really care channels = pInput.readShort(); - if (channels <= 0) { - throw new IIOException(String.format("Unsupported number of channels: %d", channels)); + if (channels < 1 || channels > 56) { + throw new IIOException(String.format("Unsupported number of channels for PSD: %d", channels)); } height = pInput.readInt(); // Rows 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 1357cd51..0b50a536 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 @@ -170,23 +170,21 @@ public final class PSDImageReader extends ImageReaderBase { cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); } - if (header.channels == 1 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_BYTE, false, false); - } - else if (header.channels == 2 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_BYTE, true, false); - } - else if (header.channels == 1 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_USHORT, false, false); - } - else if (header.channels == 2 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_USHORT, true, false); - } - else if (header.channels == 1 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_INT, false, false); - } - else if (header.channels == 2 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_INT, true, false); + if (header.channels >= 1) { + switch (header.bits) { + case 8: + return metadata.hasAlpha() && header.channels > 1 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_BYTE, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_BYTE, false, false); + case 16: + return metadata.hasAlpha() && header.channels > 1 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_USHORT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_USHORT, false, false); + case 32: + return metadata.hasAlpha() && header.channels > 1 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_INT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_INT, false, false); + } } throw new IIOException(String.format("Unsupported channel count/bit depth for Gray Scale PSD: %d channels/%d bits", header.channels, header.bits)); @@ -197,23 +195,22 @@ public final class PSDImageReader extends ImageReaderBase { cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); } - if (header.channels == 3 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); - } - else if (header.channels >= 4 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false); - } - else if (header.channels == 3 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_USHORT, false, false); - } - else if (header.channels >= 4 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false); - } - else if (header.channels == 3 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_INT, false, false); - } - else if (header.channels >= 4 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false); + if (header.channels >= 3) { + switch (header.bits) { + case 8: + return metadata.hasAlpha() && header.channels > 3 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); + case 16: + return metadata.hasAlpha() && header.channels > 3 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_USHORT, false, false); + case 32: + return metadata.hasAlpha() && header.channels > 3 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_INT, false, false); + + } } throw new IIOException(String.format("Unsupported channel count/bit depth for RGB PSD: %d channels/%d bits", header.channels, header.bits)); @@ -224,23 +221,22 @@ public final class PSDImageReader extends ImageReaderBase { cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK); } - if (header.channels == 4 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); - } - else if (header.channels >= 5 && header.bits == 8) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false); - } - else if (header.channels == 4 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false); - } - else if (header.channels >= 5 && header.bits == 16) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false); - } - else if (header.channels == 4 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, false, false); - } - else if (header.channels >= 5 && header.bits == 32) { - return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false); + if (header.channels >= 4) { + switch (header.bits) { + case 8: + return metadata.hasAlpha() && header.channels > 4 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); + case 16: + return metadata.hasAlpha() && header.channels > 4 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false); + case 32: + return metadata.hasAlpha() && header.channels > 4 + ? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false) + : ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, false, false); + + } } throw new IIOException(String.format("Unsupported channel count/bit depth for CMYK PSD: %d channels/%d bits", header.channels, header.bits)); @@ -465,19 +461,19 @@ public final class PSDImageReader extends ImageReaderBase { switch (header.bits) { case 1: byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - read1bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE); + read1bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE); break; case 8: byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - read8bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); + read8bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); break; case 16: short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); - read16bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); + read16bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); break; case 32: int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); - read32bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); + read32bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE); break; default: throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits)); @@ -494,8 +490,8 @@ public final class PSDImageReader extends ImageReaderBase { } // NOTE: ColorSpace uses Object.equals(), so we rely on using same instances! - if (!pSourceCM.getColorSpace().equals(destination.getColorModel().getColorSpace())) { - convertToDestinationCS(pSourceCM, destination.getColorModel(), destRaster); + if (!pSourceCM.getColorSpace().equals(destCM.getColorSpace())) { + convertToDestinationCS(pSourceCM, destCM, destRaster); } } @@ -505,8 +501,8 @@ public final class PSDImageReader extends ImageReaderBase { // Color conversion from embedded color space, to destination color space WritableRaster alphaMaskedRaster = destinationCM.hasAlpha() ? raster.createWritableChild(0, 0, raster.getWidth(), raster.getHeight(), - raster.getMinX(), raster.getMinY(), - createBandList(sourceCM.getColorSpace().getNumComponents())) + raster.getMinX(), raster.getMinY(), + createBandList(sourceCM.getColorSpace().getNumComponents())) : raster; new ColorConvertOp(sourceCM.getColorSpace(), destinationCM.getColorSpace(), null) @@ -890,34 +886,32 @@ public final class PSDImageReader extends ImageReaderBase { private void readImageResources(final boolean pParseData) throws IOException { readHeader(); - if (pParseData || metadata.layerAndMaskInfoStart == 0) { + if (pParseData && metadata.imageResources == null || metadata.layerAndMaskInfoStart == 0) { imageInput.seek(metadata.imageResourcesStart); long imageResourcesLength = imageInput.readUnsignedInt(); - if (pParseData && imageResourcesLength > 0) { - if (metadata.imageResources == null) { - metadata.imageResources = new ArrayList<>(); - long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength; + if (pParseData && metadata.imageResources == null && imageResourcesLength > 0) { + metadata.imageResources = new ArrayList<>(); + long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength; - while (imageInput.getStreamPosition() < expectedEnd) { - PSDImageResource resource = PSDImageResource.read(imageInput); - metadata.imageResources.add(resource); - } - - if (DEBUG) { - System.out.println("imageResources: " + metadata.imageResources); - } - - if (imageInput.getStreamPosition() != expectedEnd) { - throw new IIOException("Corrupt PSD document"); // ..or maybe just a bug in the reader.. ;-) - } + while (imageInput.getStreamPosition() < expectedEnd) { + PSDImageResource resource = PSDImageResource.read(imageInput); + metadata.imageResources.add(resource); } - // TODO: We should now be able to flush input -// imageInput.flushBefore(metadata.imageResourcesStart + imageResourcesLength + 4); + if (DEBUG) { + System.out.println("imageResources: " + metadata.imageResources); + } + + if (imageInput.getStreamPosition() != expectedEnd) { + throw new IIOException("Corrupt PSD document"); // ..or maybe just a bug in the reader.. ;-) + } } + // TODO: We should now be able to flush input +// imageInput.flushBefore(metadata.imageResourcesStart + imageResourcesLength + 4); + metadata.layerAndMaskInfoStart = metadata.imageResourcesStart + imageResourcesLength + 4; // + 4 for the length field itself } } @@ -927,7 +921,7 @@ public final class PSDImageReader extends ImageReaderBase { private void readLayerAndMaskInfo(final boolean pParseData) throws IOException { readImageResources(false); - if (pParseData || metadata.imageDataStart == 0) { + if (pParseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) { imageInput.seek(metadata.layerAndMaskInfoStart); long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt(); @@ -937,55 +931,62 @@ public final class PSDImageReader extends ImageReaderBase { // is alo not as per spec, as layer count should be included if there's a layer info // block, so minimum size should be either 0 or 14 (or 16 if multiple of 4 for PSB))... - if (pParseData && layerAndMaskInfoLength > 0) { + if (layerAndMaskInfoLength > 0) { long pos = imageInput.getStreamPosition(); - if (metadata.layerInfo == null) { - long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt(); + //if (metadata.layerInfo == null) { + long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt(); - if (layerInfoLength > 0) { - // "Layer count. If it is a negative number, its absolute value is the number of - // layers and the first alpha channel contains the transparency data for the - // merged result." - int layerCount = imageInput.readShort(); + if (layerInfoLength > 0) { + // "Layer count. If it is a negative number, its absolute value is the number of + // layers and the first alpha channel contains the transparency data for the + // merged result." + int layerCount = imageInput.readShort(); + metadata.layerCount = layerCount; + if (pParseData && metadata.layerInfo == null) { PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)]; for (int i = 0; i < layerInfos.length; i++) { layerInfos[i] = new PSDLayerInfo(header.largeFormat, imageInput); } + metadata.layerInfo = Arrays.asList(layerInfos); metadata.layersStart = imageInput.getStreamPosition(); - long read = imageInput.getStreamPosition() - pos; - - long diff = layerInfoLength - (read - (header.largeFormat - ? 8 - : 4)); // - 4 for the layerInfoLength field itself - - imageInput.skipBytes(diff); - } - else { - metadata.layerInfo = Collections.emptyList(); } - // Global LayerMaskInfo (18 bytes or more..?) - // 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad) - long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB! + long read = imageInput.getStreamPosition() - pos; + long diff = layerInfoLength - (read - (header.largeFormat ? 8 : 4)); // - 8 or 4 for the layerInfoLength field itself - if (globalLayerMaskInfoLength > 0) { + imageInput.skipBytes(diff); + } + else { + metadata.layerInfo = Collections.emptyList(); + } + + + // Global LayerMaskInfo (18 bytes or more..?) + // 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad) + long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB! + + if (globalLayerMaskInfoLength > 0) { + if (pParseData && metadata.globalLayerMask == null) { metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength); } + // TODO: Else skip? + } - // TODO: Parse "Additional layer information" + // TODO: Parse "Additional layer information" - // TODO: We should now be able to flush input + // TODO: We should now be able to flush input // imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4)); // imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4)); - if (DEBUG) { - System.out.println("layerInfo: " + metadata.layerInfo); - } + if (DEBUG) { + System.out.println("layerInfo: " + metadata.layerInfo); + System.out.println("globalLayerMask: " + metadata.globalLayerMask); } + //} } metadata.imageDataStart = metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4); @@ -1003,8 +1004,6 @@ public final class PSDImageReader extends ImageReaderBase { } PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex); -// final int width = layerInfo.right - layerInfo.left; -// final int height = layerInfo.bottom - layerInfo.top; // Even if raw/imageType has no alpha, the layers may still have alpha... ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex); @@ -1035,8 +1034,7 @@ public final class PSDImageReader extends ImageReaderBase { // Skip layer if we can't read it // channelId // -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present) - if (width <= 0 || height <= 0 || channelInfo.channelId < -1 || - (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions! + if (channelInfo.channelId < -1 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions! imageInput.skipBytes(channelInfo.length - 2); } else { @@ -1080,17 +1078,17 @@ public final class PSDImageReader extends ImageReaderBase { case 8: byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, - ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); + ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; case 16: short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, - ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); + ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; case 32: int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); read32bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row32, area, area, xsub, - ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); + ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; default: throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits)); @@ -1147,26 +1145,13 @@ public final class PSDImageReader extends ImageReaderBase { /// Layer support - @Override - protected void checkBounds(final int index) throws IOException { - // Avoid parsing layer stuff, if we just want to read the composite data - if (index == 0) { - assertInput(); - readLayerAndMaskInfo(false); - } - else { - super.checkBounds(index); - } - } - @Override public int getNumImages(boolean allowSearch) throws IOException { // NOTE: Spec says this method should throw IllegalStateException if allowSearch && isSeekForwardOnly() // But that makes no sense for a format (like PSD) that does not need to search, right? + readLayerAndMaskInfo(false); - readLayerAndMaskInfo(true); - - return metadata.layerInfo != null ? metadata.layerInfo.size() + 1 : 1; // TODO: Only plus one, if "has real merged data"? + return metadata.getLayerCount() + 1; // TODO: Only plus one, if "has real merged data"? } /// Metadata support @@ -1374,7 +1359,7 @@ public final class PSDImageReader extends ImageReaderBase { param.setSourceSubsampling(subsampleFactor, subsampleFactor, 0, 0); } - // param.setDestinationType(imageReader.getRawImageType(0)); + // param.setDestinationType(imageReader.getRawImageType(0)); BufferedImage image = imageReader.read(0, param); System.out.println("read time: " + (System.currentTimeMillis() - start)); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 2e2e4901..2f2ba8be 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -64,6 +64,7 @@ public final class PSDMetadata extends AbstractMetadata { PSDGlobalLayerMask globalLayerMask; List layerInfo; + int layerCount; long imageResourcesStart; long layerAndMaskInfoStart; long layersStart; @@ -170,7 +171,7 @@ public final class PSDMetadata extends AbstractMetadata { node = new IIOMetadataNode("DisplayInfo"); node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.colorSpace]); - + StringBuilder builder = new StringBuilder(); for (short color : displayInfo.colors) { @@ -307,7 +308,7 @@ public final class PSDMetadata extends AbstractMetadata { } else if (imageResource instanceof PSDXMPData) { // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid... - // Or maybe use the Directory approach used by IPTC and EXIF.. + // Or maybe use the Directory approach used by IPTC and EXIF.. PSDXMPData xmp = (PSDXMPData) imageResource; node = new IIOMetadataNode("DirectoryResource"); @@ -627,7 +628,7 @@ public final class PSDMetadata extends AbstractMetadata { // TODO: If no PSDResolutionInfo, this might still be available in the EXIF data... Iterator resolutionInfos = getResources(PSDResolutionInfo.class); - if (!resolutionInfos.hasNext()) { + if (resolutionInfos.hasNext()) { PSDResolutionInfo resolutionInfo = resolutionInfos.next(); node = new IIOMetadataNode("HorizontalPixelSize"); @@ -643,7 +644,7 @@ public final class PSDMetadata extends AbstractMetadata { } private static float asMM(final short unit, final float resolution) { - // Unit: 1 -> pixels per inch, 2 -> pixels pr cm + // Unit: 1 -> pixels per inch, 2 -> pixels pr cm return (unit == 1 ? 25.4f : 10) / resolution; } @@ -795,12 +796,15 @@ public final class PSDMetadata extends AbstractMetadata { return transparencyNode; } - private boolean hasAlpha() { - return header.mode == PSD.COLOR_MODE_RGB && header.channels > 3 || - header.mode == PSD.COLOR_MODE_CMYK & header.channels > 4; + boolean hasAlpha() { + return layerCount < 0; } - Iterator getResources(final Class resourceType) { + int getLayerCount() { + return Math.abs(layerCount); + } + + private Iterator getResources(final Class resourceType) { // NOTE: The cast here is wrong, strictly speaking, but it does not matter... @SuppressWarnings({"unchecked"}) Iterator iterator = (Iterator) imageResources.iterator(); @@ -812,7 +816,7 @@ public final class PSDMetadata extends AbstractMetadata { }); } - Iterator getResources(final int... resourceTypes) { + private Iterator getResources(final int... resourceTypes) { Iterator iterator = imageResources.iterator(); return new FilterIterator<>(iterator, new FilterIterator.Filter() { diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java index 23705172..6a6bf90c 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java @@ -49,7 +49,7 @@ final class PSDUnicodeAlphaNames extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { - names = new ArrayList(); + names = new ArrayList<>(); long left = size; while (left > 0) { @@ -58,4 +58,11 @@ final class PSDUnicodeAlphaNames extends PSDImageResource { left -= name.length() * 2 + 4; } } + + @Override + public String toString() { + StringBuilder builder = toStringBuilder(); + builder.append(", alpha channels: ").append(names).append("]"); + return builder.toString(); + } } 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 e3baf3a5..fdeaf75e 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 @@ -83,7 +83,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest // 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("/psd/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/ new TestData(getClassLoaderResource("/psd/adobehq.psd"), new Dimension(341, 512)), new TestData(getClassLoaderResource("/psd/adobehq_ind.psd"), new Dimension(341, 512)), @@ -93,7 +93,10 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest 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)) // TODO: Test read layers! + 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)) // TODO: Need uncompressed PSD // TODO: Need more recent ZIP compressed PSD files from CS2/CS3+ ); @@ -453,6 +456,34 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest } } + @Test + public void testMultiChannelNoTransparencyPSD() throws IOException { + PSDImageReader imageReader = createReader(); + + // The following PSD is RGB, has 4 channels (1 alpha/auxillary channel), but should be treated as opaque + try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"))) { + imageReader.setInput(stream); + + BufferedImage image = imageReader.read(0); + + assertEquals(Transparency.OPAQUE, image.getTransparency()); + } + } + + @Test + 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 + try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"))) { + imageReader.setInput(stream); + + BufferedImage image = imageReader.read(0); + + assertEquals(Transparency.OPAQUE, image.getTransparency()); + } + } + @Test public void testReadUnicodeLayerName() throws IOException { PSDImageReader imageReader = createReader(); diff --git a/imageio/imageio-psd/src/test/resources/psb/rgb-multichannel-no-transparency.psb b/imageio/imageio-psd/src/test/resources/psb/rgb-multichannel-no-transparency.psb new file mode 100755 index 0000000000000000000000000000000000000000..d097b54e5bc19f9ebd1bac6ab695cb91611824f1 GIT binary patch literal 44596 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 V_)LbFB4BJX#B?&m^#2-S`X3`IjwS#A literal 0 HcmV?d00001 diff --git a/imageio/imageio-psd/src/test/resources/psd/test_original.psb b/imageio/imageio-psd/src/test/resources/psb/test_original.psb similarity index 100% rename from imageio/imageio-psd/src/test/resources/psd/test_original.psb rename to imageio/imageio-psd/src/test/resources/psb/test_original.psb diff --git a/imageio/imageio-psd/src/test/resources/psd/rgb-multichannel-no-transparency.psd b/imageio/imageio-psd/src/test/resources/psd/rgb-multichannel-no-transparency.psd new file mode 100755 index 0000000000000000000000000000000000000000..e424b2c1d75dbd54630918c94eb50637a779a668 GIT binary patch literal 43770 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