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 <contact.tinf...@gmail.com>
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);
+        }
+    }
+}

Reply via email to