Further clean-up

This commit is contained in:
Harald Kuhr 2023-11-13 19:35:58 +01:00
parent ee424583c4
commit 3623a7c5dd

View File

@ -1176,34 +1176,137 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFCustom.COMPRESSION_WEBP: case TIFFCustom.COMPRESSION_WEBP:
case TIFFCustom.COMPRESSION_JBIG: case TIFFCustom.COMPRESSION_JBIG:
case TIFFCustom.COMPRESSION_JPEG2000: case TIFFCustom.COMPRESSION_JPEG2000:
readUsingDelegate(imageIndex, compression, interpretation, width, height, tilesAcross, tilesDown, stripTileWidth, stripTileHeight, srcRegion, stripTileOffsets, stripTileByteCounts, param, destination, samplesInTile); readUsingDelegate(imageIndex, compression, interpretation, width, height, tilesAcross, tilesDown, stripTileWidth, stripTileHeight, srcRegion,
new PlainTileStreamFactory(stripTileOffsets, stripTileByteCounts), param, destination, samplesInTile);
break; break;
case TIFFExtension.COMPRESSION_OLD_JPEG: { case TIFFExtension.COMPRESSION_OLD_JPEG:
// JPEG ('old-style' JPEG, later overridden in Technote2) boolean interChangeFormat = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1) >= 0;
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
// TODO: Rewrite to use readUsingDelegate with special case handling inside OldJPEGTileDecoder
int srcRow = 0; if (!interChangeFormat) {
Boolean needsCSConversion = null; processWarningOccurred("Old-style JPEG compressed TIFF without JPEGInterchangeFormat encountered. Attempting to re-create JFIF stream.");
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
switch (mode) {
case TIFFExtension.JPEG_PROC_BASELINE:
case TIFFExtension.JPEG_PROC_LOSSLESS:
break; // Supported
default:
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
} }
ImageReader jpegReader = createJPEGDelegate(); // TODO: Perhaps use the jpegTables param to the tiledecoder instead of re-creating a full JFIF stream...
ImageReadParam jpegParam = jpegReader.getDefaultReadParam(); TileStreamFactory tileStreamFactory = interChangeFormat
? new JIFTileStreamFactory(stripTileOffsets, stripTileByteCounts)
: new JPEGTablesStreamFactory(stripTileOffsets, stripTileByteCounts, stripTileWidth, stripTileHeight, destRaster.getNumBands());
readUsingDelegate(imageIndex, compression, interpretation, width, height, tilesAcross, tilesDown, stripTileWidth, stripTileHeight, srcRegion,
tileStreamFactory, param, destination, samplesInTile);
break;
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_SGILOG:
case TIFFCustom.COMPRESSION_SGILOG24:
throw new IIOException("Unsupported TIFF Compression value: " + compression);
default:
throw new IIOException("Unknown TIFF Compression value: " + compression);
}
// TODO: Convert color space from source to destination
processImageComplete();
return destination;
}
private void readUsingDelegate(int imageIndex, int compression, int interpretation, int width, int height,
int tilesAcross, int tilesDown, int stripTileWidth, int stripTileHeight, Rectangle srcRegion,
TileStreamFactory factory,
ImageReadParam param, BufferedImage destination, int samplesInTile) throws IOException {
// Read data
try (TileDecoder tileDecoder = createTileDecoder(param, compression, interpretation, tilesAcross * tilesDown, samplesInTile)) {
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
int row = 0;
for (int y = 0; y < tilesDown; y++) {
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - row);
for (int x = 0; x < tilesAcross; x++) {
int colsInTile = Math.min(stripTileWidth, width - col);
// Read only tiles that lies within region
Rectangle tileRect = new Rectangle(col, row, colsInTile, rowsInTile);
Rectangle intersection = tileRect.intersection(srcRegion);
if (!intersection.isEmpty()) {
int tileIndex = y * tilesAcross + x;
try (ImageInputStream tileStream = factory.createTileStream(tileIndex)) {
Point destinationOffset = new Point((intersection.x - srcRegion.x) / param.getSourceXSubsampling(), (intersection.y - srcRegion.y) / param.getSourceYSubsampling());
Rectangle sourceRegion = new Rectangle(intersection.x - col, intersection.y - row, intersection.width, intersection.height);
tileDecoder.decodeTile(tileStream, sourceRegion, destinationOffset, destination);
}
}
if (abortRequested()) {
break;
}
col += colsInTile;
}
processImageProgress(100f * row / height);
if (abortRequested()) {
processReadAborted();
break;
}
row += rowsInTile;
}
}
}
static abstract class TileStreamFactory {
final long[] stripTileOffsets;
final long[] stripTileByteCounts;
TileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) {
this.stripTileOffsets = stripTileOffsets;
this.stripTileByteCounts = stripTileByteCounts;
}
abstract ImageInputStream createTileStream(int tileIndex) throws IOException;
}
final class PlainTileStreamFactory extends TileStreamFactory {
PlainTileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) {
super(stripTileOffsets, stripTileByteCounts);
}
@Override
public ImageInputStream createTileStream(final int tileIndex) throws IOException {
imageInput.seek(stripTileOffsets[tileIndex]);
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[tileIndex] : Short.MAX_VALUE;
return new SubImageInputStream(imageInput, length);
}
}
final class JIFTileStreamFactory extends TileStreamFactory {
private final byte[] jpegHeader;
private long realJPEGOffset;
JIFTileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) throws IOException {
super(stripTileOffsets, stripTileByteCounts);
// 513/JPEGInterchangeFormat (may be absent or 0) // 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, or incorrect) // 514/JPEGInterchangeFormatLength (may be absent, or incorrect)
// TODO: We used to issue a warning if the value was incorrect, should we still do that?
int jpegLength = 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)
@ -1211,7 +1314,6 @@ public final class TIFFImageReader extends ImageReaderBase {
// 517/JPEGLosslessPredictors // 517/JPEGLosslessPredictors
// 518/JPEGPointTransforms // 518/JPEGPointTransforms
if (jpegOffset > 0) {
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
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) { || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
@ -1225,7 +1327,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// NOTE: Some known TIFF encoder encodes bad JPEGInterchangeFormat tags, // NOTE: Some known TIFF encoder encodes bad JPEGInterchangeFormat tags,
// but has the correct offset to the JPEG stream in the StripOffsets tag. // but has the correct offset to the JPEG stream in the StripOffsets tag.
long realJPEGOffset = jpegOffset; realJPEGOffset = jpegOffset;
short expectedSOI = (short) (imageInput.readByte() << 8 | imageInput.readByte()); short expectedSOI = (short) (imageInput.readByte() << 8 | imageInput.readByte());
if (expectedSOI != (short) JPEG.SOI) { if (expectedSOI != (short) JPEG.SOI) {
@ -1247,8 +1349,6 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
byte[] jpegHeader;
if (stripTileOffsets == null || stripTileOffsets.length == 1 && realJPEGOffset == stripTileOffsets[0]) { if (stripTileOffsets == null || stripTileOffsets.length == 1 && realJPEGOffset == stripTileOffsets[0]) {
// In this case, we'll just read everything as a single tile // In this case, we'll just read everything as a single tile
jpegHeader = new byte[0]; jpegHeader = new byte[0];
@ -1276,72 +1376,38 @@ public final class TIFFImageReader extends ImageReaderBase {
processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead."); processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead.");
stripTileByteCounts[0] = jpegLength; stripTileByteCounts[0] = jpegLength;
} }
}
// Read data @Override
processImageStarted(imageIndex); public ImageInputStream createTileStream(final int tileIndex) throws IOException {
long length = stripTileByteCounts != null ? stripTileByteCounts[tileIndex] : Integer.MAX_VALUE;
for (int y = 0; y < tilesDown; y++) { imageInput.seek(stripTileOffsets != null ? stripTileOffsets[tileIndex] : realJPEGOffset);
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - srcRow);
for (int x = 0; x < tilesAcross; x++) { return ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(asList(
int colsInTile = Math.min(stripTileWidth, width - col);
int i = y * tilesAcross + x;
// Read only tiles that lies within region
if (new Rectangle(col, srcRow, colsInTile, rowsInTile).intersects(srcRegion)) {
int len = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Integer.MAX_VALUE;
imageInput.seek(stripTileOffsets != null ? stripTileOffsets[i] : realJPEGOffset);
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(asList(
new ByteArrayInputStream(jpegHeader), new ByteArrayInputStream(jpegHeader),
createStreamAdapter(imageInput, len), createStreamAdapter(imageInput, length),
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
))))) { ))));
jpegReader.setInput(stream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
Point offset = new Point(col - srcRegion.x, srcRow - srcRegion.y);
if (needsCSConversion == null) {
needsCSConversion = needsCSConversion(compression, interpretation, readJPEGMetadataSafe(jpegReader));
}
if (!needsCSConversion) {
jpegParam.setDestinationOffset(offset);
jpegParam.setDestination(destination);
jpegReader.read(0, jpegParam);
}
else {
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
// We'll have to use readAsRaster and later apply color space conversion ourselves
Raster raster = jpegReader.readRaster(0, jpegParam);
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
destination.getRaster().setDataElements(offset.x, offset.y, raster);
}
} }
} }
if (abortRequested()) { final class JPEGTablesStreamFactory extends TileStreamFactory {
break; private final int stripTileWidth;
} private final int stripTileHeight;
private final int numBands;
private final int subsampling;
private final byte[][] acTables;
private final byte[][] dcTables;
private final byte[][] qTables;
col += colsInTile; JPEGTablesStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts, final int stripTileWidth, final int stripTileHeight, final int numBands) throws IOException {
} super(stripTileOffsets, stripTileByteCounts);
this.stripTileWidth = stripTileWidth;
this.stripTileHeight = stripTileHeight;
this.numBands = numBands;
processImageProgress(100f * srcRow / height);
if (abortRequested()) {
processReadAborted();
break;
}
srcRow += rowsInTile;
}
}
else {
// The hard way: Read tables and re-create a full JFIF stream // The hard way: Read tables and re-create a full JFIF stream
processWarningOccurred("Old-style JPEG compressed TIFF without JPEGInterchangeFormat encountered. Attempting to re-create JFIF stream.");
// 519/JPEGQTables // 519/JPEGQTables
// 520/JPEGDCTables // 520/JPEGDCTables
@ -1365,14 +1431,14 @@ public final class TIFFImageReader extends ImageReaderBase {
// use only the first occurrence, and update selectors in SOF0 and SOS // use only the first occurrence, and update selectors in SOF0 and SOS
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true); long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true);
byte[][] qTables = new byte[qTablesOffsets.length][64]; qTables = new byte[qTablesOffsets.length][64];
for (int j = 0; j < qTables.length; j++) { for (int j = 0; j < qTables.length; j++) {
imageInput.seek(qTablesOffsets[j]); imageInput.seek(qTablesOffsets[j]);
imageInput.readFully(qTables[j]); imageInput.readFully(qTables[j]);
} }
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true); long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true);
byte[][] dcTables = new byte[dcTablesOffsets.length][]; dcTables = new byte[dcTablesOffsets.length][];
for (int j = 0; j < dcTables.length; j++) { for (int j = 0; j < dcTables.length; j++) {
imageInput.seek(dcTablesOffsets[j]); imageInput.seek(dcTablesOffsets[j]);
@ -1391,7 +1457,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true); long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true);
byte[][] acTables = new byte[acTablesOffsets.length][]; acTables = new byte[acTablesOffsets.length][];
for (int j = 0; j < acTables.length; j++) { for (int j = 0; j < acTables.length; j++) {
imageInput.seek(acTablesOffsets[j]); imageInput.seek(acTablesOffsets[j]);
byte[] lengths = new byte[16]; byte[] lengths = new byte[16];
@ -1409,166 +1475,35 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
long[] yCbCrSubSampling = getValueAsLongArray(TIFF.TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling", false); long[] yCbCrSubSampling = getValueAsLongArray(TIFF.TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling", false);
int subsampling = yCbCrSubSampling != null subsampling = yCbCrSubSampling != null
? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf) ? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf)
: 0x22; : 0x22;
}
// Read data @Override
processImageStarted(imageIndex); public ImageInputStream createTileStream(final int tileIndex) throws IOException {
long length = stripTileByteCounts != null ? stripTileByteCounts[tileIndex] : Integer.MAX_VALUE;
for (int y = 0; y < tilesDown; y++) { imageInput.seek(stripTileOffsets[tileIndex]);
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - srcRow);
for (int x = 0; x < tilesAcross; x++) {
int colsInTile = Math.min(stripTileWidth, width - col);
int i = y * tilesAcross + x;
// Read only tiles that lies within region
if (new Rectangle(col, srcRow, colsInTile, rowsInTile).intersects(srcRegion)) {
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE;
imageInput.seek(stripTileOffsets[i]);
// If the tile stream starts with SOS... // If the tile stream starts with SOS...
if (x == 0 && y == 0) { if (tileIndex == 0) {
if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) { if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) {
imageInput.seek(stripTileOffsets[i] + 14); // TODO: Read from SOS length from stream, in case of gray/CMYK imageInput.seek(stripTileOffsets[tileIndex] + 14); // TODO: Read from SOS length from stream, in case of gray/CMYK
length -= 14; length -= 14;
} }
else { else {
imageInput.seek(stripTileOffsets[i]); imageInput.seek(stripTileOffsets[tileIndex]);
} }
} }
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration( return ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
asList( asList(
createJFIFStream(destRaster.getNumBands(), stripTileWidth, stripTileHeight, qTables, dcTables, acTables, subsampling), createJFIFStream(numBands, stripTileWidth, stripTileHeight, qTables, dcTables, acTables, subsampling),
createStreamAdapter(imageInput, length), createStreamAdapter(imageInput, length),
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
) )
)))) { )));
jpegReader.setInput(stream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
Point offset = new Point(col - srcRegion.x, srcRow - srcRegion.y);
if (needsCSConversion == null) {
needsCSConversion = needsCSConversion(compression, interpretation, readJPEGMetadataSafe(jpegReader));
}
if (!needsCSConversion) {
jpegParam.setDestinationOffset(offset);
jpegParam.setDestination(destination);
jpegReader.read(0, jpegParam);
}
else {
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
// We'll have to use readAsRaster and later apply color space conversion ourselves
Raster raster = jpegReader.readRaster(0, jpegParam);
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
destination.getRaster().setDataElements(offset.x, offset.y, raster);
}
}
}
if (abortRequested()) {
break;
}
col += colsInTile;
}
processImageProgress(100f * srcRow / height);
if (abortRequested()) {
processReadAborted();
break;
}
srcRow += rowsInTile;
}
}
break;
}
// 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_SGILOG:
case TIFFCustom.COMPRESSION_SGILOG24:
throw new IIOException("Unsupported TIFF Compression value: " + compression);
default:
throw new IIOException("Unknown TIFF Compression value: " + compression);
}
// TODO: Convert color space from source to destination
processImageComplete();
return destination;
}
private void readUsingDelegate(int imageIndex, int compression, int interpretation, int width, int height,
int tilesAcross, int tilesDown, int stripTileWidth, int stripTileHeight, Rectangle srcRegion,
long[] stripTileOffsets, long[] stripTileByteCounts,
ImageReadParam param, BufferedImage destination, int samplesInTile) throws IOException {
// JPEG ('new-style' JPEG)
// Read data
try (TileDecoder tileDecoder = createTileDecoder(param, compression, interpretation, tilesAcross * tilesDown, samplesInTile)) {
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
int row = 0;
for (int y = 0; y < tilesDown; y++) {
int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - row);
for (int x = 0; x < tilesAcross; x++) {
int i = y * tilesAcross + x;
int colsInTile = Math.min(stripTileWidth, width - col);
// Read only tiles that lies within region
Rectangle tileRect = new Rectangle(col, row, colsInTile, rowsInTile);
Rectangle intersection = tileRect.intersection(srcRegion);
if (!intersection.isEmpty()) {
imageInput.seek(stripTileOffsets[i]);
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE;
try (ImageInputStream subStream = new SubImageInputStream(imageInput, length)) {
Point destinationOffset = new Point((intersection.x - srcRegion.x) / param.getSourceXSubsampling(), (intersection.y - srcRegion.y) / param.getSourceYSubsampling());
Rectangle sourceRegion = new Rectangle(intersection.x - col, intersection.y - row, intersection.width, intersection.height);
tileDecoder.decodeTile(subStream, sourceRegion, destinationOffset, destination);
}
}
if (abortRequested()) {
break;
}
col += colsInTile;
}
processImageProgress(100f * row / height);
if (abortRequested()) {
processReadAborted();
break;
}
row += rowsInTile;
}
} }
} }
@ -1576,38 +1511,59 @@ public final class TIFFImageReader extends ImageReaderBase {
try { try {
IIOReadWarningListener warningListener = (source, warning) -> processWarningOccurred(warning); IIOReadWarningListener warningListener = (source, warning) -> processWarningOccurred(warning);
if (compression == TIFFExtension.COMPRESSION_JPEG) { switch (compression) {
case TIFFExtension.COMPRESSION_JPEG:
// New style JPEG
case TIFFExtension.COMPRESSION_OLD_JPEG: {
// JPEG ('old-style' JPEG, later overridden in Technote2)
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing: // JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI // SOI, DQT, DHT, (optional markers that we ignore)..., EOI
byte[] jpegTables = null;
if (compression == TIFFExtension.COMPRESSION_JPEG) {
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES); Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
byte[] jpegTables = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null; jpegTables = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
}
else {
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
switch (mode) {
case TIFFExtension.JPEG_PROC_BASELINE:
case TIFFExtension.JPEG_PROC_LOSSLESS:
break; // Supported
default:
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
}
// TODO: Consider re-factoring the super-complicated stream stuff to just using jpegTable also for old style?
}
Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader)); Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader));
RasterConverter normalize = (raster) -> normalizeColor(interpretation, samplesInTile, raster); RasterConverter normalize = (raster) -> normalizeColor(interpretation, samplesInTile, raster);
return new JPEGTileDecoder(warningListener, jpegTables, numTiles, param, needsConversion, normalize); return new JPEGTileDecoder(warningListener, jpegTables, numTiles, param, needsConversion, normalize);
} }
else if (compression == TIFFCustom.COMPRESSION_JBIG) { case TIFFCustom.COMPRESSION_JBIG:
// TODO: Create interop test suite using third party plugin. // TODO: Create interop test suite using third party plugin.
// LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1 // LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1
// Haven't found any plugins. There is however a JBIG2 plugin... // Haven't found any plugins. There is however a JBIG2 plugin...
return new DelegateTileDecoder(warningListener, "JBIG", param); return new DelegateTileDecoder(warningListener, "JBIG", param);
} case TIFFCustom.COMPRESSION_JPEG2000:
else if (compression == TIFFCustom.COMPRESSION_JPEG2000) {
// TODO: Create interop test suite using third party plugin // TODO: Create interop test suite using third party plugin
// LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1 // LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1
// The open source JAI JP2K reader decodes this as a fully black image... // The open source JAI JP2K reader decodes this as a fully black image...
return new DelegateTileDecoder(warningListener, "JPEG2000", param); return new DelegateTileDecoder(warningListener, "JPEG2000", param);
} case TIFFCustom.COMPRESSION_WEBP:
else if (compression == TIFFCustom.COMPRESSION_WEBP) {
return new DelegateTileDecoder(warningListener, "WebP", param); return new DelegateTileDecoder(warningListener, "WebP", param);
default:
throw new AssertionError("Unexpected TIFF Compression value: " + compression);
} }
} }
catch (IIOException e) { catch (IIOException e) {
throw new IIOException("Unsupported TIFF Compression value: " + compression, e); throw new IIOException("Unsupported TIFF Compression value: " + compression, e);
} }
throw new IIOException("Unsupported TIFF Compression value: " + compression);
} }
private InputStream createYCbCrUpsamplerStream(int photometricInterpretation, int planarConfiguration, int plane, int transferType, private InputStream createYCbCrUpsamplerStream(int photometricInterpretation, int planarConfiguration, int plane, int transferType,