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