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

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

fix Findbugs violation - ICAST: Integral division result cast to double

  • 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">http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto</a>
86 */
87 public static Integer readOrientation(File filename) {
88 try {
89 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
90 final Directory dir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
91 return dir.getInt(ExifIFD0Directory.TAG_ORIENTATION);
92 } catch (JpegProcessingException | MetadataException | IOException e) {
93 Main.error(e);
94 }
95 return null;
96 }
97
98 /**
99 * Returns the geolocation of the given JPEG file.
100 * @param filename The JPEG file to read
101 * @return The lat/lon read in the EXIF section, or {@code null} if not found
102 * @since 6209
103 */
104 public static LatLon readLatLon(File filename) {
105 try {
106 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
107 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
108 return readLatLon(dirGps);
109 } catch (JpegProcessingException e) {
110 Main.error(e);
111 } catch (IOException e) {
112 Main.error(e);
113 } catch (MetadataException e) {
114 Main.error(e);
115 }
116 return null;
117 }
118
119 /**
120 * Returns the geolocation of the given EXIF GPS directory.
121 * @param dirGps The EXIF GPS directory
122 * @return The lat/lon read in the EXIF section, or {@code null} if {@code dirGps} is null
123 * @throws MetadataException
124 * @since 6209
125 */
126 public static LatLon readLatLon(GpsDirectory dirGps) throws MetadataException {
127 if (dirGps != null) {
128 double lat = readAxis(dirGps, GpsDirectory.TAG_LATITUDE, GpsDirectory.TAG_LATITUDE_REF, 'S');
129 double lon = readAxis(dirGps, GpsDirectory.TAG_LONGITUDE, GpsDirectory.TAG_LONGITUDE_REF, 'W');
130 return new LatLon(lat, lon);
131 }
132 return null;
133 }
134
135 /**
136 * Returns the direction of the given JPEG file.
137 * @param filename The JPEG file to read
138 * @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
139 * @since 6209
140 */
141 public static Double readDirection(File filename) {
142 try {
143 final Metadata metadata = JpegMetadataReader.readMetadata(filename);
144 final GpsDirectory dirGps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
145 return readDirection(dirGps);
146 } catch (JpegProcessingException e) {
147 Main.error(e);
148 } catch (IOException e) {
149 Main.error(e);
150 }
151 return null;
152 }
153
154 /**
155 * Returns the direction of the given EXIF GPS directory.
156 * @param dirGps The EXIF GPS directory
157 * @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
158 * @since 6209
159 */
160 public static Double readDirection(GpsDirectory dirGps) {
161 if (dirGps != null) {
162 Rational direction = dirGps.getRational(GpsDirectory.TAG_IMG_DIRECTION);
163 if (direction != null) {
164 return direction.doubleValue();
165 }
166 }
167 return null;
168 }
169
170 private static double readAxis(GpsDirectory dirGps, int gpsTag, int gpsTagRef, char cRef) throws MetadataException {
171 double value;
172 Rational[] components = dirGps.getRationalArray(gpsTag);
173 if (components != null) {
174 double deg = components[0].doubleValue();
175 double min = components[1].doubleValue();
176 double sec = components[2].doubleValue();
177
178 if (Double.isNaN(deg) && Double.isNaN(min) && Double.isNaN(sec))
179 throw new IllegalArgumentException("deg, min and sec are NaN");
180
181 value = (Double.isNaN(deg) ? 0 : deg + (Double.isNaN(min) ? 0 : (min / 60)) + (Double.isNaN(sec) ? 0 : (sec / 3600)));
182
183 if (dirGps.getString(gpsTagRef).charAt(0) == cRef) {
184 value = -value;
185 }
186 } else {
187 // Try to read lon/lat as double value (Nonstandard, created by some cameras -> #5220)
188 value = dirGps.getDouble(gpsTag);
189 }
190 return value;
191 }
192
193 /**
194 * Returns a Transform that fixes the image orientation.
195 *
196 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
197 * as 1.
198 * @param orientation the exif-orientation of the image
199 * @param width the original width of the image
200 * @param height the original height of the image
201 * @return a transform that rotates the image, so it is upright
202 */
203 public static AffineTransform getRestoreOrientationTransform(final int orientation, final int width, final int height) {
204 final int q;
205 final double ax, ay;
206 switch (orientation) {
207 case 8:
208 q = -1;
209 ax = width / 2d;
210 ay = width / 2d;
211 break;
212 case 3:
213 q = 2;
214 ax = width / 2d;
215 ay = height / 2d;
216 break;
217 case 6:
218 q = 1;
219 ax = height / 2d;
220 ay = height / 2d;
221 break;
222 default:
223 q = 0;
224 ax = 0;
225 ay = 0;
226 }
227 return AffineTransform.getQuadrantRotateInstance(q, ax, ay);
228 }
229
230 /**
231 * Check, if the given orientation switches width and height of the image.
232 * E.g. 90 degree rotation
233 *
234 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
235 * as 1.
236 * @param orientation the exif-orientation of the image
237 * @return true, if it switches width and height
238 */
239 public static boolean orientationSwitchesDimensions(int orientation) {
240 return orientation == 6 || orientation == 8;
241 }
242
243 /**
244 * Check, if the given orientation requires any correction to the image.
245 *
246 * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated
247 * as 1.
248 * @param orientation the exif-orientation of the image
249 * @return true, unless the orientation value is 1 or unsupported.
250 */
251 public static boolean orientationNeedsCorrection(int orientation) {
252 return orientation == 3 || orientation == 6 || orientation == 8;
253 }
254}
Note: See TracBrowser for help on using the repository browser.