mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
#384 RGB PSB files with non-transparency alpha channels now rendered correctly
This commit is contained in:
parent
f14159de57
commit
4a1eb4b376
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
else if (header.channels == 2 && header.bits == 8) {
|
return metadata.hasAlpha() && header.channels > 1
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_BYTE, true, false);
|
? 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);
|
||||||
else if (header.channels == 1 && header.bits == 16) {
|
case 16:
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_USHORT, false, false);
|
return metadata.hasAlpha() && header.channels > 1
|
||||||
}
|
? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_USHORT, true, false)
|
||||||
else if (header.channels == 2 && header.bits == 16) {
|
: ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_USHORT, false, false);
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_USHORT, true, false);
|
case 32:
|
||||||
}
|
return metadata.hasAlpha() && header.channels > 1
|
||||||
else if (header.channels == 1 && header.bits == 32) {
|
? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_INT, true, false)
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_INT, false, false);
|
: 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:
|
||||||
else if (header.channels >= 4 && header.bits == 8) {
|
return metadata.hasAlpha() && header.channels > 3
|
||||||
return 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, 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);
|
||||||
else if (header.channels == 3 && header.bits == 16) {
|
case 16:
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
|
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)
|
||||||
else if (header.channels >= 4 && header.bits == 16) {
|
: ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false);
|
case 32:
|
||||||
}
|
return metadata.hasAlpha() && header.channels > 3
|
||||||
else if (header.channels == 3 && header.bits == 32) {
|
? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false)
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, DataBuffer.TYPE_INT, false, 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 == 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:
|
||||||
else if (header.channels >= 5 && header.bits == 8) {
|
return metadata.hasAlpha() && header.channels > 4
|
||||||
return 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, 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);
|
||||||
else if (header.channels == 4 && header.bits == 16) {
|
case 16:
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
|
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)
|
||||||
else if (header.channels >= 5 && header.bits == 16) {
|
: ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false);
|
case 32:
|
||||||
}
|
return metadata.hasAlpha() && header.channels > 4
|
||||||
else if (header.channels == 4 && header.bits == 32) {
|
? ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false)
|
||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, false, 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 == 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,8 +501,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
// Color conversion from embedded color space, to destination color space
|
// Color conversion from embedded color space, to destination color space
|
||||||
WritableRaster alphaMaskedRaster = destinationCM.hasAlpha()
|
WritableRaster alphaMaskedRaster = destinationCM.hasAlpha()
|
||||||
? raster.createWritableChild(0, 0, raster.getWidth(), raster.getHeight(),
|
? raster.createWritableChild(0, 0, raster.getWidth(), raster.getHeight(),
|
||||||
raster.getMinX(), raster.getMinY(),
|
raster.getMinX(), raster.getMinY(),
|
||||||
createBandList(sourceCM.getColorSpace().getNumComponents()))
|
createBandList(sourceCM.getColorSpace().getNumComponents()))
|
||||||
: raster;
|
: raster;
|
||||||
|
|
||||||
new ColorConvertOp(sourceCM.getColorSpace(), destinationCM.getColorSpace(), null)
|
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 {
|
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;
|
|
||||||
|
|
||||||
while (imageInput.getStreamPosition() < expectedEnd) {
|
while (imageInput.getStreamPosition() < expectedEnd) {
|
||||||
PSDImageResource resource = PSDImageResource.read(imageInput);
|
PSDImageResource resource = PSDImageResource.read(imageInput);
|
||||||
metadata.imageResources.add(resource);
|
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.. ;-)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We should now be able to flush input
|
if (DEBUG) {
|
||||||
// imageInput.flushBefore(metadata.imageResourcesStart + imageResourcesLength + 4);
|
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
|
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,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
|
// 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) {
|
||||||
// "Layer count. If it is a negative number, its absolute value is the number of
|
// "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
|
// 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
|
|
||||||
? 8
|
|
||||||
: 4)); // - 4 for the layerInfoLength field itself
|
|
||||||
|
|
||||||
imageInput.skipBytes(diff);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
metadata.layerInfo = Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global LayerMaskInfo (18 bytes or more..?)
|
long read = imageInput.getStreamPosition() - pos;
|
||||||
// 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad)
|
long diff = layerInfoLength - (read - (header.largeFormat ? 8 : 4)); // - 8 or 4 for the layerInfoLength field itself
|
||||||
long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB!
|
|
||||||
|
|
||||||
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);
|
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.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||||
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||||
|
|
||||||
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 {
|
||||||
@ -1080,17 +1078,17 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
case 8:
|
case 8:
|
||||||
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub,
|
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;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub,
|
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;
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||||
read32bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row32, area, area, xsub,
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
|
throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
|
||||||
@ -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
|
||||||
@ -1374,7 +1359,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
param.setSourceSubsampling(subsampleFactor, subsampleFactor, 0, 0);
|
param.setSourceSubsampling(subsampleFactor, subsampleFactor, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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("read time: " + (System.currentTimeMillis() - start));
|
System.out.println("read time: " + (System.currentTimeMillis() - start));
|
||||||
|
@ -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;
|
||||||
@ -170,7 +171,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
node = new IIOMetadataNode("DisplayInfo");
|
node = new IIOMetadataNode("DisplayInfo");
|
||||||
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.colorSpace]);
|
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.colorSpace]);
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
for (short color : displayInfo.colors) {
|
for (short color : displayInfo.colors) {
|
||||||
@ -307,7 +308,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
|||||||
}
|
}
|
||||||
else if (imageResource instanceof PSDXMPData) {
|
else if (imageResource instanceof PSDXMPData) {
|
||||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
// 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;
|
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||||
|
|
||||||
node = new IIOMetadataNode("DirectoryResource");
|
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...
|
// 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");
|
||||||
@ -643,7 +644,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static float asMM(final short unit, final float resolution) {
|
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;
|
return (unit == 1 ? 25.4f : 10) / resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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>() {
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
BIN
imageio/imageio-psd/src/test/resources/psb/rgb-multichannel-no-transparency.psb
Executable file
BIN
imageio/imageio-psd/src/test/resources/psb/rgb-multichannel-no-transparency.psb
Executable file
Binary file not shown.
BIN
imageio/imageio-psd/src/test/resources/psd/rgb-multichannel-no-transparency.psd
Executable file
BIN
imageio/imageio-psd/src/test/resources/psd/rgb-multichannel-no-transparency.psd
Executable file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user