#675 PSD 16/32 bit layer support

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ import com.twelvemonkeys.util.FilterIterator;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel; import java.awt.image.*;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; 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); super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
} }
// TODO: Allow creating correct metadata for layers too!
/// Native format support /// Native format support
@Override @Override
@ -148,7 +150,7 @@ public final class PSDMetadata extends AbstractMetadata {
for (PSDImageResource imageResource : imageResources) { for (PSDImageResource imageResource : imageResources) {
// TODO: Always add name (if set) and id (as resourceId) to all nodes? // 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) { if (imageResource instanceof ICCProfile) {
ICCProfile profile = (ICCProfile) imageResource; 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 formatVersion.setAttribute("value", header.largeFormat ? "2" : "1"); // PSD format version is always 1, PSB is 2
document_node.appendChild(formatVersion); 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 // Get EXIF data if present
Iterator<PSDEXIF1Data> exif = getResources(PSDEXIF1Data.class); Iterator<PSDEXIF1Data> exif = getResources(PSDEXIF1Data.class);
if (exif.hasNext()) { if (exif.hasNext()) {

View File

@ -34,7 +34,7 @@ import javax.imageio.IIOException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.*;
import java.awt.image.*; import java.awt.image.*;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; 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 // This data isn't really useful, unless we're dealing with raw bytes
widthBytes = pInput.readInt(); 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 // Consistency check
int sizeCompressed = pInput.readInt(); int sizeCompressed = pInput.readInt();

View File

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

View File

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

View File

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

View File

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

View File

@ -50,6 +50,7 @@ import static com.twelvemonkeys.imageio.plugins.tiff.HorizontalDifferencingStrea
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$ * @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
*/ */
final class HorizontalDeDifferencingStream extends InputStream { 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. // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
private final int columns; 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 // Un-apply horizontal predictor
byte original; byte original;
int sample = 0; int sample = 0;