    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}