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