mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 02:55:28 -04:00
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:
parent
cdce0aebff
commit
ff3fbc8bd2
@ -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,
|
||||
}
|
||||
};
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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 -->
|
||||
]>
|
@ -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 -->
|
||||
]>
|
Loading…
x
Reference in New Issue
Block a user