#675 PSD 16/32 bit layer support

(cherry picked from commit 48691139a30baca340d5a603207491a33f183943)
This commit is contained in:
Harald Kuhr 2022-06-10 10:14:41 +02:00
parent 3f74b2ddf3
commit b6c76d8566
12 changed files with 1594 additions and 309 deletions

View File

@ -0,0 +1,266 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.lang.Validate;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
/**
* A decoder for data converted using "horizontal differencing predictor".
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
*/
final class HorizontalDeDifferencingStream extends InputStream {
/// TODO: Create shared version with TIFF, or see if we can avoid some duplication?
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
private final int columns;
// NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1
private final int samplesPerPixel;
private final int bitsPerSample;
private final ReadableByteChannel channel;
private final ByteBuffer buffer;
public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
this.samplesPerPixel = Validate.isTrue(bitsPerSample >= 8 || samplesPerPixel == 1, samplesPerPixel, "Unsupported samples per pixel for < 8 bit samples: %s");
this.bitsPerSample = Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s");
channel = Channels.newChannel(Validate.notNull(stream, "stream"));
buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder);
buffer.flip();
}
static boolean isValidBPS(final int bitsPerSample) {
switch (bitsPerSample) {
case 1:
case 2:
case 4:
case 8:
case 16:
case 32:
case 64:
return true;
default:
return false;
}
}
@SuppressWarnings("StatementWithEmptyBody")
private boolean fetch() throws IOException {
buffer.clear();
// This *SHOULD* read an entire row of pixels (or nothing at all) into the buffer,
// otherwise we will throw EOFException below
while (channel.read(buffer) > 0);
if (buffer.position() > 0) {
if (buffer.hasRemaining()) {
throw new EOFException("Unexpected end of stream");
}
decodeRow();
buffer.flip();
return true;
}
else {
buffer.position(buffer.capacity());
return false;
}
}
private void decodeRow() {
// Un-apply horizontal predictor
byte original;
int sample = 0;
byte temp;
// Optimization:
// Access array directly for <= 8 bits per sample, as buffer does extra index bounds check for every
// put/get operation... (Measures to about 100 ms difference for 4000 x 3000 image)
final byte[] array = buffer.array();
switch (bitsPerSample) {
case 1:
for (int b = 0; b < (columns + 7) / 8; b++) {
original = array[b];
sample += (original >> 7) & 0x1;
temp = (byte) ((sample << 7) & 0x80);
sample += (original >> 6) & 0x1;
temp |= (byte) ((sample << 6) & 0x40);
sample += (original >> 5) & 0x1;
temp |= (byte) ((sample << 5) & 0x20);
sample += (original >> 4) & 0x1;
temp |= (byte) ((sample << 4) & 0x10);
sample += (original >> 3) & 0x1;
temp |= (byte) ((sample << 3) & 0x08);
sample += (original >> 2) & 0x1;
temp |= (byte) ((sample << 2) & 0x04);
sample += (original >> 1) & 0x1;
temp |= (byte) ((sample << 1) & 0x02);
sample += original & 0x1;
array[b] = (byte) (temp | sample & 0x1);
}
break;
case 2:
for (int b = 0; b < (columns + 3) / 4; b++) {
original = array[b];
sample += (original >> 6) & 0x3;
temp = (byte) ((sample << 6) & 0xc0);
sample += (original >> 4) & 0x3;
temp |= (byte) ((sample << 4) & 0x30);
sample += (original >> 2) & 0x3;
temp |= (byte) ((sample << 2) & 0x0c);
sample += original & 0x3;
array[b] = (byte) (temp | sample & 0x3);
}
break;
case 4:
for (int b = 0; b < (columns + 1) / 2; b++) {
original = array[b];
sample += (original >> 4) & 0xf;
temp = (byte) ((sample << 4) & 0xf0);
sample += original & 0x0f;
array[b] = (byte) (temp | sample & 0xf);
}
break;
case 8:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
array[off] = (byte) (array[off - samplesPerPixel] + array[off]);
}
}
break;
case 16:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putShort(2 * off, (short) (buffer.getShort(2 * (off - samplesPerPixel)) + buffer.getShort(2 * off)));
}
}
break;
case 32:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putInt(4 * off, buffer.getInt(4 * (off - samplesPerPixel)) + buffer.getInt(4 * off));
}
}
break;
case 64:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putLong(8 * off, buffer.getLong(8 * (off - samplesPerPixel)) + buffer.getLong(8 * off));
}
}
break;
default:
throw new AssertionError(String.format("Unsupported bits per sample value: %d", bitsPerSample));
}
}
@Override
public int read() throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
return buffer.get() & 0xff;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
int read = Math.min(buffer.remaining(), len);
buffer.get(b, off, read);
return read;
}
@Override
public long skip(long n) throws IOException {
if (n < 0) {
return 0;
}
if (!buffer.hasRemaining()) {
if (!fetch()) {
return 0; // SIC
}
}
int skipped = (int) Math.min(buffer.remaining(), n);
buffer.position(buffer.position() + skipped);
return skipped;
}
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
if (channel.isOpen()) {
channel.close();
}
}
}
}

