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