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