ExifInterface.java revision 9081aec61fede12049fa9adbad41a0b35813ed64
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import java.io.IOException;
20import java.text.ParsePosition;
21import java.text.SimpleDateFormat;
22import java.util.Date;
23import java.util.HashMap;
24import java.util.Map;
25
26/**
27 * This is a class for reading and writing Exif tags in a JPEG file.
28 */
29public class ExifInterface {
30
31    // The Exif tag names
32    public static final String TAG_ORIENTATION = "Orientation";
33    public static final String TAG_DATETIME = "DateTime";
34    public static final String TAG_MAKE = "Make";
35    public static final String TAG_MODEL = "Model";
36    public static final String TAG_FLASH = "Flash";
37    public static final String TAG_IMAGE_WIDTH = "ImageWidth";
38    public static final String TAG_IMAGE_LENGTH = "ImageLength";
39    public static final String TAG_GPS_LATITUDE = "GPSLatitude";
40    public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
41    public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
42    public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
43    public static final String TAG_WHITE_BALANCE = "WhiteBalance";
44
45    // Constants used for the Orientation Exif tag.
46    public static final int ORIENTATION_UNDEFINED = 0;
47    public static final int ORIENTATION_NORMAL = 1;
48    public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
49    public static final int ORIENTATION_ROTATE_180 = 3;
50    public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
51    public static final int ORIENTATION_TRANSPOSE = 5;  // flipped about top-left <--> bottom-right axis
52    public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
53    public static final int ORIENTATION_TRANSVERSE = 7;  // flipped about top-right <--> bottom-left axis
54    public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
55
56    // Constants used for white balance
57    public static final int WHITEBALANCE_AUTO = 0;
58    public static final int WHITEBALANCE_MANUAL = 1;
59
60    static {
61        System.loadLibrary("exif");
62    }
63
64    private String mFilename;
65    private HashMap<String, String> mAttributes;
66    private boolean mHasThumbnail = false;
67
68    // Because the underlying implementation (jhead) uses static variables,
69    // there can only be one user at a time for the native functions (and
70    // they cannot keep state in the native code across function calls). We
71    // use sLock the serialize the accesses.
72    private static Object sLock = new Object();
73
74    /**
75     * Reads Exif tags from the specified JPEG file.
76     */
77    public ExifInterface(String filename) throws IOException {
78        mFilename = filename;
79        loadAttributes();
80    }
81
82    /**
83     * Returns the value of the specified tag or {@code null} if there
84     * is no such tag in the file.
85     *
86     * @param tag the name of the tag.
87     */
88    public String getAttribute(String tag) {
89        return mAttributes.get(tag);
90    }
91
92    /**
93     * Set the value of the specified tag.
94     *
95     * @param tag the name of the tag.
96     * @param value the value of the tag.
97     */
98    public void setAttribute(String tag, String value) {
99        mAttributes.put(tag, value);
100    }
101
102    /**
103     * Initialize mAttributes with the attributes from the file mFilename.
104     *
105     * mAttributes is a HashMap which stores the Exif attributes of the file.
106     * The key is the standard tag name and the value is the tag's value: e.g.
107     * Model -> Nikon. Numeric values are stored as strings.
108     *
109     * This function also initialize mHasThumbnail to indicate whether the
110     * file has a thumbnail inside.
111     */
112    private void loadAttributes() {
113        // format of string passed from native C code:
114        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
115        // example:
116        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
117        mAttributes = new HashMap<String, String>();
118
119        String attrStr;
120        synchronized (sLock) {
121            attrStr = getAttributesNative(mFilename);
122        }
123
124        // get count
125        int ptr = attrStr.indexOf(' ');
126        int count = Integer.parseInt(attrStr.substring(0, ptr));
127        // skip past the space between item count and the rest of the attributes
128        ++ptr;
129
130        for (int i = 0; i < count; i++) {
131            // extract the attribute name
132            int equalPos = attrStr.indexOf('=', ptr);
133            String attrName = attrStr.substring(ptr, equalPos);
134            ptr = equalPos + 1;     // skip past =
135
136            // extract the attribute value length
137            int lenPos = attrStr.indexOf(' ', ptr);
138            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
139            ptr = lenPos + 1;       // skip pas the space
140
141            // extract the attribute value
142            String attrValue = attrStr.substring(ptr, ptr + attrLen);
143            ptr += attrLen;
144
145            if (attrName.equals("hasThumbnail")) {
146                mHasThumbnail = attrValue.equalsIgnoreCase("true");
147            } else {
148                mAttributes.put(attrName, attrValue);
149            }
150        }
151    }
152
153    /**
154     * Save the tag data into the JPEG file. This is expensive because it involves
155     * copying all the JPG data from one file to another and deleting the old file
156     * and renaming the other. It's best to use {@link #setAttribute(String,String)} to set all
157     * attributes to write and make a single call rather than multiple calls for
158     * each attribute.
159     */
160    public void saveAttributes() throws IOException {
161        // format of string passed to native C code:
162        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
163        // example:
164        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
165        StringBuilder sb = new StringBuilder();
166        int size = mAttributes.size();
167        if (mAttributes.containsKey("hasThumbnail")) {
168            --size;
169        }
170        sb.append(size + " ");
171        for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
172            String key = iter.getKey();
173            if (key.equals("hasThumbnail")) {
174                // this is a fake attribute not saved as an exif tag
175                continue;
176            }
177            String val = iter.getValue();
178            sb.append(key + "=");
179            sb.append(val.length() + " ");
180            sb.append(val);
181        }
182        String s = sb.toString();
183        synchronized (sLock) {
184            saveAttributesNative(mFilename, s);
185            commitChangesNative(mFilename);
186        }
187    }
188
189    /**
190     * Returns true if the JPEG file has a thumbnail.
191     */
192    public boolean hasThumbnail() {
193        return mHasThumbnail;
194    }
195
196    /**
197     * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
198     */
199    public byte[] getThumbnail() {
200        synchronized (sLock) {
201            return getThumbnailNative(mFilename);
202        }
203    }
204
205    /**
206     * Returns a human-readable string describing the white balance value. Returns empty
207     * string if there is no white balance value or it is not recognized.
208     */
209    public String getWhiteBalanceString() {
210        String value = getAttribute(TAG_WHITE_BALANCE);
211        if (value == null) return "";
212
213        int whitebalance;
214        try {
215            whitebalance = Integer.parseInt(value);
216        } catch (NumberFormatException ex) {
217            return "";
218        }
219
220        switch (whitebalance) {
221            case WHITEBALANCE_AUTO:
222                return "Auto";
223            case WHITEBALANCE_MANUAL:
224                return "Manual";
225            default:
226                return "";
227        }
228    }
229
230    /**
231     * Returns a human-readable string describing the orientation value. Returns empty
232     * string if there is no orientation value or it it not recognized.
233     */
234    public String getOrientationString() {
235        // TODO: this function needs to be localized.
236        String value = getAttribute(TAG_ORIENTATION);
237        if (value == null) return "";
238
239        int orientation;
240        try {
241            orientation = Integer.parseInt(value);
242        } catch (NumberFormatException ex) {
243            return "";
244        }
245
246        String orientationString;
247        switch (orientation) {
248            case ORIENTATION_NORMAL:
249                orientationString = "Normal";
250                break;
251            case ORIENTATION_FLIP_HORIZONTAL:
252                orientationString = "Flipped horizontal";
253                break;
254            case ORIENTATION_ROTATE_180:
255                orientationString = "Rotated 180 degrees";
256                break;
257            case ORIENTATION_FLIP_VERTICAL:
258                orientationString = "Upside down mirror";
259                break;
260            case ORIENTATION_TRANSPOSE:
261                orientationString = "Transposed";
262                break;
263            case ORIENTATION_ROTATE_90:
264                orientationString = "Rotated 90 degrees";
265                break;
266            case ORIENTATION_TRANSVERSE:
267                orientationString = "Transversed";
268                break;
269            case ORIENTATION_ROTATE_270:
270                orientationString = "Rotated 270 degrees";
271                break;
272            default:
273                orientationString = "Undefined";
274                break;
275        }
276        return orientationString;
277    }
278
279    /**
280     * Returns the latitude and longitude value in a float array. The first element is
281     * the latitude, and the second element is the longitude.
282     */
283    public float[] getLatLong() {
284        String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
285        String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
286        String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
287        String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
288        float[] latlng = null;
289
290        if (latValue != null && latRef != null
291                && lngValue != null && lngRef != null) {
292            latlng = new float[2];
293            latlng[0] = convertRationalLatLonToFloat(latValue, latRef);
294            latlng[1] = convertRationalLatLonToFloat(lngValue, lngRef);
295        }
296
297        return latlng;
298    }
299
300    private static SimpleDateFormat sFormatter =
301            new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
302
303    /**
304     * Returns number of milliseconds since Jan. 1, 1970, midnight GMT.
305     * Returns -1 if the date time information if not available.
306     */
307    public long getDateTime() {
308        String dateTimeString = mAttributes.get(TAG_DATETIME);
309        if (dateTimeString == null) return -1;
310
311        ParsePosition pos = new ParsePosition(0);
312        try {
313            Date date = sFormatter.parse(dateTimeString, pos);
314            if (date == null) return -1;
315            return date.getTime();
316        } catch (IllegalArgumentException ex) {
317            return -1;
318        }
319    }
320
321    private static float convertRationalLatLonToFloat(
322            String rationalString, String ref) {
323        try {
324            String [] parts = rationalString.split(",");
325
326            String [] pair;
327            pair = parts[0].split("/");
328            int degrees = (int) (Float.parseFloat(pair[0].trim())
329                    / Float.parseFloat(pair[1].trim()));
330
331            pair = parts[1].split("/");
332            int minutes = (int) ((Float.parseFloat(pair[0].trim())
333                    / Float.parseFloat(pair[1].trim())));
334
335            pair = parts[2].split("/");
336            float seconds = Float.parseFloat(pair[0].trim())
337                    / Float.parseFloat(pair[1].trim());
338
339            float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
340            if ((ref.equals("S") || ref.equals("W"))) {
341                return -result;
342            }
343            return result;
344        } catch (RuntimeException ex) {
345            // if for whatever reason we can't parse the lat long then return
346            // null
347            return 0f;
348        }
349    }
350
351    private native boolean appendThumbnailNative(String fileName,
352            String thumbnailFileName);
353
354    private native void saveAttributesNative(String fileName,
355            String compressedAttributes);
356
357    private native String getAttributesNative(String fileName);
358
359    private native void commitChangesNative(String fileName);
360
361    private native byte[] getThumbnailNative(String fileName);
362}
363