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

Last change on this file since 11454 was 11288, checked in by simon04, 7 years ago

see #13376 - Use TimeUnit instead of combinations of 1000/60/60/24

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