source: josm/trunk/src/org/openstreetmap/josm/tools/ExifReader.java@ 8148

Last change on this file since 8148 was 8132, checked in by Don-vip, 9 years ago

fix #11162 - update to metadata-extractor 2.7.2

  • Property svn:eol-style set to native
File size: 10.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.awt.geom.AffineTransform;
5import java.io.File;
6import java.io.IOException;
7import java.text.ParseException;
8import java.util.Date;
9
10import org.openstreetmap.josm.Main;
11import org.openstreetmap.josm.data.coor.LatLon;
12import org.openstreetmap.josm.tools.date.PrimaryDateParser;
13
14import com.drew.imaging.jpeg.JpegMetadataReader;
15import com.drew.imaging.jpeg.JpegProcessingException;
16import com.drew.lang.Rational;
17import com.drew.metadata.Directory;
18import com.drew.metadata.Metadata;
19import com.drew.metadata.MetadataException;
20import com.drew.metadata.Tag;
21import com.drew.metadata.exif.ExifIFD0Directory;
22import com.drew.metadata.exif.ExifSubIFDDirectory;
23import com.drew.metadata.exif.GpsDirectory;
24
25/**
26 * Read out EXIF information from a JPEG file
27 * @author Imi
28 * @since 99
29 */
30public final class ExifReader {
31
32 private ExifReader() {
33 // Hide default constructor for utils classes
34 }
35
36 /**
37 * Returns the date/time from the given JPEG file.
38 * @param filename The JPEG file to read
39 * @return The date/time read in the EXIF section, or {@code null} if not found
40 * @throws ParseException if {@link PrimaryDateParser#parse} fails to parse date/time
41 */
42 public static Date readTime(File filename) throws ParseException {
43 try {
44 Metadata metadata = JpegMetadataReader.readMetadata(filename);
45 String dateStr = null;
46 OUTER:
47 for (Directory dirIt : metadata.getDirectories()) {
48 for (Tag tag : dirIt.getTags()) {
49 if (tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL /* 0x9003 */) {
50 dateStr = tag.getDescription();
51 break OUTER; // prefer this tag
52 }
53 if (tag.getTagType() == ExifIFD0Directory.TAG_DATETIME /* 0x0132 */ ||
54 tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED /* 0x9004 */) {
55 dateStr = tag.getDescription();
56 }
57 }
58 }
59 if (dateStr != null) {
60 dateStr = dateStr.replace('/', ':'); // workaround for HTC Sensation bug, see #7228
61 return new PrimaryDateParser().parse(dateStr);
62 }
63 } catch (ParseException e) {
64 throw e;
65 } catch (Exception e) {
66 Main.error(e);
67 }
68 return null;
69 }
70
71 /**
72 * Returns the image orientation of the given JPEG file.
73 * @param filename The JPEG file to read
74 * @return The image orientation as an {@code int}. Default value is 1. Possible values are listed in EXIF spec as follows:<br><ol>
75 * <li>The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.</li>
76 * <li>The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.</li>
77 * <li>The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.</li>
78 * <li>The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.</li>
79 * <li>The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.</li>
80 * <li>The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.</li>
81 * <li>The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.</li>
82 * <li>The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.</li></ol>
83 * @see <a href="http://www.impulseadventure.com/photo/exif-orientation.html">http://www.impulseadventure.com/photo/exif-orientation.html</a>
84 * @see <a href="http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto">http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto</a>
85 */
86 public static Integer readOrientation(File filename) {
87 try {
88 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
89 final Directory dir = metadata.getDirectory(ExifIFD0Directory.class);
90 return dir.getInt(ExifIFD0Directory.TAG_ORIENTATION);
91 } catch (JpegProcessingException | MetadataException | IOException e) {
92 Main.error(e);
93 }
94 return null;
95 }
96
97 /**
98 * Returns the geolocation of the given JPEG file.
99 * @param filename The JPEG file to read
100 * @return The lat/lon read in the EXIF section, or {@code null} if not found
101 * @since 6209
102 */
103 public static LatLon readLatLon(File filename) {
104 try {
105 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
106 final GpsDirectory dirGps = metadata.getDirectory(GpsDirectory.class);
107 return readLatLon(dirGps);
108 } catch (JpegProcessingException e) {
109 Main.error(e);
110 } catch (IOException e) {
111 Main.error(e);
112 } catch (MetadataException e) {
113 Main.error(e);
114 }
115 return null;
116 }
117
118 /**
119 * Returns the geolocation of the given EXIF GPS directory.
120 * @param dirGps The EXIF GPS directory
121 * @return The lat/lon read in the EXIF section, or {@code null} if {@code dirGps} is null
122 * @throws MetadataException
123 * @since 6209
124 */
125 public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException {
126 if (dirGps != null) {
127 double lat = readAxis(dirGps, GpsDirectory.TAG_LATITUDE, GpsDirectory.TAG_LATITUDE_REF, 'S');
128 double lon = readAxis(dirGps, GpsDirectory.TAG_LONGITUDE, GpsDirectory.TAG_LONGITUDE_REF, 'W');
129 return new LatLon(lat, lon);
130 }
131 return null;
132 }
133
134 /**
135 * Returns the direction of the given JPEG file.
136 * @param filename The JPEG file to read
137 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99), or {@code null} if missing or if {@code dirGps} is null
138 * @since 6209
139 */
140 public static Double readDirection(File filename) {
141 try {
142 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
143 final GpsDirectory dirGps = metadata.getDirectory(GpsDirectory.class);
144 return readDirection(dirGps);
145 } catch (JpegProcessingException e) {
146 Main.error(e);
147 } catch (IOException e) {
148 Main.error(e);
149 }
150 return null;
151 }
152
153 /**
154 * Returns the direction of the given EXIF GPS directory.
155 * @param dirGps The EXIF GPS directory
156 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99), or {@code null} if missing or if {@code dirGps} is null
157 * @since 6209
158 */
159 public static Double readDirection(GpsDirectory dirGps) {
160 if (dirGps != null) {
161 Rational direction = dirGps.getRational(GpsDirectory.TAG_IMG_DIRECTION);
162 if (direction != null) {
163 return direction.doubleValue();
164 }
165 }
166 return null;
167 }
168
169 private static double readAxis(GpsDirectory dirGps, int gpsTag, int gpsTagRef, char cRef) throws MetadataException {
170 double value;
171 Rational[] components = dirGps.getRationalArray(gpsTag);
172 if (components != null) {
173 double deg = components[0].doubleValue();
174 double min = components[1].doubleValue();
175 double sec = components[2].doubleValue();
176
177 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
178 throw new IllegalArgumentException("deg, min and sec are NaN");
179
180 value = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
181
182 if (dirGps.getString(gpsTagRef).charAt(0) == cRef) {
183 value = -value;
184 }
185 } else {
186 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220)
187 value = dirGps.getDouble(gpsTag);
188 }
189 return value;
190 }
191
192 /**
193 * Returns a Transform that fixes the image orientation.
194 *
195 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
196 * as 1.
197 * @param orientation the exif-orientation of the image
198 * @param width the original width of the image
199 * @param height the original height of the image
200 * @return a transform that rotates the image, so it is upright
201 */
202 public static AffineTransform getRestoreOrientationTransform(final int orientation, final int width, final int height) {
203 final int q;
204 final double ax, ay;
205 switch (orientation) {
206 case 8:
207 q = -1;
208 ax = width / 2;
209 ay = width / 2;
210 break;
211 case 3:
212 q = 2;
213 ax = width / 2;
214 ay = height / 2;
215 break;
216 case 6:
217 q = 1;
218 ax = height / 2;
219 ay = height / 2;
220 break;
221 default:
222 q = 0;
223 ax = 0;
224 ay = 0;
225 }
226 return AffineTransform.getQuadrantRotateInstance(q, ax, ay);
227 }
228
229 /**
230 * Check, if the given orientation switches width and height of the image.
231 * E.g. 90 degree rotation
232 *
233 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
234 * as 1.
235 * @param orientation the exif-orientation of the image
236 * @return true, if it switches width and height
237 */
238 public static boolean orientationSwitchesDimensions(int orientation) {
239 return orientation == 6 || orientation == 8;
240 }
241
242 /**
243 * Check, if the given orientation requires any correction to the image.
244 *
245 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
246 * as 1.
247 * @param orientation the exif-orientation of the image
248 * @return true, unless the orientation value is 1 or unsupported.
249 */
250 public static boolean orientationNeedsCorrection(int orientation) {
251 return orientation == 3 || orientation == 6 || orientation == 8;
252 }
253}
Note: See TracBrowser for help on using the repository browser.