TMI-TIFF: 16 bit YCbCr support + minor improvements

This commit is contained in:
Harald Kuhr 2014-09-29 14:50:28 +02:00
parent f914d15677
commit 1ff764997b
7 changed files with 526 additions and 37 deletions

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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));
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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));
// }
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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());
}
}

View File

@ -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