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}