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

Last change on this file since 9383 was 9383, checked in by simon04, 3 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.