ExifInterface.java revision 72b1f379d5c97c8ff31d2201e78215af777d6bda
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 android.util.Log;
20
21import java.util.HashMap;
22import java.util.Map;
23
24/**
25 * Wrapper for native Exif library
26 * {@hide}
27 */
28public class ExifInterface {
29    private static final String TAG = "ExifInterface";
30    private String mFilename;
31
32    // Constants used for the Orientation Exif tag.
33    public static final int ORIENTATION_UNDEFINED = 0;
34    public static final int ORIENTATION_NORMAL = 1;
35
36    // Constants used for white balance
37    public static final int WHITEBALANCE_AUTO = 0;
38    public static final int WHITEBALANCE_MANUAL = 1;
39
40    // left right reversed mirror
41    public static final int ORIENTATION_FLIP_HORIZONTAL = 2;
42    public static final int ORIENTATION_ROTATE_180 = 3;
43
44    // upside down mirror
45    public static final int ORIENTATION_FLIP_VERTICAL = 4;
46
47    // flipped about top-left <--> bottom-right axis
48    public static final int ORIENTATION_TRANSPOSE = 5;
49
50    // rotate 90 cw to right it
51    public static final int ORIENTATION_ROTATE_90 = 6;
52
53    // flipped about top-right <--> bottom-left axis
54    public static final int ORIENTATION_TRANSVERSE = 7;
55
56    // rotate 270 to right it
57    public static final int ORIENTATION_ROTATE_270 = 8;
58
59    // The Exif tag names
60    public static final String TAG_ORIENTATION = "Orientation";
61
62    public static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
63    public static final String TAG_MAKE = "Make";
64    public static final String TAG_MODEL = "Model";
65    public static final String TAG_FLASH = "Flash";
66    public static final String TAG_IMAGE_WIDTH = "ImageWidth";
67    public static final String TAG_IMAGE_LENGTH = "ImageLength";
68
69    public static final String TAG_GPS_LATITUDE = "GPSLatitude";
70    public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
71
72    public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
73    public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
74    public static final String TAG_WHITE_BALANCE = "WhiteBalance";
75
76    private boolean mSavedAttributes = false;
77    private boolean mHasThumbnail = false;
78    private HashMap<String, String> mCachedAttributes = null;
79
80    static {
81        System.loadLibrary("exif");
82    }
83
84    private static ExifInterface sExifObj = null;
85    /**
86     * Since the underlying jhead native code is not thread-safe,
87     * ExifInterface should use singleton interface instead of public
88     * constructor.
89     */
90    private static synchronized ExifInterface instance() {
91        if (sExifObj == null) {
92            sExifObj = new ExifInterface();
93        }
94
95        return sExifObj;
96    }
97
98    /**
99     * The following 3 static methods are handy routines for atomic operation
100     * of underlying jhead library. It retrieves EXIF data and then release
101     * ExifInterface immediately.
102     */
103    public static synchronized HashMap<String, String> loadExifData(String filename) {
104        ExifInterface exif = instance();
105        HashMap<String, String> exifData = null;
106        if (exif != null) {
107            exif.setFilename(filename);
108            exifData = exif.getAttributes();
109        }
110        return exifData;
111    }
112
113    public static synchronized void saveExifData(String filename, HashMap<String, String> exifData) {
114        ExifInterface exif = instance();
115        if (exif != null) {
116            exif.setFilename(filename);
117            exif.saveAttributes(exifData);
118        }
119    }
120
121    public static synchronized byte[] getExifThumbnail(String filename) {
122        ExifInterface exif = instance();
123        if (exif != null) {
124            exif.setFilename(filename);
125            return exif.getThumbnail();
126        }
127        return null;
128    }
129
130    public void setFilename(String filename) {
131        if (mFilename == null || !mFilename.equals(filename)) {
132            mFilename = filename;
133            mCachedAttributes = null;
134        }
135    }
136
137    /**
138     * Given a HashMap of Exif tags and associated values, an Exif section in
139     * the JPG file is created and loaded with the tag data. saveAttributes()
140     * is expensive because it involves copying all the JPG data from one file
141     * to another and deleting the old file and renaming the other. It's best
142     * to collect all the attributes to write and make a single call rather
143     * than multiple calls for each attribute. You must call "commitChanges()"
144     * at some point to commit the changes.
145     */
146    public void saveAttributes(HashMap<String, String> attributes) {
147        // format of string passed to native C code:
148        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
149        // example:
150        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
151        StringBuilder sb = new StringBuilder();
152        int size = attributes.size();
153        if (attributes.containsKey("hasThumbnail")) {
154            --size;
155        }
156        sb.append(size + " ");
157        for (Map.Entry<String, String> iter : attributes.entrySet()) {
158            String key = iter.getKey();
159            if (key.equals("hasThumbnail")) {
160                // this is a fake attribute not saved as an exif tag
161                continue;
162            }
163            String val = iter.getValue();
164            sb.append(key + "=");
165            sb.append(val.length() + " ");
166            sb.append(val);
167        }
168        String s = sb.toString();
169        saveAttributesNative(mFilename, s);
170        commitChangesNative(mFilename);
171        mSavedAttributes = true;
172    }
173
174    /**
175     * Returns a HashMap loaded with the Exif attributes of the file. The key
176     * is the standard tag name and the value is the tag's value: e.g.
177     * Model -> Nikon. Numeric values are returned as strings.
178     */
179    public HashMap<String, String> getAttributes() {
180        if (mCachedAttributes != null) {
181            return mCachedAttributes;
182        }
183        // format of string passed from native C code:
184        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
185        // example:
186        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
187        mCachedAttributes = new HashMap<String, String>();
188
189        String attrStr = getAttributesNative(mFilename);
190
191        // get count
192        int ptr = attrStr.indexOf(' ');
193        int count = Integer.parseInt(attrStr.substring(0, ptr));
194        // skip past the space between item count and the rest of the attributes
195        ++ptr;
196
197        for (int i = 0; i < count; i++) {
198            // extract the attribute name
199            int equalPos = attrStr.indexOf('=', ptr);
200            String attrName = attrStr.substring(ptr, equalPos);
201            ptr = equalPos + 1;     // skip past =
202
203            // extract the attribute value length
204            int lenPos = attrStr.indexOf(' ', ptr);
205            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
206            ptr = lenPos + 1;       // skip pas the space
207
208            // extract the attribute value
209            String attrValue = attrStr.substring(ptr, ptr + attrLen);
210            ptr += attrLen;
211
212            if (attrName.equals("hasThumbnail")) {
213                mHasThumbnail = attrValue.equalsIgnoreCase("true");
214            } else {
215                mCachedAttributes.put(attrName, attrValue);
216            }
217        }
218        return mCachedAttributes;
219    }
220
221    /**
222     * Given a numerical white balance value, return a
223     * human-readable string describing it.
224     */
225    public static String whiteBalanceToString(int whitebalance) {
226        switch (whitebalance) {
227            case WHITEBALANCE_AUTO:
228                return "Auto";
229            case WHITEBALANCE_MANUAL:
230                return "Manual";
231            default:
232                return "";
233        }
234    }
235
236    /**
237     * Given a numerical orientation, return a human-readable string describing
238     * the orientation.
239     */
240    public static String orientationToString(int orientation) {
241        // TODO: this function needs to be localized and use string resource ids
242        // rather than strings
243        String orientationString;
244        switch (orientation) {
245            case ORIENTATION_NORMAL:
246                orientationString = "Normal";
247                break;
248            case ORIENTATION_FLIP_HORIZONTAL:
249                orientationString = "Flipped horizontal";
250                break;
251            case ORIENTATION_ROTATE_180:
252                orientationString = "Rotated 180 degrees";
253                break;
254            case ORIENTATION_FLIP_VERTICAL:
255                orientationString = "Upside down mirror";
256                break;
257            case ORIENTATION_TRANSPOSE:
258                orientationString = "Transposed";
259                break;
260            case ORIENTATION_ROTATE_90:
261                orientationString = "Rotated 90 degrees";
262                break;
263            case ORIENTATION_TRANSVERSE:
264                orientationString = "Transversed";
265                break;
266            case ORIENTATION_ROTATE_270:
267                orientationString = "Rotated 270 degrees";
268                break;
269            default:
270                orientationString = "Undefined";
271                break;
272        }
273        return orientationString;
274    }
275
276    /**
277     * Copies the thumbnail data out of the filename and puts it in the Exif
278     * data associated with the file used to create this object. You must call
279     * "commitChanges()" at some point to commit the changes.
280     */
281    public boolean appendThumbnail(String thumbnailFileName) {
282        if (!mSavedAttributes) {
283            throw new RuntimeException("Must call saveAttributes "
284                    + "before calling appendThumbnail");
285        }
286        mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
287        return mHasThumbnail;
288    }
289
290    public boolean hasThumbnail() {
291        if (!mSavedAttributes) {
292            getAttributes();
293        }
294        return mHasThumbnail;
295    }
296
297    public byte[] getThumbnail() {
298        return getThumbnailNative(mFilename);
299    }
300
301    public static float[] getLatLng(HashMap<String, String> exifData) {
302        if (exifData == null) {
303            return null;
304        }
305
306        String latValue = exifData.get(ExifInterface.TAG_GPS_LATITUDE);
307        String latRef = exifData.get(ExifInterface.TAG_GPS_LATITUDE_REF);
308        String lngValue = exifData.get(ExifInterface.TAG_GPS_LONGITUDE);
309        String lngRef = exifData.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
310        float[] latlng = null;
311
312        if (latValue != null && latRef != null
313                && lngValue != null && lngRef != null) {
314            latlng = new float[2];
315            latlng[0] = ExifInterface.convertRationalLatLonToFloat(
316                    latValue, latRef);
317            latlng[1] = ExifInterface.convertRationalLatLonToFloat(
318                    lngValue, lngRef);
319        }
320
321        return latlng;
322    }
323
324    public static float convertRationalLatLonToFloat(
325            String rationalString, String ref) {
326        try {
327            String [] parts = rationalString.split(",");
328
329            String [] pair;
330            pair = parts[0].split("/");
331            int degrees = (int) (Float.parseFloat(pair[0].trim())
332                    / Float.parseFloat(pair[1].trim()));
333
334            pair = parts[1].split("/");
335            int minutes = (int) ((Float.parseFloat(pair[0].trim())
336                    / Float.parseFloat(pair[1].trim())));
337
338            pair = parts[2].split("/");
339            float seconds = Float.parseFloat(pair[0].trim())
340                    / Float.parseFloat(pair[1].trim());
341
342            float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
343            if ((ref.equals("S") || ref.equals("W"))) {
344                return -result;
345            }
346            return result;
347        } catch (RuntimeException ex) {
348            // if for whatever reason we can't parse the lat long then return
349            // null
350            return 0f;
351        }
352    }
353
354    public static String convertRationalLatLonToDecimalString(
355            String rationalString, String ref, boolean usePositiveNegative) {
356            float result = convertRationalLatLonToFloat(rationalString, ref);
357
358            String preliminaryResult = String.valueOf(result);
359            if (usePositiveNegative) {
360                String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
361                return neg + preliminaryResult;
362            } else {
363                return preliminaryResult + String.valueOf((char) 186) + " "
364                        + ref;
365            }
366    }
367
368    public static String makeLatLongString(double d) {
369        d = Math.abs(d);
370
371        int degrees = (int) d;
372
373        double remainder = d - degrees;
374        int minutes = (int) (remainder * 60D);
375        // really seconds * 1000
376        int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);
377
378        String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000";
379        return retVal;
380    }
381
382    public static String makeLatStringRef(double lat) {
383        return lat >= 0D ? "N" : "S";
384    }
385
386    public static String makeLonStringRef(double lon) {
387        return lon >= 0D ? "W" : "E";
388    }
389
390    private native boolean appendThumbnailNative(String fileName,
391            String thumbnailFileName);
392
393    private native void saveAttributesNative(String fileName,
394            String compressedAttributes);
395
396    private native String getAttributesNative(String fileName);
397
398    private native void commitChangesNative(String fileName);
399
400    private native byte[] getThumbnailNative(String fileName);
401}
402