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

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

fix many checkstyle violations

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