ExifInterface.java revision 29e4a3c566f435c32f0b95e4ac8e8b33cac6faba
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;
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 to 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 JPEG 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     * Returns the integer value of the specified tag. If there is no such tag
94     * in the JPEG file or the value cannot be parsed as integer, return
95     * <var>defaultValue</var>.
96     *
97     * @param tag the name of the tag.
98     * @param defaultValue the value to return if the tag is not available.
99     */
100    public int getAttributeInt(String tag, int defaultValue) {
101        String value = mAttributes.get(tag);
102        if (value == null) return defaultValue;
103        try {
104            return Integer.valueOf(value);
105        } catch (NumberFormatException ex) {
106            return defaultValue;
107        }
108    }
109
110    /**
111     * Set the value of the specified tag.
112     *
113     * @param tag the name of the tag.
114     * @param value the value of the tag.
115     */
116    public void setAttribute(String tag, String value) {
117        mAttributes.put(tag, value);
118    }
119
120    /**
121     * Initialize mAttributes with the attributes from the file mFilename.
122     *
123     * mAttributes is a HashMap which stores the Exif attributes of the file.
124     * The key is the standard tag name and the value is the tag's value: e.g.
125     * Model -> Nikon. Numeric values are stored as strings.
126     *
127     * This function also initialize mHasThumbnail to indicate whether the
128     * file has a thumbnail inside.
129     */
130    private void loadAttributes() throws IOException {
131        // format of string passed from native C code:
132        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
133        // example:
134        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
135        mAttributes = new HashMap<String, String>();
136
137        String attrStr;
138        synchronized (sLock) {
139            attrStr = getAttributesNative(mFilename);
140        }
141
142        // get count
143        int ptr = attrStr.indexOf(' ');
144        int count = Integer.parseInt(attrStr.substring(0, ptr));
145        // skip past the space between item count and the rest of the attributes
146        ++ptr;
147
148        for (int i = 0; i < count; i++) {
149            // extract the attribute name
150            int equalPos = attrStr.indexOf('=', ptr);
151            String attrName = attrStr.substring(ptr, equalPos);
152            ptr = equalPos + 1;     // skip past =
153
154            // extract the attribute value length
155            int lenPos = attrStr.indexOf(' ', ptr);
156            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
157            ptr = lenPos + 1;       // skip pas the space
158
159            // extract the attribute value
160            String attrValue = attrStr.substring(ptr, ptr + attrLen);
161            ptr += attrLen;
162
163            if (attrName.equals("hasThumbnail")) {
164                mHasThumbnail = attrValue.equalsIgnoreCase("true");
165            } else {
166                mAttributes.put(attrName, attrValue);
167            }
168        }
169    }
170
171    /**
172     * Save the tag data into the JPEG file. This is expensive because it involves
173     * copying all the JPG data from one file to another and deleting the old file
174     * and renaming the other. It's best to use {@link #setAttribute(String,String)}
175     * to set all attributes to write and make a single call rather than multiple
176     * calls for each attribute.
177     */
178    public void saveAttributes() throws IOException {
179        // format of string passed to native C code:
180        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
181        // example:
182        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
183        StringBuilder sb = new StringBuilder();
184        int size = mAttributes.size();
185        if (mAttributes.containsKey("hasThumbnail")) {
186            --size;
187        }
188        sb.append(size + " ");
189        for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
190            String key = iter.getKey();
191            if (key.equals("hasThumbnail")) {
192                // this is a fake attribute not saved as an exif tag
193                continue;
194            }
195            String val = iter.getValue();
196            sb.append(key + "=");
197            sb.append(val.length() + " ");
198            sb.append(val);
199        }
200        String s = sb.toString();
201        synchronized (sLock) {
202            saveAttributesNative(mFilename, s);
203            commitChangesNative(mFilename);
204        }
205    }
206
207    /**
208     * Returns true if the JPEG file has a thumbnail.
209     */
210    public boolean hasThumbnail() {
211        return mHasThumbnail;
212    }
213
214    /**
215     * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
216     * The returned data is in JPEG format and can be decoded using
217     * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
218     */
219    public byte[] getThumbnail() {
220        synchronized (sLock) {
221            return getThumbnailNative(mFilename);
222        }
223    }
224
225    /**
226     * Stores the latitude and longitude value in a float array. The first element is
227     * the latitude, and the second element is the longitude. Returns false if the
228     * Exif tags are not available.
229     */
230    public boolean getLatLong(float output[]) {
231        String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
232        String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
233        String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
234        String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
235
236        if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
237            output[0] = convertRationalLatLonToFloat(latValue, latRef);
238            output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
239            return true;
240        } else {
241            return false;
242        }
243    }
244
245    private static SimpleDateFormat sFormatter =
246            new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
247
248    /**
249     * Returns number of milliseconds since Jan. 1, 1970, midnight GMT.
250     * Returns -1 if the date time information if not available.
251     * @hide
252     */
253    public long getDateTime() {
254        String dateTimeString = mAttributes.get(TAG_DATETIME);
255        if (dateTimeString == null) return -1;
256
257        ParsePosition pos = new ParsePosition(0);
258        try {
259            Date date = sFormatter.parse(dateTimeString, pos);
260            if (date == null) return -1;
261            return date.getTime();
262        } catch (IllegalArgumentException ex) {
263            return -1;
264        }
265    }
266
267    private static float convertRationalLatLonToFloat(
268            String rationalString, String ref) {
269        try {
270            String [] parts = rationalString.split(",");
271
272            String [] pair;
273            pair = parts[0].split("/");
274            int degrees = (int) (Float.parseFloat(pair[0].trim())
275                    / Float.parseFloat(pair[1].trim()));
276
277            pair = parts[1].split("/");
278            int minutes = (int) ((Float.parseFloat(pair[0].trim())
279                    / Float.parseFloat(pair[1].trim())));
280
281            pair = parts[2].split("/");
282            float seconds = Float.parseFloat(pair[0].trim())
283                    / Float.parseFloat(pair[1].trim());
284
285            float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
286            if ((ref.equals("S") || ref.equals("W"))) {
287                return -result;
288            }
289            return result;
290        } catch (RuntimeException ex) {
291            // if for whatever reason we can't parse the lat long then return
292            // null
293            return 0f;
294        }
295    }
296
297    private native boolean appendThumbnailNative(String fileName,
298            String thumbnailFileName);
299
300    private native void saveAttributesNative(String fileName,
301            String compressedAttributes);
302
303    private native String getAttributesNative(String fileName);
304
305    private native void commitChangesNative(String fileName);
306
307    private native byte[] getThumbnailNative(String fileName);
308}
309