From c0c4a936f0a86343a82025881a20c0224627c984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Szafra=C5=84ski?= Date: Fri, 17 Aug 2018 14:54:13 +0300 Subject: [PATCH] #58 Support for floating point predictor (type 3) in TIFF Deflate decompressor. --- .../plugins/tiff/BaselineTIFFTagSet.java | 9 ++- .../plugins/tiff/TIFFDeflateDecompressor.java | 76 +++++++++++++++--- .../imageio/tiff/TIFFReadTest.java | 68 ++++++++-------- .../tiff/test-data/deflate_predictor_3.tif | Bin 0 -> 1091 bytes 4 files changed, 110 insertions(+), 43 deletions(-) create mode 100644 plugin/tiff/src/test/resources/it/geosolutions/imageio/tiff/test-data/deflate_predictor_3.tif diff --git a/plugin/tiff/src/main/java/it/geosolutions/imageio/plugins/tiff/BaselineTIFFTagSet.java b/plugin/tiff/src/main/java/it/geosolutions/imageio/plugins/tiff/BaselineTIFFTagSet.java index 37cd3acb6..2f880283f 100644 --- a/plugin/tiff/src/main/java/it/geosolutions/imageio/plugins/tiff/BaselineTIFFTagSet.java +++ b/plugin/tiff/src/main/java/it/geosolutions/imageio/plugins/tiff/BaselineTIFFTagSet.java @@ -777,7 +777,14 @@ public class BaselineTIFFTagSet extends TIFFTagSet { * @see #TAG_PREDICTOR */ public static final int PREDICTOR_HORIZONTAL_DIFFERENCING = 2; - + + /** + * A value to be used with the "Predictor" tag. + * + * @see #TAG_PREDICTOR + */ + public static final int PREDICTOR_FLOATING_POINT = 3; + /** * Constant specifying the "WhitePoint" tag. */ diff --git a/plugin/tiff/src/main/java/it/geosolutions/imageioimpl/plugins/tiff/TIFFDeflateDecompressor.java b/plugin/tiff/src/main/java/it/geosolutions/imageioimpl/plugins/tiff/TIFFDeflateDecompressor.java index 7595a64e4..bef5a0325 100644 --- a/plugin/tiff/src/main/java/it/geosolutions/imageioimpl/plugins/tiff/TIFFDeflateDecompressor.java +++ b/plugin/tiff/src/main/java/it/geosolutions/imageioimpl/plugins/tiff/TIFFDeflateDecompressor.java @@ -78,12 +78,12 @@ import java.io.IOException; import java.nio.ByteOrder; +import java.util.Arrays; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import javax.imageio.IIOException; - public class TIFFDeflateDecompressor extends TIFFDecompressor { private static final boolean DEBUG = false; @@ -94,11 +94,10 @@ public class TIFFDeflateDecompressor extends TIFFDecompressor { public TIFFDeflateDecompressor(int predictor) throws IIOException { inflater = new Inflater(); - if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && - predictor != - BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { - throw new IIOException("Illegal value for Predictor in " + - "TIFF file"); + if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && + predictor != BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING && + predictor != BaselineTIFFTagSet.PREDICTOR_FLOATING_POINT) { + throw new IIOException("Illegal value for Predictor in TIFF file"); } if(DEBUG) { @@ -114,10 +113,9 @@ public synchronized void decodeRaw(byte[] b, int scanlineStride) throws IOException { // Check bitsPerSample. - if (predictor == - BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + if (predictor == BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { int len = bitsPerSample.length; - final int bps=bitsPerSample[0]; + final int bps = bitsPerSample[0]; if (bps != 8 && bps != 16) { throw new IIOException (bps + "-bit samples " + @@ -125,7 +123,7 @@ public synchronized void decodeRaw(byte[] b, "differencing Predictor"); } for (int i = 0; i < len; i++) { - if( bitsPerSample[i] != bps) { + if (bitsPerSample[i] != bps) { throw new IIOException ("Varying sample width is not " + "supported for Horizontal " + @@ -133,6 +131,31 @@ public synchronized void decodeRaw(byte[] b, bps + ", unexpected:" + bitsPerSample[i] + ")"); } } + } else if (predictor == BaselineTIFFTagSet.PREDICTOR_FLOATING_POINT) { + int len = bitsPerSample.length; + final int bps = bitsPerSample[0]; + if (bps != 16 && bps != 24 && bps != 32 && bps != 64) { + throw new IIOException + (bps + "-bit samples " + + "are not supported for Floating " + + "point Predictor"); + } + for (int i = 0; i < len; i++) { + if (bitsPerSample[i] != bps) { + throw new IIOException + ("Varying sample width is not " + + "supported for Floating " + + "point Predictor (first: " + + bps + ", unexpected:" + bitsPerSample[i] + ")"); + } + } + for (int sf : sampleFormat) { + if (sf != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + throw new IIOException + ("Floating point Predictor not supported" + + "with " + sf + " data format"); + } + } } // Seek to current tile data offset. @@ -207,6 +230,39 @@ else if(bitsPerSample[0]==16) { } } else throw new IIOException("Unexpected branch of Horizontal differencing Predictor, bps="+bitsPerSample[0]); + } else if (predictor == BaselineTIFFTagSet.PREDICTOR_FLOATING_POINT) { + int bytesPerSample = bitsPerSample[0] / 8; + if (bytesPerRow % (bytesPerSample * samplesPerPixel) != 0) { + throw new IIOException + ("The number of bytes in a row (" + bytesPerRow + ") is not divisible" + + "by the number of bytes per pixel (" + bytesPerSample * samplesPerPixel + ")"); + } + + for (int j = 0; j < srcHeight; j++) { + int offset = bufOffset + j * bytesPerRow; + int count = offset + samplesPerPixel; + for (int i = samplesPerPixel; i < bytesPerRow; i++) { + buf[count] += buf[count - samplesPerPixel]; + count++; + } + + // Reorder the semi-BigEndian bytes. + byte[] tmp = Arrays.copyOfRange(buf, offset, offset + bytesPerRow); + int samplesPerRow = srcWidth * samplesPerPixel; + if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < samplesPerRow; i++) { + for (int k = 0; k < bytesPerSample; k++) { + buf[offset + i * bytesPerSample + k] = tmp[k * samplesPerRow + i]; + } + } + } else { + for (int i = 0; i < samplesPerRow; i++) { + for (int k = 0; k < bytesPerSample; k++) { + buf[offset + i * bytesPerSample + k] = tmp[(bytesPerSample - k - 1) * samplesPerRow + i]; + } + } + } + } } if(bytesPerRow != scanlineStride) { diff --git a/plugin/tiff/src/test/java/it/geosolutions/imageio/tiff/TIFFReadTest.java b/plugin/tiff/src/test/java/it/geosolutions/imageio/tiff/TIFFReadTest.java index f55beca8f..c3704ad5e 100644 --- a/plugin/tiff/src/test/java/it/geosolutions/imageio/tiff/TIFFReadTest.java +++ b/plugin/tiff/src/test/java/it/geosolutions/imageio/tiff/TIFFReadTest.java @@ -24,6 +24,7 @@ import java.awt.image.SampleModel; import java.io.File; import java.io.IOException; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.nio.ByteOrder; import java.util.logging.Level; @@ -630,35 +631,44 @@ public void readWithEmptyTiles() throws IOException { public void readLZWWithHorizontalDifferencingPredictorOn16Bits() throws IOException { // This image has been created from test.tif using the command: // gdal_translate -OT UInt16 -co COMPRESS=LZW -co PREDICTOR=2 test.tif lzwtest.tif - final File file = TestData.file(this, "lzwtest.tif"); + assertImagesEqual(readTiff("test.tif"), readTiff("lzwtest.tif")); + } - final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() - .createReaderInstance(); + @Test + public void readDeflateWithHorizontalDifferencingPredictorOn16Bits() throws IOException { + // This image has been created from test.tif using the command: + // gdal_translate -OT UInt16 -co COMPRESS=DEFLATE -co PREDICTOR=2 test.tif deflatetest.tif + assertImagesEqual(readTiff("test.tif"), readTiff("deflatetest.tif")); + } - FileImageInputStream inputStream = new FileImageInputStream(file); - try { - reader.setInput(inputStream); - BufferedImage image = reader.read(0); - image.flush(); - image = null; - } finally { + @Test + public void readDeflateWithFloatingPointPredictor() throws IOException { + // This image has been created from test.tif using the command: + // gdal_translate -ot Float32 -co COMPRESS=DEFLATE -co PREDICTOR=3 test.tif deflate_predictor_3.tif + assertImagesEqual(readTiff("test.tif"), readTiff("deflate_predictor_3.tif")); + } - if (inputStream != null) { - inputStream.flush(); - inputStream.close(); - } + private void assertImagesEqual(BufferedImage expected, BufferedImage actual) { + assertEquals("Widths are different", expected.getWidth(), actual.getWidth()); + assertEquals("Heights are different", expected.getHeight(), actual.getHeight()); + int w = expected.getRaster().getWidth(); + int h = expected.getRaster().getHeight(); + assertArrayEquals( + "Rasters are different", + toByteArray(expected.getRaster().getDataElements(0, 0, w, h, null)), + toByteArray(actual.getRaster().getDataElements(0, 0, w, h, null))); + } - if (reader != null) { - reader.dispose(); - } + private byte[] toByteArray(Object arr) { + byte[] result = new byte[Array.getLength(arr)]; + for (int i = 0; i < result.length; i++) { + result[i] = ((Number) Array.get(arr, i)).byteValue(); } + return result; } - @Test - public void readDeflateWithHorizontalDifferencingPredictorOn16Bits() throws IOException { - // This image has been created from test.tif using the command: - // gdal_translate -OT UInt16 -co COMPRESS=DEFLATE -co PREDICTOR=2 test.tif deflatetest.tif - final File file = TestData.file(this, "deflatetest.tif"); + private BufferedImage readTiff(String filename) throws IOException { + final File file = TestData.file(this, filename); final TIFFImageReader reader = (TIFFImageReader) new TIFFImageReaderSpi() .createReaderInstance(); @@ -668,17 +678,11 @@ public void readDeflateWithHorizontalDifferencingPredictorOn16Bits() throws IOEx reader.setInput(inputStream); BufferedImage image = reader.read(0); image.flush(); - image = null; + return image; } finally { - - if (inputStream != null) { - inputStream.flush(); - inputStream.close(); - } - - if (reader != null) { - reader.dispose(); - } + inputStream.flush(); + inputStream.close(); + reader.dispose(); } } diff --git a/plugin/tiff/src/test/resources/it/geosolutions/imageio/tiff/test-data/deflate_predictor_3.tif b/plugin/tiff/src/test/resources/it/geosolutions/imageio/tiff/test-data/deflate_predictor_3.tif new file mode 100644 index 0000000000000000000000000000000000000000..63026c24d2f61f4fd9de72981afa96b969d58e10 GIT binary patch literal 1091 zcmebD)MDUZU|0pDhL3#ukS-^TP0Xd>b z;$l!X$Q*H~nnET91}R2Xu=;;MacL-<4X9TJNv{SH+ZM`Z2C5H6V)HfgFo4APfO?x+ zco^7#Y&9TzeLFLQ3XmNJWN&DPsF??3Z)|5`kO7Ks1F}H?L?L6a;GumEuaEe>IkIEF z1DKDWXvq1m;>ff&IQ-YzP)C?jMzE>CP+|d@4}?&fiDhHEC{Ph2$HsOx22ln!pb|!g zK4m_R4L}zI0S}0;naKj7m0{+g(|(RF7RCzt3ZWss3RU@esR~Ahel=jh8itBFd#~-! zp6n>W{^9-af?7_+X*{7aO&o78EL{9TD*8X;b`Mu8ixdu}J|&5+=48Qk9zMqTwez{u}{@l>g6J!x$ua5?8-MT%l%{(^GJ2)gqR1P?d;#(V>qy+T`X$- zh1w+>L$%V5Rj=9AbHQ|RYJ9uif;}u(lAikar-j} z_Ww@Jm9+a;@vC&{1@p?R`EO;`mOT&_?=1fwx2Jk>md!G4$2B)^tXO|9{xtsy)*ULJ zzhrn<-=8M8C{o@{S5CusNyNjHTN87qf9JS;?%P>KgS}_3g&t70_c$)1+>yEFyyd3c z?;6>6+2i-l-TP{CtD0gYe}hXigZ+b_(`yTNZ@Bi<`>g))dF%3<7S7nRK7x5k;nk^A zKi$%^GkMeJ*AupX{w`^Q2-YP|)-^@~%X@a;*1ci>)b{(s=UxBj9bpnV>GRO^f@pZz zrt3|G!P>VSUD@BR?{M(>6c-^qCHt^$Zq_9$xs1;z(}kvcp53u2v#aQ`WLwxn=Q`Ej z*N%8|Y+CoOQvZ~0#sGt@IC>`Q+7*{BzRmSr;j750 zl2D252lGSOpWeD#{d3KX#1nQsvSm|Fnx(P5es+vuck#t@W}XL5%OxmmyE>YS!DOYxI(;>&hDTxt6M&eh($)hTxOCYX4;-t5>^U3;dkthD-Q z_UY(dlP|3lthT!~^URaF&u^EO|2yoZmgmtGna6#tY@g;I+06G1dus}|N}N{J_lcPD jm-Tt@;_3JP-Y+qn>Dx2kZsL>If6M;g{=sYT;1weP$