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("exif_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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 287 public byte[] getThumbnail() { 288 synchronized (sLock) { 289 return getThumbnailNative(mFilename); 290 } 291 } 292 293 /** 294 * Stores the latitude and longitude value in a float array. The first element is 295 * the latitude, and the second element is the longitude. Returns false if the 296 * Exif tags are not available. 297 */ 298 public boolean getLatLong(float output[]) { 299 String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE); 300 String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF); 301 String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE); 302 String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF); 303 304 if (latValue != null && latRef != null && lngValue != null && lngRef != null) { 305 try { 306 output[0] = convertRationalLatLonToFloat(latValue, latRef); 307 output[1] = convertRationalLatLonToFloat(lngValue, lngRef); 308 return true; 309 } catch (IllegalArgumentException e) { 310 // if values are not parseable 311 } 312 } 313 314 return false; 315 } 316 317 /** 318 * Return the altitude in meters. If the exif tag does not exist, return 319 * <var>defaultValue</var>. 320 * 321 * @param defaultValue the value to return if the tag is not available. 322 */ 323 public double getAltitude(double defaultValue) { 324 double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1); 325 int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1); 326 327 if (altitude >= 0 && ref >= 0) { 328 return (double) (altitude * ((ref == 1) ? -1 : 1)); 329 } else { 330 return defaultValue; 331 } 332 } 333 334 /** 335 * Returns number of milliseconds since Jan. 1, 1970, midnight. 336 * Returns -1 if the date time information if not available. 337 * @hide 338 */ 339 public long getDateTime() { 340 String dateTimeString = mAttributes.get(TAG_DATETIME); 341 if (dateTimeString == null) return -1; 342 343 ParsePosition pos = new ParsePosition(0); 344 try { 345 Date datetime = sFormatter.parse(dateTimeString, pos); 346 if (datetime == null) return -1; 347 return datetime.getTime(); 348 } catch (IllegalArgumentException ex) { 349 return -1; 350 } 351 } 352 353 /** 354 * Returns number of milliseconds since Jan. 1, 1970, midnight UTC. 355 * Returns -1 if the date time information if not available. 356 * @hide 357 */ 358 public long getGpsDateTime() { 359 String date = mAttributes.get(TAG_GPS_DATESTAMP); 360 String time = mAttributes.get(TAG_GPS_TIMESTAMP); 361 if (date == null || time == null) return -1; 362 363 String dateTimeString = date + ' ' + time; 364 if (dateTimeString == null) return -1; 365 366 ParsePosition pos = new ParsePosition(0); 367 try { 368 Date datetime = sFormatter.parse(dateTimeString, pos); 369 if (datetime == null) return -1; 370 return datetime.getTime(); 371 } catch (IllegalArgumentException ex) { 372 return -1; 373 } 374 } 375 376 private static float convertRationalLatLonToFloat( 377 String rationalString, String ref) { 378 try { 379 String [] parts = rationalString.split(","); 380 381 String [] pair; 382 pair = parts[0].split("/"); 383 double degrees = Double.parseDouble(pair[0].trim()) 384 / Double.parseDouble(pair[1].trim()); 385 386 pair = parts[1].split("/"); 387 double minutes = Double.parseDouble(pair[0].trim()) 388 / Double.parseDouble(pair[1].trim()); 389 390 pair = parts[2].split("/"); 391 double seconds = Double.parseDouble(pair[0].trim()) 392 / Double.parseDouble(pair[1].trim()); 393 394 double result = degrees + (minutes / 60.0) + (seconds / 3600.0); 395 if ((ref.equals("S") || ref.equals("W"))) { 396 return (float) -result; 397 } 398 return (float) result; 399 } catch (NumberFormatException e) { 400 // Some of the nubmers are not valid 401 throw new IllegalArgumentException(); 402 } catch (ArrayIndexOutOfBoundsException e) { 403 // Some of the rational does not follow the correct format 404 throw new IllegalArgumentException(); 405 } 406 } 407 408 private native boolean appendThumbnailNative(String fileName, 409 String thumbnailFileName); 410 411 private native void saveAttributesNative(String fileName, 412 String compressedAttributes); 413 414 private native String getAttributesNative(String fileName); 415 416 private native void commitChangesNative(String fileName); 417 418 private native byte[] getThumbnailNative(String fileName); 419 } 420