diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
index 04b86dc0..b4e9ce79 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDeDifferencingStream.java
@@ -57,12 +57,12 @@ final class HorizontalDeDifferencingStream extends InputStream {
private final ByteBuffer buffer;
public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
- channel = Channels.newChannel(Validate.notNull(stream, "stream"));
-
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();
}
@@ -113,10 +113,15 @@ final class HorizontalDeDifferencingStream extends InputStream {
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 = buffer.get(b);
+ original = array[b];
sample += (original >> 7) & 0x1;
temp = (byte) ((sample << 7) & 0x80);
sample += (original >> 6) & 0x1;
@@ -132,13 +137,13 @@ final class HorizontalDeDifferencingStream extends InputStream {
sample += (original >> 1) & 0x1;
temp |= (byte) ((sample << 1) & 0x02);
sample += original & 0x1;
- buffer.put(b, (byte) (temp | sample & 0x1));
+ array[b] = (byte) (temp | sample & 0x1);
}
break;
case 2:
for (int b = 0; b < (columns + 3) / 4; b++) {
- original = buffer.get(b);
+ original = array[b];
sample += (original >> 6) & 0x3;
temp = (byte) ((sample << 6) & 0xc0);
sample += (original >> 4) & 0x3;
@@ -146,17 +151,17 @@ final class HorizontalDeDifferencingStream extends InputStream {
sample += (original >> 2) & 0x3;
temp |= (byte) ((sample << 2) & 0x0c);
sample += original & 0x3;
- buffer.put(b, (byte) (temp | sample & 0x3));
+ array[b] = (byte) (temp | sample & 0x3);
}
break;
case 4:
for (int b = 0; b < (columns + 1) / 2; b++) {
- original = buffer.get(b);
+ original = array[b];
sample += (original >> 4) & 0xf;
temp = (byte) ((sample << 4) & 0xf0);
sample += original & 0x0f;
- buffer.put(b, (byte) (temp | sample & 0xf));
+ array[b] = (byte) (temp | sample & 0xf);
}
break;
@@ -164,7 +169,7 @@ final class HorizontalDeDifferencingStream extends InputStream {
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
- buffer.put(off, (byte) (buffer.get(off - samplesPerPixel) + buffer.get(off)));
+ array[off] = (byte) (array[off - samplesPerPixel] + array[off]);
}
}
break;
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
index 789190d6..1cb62ba3 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
@@ -33,6 +33,7 @@ import com.twelvemonkeys.io.enc.Decoder;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.String;
import java.nio.ByteBuffer;
/**
@@ -58,7 +59,7 @@ abstract class LZWDecoder implements Decoder {
private final boolean compatibilityMode;
- private final String[] table;
+ private final LZWString[] table;
private int tableLength;
int bitsPerCode;
private int oldCode = CLEAR_CODE;
@@ -73,11 +74,11 @@ abstract class LZWDecoder implements Decoder {
protected LZWDecoder(final boolean compatibilityMode) {
this.compatibilityMode = compatibilityMode;
- table = new String[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"...
+ table = new LZWString[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"...
// First 258 entries of table is always fixed
for (int i = 0; i < 256; i++) {
- table[i] = new String((byte) i);
+ table[i] = new LZWString((byte) i);
}
init();
@@ -112,12 +113,17 @@ abstract class LZWDecoder implements Decoder {
table[code].writeTo(buffer);
}
else {
+ if (table[oldCode] == null) {
+ System.err.println("tableLength: " + tableLength);
+ System.err.println("oldCode: " + oldCode);
+ }
+
if (isInTable(code)) {
table[code].writeTo(buffer);
addStringToTable(table[oldCode].concatenate(table[code].firstChar));
}
else {
- String outString = table[oldCode].concatenate(table[oldCode].firstChar);
+ LZWString outString = table[oldCode].concatenate(table[oldCode].firstChar);
outString.writeTo(buffer);
addStringToTable(outString);
@@ -135,7 +141,7 @@ abstract class LZWDecoder implements Decoder {
return buffer.position();
}
- private void addStringToTable(final String string) throws IOException {
+ private void addStringToTable(final LZWString string) throws IOException {
table[tableLength++] = string;
if (tableLength > maxCode) {
@@ -146,7 +152,7 @@ abstract class LZWDecoder implements Decoder {
bitsPerCode--;
}
else {
- throw new DecodeException(java.lang.String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
+ throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
}
}
@@ -279,26 +285,26 @@ abstract class LZWDecoder implements Decoder {
}
}
- private static final class String {
- final String previous;
+ static final class LZWString {
+ final LZWString previous;
final int length;
final byte value;
final byte firstChar; // Copied forward for fast access
- public String(final byte code) {
+ public LZWString(final byte code) {
this(code, code, 1, null);
}
- private String(final byte value, final byte firstChar, final int length, final String previous) {
+ private LZWString(final byte value, final byte firstChar, final int length, final LZWString previous) {
this.value = value;
this.firstChar = firstChar;
this.length = length;
this.previous = previous;
}
- public final String concatenate(final byte firstChar) {
- return new String(firstChar, this.firstChar, length + 1, this);
+ public final LZWString concatenate(final byte firstChar) {
+ return new LZWString(firstChar, this.firstChar, length + 1, this);
}
public final void writeTo(final ByteBuffer buffer) {
@@ -310,7 +316,7 @@ abstract class LZWDecoder implements Decoder {
buffer.put(value);
}
else {
- String e = this;
+ LZWString e = this;
final int offset = buffer.position();
for (int i = length - 1; i >= 0; i--) {
@@ -321,6 +327,47 @@ abstract class LZWDecoder implements Decoder {
buffer.position(offset + length);
}
}
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("ZLWString[");
+ int offset = builder.length();
+ LZWString e = this;
+ for (int i = length - 1; i >= 0; i--) {
+ builder.insert(offset, String.format("%2x", e.value));
+ e = e.previous;
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ LZWString string = (LZWString) other;
+
+ return firstChar == string.firstChar &&
+ length == string.length &&
+ value == string.value &&
+// !(previous != null ? !previous.equals(string.previous) : string.previous != null);
+ previous == string.previous;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = previous != null ? previous.hashCode() : 0;
+ result = 31 * result + length;
+ result = 31 * result + (int) value;
+ result = 31 * result + (int) firstChar;
+ return result;
+ }
+
}
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
index 5e6f4501..a78b9d89 100755
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
@@ -274,7 +274,7 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFExtension.PHOTOMETRIC_YCBCR:
// JPEG reader will handle YCbCr to RGB for us, otherwise we'll convert while reading
- // TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
+ // TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] (or [16,16,16]) and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
case TIFFBaseline.PHOTOMETRIC_RGB:
// RGB
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
@@ -617,9 +617,16 @@ public class TIFFImageReader extends ImageReaderBase {
adapter = createDecompressorStream(compression, width, adapter);
adapter = createUnpredictorStream(predictor, width, numBands, getBitsPerSample(), adapter, imageInput.getByteOrder());
- if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
+ if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_BYTE) {
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients);
}
+ else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_USHORT) {
+ adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients, imageInput.getByteOrder());
+ }
+ else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
+ // Handled in getRawImageType
+ throw new AssertionError();
+ }
// According to the spec, short/long/etc should follow order of containing stream
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
@@ -765,8 +772,9 @@ public class TIFFImageReader extends ImageReaderBase {
if (jpegOffset != -1) {
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
-
- if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
+ if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
+ || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
+ || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Ignoring JPEG tables. Reading as single tile.");
}
else {
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java
new file mode 100644
index 00000000..8f3d591a
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2014, 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 "TwelveMonkeys" 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 OWNER 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.tiff;
+
+import com.twelvemonkeys.lang.Validate;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+
+/**
+ * Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr 16 bit samples
+ * to (raw) RGB 16 bit samples.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
+ */
+final class YCbCr16UpsamplerStream extends FilterInputStream {
+ // TODO: As we deal with short/16 bit samples, we need to take byte order into account
+ private final int horizChromaSub;
+ private final int vertChromaSub;
+ private final int yCbCrPos;
+ private final int columns;
+ private final double[] coefficients;
+ private final ByteOrder byteOrder;
+
+ private final int units;
+ private final int unitSize;
+ private final int padding;
+ private final byte[] decodedRows;
+
+ int decodedLength;
+ int decodedPos;
+
+ private final byte[] buffer;
+ int bufferLength;
+ int bufferPos;
+
+ public YCbCr16UpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients, final ByteOrder byteOrder) {
+ super(Validate.notNull(stream, "stream"));
+
+ Validate.notNull(chromaSub, "chromaSub");
+ Validate.isTrue(chromaSub.length == 2, "chromaSub.length != 2");
+ Validate.notNull(byteOrder, "byteOrder");
+
+ this.horizChromaSub = chromaSub[0];
+ this.vertChromaSub = chromaSub[1];
+ this.yCbCrPos = yCbCrPos;
+ this.columns = columns;
+ this.coefficients = coefficients == null ? YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS : coefficients;
+ this.byteOrder = byteOrder;
+
+ // In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
+ // For a 4:2 subsampled stream like this:
+ //
+ // Y0 Y1 Y2 Y3 Cb0 Cr0 Y8 Y9 Y10 Y11 Cb1 Cr1
+ // Y4 Y5 Y6 Y7 Y12Y13Y14 Y15
+ //
+ // In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16...
+
+ unitSize = 2 * (horizChromaSub * vertChromaSub + 2);
+ units = (columns + horizChromaSub - 1) / horizChromaSub; // If columns % horizChromasSub != 0...
+ padding = 2 * (units * horizChromaSub - columns); // ...each coded row will be padded to fill unit
+
+ decodedRows = new byte[2 * columns * vertChromaSub * 3];
+ buffer = new byte[unitSize * units];
+ }
+
+ private void fetch() throws IOException {
+ if (bufferPos >= bufferLength) {
+ int pos = 0;
+ int read;
+
+ // This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException
+ while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) {
+ pos += read;
+ }
+
+ bufferLength = pos;
+ bufferPos = 0;
+ }
+
+ if (bufferLength > 0) {
+ decodeRows();
+ }
+ else {
+ decodedLength = -1;
+ }
+ }
+
+ private void decodeRows() throws EOFException {
+ decodedLength = decodedRows.length;
+
+ for (int u = 0; u < units; u++) {
+ if (bufferPos >= bufferLength) {
+ throw new EOFException("Unexpected end of stream");
+ }
+
+ // Decode one unit
+ byte cb1 = buffer[bufferPos + unitSize - 4];
+ byte cb2 = buffer[bufferPos + unitSize - 3];
+ byte cr1 = buffer[bufferPos + unitSize - 2];
+ byte cr2 = buffer[bufferPos + unitSize - 1];
+
+ for (int y = 0; y < vertChromaSub; y++) {
+ for (int x = 0; x < horizChromaSub; x++) {
+ // Skip padding at end of row
+ int column = horizChromaSub * u + x;
+ if (column >= columns) {
+ bufferPos += padding;
+ break;
+ }
+
+ int pixelOff = 2 * 3 * (column + columns * y);
+
+ decodedRows[pixelOff ] = buffer[bufferPos++];
+ decodedRows[pixelOff + 1] = buffer[bufferPos++];
+ decodedRows[pixelOff + 2] = cb1;
+ decodedRows[pixelOff + 3] = cb2;
+ decodedRows[pixelOff + 4] = cr1;
+ decodedRows[pixelOff + 5] = cr2;
+
+ // Convert to RGB
+ convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
+ }
+ }
+
+ bufferPos += 2 * 2; // Skip CbCr bytes at end of unit
+ }
+
+ bufferPos = bufferLength;
+ decodedPos = 0;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (decodedLength < 0) {
+ return -1;
+ }
+
+ if (decodedPos >= decodedLength) {
+ fetch();
+
+ if (decodedLength < 0) {
+ return -1;
+ }
+ }
+
+ return decodedRows[decodedPos++] & 0xff;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (decodedLength < 0) {
+ return -1;
+ }
+
+ if (decodedPos >= decodedLength) {
+ fetch();
+
+ if (decodedLength < 0) {
+ return -1;
+ }
+ }
+
+ int read = Math.min(decodedLength - decodedPos, len);
+ System.arraycopy(decodedRows, decodedPos, b, off, read);
+ decodedPos += read;
+
+ return read;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (decodedLength < 0) {
+ return -1;
+ }
+
+ if (decodedPos >= decodedLength) {
+ fetch();
+
+ if (decodedLength < 0) {
+ return -1;
+ }
+ }
+
+ int skipped = (int) Math.min(decodedLength - decodedPos, n);
+ decodedPos += skipped;
+
+ return skipped;
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("mark/reset not supported");
+ }
+
+ private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
+ int y;
+ int cb;
+ int cr;
+
+ // Short values, depends on byte order!
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ y = ((yCbCr[offset ] & 0xff) << 8) | (yCbCr[offset + 1] & 0xff);
+ cb = (((yCbCr[offset + 2] & 0xff) << 8) | (yCbCr[offset + 3] & 0xff)) - 32768;
+ cr = (((yCbCr[offset + 4] & 0xff) << 8) | (yCbCr[offset + 5] & 0xff)) - 32768;
+ }
+ else {
+ y = ((yCbCr[offset + 1] & 0xff) << 8) | (yCbCr[offset ] & 0xff);
+ cb = (((yCbCr[offset + 3] & 0xff) << 8) | (yCbCr[offset + 2] & 0xff)) - 32768;
+ cr = (((yCbCr[offset + 5] & 0xff) << 8) | (yCbCr[offset + 4] & 0xff)) - 32768;
+ }
+
+ double lumaRed = coefficients[0];
+ double lumaGreen = coefficients[1];
+ double lumaBlue = coefficients[2];
+
+ int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
+ int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
+ int green = (int) Math.round((y - lumaRed * (red) - lumaBlue * (blue)) / lumaGreen);
+
+ short r = clampShort(red);
+ short g = clampShort(green);
+ short b = clampShort(blue);
+
+ // Short values, depends on byte order!
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ rgb[offset ] = (byte) ((r >>> 8) & 0xff);
+ rgb[offset + 1] = (byte) (r & 0xff);
+ rgb[offset + 2] = (byte) ((g >>> 8) & 0xff);
+ rgb[offset + 3] = (byte) (g & 0xff);
+ rgb[offset + 4] = (byte) ((b >>> 8) & 0xff);
+ rgb[offset + 5] = (byte) (b & 0xff);
+ }
+ else {
+ rgb[offset ] = (byte) (r & 0xff);
+ rgb[offset + 1] = (byte) ((r >>> 8) & 0xff);
+ rgb[offset + 2] = (byte) (g & 0xff);
+ rgb[offset + 3] = (byte) ((g >>> 8) & 0xff);
+ rgb[offset + 4] = (byte) (b & 0xff);
+ rgb[offset + 5] = (byte) ((b >>> 8) & 0xff);
+ }
+ }
+
+ private short clampShort(int val) {
+ return (short) Math.max(0, Math.min(0xffff, val));
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
index c0826c4c..82ab8a17 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
@@ -39,7 +39,7 @@ import java.io.InputStream;
import java.util.Arrays;
/**
- * Input stream that provides on-the-fly conversion and upsampling of TIFF susampled YCbCr samples to (raw) RGB samples.
+ * Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr samples to (raw) RGB samples.
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
@@ -229,7 +229,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
double y = (yCbCr[offset ] & 0xff);
- double cb = (yCbCr[offset + 1] & 0xff) - 128; // TODO: The -128 part seems bogus... Consult ReferenceBlackWhite??? But default to these values?
+ double cb = (yCbCr[offset + 1] & 0xff) - 128;
double cr = (yCbCr[offset + 2] & 0xff) - 128;
double lumaRed = coefficients[0];
@@ -238,7 +238,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
- int green = (int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen);
+ int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
rgb[offset ] = clamp(red);
rgb[offset + 2] = clamp(blue);
@@ -342,10 +342,5 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
cmyk[offset + 2] = clamp(cmykY);
cmyk[offset + 3] = (byte) k; // K passes through unchanged
}
-
-// private static byte clamp(int val) {
-// return (byte) Math.max(0, Math.min(255, val));
-// }
}
-
}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java
new file mode 100644
index 00000000..04b65723
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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 "TwelveMonkeys" 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 OWNER 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.tiff;
+
+import com.twelvemonkeys.io.LittleEndianDataInputStream;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.*;
+
+/**
+ * YCbCr16UpsamplerStreamTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: YCbCr16UpsamplerStreamTest.java,v 1.0 31.01.13 14:35 haraldk Exp$
+ */
+public class YCbCr16UpsamplerStreamTest {
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateNullStream() {
+ new YCbCr16UpsamplerStream(null, new int[2], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateNullChroma() {
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[3], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateShortChroma() {
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[1], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateNoByteOrder() {
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[] {2, 2}, 7, 5, null, null);
+ }
+
+ // TODO: The expected values seems bogus...
+ // But visually, it looks okay for the one and only sample image I've got...
+
+ @Test
+ public void testUpsample22() throws IOException {
+ short[] shorts = new short[] {
+ 1, 2, 3, 4, 5, 6, 7, 8, 42, 96,
+ 108, 109, 110, 111, 112, 113, 114, 115, 43, 97
+ };
+
+ byte[] bytes = new byte[shorts.length * 2];
+ ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);
+
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, null, ByteOrder.LITTLE_ENDIAN);
+
+ short[] expected = new short[] {
+ 0, -30864, 0, 0, -30863, 0, 0, -30966, 0, 0, -30965, 0, 0, -30870, 0, 0, -30869, 0, 0, -30815, 0, 0, -30761, 0,
+ 0, -30862, 0, 0, -30861, 0, 0, -30931, 0, 0, -30877, 0, 0, -30868, 0, 0, -30867, 0, 0, -30858, 0, 0, -30858, 0
+ };
+ short[] upsampled = new short[expected.length];
+
+ LittleEndianDataInputStream dataInput = new LittleEndianDataInputStream(stream);
+ for (int i = 0; i < upsampled.length; i++) {
+ upsampled[i] = dataInput.readShort();
+ }
+
+ assertArrayEquals(expected, upsampled);
+ assertEquals(-1, stream.read());
+ }
+
+ @Test
+ public void testUpsample21() throws IOException {
+ short[] shorts = new short[] {
+ 1, 2, 3, 4, 42, 96, 77,
+ 112, 113, 114, 115, 43, 97, 43
+ };
+
+ byte[] bytes = new byte[shorts.length * 2];
+ ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asShortBuffer().put(shorts);
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null, ByteOrder.BIG_ENDIAN);
+
+ short[] expected = new short[] {
+ 0, -30861, 0, 0, -30860, 0, 0, -30923, 0, 0, -30869, 0, 0, -30816, 0, 0, -30815, 0, 0, -30868, 0, 0, -30922, 0
+ };
+ short[] upsampled = new short[expected.length];
+
+ DataInputStream dataInput = new DataInputStream(stream);
+ for (int i = 0; i < upsampled.length; i++) {
+ upsampled[i] = dataInput.readShort();
+ }
+
+ assertArrayEquals(expected, upsampled);
+ assertEquals(-1, stream.read());
+ }
+
+ @Test
+ public void testUpsample12() throws IOException {
+ short[] shorts = new short[] {
+ 1, 2, 3, 4, 42, 96, 77,
+ 112, 113, 114, 115, 43, 97, 43
+ };
+
+ byte[] bytes = new byte[shorts.length * 2];
+ ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asShortBuffer().put(shorts);
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null, ByteOrder.BIG_ENDIAN);
+
+ short[] expected = new short[] {
+ 0, -30861, 0, 0, -30923, 0, 0, -30816, 0, 0, -30761, 0, 0, -30860, 0, 0, -30869, 0, 0, -30815, 0, 0, -30815, 0
+ };
+ short[] upsampled = new short[expected.length];
+
+ DataInputStream dataInput = new DataInputStream(stream);
+ for (int i = 0; i < upsampled.length; i++) {
+ upsampled[i] = dataInput.readShort();
+ }
+
+ assertArrayEquals(expected, upsampled);
+ assertEquals(-1, stream.read());
+ }
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
index ebb4a236..a0faaced 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
@@ -33,6 +33,7 @@ import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
+import java.io.InputStream;
import static org.junit.Assert.*;
@@ -65,7 +66,7 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 5, 6, 7, 8, 42, 96,
108, 109, 110, 111, 112, 113, 114, 115, 43, 97
};
- YCbCrUpsamplerStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, null);
byte[] expected = new byte[] {
0, -126, 0, 0, -125, 0, 0, 27, 0, 0, 28, 0, 92, 124, 85, 93, 125, 86, 0, -78, 0, 0, -24, 0,
@@ -85,7 +86,7 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 42, 96, 77,
112, 113, 114, 115, 43, 97, 43
};
- YCbCrUpsamplerStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
byte[] expected = new byte[] {
0, -123, 0, 0, -122, 0, 20, 71, 0, 74, 125, 6, 0, -78, 90, 0, -77, 91, 75, 126, 7, 21, 72, 0
@@ -104,7 +105,7 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 42, 96, 77,
112, 113, 114, 115, 43, 97, 43
};
- YCbCrUpsamplerStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
byte[] expected = new byte[] {
0, -123, 0, 20, 71, 0, 0, -78, 90, 0, -24, 0, 0, -122, 0, 74, 125, 6, 0, -77, 91, 0, -78, 0