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