diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStream.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStream.java
new file mode 100644
index 00000000..cd07e3c7
--- /dev/null
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStream.java
@@ -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 Harald Kuhr
+ * @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();
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDirectoryResource.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDirectoryResource.java
index 86f03227..4e07892a 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDirectoryResource.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDirectoryResource.java
@@ -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,15 +69,24 @@ abstract class PSDDirectoryResource extends PSDImageResource {
public String toString() {
StringBuilder builder = toStringBuilder();
- int length = Math.min(256, data.length);
- String data = StringUtil.decode(this.data, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
- builder.append(", data: \"").append(data);
-
- if (length < this.data.length) {
- builder.append("...");
+ if (directory != null) {
+ builder.append(", ").append(directory);
+ builder.append("]");
}
+ else {
+ int length = Math.min(256, data.length);
+ String data = new String(this.data, 0, length, StandardCharsets.US_ASCII)
+ .replace('\uFFFD', '.')
+ .replaceAll("\\s+", " ")
+ .replaceAll("[^\\p{Print}]", ".");
+ builder.append(", data: \"").append(data);
- builder.append("\"]");
+ if (length < this.data.length) {
+ builder.append("...");
+ }
+
+ builder.append("\"]");
+ }
return builder.toString();
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java
index ed23ca75..195fd40b 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java
@@ -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();
- }
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java
index 92dc776e..75771cb3 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java
@@ -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();
- }
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
index ced1af09..b0db795b 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
@@ -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 Adobe Photoshop File Formats Specification
* @see Adobe Photoshop File Format Summary
*/
-// 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,31 +467,33 @@ public final class PSDImageReader extends ImageReaderBase {
int interleavedBands = banded ? 1 : destRaster.getNumBands();
for (int c = 0; c < channels; c++) {
- int bandOffset = banded ? 0 : interleavedBands - 1 - 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);
- 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);
- 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);
- 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);
- break;
- default:
- throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
- }
+ switch (header.bits) {
+ case 1:
+ byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ read1bitChannel(stream, c, destRaster.getDataBuffer(), row1, pSource, pDest, pXSub, pYSub, header.width, header.height);
+ break;
+ case 8:
+ byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ 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(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(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));
+ }
- if (abortRequested()) {
- break;
+ if (abortRequested()) {
+ break;
+ }
}
}
@@ -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 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,52 +1117,51 @@ 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);
- 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);
- 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);
- 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);
- break;
- default:
- throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
- }
+ switch (header.bits) {
+ case 1:
+ byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ read1bitChannel(stream, channel, raster.getDataBuffer(), row1, area, area, xsub, ysub, width, height);
+ break;
+ case 8:
+ byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ 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(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(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));
+ }
- if (abortRequested()) {
- break;
+ if (abortRequested()) {
+ break;
+ }
}
}
}
@@ -1130,6 +1170,8 @@ public final class PSDImageReader extends ImageReaderBase {
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,38 +1354,43 @@ public final class PSDImageReader extends ImageReaderBase {
int idx = 0;
while (pArgs[idx].charAt(0) == '-') {
- if (pArgs[idx].equals("-s") || pArgs[idx].equals("--subsampling")) {
- subsampleFactor = Integer.parseInt(pArgs[++idx]);
- }
- else if (pArgs[idx].equals("-r") || pArgs[idx].equals("--sourceregion")) {
- int xw = Integer.parseInt(pArgs[++idx]);
- int yh = Integer.parseInt(pArgs[++idx]);
+ switch (pArgs[idx]) {
+ case "-s":
+ case "--subsampling":
+ subsampleFactor = Integer.parseInt(pArgs[++idx]);
+ break;
+ case "-r":
+ case "--sourceregion":
+ int xw = Integer.parseInt(pArgs[++idx]);
+ int yh = Integer.parseInt(pArgs[++idx]);
- try {
- int w = Integer.parseInt(pArgs[idx + 1]);
- int h = Integer.parseInt(pArgs[idx + 2]);
+ try {
+ int w = Integer.parseInt(pArgs[idx + 1]);
+ int h = Integer.parseInt(pArgs[idx + 2]);
- idx += 2;
+ idx += 2;
- // x y w h
- sourceRegion = new Rectangle(xw, yh, w, h);
- }
- catch (NumberFormatException e) {
- // w h
- sourceRegion = new Rectangle(xw, yh);
- }
+ // x y w h
+ sourceRegion = new Rectangle(xw, yh, w, h);
+ }
+ catch (NumberFormatException e) {
+ // w h
+ sourceRegion = new Rectangle(xw, yh);
+ }
- System.out.println("sourceRegion: " + sourceRegion);
- }
- else if (pArgs[idx].equals("-l") || pArgs[idx].equals("--layers")) {
- readLayers = true;
- }
- else if (pArgs[idx].equals("-t") || pArgs[idx].equals("--thumbnails")) {
- readThumbnails = true;
- }
- else {
- System.err.println("Usage: java PSDImageReader [-s ] [-r [] ] ");
- System.exit(1);
+ System.out.println("sourceRegion: " + sourceRegion);
+ break;
+ case "-l":
+ case "--layers":
+ readLayers = true;
+ break;
+ case "-t":
+ case "--thumbnails":
+ readThumbnails = true;
+ break;
+ default:
+ System.err.println("Usage: java PSDImageReader [-s ] [-r [] ] [-t -l] ");
+ System.exit(1);
}
idx++;
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
index 30fc926b..df89a85f 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
@@ -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 exif = getResources(PSDEXIF1Data.class);
if (exif.hasNext()) {
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java
index b7f21b9d..fcbba0f7 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java
@@ -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();
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
index e7c239a4..7555a15d 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
@@ -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 {
+ 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());
+ }
+ }
}
diff --git a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStreamTest.java b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStreamTest.java
new file mode 100644
index 00000000..5227c991
--- /dev/null
+++ b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/HorizontalDeDifferencingStreamTest.java
@@ -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 Harald Kuhr
+ * @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());
+ }
+}
diff --git a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
index 4cd5e4c4..31bc0173 100755
--- a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
+++ b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
@@ -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
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);
+ }
+ }
}
\ No newline at end of file
diff --git a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtilDecompressorStreamTest.java b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtilDecompressorStreamTest.java
new file mode 100644
index 00000000..1ff69f75
--- /dev/null
+++ b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtilDecompressorStreamTest.java
@@ -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
+ };
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
index da06f5c8..ba857518 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
@@ -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;