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