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 ImageReadParam param;
// TODO: Naming... Is this only due to color space conversion? Is it because we need to read raster?
private final Predicate<ImageReader> needsConversion;
private final Predicate<ImageReader> needsRasterConversion;
private final RasterConverter converter;
private Boolean readRasterAndConvert;
@ -35,11 +34,11 @@ class DelegateTileDecoder extends TileDecoder {
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 {
this(warningListener, createDelegate(format), originalParam, needsConversion, converter);
DelegateTileDecoder(final IIOReadWarningListener warningListener, final String format, final ImageReadParam originalParam, final Predicate<ImageReader> needsRasterConversion, final RasterConverter converter) throws IOException {
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);
this.delegate = notNull(delegate, "delegate");
@ -48,7 +47,7 @@ class DelegateTileDecoder extends TileDecoder {
param = delegate.getDefaultReadParam();
param.setSourceSubsampling(originalParam.getSourceXSubsampling(), originalParam.getSourceYSubsampling(), 0, 0);
this.needsConversion = needsConversion;
this.needsRasterConversion = needsRasterConversion;
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.
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
if (!readers.hasNext()) {
throw new IIOException("Could not instantiate " + format + "ImageReader");
throw new IIOException("No ImageReader registered for '" + format + "' format");
}
return readers.next();
@ -70,7 +69,7 @@ class DelegateTileDecoder extends TileDecoder {
if (readRasterAndConvert == null) {
// 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) {

View File

@ -15,8 +15,8 @@ import java.util.function.Predicate;
* @author last modified by $Author: haraldk$
* @version $Id: JPEGTileDecoder.java,v 1.0 09/11/2023 haraldk Exp$
*/
class JPEGTileDecoder extends DelegateTileDecoder {
JPEGTileDecoder(final IIOReadWarningListener warningListener, final byte[] jpegTables, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) throws IOException {
final class JPEGTileDecoder extends DelegateTileDecoder {
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);
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
delegate.getStreamMetadata();
}
else {
else if (numTiles > 1) {
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;
}
case TIFFCustom.COMPRESSION_WEBP:
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);
break;
case TIFFExtension.COMPRESSION_OLD_JPEG: {
// JPEG ('old-style' JPEG, later overridden in Technote2)
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
// TODO: Rewrite to use readUsingDelegate with special case handling inside OldJPEGTileDecoder
int srcRow = 0;
Boolean needsCSConversion = null;
@ -1497,10 +1502,8 @@ public final class TIFFImageReader extends ImageReaderBase {
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:
@ -1521,7 +1524,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// JPEG ('new-style' JPEG)
// 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...
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 {
if (compression == TIFFExtension.COMPRESSION_JPEG) {
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
// 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;
private TileDecoder createTileDecoder(ImageReadParam param, int compression, final int interpretation, final int numTiles, final int samplesInTile) throws IOException {
try {
IIOReadWarningListener warningListener = (source, warning) -> processWarningOccurred(warning);
Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader));
RasterConverter csConverter = (raster) -> {
switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
break;
case DataBuffer.TYPE_USHORT:
normalizeColor(interpretation, samplesInTile, ((DataBufferUShort) raster.getDataBuffer()).getData());
break;
default:
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
}
};
if (compression == TIFFExtension.COMPRESSION_JPEG) {
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
byte[] jpegTables = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
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) {
return new DelegateTileDecoder((source, warning) -> processWarningOccurred(warning), "JBIG", param);
}
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);
catch (IIOException e) {
throw new IIOException("Unsupported TIFF Compression value: " + compression, e);
}
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 {
switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:

View File

@ -6,6 +6,8 @@ import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* TileDecoder.
*
@ -18,7 +20,7 @@ abstract class TileDecoder implements AutoCloseable {
protected final 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;