mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -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;
|
||||
|
||||
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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 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));
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.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
|
||||
|
Loading…
x
Reference in New Issue
Block a user