ExifInterface.java revision 15ef59e081846b43660635391d536361c3253a22
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.graphics.Bitmap; 20import android.graphics.BitmapFactory; 21import android.system.ErrnoException; 22import android.system.Os; 23import android.system.OsConstants; 24import android.util.Log; 25import android.util.Pair; 26 27import java.io.ByteArrayInputStream; 28import java.io.DataInputStream; 29import java.io.DataOutputStream; 30import java.io.EOFException; 31import java.io.File; 32import java.io.FileDescriptor; 33import java.io.FileInputStream; 34import java.io.FileNotFoundException; 35import java.io.FileOutputStream; 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39import java.nio.ByteOrder; 40import java.nio.charset.Charset; 41import java.text.ParsePosition; 42import java.text.SimpleDateFormat; 43import java.util.Arrays; 44import java.util.Date; 45import java.util.HashMap; 46import java.util.Map; 47import java.util.TimeZone; 48import java.util.regex.Pattern; 49 50import libcore.io.IoUtils; 51import libcore.io.Streams; 52 53/** 54 * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file. 55 * <p> 56 * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF. 57 * <p> 58 * Attribute mutation is supported for JPEG image files. 59 */ 60public class ExifInterface { 61 private static final String TAG = "ExifInterface"; 62 private static final boolean DEBUG = false; 63 64 // The Exif tag names 65 /** Type is int. */ 66 public static final String TAG_ORIENTATION = "Orientation"; 67 /** Type is String. */ 68 public static final String TAG_DATETIME = "DateTime"; 69 /** Type is String. */ 70 public static final String TAG_MAKE = "Make"; 71 /** Type is String. */ 72 public static final String TAG_MODEL = "Model"; 73 /** Type is int. */ 74 public static final String TAG_FLASH = "Flash"; 75 /** Type is int. */ 76 public static final String TAG_IMAGE_WIDTH = "ImageWidth"; 77 /** Type is int. */ 78 public static final String TAG_IMAGE_LENGTH = "ImageLength"; 79 /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */ 80 public static final String TAG_GPS_LATITUDE = "GPSLatitude"; 81 /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */ 82 public static final String TAG_GPS_LONGITUDE = "GPSLongitude"; 83 /** Type is String. */ 84 public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef"; 85 /** Type is String. */ 86 public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; 87 /** Type is String. */ 88 public static final String TAG_EXPOSURE_TIME = "ExposureTime"; 89 /** Type is String. */ 90 public static final String TAG_APERTURE = "FNumber"; 91 /** Type is String. */ 92 public static final String TAG_ISO = "ISOSpeedRatings"; 93 /** Type is String. */ 94 public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; 95 /** Type is int. */ 96 public static final String TAG_SUBSEC_TIME = "SubSecTime"; 97 /** Type is int. */ 98 public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; 99 /** Type is int. */ 100 public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; 101 102 /** 103 * @hide 104 */ 105 public static final String TAG_SUBSECTIME = "SubSecTime"; 106 107 /** 108 * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF. 109 * Type is rational. 110 */ 111 public static final String TAG_GPS_ALTITUDE = "GPSAltitude"; 112 113 /** 114 * 0 if the altitude is above sea level. 1 if the altitude is below sea 115 * level. Type is int. 116 */ 117 public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef"; 118 119 /** Type is String. */ 120 public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp"; 121 /** Type is String. */ 122 public static final String TAG_GPS_DATESTAMP = "GPSDateStamp"; 123 /** Type is int. */ 124 public static final String TAG_WHITE_BALANCE = "WhiteBalance"; 125 /** Type is rational. */ 126 public static final String TAG_FOCAL_LENGTH = "FocalLength"; 127 /** Type is String. Name of GPS processing method used for location finding. */ 128 public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod"; 129 /** Type is double. */ 130 public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio"; 131 /** Type is double. */ 132 public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance"; 133 /** Type is double. */ 134 public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue"; 135 /** Type is int. */ 136 public static final String TAG_LIGHT_SOURCE = "LightSource"; 137 /** Type is int. */ 138 public static final String TAG_METERING_MODE = "MeteringMode"; 139 /** Type is int. */ 140 public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram"; 141 /** Type is int. */ 142 public static final String TAG_EXPOSURE_MODE = "ExposureMode"; 143 144 // Private tags used for thumbnail information. 145 private static final String TAG_HAS_THUMBNAIL = "hasThumbnail"; 146 private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset"; 147 private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength"; 148 149 // Constants used for the Orientation Exif tag. 150 public static final int ORIENTATION_UNDEFINED = 0; 151 public static final int ORIENTATION_NORMAL = 1; 152 public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror 153 public static final int ORIENTATION_ROTATE_180 = 3; 154 public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror 155 // flipped about top-left <--> bottom-right axis 156 public static final int ORIENTATION_TRANSPOSE = 5; 157 public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it 158 // flipped about top-right <--> bottom-left axis 159 public static final int ORIENTATION_TRANSVERSE = 7; 160 public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it 161 162 // Constants used for white balance 163 public static final int WHITEBALANCE_AUTO = 0; 164 public static final int WHITEBALANCE_MANUAL = 1; 165 166 private static SimpleDateFormat sFormatter; 167 168 // See Exchangeable image file format for digital still cameras: Exif version 2.2. 169 // The following values are for parsing EXIF data area. There are tag groups in EXIF data area. 170 // They are called "Image File Directory". They have multiple data formats to cover various 171 // image metadata from GPS longitude to camera model name. 172 173 // Types of Exif byte alignments (see JEITA CP-3451 page 10) 174 private static final short BYTE_ALIGN_II = 0x4949; // II: Intel order 175 private static final short BYTE_ALIGN_MM = 0x4d4d; // MM: Motorola order 176 177 // Formats for the value in IFD entry (See TIFF 6.0 spec Types page 15). 178 private static final int IFD_FORMAT_BYTE = 1; 179 private static final int IFD_FORMAT_STRING = 2; 180 private static final int IFD_FORMAT_USHORT = 3; 181 private static final int IFD_FORMAT_ULONG = 4; 182 private static final int IFD_FORMAT_URATIONAL = 5; 183 private static final int IFD_FORMAT_SBYTE = 6; 184 private static final int IFD_FORMAT_UNDEFINED = 7; 185 private static final int IFD_FORMAT_SSHORT = 8; 186 private static final int IFD_FORMAT_SLONG = 9; 187 private static final int IFD_FORMAT_SRATIONAL = 10; 188 private static final int IFD_FORMAT_SINGLE = 11; 189 private static final int IFD_FORMAT_DOUBLE = 12; 190 // Sizes of the components of each IFD value format 191 private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] { 192 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 193 }; 194 private static final byte[] EXIF_ASCII_PREFIX = new byte[] { 195 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 196 }; 197 198 // A class for indicating EXIF tag. 199 private static class ExifTag { 200 public final int number; 201 public final String name; 202 203 private ExifTag(String name, int number) { 204 this.name = name; 205 this.number = number; 206 } 207 } 208 209 // Primary image IFD TIFF tags (See JEITA CP-3451 Table 14. page 54). 210 private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] { 211 new ExifTag("ImageWidth", 256), 212 new ExifTag("ImageLength", 257), 213 new ExifTag("BitsPerSample", 258), 214 new ExifTag("Compression", 259), 215 new ExifTag("PhotometricInterpretation", 262), 216 new ExifTag("ImageDescription", 270), 217 new ExifTag("Make", 271), 218 new ExifTag("Model", 272), 219 new ExifTag("StripOffsets", 273), 220 new ExifTag("Orientation", 274), 221 new ExifTag("SamplesPerPixel", 277), 222 new ExifTag("RowsPerStrip", 278), 223 new ExifTag("StripByteCounts", 279), 224 new ExifTag("XResolution", 282), 225 new ExifTag("YResolution", 283), 226 new ExifTag("PlanarConfiguration", 284), 227 new ExifTag("ResolutionUnit", 296), 228 new ExifTag("TransferFunction", 301), 229 new ExifTag("Software", 305), 230 new ExifTag("DateTime", 306), 231 new ExifTag("Artist", 315), 232 new ExifTag("WhitePoint", 318), 233 new ExifTag("PrimaryChromaticities", 319), 234 new ExifTag("JPEGInterchangeFormat", 513), 235 new ExifTag("JPEGInterchangeFormatLength", 514), 236 new ExifTag("YCbCrCoefficients", 529), 237 new ExifTag("YCbCrSubSampling", 530), 238 new ExifTag("YCbCrPositioning", 531), 239 new ExifTag("ReferenceBlackWhite", 532), 240 new ExifTag("Copyright", 33432), 241 new ExifTag("ExifIFDPointer", 34665), 242 new ExifTag("GPSInfoIFDPointer", 34853), 243 }; 244 // Primary image IFD Exif Private tags (See JEITA CP-3451 Table 15. page 55). 245 private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] { 246 new ExifTag("ExposureTime", 33434), 247 new ExifTag("FNumber", 33437), 248 new ExifTag("ExposureProgram", 34850), 249 new ExifTag("SpectralSensitivity", 34852), 250 new ExifTag("ISOSpeedRatings", 34855), 251 new ExifTag("OECF", 34856), 252 new ExifTag("ExifVersion", 36864), 253 new ExifTag("DateTimeOriginal", 36867), 254 new ExifTag("DateTimeDigitized", 36868), 255 new ExifTag("ComponentsConfiguration", 37121), 256 new ExifTag("CompressedBitsPerPixel", 37122), 257 new ExifTag("ShutterSpeedValue", 37377), 258 new ExifTag("ApertureValue", 37378), 259 new ExifTag("BrightnessValue", 37379), 260 new ExifTag("ExposureBiasValue", 37380), 261 new ExifTag("MaxApertureValue", 37381), 262 new ExifTag("SubjectDistance", 37382), 263 new ExifTag("MeteringMode", 37383), 264 new ExifTag("LightSource", 37384), 265 new ExifTag("Flash", 37385), 266 new ExifTag("FocalLength", 37386), 267 new ExifTag("SubjectArea", 37396), 268 new ExifTag("MakerNote", 37500), 269 new ExifTag("UserComment", 37510), 270 new ExifTag("SubSecTime", 37520), 271 new ExifTag("SubSecTimeOriginal", 37521), 272 new ExifTag("SubSecTimeDigitized", 37522), 273 new ExifTag("FlashpixVersion", 40960), 274 new ExifTag("ColorSpace", 40961), 275 new ExifTag("PixelXDimension", 40962), 276 new ExifTag("PixelYDimension", 40963), 277 new ExifTag("RelatedSoundFile", 40964), 278 new ExifTag("InteroperabilityIFDPointer", 40965), 279 new ExifTag("FlashEnergy", 41483), 280 new ExifTag("SpatialFrequencyResponse", 41484), 281 new ExifTag("FocalPlaneXResolution", 41486), 282 new ExifTag("FocalPlaneYResolution", 41487), 283 new ExifTag("FocalPlaneResolutionUnit", 41488), 284 new ExifTag("SubjectLocation", 41492), 285 new ExifTag("ExposureIndex", 41493), 286 new ExifTag("SensingMethod", 41495), 287 new ExifTag("FileSource", 41728), 288 new ExifTag("SceneType", 41729), 289 new ExifTag("CFAPattern", 41730), 290 new ExifTag("CustomRendered", 41985), 291 new ExifTag("ExposureMode", 41986), 292 new ExifTag("WhiteBalance", 41987), 293 new ExifTag("DigitalZoomRatio", 41988), 294 new ExifTag("FocalLengthIn35mmFilm", 41989), 295 new ExifTag("SceneCaptureType", 41990), 296 new ExifTag("GainControl", 41991), 297 new ExifTag("Contrast", 41992), 298 new ExifTag("Saturation", 41993), 299 new ExifTag("Sharpness", 41994), 300 new ExifTag("DeviceSettingDescription", 41995), 301 new ExifTag("SubjectDistanceRange", 41996), 302 new ExifTag("ImageUniqueID", 42016), 303 }; 304 // Primary image IFD GPS Info tags (See JEITA CP-3451 Table 16. page 56). 305 private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] { 306 new ExifTag("GPSVersionID", 0), 307 new ExifTag("GPSLatitudeRef", 1), 308 new ExifTag("GPSLatitude", 2), 309 new ExifTag("GPSLongitudeRef", 3), 310 new ExifTag("GPSLongitude", 4), 311 new ExifTag("GPSAltitudeRef", 5), 312 new ExifTag("GPSAltitude", 6), 313 new ExifTag("GPSTimeStamp", 7), 314 new ExifTag("GPSSatellites", 8), 315 new ExifTag("GPSStatus", 9), 316 new ExifTag("GPSMeasureMode", 10), 317 new ExifTag("GPSDOP", 11), 318 new ExifTag("GPSSpeedRef", 12), 319 new ExifTag("GPSSpeed", 13), 320 new ExifTag("GPSTrackRef", 14), 321 new ExifTag("GPSTrack", 15), 322 new ExifTag("GPSImgDirectionRef", 16), 323 new ExifTag("GPSImgDirection", 17), 324 new ExifTag("GPSMapDatum", 18), 325 new ExifTag("GPSDestLatitudeRef", 19), 326 new ExifTag("GPSDestLatitude", 20), 327 new ExifTag("GPSDestLongitudeRef", 21), 328 new ExifTag("GPSDestLongitude", 22), 329 new ExifTag("GPSDestBearingRef", 23), 330 new ExifTag("GPSDestBearing", 24), 331 new ExifTag("GPSDestDistanceRef", 25), 332 new ExifTag("GPSDestDistance", 26), 333 new ExifTag("GPSProcessingMethod", 27), 334 new ExifTag("GPSAreaInformation", 28), 335 new ExifTag("GPSDateStamp", 29), 336 new ExifTag("GPSDifferential", 30), 337 }; 338 // Primary image IFD Interoperability tag (See JEITA CP-3451 Table 17. page 56). 339 private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] { 340 new ExifTag("InteroperabilityIndex", 1), 341 }; 342 // IFD Thumbnail tags (See JEITA CP-3451 Table 18. page 57). 343 private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] { 344 new ExifTag("ThumbnailImageWidth", 256), 345 new ExifTag("ThumbnailImageLength", 257), 346 new ExifTag("BitsPerSample", 258), 347 new ExifTag("Compression", 259), 348 new ExifTag("PhotometricInterpretation", 262), 349 new ExifTag("ImageDescription", 270), 350 new ExifTag("Make", 271), 351 new ExifTag("Model", 272), 352 new ExifTag("StripOffsets", 273), 353 new ExifTag("Orientation", 274), 354 new ExifTag("SamplesPerPixel", 277), 355 new ExifTag("RowsPerStrip", 278), 356 new ExifTag("StripByteCounts", 279), 357 new ExifTag("XResolution", 282), 358 new ExifTag("YResolution", 283), 359 new ExifTag("PlanarConfiguration", 284), 360 new ExifTag("ResolutionUnit", 296), 361 new ExifTag("TransferFunction", 301), 362 new ExifTag("Software", 305), 363 new ExifTag("DateTime", 306), 364 new ExifTag("Artist", 315), 365 new ExifTag("WhitePoint", 318), 366 new ExifTag("PrimaryChromaticities", 319), 367 new ExifTag("JPEGInterchangeFormat", 513), 368 new ExifTag("JPEGInterchangeFormatLength", 514), 369 new ExifTag("YCbCrCoefficients", 529), 370 new ExifTag("YCbCrSubSampling", 530), 371 new ExifTag("YCbCrPositioning", 531), 372 new ExifTag("ReferenceBlackWhite", 532), 373 new ExifTag("Copyright", 33432), 374 new ExifTag("ExifIFDPointer", 34665), 375 new ExifTag("GPSInfoIFDPointer", 34853), 376 }; 377 378 // See JEITA CP-3451 Figure 5. page 9. 379 // The following values are used for indicating pointers to the other Image File Directorys. 380 381 // Indices of Exif Ifd tag groups 382 private static final int IFD_TIFF_HINT = 0; 383 private static final int IFD_EXIF_HINT = 1; 384 private static final int IFD_GPS_HINT = 2; 385 private static final int IFD_INTEROPERABILITY_HINT = 3; 386 private static final int IFD_THUMBNAIL_HINT = 4; 387 // List of Exif tag groups 388 private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] { 389 IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS, 390 IFD_THUMBNAIL_TAGS 391 }; 392 // List of tags for pointing to the other image file directory offset. 393 private static final ExifTag[] IFD_POINTER_TAGS = new ExifTag[] { 394 new ExifTag("ExifIFDPointer", 34665), 395 new ExifTag("GPSInfoPointer", 34853), 396 new ExifTag("InteroperabilityIFDPointer", 40965), 397 }; 398 // List of indices of the indicated tag groups according to the IFD_POINTER_TAGS 399 private static final int[] IFD_POINTER_TAG_HINTS = new int[] { 400 IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT 401 }; 402 // Tags for indicating the thumbnail offset and length 403 private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG = 404 new ExifTag("JPEGInterchangeFormat", 513); 405 private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG = 406 new ExifTag("JPEGInterchangeFormatLength", 514); 407 408 // Mappings from tag number to tag name and each item represents one IFD tag group. 409 private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length]; 410 // Mapping from tag name to tag number and the corresponding tag group. 411 private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting 412 = new HashMap<>(); 413 414 // See JPEG File Interchange Format Version 1.02. 415 // The following values are defined for handling JPEG streams. In this implementation, we are 416 // not only getting information from EXIF but also from some JPEG special segments such as 417 // MARKER_COM for user comment and MARKER_SOFx for image width and height. 418 419 // Identifier for APP1 segment in JPEG 420 private static final byte[] IDENTIFIER_APP1 = "Exif\0\0".getBytes(Charset.forName("US-ASCII")); 421 // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with 422 // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start 423 // of frame(baseline DCT) and the image size info exists in its beginning part. 424 private static final byte MARKER = (byte) 0xff; 425 private static final byte MARKER_SOI = (byte) 0xd8; 426 private static final byte MARKER_SOF0 = (byte) 0xc0; 427 private static final byte MARKER_SOF1 = (byte) 0xc1; 428 private static final byte MARKER_SOF2 = (byte) 0xc2; 429 private static final byte MARKER_SOF3 = (byte) 0xc3; 430 private static final byte MARKER_SOF5 = (byte) 0xc5; 431 private static final byte MARKER_SOF6 = (byte) 0xc6; 432 private static final byte MARKER_SOF7 = (byte) 0xc7; 433 private static final byte MARKER_SOF9 = (byte) 0xc9; 434 private static final byte MARKER_SOF10 = (byte) 0xca; 435 private static final byte MARKER_SOF11 = (byte) 0xcb; 436 private static final byte MARKER_SOF13 = (byte) 0xcd; 437 private static final byte MARKER_SOF14 = (byte) 0xce; 438 private static final byte MARKER_SOF15 = (byte) 0xcf; 439 private static final byte MARKER_SOS = (byte) 0xda; 440 private static final byte MARKER_APP1 = (byte) 0xe1; 441 private static final byte MARKER_COM = (byte) 0xfe; 442 private static final byte MARKER_EOI = (byte) 0xd9; 443 444 static { 445 System.loadLibrary("media_jni"); 446 initRawNative(); 447 sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); 448 sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); 449 450 // Build up the hash tables to look up Exif tags for reading Exif tags. 451 for (int hint = 0; hint < EXIF_TAGS.length; ++hint) { 452 sExifTagMapsForReading[hint] = new HashMap(); 453 for (ExifTag tag : EXIF_TAGS[hint]) { 454 sExifTagMapsForReading[hint].put(tag.number, tag.name); 455 } 456 } 457 458 // Build up the hash tables to look up Exif tags for writing Exif tags. 459 // There are some tags that have the same tag name in the different group. For that tags, 460 // Primary image TIFF IFD and Exif private IFD have a higher priority to map than the other 461 // tag groups. For the same tags, it writes one tag in the only one IFD group, which has the 462 // higher priority group. 463 for (int hint = EXIF_TAGS.length - 1; hint >= 0; --hint) { 464 for (ExifTag tag : EXIF_TAGS[hint]) { 465 sExifTagMapForWriting.put(tag.name, new Pair<>(tag.number, hint)); 466 } 467 } 468 } 469 470 private final String mFilename; 471 private final FileDescriptor mFileDescriptor; 472 private final InputStream mInputStream; 473 private boolean mIsRaw; 474 private final HashMap<String, String> mAttributes = new HashMap<>(); 475 private boolean mHasThumbnail; 476 // The following values used for indicating a thumbnail position. 477 private int mThumbnailOffset; 478 private int mThumbnailLength; 479 private byte[] mThumbnailBytes; 480 481 // Pattern to check non zero timestamp 482 private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); 483 484 /** 485 * Reads Exif tags from the specified image file. 486 */ 487 public ExifInterface(String filename) throws IOException { 488 if (filename == null) { 489 throw new IllegalArgumentException("filename cannot be null"); 490 } 491 mFilename = filename; 492 mFileDescriptor = null; 493 mInputStream = new FileInputStream(filename); 494 loadAttributes(); 495 } 496 497 /** 498 * Reads Exif tags from the specified image file descriptor. 499 */ 500 public ExifInterface(FileDescriptor fileDescriptor) throws IOException { 501 if (fileDescriptor == null) { 502 throw new IllegalArgumentException("parcelFileDescriptor cannot be null"); 503 } 504 mFilename = null; 505 mFileDescriptor = fileDescriptor; 506 mInputStream = new FileInputStream(fileDescriptor); 507 loadAttributes(); 508 } 509 510 /** 511 * Reads Exif tags from the specified image input stream. Attribute mutation is not supported 512 * for input streams. 513 */ 514 public ExifInterface(InputStream inputStream) throws IOException { 515 if (inputStream == null) { 516 throw new IllegalArgumentException("inputStream cannot be null"); 517 } 518 mFilename = null; 519 mFileDescriptor = null; 520 mInputStream = inputStream; 521 loadAttributes(); 522 } 523 524 /** 525 * Returns the value of the specified tag or {@code null} if there 526 * is no such tag in the image file. 527 * 528 * @param tag the name of the tag. 529 */ 530 public String getAttribute(String tag) { 531 return mAttributes.get(tag); 532 } 533 534 /** 535 * Returns the integer value of the specified tag. If there is no such tag 536 * in the image file or the value cannot be parsed as integer, return 537 * <var>defaultValue</var>. 538 * 539 * @param tag the name of the tag. 540 * @param defaultValue the value to return if the tag is not available. 541 */ 542 public int getAttributeInt(String tag, int defaultValue) { 543 String value = mAttributes.get(tag); 544 if (value == null) return defaultValue; 545 try { 546 return Integer.valueOf(value); 547 } catch (NumberFormatException ex) { 548 return defaultValue; 549 } 550 } 551 552 /** 553 * Returns the double value of the tag that is specified as rational or contains a 554 * double-formatted value. If there is no such tag in the image file or the value cannot be 555 * parsed as double, return <var>defaultValue</var>. 556 * 557 * @param tag the name of the tag. 558 * @param defaultValue the value to return if the tag is not available. 559 */ 560 public double getAttributeDouble(String tag, double defaultValue) { 561 String value = mAttributes.get(tag); 562 if (value == null) return defaultValue; 563 try { 564 int index = value.indexOf("/"); 565 if (index == -1) return Double.parseDouble(value); 566 double denom = Double.parseDouble(value.substring(index + 1)); 567 if (denom == 0) return defaultValue; 568 double num = Double.parseDouble(value.substring(0, index)); 569 return num / denom; 570 } catch (NumberFormatException ex) { 571 return defaultValue; 572 } 573 } 574 575 /** 576 * Set the value of the specified tag. 577 * 578 * @param tag the name of the tag. 579 * @param value the value of the tag. 580 */ 581 public void setAttribute(String tag, String value) { 582 if (value == null) { 583 mAttributes.remove(tag); 584 return; 585 } 586 mAttributes.put(tag, value); 587 } 588 589 /** 590 * Initialize mAttributes with the attributes from the file mFilename. 591 * 592 * mAttributes is a HashMap which stores the Exif attributes of the file. 593 * The key is the standard tag name and the value is the tag's value: e.g. 594 * Model -> Nikon. Numeric values are stored as strings. 595 * 596 * This function also initialize mHasThumbnail to indicate whether the 597 * file has a thumbnail inside. 598 */ 599 private void loadAttributes() throws IOException { 600 FileInputStream in = null; 601 try { 602 if (mFilename != null) { 603 in = new FileInputStream(mFilename); 604 } 605 if (mFileDescriptor != null) { 606 in = new FileInputStream(mFileDescriptor); 607 } 608 if (in != null) { 609 // First test whether a given file is a one of RAW format or not. 610 HashMap map = getRawAttributesNative(Os.dup(in.getFD())); 611 mIsRaw = map != null; 612 if (mIsRaw) { 613 for (Object obj : map.entrySet()) { 614 Map.Entry entry = (Map.Entry) obj; 615 String attrName = (String) entry.getKey(); 616 String attrValue = (String) entry.getValue(); 617 618 switch (attrName) { 619 case TAG_HAS_THUMBNAIL: 620 mHasThumbnail = attrValue.equalsIgnoreCase("true"); 621 break; 622 case TAG_THUMBNAIL_OFFSET: 623 mThumbnailOffset = Integer.parseInt(attrValue); 624 break; 625 case TAG_THUMBNAIL_LENGTH: 626 mThumbnailLength = Integer.parseInt(attrValue); 627 break; 628 default: 629 mAttributes.put(attrName, attrValue); 630 break; 631 } 632 } 633 634 if (DEBUG) { 635 printAttributes(); 636 } 637 return; 638 } 639 } 640 } catch (ErrnoException e) { 641 e.rethrowAsIOException(); 642 } finally { 643 IoUtils.closeQuietly(in); 644 } 645 646 try { 647 if (mFileDescriptor != null) { 648 Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET); 649 } 650 651 getJpegAttributes(mInputStream); 652 } catch (ErrnoException e) { 653 e.rethrowAsIOException(); 654 } finally { 655 IoUtils.closeQuietly(mInputStream); 656 } 657 658 if (DEBUG) { 659 printAttributes(); 660 } 661 } 662 663 // Prints out attributes for debugging. 664 private void printAttributes() { 665 Log.d(TAG, "The size of tags: " + mAttributes.size()); 666 for (Map.Entry<String, String> entry : mAttributes.entrySet()) { 667 Log.d(TAG, "tagName: " + entry.getKey() + ", tagValue: " + entry.getValue()); 668 } 669 } 670 671 /** 672 * Save the tag data into the original image file. This is expensive because it involves 673 * copying all the data from one file to another and deleting the old file and renaming the 674 * other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write 675 * and make a single call rather than multiple calls for each attribute. 676 */ 677 public void saveAttributes() throws IOException { 678 if (mIsRaw) { 679 throw new UnsupportedOperationException( 680 "ExifInterface does not support saving attributes on RAW formats."); 681 } 682 if (mFileDescriptor == null && mFilename == null) { 683 throw new UnsupportedOperationException( 684 "ExifInterface does not support saving attributes for input streams."); 685 } 686 687 // Keep the thumbnail in memory 688 mThumbnailBytes = getThumbnail(); 689 690 FileInputStream in = null; 691 FileOutputStream out = null; 692 File tempFile = null; 693 try { 694 // Move the original file to temporary file. 695 if (mFilename != null) { 696 tempFile = new File(mFilename + ".tmp"); 697 File originalFile = new File(mFilename); 698 if (!originalFile.renameTo(tempFile)) { 699 throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath()); 700 } 701 } 702 if (mFileDescriptor != null) { 703 tempFile = File.createTempFile("temp", "jpg"); 704 Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET); 705 in = new FileInputStream(mFileDescriptor); 706 out = new FileOutputStream(tempFile); 707 Streams.copy(in, out); 708 } 709 } catch (ErrnoException e) { 710 e.rethrowAsIOException(); 711 } finally { 712 IoUtils.closeQuietly(in); 713 IoUtils.closeQuietly(out); 714 } 715 716 in = null; 717 out = null; 718 try { 719 // Save the new file. 720 in = new FileInputStream(tempFile); 721 if (mFilename != null) { 722 out = new FileOutputStream(mFilename); 723 } 724 if (mFileDescriptor != null) { 725 Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET); 726 out = new FileOutputStream(mFileDescriptor); 727 } 728 saveJpegAttributes(in, out); 729 } catch (ErrnoException e) { 730 e.rethrowAsIOException(); 731 } finally { 732 IoUtils.closeQuietly(in); 733 IoUtils.closeQuietly(out); 734 tempFile.delete(); 735 } 736 737 // Discard the thumbnail in memory 738 mThumbnailBytes = null; 739 } 740 741 /** 742 * Returns true if the image file has a thumbnail. 743 */ 744 public boolean hasThumbnail() { 745 return mHasThumbnail; 746 } 747 748 /** 749 * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail. 750 * The returned data is in JPEG format and can be decoded using 751 * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)} 752 */ 753 public byte[] getThumbnail() { 754 if (!mHasThumbnail) { 755 return null; 756 } 757 if (mThumbnailBytes != null) { 758 return mThumbnailBytes; 759 } 760 761 // Read the thumbnail. 762 FileInputStream in = null; 763 try { 764 if (mFileDescriptor != null) { 765 Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET); 766 in = new FileInputStream(mFileDescriptor); 767 } 768 if (mFilename != null) { 769 in = new FileInputStream(mFilename); 770 } 771 if (in == null) { 772 // Should not be reached this. 773 throw new FileNotFoundException(); 774 } 775 if (in.skip(mThumbnailOffset) != mThumbnailOffset) { 776 throw new IOException("Corrupted image"); 777 } 778 byte[] buffer = new byte[mThumbnailLength]; 779 if (in.read(buffer) != mThumbnailLength) { 780 throw new IOException("Corrupted image"); 781 } 782 return buffer; 783 } catch (IOException | ErrnoException e) { 784 // Couldn't get a thumbnail image. 785 } finally { 786 IoUtils.closeQuietly(in); 787 } 788 return null; 789 } 790 791 /** 792 * Returns the offset and length of thumbnail inside the image file, or 793 * {@code null} if there is no thumbnail. 794 * 795 * @return two-element array, the offset in the first value, and length in 796 * the second, or {@code null} if no thumbnail was found. 797 * @hide 798 */ 799 public long[] getThumbnailRange() { 800 long[] range = new long[2]; 801 range[0] = mThumbnailOffset; 802 range[1] = mThumbnailLength; 803 return range; 804 } 805 806 /** 807 * Stores the latitude and longitude value in a float array. The first element is 808 * the latitude, and the second element is the longitude. Returns false if the 809 * Exif tags are not available. 810 */ 811 public boolean getLatLong(float output[]) { 812 String latValue = mAttributes.get(TAG_GPS_LATITUDE); 813 String latRef = mAttributes.get(TAG_GPS_LATITUDE_REF); 814 String lngValue = mAttributes.get(TAG_GPS_LONGITUDE); 815 String lngRef = mAttributes.get(TAG_GPS_LONGITUDE_REF); 816 817 if (latValue != null && latRef != null && lngValue != null && lngRef != null) { 818 try { 819 output[0] = convertRationalLatLonToFloat(latValue, latRef); 820 output[1] = convertRationalLatLonToFloat(lngValue, lngRef); 821 return true; 822 } catch (IllegalArgumentException e) { 823 // if values are not parseable 824 } 825 } 826 827 return false; 828 } 829 830 /** 831 * Return the altitude in meters. If the exif tag does not exist, return 832 * <var>defaultValue</var>. 833 * 834 * @param defaultValue the value to return if the tag is not available. 835 */ 836 public double getAltitude(double defaultValue) { 837 double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1); 838 int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1); 839 840 if (altitude >= 0 && ref >= 0) { 841 return (altitude * ((ref == 1) ? -1 : 1)); 842 } else { 843 return defaultValue; 844 } 845 } 846 847 /** 848 * Returns number of milliseconds since Jan. 1, 1970, midnight local time. 849 * Returns -1 if the date time information if not available. 850 * @hide 851 */ 852 public long getDateTime() { 853 String dateTimeString = mAttributes.get(TAG_DATETIME); 854 if (dateTimeString == null 855 || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1; 856 857 ParsePosition pos = new ParsePosition(0); 858 try { 859 // The exif field is in local time. Parsing it as if it is UTC will yield time 860 // since 1/1/1970 local time 861 Date datetime = sFormatter.parse(dateTimeString, pos); 862 if (datetime == null) return -1; 863 long msecs = datetime.getTime(); 864 865 String subSecs = mAttributes.get(TAG_SUBSECTIME); 866 if (subSecs != null) { 867 try { 868 long sub = Long.valueOf(subSecs); 869 while (sub > 1000) { 870 sub /= 10; 871 } 872 msecs += sub; 873 } catch (NumberFormatException e) { 874 // Ignored 875 } 876 } 877 return msecs; 878 } catch (IllegalArgumentException ex) { 879 return -1; 880 } 881 } 882 883 /** 884 * Returns number of milliseconds since Jan. 1, 1970, midnight UTC. 885 * Returns -1 if the date time information if not available. 886 * @hide 887 */ 888 public long getGpsDateTime() { 889 String date = mAttributes.get(TAG_GPS_DATESTAMP); 890 String time = mAttributes.get(TAG_GPS_TIMESTAMP); 891 if (date == null || time == null 892 || (!sNonZeroTimePattern.matcher(date).matches() 893 && !sNonZeroTimePattern.matcher(time).matches())) return -1; 894 895 String dateTimeString = date + ' ' + time; 896 897 ParsePosition pos = new ParsePosition(0); 898 try { 899 Date datetime = sFormatter.parse(dateTimeString, pos); 900 if (datetime == null) return -1; 901 return datetime.getTime(); 902 } catch (IllegalArgumentException ex) { 903 return -1; 904 } 905 } 906 907 private static float convertRationalLatLonToFloat(String rationalString, String ref) { 908 try { 909 String [] parts = rationalString.split(","); 910 911 String [] pair; 912 pair = parts[0].split("/"); 913 double degrees = Double.parseDouble(pair[0].trim()) 914 / Double.parseDouble(pair[1].trim()); 915 916 pair = parts[1].split("/"); 917 double minutes = Double.parseDouble(pair[0].trim()) 918 / Double.parseDouble(pair[1].trim()); 919 920 pair = parts[2].split("/"); 921 double seconds = Double.parseDouble(pair[0].trim()) 922 / Double.parseDouble(pair[1].trim()); 923 924 double result = degrees + (minutes / 60.0) + (seconds / 3600.0); 925 if ((ref.equals("S") || ref.equals("W"))) { 926 return (float) -result; 927 } 928 return (float) result; 929 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { 930 // Not valid 931 throw new IllegalArgumentException(); 932 } 933 } 934 935 // Loads EXIF attributes from a JPEG input stream. 936 private void getJpegAttributes(InputStream inputStream) throws IOException { 937 // See JPEG File Interchange Format Specification page 5. 938 if (DEBUG) { 939 Log.d(TAG, "getJpegAttributes starting with: " + inputStream); 940 } 941 DataInputStream dataInputStream = new DataInputStream(inputStream); 942 byte marker; 943 int bytesRead = 0; 944 ++bytesRead; 945 if ((marker = dataInputStream.readByte()) != MARKER) { 946 throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff)); 947 } 948 ++bytesRead; 949 if (dataInputStream.readByte() != MARKER_SOI) { 950 throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff)); 951 } 952 while (true) { 953 ++bytesRead; 954 marker = dataInputStream.readByte(); 955 if (marker != MARKER) { 956 throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff)); 957 } 958 ++bytesRead; 959 marker = dataInputStream.readByte(); 960 if (DEBUG) { 961 Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff)); 962 } 963 964 // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and 965 // the image data will terminate right after. 966 if (marker == MARKER_EOI || marker == MARKER_SOS) { 967 break; 968 } 969 bytesRead += 2; 970 int length = dataInputStream.readUnsignedShort() - 2; 971 if (length < 0) 972 throw new IOException("Invalid length"); 973 bytesRead += length; 974 switch (marker) { 975 case MARKER_APP1: { 976 if (DEBUG) { 977 Log.d(TAG, "MARKER_APP1"); 978 } 979 bytesRead -= length; 980 if (length < 6) { 981 throw new IOException("Invalid exif"); 982 } 983 byte[] identifier = new byte[6]; 984 if (inputStream.read(identifier) != 6) { 985 throw new IOException("Invalid exif"); 986 } 987 if (!Arrays.equals(identifier, IDENTIFIER_APP1)) { 988 throw new IOException("Invalid app1 identifier"); 989 } 990 bytesRead += 6; 991 length -= 6; 992 if (length <= 0) { 993 throw new IOException("Invalid exif"); 994 } 995 byte[] bytes = new byte[length]; 996 if (dataInputStream.read(bytes) != length) { 997 throw new IOException("Invalid exif"); 998 } 999 readExifSegment(bytes, bytesRead); 1000 bytesRead += length; 1001 length = 0; 1002 break; 1003 } 1004 1005 case MARKER_COM: { 1006 byte[] bytes = new byte[length]; 1007 if (dataInputStream.read(bytes) != length) { 1008 throw new IOException("Invalid exif"); 1009 } 1010 mAttributes.put("UserComment", 1011 new String(bytes, Charset.forName("US-ASCII"))); 1012 break; 1013 } 1014 1015 case MARKER_SOF0: 1016 case MARKER_SOF1: 1017 case MARKER_SOF2: 1018 case MARKER_SOF3: 1019 case MARKER_SOF5: 1020 case MARKER_SOF6: 1021 case MARKER_SOF7: 1022 case MARKER_SOF9: 1023 case MARKER_SOF10: 1024 case MARKER_SOF11: 1025 case MARKER_SOF13: 1026 case MARKER_SOF14: 1027 case MARKER_SOF15: { 1028 dataInputStream.skipBytes(1); 1029 mAttributes.put("ImageLength", 1030 String.valueOf(dataInputStream.readUnsignedShort())); 1031 mAttributes.put("ImageWidth", 1032 String.valueOf(dataInputStream.readUnsignedShort())); 1033 length -= 5; 1034 break; 1035 } 1036 1037 default: { 1038 break; 1039 } 1040 } 1041 if (length < 0) { 1042 throw new IOException("Invalid length"); 1043 } 1044 dataInputStream.skipBytes(length); 1045 } 1046 } 1047 1048 // Stores a new JPEG image with EXIF attributes into a given output stream. 1049 private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream) 1050 throws IOException { 1051 // See JPEG File Interchange Format Specification page 5. 1052 if (DEBUG) { 1053 Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream 1054 + ", outputStream: " + outputStream + ")"); 1055 } 1056 DataInputStream dataInputStream = new DataInputStream(inputStream); 1057 ExifDataOutputStream dataOutputStream = new ExifDataOutputStream(outputStream); 1058 int bytesRead = 0; 1059 ++bytesRead; 1060 if (dataInputStream.readByte() != MARKER) { 1061 throw new IOException("Invalid marker"); 1062 } 1063 dataOutputStream.writeByte(MARKER); 1064 ++bytesRead; 1065 if (dataInputStream.readByte() != MARKER_SOI) { 1066 throw new IOException("Invalid marker"); 1067 } 1068 dataOutputStream.writeByte(MARKER_SOI); 1069 1070 byte[] bytes = new byte[4096]; 1071 1072 while (true) { 1073 ++bytesRead; 1074 if (dataInputStream.readByte() != MARKER) { 1075 throw new IOException("Invalid marker"); 1076 } 1077 dataOutputStream.writeByte(MARKER); 1078 ++bytesRead; 1079 byte marker = dataInputStream.readByte(); 1080 dataOutputStream.writeByte(marker); 1081 switch (marker) { 1082 case MARKER_APP1: { 1083 // Rewrite EXIF segment 1084 int length = dataInputStream.readUnsignedShort() - 2; 1085 if (length < 0) 1086 throw new IOException("Invalid length"); 1087 bytesRead += 2; 1088 int read; 1089 while ((read = dataInputStream.read( 1090 bytes, 0, Math.min(length, bytes.length))) > 0) { 1091 length -= read; 1092 } 1093 bytesRead += length; 1094 writeExifSegment(dataOutputStream, bytesRead); 1095 break; 1096 } 1097 case MARKER_EOI: 1098 case MARKER_SOS: { 1099 // Copy all the remaining data 1100 Streams.copy(dataInputStream, dataOutputStream); 1101 return; 1102 } 1103 default: { 1104 // Copy JPEG segment 1105 int length = dataInputStream.readUnsignedShort(); 1106 dataOutputStream.writeUnsignedShort(length); 1107 if (length < 0) 1108 throw new IOException("Invalid length"); 1109 length -= 2; 1110 bytesRead += 2; 1111 int read; 1112 while ((read = dataInputStream.read( 1113 bytes, 0, Math.min(length, bytes.length))) > 0) { 1114 dataOutputStream.write(bytes, 0, read); 1115 length -= read; 1116 } 1117 bytesRead += length; 1118 break; 1119 } 1120 } 1121 } 1122 } 1123 1124 // Reads the given EXIF byte area and save its tag data into attributes. 1125 private void readExifSegment(byte[] exifBytes, int exifOffsetFromBeginning) throws IOException { 1126 // Parse TIFF Headers. See JEITA CP-3451C Table 1. page 10. 1127 ByteOrderAwarenessDataInputStream dataInputStream = 1128 new ByteOrderAwarenessDataInputStream(exifBytes); 1129 1130 // Read byte align 1131 short byteOrder = dataInputStream.readShort(); 1132 switch (byteOrder) { 1133 case BYTE_ALIGN_II: 1134 if (DEBUG) { 1135 Log.d(TAG, "readExifSegment: Byte Align II"); 1136 } 1137 dataInputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 1138 break; 1139 case BYTE_ALIGN_MM: 1140 if (DEBUG) { 1141 Log.d(TAG, "readExifSegment: Byte Align MM"); 1142 } 1143 dataInputStream.setByteOrder(ByteOrder.BIG_ENDIAN); 1144 break; 1145 default: 1146 throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder)); 1147 } 1148 1149 int startCode = dataInputStream.readUnsignedShort(); 1150 if (startCode != 0x2a) { 1151 throw new IOException("Invalid exif start: " + Integer.toHexString(startCode)); 1152 } 1153 1154 // Read first ifd offset 1155 long firstIfdOffset = dataInputStream.readUnsignedInt(); 1156 if (firstIfdOffset < 8 || firstIfdOffset >= exifBytes.length) { 1157 throw new IOException("Invalid first Ifd offset: " + firstIfdOffset); 1158 } 1159 firstIfdOffset -= 8; 1160 if (firstIfdOffset > 0) { 1161 if (dataInputStream.skip(firstIfdOffset) != firstIfdOffset) 1162 throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset); 1163 } 1164 1165 // Read primary image TIFF image file directory. 1166 readImageFileDirectory(dataInputStream, IFD_TIFF_HINT); 1167 1168 // Process thumbnail. 1169 try { 1170 int jpegInterchangeFormat = Integer.parseInt( 1171 mAttributes.get(JPEG_INTERCHANGE_FORMAT_TAG.name)); 1172 int jpegInterchangeFormatLength = Integer.parseInt( 1173 mAttributes.get(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name)); 1174 // The following code limits the size of thumbnail size not to overflow EXIF data area. 1175 jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat 1176 + jpegInterchangeFormatLength, exifOffsetFromBeginning + exifBytes.length) 1177 - jpegInterchangeFormat; 1178 if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) { 1179 mHasThumbnail = true; 1180 mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat; 1181 mThumbnailLength = jpegInterchangeFormatLength; 1182 1183 // Do not store a thumbnail in memory if the given input can be re-read. 1184 if (mFileDescriptor == null && mFilename == null) { 1185 byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength]; 1186 dataInputStream.seek(jpegInterchangeFormat); 1187 dataInputStream.readFully(thumbnailBytes); 1188 mThumbnailBytes = thumbnailBytes; 1189 1190 if (DEBUG) { 1191 Bitmap bitmap = BitmapFactory.decodeByteArray( 1192 thumbnailBytes, 0, thumbnailBytes.length); 1193 Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: " 1194 + mThumbnailLength + ", width: " + bitmap.getWidth() + ", height: " 1195 + bitmap.getHeight()); 1196 } 1197 } 1198 } 1199 } catch (NumberFormatException e) { 1200 // Ignored the corrupted image. 1201 } 1202 1203 // For compatibility, keep data formats as follows. 1204 convertToInt(TAG_IMAGE_WIDTH); 1205 convertToInt(TAG_IMAGE_LENGTH); 1206 convertToInt(TAG_ORIENTATION); 1207 convertToInt(TAG_FLASH); 1208 convertToRational(TAG_FOCAL_LENGTH); 1209 convertToDouble(TAG_DIGITAL_ZOOM_RATIO); 1210 convertToDouble(TAG_EXPOSURE_TIME); 1211 convertToDouble(TAG_APERTURE); 1212 convertToDouble(TAG_SUBJECT_DISTANCE); 1213 convertToInt(TAG_ISO); 1214 convertToDouble(TAG_EXPOSURE_BIAS_VALUE); 1215 convertToInt(TAG_WHITE_BALANCE); 1216 convertToInt(TAG_LIGHT_SOURCE); 1217 convertToInt(TAG_METERING_MODE); 1218 convertToInt(TAG_EXPOSURE_PROGRAM); 1219 convertToInt(TAG_EXPOSURE_MODE); 1220 convertToRational(TAG_GPS_ALTITUDE); 1221 convertToInt(TAG_GPS_ALTITUDE_REF); 1222 convertToRational(TAG_GPS_LONGITUDE); 1223 convertToRational(TAG_GPS_LATITUDE); 1224 convertToTimetamp(TAG_GPS_TIMESTAMP); 1225 1226 // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag. 1227 String valueOfDateTimeOriginal = mAttributes.get("DateTimeOriginal"); 1228 if (valueOfDateTimeOriginal != null) { 1229 mAttributes.put(TAG_DATETIME, valueOfDateTimeOriginal); 1230 } 1231 1232 // Add the default value. 1233 if (!mAttributes.containsKey(TAG_IMAGE_WIDTH)) { 1234 mAttributes.put(TAG_IMAGE_WIDTH, "0"); 1235 } 1236 if (!mAttributes.containsKey(TAG_IMAGE_LENGTH)) { 1237 mAttributes.put(TAG_IMAGE_LENGTH, "0"); 1238 } 1239 if (!mAttributes.containsKey(TAG_ORIENTATION)) { 1240 mAttributes.put(TAG_ORIENTATION, "0"); 1241 } 1242 if (!mAttributes.containsKey(TAG_LIGHT_SOURCE)) { 1243 mAttributes.put(TAG_LIGHT_SOURCE, "0"); 1244 } 1245 } 1246 1247 // Converts the tag value to timestamp; Otherwise deletes the given tag. 1248 private void convertToTimetamp(String tagName) { 1249 String entryValue = mAttributes.get(tagName); 1250 if (entryValue == null) return; 1251 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1252 String[] components = entryValue.split(","); 1253 if (dataFormat == IFD_FORMAT_SRATIONAL && components.length == 3) { 1254 StringBuilder stringBuilder = new StringBuilder(); 1255 for (String component : components) { 1256 if (stringBuilder.length() > 0) { 1257 stringBuilder.append(":"); 1258 } 1259 String[] rationalNumber = component.split("/"); 1260 int numerator = Integer.parseInt(rationalNumber[0]); 1261 int denominator = Integer.parseInt(rationalNumber[1]); 1262 if (denominator == 0) { 1263 numerator = 0; 1264 denominator = 1; 1265 } 1266 int value = numerator / denominator; 1267 stringBuilder.append(String.format("%02d", value)); 1268 } 1269 mAttributes.put(tagName, stringBuilder.toString()); 1270 } else if (dataFormat != IFD_FORMAT_STRING) { 1271 mAttributes.remove(tagName); 1272 } 1273 } 1274 1275 // Checks the tag value of a given tag formatted in double type; Otherwise try to convert it to 1276 // double type or delete it. 1277 private void convertToDouble(String tagName) { 1278 String entryValue = mAttributes.get(tagName); 1279 if (entryValue == null) return; 1280 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1281 switch (dataFormat) { 1282 case IFD_FORMAT_SRATIONAL: { 1283 StringBuilder stringBuilder = new StringBuilder(); 1284 String[] components = entryValue.split(","); 1285 for (String component : components) { 1286 if (stringBuilder.length() > 0) { 1287 stringBuilder.append(","); 1288 } 1289 String[] rationalNumber = component.split("/"); 1290 int numerator = Integer.parseInt(rationalNumber[0]); 1291 int denominator = Integer.parseInt(rationalNumber[1]); 1292 if (denominator == 0) { 1293 numerator = 0; 1294 denominator = 1; 1295 } 1296 stringBuilder.append((double) numerator / denominator); 1297 } 1298 mAttributes.put(tagName, stringBuilder.toString()); 1299 break; 1300 } 1301 case IFD_FORMAT_DOUBLE: 1302 // Keep it as is. 1303 break; 1304 default: 1305 mAttributes.remove(tagName); 1306 break; 1307 } 1308 } 1309 1310 // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value. 1311 private void convertToRational(String tagName) { 1312 String entryValue = mAttributes.get(tagName); 1313 if (entryValue == null) return; 1314 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1315 switch (dataFormat) { 1316 case IFD_FORMAT_SLONG: 1317 case IFD_FORMAT_DOUBLE: { 1318 StringBuilder stringBuilder = new StringBuilder(); 1319 String[] components = entryValue.split(","); 1320 for (String component : components) { 1321 if (stringBuilder.length() > 0) { 1322 stringBuilder.append(","); 1323 } 1324 double doubleValue = Double.parseDouble(component); 1325 stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000); 1326 } 1327 mAttributes.put(tagName, stringBuilder.toString()); 1328 break; 1329 } 1330 case IFD_FORMAT_SRATIONAL: 1331 // Keep it as is. 1332 break; 1333 default: 1334 mAttributes.remove(tagName); 1335 break; 1336 } 1337 } 1338 1339 // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value. 1340 private void convertToInt(String tagName) { 1341 String entryValue = mAttributes.get(tagName); 1342 if (entryValue == null) return; 1343 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1344 if (dataFormat != IFD_FORMAT_SLONG) { 1345 mAttributes.remove(tagName); 1346 } 1347 } 1348 1349 // Reads image file directory, which is a tag group in EXIF. 1350 private void readImageFileDirectory(ByteOrderAwarenessDataInputStream dataInputStream, int hint) 1351 throws IOException { 1352 // See JEITA CP-3451 Figure 5. page 9. 1353 short numberOfDirectoryEntry = dataInputStream.readShort(); 1354 1355 if (DEBUG) { 1356 Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry); 1357 } 1358 1359 for (short i = 0; i < numberOfDirectoryEntry; ++i) { 1360 int tagNumber = dataInputStream.readUnsignedShort(); 1361 int dataFormat = dataInputStream.readUnsignedShort(); 1362 int numberOfComponents = dataInputStream.readInt(); 1363 long nextEntryOffset = dataInputStream.peek() + 4; // next four bytes is for data 1364 // offset or value. 1365 1366 if (DEBUG) { 1367 Log.d(TAG, String.format("tagNumber: %d, dataFormat: %d, numberOfComponents: %d", 1368 tagNumber, dataFormat, numberOfComponents)); 1369 } 1370 1371 // Read a value from data field or seek to the value offset which is stored in data 1372 // field if the size of the entry value is bigger than 4. 1373 int byteCount = numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat]; 1374 if (byteCount > 4) { 1375 long offset = dataInputStream.readUnsignedInt(); 1376 if (DEBUG) { 1377 Log.d(TAG, "seek to data offset: " + offset); 1378 } 1379 dataInputStream.seek(offset); 1380 } 1381 1382 // Look up a corresponding tag from tag number 1383 String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber); 1384 // Skip if the parsed tag number is not defined. 1385 if (tagName == null) { 1386 dataInputStream.seek(nextEntryOffset); 1387 continue; 1388 } 1389 1390 // Recursively parse IFD when a IFD pointer tag appears. 1391 int innerIfdHint = getIfdHintFromTagNumber(tagNumber); 1392 if (innerIfdHint >= 0) { 1393 long offset = -1L; 1394 // Get offset from data field 1395 switch (dataFormat) { 1396 case IFD_FORMAT_USHORT: { 1397 offset = dataInputStream.readUnsignedShort(); 1398 break; 1399 } 1400 case IFD_FORMAT_SSHORT: { 1401 offset = dataInputStream.readShort(); 1402 break; 1403 } 1404 case IFD_FORMAT_ULONG: { 1405 offset = dataInputStream.readUnsignedInt(); 1406 break; 1407 } 1408 case IFD_FORMAT_SLONG: { 1409 offset = dataInputStream.readInt(); 1410 break; 1411 } 1412 default: { 1413 // Nothing to do 1414 break; 1415 } 1416 } 1417 if (DEBUG) { 1418 Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tagName)); 1419 } 1420 if (offset > 0L) { 1421 dataInputStream.seek(offset); 1422 readImageFileDirectory(dataInputStream, innerIfdHint); 1423 } 1424 1425 dataInputStream.seek(nextEntryOffset); 1426 continue; 1427 } 1428 1429 if (numberOfComponents == 1 || dataFormat == IFD_FORMAT_STRING 1430 || dataFormat == IFD_FORMAT_UNDEFINED) { 1431 String entryValue = readExifEntryValue( 1432 dataInputStream, dataFormat, numberOfComponents); 1433 if (entryValue != null) { 1434 mAttributes.put(tagName, entryValue); 1435 } 1436 } else { 1437 StringBuilder entryValueBuilder = new StringBuilder(); 1438 for (int c = 0; c < numberOfComponents; ++c) { 1439 if (entryValueBuilder.length() > 0) { 1440 entryValueBuilder.append(","); 1441 } 1442 entryValueBuilder.append(readExifEntryValue( 1443 dataInputStream, dataFormat, numberOfComponents)); 1444 } 1445 mAttributes.put(tagName, entryValueBuilder.toString()); 1446 } 1447 1448 if (dataInputStream.peek() != nextEntryOffset) { 1449 dataInputStream.seek(nextEntryOffset); 1450 } 1451 } 1452 1453 long nextIfdOffset = dataInputStream.readUnsignedInt(); 1454 if (DEBUG) { 1455 Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset)); 1456 } 1457 // The next IFD offset needs to be bigger than 8 since the first IFD offset is at least 8. 1458 if (nextIfdOffset > 8) { 1459 dataInputStream.seek(nextIfdOffset); 1460 readImageFileDirectory(dataInputStream, IFD_THUMBNAIL_HINT); 1461 } 1462 } 1463 1464 // Reads a value from where the entry value are stored. 1465 private String readExifEntryValue(ByteOrderAwarenessDataInputStream dataInputStream, 1466 int dataFormat, int numberOfComponents) throws IOException { 1467 // See TIFF 6.0 spec Types. page 15. 1468 switch (dataFormat) { 1469 case IFD_FORMAT_BYTE: { 1470 return String.valueOf(dataInputStream.readByte()); 1471 } 1472 case IFD_FORMAT_SBYTE: { 1473 return String.valueOf(dataInputStream.readByte() & 0xff); 1474 } 1475 case IFD_FORMAT_USHORT: { 1476 return String.valueOf(dataInputStream.readUnsignedShort()); 1477 } 1478 case IFD_FORMAT_SSHORT: { 1479 return String.valueOf(dataInputStream.readUnsignedInt()); 1480 } 1481 case IFD_FORMAT_ULONG: { 1482 return String.valueOf(dataInputStream.readInt()); 1483 } 1484 case IFD_FORMAT_SLONG: { 1485 return String.valueOf(dataInputStream.readInt()); 1486 } 1487 case IFD_FORMAT_URATIONAL: 1488 case IFD_FORMAT_SRATIONAL: { 1489 int numerator = dataInputStream.readInt(); 1490 int denominator = dataInputStream.readInt(); 1491 return numerator + "/" + denominator; 1492 } 1493 case IFD_FORMAT_SINGLE: { 1494 return String.valueOf(dataInputStream.readFloat()); 1495 } 1496 case IFD_FORMAT_DOUBLE: { 1497 return String.valueOf(dataInputStream.readDouble()); 1498 } 1499 case IFD_FORMAT_UNDEFINED: // Usually UNDEFINED format is ASCII. 1500 case IFD_FORMAT_STRING: { 1501 byte[] bytes = new byte[numberOfComponents]; 1502 dataInputStream.readFully(bytes); 1503 int index = 0; 1504 if (numberOfComponents >= EXIF_ASCII_PREFIX.length) { 1505 boolean same = true; 1506 for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) { 1507 if (bytes[i] != EXIF_ASCII_PREFIX[i]) { 1508 same = false; 1509 break; 1510 } 1511 } 1512 if (same) { 1513 index = EXIF_ASCII_PREFIX.length; 1514 } 1515 } 1516 1517 StringBuilder stringBuilder = new StringBuilder(); 1518 while (true) { 1519 int ch = bytes[index]; 1520 if (ch < 0) 1521 throw new EOFException(); 1522 if (ch == 0) 1523 break; 1524 if (ch >= 32) 1525 stringBuilder.append((char) ch); 1526 else 1527 stringBuilder.append('?'); 1528 ++index; 1529 if (index == numberOfComponents) 1530 break; 1531 } 1532 return stringBuilder.toString(); 1533 } 1534 default: { 1535 // Nothing to do 1536 return null; 1537 } 1538 } 1539 } 1540 1541 // Gets the corresponding IFD group index of the given tag number for writing Exif Tags. 1542 private static int getIfdHintFromTagNumber(int tagNumber) { 1543 for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) { 1544 if (IFD_POINTER_TAGS[i].number == tagNumber) 1545 return IFD_POINTER_TAG_HINTS[i]; 1546 } 1547 return -1; 1548 } 1549 1550 // Writes an Exif segment into the given output stream. 1551 private int writeExifSegment(ExifDataOutputStream dataOutputStream, int exifOffsetFromBeginning) 1552 throws IOException { 1553 // The following variables are for calculating each IFD tag group size in bytes. 1554 int[] ifdOffsets = new int[EXIF_TAGS.length]; 1555 int[] ifdDataSizes = new int[EXIF_TAGS.length]; 1556 1557 // Maps to store tags per IFD tag group 1558 HashMap[] ifdTags = new HashMap[EXIF_TAGS.length]; 1559 for (int i = 0; i < EXIF_TAGS.length; ++i) { 1560 ifdTags[i] = new HashMap(); 1561 } 1562 1563 // Remove IFD pointer tags (we'll re-add it later.) 1564 for (ExifTag tag : IFD_POINTER_TAGS) { 1565 mAttributes.remove(tag.name); 1566 } 1567 1568 // Assign tags to the corresponding group 1569 for (Map.Entry<String, String> entry : mAttributes.entrySet()) { 1570 Pair<Integer, Integer> pair = sExifTagMapForWriting.get(entry.getKey()); 1571 if (pair != null) { 1572 int tagNumber = pair.first; 1573 int hint = pair.second; 1574 ifdTags[hint].put(tagNumber, entry.getValue()); 1575 } 1576 } 1577 1578 // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD 1579 // offset when there is one or more tags in the thumbnail IFD. 1580 if (!ifdTags[IFD_INTEROPERABILITY_HINT].isEmpty()) { 1581 ifdTags[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].number, "0"); 1582 } 1583 if (!ifdTags[IFD_EXIF_HINT].isEmpty()) { 1584 ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].number, "0"); 1585 } 1586 if (!ifdTags[IFD_GPS_HINT].isEmpty()) { 1587 ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].number, "0"); 1588 } 1589 if (mHasThumbnail) { 1590 ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.number, "0"); 1591 ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.number, 1592 String.valueOf(mThumbnailLength)); 1593 } 1594 1595 // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry 1596 // value which has a bigger size than 4 bytes. 1597 for (int i = 0; i < 5; ++i) { 1598 int sum = 0; 1599 for (Object entry : ifdTags[i].entrySet()) { 1600 String entryValue = (String) ((Map.Entry) entry).getValue(); 1601 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1602 int size = getSizeOfExifEntryValue(dataFormat, entryValue); 1603 if (size > 4) { 1604 sum += size; 1605 } 1606 } 1607 ifdDataSizes[i] += sum; 1608 } 1609 1610 // Calculate IFD offsets. 1611 int position = 8; 1612 for (int hint = 0; hint < EXIF_TAGS.length; ++hint) { 1613 if (!ifdTags[hint].isEmpty()) { 1614 ifdOffsets[hint] = position; 1615 position += 2 + ifdTags[hint].size() * 12 + 4 + ifdDataSizes[hint]; 1616 } 1617 } 1618 if (mHasThumbnail) { 1619 int thumbnailOffset = position; 1620 ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.number, 1621 String.valueOf(thumbnailOffset)); 1622 ifdTags[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.number, 1623 String.valueOf(mThumbnailLength)); 1624 mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset; 1625 position += mThumbnailLength; 1626 } 1627 1628 // Calculate the total size 1629 int totalSize = position + 8; // eight bytes is for header part. 1630 if (DEBUG) { 1631 Log.d(TAG, "totalSize length: " + totalSize); 1632 for (int i = 0; i < 5; ++i) { 1633 Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d", 1634 i, ifdOffsets[i], ifdTags[i].size(), ifdDataSizes[i])); 1635 } 1636 } 1637 1638 // Update IFD pointer tags with the calculated offsets. 1639 if (!ifdTags[IFD_EXIF_HINT].isEmpty()) { 1640 ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].number, 1641 String.valueOf(ifdOffsets[IFD_EXIF_HINT])); 1642 } 1643 if (!ifdTags[IFD_GPS_HINT].isEmpty()) { 1644 ifdTags[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].number, 1645 String.valueOf(ifdOffsets[IFD_GPS_HINT])); 1646 } 1647 if (!ifdTags[IFD_INTEROPERABILITY_HINT].isEmpty()) { 1648 ifdTags[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].number, 1649 String.valueOf(ifdOffsets[IFD_INTEROPERABILITY_HINT])); 1650 } 1651 1652 // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10. 1653 dataOutputStream.writeUnsignedShort(totalSize); 1654 dataOutputStream.write(IDENTIFIER_APP1); 1655 dataOutputStream.writeShort(BYTE_ALIGN_MM); 1656 dataOutputStream.writeUnsignedShort(0x2a); 1657 dataOutputStream.writeUnsignedInt(8); 1658 1659 // Write IFD groups. See JEITA CP-3451C Figure 7. page 12. 1660 for (int hint = 0; hint < EXIF_TAGS.length; ++hint) { 1661 if (!ifdTags[hint].isEmpty()) { 1662 // See JEITA CP-3451C 4.6.2 IFD structure. page 13. 1663 // Write entry count 1664 dataOutputStream.writeUnsignedShort(ifdTags[hint].size()); 1665 1666 // Write entry info 1667 int dataOffset = ifdOffsets[hint] + 2 + ifdTags[hint].size() * 12 + 4; 1668 for (Object obj : ifdTags[hint].entrySet()) { 1669 Map.Entry entry = (Map.Entry) obj; 1670 int tagNumber = (int) entry.getKey(); 1671 String entryValue = (String) entry.getValue(); 1672 1673 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1674 int numberOfComponents = getNumberOfComponentsInExifEntryValue(dataFormat, 1675 entryValue); 1676 int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue); 1677 1678 dataOutputStream.writeUnsignedShort(tagNumber); 1679 dataOutputStream.writeUnsignedShort(dataFormat); 1680 dataOutputStream.writeInt(numberOfComponents); 1681 if (byteCount > 4) { 1682 dataOutputStream.writeUnsignedInt(dataOffset); 1683 dataOffset += byteCount; 1684 } else { 1685 int bytesWritten = writeExifEntryValue(dataOutputStream, entryValue); 1686 // Fill zero up to 4 bytes 1687 if (bytesWritten < 4) { 1688 for (int i = bytesWritten; i < 4; ++i) { 1689 dataOutputStream.write(0); 1690 } 1691 } 1692 } 1693 } 1694 1695 // Write the next offset. It writes the offset of thumbnail IFD if there is one or 1696 // more tags in the thumbnail IFD when the current IFD is the primary image TIFF 1697 // IFD; Otherwise 0. 1698 if (hint == 0 && !ifdTags[IFD_THUMBNAIL_HINT].isEmpty()) { 1699 dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_THUMBNAIL_HINT]); 1700 } else { 1701 dataOutputStream.writeUnsignedInt(0); 1702 } 1703 1704 // Write values of data field exceeding 4 bytes after the next offset. 1705 for (Object obj : ifdTags[hint].entrySet()) { 1706 Map.Entry entry = (Map.Entry) obj; 1707 String entryValue = (String) entry.getValue(); 1708 1709 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1710 int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue); 1711 if (byteCount > 4) { 1712 writeExifEntryValue(dataOutputStream, entryValue); 1713 } 1714 } 1715 } 1716 } 1717 1718 // Write thumbnail 1719 if (mHasThumbnail) { 1720 dataOutputStream.write(getThumbnail()); 1721 } 1722 1723 return totalSize; 1724 } 1725 1726 // Writes EXIF entry value and its entry value type will be automatically determined. 1727 private static int writeExifEntryValue(ExifDataOutputStream dataOutputStream, String entryValue) 1728 throws IOException { 1729 int bytesWritten = 0; 1730 int dataFormat = getDataFormatOfExifEntryValue(entryValue); 1731 1732 // Values can be composed of several components. Each component is separated by char ','. 1733 String[] components = entryValue.split(","); 1734 for (String component : components) { 1735 switch (dataFormat) { 1736 case IFD_FORMAT_SLONG: 1737 dataOutputStream.writeInt(Integer.parseInt(component)); 1738 bytesWritten += 4; 1739 break; 1740 case IFD_FORMAT_DOUBLE: 1741 dataOutputStream.writeDouble(Double.parseDouble(component)); 1742 bytesWritten += 8; 1743 break; 1744 case IFD_FORMAT_STRING: 1745 byte[] asciiArray = (component + '\0').getBytes(Charset.forName("US-ASCII")); 1746 dataOutputStream.write(asciiArray); 1747 bytesWritten += asciiArray.length; 1748 break; 1749 case IFD_FORMAT_SRATIONAL: 1750 String[] rationalNumber = component.split("/"); 1751 dataOutputStream.writeInt(Integer.parseInt(rationalNumber[0])); 1752 dataOutputStream.writeInt(Integer.parseInt(rationalNumber[1])); 1753 bytesWritten += 8; 1754 break; 1755 default: 1756 throw new IllegalArgumentException(); 1757 } 1758 } 1759 return bytesWritten; 1760 } 1761 1762 // Determines the data format of EXIF entry value. 1763 private static int getDataFormatOfExifEntryValue(String entryValue) { 1764 // See TIFF 6.0 spec Types. page 15. 1765 // Take the first component if there are more than one component. 1766 if (entryValue.contains(",")) { 1767 entryValue = entryValue.split(",")[0]; 1768 } 1769 1770 if (entryValue.contains("/")) { 1771 return IFD_FORMAT_SRATIONAL; 1772 } 1773 try { 1774 Integer.parseInt(entryValue); 1775 return IFD_FORMAT_SLONG; 1776 } catch (NumberFormatException e) { 1777 // Ignored 1778 } 1779 try { 1780 Double.parseDouble(entryValue); 1781 return IFD_FORMAT_DOUBLE; 1782 } catch (NumberFormatException e) { 1783 // Ignored 1784 } 1785 return IFD_FORMAT_STRING; 1786 } 1787 1788 // Determines the size of EXIF entry value. 1789 private static int getSizeOfExifEntryValue(int dataFormat, String entryValue) { 1790 // See TIFF 6.0 spec Types page 15. 1791 int bytesEstimated = 0; 1792 String[] components = entryValue.split(","); 1793 for (String component : components) { 1794 switch (dataFormat) { 1795 case IFD_FORMAT_SLONG: 1796 bytesEstimated += 4; 1797 break; 1798 case IFD_FORMAT_DOUBLE: 1799 bytesEstimated += 8; 1800 break; 1801 case IFD_FORMAT_STRING: 1802 bytesEstimated 1803 += (component + '\0').getBytes(Charset.forName("US-ASCII")).length; 1804 break; 1805 case IFD_FORMAT_SRATIONAL: 1806 bytesEstimated += 8; 1807 break; 1808 default: 1809 throw new IllegalArgumentException(); 1810 } 1811 } 1812 return bytesEstimated; 1813 } 1814 1815 // Determines the number of components of EXIF entry value. 1816 private static int getNumberOfComponentsInExifEntryValue(int dataFormat, String entryValue) { 1817 if (dataFormat == IFD_FORMAT_STRING) { 1818 return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length; 1819 } 1820 int count = 1; 1821 for (int i = 0; i < entryValue.length(); ++i) { 1822 if (entryValue.charAt(i) == ',') { 1823 ++count; 1824 } 1825 } 1826 return count; 1827 } 1828 1829 // An input stream to parse EXIF data area, which can be written in either little or big endian 1830 // order. 1831 private static class ByteOrderAwarenessDataInputStream extends ByteArrayInputStream { 1832 private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN; 1833 private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN; 1834 1835 private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN; 1836 private final long mLength; 1837 private long mPosition; 1838 1839 public ByteOrderAwarenessDataInputStream(byte[] bytes) { 1840 super(bytes); 1841 mLength = bytes.length; 1842 mPosition = 0L; 1843 } 1844 1845 public void setByteOrder(ByteOrder byteOrder) { 1846 mByteOrder = byteOrder; 1847 } 1848 1849 public void seek(long byteCount) throws IOException { 1850 mPosition = 0L; 1851 reset(); 1852 if (skip(byteCount) != byteCount) 1853 throw new IOException("Couldn't seek up to the byteCount"); 1854 } 1855 1856 public long peek() { 1857 return mPosition; 1858 } 1859 1860 public void readFully(byte[] buffer) throws IOException { 1861 mPosition += buffer.length; 1862 if (mPosition > mLength) 1863 throw new EOFException(); 1864 if (super.read(buffer, 0, buffer.length) != buffer.length) { 1865 throw new IOException("Couldn't read up to the length of buffer"); 1866 } 1867 } 1868 1869 public byte readByte() throws IOException { 1870 ++mPosition; 1871 if (mPosition > mLength) 1872 throw new EOFException(); 1873 int ch = super.read(); 1874 if (ch < 0) 1875 throw new EOFException(); 1876 return (byte) ch; 1877 } 1878 1879 public short readShort() throws IOException { 1880 mPosition += 2; 1881 if (mPosition > mLength) 1882 throw new EOFException(); 1883 int ch1 = super.read(); 1884 int ch2 = super.read(); 1885 if ((ch1 | ch2) < 0) 1886 throw new EOFException(); 1887 if (mByteOrder == LITTLE_ENDIAN) { 1888 return (short) ((ch2 << 8) + (ch1)); 1889 } else if (mByteOrder == BIG_ENDIAN) { 1890 return (short) ((ch1 << 8) + (ch2)); 1891 } 1892 throw new IOException("Invalid byte order: " + mByteOrder); 1893 } 1894 1895 public int readInt() throws IOException { 1896 mPosition += 4; 1897 if (mPosition > mLength) 1898 throw new EOFException(); 1899 int ch1 = super.read(); 1900 int ch2 = super.read(); 1901 int ch3 = super.read(); 1902 int ch4 = super.read(); 1903 if ((ch1 | ch2 | ch3 | ch4) < 0) 1904 throw new EOFException(); 1905 if (mByteOrder == LITTLE_ENDIAN) { 1906 return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1); 1907 } else if (mByteOrder == BIG_ENDIAN) { 1908 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4); 1909 } 1910 throw new IOException("Invalid byte order: " + mByteOrder); 1911 } 1912 1913 @Override 1914 public long skip(long byteCount) { 1915 long skipped = super.skip(Math.min(byteCount, mLength - mPosition)); 1916 mPosition += skipped; 1917 return skipped; 1918 } 1919 1920 public int readUnsignedShort() throws IOException { 1921 mPosition += 2; 1922 if (mPosition > mLength) 1923 throw new EOFException(); 1924 int ch1 = super.read(); 1925 int ch2 = super.read(); 1926 if ((ch1 | ch2) < 0) 1927 throw new EOFException(); 1928 if (mByteOrder == LITTLE_ENDIAN) { 1929 return ((ch2 << 8) + (ch1)); 1930 } else if (mByteOrder == BIG_ENDIAN) { 1931 return ((ch1 << 8) + (ch2)); 1932 } 1933 throw new IOException("Invalid byte order: " + mByteOrder); 1934 } 1935 1936 public long readUnsignedInt() throws IOException { 1937 return readInt() & 0xffffffffL; 1938 } 1939 1940 public long readLong() throws IOException { 1941 mPosition += 8; 1942 if (mPosition > mLength) 1943 throw new EOFException(); 1944 int ch1 = super.read(); 1945 int ch2 = super.read(); 1946 int ch3 = super.read(); 1947 int ch4 = super.read(); 1948 int ch5 = super.read(); 1949 int ch6 = super.read(); 1950 int ch7 = super.read(); 1951 int ch8 = super.read(); 1952 if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) 1953 throw new EOFException(); 1954 if (mByteOrder == LITTLE_ENDIAN) { 1955 return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40) 1956 + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16) 1957 + ((long) ch2 << 8) + (long) ch1); 1958 } else if (mByteOrder == BIG_ENDIAN) { 1959 return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40) 1960 + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16) 1961 + ((long) ch7 << 8) + (long) ch8); 1962 } 1963 throw new IOException("Invalid byte order: " + mByteOrder); 1964 } 1965 1966 public float readFloat() throws IOException { 1967 return Float.intBitsToFloat(readInt()); 1968 } 1969 1970 public double readDouble() throws IOException { 1971 return Double.longBitsToDouble(readLong()); 1972 } 1973 } 1974 1975 // An output stream to write EXIF data area, that will be written in big endian byte order. 1976 private static class ExifDataOutputStream extends DataOutputStream { 1977 public ExifDataOutputStream(OutputStream out) { 1978 super(out); 1979 } 1980 1981 public void writeUnsignedShort(int val) throws IOException { 1982 writeShort((short) val); 1983 } 1984 1985 public void writeUnsignedInt(long val) throws IOException { 1986 writeInt((int) val); 1987 } 1988 } 1989 1990 // JNI methods for RAW formats. 1991 private static native void initRawNative(); 1992 private static native HashMap getRawAttributesNative(FileDescriptor fileDescriptor); 1993} 1994