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