This is an automated email from the ASF dual-hosted git repository.
kinow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-imaging.git
The following commit(s) were added to refs/heads/master by this push:
new e4752bf [Imaging-268] Add list of TIFF files and example survey
application
new 4b3df95 Merge branch 'pr-103'
e4752bf is described below
commit e4752bf15deec14396bee5ff95c23a8503769941
Author: gwlucastrig <[email protected]>
AuthorDate: Tue Oct 13 19:26:19 2020 -0400
[Imaging-268] Add list of TIFF files and example survey application
---
src/changes/changes.xml | 3 +
src/test/data/images/tiff/12/README.txt | 3 +
.../tiff/12/TransparencyTestStripAssociated.tif | Bin 0 -> 466 bytes
.../tiff/12/TransparencyTestStripUnassociated.tif | Bin 0 -> 468 bytes
.../tiff/12/TransparencyTestTileAssociated.tif | Bin 0 -> 714 bytes
.../tiff/12/TransparencyTestTileUnassociated.tif | Bin 0 -> 720 bytes
src/test/data/images/tiff/README.txt | 67 +++
.../imaging/examples/tiff/SurveyTiffFile.java | 454 +++++++++++++++++++++
.../imaging/examples/tiff/SurveyTiffFolder.java | 237 +++++++++++
9 files changed, 764 insertions(+)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 58e627c..30fcf30 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -45,6 +45,9 @@ The <action> type attribute can be add,update,fix,remove.
</properties>
<body>
<release version="1.0-alpha3" date="2020-??-??" description="Third 1.0
alpha release">
+ <action issue="IMAGING-268" dev="kinow" type="add" due-to="Gary Lucas">
+ Add list of TIFF files and example survey application.
+ </action>
<action issue="IMAGING-265" dev="kinow" type="fix" due-to="Gary Lucas">
ArrayIndexOutOfBoundsException on reading simple GeoTIFF.
</action>
diff --git a/src/test/data/images/tiff/12/README.txt
b/src/test/data/images/tiff/12/README.txt
new file mode 100644
index 0000000..b5447b0
--- /dev/null
+++ b/src/test/data/images/tiff/12/README.txt
@@ -0,0 +1,3 @@
+The files in this folder include images with a RGB photometric interpretation
and
+an alpha transparency channel. Variations include both associated and
unassociated
+alpha as well as strip and tile layouts.
\ No newline at end of file
diff --git a/src/test/data/images/tiff/12/TransparencyTestStripAssociated.tif
b/src/test/data/images/tiff/12/TransparencyTestStripAssociated.tif
new file mode 100644
index 0000000..a33fe84
Binary files /dev/null and
b/src/test/data/images/tiff/12/TransparencyTestStripAssociated.tif differ
diff --git a/src/test/data/images/tiff/12/TransparencyTestStripUnassociated.tif
b/src/test/data/images/tiff/12/TransparencyTestStripUnassociated.tif
new file mode 100644
index 0000000..64224b9
Binary files /dev/null and
b/src/test/data/images/tiff/12/TransparencyTestStripUnassociated.tif differ
diff --git a/src/test/data/images/tiff/12/TransparencyTestTileAssociated.tif
b/src/test/data/images/tiff/12/TransparencyTestTileAssociated.tif
new file mode 100644
index 0000000..d4bb6f7
Binary files /dev/null and
b/src/test/data/images/tiff/12/TransparencyTestTileAssociated.tif differ
diff --git a/src/test/data/images/tiff/12/TransparencyTestTileUnassociated.tif
b/src/test/data/images/tiff/12/TransparencyTestTileUnassociated.tif
new file mode 100644
index 0000000..3cc566a
Binary files /dev/null and
b/src/test/data/images/tiff/12/TransparencyTestTileUnassociated.tif differ
diff --git a/src/test/data/images/tiff/README.txt
b/src/test/data/images/tiff/README.txt
new file mode 100644
index 0000000..17b947a
--- /dev/null
+++ b/src/test/data/images/tiff/README.txt
@@ -0,0 +1,67 @@
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+ Path
Size Layout Blk_sz P_conf Compress Predict Data_Fmt B/P B/S
Photo ICC_Pro
+1/Oregon Scientific DS6639 - DSC_0307 - small.tif
300x225 Strips 300x9 Chunky None None Unknown 24 8.8.8
RGB N
+1/PICT2833.TIF
2560x1920 Strips 2560x8 Chunky None None Unknown 24 8.8.8
RGB N
+1/Ron at 2001 CWA convention.tif
1112x1219 Strips 1112x9 Chunky None None Unknown 24 8.8.8
RGB N
+1/matthew2.tif
1583x2495 Strips 1583x2495 Chunky None None Unknown 8 8
BiLevel Y
+1/ron and andy.2.tif
1500x1125 Strips 1500x1125 Chunky None None Unknown 24 8.8.8
RGB N
+1/ron and andy.tif
1500x1125 Strips 1500x1125 Chunky None None Unknown 24 8.8.8
RGB N
+3/1pagefax.tif
1728x1146 Strips 1728x4969 Chunky CCITT_1D None Unknown 1 1
BiLev Inv N
+3/Oregon Scientific DS6639 - DSC_0307 - small CCITT T.4 1D fill.tiff
300x225 Strips 300x225 Chunky CCITT_3 None Unknown 1 1
BiLev Inv N
+3/Oregon Scientific DS6639 - DSC_0307 - small CCITT T.4 1D no fill.tiff
300x225 Strips 300x225 Chunky CCITT_3 None Unknown 1 1
BiLev Inv N
+3/Oregon Scientific DS6639 - DSC_0307 - small CCITT T.4 2D fill.tiff
300x225 Strips 300x225 Chunky CCITT_3 None Unknown 1 1
BiLev Inv N
+3/Oregon Scientific DS6639 - DSC_0307 - small CCITT T.4 2D no fill.tiff
300x225 Strips 300x225 Chunky CCITT_3 None Unknown 1 1
BiLev Inv N
+3/Oregon Scientific DS6639 - DSC_0307 - small CCITT T.6.tiff
300x225 Strips 300x225 Chunky CCITT_4 None Unknown 1 1
BiLev Inv N
+4/IndexColorPalette.tif
252x265 Strips 252x48 Chunky None None Unknown 8 8
Palette N
+4/IndexColorPaletteTiled.tif
128x128 Tiles 64x64 Chunky None None Unknown 8 8
Palette N
+5/Oregon Scientific DS6639 - DSC_0307 - small - LZW - strips.tif
300x225 Strips 300x9 Chunky LZW Diff Unknown 24 8.8.8
RGB N
+5/Oregon Scientific DS6639 - DSC_0307 - small - LZW - tiled.tif
300x225 Tiles 256x256 Chunky LZW Diff Unknown 24 8.8.8
RGB N
+7/Oregon Scientific DS6639 - DSC_0307 - small - CMYK.tiff
300x225 Strips 300x27 Chunky LZW None Unknown 32 8.8.8.8
CMYK N
+8/no-compression-tag.tiff
200x100 Strips 200x64 Chunky None None Unknown 32 8.8.8.8
RGB Pre-A N
+9/Sample64BitFloatingPointPix451x337.tiff
451x337 Strips 451x2 Chunky None None Float 64 64
BiLevel N
+9/USGS_13_n38w077_dir5.tiff
338x338 Tiles 128x128 Chunky LZW FP Diff Float 32 32
BiLevel N
+10/Imaging247.TIFF
2388x1700 Strips 2388x27 Chunky PACKBITS None Unknown 1 1
Palette N
+10/Imaging258.tiff
64x64 Strips 64x2 Chunky LZW None Uns Int 24 8.8.8
RGB N
+10/Imaging265.tiff
400x200 Strips 400x20 Planar None None Uns Int 24 8.8.8
RGB N
+11/BlueMarble_GeoTIFF_LZW_NoPredictor_Tiled.tif
360x180 Tiles 256x256 Chunky LZW None Unknown 24 8.8.8
RGB N
+12/TransparencyTestStripAssociated.tif
100x100 Strips 100x100 Chunky Deflate Diff Unknown 32 8.8.8.8
RGB Pre-A N
+12/TransparencyTestStripUnassociated.tif
100x100 Strips 100x100 Chunky Deflate Diff Unknown 32 8.8.8.8
RGBA N
+12/TransparencyTestTileAssociated.tif
100x100 Tiles 64x64 Chunky Deflate Diff Unknown 32 8.8.8.8
RGB Pre-A N
+12/TransparencyTestTileUnassociated.tif
100x100 Tiles 64x64 Chunky Deflate Diff Unknown 32 8.8.8.8
RGBA N
+
+Bad Files:
+2/bad-offsets-lengths.tiff
Attempt to read byte range starting from 536870911 of length 10 which is
outside the file's size of 156
+6/bad-interoperability.tiff Field
"InteropOffset" has wrong count 0
+
+Legend:
+ Size Size of image (width-by-height)
+ Layout Organization of the image file (strips versus tiles)
+ Blk_sz Size of internal image blocks (strips versus tiles)
+ P_conf Planar configuration, Chunky (interleaved samples) versus Planar
+ Compress Compression format
+ Predict Predictor
+ Data_Fmt Data format
+ B/P Bits per pixel
+ B/S Bits per sample
+ Photo Photometric Interpretation (pixel color type)
+ ICC_Pro Is ICC color profile supplied
+ RGBA 32-bit RGB with unassociated alpha
+ RGB Pre-A 32-bit RGB with associated (premultiplied) alpha
+
diff --git
a/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFile.java
b/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFile.java
new file mode 100644
index 0000000..d3a1669
--- /dev/null
+++ b/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFile.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.examples.tiff;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Formatter;
+import org.apache.commons.imaging.FormatCompliance;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
+import org.apache.commons.imaging.formats.tiff.TiffContents;
+import org.apache.commons.imaging.formats.tiff.TiffDirectory;
+import org.apache.commons.imaging.formats.tiff.TiffField;
+import org.apache.commons.imaging.formats.tiff.TiffReader;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_PKZIP;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED;
+import static
org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_1;
+import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
+import
org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
+import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
+
+/**
+ * Provides methods to collect data for a tiff file. This class is intended for
+ * use with
+ * SurveyTiffFolder, though it could be integrated into other applications.
+ */
+public class SurveyTiffFile {
+
+ public String surveyFile(File file, boolean csv) throws
ImageReadException, IOException {
+ String delimiter = " ";
+ if (csv) {
+ delimiter = ", ";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ Formatter fmt = new Formatter(sb);
+
+ // Establish a TiffReader. This is just a simple constructor that
+ // does not actually access the file. So the application cannot
+ // obtain the byteOrder, or other details, until the contents have
+ // been read. Then read the directories associated with the
+ // file by passing in the byte source and options.
+ ByteSourceFile byteSource = new ByteSourceFile(file);
+ TiffReader tiffReader = new TiffReader(true);
+ TiffContents contents = tiffReader.readDirectories(
+ byteSource,
+ false, // read image data, if present
+ FormatCompliance.getDefault());
+
+ if (contents.directories.isEmpty()) {
+ throw new ImageReadException("No Image File Directory (IFD)
found");
+ }
+ TiffDirectory directory = contents.directories.get(0);
+
+ // Get the metadata (Tags) and write them to standard output
+ boolean hasTiffImageData = directory.hasTiffImageData();
+ if (!hasTiffImageData) {
+ throw new ImageReadException("No image data in file");
+ }
+
+ final int width =
directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
+ final int height =
directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
+
+ int samplesPerPixel = 1;
+ final TiffField samplesPerPixelField = directory.findField(
+ TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
+ if (samplesPerPixelField != null) {
+ samplesPerPixel = samplesPerPixelField.getIntValue();
+ }
+ int[] bitsPerSample = {1};
+ int bitsPerPixel = samplesPerPixel;
+ final TiffField bitsPerSampleField = directory.findField(
+ TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
+ if (bitsPerSampleField != null) {
+ bitsPerSample = bitsPerSampleField.getIntArrayValue();
+ bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
+ }
+ if (samplesPerPixel != bitsPerSample.length) {
+ throw new ImageReadException("Tiff: samplesPerPixel ("
+ + samplesPerPixel + ")!=fBitsPerSample.length ("
+ + bitsPerSample.length + ")");
+ }
+
+ int rowsPerStrip = 0;
+ int tileWidth = 0;
+ int tileHeight = 0;
+
+ boolean imageDataInStrips = directory.imageDataInStrips();
+ if (imageDataInStrips) {
+ final TiffField rowsPerStripField
+ =
directory.findField(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP);
+ rowsPerStrip = Integer.MAX_VALUE;
+ if (null != rowsPerStripField) {
+ rowsPerStrip = rowsPerStripField.getIntValue();
+ } else {
+ final TiffField imageHeight =
directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
+ /*
+ * if rows per strip not present then rowsPerStrip is equal to
+ * imageLength or an infinity value;
+ */
+ if (imageHeight != null) {
+ rowsPerStrip = imageHeight.getIntValue();
+ }
+ }
+ } else {
+ final TiffField tileWidthField =
directory.findField(TiffTagConstants.TIFF_TAG_TILE_WIDTH);
+ if (null == tileWidthField) {
+ throw new ImageReadException("Can't find tile width field.");
+ }
+ tileWidth = tileWidthField.getIntValue();
+ final TiffField tileLengthField =
directory.findField(TiffTagConstants.TIFF_TAG_TILE_LENGTH);
+ if (null == tileLengthField) {
+ throw new ImageReadException("Can't find tile length field.");
+ }
+ tileHeight = tileLengthField.getIntValue();
+ }
+
+ String compressionString = getCompressionString(directory);
+ String predictorString = getPredictorString(directory);
+ String planarConfigurationString =
getPlanarConfigurationString(directory);
+ String bitsPerSampleString = getBitsPerSampleString(bitsPerSample);
+ String sampleFmtString = getSampleFormatString(directory);
+ String piString = getPhotometricInterpreterString(directory,
bitsPerSample);
+ String iccString = getIccProfileString(directory);
+
+ fmt.format("%s%4dx%-4d", delimiter, width, height);
+ if (imageDataInStrips) {
+ fmt.format("%sStrips%s%4dx%-4d", delimiter, delimiter, width,
rowsPerStrip);
+ } else {
+ fmt.format("%sTiles %s%4dx%-4d", delimiter, delimiter, tileWidth,
tileHeight);
+ }
+
+ fmt.format("%s%s", delimiter, planarConfigurationString);
+ fmt.format("%s%-8s", delimiter, compressionString);
+ fmt.format("%s%-7s", delimiter, predictorString);
+ fmt.format("%s%-8s", delimiter, sampleFmtString);
+ fmt.format("%s%3d", delimiter, bitsPerPixel);
+ fmt.format("%s%-7s", delimiter, bitsPerSampleString);
+ fmt.format("%s%-9s", delimiter, piString);
+ fmt.format("%s%-7s", delimiter, iccString);
+
+ if (csv) {
+ return trimForCsv(sb);
+ }
+ return sb.toString();
+ }
+
+ private String getCompressionString(TiffDirectory directory) throws
ImageReadException {
+ final short compressionFieldValue;
+ if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) !=
null) {
+ compressionFieldValue
+ =
directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
+ } else {
+ compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
+ }
+ final int compression = 0xffff & compressionFieldValue;
+ switch (compression) {
+ case TIFF_COMPRESSION_UNCOMPRESSED: // None;
+ return "None";
+ case TIFF_COMPRESSION_CCITT_1D: // CCITT Group 3 1-Dimensional
Modified
+ // Huffman run-length encoding.
+ return "CCITT_1D";
+ case TIFF_COMPRESSION_CCITT_GROUP_3:
+ return "CCITT_3";
+ case TIFF_COMPRESSION_CCITT_GROUP_4:
+ return "CCITT_4";
+ case TIFF_COMPRESSION_LZW:
+ return "LZW";
+ case TIFF_COMPRESSION_PACKBITS:
+ return "PACKBITS";
+ case TIFF_COMPRESSION_DEFLATE_ADOBE:
+ case TIFF_COMPRESSION_DEFLATE_PKZIP:
+ return "Deflate";
+ default:
+ return "None";
+ }
+ }
+
+ String getPredictorString(TiffDirectory directory) throws
ImageReadException {
+ int predictor = -1;
+
+ final TiffField predictorField = directory.findField(
+ TiffTagConstants.TIFF_TAG_PREDICTOR);
+ if (null != predictorField) {
+ predictor = predictorField.getIntValueOrArraySum();
+ }
+
+ switch (predictor) {
+ case TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING:
+ return "Diff";
+ case TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING:
+ return "FP Diff";
+ default:
+ return "None";
+
+ }
+ }
+
+ String getSampleFormatString(TiffDirectory directory) throws
ImageReadException {
+ short[] sSampleFmt = directory.getFieldValue(
+ TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
+ if (sSampleFmt == null || sSampleFmt.length == 0) {
+ return "Unknown";
+ }
+ String heterogeneous = "";
+ for (int i = 1; i < sSampleFmt.length; i++) {
+ if (sSampleFmt[i] != sSampleFmt[0]) {
+ heterogeneous = "*";
+ break;
+ }
+ }
+ int test = sSampleFmt[0];
+ switch (test) {
+ case TiffTagConstants.SAMPLE_FORMAT_VALUE_COMPLEX_INTEGER:
+ return "Comp I" + heterogeneous;
+ case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
+ case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT_1:
+ return "Float" + heterogeneous;
+ case
TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
+ return "Sgn Int" + heterogeneous;
+ case TiffTagConstants.SAMPLE_FORMAT_VALUE_UNSIGNED_INTEGER:
+ return "Uns Int" + heterogeneous;
+ default:
+ return "Unknown" + heterogeneous;
+ }
+ }
+
+ String getBitsPerSampleString(int[] bitsPerSample) {
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < bitsPerSample.length; i++) {
+ if (i > 0) {
+ s.append(".");
+ }
+ s.append(Integer.toString(bitsPerSample[i], 10));
+ }
+ return s.toString();
+ }
+
+ private String getPhotometricInterpreterString(TiffDirectory directory,
int[] bitsPerSample) throws ImageReadException {
+ final int photometricInterpretation = 0xffff & directory.getFieldValue(
+ TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
+
+ switch (photometricInterpretation) {
+ case 0:
+ return "BiLev Inv";
+ case 1:
+ return "BiLevel";
+ case 2:
+ String a = "RGB";
+ if (bitsPerSample.length == 4) {
+ Object o =
directory.getFieldValue(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
+ short extraSamples = 0;
+ if (o instanceof Short) {
+ extraSamples = ((Short) o);
+ }
+ if (extraSamples == 1) {
+ a = "RGB Pre-A";
+ } else {
+ a = "RGBA";
+ }
+ }
+
+ return a;
+ case 3:
+ return "Palette";
+
+ case 5: // CMYK
+ return "CMYK";
+ case 6:
+ return "YCbCr";
+ case 8:
+ return "CieLab";
+
+ case 32844:
+ case 32845:
+ return "LogLuv";
+ default:
+ return "Unknown";
+
+ }
+
+ }
+
+ String getIccProfileString(TiffDirectory directory) throws
ImageReadException {
+ byte[] b =
directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE,
+ false);
+ if (b == null || b.length == 0) {
+ return "N";
+ }
+ return "Y";
+ }
+
+ String getPlanarConfigurationString(TiffDirectory directory) throws
ImageReadException {
+
+ // Obtain the planar configuration
+ final TiffField pcField = directory.findField(
+ TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
+ final TiffPlanarConfiguration planarConfiguration
+ = pcField == null
+ ? TiffPlanarConfiguration.CHUNKY
+ :
TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
+
+ if (planarConfiguration == TiffPlanarConfiguration.CHUNKY) {
+ return "Chunky";
+ } else {
+ return "Planar";
+ }
+ }
+
+ /**
+ * Formats a header allowing space for the maximum length of
+ * the file paths in the list. If the comma-separated-value option
+ * is set, spaces will be suppressed and commas introduced as separators.
+ *
+ * @param maxPathLen the maximum length of a file path (used if csv
+ * option is not set)
+ * @param csv true if formatting is configured for comma-separated-value
+ * files.
+ * @return a valid string.
+ */
+ String formatHeader(int maxPathLen, boolean csv) {
+ // After some false starts, it turned out that the easiest
+ // way to do this is just to create a regular header and then
+ // search-and-replace spaces with comma as appropriate.
+ int n = maxPathLen;
+ if (n < 10) {
+ n = 10;
+ }
+ int k0 = (n - 4) / 2;
+ int k1 = (n - 4 - k0);
+
+ String header = String.format(
+ "%" + k0 + "sPath%" + k1 + "s%s", "", "",
+ " Size Layout Blk_sz P_conf Compress "
+ + "Predict Data_Fmt B/P B/S Photo ICC_Pro");
+ if (csv) {
+ return reformatHeaderForCsv(header);
+ } else {
+ return header;
+ }
+ }
+
+ /**
+ * Prints the legend information to the output stream
+ *
+ * @param ps a valid instance
+ */
+ void printLegend(PrintStream ps) {
+ ps.println("Legend:");
+ ps.println(" Size Size of image (width-by-height)");
+ ps.println(" Layout Organization of the image file (strips versus
tiles)");
+ ps.println(" Blk_sz Size of internal image blocks (strips versus
tiles)");
+ ps.println(" P_conf Planar configuration, Chunky (interleaved
samples) versus Planar ");
+ ps.println(" Compress Compression format");
+ ps.println(" Predict Predictor");
+ ps.println(" Data_Fmt Data format");
+ ps.println(" B/P Bits per pixel");
+ ps.println(" B/S Bits per sample");
+ ps.println(" Photo Photometric Interpretation (pixel color
type)");
+ ps.println(" ICC_Pro Is ICC color profile supplied");
+ ps.println("");
+ ps.println(" RGBA RGB with unassociated alpha (transparency)");
+ ps.println(" RGBA_Pre-A RGB with associated (premultiplied) alpha");
+ ps.println("");
+ }
+
+ /**
+ * Reformats the header inserting commas and removing spaces
+ *
+ * @param s a valid string
+ * @return a header suitable for a CSV file.
+ */
+ private String reformatHeaderForCsv(String s) {
+ StringBuilder sb = new StringBuilder(s.length());
+ boolean enableComma = false;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (Character.isWhitespace(c)) {
+ if (enableComma) {
+ enableComma = false;
+ sb.append(',');
+ }
+ } else {
+ enableComma = true;
+ if (Character.isUpperCase(c)) {
+ c = Character.toLowerCase(c);
+ }
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trims spaces from a range of characters intended for a CSV output
+ *
+ * @param source the standard source file
+ * @return the equivalent string with spaces removed.
+ */
+ private String trimForCsv(StringBuilder source) {
+ int n = source.length();
+ StringBuilder sb = new StringBuilder(n);
+ boolean spaceEnabled = false;
+ boolean spacePending = false;
+ for (int i = 0; i < n; i++) {
+ char c = source.charAt(i);
+ if (Character.isWhitespace(c)) {
+ if (spaceEnabled) {
+ spacePending = true;
+ spaceEnabled = false;
+ }
+ } else {
+
+ if (Character.isLetter(c) || Character.isDigit(c)) {
+ if (spacePending) {
+ sb.append(' ');
+ spacePending = false;
+ }
+ spaceEnabled = true;
+ } else {
+ spacePending = false;
+ spaceEnabled = false;
+ }
+ sb.append(c);
+ }
+ }
+ n = sb.length();
+ if (n > 0 && sb.charAt(n - 1) == ' ') {
+ sb.setLength(n - 1);
+ }
+ return sb.toString();
+ }
+}
diff --git
a/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFolder.java
b/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFolder.java
new file mode 100644
index 0000000..2389536
--- /dev/null
+++
b/src/test/java/org/apache/commons/imaging/examples/tiff/SurveyTiffFolder.java
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.imaging.examples.tiff;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import org.apache.commons.imaging.ImageReadException;
+
+/**
+ * Recursively search the specified path and list TIFF files and metadata.
+ * <p>
+ * Command-line Arguments:</p>
+ * <ol>
+ * <li>Top-level directory (mandatory)</li>
+ * <li>Output file for results (optional)</li>
+ * </ol>
+ * If the optional output file has the extension ".csv", the output
+ * will be formatted as a comma-separated-value file suitable
+ * for inspection in Excel.
+ */
+public class SurveyTiffFolder {
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Missing directory path");
+ System.exit(-1);
+ }
+ File topLevelDir = new File(args[0]);
+ if (!topLevelDir.isDirectory() || !topLevelDir.canRead()) {
+ System.err.println("Path specification is not an accessible
directory " + args[0]);
+ System.exit(-1);
+ }
+
+ // recursively survey file paths
+ String[] scratch = new String[256];
+ List<String[]> pathList = new ArrayList<>();
+ collectPaths(topLevelDir, pathList, scratch, 0);
+ pathList.sort(new PathComparator());
+
+ // find maximum lengths of each entry
+ int[] maxLen = findMaxLengths(pathList);
+
+ // If args.length is 1, write report to System.out,
+ // otherwise, write to a file.
+ if (args.length == 1) {
+ surveyFiles(topLevelDir, pathList, maxLen, false, System.out);
+ } else {
+
+ boolean csv = false;
+
+ int i = args[1].lastIndexOf('.');
+ if (i > 0) {
+ String ext = args[1].substring(i);
+ if (".csv".equalsIgnoreCase(ext)) {
+ csv = true;
+ }
+ }
+ File reportFile = new File(args[1]);
+ try (FileOutputStream fos = new FileOutputStream(reportFile);
+ BufferedOutputStream bos = new BufferedOutputStream(fos);
+ PrintStream ps = new PrintStream(bos, true, "UTF-8")) {
+ surveyFiles(topLevelDir, pathList, maxLen, csv, ps);
+ } catch (IOException ioex) {
+ System.err.println("IOException writing report to " + args[1]);
+ System.err.println("" + ioex.getMessage());
+ }
+ }
+ }
+
+ private static int collectPaths(
+ File parent,
+ List<String[]> pathList,
+ String[] scratch,
+ int depth) {
+ if (depth == scratch.length) {
+ // directory hierarchy is too deep
+ return 0;
+ }
+
+ File[] files = parent.listFiles();
+ for (File f : files) {
+ if (!f.isHidden()) {
+ String name = f.getName();
+ scratch[depth] = name;
+ if (f.isDirectory()) {
+ collectPaths(f, pathList, scratch, depth + 1);
+ } else {
+ int i = name.lastIndexOf('.');
+ if (i > 0) {
+ String ext = name.substring(i).toLowerCase();
+ if (".tif".equals(ext) || ".tiff".equals(ext)) {
+ String[] temp = new String[depth + 1];
+ System.arraycopy(scratch, 0, temp, 0, depth + 1);
+ pathList.add(temp);
+ }
+ }
+ }
+ }
+ }
+ return depth;
+ }
+
+ private static class PathComparator implements Comparator<String[]> {
+
+ @Override
+ public int compare(String[] a, String[] b) {
+ for (int i = 0; i < a.length && i < b.length; i++) {
+ int test;
+ if (isNumeric(a[i]) && isNumeric(b[i])) {
+ int iA = Integer.parseInt(a[i]);
+ int iB = Integer.parseInt(b[i]);
+ test = iA - iB;
+ } else {
+ test = a[i].compareTo(b[i]);
+ }
+ if (test != 0) {
+ return test;
+ }
+ }
+ // in practice, the program should never reach this position.
+ // at this point, all entries in both arrays are equal,
+ // so order the entries so that the shortest array goes first
+ if (a.length < b.length) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ private boolean isNumeric(String a) {
+ for (int i = 0; i < a.length(); i++) {
+ if (!Character.isDigit(a.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ }
+
+ private static int[] findMaxLengths(List<String[]> pathList) {
+ int[] m = new int[1];
+ for (String[] s : pathList) {
+ if (s.length > m.length) {
+ m = Arrays.copyOf(m, s.length);
+ }
+ for (int i = 0; i < s.length; i++) {
+ if (s[i].length() > m[i]) {
+ m[i] = s[i].length();
+ }
+ }
+ }
+ return m;
+ }
+
+ private static void surveyFiles(File topDir, List<String[]> pathList,
int[] maxLen, boolean csv, PrintStream ps) {
+ SurveyTiffFile surveyor = new SurveyTiffFile();
+ int n = maxLen.length - 1;
+ for (int i = 0; i < maxLen.length; i++) {
+ n += maxLen[i];
+ }
+ if (n < 10) {
+ n = 10;
+ }
+
+ String header = surveyor.formatHeader(n, csv);
+ ps.println(header);
+
+ List<String> badFiles = new ArrayList<>();
+ for (String[] path : pathList) {
+ StringBuilder sBuilder = new StringBuilder();
+ File file = topDir;
+ for (String s : path) {
+ file = new File(file, s);
+ }
+ for (int i = 0; i < path.length; i++) {
+ if (i > 0) {
+ sBuilder.append('/');
+ }
+ sBuilder.append(path[i]);
+ }
+ if (!csv) {
+ for (int i = sBuilder.length(); i < n; i++) {
+ sBuilder.append(' ');
+ }
+ }
+
+ String result;
+ try {
+ result = surveyor.surveyFile(file, csv);
+ } catch (IOException | ImageReadException ex) {
+ sBuilder.append(ex.getMessage());
+ badFiles.add(sBuilder.toString());
+ continue; // result = ex.getMessage();
+ }
+ sBuilder.append(result);
+ ps.println(sBuilder.toString());
+ }
+ if (!csv && !badFiles.isEmpty()) {
+ ps.println();
+ ps.println("Bad Files:");
+ for (String s : badFiles) {
+ ps.println(s);
+ }
+ }
+
+ if (!csv) {
+ ps.println();
+ surveyor.printLegend(ps);
+ }
+ }
+}