mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
TMI-TIFF: 16 bit YCbCr support + minor improvements
This commit is contained in:
parent
f914d15677
commit
1ff764997b
@ -57,12 +57,12 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
|||||||
private final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
|
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.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.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");
|
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 = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
}
|
}
|
||||||
@ -113,10 +113,15 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
|||||||
int sample = 0;
|
int sample = 0;
|
||||||
byte temp;
|
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) {
|
switch (bitsPerSample) {
|
||||||
case 1:
|
case 1:
|
||||||
for (int b = 0; b < (columns + 7) / 8; b++) {
|
for (int b = 0; b < (columns + 7) / 8; b++) {
|
||||||
original = buffer.get(b);
|
original = array[b];
|
||||||
sample += (original >> 7) & 0x1;
|
sample += (original >> 7) & 0x1;
|
||||||
temp = (byte) ((sample << 7) & 0x80);
|
temp = (byte) ((sample << 7) & 0x80);
|
||||||
sample += (original >> 6) & 0x1;
|
sample += (original >> 6) & 0x1;
|
||||||
@ -132,13 +137,13 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
|||||||
sample += (original >> 1) & 0x1;
|
sample += (original >> 1) & 0x1;
|
||||||
temp |= (byte) ((sample << 1) & 0x02);
|
temp |= (byte) ((sample << 1) & 0x02);
|
||||||
sample += original & 0x1;
|
sample += original & 0x1;
|
||||||
buffer.put(b, (byte) (temp | sample & 0x1));
|
array[b] = (byte) (temp | sample & 0x1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
for (int b = 0; b < (columns + 3) / 4; b++) {
|
for (int b = 0; b < (columns + 3) / 4; b++) {
|
||||||
original = buffer.get(b);
|
original = array[b];
|
||||||
sample += (original >> 6) & 0x3;
|
sample += (original >> 6) & 0x3;
|
||||||
temp = (byte) ((sample << 6) & 0xc0);
|
temp = (byte) ((sample << 6) & 0xc0);
|
||||||
sample += (original >> 4) & 0x3;
|
sample += (original >> 4) & 0x3;
|
||||||
@ -146,17 +151,17 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
|||||||
sample += (original >> 2) & 0x3;
|
sample += (original >> 2) & 0x3;
|
||||||
temp |= (byte) ((sample << 2) & 0x0c);
|
temp |= (byte) ((sample << 2) & 0x0c);
|
||||||
sample += original & 0x3;
|
sample += original & 0x3;
|
||||||
buffer.put(b, (byte) (temp | sample & 0x3));
|
array[b] = (byte) (temp | sample & 0x3);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
for (int b = 0; b < (columns + 1) / 2; b++) {
|
for (int b = 0; b < (columns + 1) / 2; b++) {
|
||||||
original = buffer.get(b);
|
original = array[b];
|
||||||
sample += (original >> 4) & 0xf;
|
sample += (original >> 4) & 0xf;
|
||||||
temp = (byte) ((sample << 4) & 0xf0);
|
temp = (byte) ((sample << 4) & 0xf0);
|
||||||
sample += original & 0x0f;
|
sample += original & 0x0f;
|
||||||
buffer.put(b, (byte) (temp | sample & 0xf));
|
array[b] = (byte) (temp | sample & 0xf);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -164,7 +169,7 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
|||||||
for (int x = 1; x < columns; x++) {
|
for (int x = 1; x < columns; x++) {
|
||||||
for (int b = 0; b < samplesPerPixel; b++) {
|
for (int b = 0; b < samplesPerPixel; b++) {
|
||||||
int off = x * 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;
|
break;
|
||||||
|
@ -33,6 +33,7 @@ import com.twelvemonkeys.io.enc.Decoder;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.lang.String;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +59,7 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
|
|
||||||
private final boolean compatibilityMode;
|
private final boolean compatibilityMode;
|
||||||
|
|
||||||
private final String[] table;
|
private final LZWString[] table;
|
||||||
private int tableLength;
|
private int tableLength;
|
||||||
int bitsPerCode;
|
int bitsPerCode;
|
||||||
private int oldCode = CLEAR_CODE;
|
private int oldCode = CLEAR_CODE;
|
||||||
@ -73,11 +74,11 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
protected LZWDecoder(final boolean compatibilityMode) {
|
protected LZWDecoder(final boolean compatibilityMode) {
|
||||||
this.compatibilityMode = 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
|
// First 258 entries of table is always fixed
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
table[i] = new String((byte) i);
|
table[i] = new LZWString((byte) i);
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@ -112,12 +113,17 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
table[code].writeTo(buffer);
|
table[code].writeTo(buffer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (table[oldCode] == null) {
|
||||||
|
System.err.println("tableLength: " + tableLength);
|
||||||
|
System.err.println("oldCode: " + oldCode);
|
||||||
|
}
|
||||||
|
|
||||||
if (isInTable(code)) {
|
if (isInTable(code)) {
|
||||||
table[code].writeTo(buffer);
|
table[code].writeTo(buffer);
|
||||||
addStringToTable(table[oldCode].concatenate(table[code].firstChar));
|
addStringToTable(table[oldCode].concatenate(table[code].firstChar));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String outString = table[oldCode].concatenate(table[oldCode].firstChar);
|
LZWString outString = table[oldCode].concatenate(table[oldCode].firstChar);
|
||||||
|
|
||||||
outString.writeTo(buffer);
|
outString.writeTo(buffer);
|
||||||
addStringToTable(outString);
|
addStringToTable(outString);
|
||||||
@ -135,7 +141,7 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
return buffer.position();
|
return buffer.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStringToTable(final String string) throws IOException {
|
private void addStringToTable(final LZWString string) throws IOException {
|
||||||
table[tableLength++] = string;
|
table[tableLength++] = string;
|
||||||
|
|
||||||
if (tableLength > maxCode) {
|
if (tableLength > maxCode) {
|
||||||
@ -146,7 +152,7 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
bitsPerCode--;
|
bitsPerCode--;
|
||||||
}
|
}
|
||||||
else {
|
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 {
|
static final class LZWString {
|
||||||
final String previous;
|
final LZWString previous;
|
||||||
|
|
||||||
final int length;
|
final int length;
|
||||||
final byte value;
|
final byte value;
|
||||||
final byte firstChar; // Copied forward for fast access
|
final byte firstChar; // Copied forward for fast access
|
||||||
|
|
||||||
public String(final byte code) {
|
public LZWString(final byte code) {
|
||||||
this(code, code, 1, null);
|
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.value = value;
|
||||||
this.firstChar = firstChar;
|
this.firstChar = firstChar;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.previous = previous;
|
this.previous = previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final String concatenate(final byte firstChar) {
|
public final LZWString concatenate(final byte firstChar) {
|
||||||
return new String(firstChar, this.firstChar, length + 1, this);
|
return new LZWString(firstChar, this.firstChar, length + 1, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void writeTo(final ByteBuffer buffer) {
|
public final void writeTo(final ByteBuffer buffer) {
|
||||||
@ -310,7 +316,7 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
buffer.put(value);
|
buffer.put(value);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String e = this;
|
LZWString e = this;
|
||||||
final int offset = buffer.position();
|
final int offset = buffer.position();
|
||||||
|
|
||||||
for (int i = length - 1; i >= 0; i--) {
|
for (int i = length - 1; i >= 0; i--) {
|
||||||
@ -321,6 +327,47 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
buffer.position(offset + length);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||||
// JPEG reader will handle YCbCr to RGB for us, otherwise we'll convert while reading
|
// 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:
|
case TIFFBaseline.PHOTOMETRIC_RGB:
|
||||||
// RGB
|
// RGB
|
||||||
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
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 = createDecompressorStream(compression, width, adapter);
|
||||||
adapter = createUnpredictorStream(predictor, width, numBands, getBitsPerSample(), adapter, imageInput.getByteOrder());
|
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);
|
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
|
// According to the spec, short/long/etc should follow order of containing stream
|
||||||
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||||
@ -765,8 +772,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (jpegOffset != -1) {
|
if (jpegOffset != -1) {
|
||||||
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
// 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
|
||||||
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) {
|
|| 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.");
|
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Ignoring JPEG tables. Reading as single tile.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ import java.io.InputStream;
|
|||||||
import java.util.Arrays;
|
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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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) {
|
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||||
double y = (yCbCr[offset ] & 0xff);
|
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 cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||||
|
|
||||||
double lumaRed = coefficients[0];
|
double lumaRed = coefficients[0];
|
||||||
@ -238,7 +238,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
|
|
||||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + 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 ] = clamp(red);
|
||||||
rgb[offset + 2] = clamp(blue);
|
rgb[offset + 2] = clamp(blue);
|
||||||
@ -342,10 +342,5 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
|||||||
cmyk[offset + 2] = clamp(cmykY);
|
cmyk[offset + 2] = clamp(cmykY);
|
||||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
// private static byte clamp(int val) {
|
|
||||||
// return (byte) Math.max(0, Math.min(255, val));
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ import org.junit.Test;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ public class YCbCrUpsamplerStreamTest {
|
|||||||
1, 2, 3, 4, 5, 6, 7, 8, 42, 96,
|
1, 2, 3, 4, 5, 6, 7, 8, 42, 96,
|
||||||
108, 109, 110, 111, 112, 113, 114, 115, 43, 97
|
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[] {
|
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,
|
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,
|
1, 2, 3, 4, 42, 96, 77,
|
||||||
112, 113, 114, 115, 43, 97, 43
|
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[] {
|
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
|
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,
|
1, 2, 3, 4, 42, 96, 77,
|
||||||
112, 113, 114, 115, 43, 97, 43
|
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[] {
|
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
|
0, -123, 0, 20, 71, 0, 0, -78, 90, 0, -24, 0, 0, -122, 0, 74, 125, 6, 0, -77, 91, 0, -78, 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user