Layer support for PSD format. Still work in progress, but the basics is done.

This commit is contained in:
Harald Kuhr 2011-10-30 21:54:47 +01:00
parent 5857e27cf2
commit 8edc448bf9
5 changed files with 409 additions and 140 deletions

View File

@ -62,6 +62,7 @@ class ICCProfile extends PSDImageResource {
} }
public ICC_Profile getProfile() { public ICC_Profile getProfile() {
// Warning: Mutable...
return profile; return profile;
} }
@ -74,5 +75,4 @@ class ICCProfile extends PSDImageResource {
return builder.toString(); return builder.toString();
} }
} }

View File

@ -63,14 +63,13 @@ import java.util.List;
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$ * @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
*/ */
// TODO: Implement ImageIO meta data interface // TODO: Implement ImageIO meta data interface
// TODO: Allow reading the extra alpha channels (index after composite data)
// TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile? // TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile?
// TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0) // TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0)
// TODO: Support for API for reading separate layers (index after composite data, and optional alpha channels)
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers // TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx // See http://www.codeproject.com/KB/graphics/PSDParser.aspx
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0 // See http://www.adobeforums.com/webx?14@@.3bc381dc/0
// Done: Allow reading the extra alpha channels (index after composite data)
public class PSDImageReader extends ImageReaderBase { public class PSDImageReader extends ImageReaderBase {
private PSDHeader header; private PSDHeader header;
private ICC_ColorSpace colorSpace; private ICC_ColorSpace colorSpace;
@ -89,15 +88,47 @@ public class PSDImageReader extends ImageReaderBase {
public int getWidth(final int imageIndex) throws IOException { public int getWidth(final int imageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(imageIndex);
readHeader(); readHeader();
if (imageIndex > 0) {
return getLayerWidth(imageIndex - 1);
}
return header.width; return header.width;
} }
public int getHeight(final int imageIndex) throws IOException { public int getHeight(final int imageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(imageIndex);
readHeader(); readHeader();
if (imageIndex > 0) {
return getLayerHeight(imageIndex - 1);
}
return header.height; return header.height;
} }
private int getLayerWidth(int layerIndex) throws IOException {
if (metadata == null || metadata.layerInfo == null) {
readImageResources(false);
readLayerAndMaskInfo(true);
}
PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
return layerInfo.right - layerInfo.left;
}
private int getLayerHeight(int layerIndex) throws IOException {
if (metadata == null || metadata.layerInfo == null) {
readImageResources(false);
readLayerAndMaskInfo(true);
}
PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
return layerInfo.bottom - layerInfo.top;
}
@Override @Override
public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException { public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException {
return getRawImageTypeInternal(imageIndex); return getRawImageTypeInternal(imageIndex);
@ -107,8 +138,22 @@ public class PSDImageReader extends ImageReaderBase {
checkBounds(imageIndex); checkBounds(imageIndex);
readHeader(); readHeader();
ColorSpace cs; // Image index above 0, means a layer
if (imageIndex > 0) {
if (metadata == null || metadata.layerInfo == null) {
readImageResources(false);
readLayerAndMaskInfo(true);
}
return getRawImageTypeForLayer(imageIndex - 1);
}
// Otherwise, get the type specifier for the composite layer
return getRawImageTypeForCompositeLayer();
}
private ImageTypeSpecifier getRawImageTypeForCompositeLayer() throws IOException {
ColorSpace cs;
switch (header.mode) { switch (header.mode) {
case PSD.COLOR_MODE_MONOCHROME: case PSD.COLOR_MODE_MONOCHROME:
if (header.channels == 1 && header.bits == 1) { if (header.channels == 1 && header.bits == 1) {
@ -147,8 +192,8 @@ public class PSDImageReader extends ImageReaderBase {
case PSD.COLOR_MODE_RGB: case PSD.COLOR_MODE_RGB:
cs = getEmbeddedColorSpace(); cs = getEmbeddedColorSpace();
if (cs == null) { if (cs == null) {
// TODO: Should probably be Adobe RGB (1998), not sRGB. Or..? // TODO: Should probably be Adobe RGB (1998), not sRGB. Or..? Can't find any spec saying either...
cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); // ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998); ?
} }
if (header.channels == 3 && header.bits == 8) { if (header.channels == 3 && header.bits == 8) {
@ -216,7 +261,8 @@ public class PSDImageReader extends ImageReaderBase {
switch (header.mode) { switch (header.mode) {
case PSD.COLOR_MODE_RGB: case PSD.COLOR_MODE_RGB:
// Prefer interleaved versions as they are much faster to display // Prefer interleaved versions as they are much faster to display
if (header.channels == 3 && header.bits == 8) { // if (header.channels == 3 && header.bits == 8) {
if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) {
// TODO: ColorConvertOp to CS_sRGB // TODO: ColorConvertOp to CS_sRGB
// TODO: Integer raster // TODO: Integer raster
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_RGB)); // types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_RGB));
@ -227,7 +273,8 @@ public class PSDImageReader extends ImageReaderBase {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
} }
} }
else if (header.channels >= 4 && header.bits == 8) { // else if (header.channels >= 4 && header.bits == 8) {
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 8) {
// TODO: ColorConvertOp to CS_sRGB // TODO: ColorConvertOp to CS_sRGB
// TODO: Integer raster // TODO: Integer raster
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_ARGB)); // types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_ARGB));
@ -238,10 +285,12 @@ public class PSDImageReader extends ImageReaderBase {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
} }
} }
else if (header.channels == 3 && header.bits == 16) { // else if (header.channels == 3 && header.bits == 16) {
else if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 16) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_USHORT, false, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
} }
else if (header.channels >= 4 && header.bits == 16) { // else if (header.channels >= 4 && header.bits == 16) {
else if (rawType.getNumBands() >= 4 && rawType.getBitsPerBand(0) == 16) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
} }
break; break;
@ -252,16 +301,20 @@ public class PSDImageReader extends ImageReaderBase {
// as Java2D is extremely slow displaying custom images. // as Java2D is extremely slow displaying custom images.
// Converting to RGB is also correct behaviour, according to the docs. // Converting to RGB is also correct behaviour, according to the docs.
// Doing this, will require rewriting the image reading, as the raw image data is channelled, not interleaved :-/ // Doing this, will require rewriting the image reading, as the raw image data is channelled, not interleaved :-/
if (header.channels == 4 && header.bits == 8) { // if (header.channels == 4 && header.bits == 8) {
if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 8) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
} }
else if (header.channels == 5 && header.bits == 8) { // else if (header.channels == 5 && header.bits == 8) {
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 8) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
} }
else if (header.channels == 4 && header.bits == 16) { // else if (header.channels == 4 && header.bits == 16) {
else if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 16) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
} }
else if (header.channels == 5 && header.bits == 16) { // else if (header.channels == 5 && header.bits == 16) {
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 16) {
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false)); types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
} }
break; break;
@ -301,7 +354,26 @@ public class PSDImageReader extends ImageReaderBase {
readHeader(); readHeader();
readImageResources(false); readImageResources(false);
readLayerAndMaskInfo(false); // readLayerAndMaskInfo(false);
readLayerAndMaskInfo(imageIndex > 0);
// TODO: What about the extra alpha channels possibly present? Read as gray scale as extra images?
// Layer hacks... For now, any index above 0 is considered to be a layer...
// TODO: Support layer in index 0, if "has real merged data" flag is false?
// TODO: Param support in layer code (more duping/cleanup..)
if (imageIndex > 0) {
// ImageTypeSpecifier compositeType = getRawImageTypeForCompositeLayer();
// ImageTypeSpecifier imageType = getImageTypes(0).next();
// int layerIndex = imageIndex - 1;
// PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
//
// imageInput.seek(findLayerStartPos(layerIndex));
// return readLayerData(layerIndex, layerInfo, compositeType, imageType, param);
return readLayerData(imageIndex - 1, param);
}
BufferedImage image = getDestination(param, getImageTypes(imageIndex), header.width, header.height); BufferedImage image = getDestination(param, getImageTypes(imageIndex), header.width, header.height);
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
@ -371,7 +443,7 @@ public class PSDImageReader extends ImageReaderBase {
case PSD.COMPRESSION_ZIP_PREDICTION: case PSD.COMPRESSION_ZIP_PREDICTION:
// TODO: Need to find out if the normal java.util.zip can handle this... // TODO: Need to find out if the normal java.util.zip can handle this...
// Could be same as PNG prediction? Read up... // Could be same as PNG prediction? Read up...
throw new IIOException("ZIP compression not supported yet"); throw new IIOException("PSD with ZIP compression not supported");
default: default:
throw new IIOException( throw new IIOException(
String.format( String.format(
@ -394,6 +466,20 @@ public class PSDImageReader extends ImageReaderBase {
return image; return image;
} }
private long findLayerStartPos(int layerIndex) {
long layersStart = metadata.layersStart;
for (int i = 0; i < layerIndex; i++) {
PSDLayerInfo layerInfo = metadata.layerInfo.get(i);
for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
layersStart += channelInfo.length;
}
}
return layersStart;
}
private void readImageData(final BufferedImage pImage, private void readImageData(final BufferedImage pImage,
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest, final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub, final int pXSub, final int pYSub,
@ -539,7 +625,6 @@ public class PSDImageReader extends ImageReaderBase {
imageInput.readFully(pRow, 0, pChannelWidth); imageInput.readFully(pRow, 0, pChannelWidth);
} }
// TODO: If banded and not sub sampling/cmyk, we could just copy using System.arraycopy
// TODO: Destination offset...?? // TODO: Destination offset...??
// Copy line sub sampled into real data // Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset; int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
@ -652,7 +737,6 @@ public class PSDImageReader extends ImageReaderBase {
// TODO: Probably faster to do this in line.. // TODO: Probably faster to do this in line..
if (pBuffer.getNumBanks() > 1) { if (pBuffer.getNumBanks() > 1) {
for (int y = 0; y < pHeight; y++) { for (int y = 0; y < pHeight; y++) {
for (int x = 0; x < pWidth; x++) { for (int x = 0; x < pWidth; x++) {
int offset = (x + y * pWidth); int offset = (x + y * pWidth);
@ -772,10 +856,13 @@ public class PSDImageReader extends ImageReaderBase {
// TODO: Obey ignoreMetadata // TODO: Obey ignoreMetadata
private void readLayerAndMaskInfo(final boolean pParseData) throws IOException { private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
// TODO: Make sure we are positioned correctly // TODO: Make sure we are positioned correctly
// TODO: Avoid unnecessary stream repositioning
long length = imageInput.readUnsignedInt(); long length = imageInput.readUnsignedInt();
if (pParseData && length > 0) { if (pParseData && length > 0) {
long pos = imageInput.getStreamPosition(); long pos = imageInput.getStreamPosition();
long read;
if (metadata.layerInfo == null) {
long layerInfoLength = imageInput.readUnsignedInt(); long layerInfoLength = imageInput.readUnsignedInt();
/* /*
@ -791,36 +878,22 @@ public class PSDImageReader extends ImageReaderBase {
layerInfos[i] = new PSDLayerInfo(imageInput); layerInfos[i] = new PSDLayerInfo(imageInput);
} }
metadata.layerInfo = Arrays.asList(layerInfos); metadata.layerInfo = Arrays.asList(layerInfos);
metadata.layersStart = imageInput.getStreamPosition();
// TODO: Clean-up read = imageInput.getStreamPosition() - pos;
imageInput.mark();
ImageTypeSpecifier raw = getRawImageTypeInternal(0);
ImageTypeSpecifier imageType = getImageTypes(0).next();
imageInput.reset();
for (PSDLayerInfo layerInfo : layerInfos) {
// TODO: If not explicitly needed, skip layers...
BufferedImage layer = readLayerData(layerInfo, raw, imageType);
// TODO: Don't show! Store in meta data somehow...
// if (layer != null) {
// showIt(layer, layerInfo.layerName + " " + layerInfo.blendMode.toString());
// }
}
long read = imageInput.getStreamPosition() - pos;
long diff = layerInfoLength - (read - 4); // - 4 for the layerInfoLength field itself long diff = layerInfoLength - (read - 4); // - 4 for the layerInfoLength field itself
// System.out.println("diff: " + diff); // System.out.println("diff: " + diff);
imageInput.skipBytes(diff); imageInput.skipBytes(diff);
// TODO: Global LayerMaskInfo (18 bytes or more..?) // TODO: 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 layerMaskInfoLength = imageInput.readUnsignedInt(); long layerMaskInfoLength = imageInput.readUnsignedInt();
// System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength); // System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength);
if (layerMaskInfoLength > 0) { if (layerMaskInfoLength > 0) {
metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput); metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput);
// System.out.println("globalLayerMask: " + globalLayerMask); // System.err.println("globalLayerMask: " + metadata.globalLayerMask);
}
} }
read = imageInput.getStreamPosition() - pos; read = imageInput.getStreamPosition() - pos;
@ -835,15 +908,28 @@ public class PSDImageReader extends ImageReaderBase {
} }
} }
private BufferedImage readLayerData(final PSDLayerInfo pLayerInfo, final ImageTypeSpecifier pRawType, final ImageTypeSpecifier pImageType) throws IOException { // private BufferedImage readLayerData(int layerIndex, final PSDLayerInfo pLayerInfo, final ImageTypeSpecifier pRawType, final ImageTypeSpecifier pImageType, ImageReadParam param) throws IOException {
final int width = pLayerInfo.right - pLayerInfo.left; private BufferedImage readLayerData(final int layerIndex, final ImageReadParam param) throws IOException {
final int height = pLayerInfo.bottom - pLayerInfo.top; final int width = getLayerWidth(layerIndex);
final int height = getLayerHeight(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 = getImageTypeForLayer(pImageType, pLayerInfo); ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex);
// TODO: Find a better way of handling layers of size 0
// - Return null? Return a BufferedImage subclass that has no data (0 x 0)?
// Create image (or dummy, if h/w are <= 0) // Create image (or dummy, if h/w are <= 0)
BufferedImage layer = imageType.createBufferedImage(Math.max(1, width), Math.max(1, height)); // BufferedImage layer = imageType.createBufferedImage(Math.max(1, width), Math.max(1, height));
if (width <= 0 || height <= 0) {
return null;
}
BufferedImage layer = getDestination(param, getImageTypes(layerIndex + 1), Math.max(1, width), Math.max(1, height));
imageInput.seek(findLayerStartPos(layerIndex));
// Source/destination area // Source/destination area
Rectangle area = new Rectangle(width, height); Rectangle area = new Rectangle(width, height);
@ -856,14 +942,14 @@ public class PSDImageReader extends ImageReaderBase {
final ColorModel destCM = layer.getColorModel(); final ColorModel destCM = layer.getColorModel();
// TODO: This raster is 3-5 times longer than needed, depending on number of channels... // TODO: This raster is 3-5 times longer than needed, depending on number of channels...
ColorModel sourceCM = pRawType.getColorModel(); ColorModel sourceCM = imageType.getColorModel();
final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null; final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
// final int channels = rowRaster.getNumBands(); // final int channels = rowRaster.getNumBands();
final boolean banded = raster.getDataBuffer().getNumBanks() > 1; final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
final int interleavedBands = banded ? 1 : raster.getNumBands(); final int interleavedBands = banded ? 1 : raster.getNumBands();
for (PSDChannelInfo channelInfo : pLayerInfo.channelInfo) { for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
int compression = imageInput.readUnsignedShort(); int compression = imageInput.readUnsignedShort();
// Skip layer if we can't read it // Skip layer if we can't read it
@ -875,7 +961,7 @@ public class PSDImageReader extends ImageReaderBase {
else { else {
// 0 = red, 1 = green, etc // 0 = red, 1 = green, etc
// -1 = transparency mask; -2 = user supplied layer mask // -1 = transparency mask; -2 = user supplied layer mask
int c = channelInfo.channelId == -1 ? pLayerInfo.channelInfo.length - 1 : channelInfo.channelId; int c = channelInfo.channelId == -1 ? layerInfo.channelInfo.length - 1 : channelInfo.channelId;
// NOTE: For layers, byte counts are written per channel, while for the composite data // NOTE: For layers, byte counts are written per channel, while for the composite data
// byte counts are written for all channels before the image data. // byte counts are written for all channels before the image data.
@ -890,7 +976,7 @@ public class PSDImageReader extends ImageReaderBase {
// If RLE, the the image data starts with the byte counts // If RLE, the the image data starts with the byte counts
// for all the scan lines in the channel (LayerBottom-LayerTop), with // for all the scan lines in the channel (LayerBottom-LayerTop), with
// each count stored as a two*byte value. // each count stored as a two*byte value.
byteCounts = new int[pLayerInfo.bottom - pLayerInfo.top]; byteCounts = new int[layerInfo.bottom - layerInfo.top];
for (int i = 0; i < byteCounts.length; i++) { for (int i = 0; i < byteCounts.length; i++) {
byteCounts[i] = imageInput.readUnsignedShort(); byteCounts[i] = imageInput.readUnsignedShort();
} }
@ -908,24 +994,14 @@ public 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();
// DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer();
// byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData();
read1bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE); read1bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE);
break; break;
case 8: case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
// DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer();
// byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData();
// read8bitChannel(c, imageType.getNumBands(), data8, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
break; break;
case 16: case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
// DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer();
// short[] data16 = banded ? buffer16.getData(c) : buffer16.getData();
read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
break; break;
default: default:
@ -941,49 +1017,64 @@ public class PSDImageReader extends ImageReaderBase {
return layer; return layer;
} }
private ImageTypeSpecifier getImageTypeForLayer(final ImageTypeSpecifier pOriginal, final PSDLayerInfo pLayerInfo) { private ImageTypeSpecifier getRawImageTypeForLayer(final int layerIndex) throws IOException {
ImageTypeSpecifier compositeType = getRawImageTypeForCompositeLayer();
PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
// If layer has more channels than composite data, it's normally extra alpha... // If layer has more channels than composite data, it's normally extra alpha...
if (pLayerInfo.channelInfo.length > pOriginal.getNumBands()) { if (layerInfo.channelInfo.length > compositeType.getNumBands()) {
// ...but, it could also be just the user mask... // ...but, it could also be just the user mask...
boolean userMask = false; boolean userMask = false;
for (PSDChannelInfo channelInfo : pLayerInfo.channelInfo) { for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
if (channelInfo.channelId == -2) { if (channelInfo.channelId == -2) {
userMask = true; userMask = true;
break; break;
} }
} }
int newBandNum = pLayerInfo.channelInfo.length - (userMask ? 1 : 0); int newBandNum = layerInfo.channelInfo.length - (userMask ? 1 : 0);
// If there really is more channels, then create new imageTypeSpec // If there really is more channels, then create new imageTypeSpec
if (newBandNum > pOriginal.getNumBands()) { if (newBandNum > compositeType.getNumBands()) {
int[] offs = new int[newBandNum]; int[] indices = new int[newBandNum];
for (int i = 0, offsLength = offs.length; i < offsLength; i++) { for (int i = 0, indicesLength = indices.length; i < indicesLength; i++) {
offs[i] = offsLength - i; indices[i] = indicesLength - i;
} }
return ImageTypeSpecifier.createInterleaved(pOriginal.getColorModel().getColorSpace(), offs, pOriginal.getSampleModel().getDataType(), true, false); int[] offs = new int[newBandNum];
for (int i = 0, offsLength = offs.length; i < offsLength; i++) {
offs[i] = 0;
}
return ImageTypeSpecifier.createBanded(compositeType.getColorModel().getColorSpace(), indices, offs, compositeType.getSampleModel().getDataType(), true, false);
} }
} }
return pOriginal;
return compositeType;
} }
/// Layer support /// Layer support
@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?
readHeader();
readImageResources(false);
readLayerAndMaskInfo(true); // TODO: Consider quicker reading of just the number of layers.
return metadata.layerInfo != null ? metadata.layerInfo.size() + 1 : 1; // TODO: Only plus one, if "has real merged data"?
}
// TODO: For now, leave as Metadata // TODO: For now, leave as Metadata
/* /*
int getNumLayers(int pImageIndex) throws IOException;
boolean hasLayers(int pImageIndex) throws IOException;
BufferedImage readLayer(int pImageIndex, int pLayerIndex, ImageReadParam pParam) throws IOException;
int getLayerWidth(int pImageIndex, int pLayerIndex) throws IOException;
int getLayerHeight(int pImageIndex, int pLayerIndex) throws IOException;
// ? // ?
Point getLayerOffset(int pImageIndex, int pLayerIndex) throws IOException; Point getOffset(int pImageIndex) throws IOException;
// Return 0, 0 for index 0, otherwise use layer offset
*/ */
@ -1087,26 +1178,29 @@ public class PSDImageReader extends ImageReaderBase {
// TODO: Thumbnail progress listeners... // TODO: Thumbnail progress listeners...
PSDThumbnail thumbnail = getThumbnailResource(imageIndex, thumbnailIndex); PSDThumbnail thumbnail = getThumbnailResource(imageIndex, thumbnailIndex);
// TODO: Defer decoding
// TODO: It's possible to attach listeners to the ImageIO reader delegate... But do we really care? // TODO: It's possible to attach listeners to the ImageIO reader delegate... But do we really care?
processThumbnailStarted(imageIndex, thumbnailIndex); processThumbnailStarted(imageIndex, thumbnailIndex);
processThumbnailProgress(0);
BufferedImage image = thumbnail.getThumbnail();
processThumbnailProgress(100);
processThumbnailComplete(); processThumbnailComplete();
// TODO: Returning a cached mutable thumbnail is not really safe... return image;
return thumbnail.getThumbnail();
} }
/// Functional testing /// Functional testing
public static void main(final String[] pArgs) throws IOException { public static void main(final String[] pArgs) throws IOException {
int subsampleFactor = 1; int subsampleFactor = 1;
Rectangle sourceRegion = null; Rectangle sourceRegion = null;
boolean readLayers = false;
boolean readThumbnails = false;
int idx = 0; int idx = 0;
while (pArgs[idx].charAt(0) == '-') { while (pArgs[idx].charAt(0) == '-') {
if (pArgs[idx].equals("-s")) { if (pArgs[idx].equals("-s") || pArgs[idx].equals("--subsampling")) {
subsampleFactor = Integer.parseInt(pArgs[++idx]); subsampleFactor = Integer.parseInt(pArgs[++idx]);
} }
else if (pArgs[idx].equals("-r")) { else if (pArgs[idx].equals("-r") || pArgs[idx].equals("--sourceregion")) {
int xw = Integer.parseInt(pArgs[++idx]); int xw = Integer.parseInt(pArgs[++idx]);
int yh = Integer.parseInt(pArgs[++idx]); int yh = Integer.parseInt(pArgs[++idx]);
@ -1126,6 +1220,12 @@ public class PSDImageReader extends ImageReaderBase {
System.out.println("sourceRegion: " + sourceRegion); System.out.println("sourceRegion: " + sourceRegion);
} }
else if (pArgs[idx].equals("-l") || pArgs[idx].equals("--layers")) {
readLayers = true;
}
else if (pArgs[idx].equals("-t") || pArgs[idx].equals("--thumbnails")) {
readThumbnails = true;
}
else { else {
System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] <image file>"); System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] <image file>");
System.exit(1); System.exit(1);
@ -1158,7 +1258,6 @@ public class PSDImageReader extends ImageReaderBase {
node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
serializer.setIndentation(" ");
serializer.serialize(node, true); serializer.serialize(node, true);
System.out.println(); System.out.println();
@ -1166,7 +1265,7 @@ public class PSDImageReader extends ImageReaderBase {
// serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); // serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
serializer.serialize(node, true); serializer.serialize(node, true);
if (imageReader.hasThumbnails(0)) { if (readThumbnails && imageReader.hasThumbnails(0)) {
int thumbnails = imageReader.getNumThumbnails(0); int thumbnails = imageReader.getNumThumbnails(0);
for (int i = 0; i < thumbnails; i++) { for (int i = 0; i < thumbnails; i++) {
showIt(imageReader.readThumbnail(0, i), String.format("Thumbnail %d", i)); showIt(imageReader.readThumbnail(0, i), String.format("Thumbnail %d", i));
@ -1188,22 +1287,34 @@ public class PSDImageReader extends ImageReaderBase {
// param.setDestinationType(imageReader.getRawImageType(0)); // param.setDestinationType(imageReader.getRawImageType(0));
BufferedImage image = imageReader.read(0, param); BufferedImage image = imageReader.read(0, param);
System.out.println("time: " + (System.currentTimeMillis() - start)); System.out.println("read time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image); System.out.println("image: " + image);
if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) { if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
try { try {
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null); ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
image = op.filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR_PRE)); GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
image = op.filter(image, gc.createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency()));
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
image = ImageUtil.accelerate(image); image = ImageUtil.accelerate(image);
} }
System.out.println("time: " + (System.currentTimeMillis() - start)); System.out.println("conversion time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image); System.out.println("image: " + image);
} }
showIt(image, file.getName()); showIt(image, file.getName());
if (readLayers) {
int images = imageReader.getNumImages(true);
for (int i = 1; i < images; i++) {
start = System.currentTimeMillis();
BufferedImage layer = imageReader.read(i);
System.out.println("layer read time: " + (System.currentTimeMillis() - start));
System.err.println("layer: " + layer);
showIt(layer, "layer " + i);
}
}
} }
} }

View File

@ -10,6 +10,7 @@ import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel; import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -33,6 +34,7 @@ public final class PSDMetadata extends AbstractMetadata {
List<PSDImageResource> imageResources; List<PSDImageResource> imageResources;
PSDGlobalLayerMask globalLayerMask; PSDGlobalLayerMask globalLayerMask;
List<PSDLayerInfo> layerInfo; List<PSDLayerInfo> layerInfo;
long layersStart;
static final String[] COLOR_MODES = { static final String[] COLOR_MODES = {
"MONOCHROME", "GRAYSCALE", "INDEXED", "RGB", "CMYK", null, null, "MULTICHANNEL", "DUOTONE", "LAB" "MONOCHROME", "GRAYSCALE", "INDEXED", "RGB", "CMYK", null, null, "MULTICHANNEL", "DUOTONE", "LAB"
@ -240,6 +242,7 @@ public final class PSDMetadata extends AbstractMetadata {
node.setAttribute("fileVersion", String.valueOf(information.fileVersion)); node.setAttribute("fileVersion", String.valueOf(information.fileVersion));
} }
else if (imageResource instanceof PSDThumbnail) { else if (imageResource instanceof PSDThumbnail) {
try {
// TODO: Revise/rethink this... // TODO: Revise/rethink this...
PSDThumbnail thumbnail = (PSDThumbnail) imageResource; PSDThumbnail thumbnail = (PSDThumbnail) imageResource;
@ -247,6 +250,11 @@ public final class PSDMetadata extends AbstractMetadata {
// TODO: Thumbnail attributes + access to data, to avoid JPEG re-compression problems // TODO: Thumbnail attributes + access to data, to avoid JPEG re-compression problems
node.setUserObject(thumbnail.getThumbnail()); node.setUserObject(thumbnail.getThumbnail());
} }
catch (IOException e) {
// TODO: Warning
continue;
}
}
else if (imageResource instanceof PSDIPTCData) { else if (imageResource instanceof PSDIPTCData) {
// TODO: Revise/rethink this... // TODO: Revise/rethink this...
PSDIPTCData iptc = (PSDIPTCData) imageResource; PSDIPTCData iptc = (PSDIPTCData) imageResource;
@ -662,7 +670,7 @@ public final class PSDMetadata extends AbstractMetadata {
} }
else if (textResource instanceof PSDXMPData) { else if (textResource instanceof PSDXMPData) {
// TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF? // TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF?
// TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync... // TODO: Use XMP IPTC/EXIF/TIFF NativeDigest field to validate if the values are in sync..?
PSDXMPData xmp = (PSDXMPData) textResource; PSDXMPData xmp = (PSDXMPData) textResource;
} }
} }

View File

@ -1,11 +1,12 @@
package com.twelvemonkeys.imageio.plugins.psd; package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage; import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
/** /**
@ -16,9 +17,11 @@ import java.io.IOException;
* @version $Id: PSDThumbnail.java,v 1.0 Jul 29, 2009 4:41:06 PM haraldk Exp$ * @version $Id: PSDThumbnail.java,v 1.0 Jul 29, 2009 4:41:06 PM haraldk Exp$
*/ */
class PSDThumbnail extends PSDImageResource { class PSDThumbnail extends PSDImageResource {
private BufferedImage thumbnail; private int format;
private int width; private int width;
private int height; private int height;
private int widthBytes;
private byte[] data;
public PSDThumbnail(final short pId, final ImageInputStream pInput) throws IOException { public PSDThumbnail(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput); super(pId, pInput);
@ -37,25 +40,14 @@ class PSDThumbnail extends PSDImageResource {
*/ */
@Override @Override
protected void readData(final ImageInputStream pInput) throws IOException { protected void readData(final ImageInputStream pInput) throws IOException {
// TODO: Support for RAW RGB (format == 0): Extract RAW reader from PICT RAW QuickTime decompressor format = pInput.readInt();
int format = pInput.readInt();
switch (format) {
case 0:
// RAW RGB
throw new IIOException("RAW RGB format thumbnail not supported yet");
case 1:
// JPEG
break;
default:
throw new IIOException(String.format("Unsupported thumbnail format (%s) in PSD document", format));
}
width = pInput.readInt(); width = pInput.readInt();
height = pInput.readInt(); height = pInput.readInt();
// This data isn't really useful, unless we're dealing with raw bytes // This data isn't really useful, unless we're dealing with raw bytes
int widthBytes = pInput.readInt(); widthBytes = pInput.readInt();
int totalSize = pInput.readInt(); int totalSize = pInput.readInt(); // Hmm.. Is this really useful at all?
// Consistency check // Consistency check
int sizeCompressed = pInput.readInt(); int sizeCompressed = pInput.readInt();
@ -70,9 +62,21 @@ class PSDThumbnail extends PSDImageResource {
// TODO: Warning/Exception // TODO: Warning/Exception
} }
// TODO: Defer decoding until getThumbnail? data = new byte[sizeCompressed];
// TODO: Support BGR if id == RES_THUMBNAIL_PS4? Or is that already supported in the JPEG? pInput.readFully(data);
thumbnail = ImageIO.read(IIOUtil.createStreamAdapter(pInput, sizeCompressed)); }
BufferedImage imageFromRawData(int width, int height, int scanLine, byte[] data) {
DataBuffer buffer = new DataBufferByte(data, data.length);
WritableRaster raster = Raster.createInterleavedRaster(
buffer, width, height,
scanLine, 3,
new int[]{0, 1, 2},
null
);
ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
} }
public final int getWidth() { public final int getWidth() {
@ -83,15 +87,40 @@ class PSDThumbnail extends PSDImageResource {
return height; return height;
} }
public final BufferedImage getThumbnail() { public final BufferedImage getThumbnail() throws IOException {
return thumbnail; switch (format) {
case 0:
// RAW RGB
return imageFromRawData(width, height, widthBytes, data.clone()); // Clone data, as image is mutable
case 1:
// JPEG
// TODO: Support BGR if id == RES_THUMBNAIL_PS4? Or is that already supported in the JPEG reader?
return ImageIO.read(new ByteArrayInputStream(data));
default:
throw new IIOException(String.format("Unsupported thumbnail format (%s) in PSD document", format));
}
} }
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = toStringBuilder(); StringBuilder builder = toStringBuilder();
builder.append(", ").append(thumbnail); builder.append(", format: ");
switch (format) {
case 0:
// RAW RGB
builder.append("RAW RGB");
break;
case 1:
// JPEG
builder.append("JPEG");
break;
default:
builder.append("Unknown");
break;
}
builder.append(", size: ").append(data != null ? data.length : -1);
builder.append("]"); builder.append("]");

View File

@ -2,12 +2,16 @@ package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.io.IOException; import java.io.IOException;
@ -133,8 +137,10 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase<PSDImage
imageReader.setInput(getTestData().get(0).getInputStream()); imageReader.setInput(getTestData().get(0).getInputStream());
int numImages = imageReader.getNumImages(true);
try { try {
imageReader.getNumThumbnails(2); imageReader.getNumThumbnails(numImages + 1);
fail("Expected IndexOutOfBoundsException"); fail("Expected IndexOutOfBoundsException");
} }
catch (IndexOutOfBoundsException expected) { catch (IndexOutOfBoundsException expected) {
@ -159,7 +165,7 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase<PSDImage
} }
try { try {
imageReader.readThumbnail(99, 42); imageReader.readThumbnail(numImages + 99, 42);
fail("Expected IndexOutOfBoundsException"); fail("Expected IndexOutOfBoundsException");
} }
catch (IndexOutOfBoundsException expected) { catch (IndexOutOfBoundsException expected) {
@ -212,4 +218,119 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase<PSDImage
assertEquals("started", sequnce.get(0)); assertEquals("started", sequnce.get(0));
assertEquals("complete", sequnce.get(1)); assertEquals("complete", sequnce.get(1));
} }
@Test
public void testReadLayers() throws IOException {
PSDImageReader imageReader = createReader();
imageReader.setInput(getTestData().get(3).getInputStream());
int numImages = imageReader.getNumImages(true);
assertEquals(3, numImages);
for (int i = 0; i < numImages; i++) {
BufferedImage image = imageReader.read(i);
assertNotNull(image);
// Make sure layers are correct size
assertEquals(image.getWidth(), imageReader.getWidth(i));
assertEquals(image.getHeight(), imageReader.getHeight(i));
}
}
@Test
public void testImageTypesLayers() throws IOException {
PSDImageReader imageReader = createReader();
imageReader.setInput(getTestData().get(3).getInputStream());
int numImages = imageReader.getNumImages(true);
for (int i = 0; i < numImages; i++) {
ImageTypeSpecifier rawType = imageReader.getRawImageType(i);
// System.err.println("rawType: " + rawType);
assertNotNull(rawType);
Iterator<ImageTypeSpecifier> types = imageReader.getImageTypes(i);
assertNotNull(types);
assertTrue(types.hasNext());
boolean found = false;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
// System.err.println("type: " + type);
if (!found && (rawType == type || rawType.equals(type))) {
found = true;
}
}
assertTrue("RAW image type not in type iterator", found);
}
}
@Test
public void testReadLayersExplicitType() throws IOException {
PSDImageReader imageReader = createReader();
imageReader.setInput(getTestData().get(3).getInputStream());
int numImages = imageReader.getNumImages(true);
for (int i = 0; i < numImages; i++) {
Iterator<ImageTypeSpecifier> types = imageReader.getImageTypes(i);
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
ImageReadParam param = imageReader.getDefaultReadParam();
param.setDestinationType(type);
BufferedImage image = imageReader.read(i, param);
assertEquals(type.getBufferedImageType(), image.getType());
if (type.getBufferedImageType() == 0) {
// TODO: If type.getBIT == 0, test more
// Compatible color model
assertEquals(type.getNumComponents(), image.getColorModel().getNumComponents());
// Same color space
assertEquals(type.getColorModel().getColorSpace(), image.getColorModel().getColorSpace());
// Same number of samples
assertEquals(type.getNumBands(), image.getSampleModel().getNumBands());
// Same number of bits/sample
for (int j = 0; j < type.getNumBands(); j++) {
assertEquals(type.getBitsPerBand(j), image.getSampleModel().getSampleSize(j));
}
}
}
}
}
@Test
public void testReadLayersExplicitDestination() throws IOException {
PSDImageReader imageReader = createReader();
imageReader.setInput(getTestData().get(3).getInputStream());
int numImages = imageReader.getNumImages(true);
for (int i = 0; i < numImages; i++) {
Iterator<ImageTypeSpecifier> types = imageReader.getImageTypes(i);
int width = imageReader.getWidth(i);
int height = imageReader.getHeight(i);
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
ImageReadParam param = imageReader.getDefaultReadParam();
BufferedImage destination = type.createBufferedImage(width, height);
param.setDestination(destination);
BufferedImage image = imageReader.read(i, param);
assertSame(destination, image);
}
}
}
} }