    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     */
    096    public byte[] getEXIFThumbnailData() {
    097        if (exif == null) {
    098            return null;
    099        }
    100        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    101        for (final ImageMetadataItem d : dirs) {
    102            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    103
    104            byte[] data = null;
    105            if (dir.getJpegImageData() != null) {
    106                data = dir.getJpegImageData().getData();
    107            }
    108            // Support other image formats here.
    109
    110            if (data != null) {
    111                // already cloned, safe to return this copy
    112                return data;
    113            }
    114        }
    115        return null;
    116    }
    117
    118    /**
    119     * Get the thumbnail image if available.
    120     *
    121     * @return the thumbnail image. May be {@code null} if no image could
    122     *         be found.
    123     * @throws ImageReadException if it fails to read the image
    124     * @throws IOException if it fails to get the thumbnail or to read the image data
    125     */
    126    public BufferedImage getEXIFThumbnail() throws ImageReadException,
    127            IOException {
    128
    129        if (exif == null) {
    130            return null;
    131        }
    132
    133        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    134        for (final ImageMetadataItem d : dirs) {
    135            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    136            // Debug.debug("dir", dir);
    137            BufferedImage image = dir.getThumbnail();
    138            if (null != image) {
    139                return image;
    140            }
    141
    142            final JpegImageData jpegImageData = dir.getJpegImageData();
    143            if (jpegImageData != null) {
    144                // JPEG thumbnail as JPEG or other format; try to parse.
    145                boolean imageSucceeded = false;
    146                try {
    147                    image = Imaging.getBufferedImage(jpegImageData.getData());
    148                    imageSucceeded = true;
    149                } catch (final ImagingException | IOException ioException) { // NOPMD
    150                } finally {
    151                    // our JPEG reading is still a bit buggy -
    152                    // fall back to ImageIO on error
    153                    if (!imageSucceeded) {
    154                        final ByteArrayInputStream input = new ByteArrayInputStream(
    155                                jpegImageData.getData());
    156                        image = ImageIO.read(input);
    157                    }
    158                }
    159                if (image != null) {
    160                    return image;
    161                }
    162            }
    163        }
    164
    165        return null;
    166    }
    167
    168    public TiffImageData getRawImageData() {
    169        if (exif == null) {
    170            return null;
    171        }
    172        final List<? extends ImageMetadataItem> dirs = exif.getDirectories();
    173        for (final ImageMetadataItem d : dirs) {
    174            final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
    175            // Debug.debug("dir", dir);
    176            final TiffImageData rawImageData = dir.getTiffImageData();
    177            if (null != rawImageData) {
    178                return rawImageData;
    179            }
    180        }
    181
    182        return null;
    183    }
    184
    185    @Override
    186    public List<ImageMetadataItem> getItems() {
    187        final List<ImageMetadataItem> result = new ArrayList<>();
    188
    189        if (null != exif) {
    190            result.addAll(exif.getItems());
    191        }
    192
    193        if (null != photoshop) {
    194            result.addAll(photoshop.getItems());
    195        }
    196
    197        return result;
    198    }
    199
    200    @Override
    201    public String toString() {
    202        return toString(null);
    203    }
    204
    205    @Override
    206    public String toString(String prefix) {
    207        if (prefix == null) {
    208            prefix = "";
    209        }
    210
    211        final StringBuilder result = new StringBuilder();
    212
    213        result.append(prefix);
    214        if (null == exif) {
    215            result.append("No Exif metadata.");
    216        } else {
    217            result.append("Exif metadata:");
    218            result.append(NEWLINE);
    219            result.append(exif.toString("\t"));
    220        }
    221
    222        // if (null != exif && null != photoshop)
    223        result.append(NEWLINE);
    224
    225        result.append(prefix);
    226        if (null == photoshop) {
    227            result.append("No Photoshop (IPTC) metadata.");
    228        } else {
    229            result.append("Photoshop (IPTC) metadata:");
    230            result.append(NEWLINE);
    231            result.append(photoshop.toString("\t"));
    232        }
    233
    234        return result.toString();
    235    }
    236
    237    public void dump() {
    238        Debug.debug(this.toString());
    239    }
    240
    241}