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 java.awt.Dimension;
20  import java.awt.Rectangle;
21  import java.awt.image.BufferedImage;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.PrintWriter;
25  import java.io.UnsupportedEncodingException;
26  import java.nio.ByteOrder;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.commons.imaging.FormatCompliance;
32  import org.apache.commons.imaging.ImageFormat;
33  import org.apache.commons.imaging.ImageFormats;
34  import org.apache.commons.imaging.ImageInfo;
35  import org.apache.commons.imaging.ImageParser;
36  import org.apache.commons.imaging.ImageReadException;
37  import org.apache.commons.imaging.ImageWriteException;
38  import org.apache.commons.imaging.common.ImageMetadata;
39  import org.apache.commons.imaging.common.ImageBuilder;
40  import org.apache.commons.imaging.common.bytesource.ByteSource;
41  import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement;
42  import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
43  import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
44  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
45  import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader;
46  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
47  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel;
48  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab;
49  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk;
50  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv;
51  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette;
52  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
53  import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr;
54  import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
55  
56  import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.*;
57  
58  public class TiffImageParser extends ImageParser {
59      private static final String DEFAULT_EXTENSION = ".tif";
60      private static final String[] ACCEPTED_EXTENSIONS = { ".tif", ".tiff", };
61  
62      @Override
63      public String getName() {
64          return "Tiff-Custom";
65      }
66  
67      @Override
68      public String getDefaultExtension() {
69          return DEFAULT_EXTENSION;
70      }
71  
72      @Override
73      protected String[] getAcceptedExtensions() {
74          return ACCEPTED_EXTENSIONS;
75      }
76  
77      @Override
78      protected ImageFormat[] getAcceptedTypes() {
79          return new ImageFormat[] { ImageFormats.TIFF, //
80          };
81      }
82  
83      @Override
84      public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params)
85              throws ImageReadException, IOException {
86          final FormatCompliance formatCompliance = FormatCompliance.getDefault();
87          final TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(
88                  byteSource, params, false, formatCompliance);
89          final TiffDirectory directory = contents.directories.get(0);
90  
91          return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE,
92                  false);
93      }
94  
95      @Override
96      public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params)
97              throws ImageReadException, IOException {
98          final FormatCompliance formatCompliance = FormatCompliance.getDefault();
99          final TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(
100                 byteSource, params, false, formatCompliance);
101         final TiffDirectory directory = contents.directories.get(0);
102 
103         final TiffField widthField = directory.findField(
104                 TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
105         final TiffField heightField = directory.findField(
106                 TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
107 
108         if ((widthField == null) || (heightField == null)) {
109             throw new ImageReadException("TIFF image missing size info.");
110         }
111 
112         final int height = heightField.getIntValue();
113         final int width = widthField.getIntValue();
114 
115         return new Dimension(width, height);
116     }
117 
118     @Override
119     public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params)
120             throws ImageReadException, IOException {
121         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
122         final TiffReader tiffReader = new TiffReader(isStrict(params));
123         final TiffContents contents = tiffReader.readContents(byteSource, params,
124                 formatCompliance);
125 
126         final List<TiffDirectory> directories = contents.directories;
127 
128         final TiffImageMetadata result = new TiffImageMetadata(contents);
129 
130         for (final TiffDirectory dir : directories) {
131             final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(
132                     tiffReader.getByteOrder(), dir);
133 
134             final List<TiffField> entries = dir.getDirectoryEntries();
135 
136             for (final TiffField entry : entries) {
137                 metadataDirectory.add(entry);
138             }
139 
140             result.add(metadataDirectory);
141         }
142 
143         return result;
144     }
145 
146     @Override
147     public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params)
148             throws ImageReadException, IOException {
149         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
150         final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
151                 byteSource, false, formatCompliance);
152         final TiffDirectory directory = contents.directories.get(0);
153 
154         final TiffField widthField = directory.findField(
155                 TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
156         final TiffField heightField = directory.findField(
157                 TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
158 
159         if ((widthField == null) || (heightField == null)) {
160             throw new ImageReadException("TIFF image missing size info.");
161         }
162 
163         final int height = heightField.getIntValue();
164         final int width = widthField.getIntValue();
165 
166         // -------------------
167 
168         final TiffField resolutionUnitField = directory.findField(
169                 TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
170         int resolutionUnit = 2; // Inch
171         if ((resolutionUnitField != null)
172                 && (resolutionUnitField.getValue() != null)) {
173             resolutionUnit = resolutionUnitField.getIntValue();
174         }
175 
176         double unitsPerInch = -1;
177         switch (resolutionUnit) {
178         case 1:
179             break;
180         case 2: // Inch
181             unitsPerInch = 1.0;
182             break;
183         case 3: // Centimeter
184             unitsPerInch = 2.54;
185             break;
186         default:
187             break;
188 
189         }
190         final TiffField xResolutionField = directory.findField(
191                 TiffTagConstants.TIFF_TAG_XRESOLUTION);
192         final TiffField yResolutionField = directory.findField(
193                 TiffTagConstants.TIFF_TAG_YRESOLUTION);
194 
195         int physicalWidthDpi = -1;
196         float physicalWidthInch = -1;
197         int physicalHeightDpi = -1;
198         float physicalHeightInch = -1;
199 
200         if (unitsPerInch > 0) {
201             if ((xResolutionField != null)
202                     && (xResolutionField.getValue() != null)) {
203                 final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue();
204                 physicalWidthDpi = (int) Math.round((xResolutionPixelsPerUnit * unitsPerInch));
205                 physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch));
206             }
207             if ((yResolutionField != null)
208                     && (yResolutionField.getValue() != null)) {
209                 final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue();
210                 physicalHeightDpi = (int) Math.round((yResolutionPixelsPerUnit * unitsPerInch));
211                 physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch));
212             }
213         }
214 
215         // -------------------
216 
217         final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
218 
219         int bitsPerSample = 1;
220         if ((bitsPerSampleField != null)
221                 && (bitsPerSampleField.getValue() != null)) {
222             bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();
223         }
224 
225         final int bitsPerPixel = bitsPerSample; // assume grayscale;
226         // dunno if this handles colormapped images correctly.
227 
228         // -------------------
229 
230         final List<String> comments = new ArrayList<>();
231         final List<TiffField> entries = directory.entries;
232         for (final TiffField field : entries) {
233             final String comment = field.toString();
234             comments.add(comment);
235         }
236 
237         final ImageFormat format = ImageFormats.TIFF;
238         final String formatName = "TIFF Tag-based Image File Format";
239         final String mimeType = "image/tiff";
240         final int numberOfImages = contents.directories.size();
241         // not accurate ... only reflects first
242         final boolean progressive = false;
243         // is TIFF ever interlaced/progressive?
244 
245         final String formatDetails = "Tiff v." + contents.header.tiffVersion;
246 
247         final boolean transparent = false; // TODO: wrong
248         boolean usesPalette = false;
249         final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP);
250         if (colorMapField != null) {
251             usesPalette = true;
252         }
253 
254         final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB;
255 
256         final short compressionFieldValue;
257         if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
258             compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
259         } else {
260             compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
261         }
262         final int compression = 0xffff & compressionFieldValue;
263         ImageInfo.CompressionAlgorithm compressionAlgorithm;
264 
265         switch (compression) {
266         case TIFF_COMPRESSION_UNCOMPRESSED_1:
267             compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
268             break;
269         case TIFF_COMPRESSION_CCITT_1D:
270             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D;
271             break;
272         case TIFF_COMPRESSION_CCITT_GROUP_3:
273             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3;
274             break;
275         case TIFF_COMPRESSION_CCITT_GROUP_4:
276             compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4;
277             break;
278         case TIFF_COMPRESSION_LZW:
279             compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW;
280             break;
281         case TIFF_COMPRESSION_JPEG:
282             compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
283             break;
284         case TIFF_COMPRESSION_UNCOMPRESSED_2:
285             compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
286             break;
287         case TIFF_COMPRESSION_PACKBITS:
288             compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS;
289             break;
290         default:
291             compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
292             break;
293         }
294 
295         final ImageInfo result = new ImageInfo(formatDetails, bitsPerPixel, comments,
296                 format, formatName, height, mimeType, numberOfImages,
297                 physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
298                 physicalWidthInch, width, progressive, transparent,
299                 usesPalette, colorType, compressionAlgorithm);
300 
301         return result;
302     }
303 
304     @Override
305     public String getXmpXml(final ByteSource byteSource, final Map<String, Object> params)
306             throws ImageReadException, IOException {
307         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
308         final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
309                 byteSource, false, formatCompliance);
310         final TiffDirectory directory = contents.directories.get(0);
311 
312         final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP,
313                 false);
314         if (bytes == null) {
315             return null;
316         }
317 
318         try {
319             // segment data is UTF-8 encoded xml.
320             final String xml = new String(bytes, "utf-8");
321             return xml;
322         } catch (final UnsupportedEncodingException e) {
323             throw new ImageReadException("Invalid JPEG XMP Segment.", e);
324         }
325     }
326 
327     @Override
328     public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
329             throws ImageReadException, IOException {
330         try {
331             pw.println("tiff.dumpImageFile");
332 
333             {
334                 final ImageInfo imageData = getImageInfo(byteSource);
335                 if (imageData == null) {
336                     return false;
337                 }
338 
339                 imageData.toString(pw, "");
340             }
341 
342             pw.println("");
343 
344             // try
345             {
346                 final FormatCompliance formatCompliance = FormatCompliance.getDefault();
347                 final Map<String, Object> params = null;
348                 final TiffContents contents = new TiffReader(true).readContents(
349                         byteSource, params, formatCompliance);
350 
351                 final List<TiffDirectory> directories = contents.directories;
352 
353                 if (directories == null) {
354                     return false;
355                 }
356 
357                 for (int d = 0; d < directories.size(); d++) {
358                     final TiffDirectory directory = directories.get(d);
359 
360                     final List<TiffField> entries = directory.entries;
361 
362                     if (entries == null) {
363                         return false;
364                     }
365 
366                     // Debug.debug("directory offset", directory.offset);
367 
368                     for (final TiffField field : entries) {
369                         field.dump(pw, Integer.toString(d));
370                     }
371                 }
372 
373                 pw.println("");
374             }
375             // catch (Exception e)
376             // {
377             // Debug.debug(e);
378             // pw.println("");
379             // return false;
380             // }
381 
382             return true;
383         } finally {
384             pw.println("");
385         }
386     }
387 
388     @Override
389     public FormatCompliance getFormatCompliance(final ByteSource byteSource)
390             throws ImageReadException, IOException {
391         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
392         final Map<String, Object> params = null;
393         new TiffReader(isStrict(params)).readContents(byteSource, params,
394                 formatCompliance);
395         return formatCompliance;
396     }
397 
398     public List<byte[]> collectRawImageData(final ByteSource byteSource, final Map<String, Object> params)
399             throws ImageReadException, IOException {
400         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
401         final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
402                 byteSource, true, formatCompliance);
403 
404         final List<byte[]> result = new ArrayList<>();
405         for (int i = 0; i < contents.directories.size(); i++) {
406             final TiffDirectory directory = contents.directories.get(i);
407             final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements();
408             for (final ImageDataElement element : dataElements) {
409                 final byte[] bytes = byteSource.getBlock(element.offset,
410                         element.length);
411                 result.add(bytes);
412             }
413         }
414         return result;
415     }
416 
417      /**
418      * Gets a buffered image specified by the byte source.
419      * The TiffImageParser class features support for a number of options that
420      * are unique to the TIFF format.  These options can be specified by
421      * supplying the appropriate parameters using the keys from the
422      * TiffConstants class and the params argument for this method.
423      * <h4>Loading Partial Images</h4>
424      * The TIFF parser includes support for loading partial images without
425      * committing significantly more memory resources than are necessary
426      * to store the image. This feature is useful for conserving memory
427      * in applications that require a relatively small sub image from a 
428      * very large TIFF file.  The specifications for partial images are
429      * as follows:
430      * <code><pre>
431      *   HashMap<String, Object> params = new HashMap<String, Object>();
432      *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, new Integer(x));
433      *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, new Integer(y));
434      *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, new Integer(width));
435      *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, new Integer(height));
436      * </pre></code>
437      * Note that the arguments x, y, width, and height must specify a
438      * valid rectangular region that is fully contained within the 
439      * source TIFF image.
440      * @param byteSource A valid instance of ByteSource
441      * @param params Optional instructions for special-handling or 
442      * interpretation of the input data (null objects are permitted and 
443      * must be supported by implementations).
444      * @return A valid instance of BufferedImage.
445      * @throws ImageReadException In the event that the the specified 
446      * content does not conform to the format of the specific parser
447      * implementation.
448      * @throws IOException In the event of unsuccessful read or
449      * access operation.
450      */
451     @Override
452     public BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String, Object> params)
453             throws ImageReadException, IOException {
454         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
455         final TiffReader reader = new TiffReader(isStrict(params));
456         final TiffContents contents = reader.readFirstDirectory(byteSource, params,
457                 true, formatCompliance);
458         final ByteOrder byteOrder = reader.getByteOrder();
459         final TiffDirectory directory = contents.directories.get(0);
460         final BufferedImage result = directory.getTiffImage(byteOrder, params);
461         if (null == result) {
462             throw new ImageReadException("TIFF does not contain an image.");
463         }
464         return result;
465     }
466 
467     @Override
468     public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
469             throws ImageReadException, IOException {
470         final FormatCompliance formatCompliance = FormatCompliance.getDefault();
471         final TiffReader tiffReader = new TiffReader(true);
472         final TiffContents contents = tiffReader.readDirectories(byteSource, true,
473                 formatCompliance);
474         final List<BufferedImage> results = new ArrayList<>();
475         for (int i = 0; i < contents.directories.size(); i++) {
476             final TiffDirectory directory = contents.directories.get(i);
477             final BufferedImage result = directory.getTiffImage(
478                     tiffReader.getByteOrder(), null);
479             if (result != null) {
480                 results.add(result);
481             }
482         }
483         return results;
484     }
485 
486     private Integer getIntegerParameter(
487             final String key, final Map<String, Object>params)
488             throws ImageReadException
489     {
490         if (params == null) {
491             return null;
492         }
493 
494         if (!params.containsKey(key)) {
495             return null;
496         }
497 
498         final Object obj = params.get(key);
499 
500         if (obj instanceof Integer) {
501             return (Integer) obj;
502         }
503         throw new ImageReadException("Non-Integer parameter " + key);
504     }
505     
506     private Rectangle checkForSubImage(
507             final Map<String, Object> params)
508             throws ImageReadException
509     {
510         final Integer ix0 = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_X, params);
511         final Integer iy0 = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_Y, params);
512         final Integer iwidth = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, params);
513         final Integer iheight = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, params);
514         
515         if (ix0 == null && iy0 == null && iwidth == null && iheight == null) {
516             return null;
517         }
518 
519         final StringBuilder sb = new StringBuilder(32);
520         if (ix0 == null) {
521             sb.append(" x0,");
522         }
523         if (iy0 == null) {
524             sb.append(" y0,");
525         }
526         if (iwidth == null) {
527             sb.append(" width,");
528         }
529         if (iheight == null) {
530             sb.append(" height,");
531         }
532         if (sb.length() > 0) {
533             sb.setLength(sb.length() - 1);
534             throw new ImageReadException("Incomplete subimage parameters, missing" + sb.toString());
535         }
536         
537         return new Rectangle(ix0, iy0, iwidth, iheight);
538     }
539     
540     protected BufferedImage getBufferedImage(final TiffDirectory directory,
541             final ByteOrder byteOrder, final Map<String, Object> params) 
542             throws ImageReadException, IOException
543     {
544         final List<TiffField> entries = directory.entries;
545 
546         if (entries == null) {
547             throw new ImageReadException("TIFF missing entries");
548         }
549 
550         final int photometricInterpretation = 0xffff & directory.getFieldValue(
551                 TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
552         final short compressionFieldValue;
553         if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
554             compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
555         } else {
556             compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
557         }
558         final int compression = 0xffff & compressionFieldValue;
559         final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
560         final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);      
561         
562         Rectangle subImage = checkForSubImage(params);
563         if (subImage != null) {
564             // Check for valid subimage specification. The following checks
565             // are consistent with BufferedImage.getSubimage()
566             if (subImage.width <= 0) {
567                 throw new ImageReadException("negative or zero subimage width");
568             }
569             if (subImage.height <= 0) {
570                 throw new ImageReadException("negative or zero subimage height");
571             }
572             if (subImage.x < 0 || subImage.x >= width) {
573                 throw new ImageReadException("subimage x is outside raster");
574             }
575             if (subImage.x + subImage.width > width) {
576                 throw new ImageReadException("subimage (x+width) is outside raster");
577             }
578             if (subImage.y < 0 || subImage.y >= height) {
579                 throw new ImageReadException("subimage y is outside raster");
580             }
581             if (subImage.y + subImage.height > height) {
582                 throw new ImageReadException("subimage (y+height) is outside raster");
583             }
584 
585             // if the subimage is just the same thing as the whole
586             // image, suppress the subimage processing
587             if (subImage.x == 0
588                     && subImage.y == 0
589                     && subImage.width == width
590                     && subImage.height == height) {
591                 subImage = null;
592             }
593         }
594 
595         
596         int samplesPerPixel = 1;
597         final TiffField samplesPerPixelField = directory.findField(
598                 TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
599         if (samplesPerPixelField != null) {
600             samplesPerPixel = samplesPerPixelField.getIntValue();
601         }
602         int[] bitsPerSample = { 1 };
603         int bitsPerPixel = samplesPerPixel;
604         final TiffField bitsPerSampleField = directory.findField(
605                 TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
606         if (bitsPerSampleField != null) {
607             bitsPerSample = bitsPerSampleField.getIntArrayValue();
608             bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
609         }
610 
611         // int bitsPerPixel = getTagAsValueOrArraySum(entries,
612         // TIFF_TAG_BITS_PER_SAMPLE);
613 
614         int predictor = -1;
615         {
616             // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER);
617             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS);
618             // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS);
619             // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION);
620             // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION);
621             final TiffField predictorField = directory.findField(
622                     TiffTagConstants.TIFF_TAG_PREDICTOR);
623             if (null != predictorField) {
624                 predictor = predictorField.getIntValueOrArraySum();
625             }
626         }
627 
628         if (samplesPerPixel != bitsPerSample.length) {
629             throw new ImageReadException("Tiff: samplesPerPixel ("
630                     + samplesPerPixel + ")!=fBitsPerSample.length ("
631                     + bitsPerSample.length + ")");
632         }
633 
634 
635 
636         final PhotometricInterpreter photometricInterpreter = getPhotometricInterpreter(
637                 directory, photometricInterpretation, bitsPerPixel,
638                 bitsPerSample, predictor, samplesPerPixel, width, height);
639 
640         final TiffImageData imageData = directory.getTiffImageData();
641 
642         final ImageDataReader dataReader = imageData.getDataReader(directory,
643                 photometricInterpreter, bitsPerPixel, bitsPerSample, predictor,
644                 samplesPerPixel, width, height, compression, byteOrder);
645 
646         BufferedImage result = null;
647         if (subImage != null) {
648             result = dataReader.readImageData(subImage);
649         } else {
650             final boolean hasAlpha = false;
651             final ImageBuilder imageBuilder = new ImageBuilder(width, height, hasAlpha);
652 
653             dataReader.readImageData(imageBuilder);
654             result =  imageBuilder.getBufferedImage();
655         }
656         return result;     
657     }
658 
659     private PhotometricInterpreter getPhotometricInterpreter(
660             final TiffDirectory directory, final int photometricInterpretation,
661             final int bitsPerPixel, final int[] bitsPerSample, final int predictor,
662             final int samplesPerPixel, final int width, final int height)
663             throws ImageReadException {
664         switch (photometricInterpretation) {
665         case 0:
666         case 1:
667             final boolean invert = photometricInterpretation == 0;
668 
669             return new PhotometricInterpreterBiLevel(samplesPerPixel,
670                     bitsPerSample, predictor, width, height, invert);
671         case 3: // Palette
672         {
673             final int[] colorMap = directory.findField(
674                     TiffTagConstants.TIFF_TAG_COLOR_MAP, true).getIntArrayValue();
675 
676             final int expectedColormapSize = 3 * (1 << bitsPerPixel);
677 
678             if (colorMap.length != expectedColormapSize) {
679                 throw new ImageReadException("Tiff: fColorMap.length ("
680                         + colorMap.length + ")!=expectedColormapSize ("
681                         + expectedColormapSize + ")");
682             }
683 
684             return new PhotometricInterpreterPalette(samplesPerPixel,
685                     bitsPerSample, predictor, width, height, colorMap);
686         }
687         case 2: // RGB
688             return new PhotometricInterpreterRgb(samplesPerPixel,
689                     bitsPerSample, predictor, width, height);
690         case 5: // CMYK
691             return new PhotometricInterpreterCmyk(samplesPerPixel,
692                     bitsPerSample, predictor, width, height);
693         case 6: //
694         {
695 //            final double yCbCrCoefficients[] = directory.findField(
696 //                    TiffTagConstants.TIFF_TAG_YCBCR_COEFFICIENTS, true)
697 //                    .getDoubleArrayValue();
698 //
699 //            final int yCbCrPositioning[] = directory.findField(
700 //                    TiffTagConstants.TIFF_TAG_YCBCR_POSITIONING, true)
701 //                    .getIntArrayValue();
702 //            final int yCbCrSubSampling[] = directory.findField(
703 //                    TiffTagConstants.TIFF_TAG_YCBCR_SUB_SAMPLING, true)
704 //                    .getIntArrayValue();
705 //
706 //            final double referenceBlackWhite[] = directory.findField(
707 //                    TiffTagConstants.TIFF_TAG_REFERENCE_BLACK_WHITE, true)
708 //                    .getDoubleArrayValue();
709 
710             return new PhotometricInterpreterYCbCr(samplesPerPixel,
711                     bitsPerSample, predictor, width,
712                     height);
713         }
714 
715         case 8:
716             return new PhotometricInterpreterCieLab(samplesPerPixel,
717                     bitsPerSample, predictor, width, height);
718 
719         case 32844:
720         case 32845: {
721 //            final boolean yonly = (photometricInterpretation == 32844);
722             return new PhotometricInterpreterLogLuv(samplesPerPixel,
723                     bitsPerSample, predictor, width, height);
724         }
725 
726         default:
727             throw new ImageReadException(
728                     "TIFF: Unknown fPhotometricInterpretation: "
729                             + photometricInterpretation);
730         }
731     }
732 
733     @Override
734     public void writeImage(final BufferedImage src, final OutputStream os, final Map<String, Object> params)
735             throws ImageWriteException, IOException {
736         new TiffImageWriterLossy().writeImage(src, os, params);
737     }
738 
739 }