View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.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_JPEG;
25  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG_OBSOLETE;
26  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
27  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
28  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_1;
29  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_2;
30  
31  import java.awt.Dimension;
32  import java.awt.Rectangle;
33  import java.awt.image.BufferedImage;
34  import java.io.IOException;
35  import java.io.OutputStream;
36  import java.io.PrintWriter;
37  import java.nio.ByteOrder;
38  import java.nio.charset.StandardCharsets;
39  import java.util.ArrayList;
40  import java.util.List;
41  
42  import org.apache.commons.imaging.AbstractImageParser;
43  import org.apache.commons.imaging.FormatCompliance;
44  import org.apache.commons.imaging.ImageFormat;
45  import org.apache.commons.imaging.ImageFormats;
46  import org.apache.commons.imaging.ImageInfo;
47  import org.apache.commons.imaging.ImagingException;
48  import org.apache.commons.imaging.bytesource.ByteSource;
49  import org.apache.commons.imaging.common.Allocator;
50  import org.apache.commons.imaging.common.ImageBuilder;
51  import org.apache.commons.imaging.common.ImageMetadata;
52  import org.apache.commons.imaging.common.XmpEmbeddable;
53  import org.apache.commons.imaging.common.XmpImagingParameters;
54  import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement;
55  import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
56  import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
57  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
58  import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader;
59  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
60  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel;
61  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab;
62  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk;
63  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv;
64  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette;
65  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
66  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr;
67  import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
68  
69  /**
70   * Implements methods for reading and writing TIFF files. Instances of this class are invoked from the general Imaging class. Applications that require the use
71   * of TIFF-specific features may instantiate and access this class directly.
72   */
73  public class TiffImageParser extends AbstractImageParser<TiffImagingParameters> implements XmpEmbeddable<TiffImagingParameters> {
74  
75      private static final String DEFAULT_EXTENSION = ImageFormats.TIFF.getDefaultExtension();
76      private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.TIFF.getExtensions();
77  
78      private Rectangle checkForSubImage(final TiffImagingParameters params) {
79          // the params class enforces a correct specification for the
80          // sub-image, but does not have knowledge of the actual
81          // dimensions of the image that is being read. This method
82          // returns the sub-image specification, if any, and leaves
83          // further tests to the calling module.
84          if (params != null && params.isSubImageSet()) {
85              final int ix0 = params.getSubImageX();
86              final int iy0 = params.getSubImageY();
87              final int iwidth = params.getSubImageWidth();
88              final int iheight = params.getSubImageHeight();
89              return new Rectangle(ix0, iy0, iwidth, iheight);
90          }
91          return null;
92      }
93  
94      public List<byte[]> collectRawImageData(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
95          final FormatCompliance formatCompliance = FormatCompliance.getDefault();
96          final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(byteSource, true, formatCompliance);
97  
98          final List<byte[]> result = new ArrayList<>();
99          for (int i = 0; i < contents.directories.size(); i++) {
100             final TiffDirectory directory = contents.directories.get(i);
101             final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements();
102             for (final ImageDataElement element : dataElements) {
103                 final byte[] bytes = byteSource.getByteArray(element.offset, element.length);
104                 result.add(bytes);
105             }
106         }
107         return result;
108     }
109 
110     @Override
111     public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
112         try {
113             pw.println("tiff.dumpImageFile");
114 
115             {
116                 final ImageInfo imageData = getImageInfo(byteSource);
117                 if (imageData == null) {
118                     return false;
119                 }
120 
121                 imageData.toString(pw, "");
122             }
123 
124             pw.println("");
125 
126             // try
127             {
128                 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
129                 final TiffImagingParameters params = new TiffImagingParameters();
130                 final TiffContents contents = new TiffReader(true).readContents(byteSource, params, formatCompliance);
131 
132                 final List<TiffDirectory> directories = contents.directories;
133                 if (directories == null) {
134                     return false;
135                 }
136 
137                 for (int d = 0; d < directories.size(); d++) {
138                     final TiffDirectory directory = directories.get(d);
139 
140                     // Debug.debug("directory offset", directory.offset);
141 
142                     for (final TiffField field : directory) {
143                         field.dump(pw, Integer.toString(d));
144                     }
145                 }
146 
147                 pw.println("");
148             }
149             // catch (Exception e)
150             // {
151             // Debug.debug(e);
152             // pw.println("");
153             // return false;
154             // }
155 
156             return true;
157         } finally {
158             pw.println("");
159         }
160     }
161 
162     @Override
163     protected String[] getAcceptedExtensions() {
164         return ACCEPTED_EXTENSIONS;
165     }
166 
167     @Override
168     protected ImageFormat[] getAcceptedTypes() {
169         return new ImageFormat[] { ImageFormats.TIFF, //
170         };
171     }
172 
173     @Override
174     public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) throws ImagingException, IOException {
175         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
176         final TiffReader tiffReader = new TiffReader(true);
177         final TiffContents contents = tiffReader.readDirectories(byteSource, true, formatCompliance);
178         final List<BufferedImage> results = new ArrayList<>();
179         for (int i = 0; i < contents.directories.size(); i++) {
180             final TiffDirectory directory = contents.directories.get(i);
181             final BufferedImage result = directory.getTiffImage(tiffReader.getByteOrder(), null);
182             if (result != null) {
183                 results.add(result);
184             }
185         }
186         return results;
187     }
188 
189     /**
190      * <p>
191      * Gets a buffered image specified by the byte source. The TiffImageParser class features support for a number of options that are unique to the TIFF
192      * format. These options can be specified by supplying the appropriate parameters using the keys from the TiffConstants class and the params argument for
193      * this method.
194      * </p>
195      *
196      * <p>
197      * <strong>Loading Partial Images</strong>
198      * </p>
199      *
200      * <p>
201      * The TIFF parser includes support for loading partial images without committing significantly more memory resources than are necessary to store the image.
202      * This feature is useful for conserving memory in applications that require a relatively small sub image from a very large TIFF file. The specifications
203      * for partial images are as follows:
204      * </p>
205      *
206      * <pre>
207      * TiffImagingParameters params = new TiffImagingParameters();
208      * params.setSubImageX(x);
209      * params.setSubImageY(y);
210      * params.setSubImageWidth(width);
211      * params.setSubImageHeight(height);
212      * </pre>
213      *
214      * <p>
215      * Note that the arguments x, y, width, and height must specify a valid rectangular region that is fully contained within the source TIFF image.
216      * </p>
217      *
218      * @param byteSource A valid instance of ByteSource
219      * @param params     Optional instructions for special-handling or interpretation of the input data (null objects are permitted and must be supported by
220      *                   implementations).
221      * @return A valid instance of BufferedImage.
222      * @throws ImagingException In the event that the specified content does not conform to the format of the specific parser implementation.
223      * @throws IOException      In the event of unsuccessful read or access operation.
224      */
225     @Override
226     public BufferedImage getBufferedImage(final ByteSource byteSource, TiffImagingParameters params) throws ImagingException, IOException {
227         if (params == null) {
228             params = new TiffImagingParameters();
229         }
230         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
231         final TiffReader reader = new TiffReader(params.isStrict());
232         final TiffContents contents = reader.readFirstDirectory(byteSource, true, formatCompliance);
233         final ByteOrder byteOrder = reader.getByteOrder();
234         final TiffDirectory directory = contents.directories.get(0);
235         final BufferedImage result = directory.getTiffImage(byteOrder, params);
236         if (null == result) {
237             throw new ImagingException("TIFF does not contain an image.");
238         }
239         return result;
240     }
241 
242     protected BufferedImage getBufferedImage(final TiffDirectory directory, final ByteOrder byteOrder, final TiffImagingParameters params)
243             throws ImagingException, IOException {
244         final short compressionFieldValue;
245         if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
246             compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
247         } else {
248             compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
249         }
250         final int compression = 0xffff & compressionFieldValue;
251         final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
252         final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
253 
254         final Rectangle subImage = checkForSubImage(params);
255         if (subImage != null) {
256             // Check for valid subimage specification. The following checks
257             // are consistent with BufferedImage.getSubimage()
258             if (subImage.width <= 0) {
259                 throw new ImagingException("Negative or zero subimage width.");
260             }
261             if (subImage.height <= 0) {
262                 throw new ImagingException("Negative or zero subimage height.");
263             }
264             if (subImage.x < 0 || subImage.x >= width) {
265                 throw new ImagingException("Subimage x is outside raster.");
266             }
267             if (subImage.x + subImage.width > width) {
268                 throw new ImagingException("Subimage (x+width) is outside raster.");
269             }
270             if (subImage.y < 0 || subImage.y >= height) {
271                 throw new ImagingException("Subimage y is outside raster.");
272             }
273             if (subImage.y + subImage.height > height) {
274                 throw new ImagingException("Subimage (y+height) is outside raster.");
275             }
276         }
277 
278         int samplesPerPixel = 1;
279         final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
280         if (samplesPerPixelField != null) {
281             samplesPerPixel = samplesPerPixelField.getIntValue();
282         }
283         int[] bitsPerSample = { 1 };
284         int bitsPerPixel = samplesPerPixel;
285         final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
286         if (bitsPerSampleField != null) {
287             bitsPerSample = bitsPerSampleField.getIntArrayValue();
288             bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
289         }
290 
291         // int bitsPerPixel = getTagAsValueOrArraySum(entries,
292         // TIFF_TAG_BITS_PER_SAMPLE);
293 
294         int predictor = -1;
295         {
296             // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER);
297             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS);
298             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS);
299             // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION);
300             // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION);
301             final TiffField predictorField = directory.findField(TiffTagConstants.TIFF_TAG_PREDICTOR);
302             if (null != predictorField) {
303                 predictor = predictorField.getIntValueOrArraySum();
304             }
305         }
306 
307         if (samplesPerPixel != bitsPerSample.length) {
308             throw new ImagingException("Tiff: samplesPerPixel (" + samplesPerPixel + ")!=fBitsPerSample.length (" + bitsPerSample.length + ")");
309         }
310 
311         final int photometricInterpretation = 0xffff & directory.getFieldValue(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
312 
313         boolean hasAlpha = false;
314         boolean isAlphaPremultiplied = false;
315         if (photometricInterpretation == TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB && samplesPerPixel == 4) {
316             final TiffField extraSamplesField = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
317             if (extraSamplesField == null) {
318                 // this state is not defined in the TIFF specification
319                 // and so this code will interpret it as meaning that the
320                 // proper handling would be ARGB.
321                 hasAlpha = true;
322                 isAlphaPremultiplied = false;
323             } else {
324                 final int extraSamplesValue = extraSamplesField.getIntValue();
325                 switch (extraSamplesValue) {
326                 case TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA:
327                     hasAlpha = true;
328                     isAlphaPremultiplied = false;
329                     break;
330                 case TiffTagConstants.EXTRA_SAMPLE_ASSOCIATED_ALPHA:
331                     hasAlpha = true;
332                     isAlphaPremultiplied = true;
333                     break;
334                 case 0:
335                 default:
336                     hasAlpha = false;
337                     isAlphaPremultiplied = false;
338                     break;
339                 }
340             }
341         }
342 
343         PhotometricInterpreter photometricInterpreter = params == null ? null : params.getCustomPhotometricInterpreter();
344         if (photometricInterpreter == null) {
345             photometricInterpreter = getPhotometricInterpreter(directory, photometricInterpretation, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
346                     width, height);
347         }
348 
349         // Obtain the planar configuration
350         final TiffField pcField = directory.findField(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
351         final TiffPlanarConfiguration planarConfiguration = pcField == null ? TiffPlanarConfiguration.CHUNKY
352                 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
353 
354         if (planarConfiguration == TiffPlanarConfiguration.PLANAR) {
355             // currently, we support the non-interleaved (non-chunky)
356             // option only in the case of a 24-bit RBG photometric interpreter
357             // and for strips (not for tiles).
358             if (photometricInterpretation != TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB || bitsPerPixel != 24) {
359                 throw new ImagingException("For planar configuration 2, only 24 bit RGB is currently supported");
360             }
361             if (null == directory.findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS)) {
362                 throw new ImagingException("For planar configuration 2, only strips-organization is supported");
363             }
364         }
365 
366         final AbstractTiffImageData imageData = directory.getTiffImageData();
367 
368         final ImageDataReader dataReader = imageData.getDataReader(directory, photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
369                 width, height, compression, planarConfiguration, byteOrder);
370 
371         final ImageBuilder iBuilder = dataReader.readImageData(subImage, hasAlpha, isAlphaPremultiplied);
372         return iBuilder.getBufferedImage();
373     }
374 
375     @Override
376     public String getDefaultExtension() {
377         return DEFAULT_EXTENSION;
378     }
379 
380     @Override
381     public TiffImagingParameters getDefaultParameters() {
382         return new TiffImagingParameters();
383     }
384 
385     @Override
386     public FormatCompliance getFormatCompliance(final ByteSource byteSource) throws ImagingException, IOException {
387         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
388         final TiffImagingParameters params = new TiffImagingParameters();
389         new TiffReader(params.isStrict()).readContents(byteSource, params, formatCompliance);
390         return formatCompliance;
391     }
392 
393     @Override
394     public byte[] getIccProfileBytes(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
395         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
396         final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory(byteSource, false, formatCompliance);
397         final TiffDirectory directory = contents.directories.get(0);
398 
399         return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE, false);
400     }
401 
402     @Override
403     public ImageInfo getImageInfo(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
404         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
405         final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(byteSource, false, formatCompliance);
406         final TiffDirectory directory = contents.directories.get(0);
407 
408         final TiffField widthField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
409         final TiffField heightField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
410 
411         if (widthField == null || heightField == null) {
412             throw new ImagingException("TIFF image missing size info.");
413         }
414 
415         final int height = heightField.getIntValue();
416         final int width = widthField.getIntValue();
417 
418         final TiffField resolutionUnitField = directory.findField(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
419         int resolutionUnit = 2; // Inch
420         if (resolutionUnitField != null && resolutionUnitField.getValue() != null) {
421             resolutionUnit = resolutionUnitField.getIntValue();
422         }
423 
424         double unitsPerInch = -1;
425         switch (resolutionUnit) {
426         case 1:
427             break;
428         case 2: // Inch
429             unitsPerInch = 1.0;
430             break;
431         case 3: // Centimeter
432             unitsPerInch = 2.54;
433             break;
434         default:
435             break;
436 
437         }
438 
439         int physicalWidthDpi = -1;
440         float physicalWidthInch = -1;
441         int physicalHeightDpi = -1;
442         float physicalHeightInch = -1;
443 
444         if (unitsPerInch > 0) {
445             final TiffField xResolutionField = directory.findField(TiffTagConstants.TIFF_TAG_XRESOLUTION);
446             final TiffField yResolutionField = directory.findField(TiffTagConstants.TIFF_TAG_YRESOLUTION);
447 
448             if (xResolutionField != null && xResolutionField.getValue() != null) {
449                 final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue();
450                 physicalWidthDpi = (int) Math.round(xResolutionPixelsPerUnit * unitsPerInch);
451                 physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch));
452             }
453             if (yResolutionField != null && yResolutionField.getValue() != null) {
454                 final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue();
455                 physicalHeightDpi = (int) Math.round(yResolutionPixelsPerUnit * unitsPerInch);
456                 physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch));
457             }
458         }
459 
460         final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
461 
462         int bitsPerSample = 1;
463         if (bitsPerSampleField != null && bitsPerSampleField.getValue() != null) {
464             bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();
465         }
466 
467         final int bitsPerPixel = bitsPerSample; // assume grayscale;
468         // dunno if this handles colormapped images correctly.
469 
470         final List<String> comments = Allocator.arrayList(directory.size());
471         for (final TiffField field : directory) {
472             final String comment = field.toString();
473             comments.add(comment);
474         }
475 
476         final ImageFormat format = ImageFormats.TIFF;
477         final String formatName = "TIFF Tag-based Image File Format";
478         final String mimeType = "image/tiff";
479         final int numberOfImages = contents.directories.size();
480         // not accurate ... only reflects first
481         final boolean progressive = false;
482         // is TIFF ever interlaced/progressive?
483 
484         final String formatDetails = "TIFF v." + contents.header.tiffVersion;
485 
486         boolean transparent = false; // TODO: wrong
487         boolean usesPalette = false;
488         final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP);
489         if (colorMapField != null) {
490             usesPalette = true;
491         }
492 
493         final int photoInterp = 0xffff & directory.getFieldValue(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
494         final TiffField extraSamplesField = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
495         final int extraSamples;
496         if (extraSamplesField == null) {
497             extraSamples = 0; // no extra samples value
498         } else {
499             extraSamples = extraSamplesField.getIntValue();
500         }
501         final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
502         final int samplesPerPixel;
503         if (samplesPerPixelField == null) {
504             samplesPerPixel = 1;
505         } else {
506             samplesPerPixel = samplesPerPixelField.getIntValue();
507         }
508 
509         final ImageInfo.ColorType colorType;
510         switch (photoInterp) {
511         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_BLACK_IS_ZERO:
512         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_WHITE_IS_ZERO:
513             // the ImageInfo.ColorType enumeration does not distinguish
514             // between monotone white is zero or black is zero
515             colorType = ImageInfo.ColorType.BW;
516             break;
517         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB:
518             colorType = ImageInfo.ColorType.RGB;
519             // even if 4 samples per pixel are included, TIFF
520             // doesn't specify transparent unless the optional "extra samples"
521             // field is supplied with a non-zero value
522             transparent = samplesPerPixel == 4 && extraSamples != 0;
523             break;
524         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB_PALETTE:
525             colorType = ImageInfo.ColorType.RGB;
526             usesPalette = true;
527             break;
528         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_CMYK:
529             colorType = ImageInfo.ColorType.CMYK;
530             break;
531         case TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_YCB_CR:
532             colorType = ImageInfo.ColorType.YCbCr;
533             break;
534         default:
535             colorType = ImageInfo.ColorType.UNKNOWN;
536         }
537 
538         final short compressionFieldValue;
539         if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
540             compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
541         } else {
542             compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
543         }
544         final int compression = 0xffff & compressionFieldValue;
545         ImageInfo.CompressionAlgorithm compressionAlgorithm;
546 
547         switch (compression) {
548         case TIFF_COMPRESSION_UNCOMPRESSED_1:
549             compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
550             break;
551         case TIFF_COMPRESSION_CCITT_1D:
552             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D;
553             break;
554         case TIFF_COMPRESSION_CCITT_GROUP_3:
555             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3;
556             break;
557         case TIFF_COMPRESSION_CCITT_GROUP_4:
558             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4;
559             break;
560         case TIFF_COMPRESSION_LZW:
561             compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW;
562             break;
563         case TIFF_COMPRESSION_JPEG_OBSOLETE:
564             compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG_TIFF_OBSOLETE;
565             break;
566         case TIFF_COMPRESSION_JPEG:
567             compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
568             break;
569         case TIFF_COMPRESSION_UNCOMPRESSED_2:
570             compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
571             break;
572         case TIFF_COMPRESSION_PACKBITS:
573             compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS;
574             break;
575         case TIFF_COMPRESSION_DEFLATE_PKZIP:
576         case TIFF_COMPRESSION_DEFLATE_ADOBE:
577             compressionAlgorithm = ImageInfo.CompressionAlgorithm.DEFLATE;
578             break;
579         default:
580             compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
581             break;
582         }
583 
584         return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, height, mimeType, numberOfImages, physicalHeightDpi, physicalHeightInch,
585                 physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
586     }
587 
588     @Override
589     public Dimension getImageSize(final ByteSource byteSource, final TiffImagingParameters params) throws ImagingException, IOException {
590         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
591         final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory(byteSource, false, formatCompliance);
592         final TiffDirectory directory = contents.directories.get(0);
593 
594         final TiffField widthField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
595         final TiffField heightField = directory.findField(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
596 
597         if (widthField == null || heightField == null) {
598             throw new ImagingException("TIFF image missing size info.");
599         }
600 
601         final int height = heightField.getIntValue();
602         final int width = widthField.getIntValue();
603 
604         return new Dimension(width, height);
605     }
606 
607     @Override
608     public ImageMetadata getMetadata(final ByteSource byteSource, TiffImagingParameters params) throws ImagingException, IOException {
609         if (params == null) {
610             params = this.getDefaultParameters();
611         }
612         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
613         final TiffReader tiffReader = new TiffReader(params.isStrict());
614         final TiffContents contents = tiffReader.readContents(byteSource, params, formatCompliance);
615 
616         final List<TiffDirectory> directories = contents.directories;
617 
618         final TiffImageMetadata result = new TiffImageMetadata(contents);
619 
620         for (final TiffDirectory dir : directories) {
621             final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(tiffReader.getByteOrder(), dir);
622 
623             final List<TiffField> entries = dir.getDirectoryEntries();
624 
625             for (final TiffField entry : entries) {
626                 metadataDirectory.add(entry);
627             }
628 
629             result.add(metadataDirectory);
630         }
631 
632         return result;
633     }
634 
635     @Override
636     public String getName() {
637         return "Tiff-Custom";
638     }
639 
640     private PhotometricInterpreter getPhotometricInterpreter(final TiffDirectory directory, final int photometricInterpretation, final int bitsPerPixel,
641             final int[] bitsPerSample, final int predictor, final int samplesPerPixel, final int width, final int height) throws ImagingException {
642         switch (photometricInterpretation) {
643         case 0:
644         case 1:
645             final boolean invert = photometricInterpretation == 0;
646 
647             return new PhotometricInterpreterBiLevel(samplesPerPixel, bitsPerSample, predictor, width, height, invert);
648         case 3: {
649             // Palette
650             final int[] colorMap = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP, true).getIntArrayValue();
651 
652             final int expectedColormapSize = 3 * (1 << bitsPerPixel);
653 
654             if (colorMap.length != expectedColormapSize) {
655                 throw new ImagingException("Tiff: fColorMap.length (" + colorMap.length + ") != expectedColormapSize (" + expectedColormapSize + ")");
656             }
657 
658             return new PhotometricInterpreterPalette(samplesPerPixel, bitsPerSample, predictor, width, height, colorMap);
659         }
660         case 2: // RGB
661             return new PhotometricInterpreterRgb(samplesPerPixel, bitsPerSample, predictor, width, height);
662         case 5: // CMYK
663             return new PhotometricInterpreterCmyk(samplesPerPixel, bitsPerSample, predictor, width, height);
664         case 6: {
665 //            final double[] yCbCrCoefficients = directory.findField(
666 //                    TiffTagConstants.TIFF_TAG_YCBCR_COEFFICIENTS, true)
667 //                    .getDoubleArrayValue();
668 //
669 //            final int[] yCbCrPositioning = directory.findField(
670 //                    TiffTagConstants.TIFF_TAG_YCBCR_POSITIONING, true)
671 //                    .getIntArrayValue();
672 //            final int[] yCbCrSubSampling = directory.findField(
673 //                    TiffTagConstants.TIFF_TAG_YCBCR_SUB_SAMPLING, true)
674 //                    .getIntArrayValue();
675 //
676 //            final double[] referenceBlackWhite = directory.findField(
677 //                    TiffTagConstants.TIFF_TAG_REFERENCE_BLACK_WHITE, true)
678 //                    .getDoubleArrayValue();
679 
680             return new PhotometricInterpreterYCbCr(samplesPerPixel, bitsPerSample, predictor, width, height);
681         }
682 
683         case 8:
684             return new PhotometricInterpreterCieLab(samplesPerPixel, bitsPerSample, predictor, width, height);
685 
686         case 32844:
687         case 32845: {
688 //            final boolean yonly = (photometricInterpretation == 32844);
689             return new PhotometricInterpreterLogLuv(samplesPerPixel, bitsPerSample, predictor, width, height);
690         }
691 
692         default:
693             throw new ImagingException("TIFF: Unknown fPhotometricInterpretation: " + photometricInterpretation);
694         }
695     }
696 
697     /**
698      * Reads the content of a TIFF file that contains numerical data samples rather than image-related pixels.
699      * <p>
700      * If desired, sub-image data can be read from the file by using a Java {@code TiffImagingParameters} instance to specify the subsection of the image that
701      * is required. The following code illustrates the approach:
702      *
703      * <pre>
704      * int x; // coordinate (column) of corner of sub-image
705      * int y; // coordinate (row) of corner of sub-image
706      * int width; // width of sub-image
707      * int height; // height of sub-image
708      *
709      * TiffImagingParameters params = new TiffImagingParameters();
710      * params.setSubImageX(x);
711      * params.setSubImageY(y);
712      * params.setSubImageWidth(width);
713      * params.setSubImageHeight(height);
714      * TiffRasterData raster = readFloatingPointRasterData(directory, byteOrder, params);
715      * </pre>
716      *
717      * @param directory the TIFF directory pointing to the data to be extracted (TIFF files may contain multiple directories)
718      * @param byteOrder the byte order of the data to be extracted
719      * @param params    an optional parameter object instance
720      * @return a valid instance
721      * @throws ImagingException in the event of incompatible or malformed data
722      * @throws IOException      in the event of an I/O error
723      */
724     TiffRasterData getRasterData(final TiffDirectory directory, final ByteOrder byteOrder, TiffImagingParameters params) throws ImagingException, IOException {
725         if (params == null) {
726             params = this.getDefaultParameters();
727         }
728 
729         final short[] sSampleFmt = directory.getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, true);
730         if (sSampleFmt == null || sSampleFmt.length < 1) {
731             throw new ImagingException("Directory does not specify numeric raster data");
732         }
733 
734         int samplesPerPixel = 1;
735         final TiffField samplesPerPixelField = directory.findField(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
736         if (samplesPerPixelField != null) {
737             samplesPerPixel = samplesPerPixelField.getIntValue();
738         }
739 
740         int[] bitsPerSample = { 1 };
741         int bitsPerPixel = samplesPerPixel;
742         final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
743         if (bitsPerSampleField != null) {
744             bitsPerSample = bitsPerSampleField.getIntArrayValue();
745             bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
746         }
747 
748         final short compressionFieldValue;
749         if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
750             compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
751         } else {
752             compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
753         }
754         final int compression = 0xffff & compressionFieldValue;
755 
756         final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
757         final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
758 
759         Rectangle subImage = checkForSubImage(params);
760         if (subImage != null) {
761             // Check for valid subimage specification. The following checks
762             // are consistent with BufferedImage.getSubimage()
763             if (subImage.width <= 0) {
764                 throw new ImagingException("Negative or zero subimage width.");
765             }
766             if (subImage.height <= 0) {
767                 throw new ImagingException("Negative or zero subimage height.");
768             }
769             if (subImage.x < 0 || subImage.x >= width) {
770                 throw new ImagingException("Subimage x is outside raster.");
771             }
772             if (subImage.x + subImage.width > width) {
773                 throw new ImagingException("Subimage (x+width) is outside raster.");
774             }
775             if (subImage.y < 0 || subImage.y >= height) {
776                 throw new ImagingException("Subimage y is outside raster.");
777             }
778             if (subImage.y + subImage.height > height) {
779                 throw new ImagingException("Subimage (y+height) is outside raster.");
780             }
781 
782             // if the subimage is just the same thing as the whole
783             // image, suppress the subimage processing
784             if (subImage.x == 0 && subImage.y == 0 && subImage.width == width && subImage.height == height) {
785                 subImage = null;
786             }
787         }
788 
789         // int bitsPerPixel = getTagAsValueOrArraySum(entries,
790         // TIFF_TAG_BITS_PER_SAMPLE);
791         int predictor = -1;
792         {
793             // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER);
794             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS);
795             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS);
796             // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION);
797             // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION);
798             final TiffField predictorField = directory.findField(TiffTagConstants.TIFF_TAG_PREDICTOR);
799             if (null != predictorField) {
800                 predictor = predictorField.getIntValueOrArraySum();
801             }
802         }
803 
804         // Obtain the planar configuration
805         final TiffField pcField = directory.findField(TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
806         final TiffPlanarConfiguration planarConfiguration = pcField == null ? TiffPlanarConfiguration.CHUNKY
807                 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
808 
809         if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
810             if (bitsPerSample[0] != 32 && bitsPerSample[0] != 64) {
811                 throw new ImagingException("TIFF floating-point data uses unsupported bits-per-sample: " + bitsPerSample[0]);
812             }
813 
814             if (predictor != -1 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
815                     && predictor != TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) {
816                 throw new ImagingException("TIFF floating-point data uses unsupported horizontal-differencing predictor");
817             }
818         } else if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) {
819 
820             if (samplesPerPixel != 1) {
821                 throw new ImagingException("TIFF integer data uses unsupported samples per pixel: " + samplesPerPixel);
822             }
823 
824             if (bitsPerPixel != 16 && bitsPerPixel != 32) {
825                 throw new ImagingException("TIFF integer data uses unsupported bits-per-pixel: " + bitsPerPixel);
826             }
827 
828             if (predictor != -1 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
829                     && predictor != TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
830                 throw new ImagingException("TIFF integer data uses unsupported horizontal-differencing predictor");
831             }
832         } else {
833             throw new ImagingException("TIFF does not provide a supported raster-data format");
834         }
835 
836         // The photometric interpreter is not used, but the image-based
837         // data reader classes require one. So we create a dummy interpreter.
838         final PhotometricInterpreter photometricInterpreter = new PhotometricInterpreterBiLevel(samplesPerPixel, bitsPerSample, predictor, width, height,
839                 false);
840 
841         final AbstractTiffImageData imageData = directory.getTiffImageData();
842 
843         final ImageDataReader dataReader = imageData.getDataReader(directory, photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, samplesPerPixel,
844                 width, height, compression, planarConfiguration, byteOrder);
845 
846         return dataReader.readRasterData(subImage);
847     }
848 
849     @Override
850     public String getXmpXml(final ByteSource byteSource, XmpImagingParameters<TiffImagingParameters> params) throws ImagingException, IOException {
851         if (params == null) {
852             params = new XmpImagingParameters<>();
853         }
854         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
855         final TiffContents contents = new TiffReader(params.isStrict()).readDirectories(byteSource, false, formatCompliance);
856         final TiffDirectory directory = contents.directories.get(0);
857 
858         final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP, false);
859         if (bytes == null) {
860             return null;
861         }
862 
863         // segment data is UTF-8 encoded xml.
864         return new String(bytes, StandardCharsets.UTF_8);
865     }
866 
867     @Override
868     public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params) throws ImagingException, IOException {
869         if (params == null) {
870             params = new TiffImagingParameters();
871         }
872         new TiffImageWriterLossy().writeImage(src, os, params);
873     }
874 
875 }