1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.examples.tiff;
18
19 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
20 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
21 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
22 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE;
23 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_PKZIP;
24 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
25 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
26 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED;
27 import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_1;
28
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.PrintStream;
32 import java.util.Formatter;
33
34 import org.apache.commons.imaging.FormatCompliance;
35 import org.apache.commons.imaging.ImagingException;
36 import org.apache.commons.imaging.bytesource.ByteSource;
37 import org.apache.commons.imaging.formats.tiff.TiffContents;
38 import org.apache.commons.imaging.formats.tiff.TiffDirectory;
39 import org.apache.commons.imaging.formats.tiff.TiffField;
40 import org.apache.commons.imaging.formats.tiff.TiffReader;
41 import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
42 import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
43 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
44
45
46
47
48
49 public class SurveyTiffFile {
50
51
52
53
54
55
56
57
58
59 String formatHeader(final int maxPathLen, final boolean csv) {
60
61
62
63 int n = maxPathLen;
64 if (n < 10) {
65 n = 10;
66 }
67 final int k0 = (n - 4) / 2;
68 final int k1 = n - 4 - k0;
69
70 final String header = String.format("%" + k0 + "sPath%" + k1 + "s%s", "", "",
71 " Size Layout Blk_sz P_conf Compress " + "Predict Data_Fmt B/P B/S Photo ICC_Pro");
72 if (csv) {
73 return reformatHeaderForCsv(header);
74 }
75 return header;
76 }
77
78 String getBitsPerSampleString(final int[] bitsPerSample) {
79 final StringBuilder s = new StringBuilder();
80 for (int i = 0; i < bitsPerSample.length; i++) {
81 if (i > 0) {
82 s.append(".");
83 }
84 s.append(Integer.toString(bitsPerSample[i], 10));
85 }
86 return s.toString();
87 }
88
89 private String getCompressionString(final TiffDirectory directory) throws ImagingException {
90 final short compressionFieldValue;
91 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
92 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
93 } else {
94 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
95 }
96 final int compression = 0xffff & compressionFieldValue;
97 switch (compression) {
98 case TIFF_COMPRESSION_UNCOMPRESSED:
99 return "None";
100 case TIFF_COMPRESSION_CCITT_1D:
101
102 return "CCITT_1D";
103 case TIFF_COMPRESSION_CCITT_GROUP_3:
104 return "CCITT_3";
105 case TIFF_COMPRESSION_CCITT_GROUP_4:
106 return "CCITT_4";
107 case TIFF_COMPRESSION_LZW:
108 return "LZW";
109 case TIFF_COMPRESSION_PACKBITS:
110 return "PACKBITS";
111 case TIFF_COMPRESSION_DEFLATE_ADOBE:
112 case TIFF_COMPRESSION_DEFLATE_PKZIP:
113 return "Deflate";
114 default:
115 return "None";
116 }
117 }
118
119 String getIccProfileString(final TiffDirectory directory) throws ImagingException {
120 final byte[] b = directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE, false);
121 if (b == null || b.length == 0) {
122 return "N";
123 }
124 return "Y";
125 }
126
127 private String getPhotometricInterpreterString(final TiffDirectory directory, final int[] bitsPerSample) throws ImagingException {
128 final int photometricInterpretation = 0xffff & directory.getFieldValue(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
129
130 switch (photometricInterpretation) {
131 case 0:
132 return "BiLev Inv";
133 case 1:
134 return "BiLevel";
135 case 2:
136 String a = "RGB";
137 if (bitsPerSample.length == 4) {
138 final Object o = directory.getFieldValue(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
139 short extraSamples = 0;
140 if (o instanceof Short) {
141 extraSamples = (Short) o;
142 }
143 if (extraSamples == 1) {
144 a = "RGB Pre-A";
145 } else {
146 a = "RGBA";
147 }
148 }
149
150 return a;
151 case 3:
152 return "Palette";
153
154 case 5:
155 return "CMYK";
156 case 6:
157 return "YCbCr";
158 case 8:
159 return "CieLab";
160
161 case 32844:
162 case 32845:
163 return "LogLuv";
164 default:
165 return "Unknown";
166
167 }
168
169 }
170
171 String getPlanarConfigurationString(final TiffDirectory directory) throws ImagingException {
172
173
174 final TiffField pcField = directory.findField(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
175 final TiffPlanarConfiguration planarConfiguration = pcField == null ? TiffPlanarConfiguration.CHUNKY
176 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
177
178 if (planarConfiguration == TiffPlanarConfiguration.CHUNKY) {
179 return "Chunky";
180 }
181 return "Planar";
182 }
183
184 String getPredictorString(final TiffDirectory directory) throws ImagingException {
185 int predictor = -1;
186
187 final TiffField predictorField = directory.findField(TiffTagConstants.TIFF_TAG_PREDICTOR);
188 if (null != predictorField) {
189 predictor = predictorField.getIntValueOrArraySum();
190 }
191
192 switch (predictor) {
193 case TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING:
194 return "Diff";
195 case TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING:
196 return "FP Diff";
197 default:
198 return "None";
199
200 }
201 }
202
203 String getSampleFormatString(final TiffDirectory directory) throws ImagingException {
204 final short[] sSampleFmt = directory.getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false);
205 if (sSampleFmt == null || sSampleFmt.length == 0) {
206 return "Unknown";
207 }
208 String heterogeneous = "";
209 for (int i = 1; i < sSampleFmt.length; i++) {
210 if (sSampleFmt[i] != sSampleFmt[0]) {
211 heterogeneous = "*";
212 break;
213 }
214 }
215 final int test = sSampleFmt[0];
216 switch (test) {
217 case TiffTagConstants.SAMPLE_FORMAT_VALUE_COMPLEX_INTEGER:
218 return "Comp I" + heterogeneous;
219 case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT:
220 return "Float" + heterogeneous;
221 case TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_COMPLEX_FLOAT:
222 return "Comp F" + heterogeneous;
223 case TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER:
224 return "Sgn Int" + heterogeneous;
225 case TiffTagConstants.SAMPLE_FORMAT_VALUE_UNSIGNED_INTEGER:
226 return "Uns Int" + heterogeneous;
227 default:
228 return "Unknown" + heterogeneous;
229 }
230 }
231
232
233
234
235
236
237 void printLegend(final PrintStream ps) {
238 ps.println("Legend:");
239 ps.println(" Size Size of image (width-by-height)");
240 ps.println(" Layout Organization of the image file (strips versus tiles)");
241 ps.println(" Blk_sz Size of internal image blocks (strips versus tiles)");
242 ps.println(" P_conf Planar configuration, Chunky (interleaved samples) versus Planar ");
243 ps.println(" Compress Compression format");
244 ps.println(" Predict Predictor");
245 ps.println(" Data_Fmt Data format");
246 ps.println(" B/P Bits per pixel");
247 ps.println(" B/S Bits per sample");
248 ps.println(" Photo Photometric Interpretation (pixel color type)");
249 ps.println(" ICC_Pro Is ICC color profile supplied");
250 ps.println("");
251 ps.println(" RGBA RGB with unassociated alpha (transparency)");
252 ps.println(" RGBA_Pre-A RGB with associated (premultiplied) alpha");
253 ps.println("");
254 }
255
256
257
258
259
260
261
262 private String reformatHeaderForCsv(final String s) {
263 final StringBuilder sb = new StringBuilder(s.length());
264 boolean enableComma = false;
265 for (int i = 0; i < s.length(); i++) {
266 char c = s.charAt(i);
267 if (Character.isWhitespace(c)) {
268 if (enableComma) {
269 enableComma = false;
270 sb.append(',');
271 }
272 } else {
273 enableComma = true;
274 if (Character.isUpperCase(c)) {
275 c = Character.toLowerCase(c);
276 }
277 sb.append(c);
278 }
279 }
280 return sb.toString();
281 }
282
283 public String surveyFile(final File file, final boolean csv) throws ImagingException, IOException {
284 String delimiter = " ";
285 if (csv) {
286 delimiter = ", ";
287 }
288
289 final StringBuilder sb = new StringBuilder();
290 try (final Formatter fmt = new Formatter(sb)) {
291
292
293
294
295
296
297 final ByteSource byteSource = ByteSource.file(file);
298 final TiffReader tiffReader = new TiffReader(true);
299 final TiffContents contents = tiffReader.readDirectories(byteSource, false,
300 FormatCompliance.getDefault());
301
302 if (contents.directories.isEmpty()) {
303 throw new ImagingException("No Image File Directory (IFD) found");
304 }
305 final TiffDirectory directory = contents.directories.get(0);
306
307
308 final boolean hasTiffImageData = directory.hasTiffImageData();
309 if (!hasTiffImageData) {
310 throw new ImagingException("No image data in file");
311 }
312
313 final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
314 final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
315
316 int samplesPerPixel = 1;
317 final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
318 if (samplesPerPixelField != null) {
319 samplesPerPixel = samplesPerPixelField.getIntValue();
320 }
321 int[] bitsPerSample = { 1 };
322 int bitsPerPixel = samplesPerPixel;
323 final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
324 if (bitsPerSampleField != null) {
325 bitsPerSample = bitsPerSampleField.getIntArrayValue();
326 bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
327 }
328 if (samplesPerPixel != bitsPerSample.length) {
329 throw new ImagingException("Tiff: samplesPerPixel (" + samplesPerPixel + ")!=fBitsPerSample.length (" + bitsPerSample.length + ")");
330 }
331
332 int rowsPerStrip = 0;
333 int tileWidth = 0;
334 int tileHeight = 0;
335
336 final boolean imageDataInStrips = directory.imageDataInStrips();
337 if (imageDataInStrips) {
338 final TiffField rowsPerStripField = directory.findField(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP);
339 rowsPerStrip = Integer.MAX_VALUE;
340 if (null != rowsPerStripField) {
341 rowsPerStrip = rowsPerStripField.getIntValue();
342 } else {
343 final TiffField imageHeight = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
344
345
346
347 if (imageHeight != null) {
348 rowsPerStrip = imageHeight.getIntValue();
349 }
350 }
351 } else {
352 final TiffField tileWidthField = directory.findField(TiffTagConstants.TIFF_TAG_TILE_WIDTH);
353 if (null == tileWidthField) {
354 throw new ImagingException("Can't find tile width field.");
355 }
356 tileWidth = tileWidthField.getIntValue();
357 final TiffField tileLengthField = directory.findField(TiffTagConstants.TIFF_TAG_TILE_LENGTH);
358 if (null == tileLengthField) {
359 throw new ImagingException("Can't find tile length field.");
360 }
361 tileHeight = tileLengthField.getIntValue();
362 }
363
364 final String compressionString = getCompressionString(directory);
365 final String predictorString = getPredictorString(directory);
366 final String planarConfigurationString = getPlanarConfigurationString(directory);
367 final String bitsPerSampleString = getBitsPerSampleString(bitsPerSample);
368 final String sampleFmtString = getSampleFormatString(directory);
369 final String piString = getPhotometricInterpreterString(directory, bitsPerSample);
370 final String iccString = getIccProfileString(directory);
371
372 fmt.format("%s%4dx%-4d", delimiter, width, height);
373 if (imageDataInStrips) {
374 fmt.format("%sStrips%s%4dx%-4d", delimiter, delimiter, width, rowsPerStrip);
375 } else {
376 fmt.format("%sTiles %s%4dx%-4d", delimiter, delimiter, tileWidth, tileHeight);
377 }
378
379 fmt.format("%s%s", delimiter, planarConfigurationString);
380 fmt.format("%s%-8s", delimiter, compressionString);
381 fmt.format("%s%-7s", delimiter, predictorString);
382 fmt.format("%s%-8s", delimiter, sampleFmtString);
383 fmt.format("%s%3d", delimiter, bitsPerPixel);
384 fmt.format("%s%-7s", delimiter, bitsPerSampleString);
385 fmt.format("%s%-9s", delimiter, piString);
386 fmt.format("%s%-7s", delimiter, iccString);
387
388 if (csv) {
389 return trimForCsv(sb);
390 }
391 return sb.toString();
392 }
393 }
394
395
396
397
398
399
400
401 private String trimForCsv(final StringBuilder source) {
402 int n = source.length();
403 final StringBuilder sb = new StringBuilder(n);
404 boolean spaceEnabled = false;
405 boolean spacePending = false;
406 for (int i = 0; i < n; i++) {
407 final char c = source.charAt(i);
408 if (Character.isWhitespace(c)) {
409 if (spaceEnabled) {
410 spacePending = true;
411 spaceEnabled = false;
412 }
413 } else {
414
415 if (Character.isLetter(c) || Character.isDigit(c)) {
416 if (spacePending) {
417 sb.append(' ');
418 spacePending = false;
419 }
420 spaceEnabled = true;
421 } else {
422 spacePending = false;
423 spaceEnabled = false;
424 }
425 sb.append(c);
426 }
427 }
428 n = sb.length();
429 if (n > 0 && sb.charAt(n - 1) == ' ') {
430 sb.setLength(n - 1);
431 }
432 return sb.toString();
433 }
434 }