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

Last change on this file since 9383 was 9383, checked in by simon04, 8 years ago

Deprecate PrimaryDateParser in favour of DateUtils

  • Property svn:eol-style set to native
File size: 10.0 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.util.Date;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.data.coor.LatLon;
11import org.openstreetmap.josm.tools.date.DateUtils;
12
13import com.drew.imaging.jpeg.JpegMetadataReader;
14import com.drew.imaging.jpeg.JpegProcessingException;
15import com.drew.lang.Rational;
16import com.drew.metadata.Directory;
17import com.drew.metadata.Metadata;
18import com.drew.metadata.MetadataException;
19import com.drew.metadata.Tag;
20import com.drew.metadata.exif.ExifIFD0Directory;
21import com.drew.metadata.exif.ExifSubIFDDirectory;
22import com.drew.metadata.exif.GpsDirectory;
23
24/**
25 * Read out EXIF information from a JPEG file
26 * @author Imi
27 * @since 99
28 */
29public final class ExifReader {
30
31 private ExifReader() {
32 // Hide default constructor for utils classes
33 }
34
35 /**
36 * Returns the date/time from the given JPEG file.
37 * @param filename The JPEG file to read
38 * @return The date/time read in the EXIF section, or {@code null} if not found
39 */
40 public static Date readTime(File filename) {
41 try {
42 Metadata metadata = JpegMetadataReader.readMetadata(filename);
43 String dateStr = null;
44 OUTER:
45 for (Directory dirIt : metadata.getDirectories()) {
46 for (Tag tag : dirIt.getTags()) {
47 if (tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL /* 0x9003 */ &&
48 !tag.getDescription().matches("\\[[0-9]+ .+\\]")) {
49 dateStr = tag.getDescription();
50 break OUTER; // prefer this tag if known
51 }
52 if (tag.getTagType() == ExifIFD0Directory.TAG_DATETIME /* 0x0132 */ ||
53 tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED /* 0x9004 */) {
54 dateStr = tag.getDescription();
55 }
56 }
57 }
58 if (dateStr != null) {
59 dateStr = dateStr.replace('/', ':'); // workaround for HTC Sensation bug, see #7228
60 return DateUtils.fromString(dateStr);
61 }
62 } catch (Exception e) {
63 Main.error(e);
64 }
65 return null;
66 }
67
68 /**
69 * Returns the image orientation of the given JPEG file.
70 * @param filename The JPEG file to read
71 * @return The image orientation as an {@code int}. Default value is 1. Possible values are listed in EXIF spec as follows:<br><ol>
72 * <li>The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.</li>
73 * <li>The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.</li>
74 * <li>The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.</li>
75 * <li>The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.</li>
76 * <li>The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.</li>
77 * <li>The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.</li>
78 * <li>The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.</li>
79 * <li>The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.</li></ol>
80 * @see <a href="http://www.impulseadventure.com/photo/exif-orientation.html">http://www.impulseadventure.com/photo/exif-orientation.html</a>
81 * @see <a href="http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto">
82 * http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto</a>
83 */
84 public static Integer readOrientation(File filename) {
85 try {
86 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
87 final Directory dir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
88 return dir.getInt(ExifIFD0Directory.TAG_ORIENTATION);
89 } catch (JpegProcessingException | MetadataException | IOException e) {
90 Main.error(e);
91 }
92 return null;
93 }
94
95 /**
96 * Returns the geolocation of the given JPEG file.
97 * @param filename The JPEG file to read
98 * @return The lat/lon read in the EXIF section, or {@code null} if not found
99 * @since 6209
100 */
101 public static LatLon readLatLon(File filename) {
102 try {
103 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
104 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
105 return readLatLon(dirGps);
106 } catch (JpegProcessingException e) {
107 Main.error(e);
108 } catch (IOException e) {
109 Main.error(e);
110 } catch (MetadataException e) {
111 Main.error(e);
112 }
113 return null;
114 }
115
116 /**
117 * Returns the geolocation of the given EXIF GPS directory.
118 * @param dirGps The EXIF GPS directory
119 * @return The lat/lon read in the EXIF section, or {@code null} if {@code dirGps} is null
120 * @throws MetadataException if invalid metadata is given
121 * @since 6209
122 */
123 public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException {
124 if (dirGps != null) {
125 double lat = readAxis(dirGps, GpsDirectory.TAG_LATITUDE, GpsDirectory.TAG_LATITUDE_REF, 'S');
126 double lon = readAxis(dirGps, GpsDirectory.TAG_LONGITUDE, GpsDirectory.TAG_LONGITUDE_REF, 'W');
127 return new LatLon(lat, lon);
128 }
129 return null;
130 }
131
132 /**
133 * Returns the direction of the given JPEG file.
134 * @param filename The JPEG file to read
135 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99),
136 * or {@code null} if missing or if {@code dirGps} is null
137 * @since 6209
138 */
139 public static Double readDirection(File filename) {
140 try {
141 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
142 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
143 return readDirection(dirGps);
144 } catch (JpegProcessingException e) {
145 Main.error(e);
146 } catch (IOException e) {
147 Main.error(e);
148 }
149 return null;
150 }
151
152 /**
153 * Returns the direction of the given EXIF GPS directory.
154 * @param dirGps The EXIF GPS directory
155 * @return The direction of the image when it was captures (in degrees between 0.0 and 359.99),
156 * 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 / 2d;
209 ay = width / 2d;
210 break;
211 case 3:
212 q = 2;
213 ax = width / 2d;
214 ay = height / 2d;
215 break;
216 case 6:
217 q = 1;
218 ax = height / 2d;
219 ay = height / 2d;
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.