diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java index 3e053f85..c3009826 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOUtil.java @@ -40,7 +40,7 @@ import javax.imageio.spi.ServiceRegistry; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.*; -import java.awt.image.BufferedImage; +import java.awt.image.*; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.InputStream; @@ -283,7 +283,7 @@ public final class IIOUtil { "bitsPerSample must be > 0 and <= 16 and a power of 2"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel * bitsPerSample <= 16 || samplesPerPixel * bitsPerSample % 16 == 0, - "samplesPerPixel * bitsPerSample must be < 16 or a multiple of 16 "); + "samplesPerPixel * bitsPerSample must be < 16 or a multiple of 16"); int pixelStride = bitsPerSample * samplesPerPixel / 16; for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { @@ -305,7 +305,7 @@ public final class IIOUtil { "bitsPerSample must be > 0 and <= 32 and a power of 2"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, - "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 "); + "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32"); int pixelStride = bitsPerSample * samplesPerPixel / 32; for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { @@ -322,7 +322,7 @@ public final class IIOUtil { "bitsPerSample must be > 0 and <= 32 and a power of 2"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, - "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 "); + "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32"); int pixelStride = bitsPerSample * samplesPerPixel / 32; for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { @@ -330,4 +330,21 @@ public final class IIOUtil { System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); } } + + public static void subsampleRow(double[] srcRow, int srcPos, int srcWidth, + double[] destRow, int destPos, + int samplesPerPixel, int bitsPerSample, int samplePeriod) { + Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op... + Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 64 && (bitsPerSample == 1 || bitsPerSample % 2 == 0), + "bitsPerSample must be > 0 and <= 64 and a power of 2"); + Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); + Validate.isTrue(samplesPerPixel * bitsPerSample <= 64 || samplesPerPixel * bitsPerSample % 64 == 0, + "samplesPerPixel * bitsPerSample must be < 64 or a multiple of 64"); + + int pixelStride = bitsPerSample * samplesPerPixel / 64; + for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { + // System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1 + System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); + } + } } \ No newline at end of file diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index a99d0302..21879b90 100644 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -815,8 +815,11 @@ public final class TIFFImageReader extends ImageReaderBase { if (bitsPerSample == 16 || bitsPerSample == 32) { return DataBuffer.TYPE_FLOAT; } + else if (bitsPerSample == 64) { + return DataBuffer.TYPE_DOUBLE; + } - throw new IIOException("Unsupported BitsPerSample for SampleFormat 3/Floating Point (expected 16/32): " + bitsPerSample); + throw new IIOException("Unsupported BitsPerSample for SampleFormat 3/Floating Point (expected 16/32/64): " + bitsPerSample); default: throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat); } @@ -1153,10 +1156,8 @@ public final class TIFFImageReader extends ImageReaderBase { } // Need to do color normalization after reading all bands for planar - if (planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR) { - if (normalize) { - normalizeColorPlanar(interpretation, destRaster); - } + if (normalize && planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR) { + normalizeColorPlanar(interpretation, destRaster); } col += colsInTile; @@ -1252,19 +1253,17 @@ public final class TIFFImageReader extends ImageReaderBase { // We'll have to use readAsRaster and later apply color space conversion ourselves Raster raster = jpegReader.readRaster(0, jpegParam); // TODO: Refactor + duplicate this for all JPEG-in-TIFF cases - switch (raster.getTransferType()) { - case DataBuffer.TYPE_BYTE: - if (normalize) { + if (normalize) { + switch (raster.getTransferType()) { + case DataBuffer.TYPE_BYTE: normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData()); - } - break; - case DataBuffer.TYPE_USHORT: - if (normalize) { + break; + case DataBuffer.TYPE_USHORT: normalizeColor(interpretation, samplesInTile, ((DataBufferUShort) raster.getDataBuffer()).getData()); - } - break; - default: - throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType()); + break; + default: + throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType()); + } } destination.getRaster().setDataElements(offset.x, offset.y, raster); @@ -2080,6 +2079,36 @@ public final class TIFFImageReader extends ImageReaderBase { break; + case DataBuffer.TYPE_DOUBLE: + /*for (int band = 0; band < bands; band++)*/ { + double[] rowDataDouble = ((DataBufferDouble) tileRowRaster.getDataBuffer()).getData(band); + + for (int row = startRow; row < startRow + rowsInTile; row++) { + if (row >= srcRegion.y + srcRegion.height) { + break; // We're done with this tile + } + + input.readFully(rowDataDouble, 0, rowDataDouble.length); + + if (row >= srcRegion.y) { + if (normalize) { + normalizeColor(interpretation, numBands, rowDataDouble); + } + + // Subsample horizontal + if (xSub != 1) { + subsampleRow(rowDataDouble, srcRegion.x * numBands, colsInTile, + rowDataDouble, srcRegion.x * numBands / xSub, numBands, bitsPerSample, xSub); + } + + destChannel.setDataElements(startCol, row - srcRegion.y, srcChannel); + } + // Else skip data + } + } + + break; + default: throw new AssertionError("Unsupported data type: " + tileRowRaster.getTransferType()); } @@ -2091,13 +2120,24 @@ public final class TIFFImageReader extends ImageReaderBase { } } - private void clamp(final float[] rowDataFloat) { - for (int i = 0; i < rowDataFloat.length; i++) { - if (rowDataFloat[i] > 1f) { - rowDataFloat[i] = 1f; + private void clamp(final float[] data) { + for (int i = 0; i < data.length; i++) { + if (data[i] > 1f) { + data[i] = 1f; } - else if (rowDataFloat[i] < 0f) { - rowDataFloat[i] = 0f; + else if (data[i] < 0f) { + data[i] = 0f; + } + } + } + + private void clamp(final double[] data) { + for (int i = 0; i < data.length; i++) { + if (data[i] > 1d) { + data[i] = 1d; + } + else if (data[i] < 0d) { + data[i] = 0d; } } } @@ -2395,6 +2435,28 @@ public final class TIFFImageReader extends ImageReaderBase { } } + private void normalizeColor(int photometricInterpretation, @SuppressWarnings("unused") int numBands, double[] data) { + // TODO: Allow param to decide tone mapping strategy, like in the HDRImageReader + clamp(data); + + switch (photometricInterpretation) { + case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO: + // Inverse values + for (int i = 0; i < data.length; i++) { + data[i] = 1d - data[i]; + } + + break; + + case TIFFExtension.PHOTOMETRIC_CIELAB: + case TIFFExtension.PHOTOMETRIC_ICCLAB: + case TIFFExtension.PHOTOMETRIC_ITULAB: + case TIFFExtension.PHOTOMETRIC_YCBCR: + // Not supported + break; + } + } + private void convertYCbCr2RGB(final short[] yCbCr, final short[] rgb, final double[] coefficients, final double[] referenceBW, final int offset) { double y; double cb; diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index ac46736b..d7658d19 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff; import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + import org.junit.Test; import javax.imageio.IIOException; @@ -43,7 +44,7 @@ import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.IOException; import java.nio.ByteOrder; @@ -55,7 +56,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.AdditionalMatchers.and; import static org.mockito.Mockito.*; @@ -100,6 +104,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest