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}