#384 RGB PSB files with non-transparency alpha channels now rendered correctly

This commit is contained in:
Harald Kuhr 2017-10-27 19:51:09 +02:00
parent f14159de57
commit 4a1eb4b376
9 changed files with 173 additions and 146 deletions

View File

@ -49,7 +49,7 @@ final class PSDAlphaChannelInfo extends PSDImageResource {
@Override @Override
protected void readData(final ImageInputStream pInput) throws IOException { protected void readData(final ImageInputStream pInput) throws IOException {
names = new ArrayList<String>(); names = new ArrayList<>();
long left = size; long left = size;
while (left > 0) { while (left > 0) {

View File

@ -40,8 +40,8 @@ import java.io.IOException;
* @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$ * @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$
*/ */
final class PSDHeader { final class PSDHeader {
static final int PSD_MAX_SIZE = 30000; private static final int PSD_MAX_SIZE = 30000;
static final int PSB_MAX_SIZE = 300000; private static final int PSB_MAX_SIZE = 300000;
// The header is 26 bytes in length and is structured as follows: // The header is 26 bytes in length and is structured as follows:
// //
// typedef struct _PSD_HEADER // typedef struct _PSD_HEADER
@ -87,8 +87,8 @@ final class PSDHeader {
pInput.readFully(reserved); // We don't really care pInput.readFully(reserved); // We don't really care
channels = pInput.readShort(); channels = pInput.readShort();
if (channels <= 0) { if (channels < 1 || channels > 56) {
throw new IIOException(String.format("Unsupported number of channels: %d", channels)); throw new IIOException(String.format("Unsupported number of channels for PSD: %d", channels));
} }
height = pInput.readInt(); // Rows height = pInput.readInt(); // Rows

View File

@ -170,23 +170,21 @@ public final class PSDImageReader extends ImageReaderBase {
cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
} }
if (header.channels == 1 && header.bits == 8) { if (header.channels >= 1) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_BYTE, false, false); 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);
} }
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);
} }
throw new IIOException(String.format("Unsupported channel count/bit depth for Gray Scale PSD: %d channels/%d bits", header.channels, header.bits)); 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); cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
} }
if (header.channels == 3 && header.bits == 8) { if (header.channels >= 3) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); 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);
} }
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);
} }
throw new IIOException(String.format("Unsupported channel count/bit depth for RGB PSD: %d channels/%d bits", header.channels, header.bits)); 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); cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
} }
if (header.channels == 4 && header.bits == 8) { if (header.channels >= 4) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, false, false); 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);
} }
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);
} }
throw new IIOException(String.format("Unsupported channel count/bit depth for CMYK PSD: %d channels/%d bits", header.channels, header.bits)); 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) { switch (header.bits) {
case 1: case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); 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; break;
case 8: case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); 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; break;
case 16: case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); 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; break;
case 32: case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData(); 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; break;
default: default:
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits)); 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! // NOTE: ColorSpace uses Object.equals(), so we rely on using same instances!
if (!pSourceCM.getColorSpace().equals(destination.getColorModel().getColorSpace())) { if (!pSourceCM.getColorSpace().equals(destCM.getColorSpace())) {
convertToDestinationCS(pSourceCM, destination.getColorModel(), destRaster); convertToDestinationCS(pSourceCM, destCM, destRaster);
} }
} }
@ -890,13 +886,12 @@ public final class PSDImageReader extends ImageReaderBase {
private void readImageResources(final boolean pParseData) throws IOException { private void readImageResources(final boolean pParseData) throws IOException {
readHeader(); readHeader();
if (pParseData || metadata.layerAndMaskInfoStart == 0) { if (pParseData && metadata.imageResources == null || metadata.layerAndMaskInfoStart == 0) {
imageInput.seek(metadata.imageResourcesStart); imageInput.seek(metadata.imageResourcesStart);
long imageResourcesLength = imageInput.readUnsignedInt(); long imageResourcesLength = imageInput.readUnsignedInt();
if (pParseData && imageResourcesLength > 0) { if (pParseData && metadata.imageResources == null && imageResourcesLength > 0) {
if (metadata.imageResources == null) {
metadata.imageResources = new ArrayList<>(); metadata.imageResources = new ArrayList<>();
long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength; long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength;
@ -916,7 +911,6 @@ public final class PSDImageReader extends ImageReaderBase {
// TODO: We should now be able to flush input // TODO: We should now be able to flush input
// imageInput.flushBefore(metadata.imageResourcesStart + imageResourcesLength + 4); // imageInput.flushBefore(metadata.imageResourcesStart + imageResourcesLength + 4);
}
metadata.layerAndMaskInfoStart = metadata.imageResourcesStart + imageResourcesLength + 4; // + 4 for the length field itself 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 { private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
readImageResources(false); readImageResources(false);
if (pParseData || metadata.imageDataStart == 0) { if (pParseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
imageInput.seek(metadata.layerAndMaskInfoStart); imageInput.seek(metadata.layerAndMaskInfoStart);
long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt(); long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
@ -937,10 +931,10 @@ public final class PSDImageReader extends ImageReaderBase {
// is alo not as per spec, as layer count should be included if there's a layer info // 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))... // 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(); long pos = imageInput.getStreamPosition();
if (metadata.layerInfo == null) { //if (metadata.layerInfo == null) {
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt(); long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
if (layerInfoLength > 0) { if (layerInfoLength > 0) {
@ -948,19 +942,21 @@ public final class PSDImageReader extends ImageReaderBase {
// layers and the first alpha channel contains the transparency data for the // layers and the first alpha channel contains the transparency data for the
// merged result." // merged result."
int layerCount = imageInput.readShort(); int layerCount = imageInput.readShort();
metadata.layerCount = layerCount;
if (pParseData && metadata.layerInfo == null) {
PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)]; PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)];
for (int i = 0; i < layerInfos.length; i++) { for (int i = 0; i < layerInfos.length; i++) {
layerInfos[i] = new PSDLayerInfo(header.largeFormat, imageInput); layerInfos[i] = new PSDLayerInfo(header.largeFormat, imageInput);
} }
metadata.layerInfo = Arrays.asList(layerInfos); metadata.layerInfo = Arrays.asList(layerInfos);
metadata.layersStart = imageInput.getStreamPosition(); metadata.layersStart = imageInput.getStreamPosition();
long read = imageInput.getStreamPosition() - pos; }
long diff = layerInfoLength - (read - (header.largeFormat long read = imageInput.getStreamPosition() - pos;
? 8 long diff = layerInfoLength - (read - (header.largeFormat ? 8 : 4)); // - 8 or 4 for the layerInfoLength field itself
: 4)); // - 4 for the layerInfoLength field itself
imageInput.skipBytes(diff); imageInput.skipBytes(diff);
} }
@ -968,13 +964,17 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.layerInfo = Collections.emptyList(); metadata.layerInfo = Collections.emptyList();
} }
// Global LayerMaskInfo (18 bytes or more..?) // Global LayerMaskInfo (18 bytes or more..?)
// 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad) // 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 globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB!
if (globalLayerMaskInfoLength > 0) { if (globalLayerMaskInfoLength > 0) {
if (pParseData && metadata.globalLayerMask == null) {
metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength); metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength);
} }
// TODO: Else skip?
}
// TODO: Parse "Additional layer information" // TODO: Parse "Additional layer information"
@ -984,8 +984,9 @@ public final class PSDImageReader extends ImageReaderBase {
if (DEBUG) { if (DEBUG) {
System.out.println("layerInfo: " + metadata.layerInfo); System.out.println("layerInfo: " + metadata.layerInfo);
System.out.println("globalLayerMask: " + metadata.globalLayerMask);
} }
} //}
} }
metadata.imageDataStart = metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4); 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); 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... // Even if raw/imageType has no alpha, the layers may still have alpha...
ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex); ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex);
@ -1035,8 +1034,7 @@ public final class PSDImageReader extends ImageReaderBase {
// Skip layer if we can't read it // Skip layer if we can't read it
// channelId // 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) // -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 || if (channelInfo.channelId < -1 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions!
(compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions!
imageInput.skipBytes(channelInfo.length - 2); imageInput.skipBytes(channelInfo.length - 2);
} }
else { else {
@ -1147,26 +1145,13 @@ public final class PSDImageReader extends ImageReaderBase {
/// Layer support /// 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 @Override
public int getNumImages(boolean allowSearch) throws IOException { public int getNumImages(boolean allowSearch) throws IOException {
// NOTE: Spec says this method should throw IllegalStateException if allowSearch && isSeekForwardOnly() // 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? // But that makes no sense for a format (like PSD) that does not need to search, right?
readLayerAndMaskInfo(false);
readLayerAndMaskInfo(true); return metadata.getLayerCount() + 1; // TODO: Only plus one, if "has real merged data"?
return metadata.layerInfo != null ? metadata.layerInfo.size() + 1 : 1; // TODO: Only plus one, if "has real merged data"?
} }
/// Metadata support /// Metadata support

View File

@ -64,6 +64,7 @@ public final class PSDMetadata extends AbstractMetadata {
PSDGlobalLayerMask globalLayerMask; PSDGlobalLayerMask globalLayerMask;
List<PSDLayerInfo> layerInfo; List<PSDLayerInfo> layerInfo;
int layerCount;
long imageResourcesStart; long imageResourcesStart;
long layerAndMaskInfoStart; long layerAndMaskInfoStart;
long layersStart; long layersStart;
@ -627,7 +628,7 @@ public final class PSDMetadata extends AbstractMetadata {
// TODO: If no PSDResolutionInfo, this might still be available in the EXIF data... // TODO: If no PSDResolutionInfo, this might still be available in the EXIF data...
Iterator<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class); Iterator<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class);
if (!resolutionInfos.hasNext()) { if (resolutionInfos.hasNext()) {
PSDResolutionInfo resolutionInfo = resolutionInfos.next(); PSDResolutionInfo resolutionInfo = resolutionInfos.next();
node = new IIOMetadataNode("HorizontalPixelSize"); node = new IIOMetadataNode("HorizontalPixelSize");
@ -795,12 +796,15 @@ public final class PSDMetadata extends AbstractMetadata {
return transparencyNode; return transparencyNode;
} }
private boolean hasAlpha() { boolean hasAlpha() {
return header.mode == PSD.COLOR_MODE_RGB && header.channels > 3 || return layerCount < 0;
header.mode == PSD.COLOR_MODE_CMYK & header.channels > 4;
} }
<T extends PSDImageResource> Iterator<T> getResources(final Class<T> resourceType) { int getLayerCount() {
return Math.abs(layerCount);
}
private <T extends PSDImageResource> Iterator<T> getResources(final Class<T> resourceType) {
// NOTE: The cast here is wrong, strictly speaking, but it does not matter... // NOTE: The cast here is wrong, strictly speaking, but it does not matter...
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
Iterator<T> iterator = (Iterator<T>) imageResources.iterator(); Iterator<T> iterator = (Iterator<T>) imageResources.iterator();
@ -812,7 +816,7 @@ public final class PSDMetadata extends AbstractMetadata {
}); });
} }
Iterator<PSDImageResource> getResources(final int... resourceTypes) { private Iterator<PSDImageResource> getResources(final int... resourceTypes) {
Iterator<PSDImageResource> iterator = imageResources.iterator(); Iterator<PSDImageResource> iterator = imageResources.iterator();
return new FilterIterator<>(iterator, new FilterIterator.Filter<PSDImageResource>() { return new FilterIterator<>(iterator, new FilterIterator.Filter<PSDImageResource>() {

View File

@ -49,7 +49,7 @@ final class PSDUnicodeAlphaNames extends PSDImageResource {
@Override @Override
protected void readData(final ImageInputStream pInput) throws IOException { protected void readData(final ImageInputStream pInput) throws IOException {
names = new ArrayList<String>(); names = new ArrayList<>();
long left = size; long left = size;
while (left > 0) { while (left > 0) {
@ -58,4 +58,11 @@ final class PSDUnicodeAlphaNames extends PSDImageResource {
left -= name.length() * 2 + 4; left -= name.length() * 2 + 4;
} }
} }
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", alpha channels: ").append(names).append("]");
return builder.toString();
}
} }

View File

@ -83,7 +83,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
// 3 channel, RGB, 32 bit samples // 3 channel, RGB, 32 bit samples
new TestData(getClassLoaderResource("/psd/32bit5x5.psd"), new Dimension(5, 5)), 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("/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/ // From http://telegraphics.com.au/svn/psdparse/trunk/psd/
new TestData(getClassLoaderResource("/psd/adobehq.psd"), new Dimension(341, 512)), new TestData(getClassLoaderResource("/psd/adobehq.psd"), new Dimension(341, 512)),
new TestData(getClassLoaderResource("/psd/adobehq_ind.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<PSDImageReader>
new TestData(getClassLoaderResource("/psd/adobehq-5.5.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)), new TestData(getClassLoaderResource("/psd/adobehq-7.0.psd"), new Dimension(341, 512)),
// From https://github.com/kmike/psd-tools/tree/master/tests/psd_files // 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 uncompressed PSD
// TODO: Need more recent ZIP compressed PSD files from CS2/CS3+ // TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
); );
@ -453,6 +456,34 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
} }
} }
@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 @Test
public void testReadUnicodeLayerName() throws IOException { public void testReadUnicodeLayerName() throws IOException {
PSDImageReader imageReader = createReader(); PSDImageReader imageReader = createReader();