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