This commit is contained in:
Harald Kuhr 2023-11-11 15:03:09 +01:00
parent 1292c95040
commit ee424583c4
4 changed files with 64 additions and 45 deletions

View File

@ -26,8 +26,7 @@ class DelegateTileDecoder extends TileDecoder {
protected final ImageReader delegate; protected final ImageReader delegate;
protected final ImageReadParam param; protected final ImageReadParam param;
// TODO: Naming... Is this only due to color space conversion? Is it because we need to read raster? private final Predicate<ImageReader> needsRasterConversion;
private final Predicate<ImageReader> needsConversion;
private final RasterConverter converter; private final RasterConverter converter;
private Boolean readRasterAndConvert; private Boolean readRasterAndConvert;
@ -35,11 +34,11 @@ class DelegateTileDecoder extends TileDecoder {
this(warningListener, createDelegate(format), originalParam, imageReader -> false, null); this(warningListener, createDelegate(format), originalParam, imageReader -> false, null);
} }
DelegateTileDecoder(final IIOReadWarningListener warningListener, final String format, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) throws IOException { DelegateTileDecoder(final IIOReadWarningListener warningListener, final String format, final ImageReadParam originalParam, final Predicate<ImageReader> needsRasterConversion, final RasterConverter converter) throws IOException {
this(warningListener, createDelegate(format), originalParam, needsConversion, converter); this(warningListener, createDelegate(format), originalParam, needsRasterConversion, converter);
} }
private DelegateTileDecoder(final IIOReadWarningListener warningListener, final ImageReader delegate, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) { private DelegateTileDecoder(final IIOReadWarningListener warningListener, final ImageReader delegate, final ImageReadParam originalParam, final Predicate<ImageReader> needsRasterConversion, final RasterConverter converter) {
super(warningListener); super(warningListener);
this.delegate = notNull(delegate, "delegate"); this.delegate = notNull(delegate, "delegate");
@ -48,7 +47,7 @@ class DelegateTileDecoder extends TileDecoder {
param = delegate.getDefaultReadParam(); param = delegate.getDefaultReadParam();
param.setSourceSubsampling(originalParam.getSourceXSubsampling(), originalParam.getSourceYSubsampling(), 0, 0); param.setSourceSubsampling(originalParam.getSourceXSubsampling(), originalParam.getSourceYSubsampling(), 0, 0);
this.needsConversion = needsConversion; this.needsRasterConversion = needsRasterConversion;
this.converter = converter; this.converter = converter;
} }
@ -57,7 +56,7 @@ class DelegateTileDecoder extends TileDecoder {
// If it's the TwelveMonkeys one, we will be able to read JPEG Lossless etc. // If it's the TwelveMonkeys one, we will be able to read JPEG Lossless etc.
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format); Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
if (!readers.hasNext()) { if (!readers.hasNext()) {
throw new IIOException("Could not instantiate " + format + "ImageReader"); throw new IIOException("No ImageReader registered for '" + format + "' format");
} }
return readers.next(); return readers.next();
@ -70,7 +69,7 @@ class DelegateTileDecoder extends TileDecoder {
if (readRasterAndConvert == null) { if (readRasterAndConvert == null) {
// All tiles in an image will use the same format, test once and cache result // All tiles in an image will use the same format, test once and cache result
readRasterAndConvert = needsConversion.test(delegate); readRasterAndConvert = needsRasterConversion.test(delegate);
} }
if (!readRasterAndConvert) { if (!readRasterAndConvert) {

View File

@ -15,8 +15,8 @@ import java.util.function.Predicate;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: JPEGTileDecoder.java,v 1.0 09/11/2023 haraldk Exp$ * @version $Id: JPEGTileDecoder.java,v 1.0 09/11/2023 haraldk Exp$
*/ */
class JPEGTileDecoder extends DelegateTileDecoder { final class JPEGTileDecoder extends DelegateTileDecoder {
JPEGTileDecoder(final IIOReadWarningListener warningListener, final byte[] jpegTables, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) throws IOException { JPEGTileDecoder(final IIOReadWarningListener warningListener, final byte[] jpegTables, final int numTiles, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) throws IOException {
super(warningListener, "JPEG", originalParam, needsConversion, converter); super(warningListener, "JPEG", originalParam, needsConversion, converter);
if (jpegTables != null) { if (jpegTables != null) {
@ -29,9 +29,9 @@ class JPEGTileDecoder extends DelegateTileDecoder {
// http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev // http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev
delegate.getStreamMetadata(); delegate.getStreamMetadata();
} }
else { else if (numTiles > 1) {
warningListener.warningOccurred(delegate, "Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)"); warningListener.warningOccurred(delegate, "Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)");
// ...and the JPEG reader will probably choke on missing tables... // ...and the JPEG reader might choke on missing tables...
} }
} }
} }

View File

@ -1172,13 +1172,18 @@ public final class TIFFImageReader extends ImageReaderBase {
break; break;
} }
case TIFFCustom.COMPRESSION_WEBP:
case TIFFExtension.COMPRESSION_JPEG: case TIFFExtension.COMPRESSION_JPEG:
case TIFFCustom.COMPRESSION_WEBP:
case TIFFCustom.COMPRESSION_JBIG:
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, 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) // JPEG ('old-style' JPEG, later overridden in Technote2)
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html // http://www.remotesensing.org/libtiff/TIFFTechNote2.html
// TODO: Rewrite to use readUsingDelegate with special case handling inside OldJPEGTileDecoder
int srcRow = 0; int srcRow = 0;
Boolean needsCSConversion = null; Boolean needsCSConversion = null;
@ -1497,10 +1502,8 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFCustom.COMPRESSION_PIXARFILM: case TIFFCustom.COMPRESSION_PIXARFILM:
case TIFFCustom.COMPRESSION_PIXARLOG: case TIFFCustom.COMPRESSION_PIXARLOG:
case TIFFCustom.COMPRESSION_DCS: case TIFFCustom.COMPRESSION_DCS:
case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin?
case TIFFCustom.COMPRESSION_SGILOG: case TIFFCustom.COMPRESSION_SGILOG:
case TIFFCustom.COMPRESSION_SGILOG24: case TIFFCustom.COMPRESSION_SGILOG24:
case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin?
throw new IIOException("Unsupported TIFF Compression value: " + compression); throw new IIOException("Unsupported TIFF Compression value: " + compression);
default: default:
@ -1521,7 +1524,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// JPEG ('new-style' JPEG) // JPEG ('new-style' JPEG)
// Read data // Read data
try (TileDecoder tileDecoder = createTileDecoder(param, compression, interpretation, samplesInTile)) { try (TileDecoder tileDecoder = createTileDecoder(param, compression, interpretation, tilesAcross * tilesDown, samplesInTile)) {
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here... processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
int row = 0; int row = 0;
@ -1569,37 +1572,39 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private DelegateTileDecoder createTileDecoder(ImageReadParam param, int compression, final int interpretation, final int samplesInTile) throws IOException { private TileDecoder createTileDecoder(ImageReadParam param, int compression, final int interpretation, final int numTiles, final int samplesInTile) throws IOException {
if (compression == TIFFExtension.COMPRESSION_JPEG) { try {
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing: IIOReadWarningListener warningListener = (source, warning) -> processWarningOccurred(warning);
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
byte[] tablesValue = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader)); if (compression == TIFFExtension.COMPRESSION_JPEG) {
RasterConverter csConverter = (raster) -> { // JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
switch (raster.getTransferType()) { // SOI, DQT, DHT, (optional markers that we ignore)..., EOI
case DataBuffer.TYPE_BYTE: Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData()); byte[] jpegTables = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
break;
case DataBuffer.TYPE_USHORT:
normalizeColor(interpretation, samplesInTile, ((DataBufferUShort) raster.getDataBuffer()).getData());
break;
default:
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
}
};
return new JPEGTileDecoder((source, warning) -> processWarningOccurred(warning), tablesValue, param, needsConversion, csConverter); Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader));
RasterConverter normalize = (raster) -> normalizeColor(interpretation, samplesInTile, raster);
return new JPEGTileDecoder(warningListener, jpegTables, numTiles, param, needsConversion, normalize);
}
else if (compression == TIFFCustom.COMPRESSION_JBIG) {
// 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
// Haven't found any plugins. There is however a JBIG2 plugin...
return new DelegateTileDecoder(warningListener, "JBIG", param);
}
else if (compression == TIFFCustom.COMPRESSION_JPEG2000) {
// 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
// The open source JAI JP2K reader decodes this as a fully black image...
return new DelegateTileDecoder(warningListener, "JPEG2000", param);
}
else if (compression == TIFFCustom.COMPRESSION_WEBP) {
return new DelegateTileDecoder(warningListener, "WebP", param);
}
} }
else if (compression == TIFFCustom.COMPRESSION_JBIG) { catch (IIOException e) {
return new DelegateTileDecoder((source, warning) -> processWarningOccurred(warning), "JBIG", param); throw new IIOException("Unsupported TIFF Compression value: " + compression, e);
}
else if (compression == TIFFCustom.COMPRESSION_JPEG2000) {
return new DelegateTileDecoder((source, warning) -> processWarningOccurred(warning), "JP2K", param);
}
else if (compression == TIFFCustom.COMPRESSION_WEBP) {
return new DelegateTileDecoder((source, warning) -> processWarningOccurred(warning), "WebP", param);
} }
throw new IIOException("Unsupported TIFF Compression value: " + compression); throw new IIOException("Unsupported TIFF Compression value: " + compression);
@ -2165,6 +2170,19 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private void normalizeColor(int interpretation, int numBands, Raster raster) throws IOException {
switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
normalizeColor(interpretation, numBands, ((DataBufferByte) raster.getDataBuffer()).getData());
break;
case DataBuffer.TYPE_USHORT:
normalizeColor(interpretation, numBands, ((DataBufferUShort) raster.getDataBuffer()).getData());
break;
default:
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
}
}
private void normalizeColor(int photometricInterpretation, int numBands, byte[] data) throws IOException { private void normalizeColor(int photometricInterpretation, int numBands, byte[] data) throws IOException {
switch (photometricInterpretation) { switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:

View File

@ -6,6 +6,8 @@ import java.awt.*;
import java.awt.image.*; import java.awt.image.*;
import java.io.IOException; import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull;
/** /**
* TileDecoder. * TileDecoder.
* *
@ -18,7 +20,7 @@ abstract class TileDecoder implements AutoCloseable {
protected final IIOReadWarningListener warningListener; protected final IIOReadWarningListener warningListener;
public TileDecoder(IIOReadWarningListener warningListener) { public TileDecoder(IIOReadWarningListener warningListener) {
this.warningListener = warningListener; this.warningListener = notNull(warningListener, "warningListener");
} }
abstract void decodeTile(ImageInputStream input, Rectangle sourceRegion, Point destinationOffset, BufferedImage destination) throws IOException; abstract void decodeTile(ImageInputStream input, Rectangle sourceRegion, Point destinationOffset, BufferedImage destination) throws IOException;