mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 11:05:29 -04:00
TMI-TIFF: Renamed "Old-style" JPEG constants, to discourage use.
Removed hardcoding of JFIF-stream for "Old-style" JPEG reading + cleaned up code. Fixed a bug in getRawImageType for planar RGBA
This commit is contained in:
parent
c394f8a4bc
commit
8b9d5c7abc
@ -113,6 +113,7 @@ public interface TIFF {
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
@ -162,11 +163,12 @@ public interface TIFF {
|
||||
int TAG_TILE_OFFSETS = 324;
|
||||
int TAG_TILE_BYTE_COUNTS = 325;
|
||||
|
||||
// JPEG
|
||||
int TAG_JPEG_TABLES = 347;
|
||||
|
||||
// "Old-style" JPEG (Obsolete)
|
||||
int TAG_JPEG_PROC = 512;
|
||||
int TAG_JPEG_QTABLES = 519;
|
||||
int TAG_JPEG_DCTABLES = 520;
|
||||
int TAG_JPEG_ACTABLES = 521;
|
||||
// "Old-style" JPEG (Obsolete) DO NOT WRITE!
|
||||
int TAG_OLD_JPEG_PROC = 512;
|
||||
int TAG_OLD_JPEG_QTABLES = 519;
|
||||
int TAG_OLD_JPEG_DCTABLES = 520;
|
||||
int TAG_OLD_JPEG_ACTABLES = 521;
|
||||
}
|
||||
|
@ -37,11 +37,13 @@ import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
@ -289,7 +291,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, false);
|
||||
|
||||
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
|
||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, false);
|
||||
}
|
||||
}
|
||||
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
||||
@ -730,7 +732,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
||||
// TODO: Issue warning?
|
||||
|
||||
int mode = getValueAsIntWithDefault(TIFF.TAG_JPEG_PROC, 1);
|
||||
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, 1);
|
||||
if (mode == TIFFExtension.JPEG_PROC_LOSSLESS) {
|
||||
throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)");
|
||||
}
|
||||
@ -774,14 +776,18 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
||||
int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
||||
|
||||
ImageInputStream subStream;
|
||||
ImageInputStream stream;
|
||||
|
||||
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_QTABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DCTABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_ACTABLES) != null) {
|
||||
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile, ignoring tables.");
|
||||
}
|
||||
|
||||
imageInput.seek(jpegOffset);
|
||||
subStream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
|
||||
jpegReader.setInput(subStream);
|
||||
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
|
||||
jpegReader.setInput(stream);
|
||||
|
||||
// Read data
|
||||
processImageStarted(imageIndex);
|
||||
@ -794,7 +800,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
jpegReader.read(0, jpegParam);
|
||||
}
|
||||
finally {
|
||||
subStream.close();
|
||||
stream.close();
|
||||
}
|
||||
|
||||
processImageProgress(100f * row / (float) height);
|
||||
@ -806,18 +812,21 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
else {
|
||||
// The hard way: Read tables and re-create a full JFIF stream
|
||||
|
||||
processWarningOccurred("Old-style JPEG compressed TIFF without JFIF stream encountered. Attempting to re-create JFIF stream.");
|
||||
|
||||
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
||||
// use only the first occurrence, and update selectors in SOF0 and SOS
|
||||
|
||||
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_QTABLES, "JPEGQTables", true);
|
||||
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_QTABLES, "JPEGQTables", true);
|
||||
byte[][] qTables = new byte[3][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets seems fragile.. Use fixed length??
|
||||
for (int j = 0; j < 3; j++) {
|
||||
imageInput.seek(qTablesOffsets[j]);
|
||||
imageInput.readFully(qTables[j]);
|
||||
}
|
||||
|
||||
// System.err.println("qTables: " + qTables[0].length);
|
||||
|
||||
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_DCTABLES, "JPEGDCTables", true);
|
||||
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DCTABLES, "JPEGDCTables", true);
|
||||
byte[][] dcTables = new byte[3][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
imageInput.seek(dcTablesOffsets[j]);
|
||||
@ -825,7 +834,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
// System.err.println("dcTables: " + dcTables[0].length);
|
||||
|
||||
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_JPEG_ACTABLES, "JPEGACTables", true);
|
||||
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_ACTABLES, "JPEGACTables", true);
|
||||
byte[][] acTables = new byte[3][(int) (acTablesOffsets[1] - acTablesOffsets[0])];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
imageInput.seek(acTablesOffsets[j]);
|
||||
@ -845,86 +854,79 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
int i = y * tilesAcross + x;
|
||||
|
||||
imageInput.seek(stripTileOffsets[i]);
|
||||
subStream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||
FastByteArrayOutputStream jfifBytes = new FastByteArrayOutputStream(
|
||||
2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
|
||||
5 * qTables.length + qTables.length * qTables[0].length +
|
||||
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
||||
5 * acTables.length + acTables.length * acTables[0].length +
|
||||
8 + 2 * raster.getNumBands()
|
||||
);
|
||||
DataOutputStream out = new DataOutputStream(jfifBytes);
|
||||
|
||||
out.writeShort(JPEG.SOI);
|
||||
out.writeShort(JPEG.SOF0);
|
||||
out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
|
||||
out.writeByte(8); // bits TODO: Consult raster/transfer type for 12/16 bits support
|
||||
out.writeShort(stripTileHeight); // height
|
||||
out.writeShort(stripTileWidth); // width
|
||||
out.writeByte(raster.getNumBands()); // Number of components
|
||||
|
||||
for (int comp = 0; comp < raster.getNumBands(); comp++) {
|
||||
out.writeByte(comp); // Component id
|
||||
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
|
||||
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
|
||||
}
|
||||
|
||||
// TODO: Consider merging if tables are equal
|
||||
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
||||
byte[] table = qTables[tableIndex];
|
||||
out.writeShort(JPEG.DQT);
|
||||
out.writeShort(3 + table.length); // DQT length
|
||||
out.writeByte(tableIndex); // Q table id
|
||||
out.write(table); // Table data
|
||||
}
|
||||
|
||||
// TODO: Consider merging if tables are equal
|
||||
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
||||
byte[] table = dcTables[tableIndex];
|
||||
out.writeShort(JPEG.DHT);
|
||||
out.writeShort(3 + table.length); // DHT length
|
||||
out.writeByte(tableIndex); // Huffman table id
|
||||
out.write(table); // Table data
|
||||
}
|
||||
|
||||
// TODO: Consider merging if tables are equal
|
||||
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
||||
byte[] table = acTables[tableIndex];
|
||||
out.writeShort(JPEG.DHT);
|
||||
out.writeShort(3 + table.length); // DHT length
|
||||
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
||||
out.write(table); // Table data
|
||||
}
|
||||
|
||||
out.writeShort(JPEG.SOS);
|
||||
out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
|
||||
out.writeByte(raster.getNumBands()); // Num comp
|
||||
|
||||
for (int component = 0; component < raster.getNumBands(); component++) {
|
||||
out.writeByte(component); // Comp id
|
||||
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
||||
}
|
||||
|
||||
// Unknown 3 bytes pad... TODO: Figure out what the last 3 bytes are...
|
||||
out.writeByte(0);
|
||||
out.writeByte(0);
|
||||
out.writeByte(0);
|
||||
|
||||
stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||
Arrays.asList(
|
||||
// TODO; Get rid of hardcoded data + extract method/class...
|
||||
// TODO:
|
||||
// - Create a BAIS with size large enough to keep JFIF structure incl tables and SOS,
|
||||
// - Wrap in DataInput,
|
||||
// - Insert width/height, component ids etc at correct place
|
||||
// - Insert tables at correct place
|
||||
|
||||
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd8, // SOI
|
||||
// SOF0 (short), length (short)
|
||||
(byte) 0xff, (byte) 0xc0, 0x00, 0x11, // SOF0, 17 bytes
|
||||
// bits (byte), width (short), height (short)
|
||||
0x08, 0x00, (byte) 0xe0, 0x00, (byte) 0xf0,
|
||||
// num comp (byte), (id (byte) h/vsub (byte), qtsel (byte) * num comp)
|
||||
0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x02,
|
||||
// 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, 0x02, 0x11, 0x01,
|
||||
// DQT
|
||||
(byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x00,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(qTables[0]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x01,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(qTables[1]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xdb, 0x00, 0x43, 0x02,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(qTables[2]),
|
||||
|
||||
// DHT (DC)
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x00,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(dcTables[0]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x01,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(dcTables[1]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, 0x1f, 0x02,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(dcTables[2]),
|
||||
|
||||
// DHT (AC)
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x10,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(acTables[0]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x11,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(acTables[1]),
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xc4, 0x00, (byte) 0xb5, 0x12,
|
||||
}),
|
||||
// ... table data
|
||||
new ByteArrayInputStream(acTables[2]),
|
||||
|
||||
new ByteArrayInputStream(new byte[] {
|
||||
(byte) 0xff, (byte) 0xda, // SOS
|
||||
// TODO: Figure out what the last 3 bytes are...
|
||||
// Length: 12 (short), num comp (byte), (id (byte), dc/ac sel (byte) * num comp), ?? byte, ?? byte ?? byte
|
||||
0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x00, 0x00
|
||||
// 0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02, 0x12, 0x00, 0x63, 0x00
|
||||
}),
|
||||
jfifBytes.createInputStream(),
|
||||
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE),
|
||||
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
||||
)
|
||||
)));
|
||||
|
||||
jpegReader.setInput(subStream);
|
||||
jpegReader.setInput(stream);
|
||||
|
||||
try {
|
||||
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
||||
@ -935,7 +937,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
jpegReader.read(0, jpegParam);
|
||||
}
|
||||
finally {
|
||||
subStream.close();
|
||||
stream.close();
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user