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); + } + } +}