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;