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