ExifInterface.java revision e208377fbab6b90f41e68699700942a81f4caaeb
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; 25import java.util.TimeZone; 26 27/** 28 * This is a class for reading and writing Exif tags in a JPEG file. 29 */ 30public 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_GPS_TIMESTAMP = "GPSTimeStamp"; 56 /** Type is String. */ 57 public static final String TAG_GPS_DATESTAMP = "GPSDateStamp"; 58 /** Type is int. */ 59 public static final String TAG_WHITE_BALANCE = "WhiteBalance"; 60 /** Type is rational. */ 61 public static final String TAG_FOCAL_LENGTH = "FocalLength"; 62 /** Type is String. Name of GPS processing method used for location finding. */ 63 public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod"; 64 65 // Constants used for the Orientation Exif tag. 66 public static final int ORIENTATION_UNDEFINED = 0; 67 public static final int ORIENTATION_NORMAL = 1; 68 public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror 69 public static final int ORIENTATION_ROTATE_180 = 3; 70 public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror 71 public static final int ORIENTATION_TRANSPOSE = 5; // flipped about top-left <--> bottom-right axis 72 public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it 73 public static final int ORIENTATION_TRANSVERSE = 7; // flipped about top-right <--> bottom-left axis 74 public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it 75 76 // Constants used for white balance 77 public static final int WHITEBALANCE_AUTO = 0; 78 public static final int WHITEBALANCE_MANUAL = 1; 79 private static SimpleDateFormat sFormatter; 80 81 static { 82 System.loadLibrary("exif"); 83 sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); 84 sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); 85 } 86 87 private String mFilename; 88 private HashMap<String, String> mAttributes; 89 private boolean mHasThumbnail; 90 91 // Because the underlying implementation (jhead) uses static variables, 92 // there can only be one user at a time for the native functions (and 93 // they cannot keep state in the native code across function calls). We 94 // use sLock to serialize the accesses. 95 private static Object sLock = new Object(); 96 97 /** 98 * Reads Exif tags from the specified JPEG file. 99 */ 100 public ExifInterface(String filename) throws IOException { 101 mFilename = filename; 102 loadAttributes(); 103 } 104 105 /** 106 * Returns the value of the specified tag or {@code null} if there 107 * is no such tag in the JPEG file. 108 * 109 * @param tag the name of the tag. 110 */ 111 public String getAttribute(String tag) { 112 return mAttributes.get(tag); 113 } 114 115 /** 116 * Returns the integer value of the specified tag. If there is no such tag 117 * in the JPEG file or the value cannot be parsed as integer, return 118 * <var>defaultValue</var>. 119 * 120 * @param tag the name of the tag. 121 * @param defaultValue the value to return if the tag is not available. 122 */ 123 public int getAttributeInt(String tag, int defaultValue) { 124 String value = mAttributes.get(tag); 125 if (value == null) return defaultValue; 126 try { 127 return Integer.valueOf(value); 128 } catch (NumberFormatException ex) { 129 return defaultValue; 130 } 131 } 132 133 /** 134 * Returns the double value of the specified rational tag. If there is no 135 * such tag in the JPEG file or the value cannot be parsed as double, return 136 * <var>defaultValue</var>. 137 * 138 * @param tag the name of the tag. 139 * @param defaultValue the value to return if the tag is not available. 140 */ 141 public double getAttributeDouble(String tag, double defaultValue) { 142 String value = mAttributes.get(tag); 143 if (value == null) return defaultValue; 144 try { 145 int index = value.indexOf("/"); 146 if (index == -1) return defaultValue; 147 double denom = Double.parseDouble(value.substring(index + 1)); 148 if (denom == 0) return defaultValue; 149 double num = Double.parseDouble(value.substring(0, index)); 150 return num / denom; 151 } catch (NumberFormatException ex) { 152 return defaultValue; 153 } 154 } 155 156 /** 157 * Set the value of the specified tag. 158 * 159 * @param tag the name of the tag. 160 * @param value the value of the tag. 161 */ 162 public void setAttribute(String tag, String value) { 163 mAttributes.put(tag, value); 164 } 165 166 /** 167 * Initialize mAttributes with the attributes from the file mFilename. 168 * 169 * mAttributes is a HashMap which stores the Exif attributes of the file. 170 * The key is the standard tag name and the value is the tag's value: e.g. 171 * Model -> Nikon. Numeric values are stored as strings. 172 * 173 * This function also initialize mHasThumbnail to indicate whether the 174 * file has a thumbnail inside. 175 */ 176 private void loadAttributes() throws IOException { 177 // format of string passed from native C code: 178 // "attrCnt attr1=valueLen value1attr2=value2Len value2..." 179 // example: 180 // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO" 181 mAttributes = new HashMap<String, String>(); 182 183 String attrStr; 184 synchronized (sLock) { 185 attrStr = getAttributesNative(mFilename); 186 } 187 188 // get count 189 int ptr = attrStr.indexOf(' '); 190 int count = Integer.parseInt(attrStr.substring(0, ptr)); 191 // skip past the space between item count and the rest of the attributes 192 ++ptr; 193 194 for (int i = 0; i < count; i++) { 195 // extract the attribute name 196 int equalPos = attrStr.indexOf('=', ptr); 197 String attrName = attrStr.substring(ptr, equalPos); 198 ptr = equalPos + 1; // skip past = 199 200 // extract the attribute value length 201 int lenPos = attrStr.indexOf(' ', ptr); 202 int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos)); 203 ptr = lenPos + 1; // skip pas the space 204 205 // extract the attribute value 206 String attrValue = attrStr.substring(ptr, ptr + attrLen); 207 ptr += attrLen; 208 209 if (attrName.equals("hasThumbnail")) { 210 mHasThumbnail = attrValue.equalsIgnoreCase("true"); 211 } else { 212 mAttributes.put(attrName, attrValue); 213 } 214 } 215 } 216 217 /** 218 * Save the tag data into the JPEG file. This is expensive because it involves 219 * copying all the JPG data from one file to another and deleting the old file 220 * and renaming the other. It's best to use {@link #setAttribute(String,String)} 221 * to set all attributes to write and make a single call rather than multiple 222 * calls for each attribute. 223 */ 224 public void saveAttributes() throws IOException { 225 // format of string passed to native C code: 226 // "attrCnt attr1=valueLen value1attr2=value2Len value2..." 227 // example: 228 // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO" 229 StringBuilder sb = new StringBuilder(); 230 int size = mAttributes.size(); 231 if (mAttributes.containsKey("hasThumbnail")) { 232 --size; 233 } 234 sb.append(size + " "); 235 for (Map.Entry<String, String> iter : mAttributes.entrySet()) { 236 String key = iter.getKey(); 237 if (key.equals("hasThumbnail")) { 238 // this is a fake attribute not saved as an exif tag 239 continue; 240 } 241 String val = iter.getValue(); 242 sb.append(key + "="); 243 sb.append(val.length() + " "); 244 sb.append(val); 245 } 246 String s = sb.toString(); 247 synchronized (sLock) { 248 saveAttributesNative(mFilename, s); 249 commitChangesNative(mFilename); 250 } 251 } 252 253 /** 254 * Returns true if the JPEG file has a thumbnail. 255 */ 256 public boolean hasThumbnail() { 257 return mHasThumbnail; 258 } 259 260 /** 261 * Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail. 262 * The returned data is in JPEG format and can be decoded using 263 * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)} 264 */ 265 public byte[] getThumbnail() { 266 synchronized (sLock) { 267 return getThumbnailNative(mFilename); 268 } 269 } 270 271 /** 272 * Stores the latitude and longitude value in a float array. The first element is 273 * the latitude, and the second element is the longitude. Returns false if the 274 * Exif tags are not available. 275 */ 276 public boolean getLatLong(float output[]) { 277 String latValue = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE); 278 String latRef = mAttributes.get(ExifInterface.TAG_GPS_LATITUDE_REF); 279 String lngValue = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE); 280 String lngRef = mAttributes.get(ExifInterface.TAG_GPS_LONGITUDE_REF); 281 282 if (latValue != null && latRef != null && lngValue != null && lngRef != null) { 283 output[0] = convertRationalLatLonToFloat(latValue, latRef); 284 output[1] = convertRationalLatLonToFloat(lngValue, lngRef); 285 return true; 286 } else { 287 return false; 288 } 289 } 290 291 /** 292 * Returns number of milliseconds since Jan. 1, 1970, midnight. 293 * Returns -1 if the date time information if not available. 294 * @hide 295 */ 296 public long getDateTime() { 297 String dateTimeString = mAttributes.get(TAG_DATETIME); 298 if (dateTimeString == null) return -1; 299 300 ParsePosition pos = new ParsePosition(0); 301 try { 302 Date datetime = sFormatter.parse(dateTimeString, pos); 303 if (datetime == null) return -1; 304 return datetime.getTime(); 305 } catch (IllegalArgumentException ex) { 306 return -1; 307 } 308 } 309 310 /** 311 * Returns number of milliseconds since Jan. 1, 1970, midnight UTC. 312 * Returns -1 if the date time information if not available. 313 * @hide 314 */ 315 public long getGpsDateTime() { 316 String date = mAttributes.get(TAG_GPS_DATESTAMP); 317 String time = mAttributes.get(TAG_GPS_TIMESTAMP); 318 if (date == null || time == null) return -1; 319 320 String dateTimeString = date + ' ' + time; 321 if (dateTimeString == null) return -1; 322 323 ParsePosition pos = new ParsePosition(0); 324 try { 325 Date datetime = sFormatter.parse(dateTimeString, pos); 326 if (datetime == null) return -1; 327 return datetime.getTime(); 328 } catch (IllegalArgumentException ex) { 329 return -1; 330 } 331 } 332 333 private static float convertRationalLatLonToFloat( 334 String rationalString, String ref) { 335 try { 336 String [] parts = rationalString.split(","); 337 338 String [] pair; 339 pair = parts[0].split("/"); 340 int degrees = (int) (Float.parseFloat(pair[0].trim()) 341 / Float.parseFloat(pair[1].trim())); 342 343 pair = parts[1].split("/"); 344 int minutes = (int) ((Float.parseFloat(pair[0].trim()) 345 / Float.parseFloat(pair[1].trim()))); 346 347 pair = parts[2].split("/"); 348 float seconds = Float.parseFloat(pair[0].trim()) 349 / Float.parseFloat(pair[1].trim()); 350 351 float result = degrees + (minutes / 60F) + (seconds / (60F * 60F)); 352 if ((ref.equals("S") || ref.equals("W"))) { 353 return -result; 354 } 355 return result; 356 } catch (RuntimeException ex) { 357 // if for whatever reason we can't parse the lat long then return 358 // null 359 return 0f; 360 } 361 } 362 363 private native boolean appendThumbnailNative(String fileName, 364 String thumbnailFileName); 365 366 private native void saveAttributesNative(String fileName, 367 String compressedAttributes); 368 369 private native String getAttributesNative(String fileName); 370 371 private native void commitChangesNative(String fileName); 372 373 private native byte[] getThumbnailNative(String fileName); 374} 375