View File

@ -31,10 +31,10 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* PSDDirectoryResource
@ -69,8 +69,16 @@ abstract class PSDDirectoryResource extends PSDImageResource {
public String toString() {
StringBuilder builder = toStringBuilder();
if (directory != null) {
builder.append(", ").append(directory);
builder.append("]");
}
else {
int length = Math.min(256, data.length);
String data = StringUtil.decode(this.data, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
String data = new String(this.data, 0, length, StandardCharsets.US_ASCII)
.replace('\uFFFD', '.')
.replaceAll("\\s+", " ")
.replaceAll("[^\\p{Print}]", ".");
builder.append(", data: \"").append(data);
if (length < this.data.length) {
@ -78,6 +86,7 @@ abstract class PSDDirectoryResource extends PSDImageResource {
}
builder.append("\"]");
}
return builder.toString();
}

View File

@ -85,19 +85,4 @@ final class PSDEXIF1Data extends PSDDirectoryResource {
output.writeInt((int) (afterExif - beforeExif));
output.seek(afterExif);
}
@Override
public String toString() {
Directory directory = getDirectory();
if (directory == null) {
return super.toString();
}
StringBuilder builder = toStringBuilder();
builder.append(", ").append(directory);
builder.append("]");
return builder.toString();
}
}

View File

@ -53,19 +53,4 @@ final class PSDIPTCData extends PSDDirectoryResource {
Directory parseDirectory() throws IOException {
return new IPTCReader().read(new ByteArrayImageInputStream(data));
}
@Override
public String toString() {
Directory directory = getDirectory();
if (directory == null) {
return super.toString();
}
StringBuilder builder = toStringBuilder();
builder.append(", ").append(directory);
builder.append("]");
return builder.toString();
}
}

View File

@ -43,15 +43,19 @@ import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.color.*;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.Set;
import java.util.Stack;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
/**
* ImageReader for Adobe Photoshop Document (PSD) format.
@ -62,7 +66,11 @@ import java.util.*;
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
*/
// TODO: Implement ImageIO meta data interface
// TODO: Rethink metadata impl.
// * We should probably move most of the current impl to stream metadata as it belongs to the document, not the individual layers
// * Make each layer's metadata contain correct data, name, sub-image type, position etc.
// * Retain some information in the merged image/layers?
// * Completely skip the non-pixel layers in the reader (no longer return null, that's just ugly)
// 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: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
@ -70,6 +78,7 @@ import java.util.*;
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
// Done: Allow reading the extra alpha channels (index after composite data)
// Done: Implement ImageIO meta data interface
public final class PSDImageReader extends ImageReaderBase {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.psd.debug"));
@ -390,21 +399,21 @@ public final class PSDImageReader extends ImageReaderBase {
int compression = imageInput.readShort();
metadata.compression = compression;
int[] byteCounts = null;
int[][] byteCounts = null;
switch (compression) {
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
case PSD.COMPRESSION_NONE:
break;
case PSD.COMPRESSION_RLE:
// NOTE: Byte counts will allow us to easily skip rows before AOI
byteCounts = new int[header.channels * header.height];
for (int i = 0; i < byteCounts.length; i++) {
byteCounts[i] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
byteCounts = new int[header.channels][header.height];
for (int c = 0; c < header.channels; c++) {
for (int y = 0; y < header.height; y++) {
byteCounts[c][y] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
}
}
break;
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here.. Look at TIFF prediction reading
throw new IIOException("PSD with ZIP compression not supported");
default:
throw new IIOException(
String.format(
@ -446,7 +455,7 @@ public final class PSDImageReader extends ImageReaderBase {
private void readImageData(final BufferedImage destination,
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int[] pByteCounts, final int pCompression) throws IOException {
final int[][] byteCounts, final int compression) throws IOException {
WritableRaster destRaster = destination.getRaster();
ColorModel destCM = destination.getColorModel();
@ -458,24 +467,25 @@ public final class PSDImageReader extends ImageReaderBase {
int interleavedBands = banded ? 1 : destRaster.getNumBands();
for (int c = 0; c < channels; c++) {
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, header.width, header.bits, byteCounts != null ? byteCounts[c] : null, -1)) {
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
read1bitChannel(stream, c, destRaster.getDataBuffer(), row1, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
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);
read8bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
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);
read16bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
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);
read32bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
default:
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
@ -485,6 +495,7 @@ public final class PSDImageReader extends ImageReaderBase {
break;
}
}
}
if (header.bits == 8) {
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
@ -529,222 +540,182 @@ public final class PSDImageReader extends ImageReaderBase {
processImageProgress(100f * channel / channelCount + 100f * y / (height * channelCount));
}
private void read32bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final int[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
private void read32bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final int[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 4 * pChannelWidth);
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readInt();
}
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
int value = pRow[pSource.x + x * pXSub];
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
int value = rowData[sourceRect.x + x * xSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = 0xffffffff - value;
}
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
data.setElem(banded ? band : 0, offset + x * bandCount, value);
}
}
else {
imageInput.skipBytes(length);
stream.skipBytes(4 * channelWidth);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
processImageProgressForChannel(channel, channelCount, y, channelHeight);
}
}
private void read16bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final short[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
private void read16bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final short[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 2 * pChannelWidth);
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readShort();
}
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
short value = pRow[pSource.x + x * pXSub];
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
short value = rowData[sourceRect.x + x * xSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = (short) (0xffff - value & 0xffff);
}
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
data.setElem(banded ? band : 0, offset + x * bandCount, value);
}
}
else {
imageInput.skipBytes(length);
stream.skipBytes(2 * channelWidth);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
processImageProgressForChannel(channel, channelCount, y, channelHeight);
}
}
private void read8bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
private void read8bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final byte[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth;
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pChannelWidth);
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
byte value = pRow[pSource.x + x * pXSub];
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
byte value = rowData[sourceRect.x + x * xSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = (byte) (0xff - value & 0xff);
}
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
data.setElem(banded ? band : 0, offset + x * bandCount, value);
}
}
else {
imageInput.skipBytes(length);
stream.skipBytes(channelWidth);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
processImageProgressForChannel(channel, channelCount, y, channelHeight);
}
}
@SuppressWarnings({"UnusedDeclaration"})
private void read1bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, boolean pRLECompressed) throws IOException {
private void read1bitChannel(final ImageInputStream stream,
final int channel,
final DataBuffer data,
final byte[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
// NOTE: 1 bit channels only occurs once
if (channel > 0) {
throw new IIOException("Multiple channels not supported for 1 bit data");
}
final int destWidth = (pDest.width + 7) / 8;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = pRLECompressed ? pRowByteCounts[y] : pChannelWidth;
final int destWidth = (destRect.width + 7) / 8;
final boolean banded = data.getNumBanks() > 1;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pRow.length);
}
}
else {
imageInput.readFully(pRow, 0, pRow.length);
}
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, rowData.length);
// TODO: Destination offset...??
int offset = (y - pSource.y) / pYSub * destWidth;
if (pXSub == 1 && pSource.x % 8 == 0) {
int offset = (y - sourceRect.y) / ySub * destWidth;
if (xSub == 1 && sourceRect.x % 8 == 0) {
// Fast normal case, no sub sampling
for (int i = 0; i < destWidth; i++) {
byte value = pRow[pSource.x / 8 + i * pXSub];
byte value = rowData[sourceRect.x / 8 + i * xSub];
// NOTE: Invert bits to match Java's default monochrome
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~value & 0xff));
data.setElem(banded ? channel : 0, offset + i, (byte) (~value & 0xff));
}
}
else {
// Copy line sub sampled into real data
final int maxX = pSource.x + pSource.width;
int x = pSource.x;
final int maxX = sourceRect.x + sourceRect.width;
int x = sourceRect.x;
for (int i = 0; i < destWidth; i++) {
byte result = 0;
@ -756,25 +727,25 @@ public final class PSDImageReader extends ImageReaderBase {
int destBitOff = 7 - j;
// Shift bit into place
result |= ((pRow[bytePos] & mask) >> sourceBitOff) << destBitOff;
result |= ((rowData[bytePos] & mask) >> sourceBitOff) << destBitOff;
x += pXSub;
x += xSub;
}
// NOTE: Invert bits to match Java's default monochrome
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~result & 0xff));
data.setElem(banded ? channel : 0, offset + i, (byte) (~result & 0xff));
}
}
}
else {
imageInput.skipBytes(length);
stream.skipBytes((channelWidth + 7) / 8);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
processImageProgressForChannel(channel, 1, y, channelHeight);
}
}
@ -920,13 +891,13 @@ public final class PSDImageReader extends ImageReaderBase {
// TODO: Flags or list of interesting resources to parse
// TODO: Obey ignoreMetadata
private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
private void readLayerAndMaskInfo(final boolean parseData) throws IOException {
readImageResources(false);
if (pParseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
if (parseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
imageInput.seek(metadata.layerAndMaskInfoStart);
long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
long layerAndMaskInfoLength = readLength(imageInput);
// NOTE: The spec says that if this section is empty, the length should be 0.
// Yet I have a PSB file that has size 12, and both contained lengths set to 0 (which
@ -936,7 +907,7 @@ public final class PSDImageReader extends ImageReaderBase {
if (layerAndMaskInfoLength > 0) {
long pos = imageInput.getStreamPosition();
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
long layerInfoLength = readLength(imageInput);
if (layerInfoLength > 0) {
// "Layer count. If it is a negative number, its absolute value is the number of
@ -945,7 +916,7 @@ public final class PSDImageReader extends ImageReaderBase {
int layerCount = imageInput.readShort();
metadata.layerCount = layerCount;
if (pParseData && metadata.layerInfo == null) {
if (metadata.layerInfo == null) {
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
metadata.layersStart = imageInput.getStreamPosition();
}
@ -955,16 +926,13 @@ public final class PSDImageReader extends ImageReaderBase {
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) {
if (parseData && metadata.globalLayerMask == null) {
metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength);
}
// TODO: Else skip?
@ -973,13 +941,52 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.globalLayerMask = PSDGlobalLayerMask.NULL_MASK;
}
// TODO: Parse "Additional layer information"
if (metadata.layerInfo == null) {
while (imageInput.getStreamPosition() + 12 < metadata.layerAndMaskInfoStart + layerAndMaskInfoLength) {
int resSig = imageInput.readInt();
if (resSig != PSD.RESOURCE_TYPE) {
processWarningOccurred(String.format("Bad resource alignment, expected: '8BIM' was '%s'", PSDUtil.intToStr(resSig)));
break;
}
int resId = imageInput.readInt();
long resLength = readLength(imageInput, resId); // In this section, resource lengths *vary* based on the resource...
// Calculate next offset, for some reason lengths are padded to 32 bit...
long nextOffset = imageInput.getStreamPosition() + 4 * ((resLength + 3) / 4);
if (DEBUG) {
System.out.println("resId: " + PSDUtil.intToStr(resId));
System.out.println("length = " + resLength);
System.out.printf("nextOffset = %d%n", nextOffset);
}
switch (resId) {
case PSD.Layr:
case PSD.Lr16:
case PSD.Lr32:
short layerCount = imageInput.readShort();
metadata.layerCount = layerCount;
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
metadata.layersStart = imageInput.getStreamPosition();
break;
default:
}
imageInput.seek(nextOffset);
}
}
if (parseData && metadata.layerInfo == null) {
// We have parsed but didn't find any layers
metadata.layerInfo = Collections.emptyList();
}
// TODO: We should now be able to flush input
// imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
if (pParseData && DEBUG) {
if (parseData && DEBUG) {
System.out.println("layerInfo: " + metadata.layerInfo);
System.out.println("globalLayerMask: " + (metadata.globalLayerMask != PSDGlobalLayerMask.NULL_MASK ? metadata.globalLayerMask : null));
}
@ -989,6 +996,39 @@ public final class PSDImageReader extends ImageReaderBase {
}
}
private long readLength(final ImageInputStream stream) throws IOException {
return header.largeFormat
? stream.readLong()
: stream.readUnsignedInt();
}
private long readLength(final ImageInputStream stream, final int resId) throws IOException {
// Only the following resources use long (64 bit) lengths:
// LMsk, Lr16, Lr32, Layr, Mt16, Mt32, Mtrn, Alph, FMsk, lnk2, FEid, FXid, PxSD
if (header.largeFormat) {
switch (resId) {
case PSD.LMsk:
case PSD.Lr16:
case PSD.Lr32:
case PSD.Layr:
case PSD.Mt16:
case PSD.Mt32:
case PSD.Mtrn:
case PSD.Alph:
case PSD.FMsk:
case PSD.lnk2:
case PSD.FEid:
case PSD.FXid:
case PSD.PxSD:
return stream.readLong();
default:
// Fall through to 32 bit length
}
}
return stream.readUnsignedInt();
}
private List<PSDLayerInfo> readLayerInfo(int layerCount) throws IOException {
PSDLayerInfo[] layerInfos = new PSDLayerInfo[layerCount];
@ -1052,21 +1092,22 @@ public final class PSDImageReader extends ImageReaderBase {
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
final int interleavedBands = banded ? 1 : raster.getNumBands();
// TODO: progress for layers!
processImageStarted(1 + layerIndex);
// TODO: Consider creating a method in PSDLayerInfo that can tell how many channels we really want to decode
for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
for (int channel = 0; channel < layerInfo.channelInfo.length; channel++) {
PSDChannelInfo channelInfo = layerInfo.channelInfo[channel];
int compression = imageInput.readUnsignedShort();
// Skip layer if we can't read it
// channelId
// -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present)
if (channelInfo.channelId < -1 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions!
// channelId -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present)
if (channelInfo.channelId < -1) {
processWarningOccurred(String.format("Skipping channel %s (%s)", channelInfo.channelId, channelInfo.channelId >= -3 ? "user supplied layer mask" : "unknown channel data"));
imageInput.skipBytes(channelInfo.length - 2);
}
else {
// 0 = red, 1 = green, etc
// -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)
int c = channelInfo.channelId == -1 ? rowRaster.getNumBands() - 1 : channelInfo.channelId;
} else {
// 0 = red, 1 = green, etc -1 = transparency mask
int band = channelInfo.channelId == -1 ? rowRaster.getNumBands() - 1 : channelInfo.channelId;
// 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.
@ -1076,45 +1117,43 @@ public final class PSDImageReader extends ImageReaderBase {
// 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
switch (compression) {
case PSD.COMPRESSION_NONE:
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
break;
case PSD.COMPRESSION_RLE:
// If RLE, the the image data starts with the byte counts
// If RLE, the image data starts with the byte counts
// for all the scan lines in the channel (LayerBottom-LayerTop), with
// each count stored as a two*byte (four for PSB) value.
// each count stored as a two-byte (four for PSB) value.
byteCounts = new int[layerInfo.bottom - layerInfo.top];
for (int i = 0; i < byteCounts.length; i++) {
byteCounts[i] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
}
break;
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
default:
// Explicitly skipped above
throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression));
}
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, width, header.bits, byteCounts, channelInfo.length - 2)) {
int bandOffset = banded ? 0 : interleavedBands - 1 - band;
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE);
read1bitChannel(stream, channel, raster.getDataBuffer(), row1, area, area, xsub, ysub, width, height);
break;
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
read8bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
read16bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
read32bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row32, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
read32bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row32, area, area, xsub, ysub, width, height);
break;
default:
throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
@ -1125,11 +1164,14 @@ public final class PSDImageReader extends ImageReaderBase {
}
}
}
}
if (!sourceCM.getColorSpace().equals(destCM.getColorSpace())) {
convertToDestinationCS(sourceCM, destCM, raster);
}
processImageComplete();
return layer;
}
@ -1177,6 +1219,7 @@ public final class PSDImageReader extends ImageReaderBase {
// But that makes no sense for a format (like PSD) that does not need to search, right?
readLayerAndMaskInfo(false);
// TODO: Do we really want to include the layers that doesn't have pixel data?
return metadata.getLayerCount() + 1; // TODO: Only plus one, if "has real merged data"?
}
@ -1311,10 +1354,13 @@ public final class PSDImageReader extends ImageReaderBase {
int idx = 0;
while (pArgs[idx].charAt(0) == '-') {
if (pArgs[idx].equals("-s") || pArgs[idx].equals("--subsampling")) {
switch (pArgs[idx]) {
case "-s":
case "--subsampling":
subsampleFactor = Integer.parseInt(pArgs[++idx]);
}
else if (pArgs[idx].equals("-r") || pArgs[idx].equals("--sourceregion")) {
break;
case "-r":
case "--sourceregion":
int xw = Integer.parseInt(pArgs[++idx]);
int yh = Integer.parseInt(pArgs[++idx]);
@ -1333,15 +1379,17 @@ public final class PSDImageReader extends ImageReaderBase {
}
System.out.println("sourceRegion: " + sourceRegion);
}
else if (pArgs[idx].equals("-l") || pArgs[idx].equals("--layers")) {
break;
case "-l":
case "--layers":
readLayers = true;
}
else if (pArgs[idx].equals("-t") || pArgs[idx].equals("--thumbnails")) {
break;
case "-t":
case "--thumbnails":
readThumbnails = true;
}
else {
System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] <image file>");
break;
default:
System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] [-t -l] <image file>");
System.exit(1);
}

View File

@ -41,7 +41,7 @@ import com.twelvemonkeys.util.FilterIterator;
import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel;
import java.awt.image.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
@ -101,6 +101,8 @@ public final class PSDMetadata extends AbstractMetadata {
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
}
// TODO: Allow creating correct metadata for layers too!
/// Native format support
@Override
@ -148,7 +150,7 @@ public final class PSDMetadata extends AbstractMetadata {
for (PSDImageResource imageResource : imageResources) {
// TODO: Always add name (if set) and id (as resourceId) to all nodes?
// Resource Id is useful for people with access to the PSD spec..
// Resource Id is useful for people with access to the PSD spec...
if (imageResource instanceof ICCProfile) {
ICCProfile profile = (ICCProfile) imageResource;
@ -675,6 +677,13 @@ public final class PSDMetadata extends AbstractMetadata {
formatVersion.setAttribute("value", header.largeFormat ? "2" : "1"); // PSD format version is always 1, PSB is 2
document_node.appendChild(formatVersion);
// TODO: For images other than image 0
// IIOMetadataNode subimageInterpretation = new IIOMetadataNode("SubimageInterpretation");
// subimageInterpretation.setAttribute("value", "CompositingLayer");
// document_node.appendChild(subimageInterpretation);
// TODO: Layer name?
// Get EXIF data if present
Iterator<PSDEXIF1Data> exif = getResources(PSDEXIF1Data.class);
if (exif.hasNext()) {

View File

@ -34,7 +34,7 @@ import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.*;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -77,7 +77,7 @@ final class PSDThumbnail extends PSDImageResource {
// This data isn't really useful, unless we're dealing with raw bytes
widthBytes = pInput.readInt();
int totalSize = pInput.readInt(); // Hmm.. Is this really useful at all?
int totalSize = pInput.readInt(); // Hmm... Is this really useful at all?
// Consistency check
int sizeCompressed = pInput.readInt();

View File

@ -30,16 +30,22 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.zip.ZipInputStream;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static java.nio.ByteOrder.BIG_ENDIAN;
/**
* PSDUtil
@ -89,19 +95,49 @@ final class PSDUtil {
return StringUtil.decode(bytes, 0, bytes.length, "UTF-16");
}
static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) {
return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new PackBitsDecoder()));
}
static DataInputStream createZipStream(final ImageInputStream pInput, long pLength) {
return new DataInputStream(new ZipInputStream(IIOUtil.createStreamAdapter(pInput, pLength)));
}
static DataInputStream createZipPredictorStream(final ImageInputStream pInput, long pLength) {
throw new UnsupportedOperationException("Method createZipPredictonStream not implemented");
}
public static float fixedPointToFloat(int pFP) {
return ((pFP & 0xffff0000) >> 16) + (pFP & 0xffff) / (float) 0xffff;
}
static ImageInputStream createDecompressorStream(final ImageInputStream stream, int compression, int columns, int bitsPerSample,
final int[] byteCounts, long compressedLength) throws IOException {
switch (compression) {
case PSD.COMPRESSION_NONE:
return new SubImageInputStream(stream, stream.length());
case PSD.COMPRESSION_RLE:
return new DirectImageInputStream(new SequenceInputStream(new LazyPackBitsStreamEnumeration(byteCounts, stream)));
case PSD.COMPRESSION_ZIP:
return new DirectImageInputStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength)));
case PSD.COMPRESSION_ZIP_PREDICTION:
return new DirectImageInputStream(new HorizontalDeDifferencingStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength)), columns, 1, bitsPerSample, BIG_ENDIAN));
default:
}
throw new IllegalArgumentException("Unknown PSD compression: " + compression);
}
private static class LazyPackBitsStreamEnumeration implements Enumeration<InputStream> {
private final ImageInputStream stream;
private final int[] byteCounts;
private int index;
public LazyPackBitsStreamEnumeration(int[] byteCounts, ImageInputStream stream) {
this.byteCounts = byteCounts;
this.stream = stream;
}
@Override
public boolean hasMoreElements() {
return index < byteCounts.length;
}
@Override
public InputStream nextElement() {
return new DecoderStream(createStreamAdapter(stream, byteCounts[index++]), new PackBitsDecoder());
}
}
}

View File

@ -0,0 +1,579 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.LittleEndianDataOutputStream;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* HorizontalDeDifferencingStreamTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HorizontalDeDifferencingStreamTest.java,v 1.0 13.03.13 12:46 haraldk Exp$
*/
public class HorizontalDeDifferencingStreamTest {
@Test
public void testRead1SPP1BPS() throws IOException {
// 1 sample per pixel, 1 bits per sample (mono/indexed)
byte[] data = {
(byte) 0x80, 0x00, 0x00,
0x71, 0x11, 0x44,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 24, 1, 1, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x5e, stream.read());
assertEquals(0x1e, stream.read());
assertEquals(0x78, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP2BPS() throws IOException {
// 1 sample per pixel, 2 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xc0, 0x00, 0x00, 0x00,
0x71, 0x11, 0x44, (byte) 0xcc,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 16, 1, 2, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x41, stream.read());
assertEquals(0x6b, stream.read());
assertEquals(0x05, stream.read());
assertEquals(0x0f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP4BPS() throws IOException {
// 1 sample per pixel, 4 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xf0, 0x00, 0x00, 0x00,
0x70, 0x11, 0x44, (byte) 0xcc,
0x00, 0x01, 0x10, (byte) 0xe0
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 8, 1, 4, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x77, stream.read());
assertEquals(0x89, stream.read());
assertEquals(0xd1, stream.read());
assertEquals(0xd9, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x01, stream.read());
assertEquals(0x22, stream.read());
assertEquals(0x00, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP8BPS() throws IOException {
// 1 sample per pixel, 8 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xff, 0, 0, 0,
0x7f, 1, 4, -4,
0x00, 127, 127, -127
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0x7f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testReadArray1SPP8BPS() throws IOException {
// 1 sample per pixel, 8 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xff, 0, 0, 0,
0x7f, 1, 4, -4,
0x00, 127, 127, -127
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
byte[] result = new byte[data.length];
new DataInputStream(stream).readFully(result);
assertArrayEquals(
new byte[] {
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
0x7f, (byte) 0x80, (byte) 0x84, (byte) 0x80,
0x00, 0x7f, (byte) 0xfe, 0x7f,
},
result
);
// EOF
assertEquals(-1, stream.read(new byte[16]));
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP32BPS() throws IOException {
// 1 sample per pixel, 32 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeInt(0x00000000);
dataOut.writeInt(305419896);
dataOut.writeInt(305419896);
dataOut.writeInt(-610839792);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readInt());
assertEquals(305419896, dataIn.readInt());
assertEquals(610839792, dataIn.readInt());
assertEquals(0, dataIn.readInt());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP32BPSLittleEndian() throws IOException {
// 1 sample per pixel, 32 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeInt(0x00000000);
dataOut.writeInt(305419896);
dataOut.writeInt(305419896);
dataOut.writeInt(-610839792);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readInt());
assertEquals(305419896, dataIn.readInt());
assertEquals(610839792, dataIn.readInt());
assertEquals(0, dataIn.readInt());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP64BPS() throws IOException {
// 1 sample per pixel, 64 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeLong(0x00000000);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(-163971058432973790L);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readLong());
assertEquals(81985529216486895L, dataIn.readLong());
assertEquals(163971058432973790L, dataIn.readLong());
assertEquals(0, dataIn.readLong());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP64BPSLittleEndian() throws IOException {
// 1 sample per pixel, 64 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeLong(0x00000000);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(-163971058432973790L);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readLong());
assertEquals(81985529216486895L, dataIn.readLong());
assertEquals(163971058432973790L, dataIn.readLong());
assertEquals(0, dataIn.readLong());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead3SPP8BPS() throws IOException {
// 3 samples per pixel, 8 bits per sample (RGB)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, -1, -1, -1, -4, -4, -4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 1, 1, 1, 4, 4, 4, -4, -4, -4,
0x00, 0x00, 0x00, 127, -127, 0, -127, 127, 0, 0, 0, 127,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 3, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xfa, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0x7a, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x81, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead3SPP16BPS() throws IOException {
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(-9320);
dataOut.writeShort(-60584);
dataOut.writeShort(-9320);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// Row 2
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead3SPP16BPSLittleEndian() throws IOException {
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(-9320);
dataOut.writeShort(-60584);
dataOut.writeShort(-9320);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// Row 2
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead4SPP8BPS() throws IOException {
// 4 samples per pixel, 8 bits per sample (RGBA)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xfa, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0x7a, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testReadArray4SPP8BPS() throws IOException {
// 4 samples per pixel, 8 bits per sample (RGBA)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
byte[] result = new byte[data.length];
new DataInputStream(stream).readFully(result);
assertArrayEquals(
new byte[] {
(byte) 0xff, 0x00, 0x7f, 0x00,
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
(byte) 0xfa, (byte) 0xfb, 0x7a, (byte) 0xfb,
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
0x7f, 0x7f, 0x7f, 0x7f,
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
(byte) 0x84, (byte) 0x84, (byte) 0x84, (byte) 0x84,
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
},
result
);
// EOF
assertEquals(-1, stream.read(new byte[16]));
assertEquals(-1, stream.read());
}
}

View File

@ -45,10 +45,13 @@ import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import static org.junit.Assert.*;
@ -618,4 +621,65 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
assertFalse(layer1.isDivider);
}
}
@Test
public void test16bitLr16AndZIPPredictor() throws IOException {
PSDImageReader imageReader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"))) {
imageReader.setInput(stream);
assertEquals(5, imageReader.getNumImages(true));
assertEquals(400, imageReader.getWidth(2));
assertEquals(191, imageReader.getHeight(2));
BufferedImage layer2 = imageReader.read(2);// Read the 16 bit ZIP Predictor based layer
assertNotNull(layer2);
assertEquals(400, layer2.getWidth());
assertEquals(191, layer2.getHeight());
assertRGBEquals("RGB differ at (0,0)", 0xff090b0b, layer2.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (399,0)", 0xff090b0b, layer2.getRGB(399, 0), 4);
assertRGBEquals("RGB differ at (200,95)", 0x00ffffff, layer2.getRGB(200, 95), 4); // Transparent
assertRGBEquals("RGB differ at (0,191)", 0xff090b0b, layer2.getRGB(0, 190), 4);
assertRGBEquals("RGB differ at (399,191)", 0xff090b0b, layer2.getRGB(399, 190), 4);
assertEquals(400, imageReader.getWidth(3));
assertEquals(191, imageReader.getHeight(3));
BufferedImage layer3 = imageReader.read(3);// Read the 16 bit ZIP Predictor based layer
assertNotNull(layer3);
assertEquals(400, layer3.getWidth());
assertEquals(191, layer3.getHeight());
assertRGBEquals("RGB differ at (0,0)", 0xffeec335, layer3.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (399,0)", 0xffeec335, layer3.getRGB(399, 0), 4);
assertRGBEquals("RGB differ at (200,95)", 0xffdb3b3b, layer3.getRGB(200, 95), 4); // Red
assertRGBEquals("RGB differ at (0,191)", 0xffeec335, layer3.getRGB(0, 190), 4);
assertRGBEquals("RGB differ at (399,191)", 0xffeec335, layer3.getRGB(399, 190), 4);
}
}
@Test
public void test32bitLr32AndZIPPredictor() throws IOException {
PSDImageReader imageReader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psd/32bit5x5.psd"))) {
imageReader.setInput(stream);
assertEquals(4, imageReader.getNumImages(true));
assertEquals(5, imageReader.getWidth(1));
assertEquals(5, imageReader.getHeight(1));
BufferedImage image = imageReader.read(1);// Read the 32 bit ZIP Predictor based layer
assertNotNull(image);
assertEquals(5, image.getWidth());
assertEquals(5, image.getHeight());
assertRGBEquals("RGB differ at (0,0)", 0xff888888, image.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (4,4)", 0xff888888, image.getRGB(4, 4), 4);
}
}
}

View File

@ -0,0 +1,303 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import org.junit.Test;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
import static org.junit.Assert.assertEquals;
public class PSDUtilDecompressorStreamTest {
@Test
public void testUncompressed() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 0x7f's
byte[] data = new byte[] {
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f
};
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(data), PSD.COMPRESSION_NONE, 3, 8, null, 9)) {
byte[] row = new byte[3];
for (int y = 0; y < 3; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 0x7f, b);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testPackBits() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 42's
byte[] packBitsData = {
-2, 42, // 3 byte run
2, 42, 42, 42, // 3 byte literal
0, 42, -1, 42 // 1 byte literal + 2 byte run
};
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(packBitsData), PSD.COMPRESSION_RLE, 3, 8, new int[] {2, 4, 4}, packBitsData.length)) {
byte[] row = new byte[3];
for (int y = 0; y < 3; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 42, b);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testZIP() throws IOException {
// Data represents 710 x 512 raster with 16 bit samples, first two 0xFF samples, then all 0x00's
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(ZIP_DATA), PSD.COMPRESSION_ZIP, 710, 16, null, ZIP_DATA.length)) {
byte[] row = new byte[710 * 2];
for (int y = 0; y < 512; y++) {
input.readFully(row);
for (int i = 0; i < 2; i++) {
assertEquals((byte) 0xff, row[i]);
}
for (int i = 2; i < row.length; i++) {
assertEquals((byte) 0x00, row[i]);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testZIPPredictor() throws IOException {
// Data represents 710 x 512 raster with 16 bit samples, all 0xFF's
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(ZIP_DATA), PSD.COMPRESSION_ZIP_PREDICTION, 710, 16, null, ZIP_DATA.length)) {
byte[] row = new byte[710 * 2];
for (int y = 0; y < 512; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 0xff, b);
}
}
assertEquals(-1, input.read());
}
}
private static final byte[] ZIP_DATA = new byte[] {
(byte) 0x48, (byte) 0x89, (byte) 0xEC, (byte) 0xD4, (byte) 0x31, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0C, (byte) 0xC3,
(byte) 0xA0, (byte) 0xF9, (byte) 0x37, (byte) 0xDD, (byte) 0xC9, (byte) 0xC8, (byte) 0x03, (byte) 0x22, (byte) 0xD8, (byte) 0x0E,
(byte) 0x80, (byte) 0xD8, (byte) 0x5C, (byte) 0x0C, (byte) 0x90, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0xC3, (byte) 0xB3, (byte) 0x53, (byte) 0xC7, (byte) 0x02,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x83, (byte) 0xFC, (byte) 0xAD, (byte) 0x87, (byte) 0xB1, (byte) 0xA7,
(byte) 0x20, (byte) 0x82, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0x97, (byte) 0x00,
(byte) 0x03, (byte) 0x00, (byte) 0x3E, (byte) 0xEE, (byte) 0xFC, (byte) 0x2E
};
}

View File

@ -50,6 +50,7 @@ import static com.twelvemonkeys.imageio.plugins.tiff.HorizontalDifferencingStrea
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
*/
final class HorizontalDeDifferencingStream extends InputStream {
/// TODO: Create shared version with PSD, or see if we can avoid some duplication?
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
private final int columns;
@ -96,7 +97,7 @@ final class HorizontalDeDifferencingStream extends InputStream {
}
}
private void decodeRow() throws EOFException {
private void decodeRow() {
// Un-apply horizontal predictor
byte original;
int sample = 0;