#682 TIFF Lab w/alpha support

This commit is contained in:
Harald Kuhr 2022-06-01 19:30:01 +02:00
parent 6ddb799a95
commit 91493c5145
4 changed files with 46 additions and 66 deletions

View File

@ -59,15 +59,12 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder; import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import javax.imageio.*; import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener; import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam; import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
@ -1145,7 +1142,6 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
break; break;
case TIFFExtension.COMPRESSION_JPEG: case TIFFExtension.COMPRESSION_JPEG:
@ -1225,10 +1221,10 @@ public final class TIFFImageReader extends ImageReaderBase {
// TODO: Refactor + duplicate this for all JPEG-in-TIFF cases // TODO: Refactor + duplicate this for all JPEG-in-TIFF cases
switch (raster.getTransferType()) { switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_BYTE:
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData()); normalizeColor(interpretation, numBands, ((DataBufferByte) raster.getDataBuffer()).getData());
break; break;
case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_USHORT:
normalizeColor(interpretation, ((DataBufferUShort) raster.getDataBuffer()).getData()); normalizeColor(interpretation, numBands, ((DataBufferUShort) raster.getDataBuffer()).getData());
break; break;
default: default:
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType()); throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
@ -1391,7 +1387,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert. // 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 // We'll have to use readAsRaster and later apply color space conversion ourselves
Raster raster = jpegReader.readRaster(0, jpegParam); Raster raster = jpegReader.readRaster(0, jpegParam);
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData()); normalizeColor(interpretation, numBands, ((DataBufferByte) raster.getDataBuffer()).getData());
destination.getRaster().setDataElements(offset.x, offset.y, raster); destination.getRaster().setDataElements(offset.x, offset.y, raster);
} }
} }
@ -1541,7 +1537,7 @@ public final class TIFFImageReader extends ImageReaderBase {
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert. // 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 // We'll have to use readAsRaster and later apply color space conversion ourselves
Raster raster = jpegReader.readRaster(0, jpegParam); Raster raster = jpegReader.readRaster(0, jpegParam);
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData()); normalizeColor(interpretation, numBands, ((DataBufferByte) raster.getDataBuffer()).getData());
destination.getRaster().setDataElements(offset.x, offset.y, raster); destination.getRaster().setDataElements(offset.x, offset.y, raster);
} }
} }
@ -1901,10 +1897,16 @@ public final class TIFFImageReader extends ImageReaderBase {
throws IOException { throws IOException {
DataBuffer dataBuffer = tileRowRaster.getDataBuffer(); DataBuffer dataBuffer = tileRowRaster.getDataBuffer();
int bands = dataBuffer.getNumBanks(); boolean banded = dataBuffer.getNumBanks() > 1;
boolean banded = bands > 1;
int bitsPerSample = getBitsPerSample(); int bitsPerSample = getBitsPerSample();
WritableRaster destChannel = banded
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
: raster;
Raster srcChannel = banded
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
: tileRowRaster;
switch (tileRowRaster.getTransferType()) { switch (tileRowRaster.getTransferType()) {
case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_BYTE:
@ -1912,12 +1914,6 @@ public final class TIFFImageReader extends ImageReaderBase {
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
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
: raster;
Raster srcChannel = banded
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
: tileRowRaster;
for (int row = startRow; row < startRow + rowsInTile; row++) { for (int row = startRow; row < startRow + rowsInTile; row++) {
if (row >= srcRegion.y + srcRegion.height) { if (row >= srcRegion.y + srcRegion.height) {
@ -1928,7 +1924,7 @@ public final class TIFFImageReader extends ImageReaderBase {
if (row % ySub == 0 && row >= srcRegion.y) { if (row % ySub == 0 && row >= srcRegion.y) {
if (!banded) { if (!banded) {
normalizeColor(interpretation, rowDataByte); normalizeColor(interpretation, numBands, rowDataByte);
} }
// Subsample horizontal // Subsample horizontal
@ -1950,13 +1946,6 @@ public final class TIFFImageReader extends ImageReaderBase {
? ((DataBufferUShort) dataBuffer).getData(band) ? ((DataBufferUShort) dataBuffer).getData(band)
: ((DataBufferShort) dataBuffer).getData(band); : ((DataBufferShort) dataBuffer).getData(band);
WritableRaster destChannel = banded
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
: raster;
Raster srcChannel = banded
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
: tileRowRaster;
for (int row = startRow; row < startRow + rowsInTile; row++) { for (int row = startRow; row < startRow + rowsInTile; row++) {
if (row >= srcRegion.y + srcRegion.height) { if (row >= srcRegion.y + srcRegion.height) {
break; // We're done with this tile break; // We're done with this tile
@ -1965,7 +1954,7 @@ public final class TIFFImageReader extends ImageReaderBase {
readFully(input, rowDataShort); readFully(input, rowDataShort);
if (row >= srcRegion.y) { if (row >= srcRegion.y) {
normalizeColor(interpretation, rowDataShort); normalizeColor(interpretation, numBands, rowDataShort);
// Subsample horizontal // Subsample horizontal
subsampleRow(rowDataShort, srcRegion.x * numBands, colsInTile, subsampleRow(rowDataShort, srcRegion.x * numBands, colsInTile,
@ -1985,13 +1974,6 @@ public final class TIFFImageReader extends ImageReaderBase {
/*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
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
: raster;
Raster srcChannel = banded
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
: tileRowRaster;
for (int row = startRow; row < startRow + rowsInTile; row++) { for (int row = startRow; row < startRow + rowsInTile; row++) {
if (row >= srcRegion.y + srcRegion.height) { if (row >= srcRegion.y + srcRegion.height) {
break; // We're done with this tile break; // We're done with this tile
@ -2000,7 +1982,7 @@ public final class TIFFImageReader extends ImageReaderBase {
readFully(input, rowDataInt); readFully(input, rowDataInt);
if (row >= srcRegion.y) { if (row >= srcRegion.y) {
normalizeColor(interpretation, rowDataInt); normalizeColor(interpretation, numBands, rowDataInt);
// Subsample horizontal // Subsample horizontal
subsampleRow(rowDataInt, srcRegion.x * numBands, colsInTile, subsampleRow(rowDataInt, srcRegion.x * numBands, colsInTile,
@ -2020,14 +2002,6 @@ public final class TIFFImageReader extends ImageReaderBase {
float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band); float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band);
short[] rowDataShort = needsWidening ? new short[rowDataFloat.length] : null; short[] rowDataShort = needsWidening ? new short[rowDataFloat.length] : null;
WritableRaster destChannel = banded
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
: raster;
Raster srcChannel = banded
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
: tileRowRaster;
for (int row = startRow; row < startRow + rowsInTile; row++) { for (int row = startRow; row < startRow + rowsInTile; row++) {
if (row >= srcRegion.y + srcRegion.height) { if (row >= srcRegion.y + srcRegion.height) {
break; // We're done with this tile break; // We're done with this tile
@ -2042,7 +2016,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
if (row >= srcRegion.y) { if (row >= srcRegion.y) {
normalizeColor(interpretation, rowDataFloat); normalizeColor(interpretation, numBands, rowDataFloat);
// Subsample horizontal // Subsample horizontal
if (xSub != 1) { if (xSub != 1) {
@ -2216,7 +2190,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private void normalizeColor(int photometricInterpretation, 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:
// NOTE: Preserve WhiteIsZero for 1 bit monochrome, for CCITT compatibility // NOTE: Preserve WhiteIsZero for 1 bit monochrome, for CCITT compatibility
@ -2240,7 +2214,7 @@ public final class TIFFImageReader extends ImageReaderBase {
); );
float[] temp = new float[3]; float[] temp = new float[3];
for (int i = 0; i < data.length; i += 3) { for (int i = 0; i < data.length; i += numBands) {
// Unsigned scaled form 0...100 // Unsigned scaled form 0...100
float LStar = (data[i] & 0xff) * 100f / 255.0f; float LStar = (data[i] & 0xff) * 100f / 255.0f;
float aStar; float aStar;
@ -2301,7 +2275,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private void normalizeColor(int photometricInterpretation, short[] data) throws IIOException { private void normalizeColor(int photometricInterpretation, int numBands, short[] data) throws IIOException {
switch (photometricInterpretation) { switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
// Inverse values // Inverse values
@ -2324,7 +2298,7 @@ public final class TIFFImageReader extends ImageReaderBase {
float[] temp = new float[3]; float[] temp = new float[3];
float scaleL = photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB ? 65535f : 65280f; // Is for ICC lab, assumes the same for ITU.... float scaleL = photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB ? 65535f : 65280f; // Is for ICC lab, assumes the same for ITU....
for (int i = 0; i < data.length; i += 3) { for (int i = 0; i < data.length; i += numBands) {
// Unsigned scaled form 0...100 // Unsigned scaled form 0...100
float LStar = (data[i] & 0xffff) * 100.0f / scaleL; float LStar = (data[i] & 0xffff) * 100.0f / scaleL;
float aStar; float aStar;
@ -2372,7 +2346,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
} }
private void normalizeColor(int photometricInterpretation, int[] data) { private void normalizeColor(int photometricInterpretation, @SuppressWarnings("unused") int numBands, int[] data) {
switch (photometricInterpretation) { switch (photometricInterpretation) {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
// Inverse values // Inverse values
@ -2391,7 +2365,7 @@ public final class TIFFImageReader extends ImageReaderBase {
} }
} }
private void normalizeColor(int photometricInterpretation, float[] data) { private void normalizeColor(int photometricInterpretation, @SuppressWarnings("unused") int numBands, float[] data) {
// TODO: Allow param to decide tone mapping strategy, like in the HDRImageReader // TODO: Allow param to decide tone mapping strategy, like in the HDRImageReader
clamp(data); clamp(data);
@ -2770,10 +2744,11 @@ public final class TIFFImageReader extends ImageReaderBase {
}); });
reader.addIIOReadProgressListener(new ProgressListenerBase() { reader.addIIOReadProgressListener(new ProgressListenerBase() {
private static final int MAX_W = 78; private static final int MAX_W = 78;
int lastProgress = 0; int lastProgress;
@Override @Override
public void imageStarted(ImageReader source, int imageIndex) { public void imageStarted(ImageReader source, int imageIndex) {
lastProgress = 0;
System.out.print("["); System.out.print("[");
} }
@ -2781,12 +2756,14 @@ public final class TIFFImageReader extends ImageReaderBase {
public void imageProgress(ImageReader source, float percentageDone) { public void imageProgress(ImageReader source, float percentageDone) {
int steps = ((int) (percentageDone * MAX_W) / 100); int steps = ((int) (percentageDone * MAX_W) / 100);
for (int i = lastProgress; i < steps; i++) { if (steps > lastProgress) {
System.out.print("."); for (int i = lastProgress; i < steps; i++) {
} System.out.print(".");
}
System.out.flush(); System.out.flush();
lastProgress = steps; lastProgress = steps;
}
} }
@Override @Override
@ -2839,18 +2816,18 @@ public final class TIFFImageReader extends ImageReaderBase {
BufferedImage image = reader.read(imageNo, param); BufferedImage image = reader.read(imageNo, param);
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms"); System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
IIOMetadata metadata = reader.getImageMetadata(imageNo); // IIOMetadata metadata = reader.getImageMetadata(imageNo);
if (metadata != null) { // if (metadata != null) {
if (metadata.getNativeMetadataFormatName() != null) { // if (metadata.getNativeMetadataFormatName() != null) {
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName()); // Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
replaceBytesWithUndefined((IIOMetadataNode) tree); // replaceBytesWithUndefined((IIOMetadataNode) tree);
new XMLSerializer(System.out, "UTF-8").serialize(tree, false); // new XMLSerializer(System.out, "UTF-8").serialize(tree, false);
} // }
/*else*/ // /*else*/
if (metadata.isStandardMetadataFormatSupported()) { // if (metadata.isStandardMetadataFormatSupported()) {
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false); // new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
} // }
} // }
System.err.println("image: " + image); System.err.println("image: " + image);

View File

@ -175,6 +175,9 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)), // ZIP Gray, 8 bit/sample new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)), // ZIP Gray, 8 bit/sample
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2) new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
// Planar YCbCr full chroma // Planar YCbCr full chroma
new TestData(getClassLoaderResource("/tiff/lab-a-8.tiff"), new Dimension(589, 340)), // Lab + Alpha, uncompressed, striped
new TestData(getClassLoaderResource("/tiff/lab-a-16.tiff"), new Dimension(589, 340)), // Lab + Alpha, 16 bit uncompressed, striped
// Planar YCbCr full chroma
new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, LZW compressed, striped new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, LZW compressed, striped
// Planar YCbCr subsampled // Planar YCbCr subsampled