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