#282 TIFFImageReader now supports compressed planar data in strips/tiles.

This commit is contained in:
Harald Kuhr 2016-09-28 12:37:47 +02:00
parent 288ad54c42
commit d4afbee0f5
3 changed files with 54 additions and 48 deletions

View File

@ -68,7 +68,6 @@ import java.awt.*;
import java.awt.color.CMMException; import java.awt.color.CMMException;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile; import java.awt.color.ICC_Profile;
import java.awt.geom.Rectangle2D;
import java.awt.image.*; import java.awt.image.*;
import java.io.*; import java.io.*;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -433,7 +432,7 @@ public class TIFFImageReader extends ImageReaderBase {
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) { if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
switch (planarConfiguration) { switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY: case TIFFBaseline.PLANARCONFIG_CHUNKY:
// "TYPE_3_BYTE_RGB" if cs.isCS_sRGB() // "TYPE_3BYTE_RGB" if cs.isCS_sRGB()
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false); return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false);
case TIFFExtension.PLANARCONFIG_PLANAR: case TIFFExtension.PLANARCONFIG_PLANAR:
@ -444,14 +443,14 @@ public class TIFFImageReader extends ImageReaderBase {
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) { if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
switch (planarConfiguration) { switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY: case TIFFBaseline.PLANARCONFIG_CHUNKY:
// "TYPE_4_BYTE_RGBA" if cs.isCS_sRGB() // "TYPE_4BYTE_RGBA" if cs.isCS_sRGB()
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, isAlphaPremultiplied); return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, isAlphaPremultiplied);
case TIFFExtension.PLANARCONFIG_PLANAR: case TIFFExtension.PLANARCONFIG_PLANAR:
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, isAlphaPremultiplied); return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, isAlphaPremultiplied);
} }
} }
else if (bitsPerSample == 4) { else if (significantSamples == 4 && bitsPerSample == 4) {
return ImageTypeSpecifiers.createPacked(cs, 0xF000, 0xF00, 0xF0, 0xF, DataBuffer.TYPE_USHORT, isAlphaPremultiplied); return ImageTypeSpecifiers.createPacked(cs, 0xF000, 0xF00, 0xF0, 0xF, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
} }
default: default:
@ -778,7 +777,7 @@ public class TIFFImageReader extends ImageReaderBase {
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip // NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
// Strips are top/down, tiles are left/right, top/down // Strips are top/down, tiles are left/right, top/down
int stripTileWidth = width; int stripTileWidth = width;
long rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1l << 32) - 1); long rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1L << 32) - 1);
int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height; int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height;
long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false); long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
@ -880,72 +879,77 @@ public class TIFFImageReader extends ImageReaderBase {
processImageStarted(imageIndex); processImageStarted(imageIndex);
// General uncompressed/compressed reading // General uncompressed/compressed reading
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
for (int y = 0; y < tilesDown; y++) { for (int y = 0; y < tilesDown; y++) {
int col = 0; int col = 0;
int rowsInTile = Math.min(stripTileHeight, height - row); int rowsInTile = Math.min(stripTileHeight, height - row);
for (int x = 0; x < tilesAcross; x++) { for (int x = 0; x < tilesAcross; x++) {
int colsInTile = Math.min(stripTileWidth, width - col); int colsInTile = Math.min(stripTileWidth, width - col);
int i = y * tilesAcross + x;
imageInput.seek(stripTileOffsets[i]); for (int b = 0; b < bands; b++) {
int i = b * tilesDown * tilesAcross + y * tilesAcross + x;
DataInput input; imageInput.seek(stripTileOffsets[i]);
if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) {
// No need for transformation, fast forward
input = imageInput;
}
else {
InputStream adapter = stripTileByteCounts != null
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
: IIOUtil.createStreamAdapter(imageInput);
adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter); DataInput input;
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, getBitsPerSample(), adapter, imageInput.getByteOrder()); if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) {
// No need for transformation, fast forward
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_BYTE) { input = imageInput;
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile);
} }
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_USHORT) { else {
adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, imageInput.getByteOrder()); InputStream adapter = stripTileByteCounts != null
} ? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) { : IIOUtil.createStreamAdapter(imageInput);
// Handled in getRawImageType
throw new AssertionError(); adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter);
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, getBitsPerSample(), adapter, imageInput.getByteOrder());
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_BYTE) {
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile);
}
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_USHORT) {
adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, imageInput.getByteOrder());
}
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
// Handled in getRawImageType
throw new AssertionError();
}
// According to the spec, short/long/etc should follow order of containing stream
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
? new DataInputStream(adapter)
: new LittleEndianDataInputStream(adapter);
} }
// According to the spec, short/long/etc should follow order of containing stream // Clip the stripTile rowRaster to not exceed the srcRegion
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN clip.width = Math.min((colsInTile + xSub - 1) / xSub, srcRegion.width);
? new DataInputStream(adapter) Raster clippedRow = clipRowToRect(rowRaster, clip,
: new LittleEndianDataInputStream(adapter); param != null ? param.getSourceBands() : null,
param != null ? param.getSourceXSubsampling() : 1);
// Read a full strip/tile
readStripTileData(clippedRow, srcRegion, xSub, ySub, b, numBands, interpretation, destRaster, col, row, colsInTile, rowsInTile, input);
} }
// Clip the stripTile rowRaster to not exceed the srcRegion col += colsInTile;
clip.width = Math.min((colsInTile + xSub - 1) / xSub, srcRegion.width);
Raster clippedRow = clipRowToRect(rowRaster, clip,
param != null ? param.getSourceBands() : null,
param != null ? param.getSourceXSubsampling() : 1);
// Read a full strip/tile
readStripTileData(clippedRow, srcRegion, xSub, ySub, numBands, interpretation, destRaster, col, row, colsInTile, rowsInTile, input);
if (abortRequested()) { if (abortRequested()) {
break; break;
} }
col += colsInTile;
} }
row += rowsInTile;
processImageProgress(100f * row / height); processImageProgress(100f * row / height);
if (abortRequested()) { if (abortRequested()) {
processReadAborted(); processReadAborted();
break; break;
} }
row += rowsInTile;
} }
break; break;
case TIFFExtension.COMPRESSION_JPEG: case TIFFExtension.COMPRESSION_JPEG:
@ -1455,7 +1459,7 @@ public class TIFFImageReader extends ImageReaderBase {
} }
private void readStripTileData(final Raster tileRowRaster, final Rectangle srcRegion, final int xSub, final int ySub, private void readStripTileData(final Raster tileRowRaster, final Rectangle srcRegion, final int xSub, final int ySub,
final int numBands, final int interpretation, final int band, final int numBands, final int interpretation,
final WritableRaster raster, final int startCol, final int startRow, final WritableRaster raster, final int startCol, final int startRow,
final int colsInTile, final int rowsInTile, final DataInput input) final int colsInTile, final int rowsInTile, final DataInput input)
throws IOException { throws IOException {
@ -1467,8 +1471,9 @@ public class TIFFImageReader extends ImageReaderBase {
switch (tileRowRaster.getTransferType()) { switch (tileRowRaster.getTransferType()) {
case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_BYTE:
for (int band = 0; band < bands; band++) { /*for (int band = 0; band < bands; band++)*/ {
int bank = banded ? ((BandedSampleModel) tileRowRaster.getSampleModel()).getBankIndices()[band] : band; int bank = banded ? ((BandedSampleModel) tileRowRaster.getSampleModel()).getBankIndices()[band] : band;
byte[] rowDataByte = ((DataBufferByte) dataBuffer).getData(bank); byte[] rowDataByte = ((DataBufferByte) dataBuffer).getData(bank);
WritableRaster destChannel = banded WritableRaster destChannel = banded
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band}) ? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
@ -1510,7 +1515,7 @@ public class TIFFImageReader extends ImageReaderBase {
break; break;
case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT: case DataBuffer.TYPE_SHORT:
for (int band = 0; band < bands; band++) { /*for (int band = 0; band < bands; band++)*/ {
short[] rowDataShort = dataBuffer.getDataType() == DataBuffer.TYPE_USHORT short[] rowDataShort = dataBuffer.getDataType() == DataBuffer.TYPE_USHORT
? ((DataBufferUShort) dataBuffer).getData(band) ? ((DataBufferUShort) dataBuffer).getData(band)
: ((DataBufferShort) dataBuffer).getData(band); : ((DataBufferShort) dataBuffer).getData(band);
@ -1549,7 +1554,7 @@ public class TIFFImageReader extends ImageReaderBase {
break; break;
case DataBuffer.TYPE_INT: case DataBuffer.TYPE_INT:
for (int band = 0; band < bands; band++) { /*for (int band = 0; band < bands; band++)*/ {
int[] rowDataInt = ((DataBufferInt) dataBuffer).getData(band); int[] rowDataInt = ((DataBufferInt) dataBuffer).getData(band);
WritableRaster destChannel = banded WritableRaster destChannel = banded
@ -1585,7 +1590,7 @@ public class TIFFImageReader extends ImageReaderBase {
break; break;
case DataBuffer.TYPE_FLOAT: case DataBuffer.TYPE_FLOAT:
for (int band = 0; band < bands; band++) { /*for (int band = 0; band < bands; band++)*/ {
float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band); float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band);
WritableRaster destChannel = banded WritableRaster destChannel = banded

View File

@ -92,6 +92,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/lzw-rgba-4444.tif"), new Dimension(64, 64)), // RGBA, LZW compression with UINT 4/4/4/4 + gray 2/2 new TestData(getClassLoaderResource("/tiff/lzw-rgba-4444.tif"), new Dimension(64, 64)), // RGBA, LZW compression with UINT 4/4/4/4 + gray 2/2
new TestData(getClassLoaderResource("/tiff/lzw-buffer-overflow.tif"), new Dimension(5, 49)), // RGBA, LZW compression, will throw IOOBE if small buffer new TestData(getClassLoaderResource("/tiff/lzw-buffer-overflow.tif"), new Dimension(5, 49)), // RGBA, LZW compression, will throw IOOBE if small buffer
new TestData(getClassLoaderResource("/tiff/scan-mono-iccgray.tif"), new Dimension(2408, 3436)), // B/W, PackBits w/gray ICC profile new TestData(getClassLoaderResource("/tiff/scan-mono-iccgray.tif"), new Dimension(2408, 3436)), // B/W, PackBits w/gray ICC profile
new TestData(getClassLoaderResource("/tiff/planar-striped-lzw.tif"), new Dimension(229, 229)), // RGB 8 bit/sample, planar, LZW compression
// CCITT // CCITT
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D