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}