mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
#265: Fix for old-style JPEG compressed TIFFs with incorrect JPEGInterchangeFormatLength
This commit is contained in:
parent
a4a314a0f9
commit
c2aa7e3150
@ -1047,23 +1047,20 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// May use normal tiling??
|
|
||||||
|
|
||||||
jpegReader = createJPEGDelegate();
|
jpegReader = createJPEGDelegate();
|
||||||
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
||||||
|
|
||||||
// 513/JPEGInterchangeFormat (may be absent...)
|
// 513/JPEGInterchangeFormat (may be absent or 0)
|
||||||
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
||||||
// 514/JPEGInterchangeFormatLength (may be absent...)
|
// 514/JPEGInterchangeFormatLength (may be absent)
|
||||||
int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
int jpegLength = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
||||||
// TODO: 515/JPEGRestartInterval (may be absent)
|
// TODO: 515/JPEGRestartInterval (may be absent)
|
||||||
|
|
||||||
// Currently ignored (for lossless only)
|
// Currently ignored (for lossless only)
|
||||||
// 517/JPEGLosslessPredictors
|
// 517/JPEGLosslessPredictors
|
||||||
// 518/JPEGPointTransforms
|
// 518/JPEGPointTransforms
|
||||||
|
|
||||||
|
if (jpegOffset > 0) {
|
||||||
if (jpegOffset != -1) {
|
|
||||||
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
||||||
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
|
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
|
||||||
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
|
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
|
||||||
@ -1100,13 +1097,34 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInput.seek(realJPEGOffset);
|
// Determine correct JPEG stream length
|
||||||
|
int length;
|
||||||
|
if (jpegLength == -1) {
|
||||||
|
// If have no length, we'll just try to decode, as long as we can
|
||||||
|
length = Integer.MAX_VALUE;
|
||||||
|
processWarningOccurred("Missing JPEGInterchangeFormatLength tag");
|
||||||
|
}
|
||||||
|
else if (stripTileOffsets != null && stripTileOffsets.length == 1 && stripTileOffsets[0] >= jpegOffset + jpegLength) {
|
||||||
|
// NOTE: Some known TIFF encoder writes obviously bogus JPEGInterchangeFormatLength value,
|
||||||
|
// but the real stream length can be determined from the StripByteCounts (may include padding).
|
||||||
|
if (stripTileByteCounts != null && stripTileByteCounts.length == 1 && stripTileByteCounts[0] > jpegLength) {
|
||||||
|
length = (int) (jpegLength + stripTileByteCounts[0]);
|
||||||
|
processWarningOccurred("Incorrect JPEGInterchangeFormatLength tag encountered, using StripByteCounts instead");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// No StripByteCounts, we'll just try tro decode as much as we can
|
||||||
|
length = Integer.MAX_VALUE;
|
||||||
|
processWarningOccurred("Incorrect JPEGInterchangeFormatLength tag encountered, ignoring tag value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Ok! We'll go with JPEGInterchangeFormatLength
|
||||||
|
length = jpegLength;
|
||||||
|
}
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
|
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
|
||||||
|
imageInput.seek(realJPEGOffset);
|
||||||
int length = jpegLenght != -1 ? jpegLenght : Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
try (ImageInputStream stream = new SubImageInputStream(imageInput, length)) {
|
try (ImageInputStream stream = new SubImageInputStream(imageInput, length)) {
|
||||||
jpegReader.setInput(stream);
|
jpegReader.setInput(stream);
|
||||||
@ -1199,7 +1217,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
createJFIFStream(destRaster, stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
|
createJFIFStream(destRaster.getNumBands(), stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
|
||||||
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null
|
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null
|
||||||
? (int) stripTileByteCounts[i]
|
? (int) stripTileByteCounts[i]
|
||||||
: Short.MAX_VALUE),
|
: Short.MAX_VALUE),
|
||||||
@ -1298,26 +1316,26 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return readers.next();
|
return readers.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputStream createJFIFStream(WritableRaster raster, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
private static InputStream createJFIFStream(int bands, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
||||||
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
|
2 + 2 + 2 + 6 + 3 * bands +
|
||||||
5 * qTables.length + qTables.length * qTables[0].length +
|
5 * qTables.length + qTables.length * qTables[0].length +
|
||||||
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
||||||
5 * acTables.length + acTables.length * acTables[0].length +
|
5 * acTables.length + acTables.length * acTables[0].length +
|
||||||
8 + 2 * raster.getNumBands()
|
8 + 2 * bands
|
||||||
);
|
);
|
||||||
|
|
||||||
DataOutputStream out = new DataOutputStream(stream);
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
out.writeShort(JPEG.SOI);
|
out.writeShort(JPEG.SOI);
|
||||||
out.writeShort(JPEG.SOF0);
|
out.writeShort(JPEG.SOF0);
|
||||||
out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
|
out.writeShort(2 + 6 + 3 * bands); // SOF0 len
|
||||||
out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
|
out.writeByte(8); // bits TODO: Consult bands/transfer type or BitsPerSample for 12/16 bits support
|
||||||
out.writeShort(stripTileHeight); // height
|
out.writeShort(stripTileHeight); // height
|
||||||
out.writeShort(stripTileWidth); // width
|
out.writeShort(stripTileWidth); // width
|
||||||
out.writeByte(raster.getNumBands()); // Number of components
|
out.writeByte(bands); // Number of components
|
||||||
|
|
||||||
for (int comp = 0; comp < raster.getNumBands(); comp++) {
|
for (int comp = 0; comp < bands; comp++) {
|
||||||
out.writeByte(comp); // Component id
|
out.writeByte(comp); // Component id
|
||||||
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
|
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
|
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
|
||||||
@ -1351,10 +1369,10 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out.writeShort(JPEG.SOS);
|
out.writeShort(JPEG.SOS);
|
||||||
out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
|
out.writeShort(6 + 2 * bands); // SOS length
|
||||||
out.writeByte(raster.getNumBands()); // Num comp
|
out.writeByte(bands); // Num comp
|
||||||
|
|
||||||
for (int component = 0; component < raster.getNumBands(); component++) {
|
for (int component = 0; component < bands; component++) {
|
||||||
out.writeByte(component); // Comp id
|
out.writeByte(component); // Comp id
|
||||||
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
||||||
}
|
}
|
||||||
|
@ -198,6 +198,20 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadOldStyleJPEGIncorrectJPEGInterchangeFormatLength() throws IOException {
|
||||||
|
TestData testData = new TestData(getClassLoaderResource("/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif"), new Dimension(1632, 2328));
|
||||||
|
|
||||||
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
|
TIFFImageReader reader = createReader();
|
||||||
|
reader.setInput(stream);
|
||||||
|
BufferedImage image = reader.read(0);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadIncompatibleICCProfileIgnoredWithWarning() throws IOException {
|
public void testReadIncompatibleICCProfileIgnoredWithWarning() throws IOException {
|
||||||
TestData testData = new TestData(getClassLoaderResource("/tiff/rgb-with-embedded-cmyk-icc.tif"), new Dimension(1500, 1500));
|
TestData testData = new TestData(getClassLoaderResource("/tiff/rgb-with-embedded-cmyk-icc.tif"), new Dimension(1500, 1500));
|
||||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user