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}