TMI-TIFF: Getting close to full baseline support!

- Added Modified Huffman decoding (needs a proper test image)
 - Improved predictor support (16/32 bpp)
 - Fixed handling of bogus RowsPerStrip
This commit is contained in:
Harald Kuhr 2013-06-12 21:54:28 +02:00
parent cdce0aebff
commit ff3fbc8bd2
7 changed files with 670 additions and 124 deletions

View File

@ -0,0 +1,452 @@
/*
* Copyright (c) 2012, 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;
/**
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
*/
final class CCITTFaxDecoderStream extends FilterInputStream {
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
private final int columns;
private final byte[] decodedRow;
private int decodedLength;
private int decodedPos;
private int bitBuffer;
private int bitBufferLength;
// Need to take fill order into account (?) (use flip table?)
private final int fillOrder;
private final int type;
private final int[] changes;
private int changesCount;
private static final int EOL_CODE = 0x01; // 12 bit
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) {
super(Validate.notNull(stream, "stream"));
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
// We know this is only used for b/w (1 bit)
this.decodedRow = new byte[(columns + 7) / 8];
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4
this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
this.changes = new int[columns];
}
// IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table?
// -- If white run, start at 4 bits to determine length, if black, start at 2 bits
private void fetch() throws IOException {
if (decodedPos >= decodedLength) {
decodedLength = 0;
try {
decodeRow();
}
catch (EOFException e) {
// TODO: Rewrite to avoid throw/catch for normal flow...
if (decodedLength != 0) {
throw e;
}
// ..otherwise, just client code trying to read past the end of stream
decodedLength = -1;
}
decodedPos = 0;
}
}
private void decodeRow() throws IOException {
resetBuffer();
boolean literalRun = true;
/*
if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
int eol = readBits(12);
System.err.println("eol: " + eol);
while (eol != EOL_CODE) {
eol = readBits(1);
System.err.println("eol: " + eol);
// throw new IOException("Missing EOL");
}
literalRun = readBits(1) == 1;
}
System.err.println("literalRun: " + literalRun);
*/
int index = 0;
if (literalRun) {
changesCount = 0;
boolean white = true;
do {
int completeRun = 0;
int run;
do {
if (white) {
run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
}
else {
run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2);
}
completeRun += run;
}
while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes
changes[changesCount++] = index + completeRun;
// System.err.printf("%s run: %d\n", white ? "white" : "black", run);
// TODO: Optimize with lookup for 0-7 bits?
// Fill bits to byte boundary...
while (index % 8 != 0 && completeRun-- > 0) {
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
}
// ...then fill complete bytes to either 0xff or 0x00...
if (index % 8 == 0) {
final byte value = (byte) (white ? 0xff : 0x00);
while (completeRun > 7) {
decodedRow[index / 8] = value;
completeRun -= 8;
index += 8;
}
}
// ...finally fill any remaining bits
while (completeRun-- > 0) {
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
}
// Flip color for next run
white = !white;
}
while (index < columns);
}
else {
// non-literal run
}
if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) {
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
}
decodedLength = (index / 8) + 1;
}
private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException {
// TODO: Optimize...
// Looping and comparing is the most straight-forward, but probably not the most effective way...
int code = readBits(minCodeSize);
for (int bits = 0; bits < codes.length; bits++) {
short[] bitCodes = codes[bits];
for (int i = 0; i < bitCodes.length; i++) {
if (bitCodes[i] == code) {
// System.err.println("code: " + code);
// Code found, return matching run length
return runLengths[bits][i];
}
}
// No code found, read one more bit and try again
code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
}
throw new IOException("Unknown code in Huffman RLE stream");
}
private void resetBuffer() {
for (int i = 0; i < decodedRow.length; i++) {
decodedRow[i] = 0;
}
bitBuffer = 0;
bitBufferLength = 0;
}
private int readBits(int bitCount) throws IOException {
while (bitBufferLength < bitCount) {
int read = in.read();
if (read == -1) {
throw new EOFException("Unexpected end of Huffman RLE stream");
}
int bits = read & 0xff;
bitBuffer = (bitBuffer << 8) | bits;
bitBufferLength += 8;
}
// TODO: Take fill order into account
bitBufferLength -= bitCount;
int result = bitBuffer >> bitBufferLength;
bitBuffer &= (1 << bitBufferLength) - 1;
return result;
}
@Override
public int read() throws IOException {
if (decodedLength < 0) {
return -1;
}
if (decodedPos >= decodedLength) {
fetch();
if (decodedLength < 0) {
return -1;
}
}
return decodedRow[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(decodedRow, 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");
}
static final short[][] BLACK_CODES = {
{ // 2 bits
0x2, 0x3,
},
{ // 3 bits
0x2, 0x3,
},
{ // 4 bits
0x2, 0x3,
},
{ // 5 bits
0x3,
},
{ // 6 bits
0x4, 0x5,
},
{ // 7 bits
0x4, 0x5, 0x7,
},
{ // 8 bits
0x4, 0x7,
},
{ // 9 bits
0x18,
},
{ // 10 bits
0x17, 0x18, 0x37, 0x8, 0xf,
},
{ // 11 bits
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
},
{ // 12 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
},
{ // 13 bits
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
0x74, 0x75, 0x76, 0x77,
}
};
static final short[][] BLACK_RUN_LENGTHS = {
{ // 2 bits
3, 2,
},
{ // 3 bits
1, 4,
},
{ // 4 bits
6, 5,
},
{ // 5 bits
7,
},
{ // 6 bits
9, 8,
},
{ // 7 bits
10, 11, 12,
},
{ // 8 bits
13, 14,
},
{ // 9 bits
15,
},
{ // 10 bits
16, 17, 0, 18, 64,
},
{ // 11 bits
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
},
{ // 12 bits
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320,
384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49,
62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35,
36, 37, 38, 39, 42, 43,
},
{ // 13 bits
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960,
1024, 1088, 1152, 1216,
}
};
public static final short[][] WHITE_CODES = {
{ // 4 bits
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
},
{ // 5 bits
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
},
{ // 6 bits
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
},
{ // 7 bits
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
},
{ // 8 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55,
0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
},
{ // 9 bits
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
},
{ // 10 bits
},
{ // 11 bits
0x8, 0xc, 0xd,
},
{ // 12 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
}
};
public static final short[][] WHITE_RUN_LENGTHS = {
{ // 4 bits
2, 3, 4, 5, 6, 7,
},
{ // 5 bits
128, 8, 9, 64, 10, 11,
},
{ // 6 bits
192, 1664, 16, 17, 13, 14, 15, 1, 12,
},
{ // 7 bits
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
},
{ // 8 bits
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43,
44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51,
52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
},
{ // 9 bits
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
},
{ // 10 bits
},
{ // 11 bits
1792, 1856, 1920,
},
{ // 12 bits
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
}
};
}

View File

@ -1,47 +0,0 @@
/*
* Copyright (c) 2012, 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.enc.Decoder;
import java.io.IOException;
import java.io.InputStream;
/**
* CCITT Group 3 One-Dimensional (G31D) "No EOLs" Decoder.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: G31DDecoder.java,v 1.0 23.05.12 15:55 haraldk Exp$
*/
final class G31DDecoder implements Decoder {
public int decode(final InputStream stream, final byte[] buffer) throws IOException {
throw new UnsupportedOperationException("Method decode not implemented"); // TODO: Implement
}
}

View File

@ -37,7 +37,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
*/
interface TIFFBaseline {
int COMPRESSION_NONE = 1;
int COMPRESSION_CCITT_HUFFMAN = 2;
int COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE = 2;
int COMPRESSION_PACKBITS = 32773;
int PHOTOMETRIC_WHITE_IS_ZERO = 0;

View File

@ -49,7 +49,7 @@ interface TIFFCustom {
int COMPRESSION_JBIG = 34661;
int COMPRESSION_SGILOG = 34676;
int COMPRESSION_SGILOG24 = 34677;
int COMPRESSION_JP2000 = 34712;
int COMPRESSION_JPEG2000 = 34712;
int PHOTOMETRIC_LOGL = 32844;
int PHOTOMETRIC_LOGLUV = 32845;

View File

@ -106,16 +106,16 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Source region (*tests should be failing*)
// TODO: TIFFImageWriter + Spi
// TODOs Full BaseLine support:
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
// (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
// TODOs ImageIO advanced functionality:
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
// For some layouts, we could do reads super-fast with a memory mapped buffer.
// TODO: Implement readAsRaster directly
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
// TODOs Full BaseLine support:
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
// (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
// TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
// TODOs Extension support
// TODO: Support PlanarConfiguration 2
@ -127,6 +127,7 @@ public class TIFFImageReader extends ImageReaderBase {
// DONE:
// Handle SampleFormat (and give up if not == 1)
// Support Compression 6 ('Old-style' JPEG)
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
@ -175,7 +176,7 @@ public class TIFFImageReader extends ImageReaderBase {
return IFDs.directoryCount();
}
private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException {
private Number getValueAsNumberWithDefault(final int tag, final String tagName, final Number defaultValue) throws IIOException {
Entry entry = currentIFD.getEntryById(tag);
if (entry == null) {
@ -186,7 +187,19 @@ public class TIFFImageReader extends ImageReaderBase {
throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag));
}
return ((Number) entry.getValue()).intValue();
return (Number) entry.getValue();
}
private long getValueAsLongWithDefault(final int tag, final String tagName, final Long defaultValue) throws IIOException {
return getValueAsNumberWithDefault(tag, tagName, defaultValue).longValue();
}
private long getValueAsLongWithDefault(final int tag, final Long defaultValue) throws IIOException {
return getValueAsLongWithDefault(tag, null, defaultValue);
}
private int getValueAsIntWithDefault(final int tag, final String tagName, final Integer defaultValue) throws IIOException {
return getValueAsNumberWithDefault(tag, tagName, defaultValue).intValue();
}
private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException {
@ -364,7 +377,6 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFBaseline.PHOTOMETRIC_MASK:
// Transparency mask
// TODO: Known extensions
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
default:
throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
@ -464,7 +476,9 @@ public class TIFFImageReader extends ImageReaderBase {
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
// Strips are top/down, tiles are left/right, top/down
int stripTileWidth = width;
int stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_ROWS_PER_STRIP, height);
long rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1l << 32) - 1);
int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height;
long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
long[] stripTileByteCounts;
@ -507,6 +521,13 @@ public class TIFFImageReader extends ImageReaderBase {
// LZW
case TIFFExtension.COMPRESSION_ZLIB:
// 'Adobe-style' Deflate
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
// CCITT modified Huffman
// Additionally, the specification defines these values as part of the TIFF extensions:
// case TIFFExtension.COMPRESSION_CCITT_T4:
// CCITT Group 3 fax encoding
// case TIFFExtension.COMPRESSION_CCITT_T6:
// CCITT Group 4 fax encoding
int[] yCbCrSubsampling = null;
int yCbCrPos = 1;
@ -585,7 +606,8 @@ public class TIFFImageReader extends ImageReaderBase {
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
: IIOUtil.createStreamAdapter(imageInput);
adapter = createDecoderInputStream(compression, adapter);
adapter = createDecompressorStream(compression, width, adapter);
adapter = createUnpredictorStream(predictor, width, planarConfiguration == 2 ? 1 : raster.getNumBands(), getBitsPerSample(), adapter, imageInput.getByteOrder());
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients);
@ -598,7 +620,7 @@ public class TIFFImageReader extends ImageReaderBase {
}
// Read a full strip/tile
readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input);
readStripTileData(rowRaster, interpretation, raster, col, row, colsInTile, rowsInTile, input);
if (abortRequested()) {
break;
@ -917,14 +939,28 @@ public class TIFFImageReader extends ImageReaderBase {
break;
case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN:
// CCITT modified Huffman
// Additionally, the specification defines these values as part of the TIFF extensions:
case TIFFExtension.COMPRESSION_CCITT_T4:
// CCITT Group 3 fax encoding
case TIFFExtension.COMPRESSION_CCITT_T6:
// CCITT Group 4 fax encoding
// Known, but unsupported compression types
case TIFFCustom.COMPRESSION_NEXT:
case TIFFCustom.COMPRESSION_CCITTRLEW:
case TIFFCustom.COMPRESSION_THUNDERSCAN:
case TIFFCustom.COMPRESSION_IT8CTPAD:
case TIFFCustom.COMPRESSION_IT8LW:
case TIFFCustom.COMPRESSION_IT8MP:
case TIFFCustom.COMPRESSION_IT8BL:
case TIFFCustom.COMPRESSION_PIXARFILM:
case TIFFCustom.COMPRESSION_PIXARLOG:
case TIFFCustom.COMPRESSION_DCS:
case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin?
case TIFFCustom.COMPRESSION_SGILOG:
case TIFFCustom.COMPRESSION_SGILOG24:
case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin?
throw new IIOException("Unsupported TIFF Compression value: " + compression);
default:
throw new IIOException("Unknown TIFF Compression value: " + compression);
@ -1004,8 +1040,8 @@ public class TIFFImageReader extends ImageReaderBase {
return stream.createInputStream();
}
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
final WritableRaster raster, final int numBands, final int col, final int startRow,
private void readStripTileData(final WritableRaster rowRaster, final int interpretation,
final WritableRaster raster, final int col, final int startRow,
final int colsInStrip, final int rowsInStrip, final DataInput input)
throws IOException {
switch (rowRaster.getTransferType()) {
@ -1020,8 +1056,6 @@ public class TIFFImageReader extends ImageReaderBase {
}
input.readFully(rowData);
unPredict(predictor, colsInStrip, 1, numBands, rowData);
normalizeBlack(interpretation, rowData);
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
@ -1048,7 +1082,6 @@ public class TIFFImageReader extends ImageReaderBase {
rowDataShort[k] = input.readShort();
}
unPredict(predictor, colsInStrip, 1, numBands, rowDataShort);
normalizeBlack(interpretation, rowDataShort);
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
@ -1075,7 +1108,6 @@ public class TIFFImageReader extends ImageReaderBase {
rowDataInt[k] = input.readInt();
}
unPredict(predictor, colsInStrip, 1, numBands, rowDataInt);
normalizeBlack(interpretation, rowDataInt);
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
@ -1118,61 +1150,7 @@ public class TIFFImageReader extends ImageReaderBase {
}
}
@SuppressWarnings("UnusedParameters")
private void unPredict(final int predictor, int scanLine, int rows, int bands, int[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFBaseline.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
// TODO: Implement
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
@SuppressWarnings("UnusedParameters")
private void unPredict(final int predictor, int scanLine, int rows, int bands, short[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFBaseline.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
// TODO: Implement
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
private void unPredict(final int predictor, int scanLine, int rows, final int bands, byte[] data) throws IIOException {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
switch (predictor) {
case TIFFBaseline.PREDICTOR_NONE:
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
for (int y = 0; y < rows; y++) {
for (int x = 1; x < scanLine; x++) {
// TODO: For planar data (PlanarConfiguration == 2), treat as bands == 1
for (int b = 0; b < bands; b++) {
int off = y * scanLine + x;
data[off * bands + b] = (byte) (data[(off - 1) * bands + b] + data[off * bands + b]);
}
}
}
break;
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException {
private InputStream createDecompressorStream(final int compression, final int width, final InputStream stream) throws IOException {
switch (compression) {
case TIFFBaseline.COMPRESSION_NONE:
return stream;
@ -1181,14 +1159,31 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFExtension.COMPRESSION_LZW:
return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), 1024);
case TIFFExtension.COMPRESSION_ZLIB:
case TIFFExtension.COMPRESSION_DEFLATE:
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
case TIFFExtension.COMPRESSION_DEFLATE:
return new InflaterInputStream(stream, new Inflater(), 1024);
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
case TIFFExtension.COMPRESSION_CCITT_T4:
case TIFFExtension.COMPRESSION_CCITT_T6:
return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1));
default:
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
}
}
private InputStream createUnpredictorStream(final int predictor, final int width, final int samplesPerPixel, final int bitsPerSample, final InputStream stream, final ByteOrder byteOrder) throws IOException {
switch (predictor) {
case TIFFBaseline.PREDICTOR_NONE:
return stream;
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder);
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
}
}
private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
Entry entry = currentIFD.getEntryById(tag);
if (entry == null) {

View File

@ -0,0 +1,137 @@
<!DOCTYPE com_sun_media_imageio_plugins_tiff_image_1.0 [
<!ELEMENT com_sun_media_imageio_plugins_tiff_image_1.0 (TIFFIFD)*>
<!ELEMENT TIFFIFD (TIFFField | TIFFIFD)*>
<!-- An IFD (directory) containing fields -->
<!ATTLIST TIFFIFD "tagSets" CDATA #REQUIRED>
<!-- Data type: String -->
<!ATTLIST TIFFIFD "parentTagNumber" CDATA #IMPLIED>
<!-- The tag number of the field pointing to this IFD -->
<!-- Data type: Integer -->
<!ATTLIST TIFFIFD "parentTagName" CDATA #IMPLIED>
<!-- A mnemonic name for the field pointing to this IFD, if known
-->
<!-- Data type: String -->
<!ELEMENT TIFFField (TIFFBytes | TIFFAsciis | TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs | TIFFRationals | TIFFSRationals | TIFFFloats | TIFFDoubles | TIFFUndefined)>
<!-- A field containing data -->
<!ATTLIST TIFFField "number" CDATA #REQUIRED>
<!-- The tag number asociated with the field -->
<!-- Data type: String -->
<!ATTLIST TIFFField "name" CDATA #IMPLIED>
<!-- A mnemonic name associated with the field, if known -->
<!-- Data type: String -->
<!ELEMENT TIFFBytes (TIFFByte)*>
<!-- A sequence of TIFFByte nodes -->
<!ELEMENT TIFFByte EMPTY>
<!-- An integral value between 0 and 255 -->
<!ATTLIST TIFFByte "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ATTLIST TIFFByte "description" CDATA #IMPLIED>
<!-- A description, if available -->
<!-- Data type: String -->
<!ELEMENT TIFFAsciis (TIFFAscii)*>
<!-- A sequence of TIFFAscii nodes -->
<!ELEMENT TIFFAscii EMPTY>
<!-- A String value -->
<!ATTLIST TIFFAscii "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ELEMENT TIFFShorts (TIFFShort)*>
<!-- A sequence of TIFFShort nodes -->
<!ELEMENT TIFFShort EMPTY>
<!-- An integral value between 0 and 65535 -->
<!ATTLIST TIFFShort "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ATTLIST TIFFShort "description" CDATA #IMPLIED>
<!-- A description, if available -->
<!-- Data type: String -->
<!ELEMENT TIFFSShorts (TIFFSShort)*>
<!-- A sequence of TIFFSShort nodes -->
<!ELEMENT TIFFSShort EMPTY>
<!-- An integral value between -32768 and 32767 -->
<!ATTLIST TIFFSShort "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ATTLIST TIFFSShort "description" CDATA #IMPLIED>
<!-- A description, if available -->
<!-- Data type: String -->
<!ELEMENT TIFFLongs (TIFFLong)*>
<!-- A sequence of TIFFLong nodes -->
<!ELEMENT TIFFLong EMPTY>
<!-- An integral value between 0 and 4294967295 -->
<!ATTLIST TIFFLong "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ATTLIST TIFFLong "description" CDATA #IMPLIED>
<!-- A description, if available -->
<!-- Data type: String -->
<!ELEMENT TIFFSLongs (TIFFSLong)*>
<!-- A sequence of TIFFSLong nodes -->
<!ELEMENT TIFFSLong EMPTY>
<!-- An integral value between -2147483648 and 2147482647 -->
<!ATTLIST TIFFSLong "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ATTLIST TIFFSLong "description" CDATA #IMPLIED>
<!-- A description, if available -->
<!-- Data type: String -->
<!ELEMENT TIFFRationals (TIFFRational)*>
<!-- A sequence of TIFFRational nodes -->
<!ELEMENT TIFFRational EMPTY>
<!-- A rational value consisting of an unsigned numerator and
denominator -->
<!ATTLIST TIFFRational "value" CDATA #IMPLIED>
<!-- The numerator and denominator, separated by a slash -->
<!-- Data type: String -->
<!ELEMENT TIFFSRationals (TIFFSRational)*>
<!-- A sequence of TIFFSRational nodes -->
<!ELEMENT TIFFSRational EMPTY>
<!-- A rational value consisting of a signed numerator and
denominator -->
<!ATTLIST TIFFSRational "value" CDATA #IMPLIED>
<!-- The numerator and denominator, separated by a slash -->
<!-- Data type: String -->
<!ELEMENT TIFFFloats (TIFFFloat)*>
<!-- A sequence of TIFFFloat nodes -->
<!ELEMENT TIFFFloat EMPTY>
<!-- A single-precision floating-point value -->
<!ATTLIST TIFFFloat "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ELEMENT TIFFDoubles (TIFFDouble)*>
<!-- A sequence of TIFFDouble nodes -->
<!ELEMENT TIFFDouble EMPTY>
<!-- A double-precision floating-point value -->
<!ATTLIST TIFFDouble "value" CDATA #IMPLIED>
<!-- The value -->
<!-- Data type: String -->
<!ELEMENT TIFFUndefined EMPTY>
<!-- Uninterpreted byte data -->
<!ATTLIST TIFFUndefined "value" CDATA #IMPLIED>
<!-- A list of comma-separated byte values -->
<!-- Data type: String -->
]>

View File

@ -0,0 +1,9 @@
<!DOCTYPE com_sun_media_imageio_plugins_tiff_stream_1.0 [
<!ELEMENT com_sun_media_imageio_plugins_tiff_stream_1.0 (ByteOrder)>
<!ELEMENT ByteOrder EMPTY>
<!-- The stream byte order -->
<!ATTLIST ByteOrder "value" CDATA #REQUIRED>
<!-- One of "BIG_ENDIAN" or "LITTLE_ENDIAN" -->
<!-- Data type: String -->
]>