#666 Support for TIFF RGB 2/4 bit per sample.

This commit is contained in:
Harald Kuhr 2022-03-11 19:54:06 +01:00
parent b67d687761
commit d34b0b7fcf
3 changed files with 26 additions and 12 deletions

View File

@ -63,7 +63,7 @@ final class BitPaddingStream extends FilterInputStream {
private final byte[] inputBuffer; private final byte[] inputBuffer;
private final ByteBuffer buffer; private final ByteBuffer buffer;
private int componentSize; private final int componentSize;
BitPaddingStream(final InputStream stream, int samplesPerPixel, final int bitsPerSample, final int colsInTile, final ByteOrder byteOrder) { BitPaddingStream(final InputStream stream, int samplesPerPixel, final int bitsPerSample, final int colsInTile, final ByteOrder byteOrder) {
super(notNull(stream, "stream")); super(notNull(stream, "stream"));
@ -169,6 +169,7 @@ final class BitPaddingStream extends FilterInputStream {
return length; return length;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean fillBuffer() throws IOException { private boolean fillBuffer() throws IOException {
if (!readFully(inputBuffer)) { if (!readFully(inputBuffer)) {
return false; return false;

View File

@ -571,8 +571,13 @@ public final class TIFFImageReader extends ImageReaderBase {
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) { if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, false, false); return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, false, false);
} }
else if (bitsPerSample == 2 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
return ImageTypeSpecifiers.createPacked(cs, 0x30, 0xC, 0x3, 0, DataBuffer.TYPE_BYTE, isAlphaPremultiplied);
}
else if (bitsPerSample == 4 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
return ImageTypeSpecifiers.createPacked(cs, 0xF00, 0xF0, 0xF, 0, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
}
else if (bitsPerSample > 8 && bitsPerSample % 2 == 0) { else if (bitsPerSample > 8 && bitsPerSample % 2 == 0) {
// TODO: Support variable bits/sample?
ColorModel colorModel = new ComponentColorModel(cs, new int[] {bitsPerSample, bitsPerSample, bitsPerSample}, false, false, Transparency.OPAQUE, dataType); ColorModel colorModel = new ComponentColorModel(cs, new int[] {bitsPerSample, bitsPerSample, bitsPerSample}, false, false, Transparency.OPAQUE, dataType);
SampleModel sampleModel = planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY SampleModel sampleModel = planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY
? colorModel.createCompatibleSampleModel(1, 1) ? colorModel.createCompatibleSampleModel(1, 1)
@ -583,11 +588,14 @@ public final class TIFFImageReader extends ImageReaderBase {
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) { if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, true, isAlphaPremultiplied); return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, true, isAlphaPremultiplied);
} }
else if (significantSamples == 4 && bitsPerSample == 4) { else if (bitsPerSample == 2 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
return ImageTypeSpecifiers.createPacked(cs, 0xC0, 0x30, 0xC, 0x3, DataBuffer.TYPE_BYTE, isAlphaPremultiplied);
}
else if (bitsPerSample == 4 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
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:
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample)); throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for RGB TIFF (expected 3 or 4/a multiple of 2): %d/%d", samplesPerPixel, bitsPerSample));
} }
case TIFFBaseline.PHOTOMETRIC_PALETTE: case TIFFBaseline.PHOTOMETRIC_PALETTE:
// Palette // Palette
@ -1055,7 +1063,9 @@ public final class TIFFImageReader extends ImageReaderBase {
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1; int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, TIFFBaseline.FILL_LEFT_TO_RIGHT); int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, TIFFBaseline.FILL_LEFT_TO_RIGHT);
int bitsPerSample = getBitsPerSample(); int bitsPerSample = getBitsPerSample();
boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0 || bitsPerSample == 6; boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0
|| numBands == 1 && bitsPerSample == 6 // IndexColorModel or Gray
|| numBands == 3 && (bitsPerSample == 2 || bitsPerSample == 4); // RGB/YCbCr/etc.
boolean needsAdapter = compression != TIFFBaseline.COMPRESSION_NONE || fillOrder != TIFFBaseline.FILL_LEFT_TO_RIGHT boolean needsAdapter = compression != TIFFBaseline.COMPRESSION_NONE || fillOrder != TIFFBaseline.FILL_LEFT_TO_RIGHT
|| interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || needsBitPadding; || interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || needsBitPadding;
@ -1098,7 +1108,9 @@ public final class TIFFImageReader extends ImageReaderBase {
if (needsBitPadding) { if (needsBitPadding) {
// We'll pad "odd" bitsPerSample streams to the smallest data type (byte/short/int) larger than the input // We'll pad "odd" bitsPerSample streams to the smallest data type (byte/short/int) larger than the input
adapter = new BitPaddingStream(adapter, numBands, bitsPerSample, colsInTile, imageInput.getByteOrder()); adapter = bitsPerSample < 8
? new BitPaddingStream(adapter, 1, numBands * bitsPerSample, colsInTile, imageInput.getByteOrder())
: new BitPaddingStream(adapter, numBands, bitsPerSample, colsInTile, imageInput.getByteOrder());
} }
// According to the spec, short/long/etc should follow order of containing stream // According to the spec, short/long/etc should follow order of containing stream
@ -2004,7 +2016,7 @@ public final class TIFFImageReader extends ImageReaderBase {
if (needsWidening) { if (needsWidening) {
readFully(input, rowDataShort); readFully(input, rowDataShort);
toFloat(rowDataFloat, rowDataShort); toFloat(rowDataShort, rowDataFloat);
} }
else { else {
readFully(input, rowDataFloat); readFully(input, rowDataFloat);
@ -2033,7 +2045,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private void toFloat(final float[] rowDataFloat, final short[] rowDataShort) { private void toFloat(final short[] rowDataShort, final float[] rowDataFloat) {
for (int i = 0; i < rowDataFloat.length; i++) { for (int i = 0; i < rowDataFloat.length; i++) {
rowDataFloat[i] = Half.shortBitsToFloat(rowDataShort[i]); rowDataFloat[i] = Half.shortBitsToFloat(rowDataShort[i]);
} }

View File

@ -142,6 +142,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample
// RGB Interleaved (PlanarConfiguration: 1) // RGB Interleaved (PlanarConfiguration: 1)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
@ -194,6 +196,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample
// RGB Interleaved (PlanarConfiguration: 1) // RGB Interleaved (PlanarConfiguration: 1)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
@ -221,9 +225,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
private List<TestData> getUnsupportedTestData() { private List<TestData> getUnsupportedTestData() {
return Arrays.asList( return Arrays.asList(
// RGB Interleaved (PlanarConfiguration: 1)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
// RGB Planar (PlanarConfiguration: 2) // RGB Planar (PlanarConfiguration: 2)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-04.tif"), new Dimension(73, 43)) // RGB 4 bit/sample new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-04.tif"), new Dimension(73, 43)) // RGB 4 bit/sample
@ -886,7 +887,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
for (int i = 0; i < data.getImageCount(); i++) { for (int i = 0; i < data.getImageCount(); i++) {
try { try {
reader.read(i); reader.read(i);
fail("Sample should be moved from unsupported to normal test case"); fail("Sample should be moved from unsupported to normal test case: " + data);
} }
catch (IIOException e) { catch (IIOException e) {
assertThat(e.getMessage().toLowerCase(), containsString("unsupported")); assertThat(e.getMessage().toLowerCase(), containsString("unsupported"));