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;
038
039import org.apache.commons.imaging.FormatCompliance;
040import org.apache.commons.imaging.ImageFormat;
041import org.apache.commons.imaging.ImageFormats;
042import org.apache.commons.imaging.ImageInfo;
043import org.apache.commons.imaging.ImageParser;
044import org.apache.commons.imaging.ImageReadException;
045import org.apache.commons.imaging.ImageWriteException;
046import org.apache.commons.imaging.common.ImageBuilder;
047import org.apache.commons.imaging.common.ImageMetadata;
048import org.apache.commons.imaging.common.XmpEmbeddable;
049import org.apache.commons.imaging.common.XmpImagingParameters;
050import org.apache.commons.imaging.common.bytesource.ByteSource;
051import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement;
052import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants;
053import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration;
054import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
055import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader;
056import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter;
057import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel;
058import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab;
059import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk;
060import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv;
061import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette;
062import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb;
063import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr;
064import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy;
065
066public class TiffImageParser extends ImageParser<TiffImagingParameters> implements XmpEmbeddable {
067    private static final String DEFAULT_EXTENSION = ImageFormats.TIFF.getDefaultExtension();
068    private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.TIFF.getExtensions();
069
070    @Override
071    public TiffImagingParameters getDefaultParameters() {
072        return new TiffImagingParameters();
073    }
074
075    @Override
076    public String getName() {
077        return "Tiff-Custom";
078    }
079
080    @Override
081    public String getDefaultExtension() {
082        return DEFAULT_EXTENSION;
083    }
084
085    @Override
086    protected String[] getAcceptedExtensions() {
087        return ACCEPTED_EXTENSIONS;
088    }
089
090    @Override
091    protected ImageFormat[] getAcceptedTypes() {
092        return new ImageFormat[] { ImageFormats.TIFF, //
093        };
094    }
095
096    @Override
097    public byte[] getICCProfileBytes(final ByteSource byteSource, final TiffImagingParameters params)
098            throws ImageReadException, IOException {
099        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
100        final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory(
101                byteSource, false, formatCompliance);
102        final TiffDirectory directory = contents.directories.get(0);
103
104        return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE,
105                false);
106    }
107
108    @Override
109    public Dimension getImageSize(final ByteSource byteSource, final TiffImagingParameters params)
110            throws ImageReadException, IOException {
111        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
112        final TiffContents contents = new TiffReader(params != null && params.isStrict())
113                .readFirstDirectory(byteSource, false, formatCompliance);
114        final TiffDirectory directory = contents.directories.get(0);
115
116        final TiffField widthField = directory.findField(
117                TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
118        final TiffField heightField = directory.findField(
119                TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
120
121        if ((widthField == null) || (heightField == null)) {
122            throw new ImageReadException("TIFF image missing size info.");
123        }
124
125        final int height = heightField.getIntValue();
126        final int width = widthField.getIntValue();
127
128        return new Dimension(width, height);
129    }
130
131    @Override
132    public ImageMetadata getMetadata(final ByteSource byteSource, TiffImagingParameters params)
133            throws ImageReadException, IOException {
134        if (params == null) {
135            params = this.getDefaultParameters();
136        }
137        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
138        final TiffReader tiffReader = new TiffReader(params.isStrict());
139        final TiffContents contents = tiffReader.readContents(byteSource, params, formatCompliance);
140
141        final List<TiffDirectory> directories = contents.directories;
142
143        final TiffImageMetadata result = new TiffImageMetadata(contents);
144
145        for (final TiffDirectory dir : directories) {
146            final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory(
147                    tiffReader.getByteOrder(), dir);
148
149            final List<TiffField> entries = dir.getDirectoryEntries();
150
151            for (final TiffField entry : entries) {
152                metadataDirectory.add(entry);
153            }
154
155            result.add(metadataDirectory);
156        }
157
158        return result;
159    }
160
161    @Override
162    public ImageInfo getImageInfo(final ByteSource byteSource, final TiffImagingParameters params)
163            throws ImageReadException, IOException {
164        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
165        final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(
166                byteSource, false, formatCompliance);
167        final TiffDirectory directory = contents.directories.get(0);
168
169        final TiffField widthField = directory.findField(
170                TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true);
171        final TiffField heightField = directory.findField(
172                TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true);
173
174        if ((widthField == null) || (heightField == null)) {
175            throw new ImageReadException("TIFF image missing size info.");
176        }
177
178        final int height = heightField.getIntValue();
179        final int width = widthField.getIntValue();
180
181        // -------------------
182
183        final TiffField resolutionUnitField = directory.findField(
184                TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
185        int resolutionUnit = 2; // Inch
186        if ((resolutionUnitField != null)
187                && (resolutionUnitField.getValue() != null)) {
188            resolutionUnit = resolutionUnitField.getIntValue();
189        }
190
191        double unitsPerInch = -1;
192        switch (resolutionUnit) {
193        case 1:
194            break;
195        case 2: // Inch
196            unitsPerInch = 1.0;
197            break;
198        case 3: // Centimeter
199            unitsPerInch = 2.54;
200            break;
201        default:
202            break;
203
204        }
205
206        int physicalWidthDpi = -1;
207        float physicalWidthInch = -1;
208        int physicalHeightDpi = -1;
209        float physicalHeightInch = -1;
210
211        if (unitsPerInch > 0) {
212            final TiffField xResolutionField = directory.findField(
213                    TiffTagConstants.TIFF_TAG_XRESOLUTION);
214            final TiffField yResolutionField = directory.findField(
215                    TiffTagConstants.TIFF_TAG_YRESOLUTION);
216
217            if ((xResolutionField != null)
218                    && (xResolutionField.getValue() != null)) {
219                final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue();
220                physicalWidthDpi = (int) Math.round((xResolutionPixelsPerUnit * unitsPerInch));
221                physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch));
222            }
223            if ((yResolutionField != null)
224                    && (yResolutionField.getValue() != null)) {
225                final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue();
226                physicalHeightDpi = (int) Math.round((yResolutionPixelsPerUnit * unitsPerInch));
227                physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch));
228            }
229        }
230
231        // -------------------
232
233        final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
234
235        int bitsPerSample = 1;
236        if ((bitsPerSampleField != null)
237                && (bitsPerSampleField.getValue() != null)) {
238            bitsPerSample = bitsPerSampleField.getIntValueOrArraySum();
239        }
240
241        final int bitsPerPixel = bitsPerSample; // assume grayscale;
242        // dunno if this handles colormapped images correctly.
243
244        // -------------------
245
246        final List<TiffField> entries = directory.entries;
247        final List<String> comments = new ArrayList<>(entries.size());
248        for (final TiffField field : entries) {
249            final String comment = field.toString();
250            comments.add(comment);
251        }
252
253        final ImageFormat format = ImageFormats.TIFF;
254        final String formatName = "TIFF Tag-based Image File Format";
255        final String mimeType = "image/tiff";
256        final int numberOfImages = contents.directories.size();
257        // not accurate ... only reflects first
258        final boolean progressive = false;
259        // is TIFF ever interlaced/progressive?
260
261        final String formatDetails = "Tiff v." + contents.header.tiffVersion;
262
263        final boolean transparent = false; // TODO: wrong
264        boolean usesPalette = false;
265        final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP);
266        if (colorMapField != null) {
267            usesPalette = true;
268        }
269
270        final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB;
271
272        final short compressionFieldValue;
273        if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
274            compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
275        } else {
276            compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
277        }
278        final int compression = 0xffff & compressionFieldValue;
279        ImageInfo.CompressionAlgorithm compressionAlgorithm;
280
281        switch (compression) {
282        case TIFF_COMPRESSION_UNCOMPRESSED_1:
283            compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
284            break;
285        case TIFF_COMPRESSION_CCITT_1D:
286            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D;
287            break;
288        case TIFF_COMPRESSION_CCITT_GROUP_3:
289            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3;
290            break;
291        case TIFF_COMPRESSION_CCITT_GROUP_4:
292            compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4;
293            break;
294        case TIFF_COMPRESSION_LZW:
295            compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW;
296            break;
297        case TIFF_COMPRESSION_JPEG:
298            compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
299            break;
300        case TIFF_COMPRESSION_UNCOMPRESSED_2:
301            compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE;
302            break;
303        case TIFF_COMPRESSION_PACKBITS:
304            compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS;
305            break;
306        default:
307            compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN;
308            break;
309        }
310
311        return new ImageInfo(formatDetails, bitsPerPixel, comments,
312                format, formatName, height, mimeType, numberOfImages,
313                physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
314                physicalWidthInch, width, progressive, transparent,
315                usesPalette, colorType, compressionAlgorithm);
316    }
317
318    @Override
319    public String getXmpXml(final ByteSource byteSource, XmpImagingParameters params)
320            throws ImageReadException, IOException {
321        if (params == null) {
322            params = new XmpImagingParameters();
323        }
324        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
325        final TiffContents contents = new TiffReader(params.isStrict()).readDirectories(
326                byteSource, false, formatCompliance);
327        final TiffDirectory directory = contents.directories.get(0);
328
329        final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP,
330                false);
331        if (bytes == null) {
332            return null;
333        }
334
335        // segment data is UTF-8 encoded xml.
336        return new String(bytes, StandardCharsets.UTF_8);
337    }
338
339    @Override
340    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
341            throws ImageReadException, IOException {
342        try {
343            pw.println("tiff.dumpImageFile");
344
345            {
346                final ImageInfo imageData = getImageInfo(byteSource);
347                if (imageData == null) {
348                    return false;
349                }
350
351                imageData.toString(pw, "");
352            }
353
354            pw.println("");
355
356            // try
357            {
358                final FormatCompliance formatCompliance = FormatCompliance.getDefault();
359                final TiffImagingParameters params = new TiffImagingParameters();
360                final TiffContents contents = new TiffReader(true).readContents(
361                        byteSource, params, formatCompliance);
362
363                final List<TiffDirectory> directories = contents.directories;
364                if (directories == null) {
365                    return false;
366                }
367
368                for (int d = 0; d < directories.size(); d++) {
369                    final TiffDirectory directory = directories.get(d);
370
371                    final List<TiffField> entries = directory.entries;
372
373                    if (entries == null) {
374                        return false;
375                    }
376
377                    // Debug.debug("directory offset", directory.offset);
378
379                    for (final TiffField field : entries) {
380                        field.dump(pw, Integer.toString(d));
381                    }
382                }
383
384                pw.println("");
385            }
386            // catch (Exception e)
387            // {
388            // Debug.debug(e);
389            // pw.println("");
390            // return false;
391            // }
392
393            return true;
394        } finally {
395            pw.println("");
396        }
397    }
398
399    @Override
400    public FormatCompliance getFormatCompliance(final ByteSource byteSource)
401            throws ImageReadException, IOException {
402        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
403        final TiffImagingParameters params = new TiffImagingParameters();
404        new TiffReader(params.isStrict()).readContents(byteSource, params,
405                formatCompliance);
406        return formatCompliance;
407    }
408
409    public List<byte[]> collectRawImageData(final ByteSource byteSource, final TiffImagingParameters params)
410            throws ImageReadException, IOException {
411        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
412        final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories(
413                byteSource, true, formatCompliance);
414
415        final List<byte[]> result = new ArrayList<>();
416        for (int i = 0; i < contents.directories.size(); i++) {
417            final TiffDirectory directory = contents.directories.get(i);
418            final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements();
419            for (final ImageDataElement element : dataElements) {
420                final byte[] bytes = byteSource.getBlock(element.offset,
421                        element.length);
422                result.add(bytes);
423            }
424        }
425        return result;
426    }
427
428     /**
429     * <p>Gets a buffered image specified by the byte source.
430     * The TiffImageParser class features support for a number of options that
431     * are unique to the TIFF format.  These options can be specified by
432     * supplying the appropriate parameters using the keys from the
433     * TiffConstants class and the params argument for this method.</p>
434     *
435     * <p><strong>Loading Partial Images</strong></p>
436     *
437     * <p>The TIFF parser includes support for loading partial images without
438     * committing significantly more memory resources than are necessary
439     * to store the image. This feature is useful for conserving memory
440     * in applications that require a relatively small sub image from a
441     * very large TIFF file.  The specifications for partial images are
442     * as follows:</p>
443     *
444     * <pre>
445     *   TiffImagingParameters params = new TiffImagingParameters();
446     *   params.setSubImageX(x);
447     *   params.setSubImageY(y);
448     *   params.setSubImageWidth(width);
449     *   params.setSubImageHeight(height);
450     * </pre>
451     *
452     * <p>Note that the arguments x, y, width, and height must specify a
453     * valid rectangular region that is fully contained within the
454     * source TIFF image.</p>
455     *
456     * @param byteSource A valid instance of ByteSource
457     * @param params Optional instructions for special-handling or
458     * interpretation of the input data (null objects are permitted and
459     * must be supported by implementations).
460     * @return A valid instance of BufferedImage.
461     * @throws ImageReadException In the event that the specified
462     * content does not conform to the format of the specific parser
463     * implementation.
464     * @throws IOException In the event of unsuccessful read or
465     * access operation.
466     */
467    @Override
468    public BufferedImage getBufferedImage(final ByteSource byteSource, TiffImagingParameters params)
469            throws ImageReadException, IOException {
470        if (params == null) {
471            params = new TiffImagingParameters();
472        }
473        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
474        final TiffReader reader = new TiffReader(params.isStrict());
475        final TiffContents contents = reader.readFirstDirectory(byteSource, true, formatCompliance);
476        final ByteOrder byteOrder = reader.getByteOrder();
477        final TiffDirectory directory = contents.directories.get(0);
478        final BufferedImage result = directory.getTiffImage(byteOrder, params);
479        if (null == result) {
480            throw new ImageReadException("TIFF does not contain an image.");
481        }
482        return result;
483    }
484
485    @Override
486    public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource)
487            throws ImageReadException, IOException {
488        final FormatCompliance formatCompliance = FormatCompliance.getDefault();
489        final TiffReader tiffReader = new TiffReader(true);
490        final TiffContents contents = tiffReader.readDirectories(byteSource, true,
491                formatCompliance);
492        final List<BufferedImage> results = new ArrayList<>();
493        for (int i = 0; i < contents.directories.size(); i++) {
494            final TiffDirectory directory = contents.directories.get(i);
495            final BufferedImage result = directory.getTiffImage(
496                    tiffReader.getByteOrder(), null);
497            if (result != null) {
498                results.add(result);
499            }
500        }
501        return results;
502    }
503
504    private Rectangle checkForSubImage(
505            final TiffImagingParameters params)
506            throws ImageReadException {
507        // the params class enforces a correct specification for the
508        // sub-image, but does not have knowledge of the actual
509        // dimensions of the image that is being read.  This method
510        // returns the sub-image specification, if any, and leaves
511        // further tests to the calling module.
512        if (params.isSubImageSet()) {
513            final int ix0 = params.getSubImageX();
514            final int iy0 = params.getSubImageY();
515            final int iwidth = params.getSubImageWidth();
516            final int iheight = params.getSubImageHeight();
517            return new Rectangle(ix0, iy0, iwidth, iheight);
518        } else {
519            return null;
520        }
521    }
522
523    protected BufferedImage getBufferedImage(final TiffDirectory directory,
524            final ByteOrder byteOrder, final TiffImagingParameters params)
525            throws ImageReadException, IOException {
526        final List<TiffField> entries = directory.entries;
527
528        if (entries == null) {
529            throw new ImageReadException("TIFF missing entries");
530        }
531
532        final short compressionFieldValue;
533        if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
534            compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
535        } else {
536            compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
537        }
538        final int compression = 0xffff & compressionFieldValue;
539        final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
540        final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
541
542        final Rectangle subImage = checkForSubImage(params);
543        if (subImage != null) {
544            // Check for valid subimage specification. The following checks
545            // are consistent with BufferedImage.getSubimage()
546            if (subImage.width <= 0) {
547                throw new ImageReadException("negative or zero subimage width");
548            }
549            if (subImage.height <= 0) {
550                throw new ImageReadException("negative or zero subimage height");
551            }
552            if (subImage.x < 0 || subImage.x >= width) {
553                throw new ImageReadException("subimage x is outside raster");
554            }
555            if (subImage.x + subImage.width > width) {
556                throw new ImageReadException("subimage (x+width) is outside raster");
557            }
558            if (subImage.y < 0 || subImage.y >= height) {
559                throw new ImageReadException("subimage y is outside raster");
560            }
561            if (subImage.y + subImage.height > height) {
562                throw new ImageReadException("subimage (y+height) is outside raster");
563            }
564        }
565
566        int samplesPerPixel = 1;
567        final TiffField samplesPerPixelField = directory.findField(
568                TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
569        if (samplesPerPixelField != null) {
570            samplesPerPixel = samplesPerPixelField.getIntValue();
571        }
572        int[] bitsPerSample = { 1 };
573        int bitsPerPixel = samplesPerPixel;
574        final TiffField bitsPerSampleField = directory.findField(
575                TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
576        if (bitsPerSampleField != null) {
577            bitsPerSample = bitsPerSampleField.getIntArrayValue();
578            bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
579        }
580
581        // int bitsPerPixel = getTagAsValueOrArraySum(entries,
582        // TIFF_TAG_BITS_PER_SAMPLE);
583
584        int predictor = -1;
585        {
586            // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER);
587            // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS);
588            // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS);
589            // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION);
590            // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION);
591            final TiffField predictorField = directory.findField(
592                    TiffTagConstants.TIFF_TAG_PREDICTOR);
593            if (null != predictorField) {
594                predictor = predictorField.getIntValueOrArraySum();
595            }
596        }
597
598        if (samplesPerPixel != bitsPerSample.length) {
599            throw new ImageReadException("Tiff: samplesPerPixel ("
600                    + samplesPerPixel + ")!=fBitsPerSample.length ("
601                    + bitsPerSample.length + ")");
602        }
603
604
605        final int photometricInterpretation = 0xffff & directory.getFieldValue(
606                TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION);
607
608        boolean hasAlpha = false;
609        boolean isAlphaPremultiplied = false;
610        if (photometricInterpretation == TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB
611                && samplesPerPixel == 4) {
612            final TiffField extraSamplesField
613                    = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES);
614            if (extraSamplesField == null) {
615                // this state is not defined in the TIFF specification
616                // and so this code will interpret it as meaning that the
617                // proper handling would be ARGB.
618                hasAlpha = true;
619                isAlphaPremultiplied = false;
620            } else {
621                final int extraSamplesValue = extraSamplesField.getIntValue();
622                switch (extraSamplesValue) {
623                    case TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA:
624                        hasAlpha = true;
625                        isAlphaPremultiplied = false;
626                        break;
627                    case TiffTagConstants.EXTRA_SAMPLE_ASSOCIATED_ALPHA:
628                        hasAlpha = true;
629                        isAlphaPremultiplied = true;
630                        break;
631                    case 0:
632                    default:
633                        hasAlpha = false;
634                        isAlphaPremultiplied = false;
635                        break;
636                }
637            }
638        }
639
640        PhotometricInterpreter photometricInterpreter = params.getCustomPhotometricInterpreter();
641        if (photometricInterpreter == null) {
642            photometricInterpreter = getPhotometricInterpreter(
643                directory, photometricInterpretation, bitsPerPixel,
644                bitsPerSample, predictor, samplesPerPixel, width, height);
645        }
646
647        // Obtain the planar configuration
648        final TiffField pcField = directory.findField(
649          TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
650        final TiffPlanarConfiguration planarConfiguration
651          = pcField == null
652            ? TiffPlanarConfiguration.CHUNKY
653            : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
654
655        if (planarConfiguration == TiffPlanarConfiguration.PLANAR) {
656            // currently, we support the non-interleaved (non-chunky)
657            // option only in the case of a 24-bit RBG photometric interpreter
658            // and for strips (not for tiles).
659            if (photometricInterpretation
660              != TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB
661              || bitsPerPixel != 24) {
662                throw new ImageReadException("For planar configuration 2, only 24 bit RGB is currently supported");
663            }
664            if (null == directory.findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS)) {
665                throw new ImageReadException("For planar configuration 2, only strips-organization is supported");
666            }
667        }
668
669        final TiffImageData imageData = directory.getTiffImageData();
670
671        final ImageDataReader dataReader = imageData.getDataReader(directory,
672                photometricInterpreter, bitsPerPixel, bitsPerSample, predictor,
673          samplesPerPixel, width, height, compression,
674          planarConfiguration, byteOrder);
675
676        final ImageBuilder iBuilder = dataReader.readImageData(
677            subImage, hasAlpha, isAlphaPremultiplied);
678        return iBuilder.getBufferedImage();
679    }
680
681    private PhotometricInterpreter getPhotometricInterpreter(
682            final TiffDirectory directory, final int photometricInterpretation,
683            final int bitsPerPixel, final int[] bitsPerSample, final int predictor,
684            final int samplesPerPixel, final int width, final int height)
685            throws ImageReadException {
686        switch (photometricInterpretation) {
687        case 0:
688        case 1:
689            final boolean invert = photometricInterpretation == 0;
690
691            return new PhotometricInterpreterBiLevel(samplesPerPixel,
692                    bitsPerSample, predictor, width, height, invert);
693        case 3: {
694           // Palette
695            final int[] colorMap = directory.findField(
696                    TiffTagConstants.TIFF_TAG_COLOR_MAP, true).getIntArrayValue();
697
698            final int expectedColormapSize = 3 * (1 << bitsPerPixel);
699
700            if (colorMap.length != expectedColormapSize) {
701                throw new ImageReadException("Tiff: fColorMap.length ("
702                        + colorMap.length + ")!=expectedColormapSize ("
703                        + expectedColormapSize + ")");
704            }
705
706            return new PhotometricInterpreterPalette(samplesPerPixel,
707                    bitsPerSample, predictor, width, height, colorMap);
708        }
709        case 2: // RGB
710            return new PhotometricInterpreterRgb(samplesPerPixel,
711                    bitsPerSample, predictor, width, height);
712        case 5: // CMYK
713            return new PhotometricInterpreterCmyk(samplesPerPixel,
714                    bitsPerSample, predictor, width, height);
715        case 6: {
716//            final double yCbCrCoefficients[] = directory.findField(
717//                    TiffTagConstants.TIFF_TAG_YCBCR_COEFFICIENTS, true)
718//                    .getDoubleArrayValue();
719//
720//            final int yCbCrPositioning[] = directory.findField(
721//                    TiffTagConstants.TIFF_TAG_YCBCR_POSITIONING, true)
722//                    .getIntArrayValue();
723//            final int yCbCrSubSampling[] = directory.findField(
724//                    TiffTagConstants.TIFF_TAG_YCBCR_SUB_SAMPLING, true)
725//                    .getIntArrayValue();
726//
727//            final double referenceBlackWhite[] = directory.findField(
728//                    TiffTagConstants.TIFF_TAG_REFERENCE_BLACK_WHITE, true)
729//                    .getDoubleArrayValue();
730
731            return new PhotometricInterpreterYCbCr(samplesPerPixel,
732                    bitsPerSample, predictor, width,
733                    height);
734        }
735
736        case 8:
737            return new PhotometricInterpreterCieLab(samplesPerPixel,
738                    bitsPerSample, predictor, width, height);
739
740        case 32844:
741        case 32845: {
742//            final boolean yonly = (photometricInterpretation == 32844);
743            return new PhotometricInterpreterLogLuv(samplesPerPixel,
744                    bitsPerSample, predictor, width, height);
745        }
746
747        default:
748            throw new ImageReadException(
749                    "TIFF: Unknown fPhotometricInterpretation: "
750                            + photometricInterpretation);
751        }
752    }
753
754    @Override
755    public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params)
756            throws ImageWriteException, IOException {
757        if (params == null) {
758            params = new TiffImagingParameters();
759        }
760        new TiffImageWriterLossy().writeImage(src, os, params);
761    }
762
763    /**
764     * Reads the content of a TIFF file that contains numerical data samples
765     * rather than image-related pixels.
766     * <p>
767     * If desired, sub-image data can be read from the file by using a Java
768     * {@code TiffImagingParameters} instance to specify the subsection of the image that
769     * is required. The following code illustrates the approach:
770     * <pre>
771     *   int x; // coordinate (column) of corner of sub-image
772     *   int y; // coordinate (row) of corner of sub-image
773     *   int width; // width of sub-image
774     *   int height; // height of sub-image
775     *
776     *   TiffImagingParameters params = new TiffImagingParameters();
777     *   params.setSubImageX(x);
778     *   params.setSubImageY(y);
779     *   params.setSubImageWidth(width);
780     *   params.setSubImageHeight(height);
781     *   TiffRasterData raster =
782     *        readFloatingPointRasterData(directory, byteOrder, params);
783     * </pre>
784     *
785     * @param directory the TIFF directory pointing to the data to be extracted
786     * (TIFF files may contain multiple directories)
787     * @param byteOrder the byte order of the data to be extracted
788     * @param params an optional parameter object instance
789     * @return a valid instance
790     * @throws ImageReadException in the event of incompatible or malformed data
791     * @throws IOException in the event of an I/O error
792     */
793    TiffRasterData getRasterData(
794            final TiffDirectory directory,
795            final ByteOrder byteOrder,
796            TiffImagingParameters params)
797            throws ImageReadException, IOException {
798        final List<TiffField> entries = directory.entries;
799
800        if (entries == null) {
801            throw new ImageReadException("TIFF missing entries");
802        }
803
804        if (params == null) {
805            params = this.getDefaultParameters();
806        }
807
808        final short[] sSampleFmt = directory.getFieldValue(
809                TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, true);
810        if (sSampleFmt == null || sSampleFmt.length < 1) {
811            throw new ImageReadException(
812                    "Directory does not specify numeric raster data");
813        }
814
815        int samplesPerPixel = 1;
816        final TiffField samplesPerPixelField = directory.findField(
817                TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL);
818        if (samplesPerPixelField != null) {
819            samplesPerPixel = samplesPerPixelField.getIntValue();
820        }
821
822        int[] bitsPerSample = {1};
823        int bitsPerPixel = samplesPerPixel;
824        final TiffField bitsPerSampleField = directory.findField(
825                TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE);
826        if (bitsPerSampleField != null) {
827            bitsPerSample = bitsPerSampleField.getIntArrayValue();
828            bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum();
829        }
830
831        final short compressionFieldValue;
832        if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) {
833            compressionFieldValue
834                    = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION);
835        } else {
836            compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1;
837        }
838        final int compression = 0xffff & compressionFieldValue;
839
840        final int width
841                = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH);
842        final int height
843                = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH);
844
845        Rectangle subImage = checkForSubImage(params);
846        if (subImage != null) {
847            // Check for valid subimage specification. The following checks
848            // are consistent with BufferedImage.getSubimage()
849            if (subImage.width <= 0) {
850                throw new ImageReadException("negative or zero subimage width");
851            }
852            if (subImage.height <= 0) {
853                throw new ImageReadException("negative or zero subimage height");
854            }
855            if (subImage.x < 0 || subImage.x >= width) {
856                throw new ImageReadException("subimage x is outside raster");
857            }
858            if (subImage.x + subImage.width > width) {
859                throw new ImageReadException("subimage (x+width) is outside raster");
860            }
861            if (subImage.y < 0 || subImage.y >= height) {
862                throw new ImageReadException("subimage y is outside raster");
863            }
864            if (subImage.y + subImage.height > height) {
865                throw new ImageReadException("subimage (y+height) is outside raster");
866            }
867
868            // if the subimage is just the same thing as the whole
869            // image, suppress the subimage processing
870            if (subImage.x == 0
871                    && subImage.y == 0
872                    && subImage.width == width
873                    && subImage.height == height) {
874                subImage = null;
875            }
876        }
877
878        // int bitsPerPixel = getTagAsValueOrArraySum(entries,
879        // TIFF_TAG_BITS_PER_SAMPLE);
880        int predictor = -1;
881        {
882            // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER);
883            // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS);
884            // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS);
885            // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION);
886            // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION);
887            final TiffField predictorField = directory.findField(
888                    TiffTagConstants.TIFF_TAG_PREDICTOR);
889            if (null != predictorField) {
890                predictor = predictorField.getIntValueOrArraySum();
891            }
892        }
893
894        // Obtain the planar configuration
895        final TiffField pcField = directory.findField(
896                TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION);
897        final TiffPlanarConfiguration planarConfiguration
898                = pcField == null
899                        ? TiffPlanarConfiguration.CHUNKY
900                        : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue());
901
902        if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) {
903            if (bitsPerSample[0] != 32 && bitsPerSample[0] != 64) {
904                throw new ImageReadException(
905                        "TIFF floating-point data uses unsupported bits-per-sample: "
906                        + bitsPerSample[0]);
907            }
908
909            if (predictor != -1
910                    && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
911                    && predictor != TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) {
912                throw new ImageReadException(
913                        "TIFF floating-point data uses unsupported horizontal-differencing predictor");
914            }
915        } else if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) {
916
917            if (samplesPerPixel != 1) {
918                throw new ImageReadException(
919                        "TIFF integer data uses unsupported samples per pixel: "
920                        + samplesPerPixel);
921            }
922
923            if (bitsPerPixel != 16 && bitsPerPixel != 32) {
924                throw new ImageReadException(
925                        "TIFF integer data uses unsupported bits-per-pixel: "
926                        + bitsPerPixel);
927            }
928
929            if (predictor != -1
930                    && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE
931                    && predictor != TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) {
932                throw new ImageReadException(
933                        "TIFF integer data uses unsupported horizontal-differencing predictor");
934            }
935        } else {
936            throw new ImageReadException("TIFF does not provide a supported raster-data format");
937        }
938
939        // The photometric interpreter is not used, but the image-based
940        // data reader classes require one.  So we create a dummy interpreter.
941        final PhotometricInterpreter photometricInterpreter
942                = new PhotometricInterpreterBiLevel(samplesPerPixel,
943                        bitsPerSample, predictor, width, height, false);
944
945        final TiffImageData imageData = directory.getTiffImageData();
946
947        final ImageDataReader dataReader = imageData.getDataReader(directory,
948                photometricInterpreter, bitsPerPixel, bitsPerSample, predictor,
949                samplesPerPixel, width, height, compression,
950                planarConfiguration, byteOrder);
951
952        return dataReader.readRasterData(subImage);
953    }
954
955}