diff --git a/.gitignore b/.gitignore index b83d222..c90e3b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target/ + +# IntelliJ +.idea +*.iml \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4900b0f..0747c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,15 @@ Adheres to [Semantic Versioning](http://semver.org/). --- -## 1.0.4 (TBD) - -* TBD +## [2.0.0](https://github.com/ngageoint/geopackage-tiff-java/releases/tag/2.0.0) (11-20-2017) + +* Rasters modified to use buffers in place of arrays +* Deflate compression support +* Additional Rasters constructor options +* Handle missing samples per pixel with default value of 1 +* Public access to tiff tags +* String Entry Value getter and setter +* maven-gpg-plugin version 1.6 ## [1.0.3](https://github.com/ngageoint/geopackage-tiff-java/releases/tag/1.0.3) (06-27-2017) diff --git a/README.md b/README.md index 2363f7f..6455d09 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ TIFFImage tiffImage = TiffReader.readTiff(input); List directories = tiffImage.getFileDirectories(); FileDirectory directory = directories.get(0); Rasters rasters = directory.readRasters(); - + ``` #### Write #### @@ -76,12 +76,12 @@ TiffWriter.writeTiff(file, tiffImage); ### Installation ### -Pull from the [Maven Central Repository](http://search.maven.org/#artifactdetails|mil.nga|tiff|1.0.3|jar) (JAR, POM, Source, Javadoc) +Pull from the [Maven Central Repository](http://search.maven.org/#artifactdetails|mil.nga|tiff|2.0.0|jar) (JAR, POM, Source, Javadoc) mil.nga tiff - 1.0.3 + 2.0.0 ### Build ### diff --git a/pom.xml b/pom.xml index 0d27a0a..b5c97cf 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 mil.nga tiff - 1.0.4 + 2.0.0 jar Tagged Image File Format https://github.com/ngageoint/geopackage-tiff-java @@ -84,6 +84,7 @@ org.apache.maven.plugins maven-gpg-plugin + 1.6 sign-artifacts diff --git a/src/main/java/mil/nga/tiff/FieldType.java b/src/main/java/mil/nga/tiff/FieldType.java index d9b0ec1..a2a551d 100644 --- a/src/main/java/mil/nga/tiff/FieldType.java +++ b/src/main/java/mil/nga/tiff/FieldType.java @@ -1,5 +1,8 @@ package mil.nga.tiff; +import mil.nga.tiff.util.TiffConstants; +import mil.nga.tiff.util.TiffException; + /** * Field Types * @@ -104,6 +107,16 @@ public int getBytes() { return bytes; } + /** + * Get the number of bits per value + * + * @return number of bits + * @since 2.0.0 + */ + public int getBits() { + return bytes * 8; + } + /** * Get the field type * @@ -115,4 +128,101 @@ public static FieldType getFieldType(int fieldType) { return FieldType.values()[fieldType - 1]; } + /** + * Get the field type of the sample format and bits per sample + * + * @param sampleFormat + * sample format + * @param bitsPerSample + * bits per sample + * @return field type + * @since 2.0.0 + */ + public static FieldType getFieldType(int sampleFormat, int bitsPerSample) { + + FieldType fieldType = null; + + switch (sampleFormat) { + case TiffConstants.SAMPLE_FORMAT_UNSIGNED_INT: + switch (bitsPerSample) { + case 8: + fieldType = FieldType.BYTE; + break; + case 16: + fieldType = FieldType.SHORT; + break; + case 32: + fieldType = FieldType.LONG; + break; + } + break; + case TiffConstants.SAMPLE_FORMAT_SIGNED_INT: + switch (bitsPerSample) { + case 8: + fieldType = FieldType.SBYTE; + break; + case 16: + fieldType = FieldType.SSHORT; + break; + case 32: + fieldType = FieldType.SLONG; + break; + } + break; + case TiffConstants.SAMPLE_FORMAT_FLOAT: + switch (bitsPerSample) { + case 32: + fieldType = FieldType.FLOAT; + break; + case 64: + fieldType = FieldType.DOUBLE; + break; + } + break; + } + + if (fieldType == null) { + throw new TiffException( + "Unsupported field type for sample format: " + sampleFormat + + ", bits per sample: " + bitsPerSample); + } + + return fieldType; + } + + /** + * Get the sample format of the field type + * + * @param fieldType + * field type + * @return sample format + * @since 2.0.0 + */ + public static int getSampleFormat(FieldType fieldType) { + + int sampleFormat; + + switch (fieldType) { + case BYTE: + case SHORT: + case LONG: + sampleFormat = TiffConstants.SAMPLE_FORMAT_UNSIGNED_INT; + break; + case SBYTE: + case SSHORT: + case SLONG: + sampleFormat = TiffConstants.SAMPLE_FORMAT_SIGNED_INT; + break; + case FLOAT: + case DOUBLE: + sampleFormat = TiffConstants.SAMPLE_FORMAT_FLOAT; + break; + default: + throw new TiffException( + "Unsupported sample format for field type: " + fieldType); + } + + return sampleFormat; + } + } diff --git a/src/main/java/mil/nga/tiff/FileDirectory.java b/src/main/java/mil/nga/tiff/FileDirectory.java index 4167861..cb31cc7 100644 --- a/src/main/java/mil/nga/tiff/FileDirectory.java +++ b/src/main/java/mil/nga/tiff/FileDirectory.java @@ -1,5 +1,6 @@ package mil.nga.tiff; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -151,6 +152,7 @@ public FileDirectory(SortedSet entries, throw new TiffException("JPEG compression not supported: " + compression); case TiffConstants.COMPRESSION_DEFLATE: + case TiffConstants.COMPRESSION_PKZIP_DEFLATE: decoder = new DeflateCompression(); break; case TiffConstants.COMPRESSION_PACKBITS: @@ -479,9 +481,16 @@ public void setStripOffsets(long stripOffset) { * Get the samples per pixel * * @return samples per pixel - */ - public Integer getSamplesPerPixel() { - return getIntegerEntryValue(FieldTagType.SamplesPerPixel); + * @since 2.0.0 + */ + public int getSamplesPerPixel() { + Integer samplesPerPixel = getIntegerEntryValue(FieldTagType.SamplesPerPixel); + if (samplesPerPixel == null) { + // if SamplesPerPixel tag is missing, use default value defined by + // TIFF standard + samplesPerPixel = 1; + } + return samplesPerPixel; } /** @@ -1089,21 +1098,37 @@ public Rasters readRasters(ImageWindow window, int[] samples, } } - // Create the interleaved result array - Number[] interleave = null; + // Create the interleaved result buffer + List bitsPerSample = getBitsPerSample(); + int bytesPerPixel = 0; + for (int i = 0; i < samplesPerPixel; ++i) { + bytesPerPixel += bitsPerSample.get(i) / 8; + } + ByteBuffer interleave = null; if (interleaveValues) { - interleave = new Number[numPixels * samples.length]; + interleave = ByteBuffer.allocateDirect(numPixels * bytesPerPixel); + interleave.order(reader.getByteOrder()); } - // Create the sample indexed result double array - Number[][] sample = null; + // Create the sample indexed result buffer array + ByteBuffer[] sample = null; if (sampleValues) { - sample = new Number[samples.length][numPixels]; + sample = new ByteBuffer[samplesPerPixel]; + for (int i = 0; i < sample.length; ++i) { + sample[i] = ByteBuffer.allocateDirect(numPixels + * bitsPerSample.get(i) / 8); + sample[i].order(reader.getByteOrder()); + } + } + + FieldType[] fieldTypes = new FieldType[samples.length]; + for (int i = 0; i < samples.length; i++) { + fieldTypes[i] = getFieldTypeForSample(samples[i]); } // Create the rasters results - Rasters rasters = new Rasters(windowWidth, windowHeight, - samplesPerPixel, getBitsPerSample(), sample, interleave); + Rasters rasters = new Rasters(windowWidth, windowHeight, fieldTypes, + sample, interleave); // Read the rasters readRaster(window, samples, rasters); @@ -1126,13 +1151,10 @@ private void readRaster(ImageWindow window, int[] samples, Rasters rasters) { int tileWidth = getTileWidth().intValue(); int tileHeight = getTileHeight().intValue(); - int minXTile = (int) Math - .floor(window.getMinX() / ((double) tileWidth)); - int maxXTile = (int) Math.ceil(window.getMaxX() / ((double) tileWidth)); - int minYTile = (int) Math.floor(window.getMinY() - / ((double) tileHeight)); - int maxYTile = (int) Math - .ceil(window.getMaxY() / ((double) tileHeight)); + int minXTile = window.getMinX() / tileWidth; + int maxXTile = (window.getMaxX() + tileWidth - 1) / tileWidth; + int minYTile = window.getMinY() / tileHeight; + int maxYTile = (window.getMaxY() + tileHeight - 1) / tileHeight; int windowWidth = window.getMaxX() - window.getMinX(); @@ -1190,10 +1212,9 @@ private void readRaster(ImageWindow window, int[] samples, Rasters rasters) { int windowCoordinate = (y + firstLine - window .getMinY()) * windowWidth - * samples.length - + (x + firstCol - window.getMinX()) - * samples.length + sampleIndex; - rasters.addToInterleave(windowCoordinate, value); + + (x + firstCol - window.getMinX()); + rasters.addToInterleave(sampleIndex, + windowCoordinate, value); } if (rasters.hasSampleValues()) { @@ -1261,57 +1282,22 @@ private Number readValue(ByteReader reader, FieldType fieldType) { /** * Get the field type for the sample - * + * * @param sampleIndex * sample index * @return field type */ public FieldType getFieldTypeForSample(int sampleIndex) { - FieldType fieldType = null; - - List sampleFormat = getSampleFormat(); - int format = sampleFormat != null && sampleIndex < sampleFormat.size() ? sampleFormat - .get(sampleIndex) : TiffConstants.SAMPLE_FORMAT_UNSIGNED_INT; + List sampleFormatList = getSampleFormat(); + int sampleFormat = sampleFormatList == null ? TiffConstants.SAMPLE_FORMAT_UNSIGNED_INT + : sampleFormatList + .get(sampleIndex < sampleFormatList.size() ? sampleIndex + : 0); int bitsPerSample = getBitsPerSample().get(sampleIndex); - switch (format) { - case TiffConstants.SAMPLE_FORMAT_UNSIGNED_INT: - switch (bitsPerSample) { - case 8: - fieldType = FieldType.BYTE; - break; - case 16: - fieldType = FieldType.SHORT; - break; - case 32: - fieldType = FieldType.LONG; - break; - } - break; - case TiffConstants.SAMPLE_FORMAT_SIGNED_INT: - switch (bitsPerSample) { - case 8: - fieldType = FieldType.SBYTE; - break; - case 16: - fieldType = FieldType.SSHORT; - break; - case 32: - fieldType = FieldType.SLONG; - break; - } - break; - case TiffConstants.SAMPLE_FORMAT_FLOAT: - switch (bitsPerSample) { - case 32: - fieldType = FieldType.FLOAT; - break; - case 64: - fieldType = FieldType.DOUBLE; - break; - } - break; - } + + FieldType fieldType = FieldType.getFieldType(sampleFormat, + bitsPerSample); return fieldType; } @@ -1331,10 +1317,12 @@ private byte[] getTileOrStrip(int x, int y, int sample) { byte[] tileOrStrip = null; - int numTilesPerRow = (int) Math.ceil(getImageWidth().doubleValue() - / getTileWidth().doubleValue()); - int numTilesPerCol = (int) Math.ceil(getImageHeight().doubleValue() - / getTileHeight().doubleValue()); + int imageWidth = getImageWidth().intValue(); + int imageHeight = getImageHeight().intValue(); + int tileWidth = getTileWidth().intValue(); + int tileHeight = getTileHeight().intValue(); + int numTilesPerRow = (imageWidth + tileWidth - 1) / tileWidth; + int numTilesPerCol = (imageHeight + tileHeight - 1) / tileHeight; int index = 0; if (planarConfiguration == TiffConstants.PLANAR_CONFIGURATION_CHUNKY) { @@ -1432,8 +1420,9 @@ private int getBytesPerPixel() { * @param fieldTagType * field tag type * @return integer value + * @since 2.0.0 */ - private Integer getIntegerEntryValue(FieldTagType fieldTagType) { + public Integer getIntegerEntryValue(FieldTagType fieldTagType) { return getEntryValue(fieldTagType); } @@ -1444,8 +1433,9 @@ private Integer getIntegerEntryValue(FieldTagType fieldTagType) { * field tag type * @param value * unsigned integer value (16 bit) + * @since 2.0.0 */ - private void setUnsignedIntegerEntryValue(FieldTagType fieldTagType, + public void setUnsignedIntegerEntryValue(FieldTagType fieldTagType, int value) { setEntryValue(fieldTagType, FieldType.SHORT, 1, value); } @@ -1456,8 +1446,9 @@ private void setUnsignedIntegerEntryValue(FieldTagType fieldTagType, * @param fieldTagType * field tag type * @return number value + * @since 2.0.0 */ - private Number getNumberEntryValue(FieldTagType fieldTagType) { + public Number getNumberEntryValue(FieldTagType fieldTagType) { return getEntryValue(fieldTagType); } @@ -1468,19 +1459,53 @@ private Number getNumberEntryValue(FieldTagType fieldTagType) { * field tag type * @param value * unsigned long value (32 bit) + * @since 2.0.0 */ - private void setUnsignedLongEntryValue(FieldTagType fieldTagType, long value) { + public void setUnsignedLongEntryValue(FieldTagType fieldTagType, long value) { setEntryValue(fieldTagType, FieldType.LONG, 1, value); } + /** + * Get a string entry value for the field tag type + * + * @param fieldTagType + * field tag type + * @return string value + * @since 2.0.0 + */ + public String getStringEntryValue(FieldTagType fieldTagType) { + String value = null; + List values = getEntryValue(fieldTagType); + if (values != null && !values.isEmpty()) { + value = values.get(0); + } + return value; + } + + /** + * Set string value for the field tag type + * + * @param fieldTagType + * field tag type + * @param value + * string value + * @since 2.0.0 + */ + public void setStringEntryValue(FieldTagType fieldTagType, String value) { + List values = new ArrayList<>(); + values.add(value); + setEntryValue(fieldTagType, FieldType.ASCII, value.length() + 1, values); + } + /** * Get an integer list entry value * * @param fieldTagType * field tag type * @return integer list value + * @since 2.0.0 */ - private List getIntegerListEntryValue(FieldTagType fieldTagType) { + public List getIntegerListEntryValue(FieldTagType fieldTagType) { return getEntryValue(fieldTagType); } @@ -1488,9 +1513,12 @@ private List getIntegerListEntryValue(FieldTagType fieldTagType) { * Set an unsigned integer list of values for the field tag type * * @param fieldTagType + * field tag type * @param value + * integer list value + * @since 2.0.0 */ - private void setUnsignedIntegerListEntryValue(FieldTagType fieldTagType, + public void setUnsignedIntegerListEntryValue(FieldTagType fieldTagType, List value) { setEntryValue(fieldTagType, FieldType.SHORT, value.size(), value); } @@ -1501,12 +1529,13 @@ private void setUnsignedIntegerListEntryValue(FieldTagType fieldTagType, * @param fieldTagType * field tag type * @return max integer value + * @since 2.0.0 */ - private Integer getMaxIntegerEntryValue(FieldTagType fieldTagType) { + public Integer getMaxIntegerEntryValue(FieldTagType fieldTagType) { Integer maxValue = null; List values = getIntegerListEntryValue(fieldTagType); if (values != null) { - maxValue = Collections.max(getSampleFormat()); + maxValue = Collections.max(values); } return maxValue; } @@ -1517,8 +1546,9 @@ private Integer getMaxIntegerEntryValue(FieldTagType fieldTagType) { * @param fieldTagType * field tag type * @return long list value + * @since 2.0.0 */ - private List getNumberListEntryValue(FieldTagType fieldTagType) { + public List getNumberListEntryValue(FieldTagType fieldTagType) { return getEntryValue(fieldTagType); } @@ -1528,8 +1558,9 @@ private List getNumberListEntryValue(FieldTagType fieldTagType) { * @param fieldTagType * field tag type * @return long list value + * @since 2.0.0 */ - private List getLongListEntryValue(FieldTagType fieldTagType) { + public List getLongListEntryValue(FieldTagType fieldTagType) { return getEntryValue(fieldTagType); } @@ -1537,9 +1568,12 @@ private List getLongListEntryValue(FieldTagType fieldTagType) { * Set an unsigned long list of values for the field tag type * * @param fieldTagType + * field tag type * @param value + * long list value + * @since 2.0.0 */ - private void setUnsignedLongListEntryValue(FieldTagType fieldTagType, + public void setUnsignedLongListEntryValue(FieldTagType fieldTagType, List value) { setEntryValue(fieldTagType, FieldType.LONG, value.size(), value); } diff --git a/src/main/java/mil/nga/tiff/Rasters.java b/src/main/java/mil/nga/tiff/Rasters.java index 36b56b5..5a00e0f 100644 --- a/src/main/java/mil/nga/tiff/Rasters.java +++ b/src/main/java/mil/nga/tiff/Rasters.java @@ -1,5 +1,7 @@ package mil.nga.tiff; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -17,12 +19,12 @@ public class Rasters { /** * Values separated by sample */ - private Number[][] sampleValues; + private ByteBuffer[] sampleValues; /** * Interleaved pixel sample values */ - private Number[] interleaveValues; + private ByteBuffer interleaveValues; /** * Width of pixels @@ -35,14 +37,26 @@ public class Rasters { private final int height; /** - * Samples per pixel + * Field type for each sample */ - private final int samplesPerPixel; + private final FieldType[] fieldTypes; + + // Calculated values + + /** + * Calculated pixel size in bytes + */ + private Integer pixelSize; + + /** + * @see #getBitsPerSample() + */ + private List bitsPerSample; /** - * Bits per sample + * @see #getSampleFormat() */ - private final List bitsPerSample; + private List sampleFormat; /** * Constructor @@ -51,16 +65,15 @@ public class Rasters { * width of pixels * @param height * height of pixels - * @param samplesPerPixel - * samples per pixel - * @param bitsPerSample - * bits per sample + * @param fieldTypes + * field type for each sample * @param sampleValues - * empty sample values double array + * empty sample values buffer array + * @since 2.0.0 */ - public Rasters(int width, int height, int samplesPerPixel, - List bitsPerSample, Number[][] sampleValues) { - this(width, height, samplesPerPixel, bitsPerSample, sampleValues, null); + public Rasters(int width, int height, FieldType[] fieldTypes, + ByteBuffer[] sampleValues) { + this(width, height, fieldTypes, sampleValues, null); } /** @@ -70,17 +83,15 @@ public Rasters(int width, int height, int samplesPerPixel, * width of pixels * @param height * height of pixels - * @param samplesPerPixel - * samples per pixel - * @param bitsPerSample - * bits per sample + * @param fieldTypes + * field type for each sample * @param interleaveValues - * empty interleaved values array + * empty interleaved values buffer + * @since 2.0.0 */ - public Rasters(int width, int height, int samplesPerPixel, - List bitsPerSample, Number[] interleaveValues) { - this(width, height, samplesPerPixel, bitsPerSample, null, - interleaveValues); + public Rasters(int width, int height, FieldType[] fieldTypes, + ByteBuffer interleaveValues) { + this(width, height, fieldTypes, null, interleaveValues); } /** @@ -90,76 +101,249 @@ public Rasters(int width, int height, int samplesPerPixel, * width of pixels * @param height * height of pixels - * @param samplesPerPixel - * samples per pixel - * @param bitsPerSample - * bits per sample + * @param fieldTypes + * Field type for each sample * @param sampleValues - * empty sample values double array + * empty sample values buffer array * @param interleaveValues - * empty interleaved values array + * empty interleaved values buffer + * @since 2.0.0 */ - public Rasters(int width, int height, int samplesPerPixel, - List bitsPerSample, Number[][] sampleValues, - Number[] interleaveValues) { + public Rasters(int width, int height, FieldType[] fieldTypes, + ByteBuffer[] sampleValues, ByteBuffer interleaveValues) { this.width = width; this.height = height; - this.samplesPerPixel = samplesPerPixel; - this.bitsPerSample = bitsPerSample; + this.fieldTypes = fieldTypes; this.sampleValues = sampleValues; this.interleaveValues = interleaveValues; validateValues(); - for (int bits : bitsPerSample) { - if ((bits % 8) != 0) { - throw new TiffException("Sample bit-width of " + bits - + " is not supported"); - } - } } /** - * Validate that either sample or interleave values exist + * Constructor + * + * Creates Rasters object where given field type used for each sample. + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param samplesPerPixel + * number of samples per pixel + * @param fieldType + * type of field for each sample + * @since 2.0.0 */ - private void validateValues() { - if (sampleValues == null && interleaveValues == null) { - throw new TiffException( - "Results must be sample and/or interleave based"); - } + public Rasters(int width, int height, int samplesPerPixel, + FieldType fieldType) { + this(width, height, createFieldTypeArray(samplesPerPixel, fieldType)); + } + + /** + * Constructor + * + * Creates Rasters object where given field type used for each sample. + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param samplesPerPixel + * number of samples per pixel + * @param fieldType + * type of field for each sample + * @param order + * byte order + * @since 2.0.0 + */ + public Rasters(int width, int height, int samplesPerPixel, + FieldType fieldType, ByteOrder order) { + this(width, height, createFieldTypeArray(samplesPerPixel, fieldType), + order); + } + + /** + * Constructor + * + * Creates Rasters object where one bits per sample and sample format is + * provided for each sample + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param bitsPerSamples + * bits per samples + * @param sampleFormats + * sample formats + * @since 2.0.0 + */ + public Rasters(int width, int height, int[] bitsPerSamples, + int[] sampleFormats) { + this(width, height, createFieldTypeArray(bitsPerSamples, sampleFormats)); + } + + /** + * Constructor + * + * Creates Rasters object where one bits per sample and sample format is + * provided for each sample + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param bitsPerSamples + * bits per samples + * @param sampleFormats + * sample formats + * @param order + * byte order + * @since 2.0.0 + */ + public Rasters(int width, int height, int[] bitsPerSamples, + int[] sampleFormats, ByteOrder order) { + this(width, height, + createFieldTypeArray(bitsPerSamples, sampleFormats), order); } /** * Constructor * + * Creates Rasters object where given bits per sample and sample format is + * used for each sample + * * @param width * width of pixels * @param height * height of pixels * @param samplesPerPixel - * samples per pixel + * number of samples per pixel * @param bitsPerSample - * bits per sample + * bits per each sample + * @param sampleFormat + * format for each sample + * @since 2.0.0 */ public Rasters(int width, int height, int samplesPerPixel, - List bitsPerSample) { - this(width, height, samplesPerPixel, bitsPerSample, - new Number[samplesPerPixel][width * height]); + int bitsPerSample, int sampleFormat) { + this(width, height, samplesPerPixel, FieldType.getFieldType( + sampleFormat, bitsPerSample)); } /** * Constructor * + * Creates Rasters object where given bits per sample and sample format is + * used for each sample + * * @param width * width of pixels * @param height * height of pixels * @param samplesPerPixel - * samples per pixel + * number of samples per pixel * @param bitsPerSample - * single sample bits per sample + * bits per each sample + * @param sampleFormat + * format for each sample + * @param order + * byte order + * @since 2.0.0 + */ + public Rasters(int width, int height, int samplesPerPixel, + int bitsPerSample, int sampleFormat, ByteOrder order) { + this(width, height, samplesPerPixel, FieldType.getFieldType( + sampleFormat, bitsPerSample), order); + } + + /** + * Constructor + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param fieldTypes + * field types per sample + * @since 2.0.0 + */ + public Rasters(int width, int height, FieldType[] fieldTypes) { + this(width, height, fieldTypes, ByteOrder.nativeOrder()); + } + + /** + * Constructor + * + * @param width + * width of pixels + * @param height + * height of pixels + * @param fieldTypes + * field types per sample + * @param order + * byte order + * @since 2.0.0 */ - public Rasters(int width, int height, int samplesPerPixel, int bitsPerSample) { - this(width, height, samplesPerPixel, new ArrayList( - Arrays.asList(bitsPerSample))); + public Rasters(int width, int height, FieldType[] fieldTypes, + ByteOrder order) { + this(width, height, fieldTypes, new ByteBuffer[fieldTypes.length]); + for (int i = 0; i < sampleValues.length; ++i) { + sampleValues[i] = ByteBuffer.allocateDirect( + width * height * fieldTypes[i].getBytes()).order(order); + } + } + + /** + * Validate that either sample or interleave values exist + */ + private void validateValues() { + if (sampleValues == null && interleaveValues == null) { + throw new TiffException( + "Results must be sample and/or interleave based"); + } + } + + /** + * Create {@link FieldType} filled array for samples per pixel size + * + * @param samplesPerPixel + * number of samples per pixel + * @param fieldType + * type of field for each sample + * @return field type array + */ + private static FieldType[] createFieldTypeArray(int samplesPerPixel, + FieldType fieldType) { + FieldType[] result = new FieldType[samplesPerPixel]; + Arrays.fill(result, fieldType); + return result; + } + + /** + * Create {@link FieldType} array for the bits per samples and sample + * formats + * + * @param bitsPerSamples + * bits per samples + * @param sampleFormats + * sample formats + * @return field type array + */ + private static FieldType[] createFieldTypeArray(int[] bitsPerSamples, + int[] sampleFormats) { + if (bitsPerSamples.length != sampleFormats.length) { + throw new TiffException( + "Equal number of bits per samples and sample formats expected. " + + "Bits Per Samples: " + bitsPerSamples + + ", Sample Formats: " + sampleFormats); + } + FieldType[] result = new FieldType[bitsPerSamples.length]; + for (int i = 0; i < bitsPerSamples.length; i++) { + result[i] = FieldType.getFieldType(sampleFormats[i], + bitsPerSamples[i]); + } + return result; } /** @@ -180,6 +364,54 @@ public boolean hasInterleaveValues() { return interleaveValues != null; } + /** + * Updates sample to given value in buffer. + * + * @param buffer + * A buffer to be updated. + * @param bufferIndex + * Position in buffer where to update. + * @param sampleIndex + * Sample index in sampleFieldTypes. Needed for determining + * sample size. + * @param value + * A Number value to be put in buffer. Has to be same size as + * sampleFieldTypes[sampleIndex]. + */ + private void updateSampleInByteBuffer(ByteBuffer buffer, int bufferIndex, + int sampleIndex, Number value) { + if (bufferIndex < 0 || bufferIndex >= buffer.capacity()) { + throw new IndexOutOfBoundsException("index: " + bufferIndex + + ". Buffer capacity: " + buffer.capacity()); + } + + buffer.position(bufferIndex); + writeSample(buffer, fieldTypes[sampleIndex], value); + } + + /** + * Reads sample from given buffer. + * + * @param buffer + * A buffer to read from + * @param index + * Position in buffer where to read from + * @param sampleIndex + * Index of sample type to read + * @return Number read from buffer + */ + private Number getSampleFromByteBuffer(ByteBuffer buffer, int index, + int sampleIndex) { + + if (index < 0 || index >= buffer.capacity()) { + throw new IndexOutOfBoundsException("Requested index: " + index + + ", but size of buffer is: " + buffer.capacity()); + } + + buffer.position(index); + return readSample(buffer, fieldTypes[sampleIndex]); + } + /** * Add a value to the sample results * @@ -191,19 +423,29 @@ public boolean hasInterleaveValues() { * value */ public void addToSample(int sampleIndex, int coordinate, Number value) { - sampleValues[sampleIndex][coordinate] = value; + updateSampleInByteBuffer(sampleValues[sampleIndex], coordinate + * fieldTypes[sampleIndex].getBytes(), sampleIndex, value); } /** * Add a value to the interleaved results * + * @param sampleIndex + * sample index * @param coordinate * coordinate location * @param value * value + * @since 2.0.0 */ - public void addToInterleave(int coordinate, Number value) { - interleaveValues[coordinate] = value; + public void addToInterleave(int sampleIndex, int coordinate, Number value) { + int bufferPos = coordinate * sizePixel(); + for (int i = 0; i < sampleIndex; ++i) { + bufferPos += fieldTypes[i].getBytes(); + } + + updateSampleInByteBuffer(interleaveValues, bufferPos, sampleIndex, + value); } /** @@ -239,7 +481,7 @@ public int getNumPixels() { * @return samples per pixel */ public int getSamplesPerPixel() { - return samplesPerPixel; + return fieldTypes.length; } /** @@ -248,15 +490,49 @@ public int getSamplesPerPixel() { * @return bits per sample */ public List getBitsPerSample() { - return bitsPerSample; + List result = bitsPerSample; + if (result == null) { + result = new ArrayList<>(fieldTypes.length); + for (FieldType fieldType : fieldTypes) { + result.add(fieldType.getBits()); + } + bitsPerSample = result; + } + return result; + } + + /** + * Returns list of sample types constants + * + * Returns list of sample types constants (SAMPLE_FORMAT_UNSIGNED_INT, + * SAMPLE_FORMAT_SIGNED_INT or SAMPLE_FORMAT_FLOAT) for each sample in + * sample list @see getFieldTypes(). @see {@link TiffConstants} + * + * @return list of sample type constants + * @since 2.0.0 + */ + public List getSampleFormat() { + List result = sampleFormat; + if (result == null) { + result = new ArrayList<>(fieldTypes.length); + for (FieldType fieldType : fieldTypes) { + result.add(FieldType.getSampleFormat(fieldType)); + } + sampleFormat = result; + } + return result; } /** * Get the results stored by samples * * @return sample values + * @since 2.0.0 */ - public Number[][] getSampleValues() { + public ByteBuffer[] getSampleValues() { + for (int i = 0; i < sampleValues.length; ++i) { + sampleValues[i].rewind(); + } return sampleValues; } @@ -265,9 +541,13 @@ public Number[][] getSampleValues() { * * @param sampleValues * sample values + * @since 2.0.0 */ - public void setSampleValues(Number[][] sampleValues) { + public void setSampleValues(ByteBuffer[] sampleValues) { this.sampleValues = sampleValues; + this.sampleFormat = null; + this.bitsPerSample = null; + this.pixelSize = null; validateValues(); } @@ -275,8 +555,10 @@ public void setSampleValues(Number[][] sampleValues) { * Get the results stored as interleaved pixel samples * * @return interleaved values + * @since 2.0.0 */ - public Number[] getInterleaveValues() { + public ByteBuffer getInterleaveValues() { + interleaveValues.rewind(); return interleaveValues; } @@ -285,8 +567,9 @@ public Number[] getInterleaveValues() { * * @param interleaveValues * interleaved values + * @since 2.0.0 */ - public void setInterleaveValues(Number[] interleaveValues) { + public void setInterleaveValues(ByteBuffer interleaveValues) { this.interleaveValues = interleaveValues; validateValues(); } @@ -305,18 +588,22 @@ public Number[] getPixel(int x, int y) { validateCoordinates(x, y); // Pixel with each sample value - Number[] pixel = new Number[samplesPerPixel]; + Number[] pixel = new Number[getSamplesPerPixel()]; // Get the pixel values from each sample if (sampleValues != null) { int sampleIndex = getSampleIndex(x, y); - for (int i = 0; i < samplesPerPixel; i++) { - pixel[i] = sampleValues[i][sampleIndex]; + for (int i = 0; i < getSamplesPerPixel(); i++) { + int bufferIndex = sampleIndex * fieldTypes[i].getBytes(); + pixel[i] = getSampleFromByteBuffer(sampleValues[i], + bufferIndex, i); } } else { int interleaveIndex = getInterleaveIndex(x, y); - for (int i = 0; i < samplesPerPixel; i++) { - pixel[i] = interleaveValues[interleaveIndex++]; + for (int i = 0; i < getSamplesPerPixel(); i++) { + pixel[i] = getSampleFromByteBuffer(interleaveValues, + interleaveIndex, i); + interleaveIndex += fieldTypes[i].getBytes(); } } @@ -340,18 +627,98 @@ public void setPixel(int x, int y, Number[] values) { // Set the pixel values from each sample if (sampleValues != null) { - int sampleIndex = getSampleIndex(x, y); - for (int i = 0; i < samplesPerPixel; i++) { - sampleValues[i][sampleIndex] = values[i]; + for (int i = 0; i < getSamplesPerPixel(); i++) { + int bufferIndex = getSampleIndex(x, y) + * fieldTypes[i].getBytes(); + updateSampleInByteBuffer(sampleValues[i], bufferIndex, i, + values[i]); } } else { - int interleaveIndex = getInterleaveIndex(x, y); - for (int i = 0; i < samplesPerPixel; i++) { - interleaveValues[interleaveIndex++] = values[i]; + int interleaveIndex = getSampleIndex(x, y) * sizePixel(); + for (int i = 0; i < getSamplesPerPixel(); i++) { + updateSampleInByteBuffer(interleaveValues, interleaveIndex, i, + values[i]); + interleaveIndex += fieldTypes[i].getBytes(); } } } + /** + * Returns byte array of pixel row. + * + * @param y + * Row index + * @param newOrder + * Desired byte order of result byte array + * @return Byte array of pixel row + * @since 2.0.0 + */ + public byte[] getPixelRow(int y, ByteOrder newOrder) { + ByteBuffer outBuffer = ByteBuffer.allocate(getWidth() * sizePixel()); + outBuffer.order(newOrder); + + if (sampleValues != null) { + for (int i = 0; i < getSamplesPerPixel(); ++i) { + sampleValues[i].position(y * getWidth() + * fieldTypes[i].getBytes()); + } + for (int i = 0; i < getWidth(); ++i) { + for (int j = 0; j < getSamplesPerPixel(); ++j) { + writeSample(outBuffer, sampleValues[j], fieldTypes[j]); + } + } + } else { + interleaveValues.position(y * getWidth() * sizePixel()); + + for (int i = 0; i < getWidth(); ++i) { + for (int j = 0; j < getSamplesPerPixel(); ++j) { + writeSample(outBuffer, interleaveValues, fieldTypes[j]); + } + } + } + + return outBuffer.array(); + } + + /** + * Returns byte array of sample row. + * + * @param y + * Row index + * @param sample + * Sample index + * @param newOrder + * Desired byte order of resulting byte array + * @return Byte array of sample row + * @since 2.0.0 + */ + public byte[] getSampleRow(int y, int sample, ByteOrder newOrder) { + ByteBuffer outBuffer = ByteBuffer.allocate(getWidth() + * fieldTypes[sample].getBytes()); + outBuffer.order(newOrder); + + if (sampleValues != null) { + sampleValues[sample].position(y * getWidth() + * fieldTypes[sample].getBytes()); + for (int x = 0; x < getWidth(); ++x) { + writeSample(outBuffer, sampleValues[sample], fieldTypes[sample]); + } + } else { + int sampleOffset = 0; + for (int i = 0; i < sample; ++i) { + sampleOffset += fieldTypes[sample].getBytes(); + } + + for (int i = 0; i < getWidth(); ++i) { + interleaveValues.position((y * getWidth() + i) * sizePixel() + + sampleOffset); + writeSample(outBuffer, interleaveValues, fieldTypes[sample]); + } + } + + return outBuffer.array(); + } + /** * Get a pixel sample value * @@ -373,18 +740,25 @@ public Number getPixelSample(int sample, int x, int y) { // Get the pixel sample if (sampleValues != null) { - int sampleIndex = getSampleIndex(x, y); - pixelSample = sampleValues[sample][sampleIndex]; + int bufferPos = getSampleIndex(x, y) + * fieldTypes[sample].getBytes(); + pixelSample = getSampleFromByteBuffer(sampleValues[sample], + bufferPos, sample); } else { - int interleaveIndex = getInterleaveIndex(x, y); - pixelSample = interleaveValues[interleaveIndex + sample]; + int bufferPos = getInterleaveIndex(x, y); + for (int i = 0; i < sample; i++) { + bufferPos += fieldTypes[sample].getBytes(); + } + + pixelSample = getSampleFromByteBuffer(interleaveValues, bufferPos, + sample); } return pixelSample; } /** - * Set a pixel vample value + * Set a pixel sample value * * @param sample * sample index (>= 0 && < {@link #getSamplesPerPixel()}) @@ -402,12 +776,18 @@ public void setPixelSample(int sample, int x, int y, Number value) { // Set the pixel sample if (sampleValues != null) { - int sampleIndex = getSampleIndex(x, y); - sampleValues[sample][sampleIndex] = value; + int sampleIndex = getSampleIndex(x, y) + * fieldTypes[sample].getBytes(); + updateSampleInByteBuffer(sampleValues[sample], sampleIndex, sample, + value); } if (interleaveValues != null) { - int interleaveIndex = getInterleaveIndex(x, y); - interleaveValues[interleaveIndex + sample] = value; + int interleaveIndex = getSampleIndex(x, y) * sizePixel(); + for (int i = 0; i < sample; ++i) { + interleaveIndex += fieldTypes[sample].getBytes(); + } + updateSampleInByteBuffer(interleaveValues, interleaveIndex, sample, + value); } } @@ -463,7 +843,7 @@ public int getSampleIndex(int x, int y) { * @return interleave index */ public int getInterleaveIndex(int x, int y) { - return (y * width * samplesPerPixel) + (x * samplesPerPixel); + return (y * width * sizePixel()) + (x * sizePixel()); } /** @@ -481,24 +861,18 @@ public int size() { * @return bytes */ public int sizePixel() { + if (pixelSize != null) { + return pixelSize; + } + int size = 0; - for (int i = 0; i < samplesPerPixel; i++) { - size += sizeSample(i); + for (int i = 0; i < getSamplesPerPixel(); i++) { + size += fieldTypes[i].getBytes(); } + pixelSize = size; return size; } - /** - * Size in bytes of a sample - * - * @param sample - * sample index - * @return bytes - */ - public int sizeSample(int sample) { - return bitsPerSample.get(sample) / 8; - } - /** * Validate the coordinates range * @@ -521,9 +895,9 @@ private void validateCoordinates(int x, int y) { * sample index */ private void validateSample(int sample) { - if (sample < 0 || sample >= samplesPerPixel) { + if (sample < 0 || sample >= getSamplesPerPixel()) { throw new TiffException("Pixel sample out of bounds. sample: " - + sample + ", samples per pixel: " + samplesPerPixel); + + sample + ", samples per pixel: " + getSamplesPerPixel()); } } @@ -554,16 +928,12 @@ public int calculateRowsPerStrip(int planarConfiguration, Integer rowsPerStrip = null; if (planarConfiguration == TiffConstants.PLANAR_CONFIGURATION_CHUNKY) { - int bitsPerPixel = 0; - for (int sampleBits : bitsPerSample) { - bitsPerPixel += sampleBits; - } - rowsPerStrip = rowsPerStrip(bitsPerPixel, maxBytesPerStrip); + rowsPerStrip = rowsPerStrip(sizePixel(), maxBytesPerStrip); } else { - for (int sampleBits : bitsPerSample) { - int rowsPerStripForSample = rowsPerStrip(sampleBits, - maxBytesPerStrip); + for (int sample = 0; sample < getSamplesPerPixel(); ++sample) { + int rowsPerStripForSample = rowsPerStrip( + fieldTypes[sample].getBytes(), maxBytesPerStrip); if (rowsPerStrip == null || rowsPerStripForSample < rowsPerStrip) { rowsPerStrip = rowsPerStripForSample; @@ -578,15 +948,14 @@ public int calculateRowsPerStrip(int planarConfiguration, * Get the rows per strip based upon the bits per pixel and max bytes per * strip * - * @param bitsPerPixel - * bits per pixel + * @param bytesPerPixel + * bytes per pixel * @param maxBytesPerStrip * max bytes per strip * @return rows per strip */ - private int rowsPerStrip(int bitsPerPixel, int maxBytesPerStrip) { + private int rowsPerStrip(int bytesPerPixel, int maxBytesPerStrip) { - int bytesPerPixel = (int) Math.ceil(bitsPerPixel / 8.0); int bytesPerRow = bytesPerPixel * width; int rowsPerStrip = Math.max(1, maxBytesPerStrip / bytesPerRow); @@ -594,4 +963,133 @@ private int rowsPerStrip(int bitsPerPixel, int maxBytesPerStrip) { return rowsPerStrip; } + /** + * Reads sample from given buffer + * + * @param buffer + * A buffer to read from. @note Make sure position is set. + * @param fieldType + * field type to be read + * @return Sample from buffer + */ + private Number readSample(ByteBuffer buffer, FieldType fieldType) { + Number sampleValue; + + switch (fieldType) { + case BYTE: + sampleValue = (short) (buffer.get() & 0xff); + break; + case SHORT: + sampleValue = buffer.getShort() & 0xffff; + break; + case LONG: + sampleValue = buffer.getInt() & 0xffffffffL; + break; + case SBYTE: + sampleValue = buffer.get(); + break; + case SSHORT: + sampleValue = buffer.getShort(); + break; + case SLONG: + sampleValue = buffer.getInt(); + break; + case FLOAT: + sampleValue = buffer.getFloat(); + break; + case DOUBLE: + sampleValue = buffer.getDouble(); + break; + default: + throw new TiffException("Unsupported raster field type: " + + fieldType); + } + + return sampleValue; + } + + /** + * Writes sample into given buffer. + * + * @param buffer + * A buffer to write to. @note Make sure buffer position is set. + * @param fieldType + * field type to be written. + * @param value + * Actual value to write. + */ + private void writeSample(ByteBuffer buffer, FieldType fieldType, + Number value) { + switch (fieldType) { + case BYTE: + case SBYTE: + buffer.put(value.byteValue()); + break; + case SHORT: + case SSHORT: + buffer.putShort(value.shortValue()); + break; + case LONG: + case SLONG: + buffer.putInt(value.intValue()); + break; + case FLOAT: + buffer.putFloat(value.floatValue()); + break; + case DOUBLE: + buffer.putDouble(value.doubleValue()); + break; + default: + throw new TiffException("Unsupported raster field type: " + + fieldType); + } + } + + /** + * Writes sample from input buffer to given output buffer. + * + * @param outBuffer + * A buffer to write to. @note Make sure buffer position is set. + * @param inBuffer + * A buffer to read from. @note Make sure buffer position is set. + * @param fieldType + * Field type to be read. + */ + private void writeSample(ByteBuffer outBuffer, ByteBuffer inBuffer, + FieldType fieldType) { + switch (fieldType) { + case BYTE: + case SBYTE: + outBuffer.put(inBuffer.get()); + break; + case SHORT: + case SSHORT: + outBuffer.putShort(inBuffer.getShort()); + break; + case LONG: + case SLONG: + outBuffer.putInt(inBuffer.getInt()); + break; + case FLOAT: + outBuffer.putFloat(inBuffer.getFloat()); + break; + case DOUBLE: + outBuffer.putDouble(inBuffer.getDouble()); + break; + default: + throw new TiffException("Unsupported raster field type: " + + fieldType); + } + } + + /** + * Returns field types + * + * @return field types + * @since 2.0.0 + */ + public FieldType[] getFieldTypes() { + return fieldTypes; + } + } \ No newline at end of file diff --git a/src/main/java/mil/nga/tiff/TiffWriter.java b/src/main/java/mil/nga/tiff/TiffWriter.java index c6d308c..50b5bdb 100644 --- a/src/main/java/mil/nga/tiff/TiffWriter.java +++ b/src/main/java/mil/nga/tiff/TiffWriter.java @@ -174,7 +174,7 @@ private static void writeImageFileDirectories(ByteWriter writer, List valueBytesCheck = new ArrayList<>(); // Write the raster bytes to temporary storage - if (fileDirectory.getRowsPerStrip() == null) { + if (fileDirectory.isTiled()) { throw new TiffException("Tiled images are not supported"); } @@ -259,7 +259,7 @@ private static void populateRasterEntries(FileDirectory fileDirectory) { } // Populate the raster entries - if (fileDirectory.getRowsPerStrip() != null) { + if (!fileDirectory.isTiled()) { populateStripEntries(fileDirectory); } else { throw new TiffException("Tiled images are not supported"); @@ -272,14 +272,12 @@ private static void populateRasterEntries(FileDirectory fileDirectory) { * * @param fileDirectory * file directory - * @param strips - * number of strips */ private static void populateStripEntries(FileDirectory fileDirectory) { int rowsPerStrip = fileDirectory.getRowsPerStrip().intValue(); - int stripsPerSample = (int) Math.ceil(fileDirectory.getImageHeight() - .doubleValue() / rowsPerStrip); + int imageHeight = fileDirectory.getImageHeight().intValue(); + int stripsPerSample = (imageHeight + rowsPerStrip - 1) / rowsPerStrip; int strips = stripsPerSample; if (fileDirectory.getPlanarConfiguration() == TiffConstants.PLANAR_CONFIGURATION_PLANAR) { strips *= fileDirectory.getSamplesPerPixel(); @@ -312,14 +310,6 @@ private static byte[] writeRasters(ByteOrder byteOrder, "File Directory Writer Rasters is required to create a TIFF"); } - // Get the sample field types - FieldType[] sampleFieldTypes = new FieldType[rasters - .getSamplesPerPixel()]; - for (int sample = 0; sample < rasters.getSamplesPerPixel(); sample++) { - sampleFieldTypes[sample] = fileDirectory - .getFieldTypeForSample(sample); - } - // Get the compression encoder CompressionEncoder encoder = getEncoder(fileDirectory); @@ -327,9 +317,8 @@ private static byte[] writeRasters(ByteOrder byteOrder, ByteWriter writer = new ByteWriter(byteOrder); // Write the rasters - if (fileDirectory.getRowsPerStrip() != null) { - writeStripRasters(writer, fileDirectory, offset, sampleFieldTypes, - encoder); + if (!fileDirectory.isTiled()) { + writeStripRasters(writer, fileDirectory, offset, encoder); } else { throw new TiffException("Tiled images are not supported"); } @@ -350,15 +339,12 @@ private static byte[] writeRasters(ByteOrder byteOrder, * file directory * @param offset * byte offset - * @param sampleFieldTypes - * sample field types * @param encoder * compression encoder * @throws IOException */ private static void writeStripRasters(ByteWriter writer, - FileDirectory fileDirectory, long offset, - FieldType[] sampleFieldTypes, CompressionEncoder encoder) + FileDirectory fileDirectory, long offset, CompressionEncoder encoder) throws IOException { Rasters rasters = fileDirectory.getWriteRasters(); @@ -366,8 +352,7 @@ private static void writeStripRasters(ByteWriter writer, // Get the row and strip counts int rowsPerStrip = fileDirectory.getRowsPerStrip().intValue(); int maxY = fileDirectory.getImageHeight().intValue(); - int stripsPerSample = (int) Math.ceil((double) maxY - / (double) rowsPerStrip); + int stripsPerSample = (maxY + rowsPerStrip - 1) / rowsPerStrip; int strips = stripsPerSample; if (fileDirectory.getPlanarConfiguration() == TiffConstants.PLANAR_CONFIGURATION_PLANAR) { strips *= fileDirectory.getSamplesPerPixel(); @@ -394,28 +379,15 @@ private static void writeStripRasters(ByteWriter writer, int endingY = Math.min(startingY + rowsPerStrip, maxY); for (int y = startingY; y < endingY; y++) { - - ByteWriter rowWriter = new ByteWriter(writer.getByteOrder()); - - for (int x = 0; x < fileDirectory.getImageWidth().intValue(); x++) { - - if (sample != null) { - Number value = rasters.getPixelSample(sample, x, y); - FieldType fieldType = sampleFieldTypes[sample]; - writeValue(rowWriter, fieldType, value); - } else { - Number[] values = rasters.getPixel(x, y); - for (int sampleIndex = 0; sampleIndex < values.length; sampleIndex++) { - Number value = values[sampleIndex]; - FieldType fieldType = sampleFieldTypes[sampleIndex]; - writeValue(rowWriter, fieldType, value); - } - } + // Get the row bytes and encode if needed + byte[] rowBytes = null; + if (sample != null) { + rowBytes = rasters.getSampleRow(y, sample, + writer.getByteOrder()); + } else { + rowBytes = rasters.getPixelRow(y, writer.getByteOrder()); } - // Get the row bytes and encode if needed - byte[] rowBytes = rowWriter.getBytes(); - rowWriter.close(); if (encoder.rowEncoding()) { rowBytes = encoder.encode(rowBytes, writer.getByteOrder()); } @@ -457,6 +429,7 @@ private static void writeStripRasters(ByteWriter writer, * file directory * @return encoder */ + @SuppressWarnings("deprecation") private static CompressionEncoder getEncoder(FileDirectory fileDirectory) { CompressionEncoder encoder = null; @@ -466,6 +439,7 @@ private static CompressionEncoder getEncoder(FileDirectory fileDirectory) { if (compression == null) { compression = TiffConstants.COMPRESSION_NO; } + switch (compression) { case TiffConstants.COMPRESSION_NO: encoder = new RawCompression(); @@ -487,6 +461,7 @@ private static CompressionEncoder getEncoder(FileDirectory fileDirectory) { throw new TiffException("JPEG compression not supported: " + compression); case TiffConstants.COMPRESSION_DEFLATE: + case TiffConstants.COMPRESSION_PKZIP_DEFLATE: encoder = new DeflateCompression(); break; case TiffConstants.COMPRESSION_PACKBITS: @@ -500,50 +475,6 @@ private static CompressionEncoder getEncoder(FileDirectory fileDirectory) { return encoder; } - /** - * Write the value according to the field type - * - * @param writer - * byte writer - * @param fieldType - * field type - * @throws IOException - */ - private static void writeValue(ByteWriter writer, FieldType fieldType, - Number value) throws IOException { - - switch (fieldType) { - case BYTE: - writer.writeUnsignedByte(value.shortValue()); - break; - case SHORT: - writer.writeUnsignedShort(value.intValue()); - break; - case LONG: - writer.writeUnsignedInt(value.longValue()); - break; - case SBYTE: - writer.writeByte(value.byteValue()); - break; - case SSHORT: - writer.writeShort(value.shortValue()); - break; - case SLONG: - writer.writeInt(value.intValue()); - break; - case FLOAT: - writer.writeFloat(value.floatValue()); - break; - case DOUBLE: - writer.writeDouble(value.doubleValue()); - break; - default: - throw new TiffException("Unsupported raster field type: " - + fieldType); - } - - } - /** * Write filler 0 bytes * diff --git a/src/main/java/mil/nga/tiff/compression/DeflateCompression.java b/src/main/java/mil/nga/tiff/compression/DeflateCompression.java index 49d6373..f82b4f4 100644 --- a/src/main/java/mil/nga/tiff/compression/DeflateCompression.java +++ b/src/main/java/mil/nga/tiff/compression/DeflateCompression.java @@ -1,6 +1,11 @@ package mil.nga.tiff.compression; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.ByteOrder; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; import mil.nga.tiff.util.TiffException; @@ -17,7 +22,24 @@ public class DeflateCompression implements CompressionDecoder, */ @Override public byte[] decode(byte[] bytes, ByteOrder byteOrder) { - throw new TiffException("Deflate decoder is not yet implemented"); + try { + Inflater inflater = new Inflater(); + inflater.setInput(bytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytes.length); + byte[] buffer = new byte[1024]; + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + outputStream.write(buffer, 0, count); + } + outputStream.close(); + byte[] output = outputStream.toByteArray(); + + return output; + } catch (IOException e) { + throw new TiffException("Failed close decoded byte stream", e); + } catch (DataFormatException e) { + throw new TiffException("Data format error while decoding stream", e); + } } /** @@ -33,7 +55,22 @@ public boolean rowEncoding() { */ @Override public byte[] encode(byte[] bytes, ByteOrder byteOrder) { - throw new TiffException("Deflate encoder is not yet implemented"); - } + try { + Deflater deflater = new Deflater(); + deflater.setInput(bytes); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(bytes.length); + deflater.finish(); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); // returns the generated code... index + outputStream.write(buffer, 0, count); + } + outputStream.close(); + byte[] output = outputStream.toByteArray(); + return output; + } catch (IOException e) { + throw new TiffException("Failed close encoded stream", e); + } + } } diff --git a/src/main/java/mil/nga/tiff/io/ByteReader.java b/src/main/java/mil/nga/tiff/io/ByteReader.java index 2e8c507..0b76b72 100644 --- a/src/main/java/mil/nga/tiff/io/ByteReader.java +++ b/src/main/java/mil/nga/tiff/io/ByteReader.java @@ -35,7 +35,7 @@ public class ByteReader { * bytes */ public ByteReader(byte[] bytes) { - this(bytes, ByteOrder.BIG_ENDIAN); + this(bytes, ByteOrder.nativeOrder()); } /** diff --git a/src/main/java/mil/nga/tiff/io/ByteWriter.java b/src/main/java/mil/nga/tiff/io/ByteWriter.java index cfb3c6b..8a2a037 100644 --- a/src/main/java/mil/nga/tiff/io/ByteWriter.java +++ b/src/main/java/mil/nga/tiff/io/ByteWriter.java @@ -26,7 +26,7 @@ public class ByteWriter { * Constructor */ public ByteWriter() { - this(ByteOrder.BIG_ENDIAN); + this(ByteOrder.nativeOrder()); } /** diff --git a/src/main/java/mil/nga/tiff/util/TiffConstants.java b/src/main/java/mil/nga/tiff/util/TiffConstants.java index 69fd54d..c8b0879 100644 --- a/src/main/java/mil/nga/tiff/util/TiffConstants.java +++ b/src/main/java/mil/nga/tiff/util/TiffConstants.java @@ -56,6 +56,8 @@ public class TiffConstants { public static final int COMPRESSION_JPEG_OLD = 6; public static final int COMPRESSION_JPEG_NEW = 7; public static final int COMPRESSION_DEFLATE = 8; + @Deprecated + public static final int COMPRESSION_PKZIP_DEFLATE = 32946; // PKZIP-style Deflate encoding (Obsolete). public static final int COMPRESSION_PACKBITS = 32773; // Extra Samples constants diff --git a/src/test/java/mil/nga/tiff/TiffTestUtils.java b/src/test/java/mil/nga/tiff/TiffTestUtils.java index b53ee4b..cbbaf33 100644 --- a/src/test/java/mil/nga/tiff/TiffTestUtils.java +++ b/src/test/java/mil/nga/tiff/TiffTestUtils.java @@ -93,7 +93,7 @@ private static void compareFileDirectoryAndRastersMetadata( TestCase.assertEquals(fileDirectory.getImageWidth(), rasters.getWidth()); TestCase.assertEquals(fileDirectory.getImageHeight(), rasters.getHeight()); - TestCase.assertEquals(fileDirectory.getSamplesPerPixel().intValue(), + TestCase.assertEquals(fileDirectory.getSamplesPerPixel(), rasters.getSamplesPerPixel()); TestCase.assertEquals(fileDirectory.getBitsPerSample().size(), rasters .getBitsPerSample().size()); @@ -139,11 +139,17 @@ public static void compareRastersSampleValues(Rasters rasters1, rasters2.getSampleValues().length); for (int i = 0; i < rasters1.getSampleValues().length; i++) { - TestCase.assertEquals(rasters1.getSampleValues()[i].length, - rasters2.getSampleValues()[i].length); - for (int j = 0; j < rasters2.getSampleValues()[i].length; j++) { - compareNumbers(rasters1.getSampleValues()[i][j], - rasters2.getSampleValues()[i][j], exactType); + TestCase.assertEquals( + rasters1.getSampleValues()[i].capacity() + / rasters1.getFieldTypes()[i].getBytes(), + rasters2.getSampleValues()[i].capacity() + / rasters2.getFieldTypes()[i].getBytes()); + + for (int x = 0; x < rasters1.getWidth(); ++x) { + for (int y = 0; y < rasters1.getHeight(); ++y) { + compareNumbers(rasters1.getPixelSample(i, x, y), + rasters2.getPixelSample(i, x, y), exactType); + } } } } @@ -180,12 +186,17 @@ public static void compareRastersInterleaveValues(Rasters rasters1, TestCase.assertNotNull(rasters1.getInterleaveValues()); TestCase.assertNotNull(rasters2.getInterleaveValues()); - TestCase.assertEquals(rasters1.getInterleaveValues().length, - rasters2.getInterleaveValues().length); - - for (int i = 0; i < rasters1.getInterleaveValues().length; i++) { - compareNumbers(rasters1.getInterleaveValues()[i], - rasters2.getInterleaveValues()[i], exactType); + TestCase.assertEquals(rasters1.getInterleaveValues().capacity() + / rasters1.sizePixel(), rasters2.getInterleaveValues() + .capacity() / rasters2.sizePixel()); + + for (int i = 0; i < rasters1.getSamplesPerPixel(); i++) { + for (int x = 0; x < rasters1.getWidth(); ++x) { + for (int y = 0; y < rasters1.getHeight(); ++y) { + compareNumbers(rasters1.getPixelSample(i, x, y), + rasters2.getPixelSample(i, x, y), exactType); + } + } } }