001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.imaging.formats.tiff;
018
019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D;
020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3;
021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4;
022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG;
023import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW;
024import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS;
025import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_1;
026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_2;
027
028import java.awt.Dimension;
029import java.awt.Rectangle;
030import java.awt.image.BufferedImage;
031import java.io.IOException;
032import java.io.OutputStream;
033import java.io.PrintWriter;
034import java.nio.ByteOrder;
035import java.nio.charset.StandardCharsets;
036import java.util.ArrayList;
037import java.util.List;
038import java.util.Map;
039
040import org.apache.commons.imaging.FormatCompliance;
041import org.apache.commons.imaging.ImageFormat;
042import org.apache.commons.imaging.ImageFormats;
043import org.apache.commons.imaging.ImageInfo;
044import org.apache.commons.imaging.ImageParser;
045import org.apache.commons.imaging.ImageReadException;
046import org.apache.commons.imaging.ImageWriteException;
047import org.apache.commons.imaging.common.ImageBuilder;
048import org.apache.commons.imaging.common.ImageMetadata;
049import org.apache.commons.imaging.common.bytesource.ByteSource;
050import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement;
051import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
052import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
053import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
054import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader;
055import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
056import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel;
057import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab;
058import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk;
059import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv;
060import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette;
061import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
062import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr;
063import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
064
065public class TiffImageParser extends ImageParser {
066    private static final String DEFAULT_EXTENSION = ".tif";
067    private static final String[] ACCEPTED_EXTENSIONS = { ".tif", ".tiff", };
068
069    @Override
070    public String getName() {
071        return "Tiff-Custom";
072    }
073
074    @Override
075    public String getDefaultExtension() {
076        return DEFAULT_EXTENSION;
077    }
078
079    @Override
080    protected String[] getAcceptedExtensions() {
081        return ACCEPTED_EXTENSIONS;
082    }
083
084    @Override
085    protected ImageFormat[] getAcceptedTypes() {
086        return new ImageFormat[] { ImageFormats.TIFF, //
087        };
088    }
089
090    @Override
091    public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params)
092            throws ImageReadException, IOException {
093        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
094        final TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(
095                byteSource, params, false, formatCompliance);
096        final TiffDirectory directory = contents.directories.get(0);
097
098        return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE,
099                false);
100    }
101
102    @Override
103    public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params)
104            throws ImageReadException, IOException {
105        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
106        final TiffContents contents = new TiffReader(isStrict(params)).readFirstDirectory(
107                byteSource, params, false, formatCompliance);
108        final TiffDirectory directory = contents.directories.get(0);
109
110        final TiffField widthField = directory.findField(
111                TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
112        final TiffField heightField = directory.findField(
113                TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
114
115        if ((widthField == null) || (heightField == null)) {
116            throw new ImageReadException("TIFF image missing size info.");
117        }
118
119        final int height = heightField.getIntValue();
120        final int width = widthField.getIntValue();
121
122        return new Dimension(width, height);
123    }
124
125    @Override
126    public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params)
127            throws ImageReadException, IOException {
128        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
129        final TiffReader tiffReader = new TiffReader(isStrict(params));
130        final TiffContents contents = tiffReader.readContents(byteSource, params,
131                formatCompliance);
132
133        final List<TiffDirectory> directories = contents.directories;
134
135        final TiffImageMetadata result = new TiffImageMetadata(contents);
136
137        for (final TiffDirectory dir : directories) {
138            final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(
139                    tiffReader.getByteOrder(), dir);
140
141            final List<TiffField> entries = dir.getDirectoryEntries();
142
143            for (final TiffField entry : entries) {
144                metadataDirectory.add(entry);
145            }
146
147            result.add(metadataDirectory);
148        }
149
150        return result;
151    }
152
153    @Override
154    public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params)
155            throws ImageReadException, IOException {
156        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
157        final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
158                byteSource, false, formatCompliance);
159        final TiffDirectory directory = contents.directories.get(0);
160
161        final TiffField widthField = directory.findField(
162                TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
163        final TiffField heightField = directory.findField(
164                TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
165
166        if ((widthField == null) || (heightField == null)) {
167            throw new ImageReadException("TIFF image missing size info.");
168        }
169
170        final int height = heightField.getIntValue();
171        final int width = widthField.getIntValue();
172
173        // -------------------
174
175        final TiffField resolutionUnitField = directory.findField(
176                TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
177        int resolutionUnit = 2; // Inch
178        if ((resolutionUnitField != null)
179                && (resolutionUnitField.getValue() != null)) {
180            resolutionUnit = resolutionUnitField.getIntValue();
181        }
182
183        double unitsPerInch = -1;
184        switch (resolutionUnit) {
185        case 1:
186            break;
187        case 2: // Inch
188            unitsPerInch = 1.0;
189            break;
190        case 3: // Centimeter
191            unitsPerInch = 2.54;
192            break;
193        default:
194            break;
195
196        }
197
198        int physicalWidthDpi = -1;
199        float physicalWidthInch = -1;
200        int physicalHeightDpi = -1;
201        float physicalHeightInch = -1;
202
203        if (unitsPerInch > 0) {
204            final TiffField xResolutionField = directory.findField(
205                    TiffTagConstants.TIFF_TAG_XRESOLUTION);
206            final TiffField yResolutionField = directory.findField(
207                    TiffTagConstants.TIFF_TAG_YRESOLUTION);
208
209            if ((xResolutionField != null)
210                    && (xResolutionField.getValue() != null)) {
211                final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue();
212                physicalWidthDpi = (int) Math.round((xResolutionPixelsPerUnit * unitsPerInch));
213                physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch));
214            }
215            if ((yResolutionField != null)
216                    && (yResolutionField.getValue() != null)) {
217                final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue();
218                physicalHeightDpi = (int) Math.round((yResolutionPixelsPerUnit * unitsPerInch));
219                physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch));
220            }
221        }
222
223        // -------------------
224
225        final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
226
227        int bitsPerSample = 1;
228        if ((bitsPerSampleField != null)
229                && (bitsPerSampleField.getValue() != null)) {
230            bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();
231        }
232
233        final int bitsPerPixel = bitsPerSample; // assume grayscale;
234        // dunno if this handles colormapped images correctly.
235
236        // -------------------
237
238        final List<TiffField> entries = directory.entries;
239        final List<String> comments = new ArrayList<>(entries.size());
240        for (final TiffField field : entries) {
241            final String comment = field.toString();
242            comments.add(comment);
243        }
244
245        final ImageFormat format = ImageFormats.TIFF;
246        final String formatName = "TIFF Tag-based Image File Format";
247        final String mimeType = "image/tiff";
248        final int numberOfImages = contents.directories.size();
249        // not accurate ... only reflects first
250        final boolean progressive = false;
251        // is TIFF ever interlaced/progressive?
252
253        final String formatDetails = "Tiff v." + contents.header.tiffVersion;
254
255        final boolean transparent = false; // TODO: wrong
256        boolean usesPalette = false;
257        final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP);
258        if (colorMapField != null) {
259            usesPalette = true;
260        }
261
262        final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB;
263
264        final short compressionFieldValue;
265        if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
266            compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
267        } else {
268            compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
269        }
270        final int compression = 0xffff & compressionFieldValue;
271        ImageInfo.CompressionAlgorithm compressionAlgorithm;
272
273        switch (compression) {
274        case TIFF_COMPRESSION_UNCOMPRESSED_1:
275            compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
276            break;
277        case TIFF_COMPRESSION_CCITT_1D:
278            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D;
279            break;
280        case TIFF_COMPRESSION_CCITT_GROUP_3:
281            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3;
282            break;
283        case TIFF_COMPRESSION_CCITT_GROUP_4:
284            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4;
285            break;
286        case TIFF_COMPRESSION_LZW:
287            compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW;
288            break;
289        case TIFF_COMPRESSION_JPEG:
290            compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
291            break;
292        case TIFF_COMPRESSION_UNCOMPRESSED_2:
293            compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
294            break;
295        case TIFF_COMPRESSION_PACKBITS:
296            compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS;
297            break;
298        default:
299            compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
300            break;
301        }
302
303        final ImageInfo result = new ImageInfo(formatDetails, bitsPerPixel, comments,
304                format, formatName, height, mimeType, numberOfImages,
305                physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
306                physicalWidthInch, width, progressive, transparent,
307                usesPalette, colorType, compressionAlgorithm);
308
309        return result;
310    }
311
312    @Override
313    public String getXmpXml(final ByteSource byteSource, final Map<String, Object> params)
314            throws ImageReadException, IOException {
315        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
316        final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
317                byteSource, false, formatCompliance);
318        final TiffDirectory directory = contents.directories.get(0);
319
320        final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP,
321                false);
322        if (bytes == null) {
323            return null;
324        }
325
326        // segment data is UTF-8 encoded xml.
327        return new String(bytes, StandardCharsets.UTF_8);
328    }
329
330    @Override
331    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
332            throws ImageReadException, IOException {
333        try {
334            pw.println("tiff.dumpImageFile");
335
336            {
337                final ImageInfo imageData = getImageInfo(byteSource);
338                if (imageData == null) {
339                    return false;
340                }
341
342                imageData.toString(pw, "");
343            }
344
345            pw.println("");
346
347            // try
348            {
349                final FormatCompliance formatCompliance = FormatCompliance.getDefault();
350                final Map<String, Object> params = null;
351                final TiffContents contents = new TiffReader(true).readContents(
352                        byteSource, params, formatCompliance);
353
354                final List<TiffDirectory> directories = contents.directories;
355
356                if (directories == null) {
357                    return false;
358                }
359
360                for (int d = 0; d < directories.size(); d++) {
361                    final TiffDirectory directory = directories.get(d);
362
363                    final List<TiffField> entries = directory.entries;
364
365                    if (entries == null) {
366                        return false;
367                    }
368
369                    // Debug.debug("directory offset", directory.offset);
370
371                    for (final TiffField field : entries) {
372                        field.dump(pw, Integer.toString(d));
373                    }
374                }
375
376                pw.println("");
377            }
378            // catch (Exception e)
379            // {
380            // Debug.debug(e);
381            // pw.println("");
382            // return false;
383            // }
384
385            return true;
386        } finally {
387            pw.println("");
388        }
389    }
390
391    @Override
392    public FormatCompliance getFormatCompliance(final ByteSource byteSource)
393            throws ImageReadException, IOException {
394        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
395        final Map<String, Object> params = null;
396        new TiffReader(isStrict(params)).readContents(byteSource, params,
397                formatCompliance);
398        return formatCompliance;
399    }
400
401    public List<byte[]> collectRawImageData(final ByteSource byteSource, final Map<String, Object> params)
402            throws ImageReadException, IOException {
403        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
404        final TiffContents contents = new TiffReader(isStrict(params)).readDirectories(
405                byteSource, true, formatCompliance);
406
407        final List<byte[]> result = new ArrayList<>();
408        for (int i = 0; i < contents.directories.size(); i++) {
409            final TiffDirectory directory = contents.directories.get(i);
410            final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements();
411            for (final ImageDataElement element : dataElements) {
412                final byte[] bytes = byteSource.getBlock(element.offset,
413                        element.length);
414                result.add(bytes);
415            }
416        }
417        return result;
418    }
419
420     /**
421     * Gets a buffered image specified by the byte source.
422     * The TiffImageParser class features support for a number of options that
423     * are unique to the TIFF format.  These options can be specified by
424     * supplying the appropriate parameters using the keys from the
425     * TiffConstants class and the params argument for this method.
426     * <h4>Loading Partial Images</h4>
427     * The TIFF parser includes support for loading partial images without
428     * committing significantly more memory resources than are necessary
429     * to store the image. This feature is useful for conserving memory
430     * in applications that require a relatively small sub image from a
431     * very large TIFF file.  The specifications for partial images are
432     * as follows:
433     * <code><pre>
434     *   HashMap<String, Object> params = new HashMap<String, Object>();
435     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, new Integer(x));
436     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, new Integer(y));
437     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, new Integer(width));
438     *   params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, new Integer(height));
439     * </pre></code>
440     * Note that the arguments x, y, width, and height must specify a
441     * valid rectangular region that is fully contained within the
442     * source TIFF image.
443     * @param byteSource A valid instance of ByteSource
444     * @param params Optional instructions for special-handling or
445     * interpretation of the input data (null objects are permitted and
446     * must be supported by implementations).
447     * @return A valid instance of BufferedImage.
448     * @throws ImageReadException In the event that the specified
449     * content does not conform to the format of the specific parser
450     * implementation.
451     * @throws IOException In the event of unsuccessful read or
452     * access operation.
453     */
454    @Override
455    public BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String, Object> params)
456            throws ImageReadException, IOException {
457        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
458        final TiffReader reader = new TiffReader(isStrict(params));
459        final TiffContents contents = reader.readFirstDirectory(byteSource, params,
460                true, formatCompliance);
461        final ByteOrder byteOrder = reader.getByteOrder();
462        final TiffDirectory directory = contents.directories.get(0);
463        final BufferedImage result = directory.getTiffImage(byteOrder, params);
464        if (null == result) {
465            throw new ImageReadException("TIFF does not contain an image.");
466        }
467        return result;
468    }
469
470    @Override
471    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
472            throws ImageReadException, IOException {
473        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
474        final TiffReader tiffReader = new TiffReader(true);
475        final TiffContents contents = tiffReader.readDirectories(byteSource, true,
476                formatCompliance);
477        final List<BufferedImage> results = new ArrayList<>();
478        for (int i = 0; i < contents.directories.size(); i++) {
479            final TiffDirectory directory = contents.directories.get(i);
480            final BufferedImage result = directory.getTiffImage(
481                    tiffReader.getByteOrder(), null);
482            if (result != null) {
483                results.add(result);
484            }
485        }
486        return results;
487    }
488
489    private Integer getIntegerParameter(
490            final String key, final Map<String, Object>params)
491            throws ImageReadException {
492        if (params == null) {
493            return null;
494        }
495
496        if (!params.containsKey(key)) {
497            return null;
498        }
499
500        final Object obj = params.get(key);
501
502        if (obj instanceof Integer) {
503            return (Integer) obj;
504        }
505        throw new ImageReadException("Non-Integer parameter " + key);
506    }
507
508    private Rectangle checkForSubImage(
509            final Map<String, Object> params)
510            throws ImageReadException {
511        final Integer ix0 = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_X, params);
512        final Integer iy0 = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_Y, params);
513        final Integer iwidth = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, params);
514        final Integer iheight = getIntegerParameter(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, params);
515
516        if (ix0 == null && iy0 == null && iwidth == null && iheight == null) {
517            return null;
518        }
519
520        final StringBuilder sb = new StringBuilder(32);
521        if (ix0 == null) {
522            sb.append(" x0,");
523        }
524        if (iy0 == null) {
525            sb.append(" y0,");
526        }
527        if (iwidth == null) {
528            sb.append(" width,");
529        }
530        if (iheight == null) {
531            sb.append(" height,");
532        }
533        if (sb.length() > 0) {
534            sb.setLength(sb.length() - 1);
535            throw new ImageReadException("Incomplete subimage parameters, missing" + sb.toString());
536        }
537
538        return new Rectangle(ix0, iy0, iwidth, iheight);
539    }
540
541    protected BufferedImage getBufferedImage(final TiffDirectory directory,
542            final ByteOrder byteOrder, final Map<String, Object> params)
543            throws ImageReadException, IOException {
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}