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