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}