• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.media;
18 
19 import java.io.IOException;
20 import java.text.ParsePosition;
21 import java.text.SimpleDateFormat;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.TimeZone;
26 
27 /**
28  * This is a class for reading and writing Exif tags in a JPEG file.
29  */
30 public class ExifInterface {
31     // The Exif tag names
32     /** Type is int. */
33     public static final String TAG_ORIENTATION = "Orientation";
34     /** Type is String. */
35     public static final String TAG_DATETIME = "DateTime";
36     /** Type is String. */
37     public static final String TAG_MAKE = "Make";
38     /** Type is String. */
39     public static final String TAG_MODEL = "Model";
40     /** Type is int. */
41     public static final String TAG_FLASH = "Flash";
42     /** Type is int. */
43     public static final String TAG_IMAGE_WIDTH = "ImageWidth";
44     /** Type is int. */
45     public static final String TAG_IMAGE_LENGTH = "ImageLength";
46     /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
47     public static final String TAG_GPS_LATITUDE = "GPSLatitude";
48     /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */
49     public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
50     /** Type is String. */
51     public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
52     /** Type is String. */
53     public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
54     /** Type is String. */
55     public static final String TAG_EXPOSURE_TIME = "ExposureTime";
56     /** Type is String. */
57     public static final String TAG_APERTURE = "FNumber";
58     /** Type is String. */
59     public static final String TAG_ISO = "ISOSpeedRatings";
60 
61     /**
62      * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
63      * Type is rational.
64      */
65     public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
66 
67     /**
68      * 0 if the altitude is above sea level. 1 if the altitude is below sea
69      * level. Type is int.
70      */
71     public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
72 
73     /** Type is String. */
74     public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
75     /** Type is String. */
76     public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
77     /** Type is int. */
78     public static final String TAG_WHITE_BALANCE = "WhiteBalance";
79     /** Type is rational. */
80     public static final String TAG_FOCAL_LENGTH = "FocalLength";
81     /** Type is String. Name of GPS processing method used for location finding. */
82     public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
83 
84     // Constants used for the Orientation Exif tag.
85     public static final int ORIENTATION_UNDEFINED = 0;
86     public static final int ORIENTATION_NORMAL = 1;
87     public static final int ORIENTATION_FLIP_HORIZONTAL = 2;  // left right reversed mirror
88     public static final int ORIENTATION_ROTATE_180 = 3;
89     public static final int ORIENTATION_FLIP_VERTICAL = 4;  // upside down mirror
90     public static final int ORIENTATION_TRANSPOSE = 5;  // flipped about top-left <--> bottom-right axis
91     public static final int ORIENTATION_ROTATE_90 = 6;  // rotate 90 cw to right it
92     public static final int ORIENTATION_TRANSVERSE = 7;  // flipped about top-right <--> bottom-left axis
93     public static final int ORIENTATION_ROTATE_270 = 8;  // rotate 270 to right it
94 
95     // Constants used for white balance
96     public static final int WHITEBALANCE_AUTO = 0;
97     public static final int WHITEBALANCE_MANUAL = 1;
98     private static SimpleDateFormat sFormatter;
99 
100     static {
101         System.loadLibrary("jhead_jni");
102         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
103         sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
104     }
105 
106     private String mFilename;
107     private HashMap<String, String> mAttributes;
108     private boolean mHasThumbnail;
109 
110     // Because the underlying implementation (jhead) uses static variables,
111     // there can only be one user at a time for the native functions (and
112     // they cannot keep state in the native code across function calls). We
113     // use sLock to serialize the accesses.
114     private static final Object sLock = new Object();
115 
116     /**
117      * Reads Exif tags from the specified JPEG file.
118      */
ExifInterface(String filename)119     public ExifInterface(String filename) throws IOException {
120         if (filename == null) {
121             throw new IllegalArgumentException("filename cannot be null");
122         }
123         mFilename = filename;
124         loadAttributes();
125     }
126 
127     /**
128      * Returns the value of the specified tag or {@code null} if there
129      * is no such tag in the JPEG file.
130      *
131      * @param tag the name of the tag.
132      */
getAttribute(String tag)133     public String getAttribute(String tag) {
134         return mAttributes.get(tag);
135     }
136 
137     /**
138      * Returns the integer value of the specified tag. If there is no such tag
139      * in the JPEG file or the value cannot be parsed as integer, return
140      * <var>defaultValue</var>.
141      *
142      * @param tag the name of the tag.
143      * @param defaultValue the value to return if the tag is not available.
144      */
getAttributeInt(String tag, int defaultValue)145     public int getAttributeInt(String tag, int defaultValue) {
146         String value = mAttributes.get(tag);
147         if (value == null) return defaultValue;
148         try {
149             return Integer.valueOf(value);
150         } catch (NumberFormatException ex) {
151             return defaultValue;
152         }
153     }
154 
155     /**
156      * Returns the double value of the specified rational tag. If there is no
157      * such tag in the JPEG file or the value cannot be parsed as double, return
158      * <var>defaultValue</var>.
159      *
160      * @param tag the name of the tag.
161      * @param defaultValue the value to return if the tag is not available.
162      */
getAttributeDouble(String tag, double defaultValue)163     public double getAttributeDouble(String tag, double defaultValue) {
164         String value = mAttributes.get(tag);
165         if (value == null) return defaultValue;
166         try {
167             int index = value.indexOf("/");
168             if (index == -1) return defaultValue;
169             double denom = Double.parseDouble(value.substring(index + 1));
170             if (denom == 0) return defaultValue;
171             double num = Double.parseDouble(value.substring(0, index));
172             return num / denom;
173         } catch (NumberFormatException ex) {
174             return defaultValue;
175         }
176     }
177 
178     /**
179      * Set the value of the specified tag.
180      *
181      * @param tag the name of the tag.
182      * @param value the value of the tag.
183      */
setAttribute(String tag, String value)184     public void setAttribute(String tag, String value) {
185         mAttributes.put(tag, value);
186     }
187 
188     /**
189      * Initialize mAttributes with the attributes from the file mFilename.
190      *
191      * mAttributes is a HashMap which stores the Exif attributes of the file.
192      * The key is the standard tag name and the value is the tag's value: e.g.
193      * Model -> Nikon. Numeric values are stored as strings.
194      *
195      * This function also initialize mHasThumbnail to indicate whether the
196      * file has a thumbnail inside.
197      */
loadAttributes()198     private void loadAttributes() throws IOException {
199         // format of string passed from native C code:
200         // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
201         // example:
202         // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
203         mAttributes = new HashMap<String, String>();
204 
205         String attrStr;
206         synchronized (sLock) {
207             attrStr = getAttributesNative(mFilename);
208         }
209 
210         // get count
211         int ptr = attrStr.indexOf(' ');
212         int count = Integer.parseInt(attrStr.substring(0, ptr));
213         // skip past the space between item count and the rest of the attributes
214         ++ptr;
215 
216         for (int i = 0; i < count; i++) {
217             // extract the attribute name
218             int equalPos = attrStr.indexOf('=', ptr);
219             String attrName = attrStr.substring(ptr, equalPos);
220             ptr = equalPos + 1;     // skip past =
221 
222             // extract the attribute value length
223             int lenPos = attrStr.indexOf(' ', ptr);
224             int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
225             ptr = lenPos + 1;       // skip pas the space
226 
227             // extract the attribute value
228             String attrValue = attrStr.substring(ptr, ptr + attrLen);
229             ptr += attrLen;
230 
231             if (attrName.equals("hasThumbnail")) {
232                 mHasThumbnail = attrValue.equalsIgnoreCase("true");
233             } else {
234                 mAttributes.put(attrName, attrValue);
235             }
236         }
237     }
238 
239     /**
240      * Save the tag data into the JPEG file. This is expensive because it involves
241      * copying all the JPG data from one file to another and deleting the old file
242      * and renaming the other. It's best to use {@link #setAttribute(String,String)}
243      * to set all attributes to write and make a single call rather than multiple
244      * calls for each attribute.
245      */
saveAttributes()246     public void saveAttributes() throws IOException {
247         // format of string passed to native C code:
248         // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
249         // example:
250         // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
251         StringBuilder sb = new StringBuilder();
252         int size = mAttributes.size();
253         if (mAttributes.containsKey("hasThumbnail")) {
254             --size;
255         }
256         sb.append(size + " ");
257         for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
258             String key = iter.getKey();
259             if (key.equals("hasThumbnail")) {
260                 // this is a fake attribute not saved as an exif tag
261                 continue;
262             }
263             String val = iter.getValue();
264             sb.append(key + "=");
265             sb.append(val.length() + " ");
266             sb.append(val);
267         }
268         String s = sb.toString();
269         synchronized (sLock) {
270             saveAttributesNative(mFilename, s);
271             commitChangesNative(mFilename);
272         }
273     }
274 
275     /**
276      * Returns true if the JPEG file has a thumbnail.
277      */
hasThumbnail()278     public boolean hasThumbnail() {
279         return mHasThumbnail;
280     }
281 
282     /**
283      * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
284      * The returned data is in JPEG format and can be decoded using
285      * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
286      */
getThumbnail()287     public byte[] getThumbnail() {
288         synchronized (sLock) {
289             return getThumbnailNative(mFilename);
290         }
291     }
292 
293     /**
294      * Returns the offset and length of thumbnail inside the JPEG file, or
295      * {@code null} if there is no thumbnail.
296      *
297      * @return two-element array, the offset in the first value, and length in
298      *         the second, or {@code null} if no thumbnail was found.
299      * @hide
300      */
getThumbnailRange()301     public long[] getThumbnailRange() {
302         synchronized (sLock) {
303             return getThumbnailRangeNative(mFilename);
304         }
305     }
306 
307     /**
308      * Stores the latitude and longitude value in a float array. The first element is
309      * the latitude, and the second element is the longitude. Returns false if the
310      * Exif tags are not available.
311      */
getLatLong(float output[])312     public boolean getLatLong(float output[]) {
313         String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE);
314         String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF);
315         String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE);
316         String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
317 
318         if (latValue != null && latRef != null && lngValue != null && lngRef != null) {
319             try {
320                 output[0] = convertRationalLatLonToFloat(latValue, latRef);
321                 output[1] = convertRationalLatLonToFloat(lngValue, lngRef);
322                 return true;
323             } catch (IllegalArgumentException e) {
324                 // if values are not parseable
325             }
326         }
327 
328         return false;
329     }
330 
331     /**
332      * Return the altitude in meters. If the exif tag does not exist, return
333      * <var>defaultValue</var>.
334      *
335      * @param defaultValue the value to return if the tag is not available.
336      */
getAltitude(double defaultValue)337     public double getAltitude(double defaultValue) {
338         double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1);
339         int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1);
340 
341         if (altitude >= 0 && ref >= 0) {
342             return (double) (altitude * ((ref == 1) ? -1 : 1));
343         } else {
344             return defaultValue;
345         }
346     }
347 
348     /**
349      * Returns number of milliseconds since Jan. 1, 1970, midnight.
350      * Returns -1 if the date time information if not available.
351      * @hide
352      */
getDateTime()353     public long getDateTime() {
354         String dateTimeString = mAttributes.get(TAG_DATETIME);
355         if (dateTimeString == null) return -1;
356 
357         ParsePosition pos = new ParsePosition(0);
358         try {
359             Date datetime = sFormatter.parse(dateTimeString, pos);
360             if (datetime == null) return -1;
361             return datetime.getTime();
362         } catch (IllegalArgumentException ex) {
363             return -1;
364         }
365     }
366 
367     /**
368      * Returns number of milliseconds since Jan. 1, 1970, midnight UTC.
369      * Returns -1 if the date time information if not available.
370      * @hide
371      */
getGpsDateTime()372     public long getGpsDateTime() {
373         String date = mAttributes.get(TAG_GPS_DATESTAMP);
374         String time = mAttributes.get(TAG_GPS_TIMESTAMP);
375         if (date == null || time == null) return -1;
376 
377         String dateTimeString = date + ' ' + time;
378         if (dateTimeString == null) return -1;
379 
380         ParsePosition pos = new ParsePosition(0);
381         try {
382             Date datetime = sFormatter.parse(dateTimeString, pos);
383             if (datetime == null) return -1;
384             return datetime.getTime();
385         } catch (IllegalArgumentException ex) {
386             return -1;
387         }
388     }
389 
convertRationalLatLonToFloat( String rationalString, String ref)390     private static float convertRationalLatLonToFloat(
391             String rationalString, String ref) {
392         try {
393             String [] parts = rationalString.split(",");
394 
395             String [] pair;
396             pair = parts[0].split("/");
397             double degrees = Double.parseDouble(pair[0].trim())
398                     / Double.parseDouble(pair[1].trim());
399 
400             pair = parts[1].split("/");
401             double minutes = Double.parseDouble(pair[0].trim())
402                     / Double.parseDouble(pair[1].trim());
403 
404             pair = parts[2].split("/");
405             double seconds = Double.parseDouble(pair[0].trim())
406                     / Double.parseDouble(pair[1].trim());
407 
408             double result = degrees + (minutes / 60.0) + (seconds / 3600.0);
409             if ((ref.equals("S") || ref.equals("W"))) {
410                 return (float) -result;
411             }
412             return (float) result;
413         } catch (NumberFormatException e) {
414             // Some of the nubmers are not valid
415             throw new IllegalArgumentException();
416         } catch (ArrayIndexOutOfBoundsException e) {
417             // Some of the rational does not follow the correct format
418             throw new IllegalArgumentException();
419         }
420     }
421 
appendThumbnailNative(String fileName, String thumbnailFileName)422     private native boolean appendThumbnailNative(String fileName,
423             String thumbnailFileName);
424 
saveAttributesNative(String fileName, String compressedAttributes)425     private native void saveAttributesNative(String fileName,
426             String compressedAttributes);
427 
getAttributesNative(String fileName)428     private native String getAttributesNative(String fileName);
429 
commitChangesNative(String fileName)430     private native void commitChangesNative(String fileName);
431 
getThumbnailNative(String fileName)432     private native byte[] getThumbnailNative(String fileName);
433 
getThumbnailRangeNative(String fileName)434     private native long[] getThumbnailRangeNative(String fileName);
435 }
436