    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.jpeg;
    018
    019import java.awt.Dimension;
    020import java.awt.image.BufferedImage;
    021import java.io.ByteArrayInputStream;
    022import java.io.IOException;
    023import java.util.ArrayList;
    024import java.util.List;
    025
    026import javax.imageio.ImageIO;
    027
    028import org.apache.commons.imaging.ImageReadException;
    029import org.apache.commons.imaging.Imaging;
    030import org.apache.commons.imaging.ImagingException;
    031import org.apache.commons.imaging.common.ImageMetadata;
    032import org.apache.commons.imaging.formats.tiff.JpegImageData;
    033import org.apache.commons.imaging.formats.tiff.TiffField;
    034import org.apache.commons.imaging.formats.tiff.TiffImageData;
    035import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
    036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    037import org.apache.commons.imaging.internal.Debug;
    038
    039public class JpegImageMetadata implements ImageMetadata {
    040    private final JpegPhotoshopMetadata photoshop;
    041    private final TiffImageMetadata exif;
    042    private static final String NEWLINE = System.getProperty("line.separator");
    043
    044    public JpegImageMetadata(final JpegPhotoshopMetadata photoshop,
    045            final TiffImageMetadata exif) {
    046        this.photoshop = photoshop;
    047        this.exif = exif;
    048    }
    049
    050    public TiffImageMetadata getExif() {
    051        return exif;
    052    }
    053
    054    public JpegPhotoshopMetadata getPhotoshop() {
    055        return photoshop;
    056    }
    057
    058    public TiffField findEXIFValue(final TagInfo tagInfo) {
    059        try {
    060            return exif != null ? exif.findField(tagInfo) : null;
    061        } catch (final ImageReadException cannotHappen) {
    062            return null;
    063        }
    064    }
    065
    066    public TiffField findEXIFValueWithExactMatch(final TagInfo tagInfo) {
    067        try {
    068            return exif != null ? exif.findField(tagInfo, true) : null;
    069        } catch (final ImageReadException cannotHappen) {
    070            return null;
    071        }
    072    }
    073
    074    /**
    075     * Returns the size of the first JPEG thumbnail found in the EXIF metadata.
    076     *
    077     * @return Thumbnail width and height or null if no thumbnail.
    078     * @throws ImageReadException if it fails to read the image
    079     * @throws IOException if it fails to read the image size
    080     */
    081    public Dimension getEXIFThumbnailSize() throws ImageReadException,
    082            IOException {
    083        final byte[] data = getEXIFThumbnailData();
    084
    085        if (data != null) {
    086            return Imaging.getImageSize(data);
    087        }
    088        return null;
    089    }
    090
    091    /**
    092     * Returns the data of the first JPEG thumbnail found in the EXIF metadata.
    093     *
    094     * @return JPEG data or null if no thumbnail.
    095     * @throws ImageReadException if it fails to read the image
    096     * @throws IOException if an IO error occurred
    097     */
    098    public byte[] getEXIFThumbnailData() throws ImageReadException, IOException {
    099        if (exif == null) {
    100            return null;
    101        }
    102        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    103        for (final ImageMetadataItem d : dirs) {
    104            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    105
    106            byte[] data = null;
    107            if (dir.getJpegImageData() != null) {
    108                data = dir.getJpegImageData().getData();
    109            }
    110            // Support other image formats here.
    111
    112            if (data != null) {
    113                // already cloned, safe to return this copy
    114                return data;
    115            }
    116        }
    117        return null;
    118    }
    119
    120    /**
    121     * Get the thumbnail image if available.
    122     *
    123     * @return the thumbnail image. May be {@code null} if no image could
    124     *         be found.
    125     * @throws ImageReadException if it fails to read the image
    126     * @throws IOException if it fails to get the thumbnail or to read the image data
    127     */
    128    public BufferedImage getEXIFThumbnail() throws ImageReadException,
    129            IOException {
    130
    131        if (exif == null) {
    132            return null;
    133        }
    134
    135        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    136        for (final ImageMetadataItem d : dirs) {
    137            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    138            // Debug.debug("dir", dir);
    139            BufferedImage image = dir.getThumbnail();
    140            if (null != image) {
    141                return image;
    142            }
    143
    144            final JpegImageData jpegImageData = dir.getJpegImageData();
    145            if (jpegImageData != null) {
    146                // JPEG thumbnail as JPEG or other format; try to parse.
    147                boolean imageSucceeded = false;
    148                try {
    149                    image = Imaging.getBufferedImage(jpegImageData.getData());
    150                    imageSucceeded = true;
    151                } catch (final ImagingException imagingException) { // NOPMD
    152                } catch (final IOException ioException) { // NOPMD
    153                } finally {
    154                    // our JPEG reading is still a bit buggy -
    155                    // fall back to ImageIO on error
    156                    if (!imageSucceeded) {
    157                        final ByteArrayInputStream input = new ByteArrayInputStream(
    158                                jpegImageData.getData());
    159                        image = ImageIO.read(input);
    160                    }
    161                }
    162                if (image != null) {
    163                    return image;
    164                }
    165            }
    166        }
    167
    168        return null;
    169    }
    170
    171    public TiffImageData getRawImageData() {
    172        if (exif == null) {
    173            return null;
    174        }
    175        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    176        for (final ImageMetadataItem d : dirs) {
    177            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    178            // Debug.debug("dir", dir);
    179            final TiffImageData rawImageData = dir.getTiffImageData();
    180            if (null != rawImageData) {
    181                return rawImageData;
    182            }
    183        }
    184
    185        return null;
    186    }
    187
    188    @Override
    189    public List<ImageMetadataItem> getItems() {
    190        final List<ImageMetadataItem> result = new ArrayList<>();
    191
    192        if (null != exif) {
    193            result.addAll(exif.getItems());
    194        }
    195
    196        if (null != photoshop) {
    197            result.addAll(photoshop.getItems());
    198        }
    199
    200        return result;
    201    }
    202
    203    @Override
    204    public String toString() {
    205        return toString(null);
    206    }
    207
    208    @Override
    209    public String toString(String prefix) {
    210        if (prefix == null) {
    211            prefix = "";
    212        }
    213
    214        final StringBuilder result = new StringBuilder();
    215
    216        result.append(prefix);
    217        if (null == exif) {
    218            result.append("No Exif metadata.");
    219        } else {
    220            result.append("Exif metadata:");
    221            result.append(NEWLINE);
    222            result.append(exif.toString("\t"));
    223        }
    224
    225        // if (null != exif && null != photoshop)
    226        result.append(NEWLINE);
    227
    228        result.append(prefix);
    229        if (null == photoshop) {
    230            result.append("No Photoshop (IPTC) metadata.");
    231        } else {
    232            result.append("Photoshop (IPTC) metadata:");
    233            result.append(NEWLINE);
    234            result.append(photoshop.toString("\t"));
    235        }
    236
    237        return result.toString();
    238    }
    239
    240    public void dump() {
    241        Debug.debug(this.toString());
    242    }
    243
    244}