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 17 package android.media; 18 19 import android.annotation.CurrentTimeMillisLong; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.compat.annotation.UnsupportedAppUsage; 24 import android.content.res.AssetManager; 25 import android.graphics.Bitmap; 26 import android.graphics.BitmapFactory; 27 import android.os.Build; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.system.OsConstants; 31 import android.util.Log; 32 import android.util.Pair; 33 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.util.ArrayUtils; 36 37 import libcore.io.IoUtils; 38 import libcore.io.Streams; 39 40 import java.io.BufferedInputStream; 41 import java.io.BufferedOutputStream; 42 import java.io.ByteArrayInputStream; 43 import java.io.ByteArrayOutputStream; 44 import java.io.DataInput; 45 import java.io.DataInputStream; 46 import java.io.EOFException; 47 import java.io.File; 48 import java.io.FileDescriptor; 49 import java.io.FileInputStream; 50 import java.io.FileNotFoundException; 51 import java.io.FileOutputStream; 52 import java.io.FilterOutputStream; 53 import java.io.IOException; 54 import java.io.InputStream; 55 import java.io.OutputStream; 56 import java.lang.annotation.Retention; 57 import java.lang.annotation.RetentionPolicy; 58 import java.nio.ByteBuffer; 59 import java.nio.ByteOrder; 60 import java.nio.charset.Charset; 61 import java.text.ParsePosition; 62 import java.text.SimpleDateFormat; 63 import java.util.Arrays; 64 import java.util.Date; 65 import java.util.HashMap; 66 import java.util.HashSet; 67 import java.util.Locale; 68 import java.util.Map; 69 import java.util.Set; 70 import java.util.TimeZone; 71 import java.util.UUID; 72 import java.util.regex.Matcher; 73 import java.util.regex.Pattern; 74 import java.util.zip.CRC32; 75 76 /** 77 * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file. 78 * <p> 79 * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF and HEIF. 80 * <p> 81 * Attribute mutation is supported for JPEG image files. 82 * <p> 83 * Note: It is recommended to use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a> 84 * <a href="{@docRoot}reference/androidx/exifinterface/media/ExifInterface.html">ExifInterface 85 * Library</a> since it is a superset of this class. In addition to the functionalities of this 86 * class, it supports parsing extra metadata such as exposure and data compression information 87 * as well as setting extra metadata such as GPS and datetime information. 88 */ 89 public class ExifInterface { 90 private static final String TAG = "ExifInterface"; 91 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 92 93 // The Exif tag names. See Tiff 6.0 Section 3 and Section 8. 94 /** Type is String. */ 95 public static final String TAG_ARTIST = "Artist"; 96 /** Type is int. */ 97 public static final String TAG_BITS_PER_SAMPLE = "BitsPerSample"; 98 /** Type is int. */ 99 public static final String TAG_COMPRESSION = "Compression"; 100 /** Type is String. */ 101 public static final String TAG_COPYRIGHT = "Copyright"; 102 /** Type is String. */ 103 public static final String TAG_DATETIME = "DateTime"; 104 /** Type is String. */ 105 public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription"; 106 /** Type is int. */ 107 public static final String TAG_IMAGE_LENGTH = "ImageLength"; 108 /** Type is int. */ 109 public static final String TAG_IMAGE_WIDTH = "ImageWidth"; 110 /** Type is int. */ 111 public static final String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat"; 112 /** Type is int. */ 113 public static final String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength"; 114 /** Type is String. */ 115 public static final String TAG_MAKE = "Make"; 116 /** Type is String. */ 117 public static final String TAG_MODEL = "Model"; 118 /** Type is int. */ 119 public static final String TAG_ORIENTATION = "Orientation"; 120 /** Type is int. */ 121 public static final String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation"; 122 /** Type is int. */ 123 public static final String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration"; 124 /** Type is rational. */ 125 public static final String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities"; 126 /** Type is rational. */ 127 public static final String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite"; 128 /** Type is int. */ 129 public static final String TAG_RESOLUTION_UNIT = "ResolutionUnit"; 130 /** Type is int. */ 131 public static final String TAG_ROWS_PER_STRIP = "RowsPerStrip"; 132 /** Type is int. */ 133 public static final String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel"; 134 /** Type is String. */ 135 public static final String TAG_SOFTWARE = "Software"; 136 /** Type is int. */ 137 public static final String TAG_STRIP_BYTE_COUNTS = "StripByteCounts"; 138 /** Type is int. */ 139 public static final String TAG_STRIP_OFFSETS = "StripOffsets"; 140 /** Type is int. */ 141 public static final String TAG_TRANSFER_FUNCTION = "TransferFunction"; 142 /** Type is rational. */ 143 public static final String TAG_WHITE_POINT = "WhitePoint"; 144 /** Type is rational. */ 145 public static final String TAG_X_RESOLUTION = "XResolution"; 146 /** Type is rational. */ 147 public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients"; 148 /** Type is int. */ 149 public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning"; 150 /** Type is int. */ 151 public static final String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling"; 152 /** Type is rational. */ 153 public static final String TAG_Y_RESOLUTION = "YResolution"; 154 /** Type is rational. */ 155 public static final String TAG_APERTURE_VALUE = "ApertureValue"; 156 /** Type is rational. */ 157 public static final String TAG_BRIGHTNESS_VALUE = "BrightnessValue"; 158 /** Type is String. */ 159 public static final String TAG_CFA_PATTERN = "CFAPattern"; 160 /** Type is int. */ 161 public static final String TAG_COLOR_SPACE = "ColorSpace"; 162 /** Type is String. */ 163 public static final String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration"; 164 /** Type is rational. */ 165 public static final String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel"; 166 /** Type is int. */ 167 public static final String TAG_CONTRAST = "Contrast"; 168 /** Type is int. */ 169 public static final String TAG_CUSTOM_RENDERED = "CustomRendered"; 170 /** Type is String. */ 171 public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized"; 172 /** Type is String. */ 173 public static final String TAG_DATETIME_ORIGINAL = "DateTimeOriginal"; 174 /** Type is String. */ 175 public static final String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription"; 176 /** Type is double. */ 177 public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio"; 178 /** Type is String. */ 179 public static final String TAG_EXIF_VERSION = "ExifVersion"; 180 /** Type is double. */ 181 public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue"; 182 /** Type is rational. */ 183 public static final String TAG_EXPOSURE_INDEX = "ExposureIndex"; 184 /** Type is int. */ 185 public static final String TAG_EXPOSURE_MODE = "ExposureMode"; 186 /** Type is int. */ 187 public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram"; 188 /** Type is double. */ 189 public static final String TAG_EXPOSURE_TIME = "ExposureTime"; 190 /** Type is double. */ 191 public static final String TAG_F_NUMBER = "FNumber"; 192 /** 193 * Type is double. 194 * 195 * @deprecated use {@link #TAG_F_NUMBER} instead 196 */ 197 @Deprecated 198 public static final String TAG_APERTURE = "FNumber"; 199 /** Type is String. */ 200 public static final String TAG_FILE_SOURCE = "FileSource"; 201 /** Type is int. */ 202 public static final String TAG_FLASH = "Flash"; 203 /** Type is rational. */ 204 public static final String TAG_FLASH_ENERGY = "FlashEnergy"; 205 /** Type is String. */ 206 public static final String TAG_FLASHPIX_VERSION = "FlashpixVersion"; 207 /** Type is rational. */ 208 public static final String TAG_FOCAL_LENGTH = "FocalLength"; 209 /** Type is int. */ 210 public static final String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm"; 211 /** Type is int. */ 212 public static final String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit"; 213 /** Type is rational. */ 214 public static final String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution"; 215 /** Type is rational. */ 216 public static final String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution"; 217 /** Type is int. */ 218 public static final String TAG_GAIN_CONTROL = "GainControl"; 219 /** Type is int. */ 220 public static final String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings"; 221 /** 222 * Type is int. 223 * 224 * @deprecated use {@link #TAG_ISO_SPEED_RATINGS} instead 225 */ 226 @Deprecated 227 public static final String TAG_ISO = "ISOSpeedRatings"; 228 /** Type is String. */ 229 public static final String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID"; 230 /** Type is int. */ 231 public static final String TAG_LIGHT_SOURCE = "LightSource"; 232 /** Type is String. */ 233 public static final String TAG_MAKER_NOTE = "MakerNote"; 234 /** Type is rational. */ 235 public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue"; 236 /** Type is int. */ 237 public static final String TAG_METERING_MODE = "MeteringMode"; 238 /** Type is int. */ 239 public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType"; 240 /** Type is String. */ 241 public static final String TAG_OECF = "OECF"; 242 /** 243 * <p>A tag used to record the offset from UTC (the time difference from Universal Time 244 * Coordinated including daylight saving time) of the time of DateTime tag. The format when 245 * recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When 246 * the offsets are unknown, all the character spaces except colons (":") should be filled 247 * with blank characters, or else the Interoperability field should be filled with blank 248 * characters. The character string length is 7 Bytes including NULL for termination. When 249 * the field is left blank, it is treated as unknown.</p> 250 * 251 * <ul> 252 * <li>Tag = 36880</li> 253 * <li>Type = String</li> 254 * <li>Length = 7</li> 255 * <li>Default = None</li> 256 * </ul> 257 */ 258 public static final String TAG_OFFSET_TIME = "OffsetTime"; 259 /** 260 * <p>A tag used to record the offset from UTC (the time difference from Universal Time 261 * Coordinated including daylight saving time) of the time of DateTimeOriginal tag. The format 262 * when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When 263 * the offsets are unknown, all the character spaces except colons (":") should be filled 264 * with blank characters, or else the Interoperability field should be filled with blank 265 * characters. The character string length is 7 Bytes including NULL for termination. When 266 * the field is left blank, it is treated as unknown.</p> 267 * 268 * <ul> 269 * <li>Tag = 36881</li> 270 * <li>Type = String</li> 271 * <li>Length = 7</li> 272 * <li>Default = None</li> 273 * </ul> 274 */ 275 public static final String TAG_OFFSET_TIME_ORIGINAL = "OffsetTimeOriginal"; 276 /** 277 * <p>A tag used to record the offset from UTC (the time difference from Universal Time 278 * Coordinated including daylight saving time) of the time of DateTimeDigitized tag. The format 279 * when recording the offset is "±HH:MM". The part of "±" shall be recorded as "+" or "-". When 280 * the offsets are unknown, all the character spaces except colons (":") should be filled 281 * with blank characters, or else the Interoperability field should be filled with blank 282 * characters. The character string length is 7 Bytes including NULL for termination. When 283 * the field is left blank, it is treated as unknown.</p> 284 * 285 * <ul> 286 * <li>Tag = 36882</li> 287 * <li>Type = String</li> 288 * <li>Length = 7</li> 289 * <li>Default = None</li> 290 * </ul> 291 */ 292 public static final String TAG_OFFSET_TIME_DIGITIZED = "OffsetTimeDigitized"; 293 /** Type is int. */ 294 public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension"; 295 /** Type is int. */ 296 public static final String TAG_PIXEL_Y_DIMENSION = "PixelYDimension"; 297 /** Type is String. */ 298 public static final String TAG_RELATED_SOUND_FILE = "RelatedSoundFile"; 299 /** Type is int. */ 300 public static final String TAG_SATURATION = "Saturation"; 301 /** Type is int. */ 302 public static final String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType"; 303 /** Type is String. */ 304 public static final String TAG_SCENE_TYPE = "SceneType"; 305 /** Type is int. */ 306 public static final String TAG_SENSING_METHOD = "SensingMethod"; 307 /** Type is int. */ 308 public static final String TAG_SHARPNESS = "Sharpness"; 309 /** Type is rational. */ 310 public static final String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue"; 311 /** Type is String. */ 312 public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse"; 313 /** Type is String. */ 314 public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity"; 315 /** Type is int. */ 316 public static final String TAG_SUBFILE_TYPE = "SubfileType"; 317 /** Type is String. */ 318 public static final String TAG_SUBSEC_TIME = "SubSecTime"; 319 /** 320 * Type is String. 321 * 322 * @deprecated use {@link #TAG_SUBSEC_TIME_DIGITIZED} instead 323 */ 324 public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; 325 /** Type is String. */ 326 public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; 327 /** 328 * Type is String. 329 * 330 * @deprecated use {@link #TAG_SUBSEC_TIME_ORIGINAL} instead 331 */ 332 public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; 333 /** Type is String. */ 334 public static final String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal"; 335 /** Type is int. */ 336 public static final String TAG_SUBJECT_AREA = "SubjectArea"; 337 /** Type is double. */ 338 public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance"; 339 /** Type is int. */ 340 public static final String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange"; 341 /** Type is int. */ 342 public static final String TAG_SUBJECT_LOCATION = "SubjectLocation"; 343 /** Type is String. */ 344 public static final String TAG_USER_COMMENT = "UserComment"; 345 /** Type is int. */ 346 public static final String TAG_WHITE_BALANCE = "WhiteBalance"; 347 /** 348 * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF. 349 * Type is rational. 350 */ 351 public static final String TAG_GPS_ALTITUDE = "GPSAltitude"; 352 /** 353 * 0 if the altitude is above sea level. 1 if the altitude is below sea 354 * level. Type is int. 355 */ 356 public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef"; 357 /** Type is String. */ 358 public static final String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation"; 359 /** Type is rational. */ 360 public static final String TAG_GPS_DOP = "GPSDOP"; 361 /** Type is String. */ 362 public static final String TAG_GPS_DATESTAMP = "GPSDateStamp"; 363 /** Type is rational. */ 364 public static final String TAG_GPS_DEST_BEARING = "GPSDestBearing"; 365 /** Type is String. */ 366 public static final String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef"; 367 /** Type is rational. */ 368 public static final String TAG_GPS_DEST_DISTANCE = "GPSDestDistance"; 369 /** Type is String. */ 370 public static final String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef"; 371 /** Type is rational. */ 372 public static final String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude"; 373 /** Type is String. */ 374 public static final String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef"; 375 /** Type is rational. */ 376 public static final String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude"; 377 /** Type is String. */ 378 public static final String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef"; 379 /** Type is int. */ 380 public static final String TAG_GPS_DIFFERENTIAL = "GPSDifferential"; 381 /** Type is rational. */ 382 public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection"; 383 /** Type is String. */ 384 public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef"; 385 /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */ 386 public static final String TAG_GPS_LATITUDE = "GPSLatitude"; 387 /** Type is String. */ 388 public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef"; 389 /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */ 390 public static final String TAG_GPS_LONGITUDE = "GPSLongitude"; 391 /** Type is String. */ 392 public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; 393 /** Type is String. */ 394 public static final String TAG_GPS_MAP_DATUM = "GPSMapDatum"; 395 /** Type is String. */ 396 public static final String TAG_GPS_MEASURE_MODE = "GPSMeasureMode"; 397 /** Type is String. Name of GPS processing method used for location finding. */ 398 public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod"; 399 /** Type is String. */ 400 public static final String TAG_GPS_SATELLITES = "GPSSatellites"; 401 /** Type is rational. */ 402 public static final String TAG_GPS_SPEED = "GPSSpeed"; 403 /** Type is String. */ 404 public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef"; 405 /** Type is String. */ 406 public static final String TAG_GPS_STATUS = "GPSStatus"; 407 /** Type is String. Format is "hh:mm:ss". */ 408 public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp"; 409 /** Type is rational. */ 410 public static final String TAG_GPS_TRACK = "GPSTrack"; 411 /** Type is String. */ 412 public static final String TAG_GPS_TRACK_REF = "GPSTrackRef"; 413 /** Type is String. */ 414 public static final String TAG_GPS_VERSION_ID = "GPSVersionID"; 415 /** Type is String. */ 416 public static final String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex"; 417 /** Type is int. */ 418 public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength"; 419 /** Type is int. */ 420 public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth"; 421 /** Type is int. */ 422 public static final String TAG_THUMBNAIL_ORIENTATION = "ThumbnailOrientation"; 423 /** Type is int. DNG Specification 1.4.0.0. Section 4 */ 424 public static final String TAG_DNG_VERSION = "DNGVersion"; 425 /** Type is int. DNG Specification 1.4.0.0. Section 4 */ 426 public static final String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize"; 427 /** Type is undefined. See Olympus MakerNote tags in http://www.exiv2.org/tags-olympus.html. */ 428 public static final String TAG_ORF_THUMBNAIL_IMAGE = "ThumbnailImage"; 429 /** Type is int. See Olympus Camera Settings tags in http://www.exiv2.org/tags-olympus.html. */ 430 public static final String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart"; 431 /** Type is int. See Olympus Camera Settings tags in http://www.exiv2.org/tags-olympus.html. */ 432 public static final String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength"; 433 /** Type is int. See Olympus Image Processing tags in http://www.exiv2.org/tags-olympus.html. */ 434 public static final String TAG_ORF_ASPECT_FRAME = "AspectFrame"; 435 /** 436 * Type is int. See PanasonicRaw tags in 437 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 438 */ 439 public static final String TAG_RW2_SENSOR_BOTTOM_BORDER = "SensorBottomBorder"; 440 /** 441 * Type is int. See PanasonicRaw tags in 442 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 443 */ 444 public static final String TAG_RW2_SENSOR_LEFT_BORDER = "SensorLeftBorder"; 445 /** 446 * Type is int. See PanasonicRaw tags in 447 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 448 */ 449 public static final String TAG_RW2_SENSOR_RIGHT_BORDER = "SensorRightBorder"; 450 /** 451 * Type is int. See PanasonicRaw tags in 452 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 453 */ 454 public static final String TAG_RW2_SENSOR_TOP_BORDER = "SensorTopBorder"; 455 /** 456 * Type is int. See PanasonicRaw tags in 457 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 458 */ 459 public static final String TAG_RW2_ISO = "ISO"; 460 /** 461 * Type is undefined. See PanasonicRaw tags in 462 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html 463 */ 464 public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw"; 465 /** 466 * Type is byte[]. See <a href= 467 * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform">Extensible 468 * Metadata Platform (XMP)</a> for details on contents. 469 */ 470 public static final String TAG_XMP = "Xmp"; 471 472 /** 473 * Private tags used for pointing the other IFD offsets. 474 * The types of the following tags are int. 475 * See JEITA CP-3451C Section 4.6.3: Exif-specific IFD. 476 * For SubIFD, see Note 1 of Adobe PageMaker® 6.0 TIFF Technical Notes. 477 */ 478 private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer"; 479 private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer"; 480 private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer"; 481 private static final String TAG_SUB_IFD_POINTER = "SubIFDPointer"; 482 // Proprietary pointer tags used for ORF files. 483 // See http://www.exiv2.org/tags-olympus.html 484 private static final String TAG_ORF_CAMERA_SETTINGS_IFD_POINTER = "CameraSettingsIFDPointer"; 485 private static final String TAG_ORF_IMAGE_PROCESSING_IFD_POINTER = "ImageProcessingIFDPointer"; 486 487 // Private tags used for thumbnail information. 488 private static final String TAG_HAS_THUMBNAIL = "HasThumbnail"; 489 private static final String TAG_THUMBNAIL_OFFSET = "ThumbnailOffset"; 490 private static final String TAG_THUMBNAIL_LENGTH = "ThumbnailLength"; 491 private static final String TAG_THUMBNAIL_DATA = "ThumbnailData"; 492 private static final int MAX_THUMBNAIL_SIZE = 512; 493 494 // Constants used for the Orientation Exif tag. 495 public static final int ORIENTATION_UNDEFINED = 0; 496 public static final int ORIENTATION_NORMAL = 1; 497 public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror 498 public static final int ORIENTATION_ROTATE_180 = 3; 499 public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror 500 // flipped about top-left <--> bottom-right axis 501 public static final int ORIENTATION_TRANSPOSE = 5; 502 public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it 503 // flipped about top-right <--> bottom-left axis 504 public static final int ORIENTATION_TRANSVERSE = 7; 505 public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it 506 507 // Constants used for white balance 508 public static final int WHITEBALANCE_AUTO = 0; 509 public static final int WHITEBALANCE_MANUAL = 1; 510 511 /** 512 * Constant used to indicate that the input stream contains the full image data. 513 * <p> 514 * The format of the image data should follow one of the image formats supported by this class. 515 */ 516 public static final int STREAM_TYPE_FULL_IMAGE_DATA = 0; 517 /** 518 * Constant used to indicate that the input stream contains only Exif data. 519 * <p> 520 * The format of the Exif-only data must follow the below structure: 521 * Exif Identifier Code ("Exif\0\0") + TIFF header + IFD data 522 * See JEITA CP-3451C Section 4.5.2 and 4.5.4 specifications for more details. 523 */ 524 public static final int STREAM_TYPE_EXIF_DATA_ONLY = 1; 525 526 /** @hide */ 527 @Retention(RetentionPolicy.SOURCE) 528 @IntDef({STREAM_TYPE_FULL_IMAGE_DATA, STREAM_TYPE_EXIF_DATA_ONLY}) 529 public @interface ExifStreamType {} 530 531 // Maximum size for checking file type signature (see image_type_recognition_lite.cc) 532 private static final int SIGNATURE_CHECK_SIZE = 5000; 533 534 private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff}; 535 private static final String RAF_SIGNATURE = "FUJIFILMCCD-RAW"; 536 private static final int RAF_OFFSET_TO_JPEG_IMAGE_OFFSET = 84; 537 private static final int RAF_INFO_SIZE = 160; 538 private static final int RAF_JPEG_LENGTH_VALUE_SIZE = 4; 539 540 private static final byte[] HEIF_TYPE_FTYP = new byte[] {'f', 't', 'y', 'p'}; 541 private static final byte[] HEIF_BRAND_MIF1 = new byte[] {'m', 'i', 'f', '1'}; 542 private static final byte[] HEIF_BRAND_HEIC = new byte[] {'h', 'e', 'i', 'c'}; 543 544 // See http://fileformats.archiveteam.org/wiki/Olympus_ORF 545 private static final short ORF_SIGNATURE_1 = 0x4f52; 546 private static final short ORF_SIGNATURE_2 = 0x5352; 547 // There are two formats for Olympus Makernote Headers. Each has different identifiers and 548 // offsets to the actual data. 549 // See http://www.exiv2.org/makernote.html#R1 550 private static final byte[] ORF_MAKER_NOTE_HEADER_1 = new byte[] {(byte) 0x4f, (byte) 0x4c, 551 (byte) 0x59, (byte) 0x4d, (byte) 0x50, (byte) 0x00}; // "OLYMP\0" 552 private static final byte[] ORF_MAKER_NOTE_HEADER_2 = new byte[] {(byte) 0x4f, (byte) 0x4c, 553 (byte) 0x59, (byte) 0x4d, (byte) 0x50, (byte) 0x55, (byte) 0x53, (byte) 0x00, 554 (byte) 0x49, (byte) 0x49}; // "OLYMPUS\0II" 555 private static final int ORF_MAKER_NOTE_HEADER_1_SIZE = 8; 556 private static final int ORF_MAKER_NOTE_HEADER_2_SIZE = 12; 557 558 // See http://fileformats.archiveteam.org/wiki/RW2 559 private static final short RW2_SIGNATURE = 0x0055; 560 561 // See http://fileformats.archiveteam.org/wiki/Pentax_PEF 562 private static final String PEF_SIGNATURE = "PENTAX"; 563 // See http://www.exiv2.org/makernote.html#R11 564 private static final int PEF_MAKER_NOTE_SKIP_SIZE = 6; 565 566 // See PNG (Portable Network Graphics) Specification, Version 1.2, 567 // 3.1. PNG file signature 568 private static final byte[] PNG_SIGNATURE = new byte[] {(byte) 0x89, (byte) 0x50, (byte) 0x4e, 569 (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a}; 570 // See PNG (Portable Network Graphics) Specification, Version 1.2, 571 // 3.7. eXIf Exchangeable Image File (Exif) Profile 572 private static final byte[] PNG_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x65, (byte) 0x58, 573 (byte) 0x49, (byte) 0x66}; 574 private static final byte[] PNG_CHUNK_TYPE_IHDR = new byte[]{(byte) 0x49, (byte) 0x48, 575 (byte) 0x44, (byte) 0x52}; 576 private static final byte[] PNG_CHUNK_TYPE_IEND = new byte[]{(byte) 0x49, (byte) 0x45, 577 (byte) 0x4e, (byte) 0x44}; 578 private static final int PNG_CHUNK_TYPE_BYTE_LENGTH = 4; 579 private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4; 580 581 // See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header" 582 private static final byte[] WEBP_SIGNATURE_1 = new byte[] {'R', 'I', 'F', 'F'}; 583 private static final byte[] WEBP_SIGNATURE_2 = new byte[] {'W', 'E', 'B', 'P'}; 584 private static final int WEBP_FILE_SIZE_BYTE_LENGTH = 4; 585 private static final byte[] WEBP_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x45, (byte) 0x58, 586 (byte) 0x49, (byte) 0x46}; 587 private static final int WEBP_CHUNK_TYPE_BYTE_LENGTH = 4; 588 private static final int WEBP_CHUNK_SIZE_BYTE_LENGTH = 4; 589 590 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 591 @GuardedBy("sFormatter") 592 private static SimpleDateFormat sFormatter; 593 @GuardedBy("sFormatterTz") 594 private static SimpleDateFormat sFormatterTz; 595 596 // See Exchangeable image file format for digital still cameras: Exif version 2.2. 597 // The following values are for parsing EXIF data area. There are tag groups in EXIF data area. 598 // They are called "Image File Directory". They have multiple data formats to cover various 599 // image metadata from GPS longitude to camera model name. 600 601 // Types of Exif byte alignments (see JEITA CP-3451C Section 4.5.2) 602 private static final short BYTE_ALIGN_II = 0x4949; // II: Intel order 603 private static final short BYTE_ALIGN_MM = 0x4d4d; // MM: Motorola order 604 605 // TIFF Header Fixed Constant (see JEITA CP-3451C Section 4.5.2) 606 private static final byte START_CODE = 0x2a; // 42 607 private static final int IFD_OFFSET = 8; 608 609 // Formats for the value in IFD entry (See TIFF 6.0 Section 2, "Image File Directory".) 610 private static final int IFD_FORMAT_BYTE = 1; 611 private static final int IFD_FORMAT_STRING = 2; 612 private static final int IFD_FORMAT_USHORT = 3; 613 private static final int IFD_FORMAT_ULONG = 4; 614 private static final int IFD_FORMAT_URATIONAL = 5; 615 private static final int IFD_FORMAT_SBYTE = 6; 616 private static final int IFD_FORMAT_UNDEFINED = 7; 617 private static final int IFD_FORMAT_SSHORT = 8; 618 private static final int IFD_FORMAT_SLONG = 9; 619 private static final int IFD_FORMAT_SRATIONAL = 10; 620 private static final int IFD_FORMAT_SINGLE = 11; 621 private static final int IFD_FORMAT_DOUBLE = 12; 622 // Format indicating a new IFD entry (See Adobe PageMaker® 6.0 TIFF Technical Notes, "New Tag") 623 private static final int IFD_FORMAT_IFD = 13; 624 // Names for the data formats for debugging purpose. 625 private static final String[] IFD_FORMAT_NAMES = new String[] { 626 "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT", 627 "SLONG", "SRATIONAL", "SINGLE", "DOUBLE", "IFD" 628 }; 629 // Sizes of the components of each IFD value format 630 private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] { 631 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 1 632 }; 633 private static final byte[] EXIF_ASCII_PREFIX = new byte[] { 634 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 635 }; 636 637 /** 638 * Constants used for Compression tag. 639 * For Value 1, 2, 32773, see TIFF 6.0 Spec Section 3: Bilevel Images, Compression 640 * For Value 6, see TIFF 6.0 Spec Section 22: JPEG Compression, Extensions to Existing Fields 641 * For Value 7, 8, 34892, see DNG Specification 1.4.0.0. Section 3, Compression 642 */ 643 private static final int DATA_UNCOMPRESSED = 1; 644 private static final int DATA_HUFFMAN_COMPRESSED = 2; 645 private static final int DATA_JPEG = 6; 646 private static final int DATA_JPEG_COMPRESSED = 7; 647 private static final int DATA_DEFLATE_ZIP = 8; 648 private static final int DATA_PACK_BITS_COMPRESSED = 32773; 649 private static final int DATA_LOSSY_JPEG = 34892; 650 651 /** 652 * Constants used for BitsPerSample tag. 653 * For RGB, see TIFF 6.0 Spec Section 6, Differences from Palette Color Images 654 * For Greyscale, see TIFF 6.0 Spec Section 4, Differences from Bilevel Images 655 */ 656 private static final int[] BITS_PER_SAMPLE_RGB = new int[] { 8, 8, 8 }; 657 private static final int[] BITS_PER_SAMPLE_GREYSCALE_1 = new int[] { 4 }; 658 private static final int[] BITS_PER_SAMPLE_GREYSCALE_2 = new int[] { 8 }; 659 660 /** 661 * Constants used for PhotometricInterpretation tag. 662 * For White/Black, see Section 3, Color. 663 * See TIFF 6.0 Spec Section 22, Minimum Requirements for TIFF with JPEG Compression. 664 */ 665 private static final int PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0; 666 private static final int PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 1; 667 private static final int PHOTOMETRIC_INTERPRETATION_RGB = 2; 668 private static final int PHOTOMETRIC_INTERPRETATION_YCBCR = 6; 669 670 /** 671 * Constants used for NewSubfileType tag. 672 * See TIFF 6.0 Spec Section 8 673 * */ 674 private static final int ORIGINAL_RESOLUTION_IMAGE = 0; 675 private static final int REDUCED_RESOLUTION_IMAGE = 1; 676 677 // A class for indicating EXIF rational type. 678 private static class Rational { 679 public final long numerator; 680 public final long denominator; 681 Rational(long numerator, long denominator)682 private Rational(long numerator, long denominator) { 683 // Handle erroneous case 684 if (denominator == 0) { 685 this.numerator = 0; 686 this.denominator = 1; 687 return; 688 } 689 this.numerator = numerator; 690 this.denominator = denominator; 691 } 692 693 @Override toString()694 public String toString() { 695 return numerator + "/" + denominator; 696 } 697 calculate()698 public double calculate() { 699 return (double) numerator / denominator; 700 } 701 } 702 703 // A class for indicating EXIF attribute. 704 private static class ExifAttribute { 705 public final int format; 706 public final int numberOfComponents; 707 public final long bytesOffset; 708 public final byte[] bytes; 709 710 public static final long BYTES_OFFSET_UNKNOWN = -1; 711 ExifAttribute(int format, int numberOfComponents, byte[] bytes)712 private ExifAttribute(int format, int numberOfComponents, byte[] bytes) { 713 this(format, numberOfComponents, BYTES_OFFSET_UNKNOWN, bytes); 714 } 715 ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes)716 private ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes) { 717 this.format = format; 718 this.numberOfComponents = numberOfComponents; 719 this.bytesOffset = bytesOffset; 720 this.bytes = bytes; 721 } 722 createUShort(int[] values, ByteOrder byteOrder)723 public static ExifAttribute createUShort(int[] values, ByteOrder byteOrder) { 724 final ByteBuffer buffer = ByteBuffer.wrap( 725 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_USHORT] * values.length]); 726 buffer.order(byteOrder); 727 for (int value : values) { 728 buffer.putShort((short) value); 729 } 730 return new ExifAttribute(IFD_FORMAT_USHORT, values.length, buffer.array()); 731 } 732 createUShort(int value, ByteOrder byteOrder)733 public static ExifAttribute createUShort(int value, ByteOrder byteOrder) { 734 return createUShort(new int[] {value}, byteOrder); 735 } 736 createULong(long[] values, ByteOrder byteOrder)737 public static ExifAttribute createULong(long[] values, ByteOrder byteOrder) { 738 final ByteBuffer buffer = ByteBuffer.wrap( 739 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_ULONG] * values.length]); 740 buffer.order(byteOrder); 741 for (long value : values) { 742 buffer.putInt((int) value); 743 } 744 return new ExifAttribute(IFD_FORMAT_ULONG, values.length, buffer.array()); 745 } 746 createULong(long value, ByteOrder byteOrder)747 public static ExifAttribute createULong(long value, ByteOrder byteOrder) { 748 return createULong(new long[] {value}, byteOrder); 749 } 750 createSLong(int[] values, ByteOrder byteOrder)751 public static ExifAttribute createSLong(int[] values, ByteOrder byteOrder) { 752 final ByteBuffer buffer = ByteBuffer.wrap( 753 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SLONG] * values.length]); 754 buffer.order(byteOrder); 755 for (int value : values) { 756 buffer.putInt(value); 757 } 758 return new ExifAttribute(IFD_FORMAT_SLONG, values.length, buffer.array()); 759 } 760 createSLong(int value, ByteOrder byteOrder)761 public static ExifAttribute createSLong(int value, ByteOrder byteOrder) { 762 return createSLong(new int[] {value}, byteOrder); 763 } 764 createByte(String value)765 public static ExifAttribute createByte(String value) { 766 // Exception for GPSAltitudeRef tag 767 if (value.length() == 1 && value.charAt(0) >= '0' && value.charAt(0) <= '1') { 768 final byte[] bytes = new byte[] { (byte) (value.charAt(0) - '0') }; 769 return new ExifAttribute(IFD_FORMAT_BYTE, bytes.length, bytes); 770 } 771 final byte[] ascii = value.getBytes(ASCII); 772 return new ExifAttribute(IFD_FORMAT_BYTE, ascii.length, ascii); 773 } 774 createString(String value)775 public static ExifAttribute createString(String value) { 776 final byte[] ascii = (value + '\0').getBytes(ASCII); 777 return new ExifAttribute(IFD_FORMAT_STRING, ascii.length, ascii); 778 } 779 createURational(Rational[] values, ByteOrder byteOrder)780 public static ExifAttribute createURational(Rational[] values, ByteOrder byteOrder) { 781 final ByteBuffer buffer = ByteBuffer.wrap( 782 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_URATIONAL] * values.length]); 783 buffer.order(byteOrder); 784 for (Rational value : values) { 785 buffer.putInt((int) value.numerator); 786 buffer.putInt((int) value.denominator); 787 } 788 return new ExifAttribute(IFD_FORMAT_URATIONAL, values.length, buffer.array()); 789 } 790 createURational(Rational value, ByteOrder byteOrder)791 public static ExifAttribute createURational(Rational value, ByteOrder byteOrder) { 792 return createURational(new Rational[] {value}, byteOrder); 793 } 794 createSRational(Rational[] values, ByteOrder byteOrder)795 public static ExifAttribute createSRational(Rational[] values, ByteOrder byteOrder) { 796 final ByteBuffer buffer = ByteBuffer.wrap( 797 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SRATIONAL] * values.length]); 798 buffer.order(byteOrder); 799 for (Rational value : values) { 800 buffer.putInt((int) value.numerator); 801 buffer.putInt((int) value.denominator); 802 } 803 return new ExifAttribute(IFD_FORMAT_SRATIONAL, values.length, buffer.array()); 804 } 805 createSRational(Rational value, ByteOrder byteOrder)806 public static ExifAttribute createSRational(Rational value, ByteOrder byteOrder) { 807 return createSRational(new Rational[] {value}, byteOrder); 808 } 809 createDouble(double[] values, ByteOrder byteOrder)810 public static ExifAttribute createDouble(double[] values, ByteOrder byteOrder) { 811 final ByteBuffer buffer = ByteBuffer.wrap( 812 new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_DOUBLE] * values.length]); 813 buffer.order(byteOrder); 814 for (double value : values) { 815 buffer.putDouble(value); 816 } 817 return new ExifAttribute(IFD_FORMAT_DOUBLE, values.length, buffer.array()); 818 } 819 createDouble(double value, ByteOrder byteOrder)820 public static ExifAttribute createDouble(double value, ByteOrder byteOrder) { 821 return createDouble(new double[] {value}, byteOrder); 822 } 823 824 @Override toString()825 public String toString() { 826 return "(" + IFD_FORMAT_NAMES[format] + ", data length:" + bytes.length + ")"; 827 } 828 getValue(ByteOrder byteOrder)829 private Object getValue(ByteOrder byteOrder) { 830 try { 831 ByteOrderedDataInputStream inputStream = 832 new ByteOrderedDataInputStream(bytes); 833 inputStream.setByteOrder(byteOrder); 834 switch (format) { 835 case IFD_FORMAT_BYTE: 836 case IFD_FORMAT_SBYTE: { 837 // Exception for GPSAltitudeRef tag 838 if (bytes.length == 1 && bytes[0] >= 0 && bytes[0] <= 1) { 839 return new String(new char[] { (char) (bytes[0] + '0') }); 840 } 841 return new String(bytes, ASCII); 842 } 843 case IFD_FORMAT_UNDEFINED: 844 case IFD_FORMAT_STRING: { 845 int index = 0; 846 if (numberOfComponents >= EXIF_ASCII_PREFIX.length) { 847 boolean same = true; 848 for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) { 849 if (bytes[i] != EXIF_ASCII_PREFIX[i]) { 850 same = false; 851 break; 852 } 853 } 854 if (same) { 855 index = EXIF_ASCII_PREFIX.length; 856 } 857 } 858 859 StringBuilder stringBuilder = new StringBuilder(); 860 while (index < numberOfComponents) { 861 int ch = bytes[index]; 862 if (ch == 0) { 863 break; 864 } 865 if (ch >= 32) { 866 stringBuilder.append((char) ch); 867 } else { 868 stringBuilder.append('?'); 869 } 870 ++index; 871 } 872 return stringBuilder.toString(); 873 } 874 case IFD_FORMAT_USHORT: { 875 final int[] values = new int[numberOfComponents]; 876 for (int i = 0; i < numberOfComponents; ++i) { 877 values[i] = inputStream.readUnsignedShort(); 878 } 879 return values; 880 } 881 case IFD_FORMAT_ULONG: { 882 final long[] values = new long[numberOfComponents]; 883 for (int i = 0; i < numberOfComponents; ++i) { 884 values[i] = inputStream.readUnsignedInt(); 885 } 886 return values; 887 } 888 case IFD_FORMAT_URATIONAL: { 889 final Rational[] values = new Rational[numberOfComponents]; 890 for (int i = 0; i < numberOfComponents; ++i) { 891 final long numerator = inputStream.readUnsignedInt(); 892 final long denominator = inputStream.readUnsignedInt(); 893 values[i] = new Rational(numerator, denominator); 894 } 895 return values; 896 } 897 case IFD_FORMAT_SSHORT: { 898 final int[] values = new int[numberOfComponents]; 899 for (int i = 0; i < numberOfComponents; ++i) { 900 values[i] = inputStream.readShort(); 901 } 902 return values; 903 } 904 case IFD_FORMAT_SLONG: { 905 final int[] values = new int[numberOfComponents]; 906 for (int i = 0; i < numberOfComponents; ++i) { 907 values[i] = inputStream.readInt(); 908 } 909 return values; 910 } 911 case IFD_FORMAT_SRATIONAL: { 912 final Rational[] values = new Rational[numberOfComponents]; 913 for (int i = 0; i < numberOfComponents; ++i) { 914 final long numerator = inputStream.readInt(); 915 final long denominator = inputStream.readInt(); 916 values[i] = new Rational(numerator, denominator); 917 } 918 return values; 919 } 920 case IFD_FORMAT_SINGLE: { 921 final double[] values = new double[numberOfComponents]; 922 for (int i = 0; i < numberOfComponents; ++i) { 923 values[i] = inputStream.readFloat(); 924 } 925 return values; 926 } 927 case IFD_FORMAT_DOUBLE: { 928 final double[] values = new double[numberOfComponents]; 929 for (int i = 0; i < numberOfComponents; ++i) { 930 values[i] = inputStream.readDouble(); 931 } 932 return values; 933 } 934 default: 935 return null; 936 } 937 } catch (IOException e) { 938 Log.w(TAG, "IOException occurred during reading a value", e); 939 return null; 940 } 941 } 942 getDoubleValue(ByteOrder byteOrder)943 public double getDoubleValue(ByteOrder byteOrder) { 944 Object value = getValue(byteOrder); 945 if (value == null) { 946 throw new NumberFormatException("NULL can't be converted to a double value"); 947 } 948 if (value instanceof String) { 949 return Double.parseDouble((String) value); 950 } 951 if (value instanceof long[]) { 952 long[] array = (long[]) value; 953 if (array.length == 1) { 954 return array[0]; 955 } 956 throw new NumberFormatException("There are more than one component"); 957 } 958 if (value instanceof int[]) { 959 int[] array = (int[]) value; 960 if (array.length == 1) { 961 return array[0]; 962 } 963 throw new NumberFormatException("There are more than one component"); 964 } 965 if (value instanceof double[]) { 966 double[] array = (double[]) value; 967 if (array.length == 1) { 968 return array[0]; 969 } 970 throw new NumberFormatException("There are more than one component"); 971 } 972 if (value instanceof Rational[]) { 973 Rational[] array = (Rational[]) value; 974 if (array.length == 1) { 975 return array[0].calculate(); 976 } 977 throw new NumberFormatException("There are more than one component"); 978 } 979 throw new NumberFormatException("Couldn't find a double value"); 980 } 981 getIntValue(ByteOrder byteOrder)982 public int getIntValue(ByteOrder byteOrder) { 983 Object value = getValue(byteOrder); 984 if (value == null) { 985 throw new NumberFormatException("NULL can't be converted to a integer value"); 986 } 987 if (value instanceof String) { 988 return Integer.parseInt((String) value); 989 } 990 if (value instanceof long[]) { 991 long[] array = (long[]) value; 992 if (array.length == 1) { 993 return (int) array[0]; 994 } 995 throw new NumberFormatException("There are more than one component"); 996 } 997 if (value instanceof int[]) { 998 int[] array = (int[]) value; 999 if (array.length == 1) { 1000 return array[0]; 1001 } 1002 throw new NumberFormatException("There are more than one component"); 1003 } 1004 throw new NumberFormatException("Couldn't find a integer value"); 1005 } 1006 getStringValue(ByteOrder byteOrder)1007 public String getStringValue(ByteOrder byteOrder) { 1008 Object value = getValue(byteOrder); 1009 if (value == null) { 1010 return null; 1011 } 1012 if (value instanceof String) { 1013 return (String) value; 1014 } 1015 1016 final StringBuilder stringBuilder = new StringBuilder(); 1017 if (value instanceof long[]) { 1018 long[] array = (long[]) value; 1019 for (int i = 0; i < array.length; ++i) { 1020 stringBuilder.append(array[i]); 1021 if (i + 1 != array.length) { 1022 stringBuilder.append(","); 1023 } 1024 } 1025 return stringBuilder.toString(); 1026 } 1027 if (value instanceof int[]) { 1028 int[] array = (int[]) value; 1029 for (int i = 0; i < array.length; ++i) { 1030 stringBuilder.append(array[i]); 1031 if (i + 1 != array.length) { 1032 stringBuilder.append(","); 1033 } 1034 } 1035 return stringBuilder.toString(); 1036 } 1037 if (value instanceof double[]) { 1038 double[] array = (double[]) value; 1039 for (int i = 0; i < array.length; ++i) { 1040 stringBuilder.append(array[i]); 1041 if (i + 1 != array.length) { 1042 stringBuilder.append(","); 1043 } 1044 } 1045 return stringBuilder.toString(); 1046 } 1047 if (value instanceof Rational[]) { 1048 Rational[] array = (Rational[]) value; 1049 for (int i = 0; i < array.length; ++i) { 1050 stringBuilder.append(array[i].numerator); 1051 stringBuilder.append('/'); 1052 stringBuilder.append(array[i].denominator); 1053 if (i + 1 != array.length) { 1054 stringBuilder.append(","); 1055 } 1056 } 1057 return stringBuilder.toString(); 1058 } 1059 return null; 1060 } 1061 size()1062 public int size() { 1063 return IFD_FORMAT_BYTES_PER_FORMAT[format] * numberOfComponents; 1064 } 1065 } 1066 1067 // A class for indicating EXIF tag. 1068 private static class ExifTag { 1069 public final int number; 1070 public final String name; 1071 public final int primaryFormat; 1072 public final int secondaryFormat; 1073 ExifTag(String name, int number, int format)1074 private ExifTag(String name, int number, int format) { 1075 this.name = name; 1076 this.number = number; 1077 this.primaryFormat = format; 1078 this.secondaryFormat = -1; 1079 } 1080 ExifTag(String name, int number, int primaryFormat, int secondaryFormat)1081 private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) { 1082 this.name = name; 1083 this.number = number; 1084 this.primaryFormat = primaryFormat; 1085 this.secondaryFormat = secondaryFormat; 1086 } 1087 } 1088 1089 // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 1090 private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] { 1091 // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images. 1092 new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG), 1093 new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG), 1094 new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1095 new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1096 new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT), 1097 new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT), 1098 new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT), 1099 new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING), 1100 new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING), 1101 new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING), 1102 new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1103 new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT), 1104 new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT), 1105 new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1106 new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1107 new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL), 1108 new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL), 1109 new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT), 1110 new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT), 1111 new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT), 1112 new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING), 1113 new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING), 1114 new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING), 1115 new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL), 1116 new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL), 1117 // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1. 1118 new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG), 1119 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG), 1120 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG), 1121 new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL), 1122 new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT), 1123 new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT), 1124 new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL), 1125 new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING), 1126 new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), 1127 new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), 1128 // RW2 file tags 1129 // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html) 1130 new ExifTag(TAG_RW2_SENSOR_TOP_BORDER, 4, IFD_FORMAT_ULONG), 1131 new ExifTag(TAG_RW2_SENSOR_LEFT_BORDER, 5, IFD_FORMAT_ULONG), 1132 new ExifTag(TAG_RW2_SENSOR_BOTTOM_BORDER, 6, IFD_FORMAT_ULONG), 1133 new ExifTag(TAG_RW2_SENSOR_RIGHT_BORDER, 7, IFD_FORMAT_ULONG), 1134 new ExifTag(TAG_RW2_ISO, 23, IFD_FORMAT_USHORT), 1135 new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED), 1136 new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE), 1137 }; 1138 1139 // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 1140 private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] { 1141 new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL), 1142 new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL), 1143 new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT), 1144 new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852, IFD_FORMAT_STRING), 1145 new ExifTag(TAG_ISO_SPEED_RATINGS, 34855, IFD_FORMAT_USHORT), 1146 new ExifTag(TAG_OECF, 34856, IFD_FORMAT_UNDEFINED), 1147 new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING), 1148 new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING), 1149 new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING), 1150 new ExifTag(TAG_OFFSET_TIME, 36880, IFD_FORMAT_STRING), 1151 new ExifTag(TAG_OFFSET_TIME_ORIGINAL, 36881, IFD_FORMAT_STRING), 1152 new ExifTag(TAG_OFFSET_TIME_DIGITIZED, 36882, IFD_FORMAT_STRING), 1153 new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED), 1154 new ExifTag(TAG_COMPRESSED_BITS_PER_PIXEL, 37122, IFD_FORMAT_URATIONAL), 1155 new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL), 1156 new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL), 1157 new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL), 1158 new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL), 1159 new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL), 1160 new ExifTag(TAG_SUBJECT_DISTANCE, 37382, IFD_FORMAT_URATIONAL), 1161 new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT), 1162 new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT), 1163 new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT), 1164 new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL), 1165 new ExifTag(TAG_SUBJECT_AREA, 37396, IFD_FORMAT_USHORT), 1166 new ExifTag(TAG_MAKER_NOTE, 37500, IFD_FORMAT_UNDEFINED), 1167 new ExifTag(TAG_USER_COMMENT, 37510, IFD_FORMAT_UNDEFINED), 1168 new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING), 1169 new ExifTag(TAG_SUBSEC_TIME_ORIG, 37521, IFD_FORMAT_STRING), 1170 new ExifTag(TAG_SUBSEC_TIME_DIG, 37522, IFD_FORMAT_STRING), 1171 new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED), 1172 new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT), 1173 new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1174 new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1175 new ExifTag(TAG_RELATED_SOUND_FILE, 40964, IFD_FORMAT_STRING), 1176 new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), 1177 new ExifTag(TAG_FLASH_ENERGY, 41483, IFD_FORMAT_URATIONAL), 1178 new ExifTag(TAG_SPATIAL_FREQUENCY_RESPONSE, 41484, IFD_FORMAT_UNDEFINED), 1179 new ExifTag(TAG_FOCAL_PLANE_X_RESOLUTION, 41486, IFD_FORMAT_URATIONAL), 1180 new ExifTag(TAG_FOCAL_PLANE_Y_RESOLUTION, 41487, IFD_FORMAT_URATIONAL), 1181 new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT), 1182 new ExifTag(TAG_SUBJECT_LOCATION, 41492, IFD_FORMAT_USHORT), 1183 new ExifTag(TAG_EXPOSURE_INDEX, 41493, IFD_FORMAT_URATIONAL), 1184 new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT), 1185 new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED), 1186 new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED), 1187 new ExifTag(TAG_CFA_PATTERN, 41730, IFD_FORMAT_UNDEFINED), 1188 new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT), 1189 new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT), 1190 new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT), 1191 new ExifTag(TAG_DIGITAL_ZOOM_RATIO, 41988, IFD_FORMAT_URATIONAL), 1192 new ExifTag(TAG_FOCAL_LENGTH_IN_35MM_FILM, 41989, IFD_FORMAT_USHORT), 1193 new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT), 1194 new ExifTag(TAG_GAIN_CONTROL, 41991, IFD_FORMAT_USHORT), 1195 new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT), 1196 new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT), 1197 new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT), 1198 new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995, IFD_FORMAT_UNDEFINED), 1199 new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996, IFD_FORMAT_USHORT), 1200 new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016, IFD_FORMAT_STRING), 1201 new ExifTag(TAG_DNG_VERSION, 50706, IFD_FORMAT_BYTE), 1202 new ExifTag(TAG_DEFAULT_CROP_SIZE, 50720, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG) 1203 }; 1204 1205 // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 1206 private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] { 1207 new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE), 1208 new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING), 1209 new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL), 1210 new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING), 1211 new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL), 1212 new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE), 1213 new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL), 1214 new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL), 1215 new ExifTag(TAG_GPS_SATELLITES, 8, IFD_FORMAT_STRING), 1216 new ExifTag(TAG_GPS_STATUS, 9, IFD_FORMAT_STRING), 1217 new ExifTag(TAG_GPS_MEASURE_MODE, 10, IFD_FORMAT_STRING), 1218 new ExifTag(TAG_GPS_DOP, 11, IFD_FORMAT_URATIONAL), 1219 new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING), 1220 new ExifTag(TAG_GPS_SPEED, 13, IFD_FORMAT_URATIONAL), 1221 new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING), 1222 new ExifTag(TAG_GPS_TRACK, 15, IFD_FORMAT_URATIONAL), 1223 new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING), 1224 new ExifTag(TAG_GPS_IMG_DIRECTION, 17, IFD_FORMAT_URATIONAL), 1225 new ExifTag(TAG_GPS_MAP_DATUM, 18, IFD_FORMAT_STRING), 1226 new ExifTag(TAG_GPS_DEST_LATITUDE_REF, 19, IFD_FORMAT_STRING), 1227 new ExifTag(TAG_GPS_DEST_LATITUDE, 20, IFD_FORMAT_URATIONAL), 1228 new ExifTag(TAG_GPS_DEST_LONGITUDE_REF, 21, IFD_FORMAT_STRING), 1229 new ExifTag(TAG_GPS_DEST_LONGITUDE, 22, IFD_FORMAT_URATIONAL), 1230 new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING), 1231 new ExifTag(TAG_GPS_DEST_BEARING, 24, IFD_FORMAT_URATIONAL), 1232 new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING), 1233 new ExifTag(TAG_GPS_DEST_DISTANCE, 26, IFD_FORMAT_URATIONAL), 1234 new ExifTag(TAG_GPS_PROCESSING_METHOD, 27, IFD_FORMAT_UNDEFINED), 1235 new ExifTag(TAG_GPS_AREA_INFORMATION, 28, IFD_FORMAT_UNDEFINED), 1236 new ExifTag(TAG_GPS_DATESTAMP, 29, IFD_FORMAT_STRING), 1237 new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT) 1238 }; 1239 // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 1240 private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] { 1241 new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING) 1242 }; 1243 // IFD Thumbnail tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) 1244 private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] { 1245 // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images. 1246 new ExifTag(TAG_NEW_SUBFILE_TYPE, 254, IFD_FORMAT_ULONG), 1247 new ExifTag(TAG_SUBFILE_TYPE, 255, IFD_FORMAT_ULONG), 1248 new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1249 new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1250 new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT), 1251 new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT), 1252 new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT), 1253 new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING), 1254 new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING), 1255 new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING), 1256 new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1257 new ExifTag(TAG_THUMBNAIL_ORIENTATION, 274, IFD_FORMAT_USHORT), 1258 new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT), 1259 new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1260 new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), 1261 new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL), 1262 new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL), 1263 new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT), 1264 new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT), 1265 new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT), 1266 new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING), 1267 new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING), 1268 new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING), 1269 new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL), 1270 new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL), 1271 // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1. 1272 new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG), 1273 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG), 1274 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG), 1275 new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL), 1276 new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT), 1277 new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT), 1278 new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL), 1279 new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING), 1280 new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), 1281 new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), 1282 new ExifTag(TAG_DNG_VERSION, 50706, IFD_FORMAT_BYTE), 1283 new ExifTag(TAG_DEFAULT_CROP_SIZE, 50720, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG) 1284 }; 1285 1286 // RAF file tag (See piex.cc line 372) 1287 private static final ExifTag TAG_RAF_IMAGE_SIZE = 1288 new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT); 1289 1290 // ORF file tags (See http://www.exiv2.org/tags-olympus.html) 1291 private static final ExifTag[] ORF_MAKER_NOTE_TAGS = new ExifTag[] { 1292 new ExifTag(TAG_ORF_THUMBNAIL_IMAGE, 256, IFD_FORMAT_UNDEFINED), 1293 new ExifTag(TAG_ORF_CAMERA_SETTINGS_IFD_POINTER, 8224, IFD_FORMAT_ULONG), 1294 new ExifTag(TAG_ORF_IMAGE_PROCESSING_IFD_POINTER, 8256, IFD_FORMAT_ULONG) 1295 }; 1296 private static final ExifTag[] ORF_CAMERA_SETTINGS_TAGS = new ExifTag[] { 1297 new ExifTag(TAG_ORF_PREVIEW_IMAGE_START, 257, IFD_FORMAT_ULONG), 1298 new ExifTag(TAG_ORF_PREVIEW_IMAGE_LENGTH, 258, IFD_FORMAT_ULONG) 1299 }; 1300 private static final ExifTag[] ORF_IMAGE_PROCESSING_TAGS = new ExifTag[] { 1301 new ExifTag(TAG_ORF_ASPECT_FRAME, 4371, IFD_FORMAT_USHORT) 1302 }; 1303 // PEF file tag (See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Pentax.html) 1304 private static final ExifTag[] PEF_TAGS = new ExifTag[] { 1305 new ExifTag(TAG_COLOR_SPACE, 55, IFD_FORMAT_USHORT) 1306 }; 1307 1308 // See JEITA CP-3451C Section 4.6.3: Exif-specific IFD. 1309 // The following values are used for indicating pointers to the other Image File Directories. 1310 1311 // Indices of Exif Ifd tag groups 1312 /** @hide */ 1313 @Retention(RetentionPolicy.SOURCE) 1314 @IntDef({IFD_TYPE_PRIMARY, IFD_TYPE_EXIF, IFD_TYPE_GPS, IFD_TYPE_INTEROPERABILITY, 1315 IFD_TYPE_THUMBNAIL, IFD_TYPE_PREVIEW, IFD_TYPE_ORF_MAKER_NOTE, 1316 IFD_TYPE_ORF_CAMERA_SETTINGS, IFD_TYPE_ORF_IMAGE_PROCESSING, IFD_TYPE_PEF}) 1317 public @interface IfdType {} 1318 1319 private static final int IFD_TYPE_PRIMARY = 0; 1320 private static final int IFD_TYPE_EXIF = 1; 1321 private static final int IFD_TYPE_GPS = 2; 1322 private static final int IFD_TYPE_INTEROPERABILITY = 3; 1323 private static final int IFD_TYPE_THUMBNAIL = 4; 1324 private static final int IFD_TYPE_PREVIEW = 5; 1325 private static final int IFD_TYPE_ORF_MAKER_NOTE = 6; 1326 private static final int IFD_TYPE_ORF_CAMERA_SETTINGS = 7; 1327 private static final int IFD_TYPE_ORF_IMAGE_PROCESSING = 8; 1328 private static final int IFD_TYPE_PEF = 9; 1329 1330 // List of Exif tag groups 1331 private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] { 1332 IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS, 1333 IFD_THUMBNAIL_TAGS, IFD_TIFF_TAGS, ORF_MAKER_NOTE_TAGS, ORF_CAMERA_SETTINGS_TAGS, 1334 ORF_IMAGE_PROCESSING_TAGS, PEF_TAGS 1335 }; 1336 // List of tags for pointing to the other image file directory offset. 1337 private static final ExifTag[] EXIF_POINTER_TAGS = new ExifTag[] { 1338 new ExifTag(TAG_SUB_IFD_POINTER, 330, IFD_FORMAT_ULONG), 1339 new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), 1340 new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), 1341 new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), 1342 new ExifTag(TAG_ORF_CAMERA_SETTINGS_IFD_POINTER, 8224, IFD_FORMAT_BYTE), 1343 new ExifTag(TAG_ORF_IMAGE_PROCESSING_IFD_POINTER, 8256, IFD_FORMAT_BYTE) 1344 }; 1345 1346 // Tags for indicating the thumbnail offset and length 1347 private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG = 1348 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG); 1349 private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG = 1350 new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG); 1351 1352 // Mappings from tag number to tag name and each item represents one IFD tag group. 1353 private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length]; 1354 // Mappings from tag name to tag number and each item represents one IFD tag group. 1355 private static final HashMap[] sExifTagMapsForWriting = new HashMap[EXIF_TAGS.length]; 1356 private static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList( 1357 TAG_F_NUMBER, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE, 1358 TAG_GPS_TIMESTAMP)); 1359 // Mappings from tag number to IFD type for pointer tags. 1360 private static final HashMap<Integer, Integer> sExifPointerTagMap = new HashMap(); 1361 1362 // See JPEG File Interchange Format Version 1.02. 1363 // The following values are defined for handling JPEG streams. In this implementation, we are 1364 // not only getting information from EXIF but also from some JPEG special segments such as 1365 // MARKER_COM for user comment and MARKER_SOFx for image width and height. 1366 1367 private static final Charset ASCII = Charset.forName("US-ASCII"); 1368 // Identifier for EXIF APP1 segment in JPEG 1369 private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII); 1370 // Identifier for XMP APP1 segment in JPEG 1371 private static final byte[] IDENTIFIER_XMP_APP1 = "http://ns.adobe.com/xap/1.0/\0".getBytes(ASCII); 1372 // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with 1373 // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start 1374 // of frame(baseline DCT) and the image size info exists in its beginning part. 1375 private static final byte MARKER = (byte) 0xff; 1376 private static final byte MARKER_SOI = (byte) 0xd8; 1377 private static final byte MARKER_SOF0 = (byte) 0xc0; 1378 private static final byte MARKER_SOF1 = (byte) 0xc1; 1379 private static final byte MARKER_SOF2 = (byte) 0xc2; 1380 private static final byte MARKER_SOF3 = (byte) 0xc3; 1381 private static final byte MARKER_SOF5 = (byte) 0xc5; 1382 private static final byte MARKER_SOF6 = (byte) 0xc6; 1383 private static final byte MARKER_SOF7 = (byte) 0xc7; 1384 private static final byte MARKER_SOF9 = (byte) 0xc9; 1385 private static final byte MARKER_SOF10 = (byte) 0xca; 1386 private static final byte MARKER_SOF11 = (byte) 0xcb; 1387 private static final byte MARKER_SOF13 = (byte) 0xcd; 1388 private static final byte MARKER_SOF14 = (byte) 0xce; 1389 private static final byte MARKER_SOF15 = (byte) 0xcf; 1390 private static final byte MARKER_SOS = (byte) 0xda; 1391 private static final byte MARKER_APP1 = (byte) 0xe1; 1392 private static final byte MARKER_COM = (byte) 0xfe; 1393 private static final byte MARKER_EOI = (byte) 0xd9; 1394 1395 // Supported Image File Types 1396 private static final int IMAGE_TYPE_UNKNOWN = 0; 1397 private static final int IMAGE_TYPE_ARW = 1; 1398 private static final int IMAGE_TYPE_CR2 = 2; 1399 private static final int IMAGE_TYPE_DNG = 3; 1400 private static final int IMAGE_TYPE_JPEG = 4; 1401 private static final int IMAGE_TYPE_NEF = 5; 1402 private static final int IMAGE_TYPE_NRW = 6; 1403 private static final int IMAGE_TYPE_ORF = 7; 1404 private static final int IMAGE_TYPE_PEF = 8; 1405 private static final int IMAGE_TYPE_RAF = 9; 1406 private static final int IMAGE_TYPE_RW2 = 10; 1407 private static final int IMAGE_TYPE_SRW = 11; 1408 private static final int IMAGE_TYPE_HEIF = 12; 1409 private static final int IMAGE_TYPE_PNG = 13; 1410 private static final int IMAGE_TYPE_WEBP = 14; 1411 1412 static { 1413 sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); 1414 sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); 1415 sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX"); 1416 sFormatterTz.setTimeZone(TimeZone.getTimeZone("UTC")); 1417 1418 // Build up the hash tables to look up Exif tags for reading Exif tags. 1419 for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) { 1420 sExifTagMapsForReading[ifdType] = new HashMap(); 1421 sExifTagMapsForWriting[ifdType] = new HashMap(); 1422 for (ExifTag tag : EXIF_TAGS[ifdType]) { put(tag.number, tag)1423 sExifTagMapsForReading[ifdType].put(tag.number, tag); put(tag.name, tag)1424 sExifTagMapsForWriting[ifdType].put(tag.name, tag); 1425 } 1426 } 1427 1428 // Build up the hash table to look up Exif pointer tags. sExifPointerTagMap.put(EXIF_POINTER_TAGS[0].number, IFD_TYPE_PREVIEW)1429 sExifPointerTagMap.put(EXIF_POINTER_TAGS[0].number, IFD_TYPE_PREVIEW); // 330 sExifPointerTagMap.put(EXIF_POINTER_TAGS[1].number, IFD_TYPE_EXIF)1430 sExifPointerTagMap.put(EXIF_POINTER_TAGS[1].number, IFD_TYPE_EXIF); // 34665 sExifPointerTagMap.put(EXIF_POINTER_TAGS[2].number, IFD_TYPE_GPS)1431 sExifPointerTagMap.put(EXIF_POINTER_TAGS[2].number, IFD_TYPE_GPS); // 34853 sExifPointerTagMap.put(EXIF_POINTER_TAGS[3].number, IFD_TYPE_INTEROPERABILITY)1432 sExifPointerTagMap.put(EXIF_POINTER_TAGS[3].number, IFD_TYPE_INTEROPERABILITY); // 40965 sExifPointerTagMap.put(EXIF_POINTER_TAGS[4].number, IFD_TYPE_ORF_CAMERA_SETTINGS)1433 sExifPointerTagMap.put(EXIF_POINTER_TAGS[4].number, IFD_TYPE_ORF_CAMERA_SETTINGS); // 8224 sExifPointerTagMap.put(EXIF_POINTER_TAGS[5].number, IFD_TYPE_ORF_IMAGE_PROCESSING)1434 sExifPointerTagMap.put(EXIF_POINTER_TAGS[5].number, IFD_TYPE_ORF_IMAGE_PROCESSING); // 8256 1435 } 1436 1437 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1438 private String mFilename; 1439 private FileDescriptor mSeekableFileDescriptor; 1440 private AssetManager.AssetInputStream mAssetInputStream; 1441 private boolean mIsInputStream; 1442 private int mMimeType; 1443 private boolean mIsExifDataOnly; 1444 @UnsupportedAppUsage 1445 private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; 1446 private Set<Integer> mHandledIfdOffsets = new HashSet<>(EXIF_TAGS.length); 1447 private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN; 1448 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1449 private boolean mHasThumbnail; 1450 private boolean mHasThumbnailStrips; 1451 private boolean mAreThumbnailStripsConsecutive; 1452 // Used to indicate the position of the thumbnail (includes offset to EXIF data segment). 1453 private int mThumbnailOffset; 1454 private int mThumbnailLength; 1455 private byte[] mThumbnailBytes; 1456 private int mThumbnailCompression; 1457 private int mExifOffset; 1458 private int mOrfMakerNoteOffset; 1459 private int mOrfThumbnailOffset; 1460 private int mOrfThumbnailLength; 1461 private int mRw2JpgFromRawOffset; 1462 private boolean mIsSupportedFile; 1463 private boolean mModified; 1464 // XMP data can be contained as either part of the EXIF data (tag number 700), or as a 1465 // separate data marker (a separate MARKER_APP1). 1466 private boolean mXmpIsFromSeparateMarker; 1467 1468 // Pattern to check non zero timestamp 1469 private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); 1470 // Pattern to check gps timestamp 1471 private static final Pattern sGpsTimestampPattern = 1472 Pattern.compile("^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"); 1473 1474 /** 1475 * Reads Exif tags from the specified image file. 1476 * 1477 * @param file the file of the image data 1478 * @throws NullPointerException if file is null 1479 * @throws IOException if an I/O error occurs while retrieving file descriptor via 1480 * {@link FileInputStream#getFD()}. 1481 */ ExifInterface(@onNull File file)1482 public ExifInterface(@NonNull File file) throws IOException { 1483 if (file == null) { 1484 throw new NullPointerException("file cannot be null"); 1485 } 1486 initForFilename(file.getAbsolutePath()); 1487 } 1488 1489 /** 1490 * Reads Exif tags from the specified image file. 1491 * 1492 * @param filename the name of the file of the image data 1493 * @throws NullPointerException if file name is null 1494 * @throws IOException if an I/O error occurs while retrieving file descriptor via 1495 * {@link FileInputStream#getFD()}. 1496 */ ExifInterface(@onNull String filename)1497 public ExifInterface(@NonNull String filename) throws IOException { 1498 if (filename == null) { 1499 throw new NullPointerException("filename cannot be null"); 1500 } 1501 initForFilename(filename); 1502 } 1503 1504 /** 1505 * Reads Exif tags from the specified image file descriptor. Attribute mutation is supported 1506 * for writable and seekable file descriptors only. This constructor will not rewind the offset 1507 * of the given file descriptor. Developers should close the file descriptor after use. 1508 * 1509 * @param fileDescriptor the file descriptor of the image data 1510 * @throws NullPointerException if file descriptor is null 1511 * @throws IOException if an error occurs while duplicating the file descriptor via 1512 * {@link Os#dup(FileDescriptor)}. 1513 */ ExifInterface(@onNull FileDescriptor fileDescriptor)1514 public ExifInterface(@NonNull FileDescriptor fileDescriptor) throws IOException { 1515 if (fileDescriptor == null) { 1516 throw new NullPointerException("fileDescriptor cannot be null"); 1517 } 1518 1519 mAssetInputStream = null; 1520 mFilename = null; 1521 // When FileDescriptor is duplicated and set to FileInputStream, ownership needs to be 1522 // clarified in order for garbage collection to take place. 1523 boolean isFdOwner = false; 1524 if (isSeekableFD(fileDescriptor)) { 1525 mSeekableFileDescriptor = fileDescriptor; 1526 // Keep the original file descriptor in order to save attributes when it's seekable. 1527 // Otherwise, just close the given file descriptor after reading it because the save 1528 // feature won't be working. 1529 try { 1530 fileDescriptor = Os.dup(fileDescriptor); 1531 isFdOwner = true; 1532 } catch (ErrnoException e) { 1533 throw e.rethrowAsIOException(); 1534 } 1535 } else { 1536 mSeekableFileDescriptor = null; 1537 } 1538 mIsInputStream = false; 1539 FileInputStream in = null; 1540 try { 1541 in = new FileInputStream(fileDescriptor, isFdOwner); 1542 loadAttributes(in); 1543 } finally { 1544 IoUtils.closeQuietly(in); 1545 } 1546 } 1547 1548 /** 1549 * Reads Exif tags from the specified image input stream. Attribute mutation is not supported 1550 * for input streams. The given input stream will proceed from its current position. Developers 1551 * should close the input stream after use. This constructor is not intended to be used with an 1552 * input stream that performs any networking operations. 1553 * 1554 * @param inputStream the input stream that contains the image data 1555 * @throws NullPointerException if the input stream is null 1556 */ ExifInterface(@onNull InputStream inputStream)1557 public ExifInterface(@NonNull InputStream inputStream) throws IOException { 1558 this(inputStream, false); 1559 } 1560 1561 /** 1562 * Reads Exif tags from the specified image input stream based on the stream type. Attribute 1563 * mutation is not supported for input streams. The given input stream will proceed from its 1564 * current position. Developers should close the input stream after use. This constructor is not 1565 * intended to be used with an input stream that performs any networking operations. 1566 * 1567 * @param inputStream the input stream that contains the image data 1568 * @param streamType the type of input stream 1569 * @throws NullPointerException if the input stream is null 1570 * @throws IOException if an I/O error occurs while retrieving file descriptor via 1571 * {@link FileInputStream#getFD()}. 1572 */ ExifInterface(@onNull InputStream inputStream, @ExifStreamType int streamType)1573 public ExifInterface(@NonNull InputStream inputStream, @ExifStreamType int streamType) 1574 throws IOException { 1575 this(inputStream, (streamType == STREAM_TYPE_EXIF_DATA_ONLY) ? true : false); 1576 } 1577 ExifInterface(@onNull InputStream inputStream, boolean shouldBeExifDataOnly)1578 private ExifInterface(@NonNull InputStream inputStream, boolean shouldBeExifDataOnly) 1579 throws IOException { 1580 if (inputStream == null) { 1581 throw new NullPointerException("inputStream cannot be null"); 1582 } 1583 mFilename = null; 1584 1585 if (shouldBeExifDataOnly) { 1586 inputStream = new BufferedInputStream(inputStream, SIGNATURE_CHECK_SIZE); 1587 if (!isExifDataOnly((BufferedInputStream) inputStream)) { 1588 Log.w(TAG, "Given data does not follow the structure of an Exif-only data."); 1589 return; 1590 } 1591 mIsExifDataOnly = true; 1592 mAssetInputStream = null; 1593 mSeekableFileDescriptor = null; 1594 } else { 1595 if (inputStream instanceof AssetManager.AssetInputStream) { 1596 mAssetInputStream = (AssetManager.AssetInputStream) inputStream; 1597 mSeekableFileDescriptor = null; 1598 } else if (inputStream instanceof FileInputStream 1599 && (isSeekableFD(((FileInputStream) inputStream).getFD()))) { 1600 mAssetInputStream = null; 1601 mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD(); 1602 } else { 1603 mAssetInputStream = null; 1604 mSeekableFileDescriptor = null; 1605 } 1606 } 1607 loadAttributes(inputStream); 1608 } 1609 1610 /** 1611 * Returns whether ExifInterface currently supports parsing data from the specified mime type 1612 * or not. 1613 * 1614 * @param mimeType the string value of mime type 1615 */ isSupportedMimeType(@onNull String mimeType)1616 public static boolean isSupportedMimeType(@NonNull String mimeType) { 1617 if (mimeType == null) { 1618 throw new NullPointerException("mimeType shouldn't be null"); 1619 } 1620 1621 switch (mimeType.toLowerCase(Locale.ROOT)) { 1622 case "image/jpeg": 1623 case "image/x-adobe-dng": 1624 case "image/x-canon-cr2": 1625 case "image/x-nikon-nef": 1626 case "image/x-nikon-nrw": 1627 case "image/x-sony-arw": 1628 case "image/x-panasonic-rw2": 1629 case "image/x-olympus-orf": 1630 case "image/x-pentax-pef": 1631 case "image/x-samsung-srw": 1632 case "image/x-fuji-raf": 1633 case "image/heic": 1634 case "image/heif": 1635 return true; 1636 default: 1637 return false; 1638 } 1639 } 1640 1641 /** 1642 * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in 1643 * the image file. 1644 * 1645 * @param tag the name of the tag. 1646 */ getExifAttribute(@onNull String tag)1647 private @Nullable ExifAttribute getExifAttribute(@NonNull String tag) { 1648 if (tag == null) { 1649 throw new NullPointerException("tag shouldn't be null"); 1650 } 1651 // Retrieves all tag groups. The value from primary image tag group has a higher priority 1652 // than the value from the thumbnail tag group if there are more than one candidates. 1653 for (int i = 0; i < EXIF_TAGS.length; ++i) { 1654 Object value = mAttributes[i].get(tag); 1655 if (value != null) { 1656 return (ExifAttribute) value; 1657 } 1658 } 1659 return null; 1660 } 1661 1662 /** 1663 * Returns the value of the specified tag or {@code null} if there 1664 * is no such tag in the image file. 1665 * 1666 * @param tag the name of the tag. 1667 */ getAttribute(@onNull String tag)1668 public @Nullable String getAttribute(@NonNull String tag) { 1669 if (tag == null) { 1670 throw new NullPointerException("tag shouldn't be null"); 1671 } 1672 ExifAttribute attribute = getExifAttribute(tag); 1673 if (attribute != null) { 1674 if (!sTagSetForCompatibility.contains(tag)) { 1675 return attribute.getStringValue(mExifByteOrder); 1676 } 1677 if (tag.equals(TAG_GPS_TIMESTAMP)) { 1678 // Convert the rational values to the custom formats for backwards compatibility. 1679 if (attribute.format != IFD_FORMAT_URATIONAL 1680 && attribute.format != IFD_FORMAT_SRATIONAL) { 1681 return null; 1682 } 1683 Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder); 1684 if (array.length != 3) { 1685 return null; 1686 } 1687 return String.format("%02d:%02d:%02d", 1688 (int) ((float) array[0].numerator / array[0].denominator), 1689 (int) ((float) array[1].numerator / array[1].denominator), 1690 (int) ((float) array[2].numerator / array[2].denominator)); 1691 } 1692 try { 1693 return Double.toString(attribute.getDoubleValue(mExifByteOrder)); 1694 } catch (NumberFormatException e) { 1695 return null; 1696 } 1697 } 1698 return null; 1699 } 1700 1701 /** 1702 * Returns the integer value of the specified tag. If there is no such tag 1703 * in the image file or the value cannot be parsed as integer, return 1704 * <var>defaultValue</var>. 1705 * 1706 * @param tag the name of the tag. 1707 * @param defaultValue the value to return if the tag is not available. 1708 */ getAttributeInt(@onNull String tag, int defaultValue)1709 public int getAttributeInt(@NonNull String tag, int defaultValue) { 1710 if (tag == null) { 1711 throw new NullPointerException("tag shouldn't be null"); 1712 } 1713 ExifAttribute exifAttribute = getExifAttribute(tag); 1714 if (exifAttribute == null) { 1715 return defaultValue; 1716 } 1717 1718 try { 1719 return exifAttribute.getIntValue(mExifByteOrder); 1720 } catch (NumberFormatException e) { 1721 return defaultValue; 1722 } 1723 } 1724 1725 /** 1726 * Returns the double value of the tag that is specified as rational or contains a 1727 * double-formatted value. If there is no such tag in the image file or the value cannot be 1728 * parsed as double, return <var>defaultValue</var>. 1729 * 1730 * @param tag the name of the tag. 1731 * @param defaultValue the value to return if the tag is not available. 1732 */ getAttributeDouble(@onNull String tag, double defaultValue)1733 public double getAttributeDouble(@NonNull String tag, double defaultValue) { 1734 if (tag == null) { 1735 throw new NullPointerException("tag shouldn't be null"); 1736 } 1737 ExifAttribute exifAttribute = getExifAttribute(tag); 1738 if (exifAttribute == null) { 1739 return defaultValue; 1740 } 1741 1742 try { 1743 return exifAttribute.getDoubleValue(mExifByteOrder); 1744 } catch (NumberFormatException e) { 1745 return defaultValue; 1746 } 1747 } 1748 1749 /** 1750 * Set the value of the specified tag. 1751 * 1752 * @param tag the name of the tag. 1753 * @param value the value of the tag. 1754 */ setAttribute(@onNull String tag, @Nullable String value)1755 public void setAttribute(@NonNull String tag, @Nullable String value) { 1756 if (tag == null) { 1757 throw new NullPointerException("tag shouldn't be null"); 1758 } 1759 // Convert the given value to rational values for backwards compatibility. 1760 if (value != null && sTagSetForCompatibility.contains(tag)) { 1761 if (tag.equals(TAG_GPS_TIMESTAMP)) { 1762 Matcher m = sGpsTimestampPattern.matcher(value); 1763 if (!m.find()) { 1764 Log.w(TAG, "Invalid value for " + tag + " : " + value); 1765 return; 1766 } 1767 value = Integer.parseInt(m.group(1)) + "/1," + Integer.parseInt(m.group(2)) + "/1," 1768 + Integer.parseInt(m.group(3)) + "/1"; 1769 } else { 1770 try { 1771 double doubleValue = Double.parseDouble(value); 1772 value = (long) (doubleValue * 10000L) + "/10000"; 1773 } catch (NumberFormatException e) { 1774 Log.w(TAG, "Invalid value for " + tag + " : " + value); 1775 return; 1776 } 1777 } 1778 } 1779 1780 for (int i = 0 ; i < EXIF_TAGS.length; ++i) { 1781 if (i == IFD_TYPE_THUMBNAIL && !mHasThumbnail) { 1782 continue; 1783 } 1784 final Object obj = sExifTagMapsForWriting[i].get(tag); 1785 if (obj != null) { 1786 if (value == null) { 1787 mAttributes[i].remove(tag); 1788 continue; 1789 } 1790 final ExifTag exifTag = (ExifTag) obj; 1791 Pair<Integer, Integer> guess = guessDataFormat(value); 1792 int dataFormat; 1793 if (exifTag.primaryFormat == guess.first || exifTag.primaryFormat == guess.second) { 1794 dataFormat = exifTag.primaryFormat; 1795 } else if (exifTag.secondaryFormat != -1 && (exifTag.secondaryFormat == guess.first 1796 || exifTag.secondaryFormat == guess.second)) { 1797 dataFormat = exifTag.secondaryFormat; 1798 } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE 1799 || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED 1800 || exifTag.primaryFormat == IFD_FORMAT_STRING) { 1801 dataFormat = exifTag.primaryFormat; 1802 } else { 1803 if (DEBUG) { 1804 Log.d(TAG, "Given tag (" + tag 1805 + ") value didn't match with one of expected " 1806 + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat] 1807 + (exifTag.secondaryFormat == -1 ? "" : ", " 1808 + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: " 1809 + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? "" : ", " 1810 + IFD_FORMAT_NAMES[guess.second]) + ")"); 1811 } 1812 continue; 1813 } 1814 switch (dataFormat) { 1815 case IFD_FORMAT_BYTE: { 1816 mAttributes[i].put(tag, ExifAttribute.createByte(value)); 1817 break; 1818 } 1819 case IFD_FORMAT_UNDEFINED: 1820 case IFD_FORMAT_STRING: { 1821 mAttributes[i].put(tag, ExifAttribute.createString(value)); 1822 break; 1823 } 1824 case IFD_FORMAT_USHORT: { 1825 final String[] values = value.split(","); 1826 final int[] intArray = new int[values.length]; 1827 for (int j = 0; j < values.length; ++j) { 1828 intArray[j] = Integer.parseInt(values[j]); 1829 } 1830 mAttributes[i].put(tag, 1831 ExifAttribute.createUShort(intArray, mExifByteOrder)); 1832 break; 1833 } 1834 case IFD_FORMAT_SLONG: { 1835 final String[] values = value.split(","); 1836 final int[] intArray = new int[values.length]; 1837 for (int j = 0; j < values.length; ++j) { 1838 intArray[j] = Integer.parseInt(values[j]); 1839 } 1840 mAttributes[i].put(tag, 1841 ExifAttribute.createSLong(intArray, mExifByteOrder)); 1842 break; 1843 } 1844 case IFD_FORMAT_ULONG: { 1845 final String[] values = value.split(","); 1846 final long[] longArray = new long[values.length]; 1847 for (int j = 0; j < values.length; ++j) { 1848 longArray[j] = Long.parseLong(values[j]); 1849 } 1850 mAttributes[i].put(tag, 1851 ExifAttribute.createULong(longArray, mExifByteOrder)); 1852 break; 1853 } 1854 case IFD_FORMAT_URATIONAL: { 1855 final String[] values = value.split(","); 1856 final Rational[] rationalArray = new Rational[values.length]; 1857 for (int j = 0; j < values.length; ++j) { 1858 final String[] numbers = values[j].split("/"); 1859 rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]), 1860 (long) Double.parseDouble(numbers[1])); 1861 } 1862 mAttributes[i].put(tag, 1863 ExifAttribute.createURational(rationalArray, mExifByteOrder)); 1864 break; 1865 } 1866 case IFD_FORMAT_SRATIONAL: { 1867 final String[] values = value.split(","); 1868 final Rational[] rationalArray = new Rational[values.length]; 1869 for (int j = 0; j < values.length; ++j) { 1870 final String[] numbers = values[j].split("/"); 1871 rationalArray[j] = new Rational((long) Double.parseDouble(numbers[0]), 1872 (long) Double.parseDouble(numbers[1])); 1873 } 1874 mAttributes[i].put(tag, 1875 ExifAttribute.createSRational(rationalArray, mExifByteOrder)); 1876 break; 1877 } 1878 case IFD_FORMAT_DOUBLE: { 1879 final String[] values = value.split(","); 1880 final double[] doubleArray = new double[values.length]; 1881 for (int j = 0; j < values.length; ++j) { 1882 doubleArray[j] = Double.parseDouble(values[j]); 1883 } 1884 mAttributes[i].put(tag, 1885 ExifAttribute.createDouble(doubleArray, mExifByteOrder)); 1886 break; 1887 } 1888 default: 1889 if (DEBUG) { 1890 Log.d(TAG, "Data format isn't one of expected formats: " + dataFormat); 1891 } 1892 continue; 1893 } 1894 } 1895 } 1896 } 1897 1898 /** 1899 * Update the values of the tags in the tag groups if any value for the tag already was stored. 1900 * 1901 * @param tag the name of the tag. 1902 * @param value the value of the tag in a form of {@link ExifAttribute}. 1903 * @return Returns {@code true} if updating is placed. 1904 */ updateAttribute(String tag, ExifAttribute value)1905 private boolean updateAttribute(String tag, ExifAttribute value) { 1906 boolean updated = false; 1907 for (int i = 0 ; i < EXIF_TAGS.length; ++i) { 1908 if (mAttributes[i].containsKey(tag)) { 1909 mAttributes[i].put(tag, value); 1910 updated = true; 1911 } 1912 } 1913 return updated; 1914 } 1915 1916 /** 1917 * Remove any values of the specified tag. 1918 * 1919 * @param tag the name of the tag. 1920 */ removeAttribute(String tag)1921 private void removeAttribute(String tag) { 1922 for (int i = 0 ; i < EXIF_TAGS.length; ++i) { 1923 mAttributes[i].remove(tag); 1924 } 1925 } 1926 1927 /** 1928 * This function decides which parser to read the image data according to the given input stream 1929 * type and the content of the input stream. 1930 */ loadAttributes(@onNull InputStream in)1931 private void loadAttributes(@NonNull InputStream in) { 1932 if (in == null) { 1933 throw new NullPointerException("inputstream shouldn't be null"); 1934 } 1935 try { 1936 // Initialize mAttributes. 1937 for (int i = 0; i < EXIF_TAGS.length; ++i) { 1938 mAttributes[i] = new HashMap(); 1939 } 1940 1941 // Check file type 1942 if (!mIsExifDataOnly) { 1943 in = new BufferedInputStream(in, SIGNATURE_CHECK_SIZE); 1944 mMimeType = getMimeType((BufferedInputStream) in); 1945 } 1946 1947 // Create byte-ordered input stream 1948 ByteOrderedDataInputStream inputStream = new ByteOrderedDataInputStream(in); 1949 1950 if (!mIsExifDataOnly) { 1951 switch (mMimeType) { 1952 case IMAGE_TYPE_JPEG: { 1953 getJpegAttributes(inputStream, 0, IFD_TYPE_PRIMARY); // 0 is offset 1954 break; 1955 } 1956 case IMAGE_TYPE_RAF: { 1957 getRafAttributes(inputStream); 1958 break; 1959 } 1960 case IMAGE_TYPE_HEIF: { 1961 getHeifAttributes(inputStream); 1962 break; 1963 } 1964 case IMAGE_TYPE_ORF: { 1965 getOrfAttributes(inputStream); 1966 break; 1967 } 1968 case IMAGE_TYPE_RW2: { 1969 getRw2Attributes(inputStream); 1970 break; 1971 } 1972 case IMAGE_TYPE_PNG: { 1973 getPngAttributes(inputStream); 1974 break; 1975 } 1976 case IMAGE_TYPE_WEBP: { 1977 getWebpAttributes(inputStream); 1978 break; 1979 } 1980 case IMAGE_TYPE_ARW: 1981 case IMAGE_TYPE_CR2: 1982 case IMAGE_TYPE_DNG: 1983 case IMAGE_TYPE_NEF: 1984 case IMAGE_TYPE_NRW: 1985 case IMAGE_TYPE_PEF: 1986 case IMAGE_TYPE_SRW: 1987 case IMAGE_TYPE_UNKNOWN: { 1988 getRawAttributes(inputStream); 1989 break; 1990 } 1991 default: { 1992 break; 1993 } 1994 } 1995 } else { 1996 getStandaloneAttributes(inputStream); 1997 } 1998 // Set thumbnail image offset and length 1999 setThumbnailData(inputStream); 2000 mIsSupportedFile = true; 2001 } catch (IOException | OutOfMemoryError e) { 2002 // Ignore exceptions in order to keep the compatibility with the old versions of 2003 // ExifInterface. 2004 mIsSupportedFile = false; 2005 Log.w(TAG, "Invalid image: ExifInterface got an unsupported image format file" 2006 + "(ExifInterface supports JPEG and some RAW image formats only) " 2007 + "or a corrupted JPEG file to ExifInterface.", e); 2008 } finally { 2009 addDefaultValuesForCompatibility(); 2010 2011 if (DEBUG) { 2012 printAttributes(); 2013 } 2014 } 2015 } 2016 isSeekableFD(FileDescriptor fd)2017 private static boolean isSeekableFD(FileDescriptor fd) { 2018 try { 2019 Os.lseek(fd, 0, OsConstants.SEEK_CUR); 2020 return true; 2021 } catch (ErrnoException e) { 2022 if (DEBUG) { 2023 Log.d(TAG, "The file descriptor for the given input is not seekable"); 2024 } 2025 return false; 2026 } 2027 } 2028 2029 // Prints out attributes for debugging. printAttributes()2030 private void printAttributes() { 2031 for (int i = 0; i < mAttributes.length; ++i) { 2032 Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size()); 2033 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) { 2034 final ExifAttribute tagValue = (ExifAttribute) entry.getValue(); 2035 Log.d(TAG, "tagName: " + entry.getKey() + ", tagType: " + tagValue.toString() 2036 + ", tagValue: '" + tagValue.getStringValue(mExifByteOrder) + "'"); 2037 } 2038 } 2039 } 2040 2041 /** 2042 * Save the tag data into the original image file. This is expensive because 2043 * it involves copying all the data from one file to another and deleting 2044 * the old file and renaming the other. It's best to use 2045 * {@link #setAttribute(String,String)} to set all attributes to write and 2046 * make a single call rather than multiple calls for each attribute. 2047 * <p> 2048 * This method is only supported for JPEG and PNG files. 2049 * <p class="note"> 2050 * Note: after calling this method, any attempts to obtain range information 2051 * from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()} 2052 * will throw {@link IllegalStateException}, since the offsets may have 2053 * changed in the newly written file. 2054 * </p> 2055 */ saveAttributes()2056 public void saveAttributes() throws IOException { 2057 if (!mIsSupportedFile || (mMimeType != IMAGE_TYPE_JPEG && mMimeType != IMAGE_TYPE_PNG)) { 2058 throw new IOException("ExifInterface only supports saving attributes on JPEG or PNG " 2059 + "formats."); 2060 } 2061 if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) { 2062 throw new IOException( 2063 "ExifInterface does not support saving attributes for the current input."); 2064 } 2065 2066 // Remember the fact that we've changed the file on disk from what was 2067 // originally parsed, meaning we can't answer range questions 2068 mModified = true; 2069 2070 // Keep the thumbnail in memory 2071 mThumbnailBytes = getThumbnail(); 2072 2073 FileInputStream in = null; 2074 FileOutputStream out = null; 2075 File originalFile = null; 2076 if (mFilename != null) { 2077 originalFile = new File(mFilename); 2078 } 2079 File tempFile = null; 2080 try { 2081 // Move the original file to temporary file. 2082 if (mFilename != null) { 2083 String parent = originalFile.getParent(); 2084 String name = originalFile.getName(); 2085 String tempPrefix = UUID.randomUUID().toString() + "_"; 2086 tempFile = new File(parent, tempPrefix + name); 2087 if (!originalFile.renameTo(tempFile)) { 2088 throw new IOException("Couldn't rename to " + tempFile.getAbsolutePath()); 2089 } 2090 } else if (mSeekableFileDescriptor != null) { 2091 if (mMimeType == IMAGE_TYPE_JPEG) { 2092 tempFile = File.createTempFile("temp", "jpg"); 2093 } else if (mMimeType == IMAGE_TYPE_PNG) { 2094 tempFile = File.createTempFile("temp", "png"); 2095 } 2096 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET); 2097 in = new FileInputStream(mSeekableFileDescriptor); 2098 out = new FileOutputStream(tempFile); 2099 Streams.copy(in, out); 2100 } 2101 } catch (Exception e) { 2102 throw new IOException("Failed to copy original file to temp file", e); 2103 } finally { 2104 IoUtils.closeQuietly(in); 2105 IoUtils.closeQuietly(out); 2106 } 2107 2108 in = null; 2109 out = null; 2110 try { 2111 // Save the new file. 2112 in = new FileInputStream(tempFile); 2113 if (mFilename != null) { 2114 out = new FileOutputStream(mFilename); 2115 } else if (mSeekableFileDescriptor != null) { 2116 Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET); 2117 out = new FileOutputStream(mSeekableFileDescriptor); 2118 } 2119 try (BufferedInputStream bufferedIn = new BufferedInputStream(in); 2120 BufferedOutputStream bufferedOut = new BufferedOutputStream(out)) { 2121 if (mMimeType == IMAGE_TYPE_JPEG) { 2122 saveJpegAttributes(bufferedIn, bufferedOut); 2123 } else if (mMimeType == IMAGE_TYPE_PNG) { 2124 savePngAttributes(bufferedIn, bufferedOut); 2125 } 2126 } 2127 } catch (Exception e) { 2128 if (mFilename != null) { 2129 if (!tempFile.renameTo(originalFile)) { 2130 throw new IOException("Couldn't restore original file: " 2131 + originalFile.getAbsolutePath()); 2132 } 2133 } 2134 throw new IOException("Failed to save new file", e); 2135 } finally { 2136 IoUtils.closeQuietly(in); 2137 IoUtils.closeQuietly(out); 2138 tempFile.delete(); 2139 } 2140 2141 // Discard the thumbnail in memory 2142 mThumbnailBytes = null; 2143 } 2144 2145 /** 2146 * Returns true if the image file has a thumbnail. 2147 */ hasThumbnail()2148 public boolean hasThumbnail() { 2149 return mHasThumbnail; 2150 } 2151 2152 /** 2153 * Returns true if the image file has the given attribute defined. 2154 * 2155 * @param tag the name of the tag. 2156 */ hasAttribute(@onNull String tag)2157 public boolean hasAttribute(@NonNull String tag) { 2158 return (getExifAttribute(tag) != null); 2159 } 2160 2161 /** 2162 * Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no 2163 * JPEG compressed thumbnail. 2164 * The returned data can be decoded using 2165 * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)} 2166 */ getThumbnail()2167 public byte[] getThumbnail() { 2168 if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { 2169 return getThumbnailBytes(); 2170 } 2171 return null; 2172 } 2173 2174 /** 2175 * Returns the thumbnail bytes inside the image file, regardless of the compression type of the 2176 * thumbnail image. 2177 */ getThumbnailBytes()2178 public byte[] getThumbnailBytes() { 2179 if (!mHasThumbnail) { 2180 return null; 2181 } 2182 if (mThumbnailBytes != null) { 2183 return mThumbnailBytes; 2184 } 2185 2186 // Read the thumbnail. 2187 InputStream in = null; 2188 try { 2189 if (mAssetInputStream != null) { 2190 in = mAssetInputStream; 2191 if (in.markSupported()) { 2192 in.reset(); 2193 } else { 2194 Log.d(TAG, "Cannot read thumbnail from inputstream without mark/reset support"); 2195 return null; 2196 } 2197 } else if (mFilename != null) { 2198 in = new FileInputStream(mFilename); 2199 } else if (mSeekableFileDescriptor != null) { 2200 FileDescriptor fileDescriptor = Os.dup(mSeekableFileDescriptor); 2201 Os.lseek(fileDescriptor, 0, OsConstants.SEEK_SET); 2202 in = new FileInputStream(fileDescriptor, true); 2203 } 2204 if (in == null) { 2205 // Should not be reached this. 2206 throw new FileNotFoundException(); 2207 } 2208 if (in.skip(mThumbnailOffset) != mThumbnailOffset) { 2209 throw new IOException("Corrupted image"); 2210 } 2211 // TODO: Need to handle potential OutOfMemoryError 2212 byte[] buffer = new byte[mThumbnailLength]; 2213 if (in.read(buffer) != mThumbnailLength) { 2214 throw new IOException("Corrupted image"); 2215 } 2216 mThumbnailBytes = buffer; 2217 return buffer; 2218 } catch (IOException | ErrnoException e) { 2219 // Couldn't get a thumbnail image. 2220 Log.d(TAG, "Encountered exception while getting thumbnail", e); 2221 } finally { 2222 IoUtils.closeQuietly(in); 2223 } 2224 return null; 2225 } 2226 2227 /** 2228 * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the 2229 * thumbnail compression value, or {@code null} if the compression type is unsupported. 2230 */ getThumbnailBitmap()2231 public Bitmap getThumbnailBitmap() { 2232 if (!mHasThumbnail) { 2233 return null; 2234 } else if (mThumbnailBytes == null) { 2235 mThumbnailBytes = getThumbnailBytes(); 2236 } 2237 2238 if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { 2239 return BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailLength); 2240 } else if (mThumbnailCompression == DATA_UNCOMPRESSED) { 2241 int[] rgbValues = new int[mThumbnailBytes.length / 3]; 2242 byte alpha = (byte) 0xff000000; 2243 for (int i = 0; i < rgbValues.length; i++) { 2244 rgbValues[i] = alpha + (mThumbnailBytes[3 * i] << 16) 2245 + (mThumbnailBytes[3 * i + 1] << 8) + mThumbnailBytes[3 * i + 2]; 2246 } 2247 2248 ExifAttribute imageLengthAttribute = 2249 (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_LENGTH); 2250 ExifAttribute imageWidthAttribute = 2251 (ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_WIDTH); 2252 if (imageLengthAttribute != null && imageWidthAttribute != null) { 2253 int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder); 2254 int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder); 2255 return Bitmap.createBitmap( 2256 rgbValues, imageWidth, imageLength, Bitmap.Config.ARGB_8888); 2257 } 2258 } 2259 return null; 2260 } 2261 2262 /** 2263 * Returns true if thumbnail image is JPEG Compressed, or false if either thumbnail image does 2264 * not exist or thumbnail image is uncompressed. 2265 */ isThumbnailCompressed()2266 public boolean isThumbnailCompressed() { 2267 if (!mHasThumbnail) { 2268 return false; 2269 } 2270 if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { 2271 return true; 2272 } 2273 return false; 2274 } 2275 2276 /** 2277 * Returns the offset and length of thumbnail inside the image file, or 2278 * {@code null} if either there is no thumbnail or the thumbnail bytes are stored 2279 * non-consecutively. 2280 * 2281 * @return two-element array, the offset in the first value, and length in 2282 * the second, or {@code null} if no thumbnail was found or the thumbnail strips are 2283 * not placed consecutively. 2284 * @throws IllegalStateException if {@link #saveAttributes()} has been 2285 * called since the underlying file was initially parsed, since 2286 * that means offsets may have changed. 2287 */ getThumbnailRange()2288 public @Nullable long[] getThumbnailRange() { 2289 if (mModified) { 2290 throw new IllegalStateException( 2291 "The underlying file has been modified since being parsed"); 2292 } 2293 2294 if (mHasThumbnail) { 2295 if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) { 2296 return null; 2297 } 2298 return new long[] { mThumbnailOffset, mThumbnailLength }; 2299 } 2300 return null; 2301 } 2302 2303 /** 2304 * Returns the offset and length of the requested tag inside the image file, 2305 * or {@code null} if the tag is not contained. 2306 * 2307 * @return two-element array, the offset in the first value, and length in 2308 * the second, or {@code null} if no tag was found. 2309 * @throws IllegalStateException if {@link #saveAttributes()} has been 2310 * called since the underlying file was initially parsed, since 2311 * that means offsets may have changed. 2312 */ getAttributeRange(@onNull String tag)2313 public @Nullable long[] getAttributeRange(@NonNull String tag) { 2314 if (tag == null) { 2315 throw new NullPointerException("tag shouldn't be null"); 2316 } 2317 if (mModified) { 2318 throw new IllegalStateException( 2319 "The underlying file has been modified since being parsed"); 2320 } 2321 2322 final ExifAttribute attribute = getExifAttribute(tag); 2323 if (attribute != null) { 2324 return new long[] { attribute.bytesOffset, attribute.bytes.length }; 2325 } else { 2326 return null; 2327 } 2328 } 2329 2330 /** 2331 * Returns the raw bytes for the value of the requested tag inside the image 2332 * file, or {@code null} if the tag is not contained. 2333 * 2334 * @return raw bytes for the value of the requested tag, or {@code null} if 2335 * no tag was found. 2336 */ getAttributeBytes(@onNull String tag)2337 public @Nullable byte[] getAttributeBytes(@NonNull String tag) { 2338 if (tag == null) { 2339 throw new NullPointerException("tag shouldn't be null"); 2340 } 2341 final ExifAttribute attribute = getExifAttribute(tag); 2342 if (attribute != null) { 2343 return attribute.bytes; 2344 } else { 2345 return null; 2346 } 2347 } 2348 2349 /** 2350 * Stores the latitude and longitude value in a float array. The first element is 2351 * the latitude, and the second element is the longitude. Returns false if the 2352 * Exif tags are not available. 2353 */ getLatLong(float output[])2354 public boolean getLatLong(float output[]) { 2355 String latValue = getAttribute(TAG_GPS_LATITUDE); 2356 String latRef = getAttribute(TAG_GPS_LATITUDE_REF); 2357 String lngValue = getAttribute(TAG_GPS_LONGITUDE); 2358 String lngRef = getAttribute(TAG_GPS_LONGITUDE_REF); 2359 2360 if (latValue != null && latRef != null && lngValue != null && lngRef != null) { 2361 try { 2362 output[0] = convertRationalLatLonToFloat(latValue, latRef); 2363 output[1] = convertRationalLatLonToFloat(lngValue, lngRef); 2364 return true; 2365 } catch (IllegalArgumentException e) { 2366 // if values are not parseable 2367 } 2368 } 2369 2370 return false; 2371 } 2372 2373 /** 2374 * Return the altitude in meters. If the exif tag does not exist, return 2375 * <var>defaultValue</var>. 2376 * 2377 * @param defaultValue the value to return if the tag is not available. 2378 */ getAltitude(double defaultValue)2379 public double getAltitude(double defaultValue) { 2380 double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1); 2381 int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1); 2382 2383 if (altitude >= 0 && ref >= 0) { 2384 return (altitude * ((ref == 1) ? -1 : 1)); 2385 } else { 2386 return defaultValue; 2387 } 2388 } 2389 2390 /** 2391 * Returns parsed {@code DateTime} value, or -1 if unavailable or invalid. 2392 * 2393 * @hide 2394 */ 2395 @UnsupportedAppUsage getDateTime()2396 public @CurrentTimeMillisLong long getDateTime() { 2397 return parseDateTime(getAttribute(TAG_DATETIME), 2398 getAttribute(TAG_SUBSEC_TIME), 2399 getAttribute(TAG_OFFSET_TIME)); 2400 } 2401 2402 /** 2403 * Returns parsed {@code DateTimeDigitized} value, or -1 if unavailable or 2404 * invalid. 2405 * 2406 * @hide 2407 */ getDateTimeDigitized()2408 public @CurrentTimeMillisLong long getDateTimeDigitized() { 2409 return parseDateTime(getAttribute(TAG_DATETIME_DIGITIZED), 2410 getAttribute(TAG_SUBSEC_TIME_DIGITIZED), 2411 getAttribute(TAG_OFFSET_TIME_DIGITIZED)); 2412 } 2413 2414 /** 2415 * Returns parsed {@code DateTimeOriginal} value, or -1 if unavailable or 2416 * invalid. 2417 * 2418 * @hide 2419 */ 2420 @UnsupportedAppUsage getDateTimeOriginal()2421 public @CurrentTimeMillisLong long getDateTimeOriginal() { 2422 return parseDateTime(getAttribute(TAG_DATETIME_ORIGINAL), 2423 getAttribute(TAG_SUBSEC_TIME_ORIGINAL), 2424 getAttribute(TAG_OFFSET_TIME_ORIGINAL)); 2425 } 2426 parseDateTime(@ullable String dateTimeString, @Nullable String subSecs, @Nullable String offsetString)2427 private static @CurrentTimeMillisLong long parseDateTime(@Nullable String dateTimeString, 2428 @Nullable String subSecs, @Nullable String offsetString) { 2429 if (dateTimeString == null 2430 || !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1; 2431 2432 ParsePosition pos = new ParsePosition(0); 2433 try { 2434 // The exif field is in local time. Parsing it as if it is UTC will yield time 2435 // since 1/1/1970 local time 2436 Date datetime; 2437 synchronized (sFormatter) { 2438 datetime = sFormatter.parse(dateTimeString, pos); 2439 } 2440 2441 if (offsetString != null) { 2442 dateTimeString = dateTimeString + " " + offsetString; 2443 ParsePosition position = new ParsePosition(0); 2444 synchronized (sFormatterTz) { 2445 datetime = sFormatterTz.parse(dateTimeString, position); 2446 } 2447 } 2448 2449 if (datetime == null) return -1; 2450 long msecs = datetime.getTime(); 2451 2452 if (subSecs != null) { 2453 try { 2454 long sub = Long.parseLong(subSecs); 2455 while (sub > 1000) { 2456 sub /= 10; 2457 } 2458 msecs += sub; 2459 } catch (NumberFormatException e) { 2460 // Ignored 2461 } 2462 } 2463 return msecs; 2464 } catch (IllegalArgumentException e) { 2465 return -1; 2466 } 2467 } 2468 2469 /** 2470 * Returns number of milliseconds since Jan. 1, 1970, midnight UTC. 2471 * Returns -1 if the date time information if not available. 2472 * @hide 2473 */ 2474 @UnsupportedAppUsage getGpsDateTime()2475 public long getGpsDateTime() { 2476 String date = getAttribute(TAG_GPS_DATESTAMP); 2477 String time = getAttribute(TAG_GPS_TIMESTAMP); 2478 if (date == null || time == null 2479 || (!sNonZeroTimePattern.matcher(date).matches() 2480 && !sNonZeroTimePattern.matcher(time).matches())) { 2481 return -1; 2482 } 2483 2484 String dateTimeString = date + ' ' + time; 2485 2486 ParsePosition pos = new ParsePosition(0); 2487 try { 2488 final Date datetime; 2489 synchronized (sFormatter) { 2490 datetime = sFormatter.parse(dateTimeString, pos); 2491 } 2492 if (datetime == null) return -1; 2493 return datetime.getTime(); 2494 } catch (IllegalArgumentException e) { 2495 return -1; 2496 } 2497 } 2498 2499 /** {@hide} */ 2500 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) convertRationalLatLonToFloat(String rationalString, String ref)2501 public static float convertRationalLatLonToFloat(String rationalString, String ref) { 2502 try { 2503 String [] parts = rationalString.split(","); 2504 2505 String [] pair; 2506 pair = parts[0].split("/"); 2507 double degrees = Double.parseDouble(pair[0].trim()) 2508 / Double.parseDouble(pair[1].trim()); 2509 2510 pair = parts[1].split("/"); 2511 double minutes = Double.parseDouble(pair[0].trim()) 2512 / Double.parseDouble(pair[1].trim()); 2513 2514 pair = parts[2].split("/"); 2515 double seconds = Double.parseDouble(pair[0].trim()) 2516 / Double.parseDouble(pair[1].trim()); 2517 2518 double result = degrees + (minutes / 60.0) + (seconds / 3600.0); 2519 if ((ref.equals("S") || ref.equals("W"))) { 2520 return (float) -result; 2521 } 2522 return (float) result; 2523 } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) { 2524 // Not valid 2525 throw new IllegalArgumentException(); 2526 } 2527 } 2528 initForFilename(String filename)2529 private void initForFilename(String filename) throws IOException { 2530 FileInputStream in = null; 2531 mAssetInputStream = null; 2532 mFilename = filename; 2533 mIsInputStream = false; 2534 try { 2535 in = new FileInputStream(filename); 2536 if (isSeekableFD(in.getFD())) { 2537 mSeekableFileDescriptor = in.getFD(); 2538 } else { 2539 mSeekableFileDescriptor = null; 2540 } 2541 loadAttributes(in); 2542 } finally { 2543 IoUtils.closeQuietly(in); 2544 } 2545 } 2546 2547 // Checks the type of image file getMimeType(BufferedInputStream in)2548 private int getMimeType(BufferedInputStream in) throws IOException { 2549 // TODO (b/142218289): Need to handle case where input stream does not support mark 2550 in.mark(SIGNATURE_CHECK_SIZE); 2551 byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE]; 2552 in.read(signatureCheckBytes); 2553 in.reset(); 2554 if (isJpegFormat(signatureCheckBytes)) { 2555 return IMAGE_TYPE_JPEG; 2556 } else if (isRafFormat(signatureCheckBytes)) { 2557 return IMAGE_TYPE_RAF; 2558 } else if (isHeifFormat(signatureCheckBytes)) { 2559 return IMAGE_TYPE_HEIF; 2560 } else if (isOrfFormat(signatureCheckBytes)) { 2561 return IMAGE_TYPE_ORF; 2562 } else if (isRw2Format(signatureCheckBytes)) { 2563 return IMAGE_TYPE_RW2; 2564 } else if (isPngFormat(signatureCheckBytes)) { 2565 return IMAGE_TYPE_PNG; 2566 } else if (isWebpFormat(signatureCheckBytes)) { 2567 return IMAGE_TYPE_WEBP; 2568 } 2569 // Certain file formats (PEF) are identified in readImageFileDirectory() 2570 return IMAGE_TYPE_UNKNOWN; 2571 } 2572 2573 /** 2574 * This method looks at the first 3 bytes to determine if this file is a JPEG file. 2575 * See http://www.media.mit.edu/pia/Research/deepview/exif.html, "JPEG format and Marker" 2576 */ isJpegFormat(byte[] signatureCheckBytes)2577 private static boolean isJpegFormat(byte[] signatureCheckBytes) throws IOException { 2578 for (int i = 0; i < JPEG_SIGNATURE.length; i++) { 2579 if (signatureCheckBytes[i] != JPEG_SIGNATURE[i]) { 2580 return false; 2581 } 2582 } 2583 return true; 2584 } 2585 2586 /** 2587 * This method looks at the first 15 bytes to determine if this file is a RAF file. 2588 * There is no official specification for RAF files from Fuji, but there is an online archive of 2589 * image file specifications: 2590 * http://fileformats.archiveteam.org/wiki/Fujifilm_RAF 2591 */ isRafFormat(byte[] signatureCheckBytes)2592 private boolean isRafFormat(byte[] signatureCheckBytes) throws IOException { 2593 byte[] rafSignatureBytes = RAF_SIGNATURE.getBytes(); 2594 for (int i = 0; i < rafSignatureBytes.length; i++) { 2595 if (signatureCheckBytes[i] != rafSignatureBytes[i]) { 2596 return false; 2597 } 2598 } 2599 return true; 2600 } 2601 isHeifFormat(byte[] signatureCheckBytes)2602 private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException { 2603 ByteOrderedDataInputStream signatureInputStream = null; 2604 try { 2605 signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); 2606 signatureInputStream.setByteOrder(ByteOrder.BIG_ENDIAN); 2607 2608 long chunkSize = signatureInputStream.readInt(); 2609 byte[] chunkType = new byte[4]; 2610 signatureInputStream.read(chunkType); 2611 2612 if (!Arrays.equals(chunkType, HEIF_TYPE_FTYP)) { 2613 return false; 2614 } 2615 2616 long chunkDataOffset = 8; 2617 if (chunkSize == 1) { 2618 // This indicates that the next 8 bytes represent the chunk size, 2619 // and chunk data comes after that. 2620 chunkSize = signatureInputStream.readLong(); 2621 if (chunkSize < 16) { 2622 // The smallest valid chunk is 16 bytes long in this case. 2623 return false; 2624 } 2625 chunkDataOffset += 8; 2626 } 2627 2628 // only sniff up to signatureCheckBytes.length 2629 if (chunkSize > signatureCheckBytes.length) { 2630 chunkSize = signatureCheckBytes.length; 2631 } 2632 2633 long chunkDataSize = chunkSize - chunkDataOffset; 2634 2635 // It should at least have major brand (4-byte) and minor version (4-byte). 2636 // The rest of the chunk (if any) is a list of (4-byte) compatible brands. 2637 if (chunkDataSize < 8) { 2638 return false; 2639 } 2640 2641 byte[] brand = new byte[4]; 2642 boolean isMif1 = false; 2643 boolean isHeic = false; 2644 for (long i = 0; i < chunkDataSize / 4; ++i) { 2645 if (signatureInputStream.read(brand) != brand.length) { 2646 return false; 2647 } 2648 if (i == 1) { 2649 // Skip this index, it refers to the minorVersion, not a brand. 2650 continue; 2651 } 2652 if (Arrays.equals(brand, HEIF_BRAND_MIF1)) { 2653 isMif1 = true; 2654 } else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) { 2655 isHeic = true; 2656 } 2657 if (isMif1 && isHeic) { 2658 return true; 2659 } 2660 } 2661 } catch (Exception e) { 2662 if (DEBUG) { 2663 Log.d(TAG, "Exception parsing HEIF file type box.", e); 2664 } 2665 } finally { 2666 if (signatureInputStream != null) { 2667 signatureInputStream.close(); 2668 signatureInputStream = null; 2669 } 2670 } 2671 return false; 2672 } 2673 2674 /** 2675 * ORF has a similar structure to TIFF but it contains a different signature at the TIFF Header. 2676 * This method looks at the 2 bytes following the Byte Order bytes to determine if this file is 2677 * an ORF file. 2678 * There is no official specification for ORF files from Olympus, but there is an online archive 2679 * of image file specifications: 2680 * http://fileformats.archiveteam.org/wiki/Olympus_ORF 2681 */ isOrfFormat(byte[] signatureCheckBytes)2682 private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException { 2683 ByteOrderedDataInputStream signatureInputStream = null; 2684 2685 try { 2686 signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); 2687 2688 // Read byte order 2689 mExifByteOrder = readByteOrder(signatureInputStream); 2690 // Set byte order 2691 signatureInputStream.setByteOrder(mExifByteOrder); 2692 2693 short orfSignature = signatureInputStream.readShort(); 2694 return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2; 2695 } catch (Exception e) { 2696 // Do nothing 2697 } finally { 2698 if (signatureInputStream != null) { 2699 signatureInputStream.close(); 2700 } 2701 } 2702 return false; 2703 } 2704 2705 /** 2706 * RW2 is TIFF-based, but stores 0x55 signature byte instead of 0x42 at the header 2707 * See http://lclevy.free.fr/raw/ 2708 */ isRw2Format(byte[] signatureCheckBytes)2709 private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException { 2710 ByteOrderedDataInputStream signatureInputStream = null; 2711 2712 try { 2713 signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); 2714 2715 // Read byte order 2716 mExifByteOrder = readByteOrder(signatureInputStream); 2717 // Set byte order 2718 signatureInputStream.setByteOrder(mExifByteOrder); 2719 2720 short signatureByte = signatureInputStream.readShort(); 2721 signatureInputStream.close(); 2722 return signatureByte == RW2_SIGNATURE; 2723 } catch (Exception e) { 2724 // Do nothing 2725 } finally { 2726 if (signatureInputStream != null) { 2727 signatureInputStream.close(); 2728 } 2729 } 2730 return false; 2731 } 2732 2733 /** 2734 * PNG's file signature is first 8 bytes. 2735 * See PNG (Portable Network Graphics) Specification, Version 1.2, 3.1. PNG file signature 2736 */ isPngFormat(byte[] signatureCheckBytes)2737 private boolean isPngFormat(byte[] signatureCheckBytes) throws IOException { 2738 for (int i = 0; i < PNG_SIGNATURE.length; i++) { 2739 if (signatureCheckBytes[i] != PNG_SIGNATURE[i]) { 2740 return false; 2741 } 2742 } 2743 return true; 2744 } 2745 2746 /** 2747 * WebP's file signature is composed of 12 bytes: 2748 * 'RIFF' (4 bytes) + file length value (4 bytes) + 'WEBP' (4 bytes) 2749 * See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header" 2750 */ isWebpFormat(byte[] signatureCheckBytes)2751 private boolean isWebpFormat(byte[] signatureCheckBytes) throws IOException { 2752 for (int i = 0; i < WEBP_SIGNATURE_1.length; i++) { 2753 if (signatureCheckBytes[i] != WEBP_SIGNATURE_1[i]) { 2754 return false; 2755 } 2756 } 2757 for (int i = 0; i < WEBP_SIGNATURE_2.length; i++) { 2758 if (signatureCheckBytes[i + WEBP_SIGNATURE_1.length + WEBP_FILE_SIZE_BYTE_LENGTH] 2759 != WEBP_SIGNATURE_2[i]) { 2760 return false; 2761 } 2762 } 2763 return true; 2764 } 2765 isExifDataOnly(BufferedInputStream in)2766 private static boolean isExifDataOnly(BufferedInputStream in) throws IOException { 2767 in.mark(IDENTIFIER_EXIF_APP1.length); 2768 byte[] signatureCheckBytes = new byte[IDENTIFIER_EXIF_APP1.length]; 2769 in.read(signatureCheckBytes); 2770 in.reset(); 2771 for (int i = 0; i < IDENTIFIER_EXIF_APP1.length; i++) { 2772 if (signatureCheckBytes[i] != IDENTIFIER_EXIF_APP1[i]) { 2773 return false; 2774 } 2775 } 2776 return true; 2777 } 2778 2779 /** 2780 * Loads EXIF attributes from a JPEG input stream. 2781 * 2782 * @param in The input stream that starts with the JPEG data. 2783 * @param jpegOffset The offset value in input stream for JPEG data. 2784 * @param imageType The image type from which to retrieve metadata. Use IFD_TYPE_PRIMARY for 2785 * primary image, IFD_TYPE_PREVIEW for preview image, and 2786 * IFD_TYPE_THUMBNAIL for thumbnail image. 2787 * @throws IOException If the data contains invalid JPEG markers, offsets, or length values. 2788 */ getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType)2789 private void getJpegAttributes(ByteOrderedDataInputStream in, int jpegOffset, int imageType) 2790 throws IOException { 2791 // See JPEG File Interchange Format Specification, "JFIF Specification" 2792 if (DEBUG) { 2793 Log.d(TAG, "getJpegAttributes starting with: " + in); 2794 } 2795 2796 // JPEG uses Big Endian by default. See https://people.cs.umass.edu/~verts/cs32/endian.html 2797 in.setByteOrder(ByteOrder.BIG_ENDIAN); 2798 2799 // Skip to JPEG data 2800 in.seek(jpegOffset); 2801 int bytesRead = jpegOffset; 2802 2803 byte marker; 2804 if ((marker = in.readByte()) != MARKER) { 2805 throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff)); 2806 } 2807 ++bytesRead; 2808 if (in.readByte() != MARKER_SOI) { 2809 throw new IOException("Invalid marker: " + Integer.toHexString(marker & 0xff)); 2810 } 2811 ++bytesRead; 2812 while (true) { 2813 marker = in.readByte(); 2814 if (marker != MARKER) { 2815 throw new IOException("Invalid marker:" + Integer.toHexString(marker & 0xff)); 2816 } 2817 ++bytesRead; 2818 marker = in.readByte(); 2819 if (DEBUG) { 2820 Log.d(TAG, "Found JPEG segment indicator: " + Integer.toHexString(marker & 0xff)); 2821 } 2822 ++bytesRead; 2823 2824 // EOI indicates the end of an image and in case of SOS, JPEG image stream starts and 2825 // the image data will terminate right after. 2826 if (marker == MARKER_EOI || marker == MARKER_SOS) { 2827 break; 2828 } 2829 int length = in.readUnsignedShort() - 2; 2830 bytesRead += 2; 2831 if (DEBUG) { 2832 Log.d(TAG, "JPEG segment: " + Integer.toHexString(marker & 0xff) + " (length: " 2833 + (length + 2) + ")"); 2834 } 2835 if (length < 0) { 2836 throw new IOException("Invalid length"); 2837 } 2838 switch (marker) { 2839 case MARKER_APP1: { 2840 final int start = bytesRead; 2841 final byte[] bytes = new byte[length]; 2842 in.readFully(bytes); 2843 bytesRead += length; 2844 length = 0; 2845 2846 if (ArrayUtils.startsWith(bytes, IDENTIFIER_EXIF_APP1)) { 2847 final long offset = start + IDENTIFIER_EXIF_APP1.length; 2848 final byte[] value = Arrays.copyOfRange(bytes, 2849 IDENTIFIER_EXIF_APP1.length, bytes.length); 2850 // Save offset values for handleThumbnailFromJfif() function 2851 mExifOffset = (int) offset; 2852 readExifSegment(value, imageType); 2853 } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) { 2854 // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6 2855 final long offset = start + IDENTIFIER_XMP_APP1.length; 2856 final byte[] value = Arrays.copyOfRange(bytes, 2857 IDENTIFIER_XMP_APP1.length, bytes.length); 2858 // TODO: check if ignoring separate XMP data when tag 700 already exists is 2859 // valid. 2860 if (getAttribute(TAG_XMP) == null) { 2861 mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( 2862 IFD_FORMAT_BYTE, value.length, offset, value)); 2863 mXmpIsFromSeparateMarker = true; 2864 } 2865 } 2866 break; 2867 } 2868 2869 case MARKER_COM: { 2870 byte[] bytes = new byte[length]; 2871 if (in.read(bytes) != length) { 2872 throw new IOException("Invalid exif"); 2873 } 2874 length = 0; 2875 if (getAttribute(TAG_USER_COMMENT) == null) { 2876 mAttributes[IFD_TYPE_EXIF].put(TAG_USER_COMMENT, ExifAttribute.createString( 2877 new String(bytes, ASCII))); 2878 } 2879 break; 2880 } 2881 2882 case MARKER_SOF0: 2883 case MARKER_SOF1: 2884 case MARKER_SOF2: 2885 case MARKER_SOF3: 2886 case MARKER_SOF5: 2887 case MARKER_SOF6: 2888 case MARKER_SOF7: 2889 case MARKER_SOF9: 2890 case MARKER_SOF10: 2891 case MARKER_SOF11: 2892 case MARKER_SOF13: 2893 case MARKER_SOF14: 2894 case MARKER_SOF15: { 2895 if (in.skipBytes(1) != 1) { 2896 throw new IOException("Invalid SOFx"); 2897 } 2898 mAttributes[imageType].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong( 2899 in.readUnsignedShort(), mExifByteOrder)); 2900 mAttributes[imageType].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong( 2901 in.readUnsignedShort(), mExifByteOrder)); 2902 length -= 5; 2903 break; 2904 } 2905 2906 default: { 2907 break; 2908 } 2909 } 2910 if (length < 0) { 2911 throw new IOException("Invalid length"); 2912 } 2913 if (in.skipBytes(length) != length) { 2914 throw new IOException("Invalid JPEG segment"); 2915 } 2916 bytesRead += length; 2917 } 2918 // Restore original byte order 2919 in.setByteOrder(mExifByteOrder); 2920 } 2921 getRawAttributes(ByteOrderedDataInputStream in)2922 private void getRawAttributes(ByteOrderedDataInputStream in) throws IOException { 2923 // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1. 2924 parseTiffHeaders(in, in.available()); 2925 2926 // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6. 2927 readImageFileDirectory(in, IFD_TYPE_PRIMARY); 2928 2929 // Update ImageLength/Width tags for all image data. 2930 updateImageSizeValues(in, IFD_TYPE_PRIMARY); 2931 updateImageSizeValues(in, IFD_TYPE_PREVIEW); 2932 updateImageSizeValues(in, IFD_TYPE_THUMBNAIL); 2933 2934 // Check if each image data is in valid position. 2935 validateImages(); 2936 2937 if (mMimeType == IMAGE_TYPE_PEF) { 2938 // PEF files contain a MakerNote data, which contains the data for ColorSpace tag. 2939 // See http://lclevy.free.fr/raw/ and piex.cc PefGetPreviewData() 2940 ExifAttribute makerNoteAttribute = 2941 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE); 2942 if (makerNoteAttribute != null) { 2943 // Create an ordered DataInputStream for MakerNote 2944 ByteOrderedDataInputStream makerNoteDataInputStream = 2945 new ByteOrderedDataInputStream(makerNoteAttribute.bytes); 2946 makerNoteDataInputStream.setByteOrder(mExifByteOrder); 2947 2948 // Seek to MakerNote data 2949 makerNoteDataInputStream.seek(PEF_MAKER_NOTE_SKIP_SIZE); 2950 2951 // Read IFD data from MakerNote 2952 readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_PEF); 2953 2954 // Update ColorSpace tag 2955 ExifAttribute colorSpaceAttribute = 2956 (ExifAttribute) mAttributes[IFD_TYPE_PEF].get(TAG_COLOR_SPACE); 2957 if (colorSpaceAttribute != null) { 2958 mAttributes[IFD_TYPE_EXIF].put(TAG_COLOR_SPACE, colorSpaceAttribute); 2959 } 2960 } 2961 } 2962 } 2963 2964 /** 2965 * RAF files contains a JPEG and a CFA data. 2966 * The JPEG contains two images, a preview and a thumbnail, while the CFA contains a RAW image. 2967 * This method looks at the first 160 bytes of a RAF file to retrieve the offset and length 2968 * values for the JPEG and CFA data. 2969 * Using that data, it parses the JPEG data to retrieve the preview and thumbnail image data, 2970 * then parses the CFA metadata to retrieve the primary image length/width values. 2971 * For data format details, see http://fileformats.archiveteam.org/wiki/Fujifilm_RAF 2972 */ getRafAttributes(ByteOrderedDataInputStream in)2973 private void getRafAttributes(ByteOrderedDataInputStream in) throws IOException { 2974 // Retrieve offset & length values 2975 in.skipBytes(RAF_OFFSET_TO_JPEG_IMAGE_OFFSET); 2976 byte[] jpegOffsetBytes = new byte[4]; 2977 byte[] cfaHeaderOffsetBytes = new byte[4]; 2978 in.read(jpegOffsetBytes); 2979 // Skip JPEG length value since it is not needed 2980 in.skipBytes(RAF_JPEG_LENGTH_VALUE_SIZE); 2981 in.read(cfaHeaderOffsetBytes); 2982 int rafJpegOffset = ByteBuffer.wrap(jpegOffsetBytes).getInt(); 2983 int rafCfaHeaderOffset = ByteBuffer.wrap(cfaHeaderOffsetBytes).getInt(); 2984 2985 // Retrieve JPEG image metadata 2986 getJpegAttributes(in, rafJpegOffset, IFD_TYPE_PREVIEW); 2987 2988 // Skip to CFA header offset. 2989 in.seek(rafCfaHeaderOffset); 2990 2991 // Retrieve primary image length/width values, if TAG_RAF_IMAGE_SIZE exists 2992 in.setByteOrder(ByteOrder.BIG_ENDIAN); 2993 int numberOfDirectoryEntry = in.readInt(); 2994 if (DEBUG) { 2995 Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry); 2996 } 2997 // CFA stores some metadata about the RAW image. Since CFA uses proprietary tags, can only 2998 // find and retrieve image size information tags, while skipping others. 2999 // See piex.cc RafGetDimension() 3000 for (int i = 0; i < numberOfDirectoryEntry; ++i) { 3001 int tagNumber = in.readUnsignedShort(); 3002 int numberOfBytes = in.readUnsignedShort(); 3003 if (tagNumber == TAG_RAF_IMAGE_SIZE.number) { 3004 int imageLength = in.readShort(); 3005 int imageWidth = in.readShort(); 3006 ExifAttribute imageLengthAttribute = 3007 ExifAttribute.createUShort(imageLength, mExifByteOrder); 3008 ExifAttribute imageWidthAttribute = 3009 ExifAttribute.createUShort(imageWidth, mExifByteOrder); 3010 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, imageLengthAttribute); 3011 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, imageWidthAttribute); 3012 if (DEBUG) { 3013 Log.d(TAG, "Updated to length: " + imageLength + ", width: " + imageWidth); 3014 } 3015 return; 3016 } 3017 in.skipBytes(numberOfBytes); 3018 } 3019 } 3020 getHeifAttributes(ByteOrderedDataInputStream in)3021 private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException { 3022 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 3023 try { 3024 retriever.setDataSource(new MediaDataSource() { 3025 long mPosition; 3026 3027 @Override 3028 public void close() throws IOException {} 3029 3030 @Override 3031 public int readAt(long position, byte[] buffer, int offset, int size) 3032 throws IOException { 3033 if (size == 0) { 3034 return 0; 3035 } 3036 if (position < 0) { 3037 return -1; 3038 } 3039 try { 3040 if (mPosition != position) { 3041 // We don't allow seek to positions after the available bytes, 3042 // the input stream won't be able to seek back then. 3043 // However, if we hit an exception before (mPosition set to -1), 3044 // let it try the seek in hope it might recover. 3045 if (mPosition >= 0 && position >= mPosition + in.available()) { 3046 return -1; 3047 } 3048 in.seek(position); 3049 mPosition = position; 3050 } 3051 3052 // If the read will cause us to go over the available bytes, 3053 // reduce the size so that we stay in the available range. 3054 // Otherwise the input stream may not be able to seek back. 3055 if (size > in.available()) { 3056 size = in.available(); 3057 } 3058 3059 int bytesRead = in.read(buffer, offset, size); 3060 if (bytesRead >= 0) { 3061 mPosition += bytesRead; 3062 return bytesRead; 3063 } 3064 } catch (IOException e) {} 3065 mPosition = -1; // need to seek on next read 3066 return -1; 3067 } 3068 3069 @Override 3070 public long getSize() throws IOException { 3071 return -1; 3072 } 3073 }); 3074 3075 String exifOffsetStr = retriever.extractMetadata( 3076 MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET); 3077 String exifLengthStr = retriever.extractMetadata( 3078 MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH); 3079 String hasImage = retriever.extractMetadata( 3080 MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE); 3081 String hasVideo = retriever.extractMetadata( 3082 MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); 3083 3084 String width = null; 3085 String height = null; 3086 String rotation = null; 3087 final String METADATA_VALUE_YES = "yes"; 3088 // If the file has both image and video, prefer image info over video info. 3089 // App querying ExifInterface is most likely using the bitmap path which 3090 // picks the image first. 3091 if (METADATA_VALUE_YES.equals(hasImage)) { 3092 width = retriever.extractMetadata( 3093 MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH); 3094 height = retriever.extractMetadata( 3095 MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT); 3096 rotation = retriever.extractMetadata( 3097 MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION); 3098 } else if (METADATA_VALUE_YES.equals(hasVideo)) { 3099 width = retriever.extractMetadata( 3100 MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); 3101 height = retriever.extractMetadata( 3102 MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); 3103 rotation = retriever.extractMetadata( 3104 MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); 3105 } 3106 3107 if (width != null) { 3108 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, 3109 ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder)); 3110 } 3111 3112 if (height != null) { 3113 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, 3114 ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder)); 3115 } 3116 3117 if (rotation != null) { 3118 int orientation = ExifInterface.ORIENTATION_NORMAL; 3119 3120 // all rotation angles in CW 3121 switch (Integer.parseInt(rotation)) { 3122 case 90: 3123 orientation = ExifInterface.ORIENTATION_ROTATE_90; 3124 break; 3125 case 180: 3126 orientation = ExifInterface.ORIENTATION_ROTATE_180; 3127 break; 3128 case 270: 3129 orientation = ExifInterface.ORIENTATION_ROTATE_270; 3130 break; 3131 } 3132 3133 mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION, 3134 ExifAttribute.createUShort(orientation, mExifByteOrder)); 3135 } 3136 3137 if (exifOffsetStr != null && exifLengthStr != null) { 3138 int offset = Integer.parseInt(exifOffsetStr); 3139 int length = Integer.parseInt(exifLengthStr); 3140 if (length <= 6) { 3141 throw new IOException("Invalid exif length"); 3142 } 3143 in.seek(offset); 3144 byte[] identifier = new byte[6]; 3145 if (in.read(identifier) != 6) { 3146 throw new IOException("Can't read identifier"); 3147 } 3148 offset += 6; 3149 length -= 6; 3150 if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) { 3151 throw new IOException("Invalid identifier"); 3152 } 3153 3154 // TODO: Need to handle potential OutOfMemoryError 3155 byte[] bytes = new byte[length]; 3156 if (in.read(bytes) != length) { 3157 throw new IOException("Can't read exif"); 3158 } 3159 // Save offset values for handling thumbnail and attribute offsets. 3160 mExifOffset = offset; 3161 readExifSegment(bytes, IFD_TYPE_PRIMARY); 3162 } 3163 3164 if (DEBUG) { 3165 Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation); 3166 } 3167 } finally { 3168 retriever.release(); 3169 } 3170 } 3171 getStandaloneAttributes(ByteOrderedDataInputStream in)3172 private void getStandaloneAttributes(ByteOrderedDataInputStream in) throws IOException { 3173 in.skipBytes(IDENTIFIER_EXIF_APP1.length); 3174 // TODO: Need to handle potential OutOfMemoryError 3175 byte[] data = new byte[in.available()]; 3176 in.readFully(data); 3177 // Save offset values for handling thumbnail and attribute offsets. 3178 mExifOffset = IDENTIFIER_EXIF_APP1.length; 3179 readExifSegment(data, IFD_TYPE_PRIMARY); 3180 } 3181 3182 /** 3183 * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail 3184 * images. Both data takes the form of IFDs and can therefore be read with the 3185 * readImageFileDirectory() method. 3186 * This method reads all the necessary data and updates the primary/preview/thumbnail image 3187 * information according to the GetOlympusPreviewImage() method in piex.cc. 3188 * For data format details, see the following: 3189 * http://fileformats.archiveteam.org/wiki/Olympus_ORF 3190 * https://libopenraw.freedesktop.org/wiki/Olympus_ORF 3191 */ getOrfAttributes(ByteOrderedDataInputStream in)3192 private void getOrfAttributes(ByteOrderedDataInputStream in) throws IOException { 3193 // Retrieve primary image data 3194 // Other Exif data will be located in the Makernote. 3195 getRawAttributes(in); 3196 3197 // Additionally retrieve preview/thumbnail information from MakerNote tag, which contains 3198 // proprietary tags and therefore does not have offical documentation 3199 // See GetOlympusPreviewImage() in piex.cc & http://www.exiv2.org/tags-olympus.html 3200 ExifAttribute makerNoteAttribute = 3201 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_MAKER_NOTE); 3202 if (makerNoteAttribute != null) { 3203 // Create an ordered DataInputStream for MakerNote 3204 ByteOrderedDataInputStream makerNoteDataInputStream = 3205 new ByteOrderedDataInputStream(makerNoteAttribute.bytes); 3206 makerNoteDataInputStream.setByteOrder(mExifByteOrder); 3207 3208 // There are two types of headers for Olympus MakerNotes 3209 // See http://www.exiv2.org/makernote.html#R1 3210 byte[] makerNoteHeader1Bytes = new byte[ORF_MAKER_NOTE_HEADER_1.length]; 3211 makerNoteDataInputStream.readFully(makerNoteHeader1Bytes); 3212 makerNoteDataInputStream.seek(0); 3213 byte[] makerNoteHeader2Bytes = new byte[ORF_MAKER_NOTE_HEADER_2.length]; 3214 makerNoteDataInputStream.readFully(makerNoteHeader2Bytes); 3215 // Skip the corresponding amount of bytes for each header type 3216 if (Arrays.equals(makerNoteHeader1Bytes, ORF_MAKER_NOTE_HEADER_1)) { 3217 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_1_SIZE); 3218 } else if (Arrays.equals(makerNoteHeader2Bytes, ORF_MAKER_NOTE_HEADER_2)) { 3219 makerNoteDataInputStream.seek(ORF_MAKER_NOTE_HEADER_2_SIZE); 3220 } 3221 3222 // Read IFD data from MakerNote 3223 readImageFileDirectory(makerNoteDataInputStream, IFD_TYPE_ORF_MAKER_NOTE); 3224 3225 // Retrieve & update preview image offset & length values 3226 ExifAttribute imageLengthAttribute = (ExifAttribute) 3227 mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_START); 3228 ExifAttribute bitsPerSampleAttribute = (ExifAttribute) 3229 mAttributes[IFD_TYPE_ORF_CAMERA_SETTINGS].get(TAG_ORF_PREVIEW_IMAGE_LENGTH); 3230 3231 if (imageLengthAttribute != null && bitsPerSampleAttribute != null) { 3232 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT, 3233 imageLengthAttribute); 3234 mAttributes[IFD_TYPE_PREVIEW].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 3235 bitsPerSampleAttribute); 3236 } 3237 3238 // TODO: Check this behavior in other ORF files 3239 // Retrieve primary image length & width values 3240 // See piex.cc GetOlympusPreviewImage() 3241 ExifAttribute aspectFrameAttribute = (ExifAttribute) 3242 mAttributes[IFD_TYPE_ORF_IMAGE_PROCESSING].get(TAG_ORF_ASPECT_FRAME); 3243 if (aspectFrameAttribute != null) { 3244 int[] aspectFrameValues = new int[4]; 3245 aspectFrameValues = (int[]) aspectFrameAttribute.getValue(mExifByteOrder); 3246 if (aspectFrameValues[2] > aspectFrameValues[0] && 3247 aspectFrameValues[3] > aspectFrameValues[1]) { 3248 int primaryImageWidth = aspectFrameValues[2] - aspectFrameValues[0] + 1; 3249 int primaryImageLength = aspectFrameValues[3] - aspectFrameValues[1] + 1; 3250 // Swap width & length values 3251 if (primaryImageWidth < primaryImageLength) { 3252 primaryImageWidth += primaryImageLength; 3253 primaryImageLength = primaryImageWidth - primaryImageLength; 3254 primaryImageWidth -= primaryImageLength; 3255 } 3256 ExifAttribute primaryImageWidthAttribute = 3257 ExifAttribute.createUShort(primaryImageWidth, mExifByteOrder); 3258 ExifAttribute primaryImageLengthAttribute = 3259 ExifAttribute.createUShort(primaryImageLength, mExifByteOrder); 3260 3261 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, primaryImageWidthAttribute); 3262 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, primaryImageLengthAttribute); 3263 } 3264 } 3265 } 3266 } 3267 3268 // RW2 contains the primary image data in IFD0 and the preview and/or thumbnail image data in 3269 // the JpgFromRaw tag 3270 // See https://libopenraw.freedesktop.org/wiki/Panasonic_RAW/ and piex.cc Rw2GetPreviewData() getRw2Attributes(ByteOrderedDataInputStream in)3271 private void getRw2Attributes(ByteOrderedDataInputStream in) throws IOException { 3272 // Retrieve primary image data 3273 getRawAttributes(in); 3274 3275 // Retrieve preview and/or thumbnail image data 3276 ExifAttribute jpgFromRawAttribute = 3277 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_JPG_FROM_RAW); 3278 if (jpgFromRawAttribute != null) { 3279 getJpegAttributes(in, mRw2JpgFromRawOffset, IFD_TYPE_PREVIEW); 3280 } 3281 3282 // Set ISO tag value if necessary 3283 ExifAttribute rw2IsoAttribute = 3284 (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].get(TAG_RW2_ISO); 3285 ExifAttribute exifIsoAttribute = 3286 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_ISO_SPEED_RATINGS); 3287 if (rw2IsoAttribute != null && exifIsoAttribute == null) { 3288 // Place this attribute only if it doesn't exist 3289 mAttributes[IFD_TYPE_EXIF].put(TAG_ISO_SPEED_RATINGS, rw2IsoAttribute); 3290 } 3291 } 3292 3293 // PNG contains the EXIF data as a Special-Purpose Chunk getPngAttributes(ByteOrderedDataInputStream in)3294 private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException { 3295 if (DEBUG) { 3296 Log.d(TAG, "getPngAttributes starting with: " + in); 3297 } 3298 3299 // PNG uses Big Endian by default. 3300 // See PNG (Portable Network Graphics) Specification, Version 1.2, 3301 // 2.1. Integers and byte order 3302 in.setByteOrder(ByteOrder.BIG_ENDIAN); 3303 3304 int bytesRead = 0; 3305 3306 // Skip the signature bytes 3307 in.skipBytes(PNG_SIGNATURE.length); 3308 bytesRead += PNG_SIGNATURE.length; 3309 3310 // Each chunk is made up of four parts: 3311 // 1) Length: 4-byte unsigned integer indicating the number of bytes in the 3312 // Chunk Data field. Excludes Chunk Type and CRC bytes. 3313 // 2) Chunk Type: 4-byte chunk type code. 3314 // 3) Chunk Data: The data bytes. Can be zero-length. 3315 // 4) CRC: 4-byte data calculated on the preceding bytes in the chunk. Always 3316 // present. 3317 // --> 4 (length bytes) + 4 (type bytes) + X (data bytes) + 4 (CRC bytes) 3318 // See PNG (Portable Network Graphics) Specification, Version 1.2, 3319 // 3.2. Chunk layout 3320 try { 3321 while (true) { 3322 int length = in.readInt(); 3323 bytesRead += 4; 3324 3325 byte[] type = new byte[PNG_CHUNK_TYPE_BYTE_LENGTH]; 3326 if (in.read(type) != type.length) { 3327 throw new IOException("Encountered invalid length while parsing PNG chunk" 3328 + "type"); 3329 } 3330 bytesRead += PNG_CHUNK_TYPE_BYTE_LENGTH; 3331 3332 // The first chunk must be the IHDR chunk 3333 if (bytesRead == 16 && !Arrays.equals(type, PNG_CHUNK_TYPE_IHDR)) { 3334 throw new IOException("Encountered invalid PNG file--IHDR chunk should appear" 3335 + "as the first chunk"); 3336 } 3337 3338 if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) { 3339 // IEND marks the end of the image. 3340 break; 3341 } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) { 3342 // TODO: Need to handle potential OutOfMemoryError 3343 byte[] data = new byte[length]; 3344 if (in.read(data) != length) { 3345 throw new IOException("Failed to read given length for given PNG chunk " 3346 + "type: " + byteArrayToHexString(type)); 3347 } 3348 3349 // Compare CRC values for potential data corruption. 3350 int dataCrcValue = in.readInt(); 3351 // Cyclic Redundancy Code used to check for corruption of the data 3352 CRC32 crc = new CRC32(); 3353 crc.update(type); 3354 crc.update(data); 3355 if ((int) crc.getValue() != dataCrcValue) { 3356 throw new IOException("Encountered invalid CRC value for PNG-EXIF chunk." 3357 + "\n recorded CRC value: " + dataCrcValue + ", calculated CRC " 3358 + "value: " + crc.getValue()); 3359 } 3360 // Save offset values for handleThumbnailFromJfif() function 3361 mExifOffset = bytesRead; 3362 readExifSegment(data, IFD_TYPE_PRIMARY); 3363 3364 validateImages(); 3365 break; 3366 } else { 3367 // Skip to next chunk 3368 in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH); 3369 bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH; 3370 } 3371 } 3372 } catch (EOFException e) { 3373 // Should not reach here. Will only reach here if the file is corrupted or 3374 // does not follow the PNG specifications 3375 throw new IOException("Encountered corrupt PNG file."); 3376 } 3377 } 3378 3379 // WebP contains EXIF data as a RIFF File Format Chunk 3380 // All references below can be found in the following link. 3381 // https://developers.google.com/speed/webp/docs/riff_container getWebpAttributes(ByteOrderedDataInputStream in)3382 private void getWebpAttributes(ByteOrderedDataInputStream in) throws IOException { 3383 if (DEBUG) { 3384 Log.d(TAG, "getWebpAttributes starting with: " + in); 3385 } 3386 // WebP uses little-endian by default. 3387 // See Section "Terminology & Basics" 3388 in.setByteOrder(ByteOrder.LITTLE_ENDIAN); 3389 in.skipBytes(WEBP_SIGNATURE_1.length); 3390 // File size corresponds to the size of the entire file from offset 8. 3391 // See Section "WebP File Header" 3392 int fileSize = in.readInt() + 8; 3393 int bytesRead = 8; 3394 bytesRead += in.skipBytes(WEBP_SIGNATURE_2.length); 3395 try { 3396 while (true) { 3397 // Each chunk is made up of three parts: 3398 // 1) Chunk FourCC: 4-byte concatenating four ASCII characters. 3399 // 2) Chunk Size: 4-byte unsigned integer indicating the size of the chunk. 3400 // Excludes Chunk FourCC and Chunk Size bytes. 3401 // 3) Chunk Payload: data payload. A single padding byte ('0') is added if 3402 // Chunk Size is odd. 3403 // See Section "RIFF File Format" 3404 byte[] code = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH]; 3405 if (in.read(code) != code.length) { 3406 throw new IOException("Encountered invalid length while parsing WebP chunk" 3407 + "type"); 3408 } 3409 bytesRead += 4; 3410 int chunkSize = in.readInt(); 3411 bytesRead += 4; 3412 if (Arrays.equals(WEBP_CHUNK_TYPE_EXIF, code)) { 3413 // TODO: Need to handle potential OutOfMemoryError 3414 byte[] payload = new byte[chunkSize]; 3415 if (in.read(payload) != chunkSize) { 3416 throw new IOException("Failed to read given length for given PNG chunk " 3417 + "type: " + byteArrayToHexString(code)); 3418 } 3419 // Save offset values for handling thumbnail and attribute offsets. 3420 mExifOffset = bytesRead; 3421 readExifSegment(payload, IFD_TYPE_PRIMARY); 3422 break; 3423 } else { 3424 // Add a single padding byte at end if chunk size is odd 3425 chunkSize = (chunkSize % 2 == 1) ? chunkSize + 1 : chunkSize; 3426 // Check if skipping to next chunk is necessary 3427 if (bytesRead + chunkSize == fileSize) { 3428 // Reached end of file 3429 break; 3430 } else if (bytesRead + chunkSize > fileSize) { 3431 throw new IOException("Encountered WebP file with invalid chunk size"); 3432 } 3433 // Skip to next chunk 3434 int skipped = in.skipBytes(chunkSize); 3435 if (skipped != chunkSize) { 3436 throw new IOException("Encountered WebP file with invalid chunk size"); 3437 } 3438 bytesRead += skipped; 3439 } 3440 } 3441 } catch (EOFException e) { 3442 // Should not reach here. Will only reach here if the file is corrupted or 3443 // does not follow the WebP specifications 3444 throw new IOException("Encountered corrupt WebP file."); 3445 } 3446 } 3447 3448 // Stores a new JPEG image with EXIF attributes into a given output stream. saveJpegAttributes(InputStream inputStream, OutputStream outputStream)3449 private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream) 3450 throws IOException { 3451 // See JPEG File Interchange Format Specification, "JFIF Specification" 3452 if (DEBUG) { 3453 Log.d(TAG, "saveJpegAttributes starting with (inputStream: " + inputStream 3454 + ", outputStream: " + outputStream + ")"); 3455 } 3456 DataInputStream dataInputStream = new DataInputStream(inputStream); 3457 ByteOrderedDataOutputStream dataOutputStream = 3458 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN); 3459 if (dataInputStream.readByte() != MARKER) { 3460 throw new IOException("Invalid marker"); 3461 } 3462 dataOutputStream.writeByte(MARKER); 3463 if (dataInputStream.readByte() != MARKER_SOI) { 3464 throw new IOException("Invalid marker"); 3465 } 3466 dataOutputStream.writeByte(MARKER_SOI); 3467 3468 // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not 3469 // IDENTIFIER_EXIF_APP1) 3470 // Will re-add it later after the rest of the file is written 3471 ExifAttribute xmpAttribute = null; 3472 if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) { 3473 xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP); 3474 } 3475 3476 // Write EXIF APP1 segment 3477 dataOutputStream.writeByte(MARKER); 3478 dataOutputStream.writeByte(MARKER_APP1); 3479 writeExifSegment(dataOutputStream); 3480 3481 // Re-add previously removed XMP data. 3482 if (xmpAttribute != null) { 3483 mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute); 3484 } 3485 3486 byte[] bytes = new byte[4096]; 3487 3488 while (true) { 3489 byte marker = dataInputStream.readByte(); 3490 if (marker != MARKER) { 3491 throw new IOException("Invalid marker"); 3492 } 3493 marker = dataInputStream.readByte(); 3494 switch (marker) { 3495 case MARKER_APP1: { 3496 int length = dataInputStream.readUnsignedShort() - 2; 3497 if (length < 0) { 3498 throw new IOException("Invalid length"); 3499 } 3500 byte[] identifier = new byte[6]; 3501 if (length >= 6) { 3502 if (dataInputStream.read(identifier) != 6) { 3503 throw new IOException("Invalid exif"); 3504 } 3505 if (Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) { 3506 // Skip the original EXIF APP1 segment. 3507 if (dataInputStream.skipBytes(length - 6) != length - 6) { 3508 throw new IOException("Invalid length"); 3509 } 3510 break; 3511 } 3512 } 3513 // Copy non-EXIF APP1 segment. 3514 dataOutputStream.writeByte(MARKER); 3515 dataOutputStream.writeByte(marker); 3516 dataOutputStream.writeUnsignedShort(length + 2); 3517 if (length >= 6) { 3518 length -= 6; 3519 dataOutputStream.write(identifier); 3520 } 3521 int read; 3522 while (length > 0 && (read = dataInputStream.read( 3523 bytes, 0, Math.min(length, bytes.length))) >= 0) { 3524 dataOutputStream.write(bytes, 0, read); 3525 length -= read; 3526 } 3527 break; 3528 } 3529 case MARKER_EOI: 3530 case MARKER_SOS: { 3531 dataOutputStream.writeByte(MARKER); 3532 dataOutputStream.writeByte(marker); 3533 // Copy all the remaining data 3534 Streams.copy(dataInputStream, dataOutputStream); 3535 return; 3536 } 3537 default: { 3538 // Copy JPEG segment 3539 dataOutputStream.writeByte(MARKER); 3540 dataOutputStream.writeByte(marker); 3541 int length = dataInputStream.readUnsignedShort(); 3542 dataOutputStream.writeUnsignedShort(length); 3543 length -= 2; 3544 if (length < 0) { 3545 throw new IOException("Invalid length"); 3546 } 3547 int read; 3548 while (length > 0 && (read = dataInputStream.read( 3549 bytes, 0, Math.min(length, bytes.length))) >= 0) { 3550 dataOutputStream.write(bytes, 0, read); 3551 length -= read; 3552 } 3553 break; 3554 } 3555 } 3556 } 3557 } 3558 savePngAttributes(InputStream inputStream, OutputStream outputStream)3559 private void savePngAttributes(InputStream inputStream, OutputStream outputStream) 3560 throws IOException { 3561 if (DEBUG) { 3562 Log.d(TAG, "savePngAttributes starting with (inputStream: " + inputStream 3563 + ", outputStream: " + outputStream + ")"); 3564 } 3565 DataInputStream dataInputStream = new DataInputStream(inputStream); 3566 ByteOrderedDataOutputStream dataOutputStream = 3567 new ByteOrderedDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN); 3568 // Copy PNG signature bytes 3569 copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length); 3570 // EXIF chunk can appear anywhere between the first (IHDR) and last (IEND) chunks, except 3571 // between IDAT chunks. 3572 // Adhering to these rules, 3573 // 1) if EXIF chunk did not exist in the original file, it will be stored right after the 3574 // first chunk, 3575 // 2) if EXIF chunk existed in the original file, it will be stored in the same location. 3576 if (mExifOffset == 0) { 3577 // Copy IHDR chunk bytes 3578 int ihdrChunkLength = dataInputStream.readInt(); 3579 dataOutputStream.writeInt(ihdrChunkLength); 3580 copy(dataInputStream, dataOutputStream, PNG_CHUNK_TYPE_BYTE_LENGTH 3581 + ihdrChunkLength + PNG_CHUNK_CRC_BYTE_LENGTH); 3582 } else { 3583 // Copy up until the point where EXIF chunk length information is stored. 3584 int copyLength = mExifOffset - PNG_SIGNATURE.length 3585 - 4 /* PNG EXIF chunk length bytes */ 3586 - PNG_CHUNK_TYPE_BYTE_LENGTH; 3587 copy(dataInputStream, dataOutputStream, copyLength); 3588 // Skip to the start of the chunk after the EXIF chunk 3589 int exifChunkLength = dataInputStream.readInt(); 3590 dataInputStream.skipBytes(PNG_CHUNK_TYPE_BYTE_LENGTH + exifChunkLength 3591 + PNG_CHUNK_CRC_BYTE_LENGTH); 3592 } 3593 // Write EXIF data 3594 try (ByteArrayOutputStream exifByteArrayOutputStream = new ByteArrayOutputStream()) { 3595 // A byte array is needed to calculate the CRC value of this chunk which requires 3596 // the chunk type bytes and the chunk data bytes. 3597 ByteOrderedDataOutputStream exifDataOutputStream = 3598 new ByteOrderedDataOutputStream(exifByteArrayOutputStream, 3599 ByteOrder.BIG_ENDIAN); 3600 // Store Exif data in separate byte array 3601 writeExifSegment(exifDataOutputStream); 3602 byte[] exifBytes = 3603 ((ByteArrayOutputStream) exifDataOutputStream.mOutputStream).toByteArray(); 3604 // Write EXIF chunk data 3605 dataOutputStream.write(exifBytes); 3606 // Write EXIF chunk CRC 3607 CRC32 crc = new CRC32(); 3608 crc.update(exifBytes, 4 /* skip length bytes */, exifBytes.length - 4); 3609 dataOutputStream.writeInt((int) crc.getValue()); 3610 } 3611 // Copy the rest of the file 3612 Streams.copy(dataInputStream, dataOutputStream); 3613 } 3614 3615 // Reads the given EXIF byte area and save its tag data into attributes. readExifSegment(byte[] exifBytes, int imageType)3616 private void readExifSegment(byte[] exifBytes, int imageType) throws IOException { 3617 ByteOrderedDataInputStream dataInputStream = 3618 new ByteOrderedDataInputStream(exifBytes); 3619 3620 // Parse TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1. 3621 parseTiffHeaders(dataInputStream, exifBytes.length); 3622 3623 // Read TIFF image file directories. See JEITA CP-3451C Section 4.5.2. Figure 6. 3624 readImageFileDirectory(dataInputStream, imageType); 3625 } 3626 addDefaultValuesForCompatibility()3627 private void addDefaultValuesForCompatibility() { 3628 // If DATETIME tag has no value, then set the value to DATETIME_ORIGINAL tag's. 3629 String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL); 3630 if (valueOfDateTimeOriginal != null && getAttribute(TAG_DATETIME) == null) { 3631 mAttributes[IFD_TYPE_PRIMARY].put(TAG_DATETIME, 3632 ExifAttribute.createString(valueOfDateTimeOriginal)); 3633 } 3634 3635 // Add the default value. 3636 if (getAttribute(TAG_IMAGE_WIDTH) == null) { 3637 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, 3638 ExifAttribute.createULong(0, mExifByteOrder)); 3639 } 3640 if (getAttribute(TAG_IMAGE_LENGTH) == null) { 3641 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, 3642 ExifAttribute.createULong(0, mExifByteOrder)); 3643 } 3644 if (getAttribute(TAG_ORIENTATION) == null) { 3645 mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION, 3646 ExifAttribute.createUShort(0, mExifByteOrder)); 3647 } 3648 if (getAttribute(TAG_LIGHT_SOURCE) == null) { 3649 mAttributes[IFD_TYPE_EXIF].put(TAG_LIGHT_SOURCE, 3650 ExifAttribute.createULong(0, mExifByteOrder)); 3651 } 3652 } 3653 readByteOrder(ByteOrderedDataInputStream dataInputStream)3654 private ByteOrder readByteOrder(ByteOrderedDataInputStream dataInputStream) 3655 throws IOException { 3656 // Read byte order. 3657 short byteOrder = dataInputStream.readShort(); 3658 switch (byteOrder) { 3659 case BYTE_ALIGN_II: 3660 if (DEBUG) { 3661 Log.d(TAG, "readExifSegment: Byte Align II"); 3662 } 3663 return ByteOrder.LITTLE_ENDIAN; 3664 case BYTE_ALIGN_MM: 3665 if (DEBUG) { 3666 Log.d(TAG, "readExifSegment: Byte Align MM"); 3667 } 3668 return ByteOrder.BIG_ENDIAN; 3669 default: 3670 throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder)); 3671 } 3672 } 3673 parseTiffHeaders(ByteOrderedDataInputStream dataInputStream, int exifBytesLength)3674 private void parseTiffHeaders(ByteOrderedDataInputStream dataInputStream, 3675 int exifBytesLength) throws IOException { 3676 // Read byte order 3677 mExifByteOrder = readByteOrder(dataInputStream); 3678 // Set byte order 3679 dataInputStream.setByteOrder(mExifByteOrder); 3680 3681 // Check start code 3682 int startCode = dataInputStream.readUnsignedShort(); 3683 if (mMimeType != IMAGE_TYPE_ORF && mMimeType != IMAGE_TYPE_RW2 && startCode != START_CODE) { 3684 throw new IOException("Invalid start code: " + Integer.toHexString(startCode)); 3685 } 3686 3687 // Read and skip to first ifd offset 3688 int firstIfdOffset = dataInputStream.readInt(); 3689 if (firstIfdOffset < 8 || firstIfdOffset >= exifBytesLength) { 3690 throw new IOException("Invalid first Ifd offset: " + firstIfdOffset); 3691 } 3692 firstIfdOffset -= 8; 3693 if (firstIfdOffset > 0) { 3694 if (dataInputStream.skipBytes(firstIfdOffset) != firstIfdOffset) { 3695 throw new IOException("Couldn't jump to first Ifd: " + firstIfdOffset); 3696 } 3697 } 3698 } 3699 3700 // Reads image file directory, which is a tag group in EXIF. readImageFileDirectory(ByteOrderedDataInputStream dataInputStream, @IfdType int ifdType)3701 private void readImageFileDirectory(ByteOrderedDataInputStream dataInputStream, 3702 @IfdType int ifdType) throws IOException { 3703 // Save offset of current IFD to prevent reading an IFD that is already read. 3704 mHandledIfdOffsets.add(dataInputStream.mPosition); 3705 3706 if (dataInputStream.mPosition + 2 > dataInputStream.mLength) { 3707 // Return if there is no data from the offset. 3708 return; 3709 } 3710 // See TIFF 6.0 Section 2: TIFF Structure, Figure 1. 3711 short numberOfDirectoryEntry = dataInputStream.readShort(); 3712 if (dataInputStream.mPosition + 12 * numberOfDirectoryEntry > dataInputStream.mLength 3713 || numberOfDirectoryEntry <= 0) { 3714 // Return if the size of entries is either too big or negative. 3715 return; 3716 } 3717 3718 if (DEBUG) { 3719 Log.d(TAG, "numberOfDirectoryEntry: " + numberOfDirectoryEntry); 3720 } 3721 3722 // See TIFF 6.0 Section 2: TIFF Structure, "Image File Directory". 3723 for (short i = 0; i < numberOfDirectoryEntry; ++i) { 3724 int tagNumber = dataInputStream.readUnsignedShort(); 3725 int dataFormat = dataInputStream.readUnsignedShort(); 3726 int numberOfComponents = dataInputStream.readInt(); 3727 // Next four bytes is for data offset or value. 3728 long nextEntryOffset = dataInputStream.peek() + 4; 3729 3730 // Look up a corresponding tag from tag number 3731 ExifTag tag = (ExifTag) sExifTagMapsForReading[ifdType].get(tagNumber); 3732 3733 if (DEBUG) { 3734 Log.d(TAG, String.format("ifdType: %d, tagNumber: %d, tagName: %s, dataFormat: %d, " 3735 + "numberOfComponents: %d", ifdType, tagNumber, 3736 tag != null ? tag.name : null, dataFormat, numberOfComponents)); 3737 } 3738 3739 long byteCount = 0; 3740 boolean valid = false; 3741 if (tag == null) { 3742 if (DEBUG) { 3743 Log.d(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber); 3744 } 3745 } else if (dataFormat <= 0 || dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) { 3746 if (DEBUG) { 3747 Log.d(TAG, "Skip the tag entry since data format is invalid: " + dataFormat); 3748 } 3749 } else { 3750 byteCount = (long) numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat]; 3751 if (byteCount < 0 || byteCount > Integer.MAX_VALUE) { 3752 if (DEBUG) { 3753 Log.d(TAG, "Skip the tag entry since the number of components is invalid: " 3754 + numberOfComponents); 3755 } 3756 } else { 3757 valid = true; 3758 } 3759 } 3760 if (!valid) { 3761 dataInputStream.seek(nextEntryOffset); 3762 continue; 3763 } 3764 3765 // Read a value from data field or seek to the value offset which is stored in data 3766 // field if the size of the entry value is bigger than 4. 3767 if (byteCount > 4) { 3768 int offset = dataInputStream.readInt(); 3769 if (DEBUG) { 3770 Log.d(TAG, "seek to data offset: " + offset); 3771 } 3772 if (mMimeType == IMAGE_TYPE_ORF) { 3773 if (tag.name == TAG_MAKER_NOTE) { 3774 // Save offset value for reading thumbnail 3775 mOrfMakerNoteOffset = offset; 3776 } else if (ifdType == IFD_TYPE_ORF_MAKER_NOTE 3777 && tag.name == TAG_ORF_THUMBNAIL_IMAGE) { 3778 // Retrieve & update values for thumbnail offset and length values for ORF 3779 mOrfThumbnailOffset = offset; 3780 mOrfThumbnailLength = numberOfComponents; 3781 3782 ExifAttribute compressionAttribute = 3783 ExifAttribute.createUShort(DATA_JPEG, mExifByteOrder); 3784 ExifAttribute jpegInterchangeFormatAttribute = 3785 ExifAttribute.createULong(mOrfThumbnailOffset, mExifByteOrder); 3786 ExifAttribute jpegInterchangeFormatLengthAttribute = 3787 ExifAttribute.createULong(mOrfThumbnailLength, mExifByteOrder); 3788 3789 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_COMPRESSION, compressionAttribute); 3790 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT, 3791 jpegInterchangeFormatAttribute); 3792 mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 3793 jpegInterchangeFormatLengthAttribute); 3794 } 3795 } else if (mMimeType == IMAGE_TYPE_RW2) { 3796 if (tag.name == TAG_RW2_JPG_FROM_RAW) { 3797 mRw2JpgFromRawOffset = offset; 3798 } 3799 } 3800 if (offset + byteCount <= dataInputStream.mLength) { 3801 dataInputStream.seek(offset); 3802 } else { 3803 // Skip if invalid data offset. 3804 if (DEBUG) { 3805 Log.d(TAG, "Skip the tag entry since data offset is invalid: " + offset); 3806 } 3807 dataInputStream.seek(nextEntryOffset); 3808 continue; 3809 } 3810 } 3811 3812 // Recursively parse IFD when a IFD pointer tag appears. 3813 Integer nextIfdType = sExifPointerTagMap.get(tagNumber); 3814 if (DEBUG) { 3815 Log.d(TAG, "nextIfdType: " + nextIfdType + " byteCount: " + byteCount); 3816 } 3817 3818 if (nextIfdType != null) { 3819 long offset = -1L; 3820 // Get offset from data field 3821 switch (dataFormat) { 3822 case IFD_FORMAT_USHORT: { 3823 offset = dataInputStream.readUnsignedShort(); 3824 break; 3825 } 3826 case IFD_FORMAT_SSHORT: { 3827 offset = dataInputStream.readShort(); 3828 break; 3829 } 3830 case IFD_FORMAT_ULONG: { 3831 offset = dataInputStream.readUnsignedInt(); 3832 break; 3833 } 3834 case IFD_FORMAT_SLONG: 3835 case IFD_FORMAT_IFD: { 3836 offset = dataInputStream.readInt(); 3837 break; 3838 } 3839 default: { 3840 // Nothing to do 3841 break; 3842 } 3843 } 3844 if (DEBUG) { 3845 Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name)); 3846 } 3847 3848 // Check if the next IFD offset 3849 // 1. Exists within the boundaries of the input stream 3850 // 2. Does not point to a previously read IFD. 3851 if (offset > 0L && offset < dataInputStream.mLength) { 3852 if (!mHandledIfdOffsets.contains((int) offset)) { 3853 dataInputStream.seek(offset); 3854 readImageFileDirectory(dataInputStream, nextIfdType); 3855 } else { 3856 if (DEBUG) { 3857 Log.d(TAG, "Skip jump into the IFD since it has already been read: " 3858 + "IfdType " + nextIfdType + " (at " + offset + ")"); 3859 } 3860 } 3861 } else { 3862 if (DEBUG) { 3863 Log.d(TAG, "Skip jump into the IFD since its offset is invalid: " + offset); 3864 } 3865 } 3866 3867 dataInputStream.seek(nextEntryOffset); 3868 continue; 3869 } 3870 3871 final int bytesOffset = dataInputStream.peek() + mExifOffset; 3872 final byte[] bytes = new byte[(int) byteCount]; 3873 dataInputStream.readFully(bytes); 3874 ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents, 3875 bytesOffset, bytes); 3876 mAttributes[ifdType].put(tag.name, attribute); 3877 3878 // DNG files have a DNG Version tag specifying the version of specifications that the 3879 // image file is following. 3880 // See http://fileformats.archiveteam.org/wiki/DNG 3881 if (tag.name == TAG_DNG_VERSION) { 3882 mMimeType = IMAGE_TYPE_DNG; 3883 } 3884 3885 // PEF files have a Make or Model tag that begins with "PENTAX" or a compression tag 3886 // that is 65535. 3887 // See http://fileformats.archiveteam.org/wiki/Pentax_PEF 3888 if (((tag.name == TAG_MAKE || tag.name == TAG_MODEL) 3889 && attribute.getStringValue(mExifByteOrder).contains(PEF_SIGNATURE)) 3890 || (tag.name == TAG_COMPRESSION 3891 && attribute.getIntValue(mExifByteOrder) == 65535)) { 3892 mMimeType = IMAGE_TYPE_PEF; 3893 } 3894 3895 // Seek to next tag offset 3896 if (dataInputStream.peek() != nextEntryOffset) { 3897 dataInputStream.seek(nextEntryOffset); 3898 } 3899 } 3900 3901 if (dataInputStream.peek() + 4 <= dataInputStream.mLength) { 3902 int nextIfdOffset = dataInputStream.readInt(); 3903 if (DEBUG) { 3904 Log.d(TAG, String.format("nextIfdOffset: %d", nextIfdOffset)); 3905 } 3906 // Check if the next IFD offset 3907 // 1. Exists within the boundaries of the input stream 3908 // 2. Does not point to a previously read IFD. 3909 if (nextIfdOffset > 0L && nextIfdOffset < dataInputStream.mLength) { 3910 if (!mHandledIfdOffsets.contains(nextIfdOffset)) { 3911 dataInputStream.seek(nextIfdOffset); 3912 // Do not overwrite thumbnail IFD data if it alreay exists. 3913 if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) { 3914 readImageFileDirectory(dataInputStream, IFD_TYPE_THUMBNAIL); 3915 } else if (mAttributes[IFD_TYPE_PREVIEW].isEmpty()) { 3916 readImageFileDirectory(dataInputStream, IFD_TYPE_PREVIEW); 3917 } 3918 } else { 3919 if (DEBUG) { 3920 Log.d(TAG, "Stop reading file since re-reading an IFD may cause an " 3921 + "infinite loop: " + nextIfdOffset); 3922 } 3923 } 3924 } else { 3925 if (DEBUG) { 3926 Log.d(TAG, "Stop reading file since a wrong offset may cause an infinite loop: " 3927 + nextIfdOffset); 3928 } 3929 } 3930 } 3931 } 3932 3933 /** 3934 * JPEG compressed images do not contain IMAGE_LENGTH & IMAGE_WIDTH tags. 3935 * This value uses JpegInterchangeFormat(JPEG data offset) value, and calls getJpegAttributes() 3936 * to locate SOF(Start of Frame) marker and update the image length & width values. 3937 * See JEITA CP-3451C Table 5 and Section 4.8.1. B. 3938 */ retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType)3939 private void retrieveJpegImageSize(ByteOrderedDataInputStream in, int imageType) 3940 throws IOException { 3941 // Check if image already has IMAGE_LENGTH & IMAGE_WIDTH values 3942 ExifAttribute imageLengthAttribute = 3943 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_LENGTH); 3944 ExifAttribute imageWidthAttribute = 3945 (ExifAttribute) mAttributes[imageType].get(TAG_IMAGE_WIDTH); 3946 3947 if (imageLengthAttribute == null || imageWidthAttribute == null) { 3948 // Find if offset for JPEG data exists 3949 ExifAttribute jpegInterchangeFormatAttribute = 3950 (ExifAttribute) mAttributes[imageType].get(TAG_JPEG_INTERCHANGE_FORMAT); 3951 if (jpegInterchangeFormatAttribute != null) { 3952 int jpegInterchangeFormat = 3953 jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); 3954 3955 // Searches for SOF marker in JPEG data and updates IMAGE_LENGTH & IMAGE_WIDTH tags 3956 getJpegAttributes(in, jpegInterchangeFormat, imageType); 3957 } 3958 } 3959 } 3960 3961 // Sets thumbnail offset & length attributes based on JpegInterchangeFormat or StripOffsets tags setThumbnailData(ByteOrderedDataInputStream in)3962 private void setThumbnailData(ByteOrderedDataInputStream in) throws IOException { 3963 HashMap thumbnailData = mAttributes[IFD_TYPE_THUMBNAIL]; 3964 3965 ExifAttribute compressionAttribute = 3966 (ExifAttribute) thumbnailData.get(TAG_COMPRESSION); 3967 if (compressionAttribute != null) { 3968 mThumbnailCompression = compressionAttribute.getIntValue(mExifByteOrder); 3969 switch (mThumbnailCompression) { 3970 case DATA_JPEG: { 3971 handleThumbnailFromJfif(in, thumbnailData); 3972 break; 3973 } 3974 case DATA_UNCOMPRESSED: 3975 case DATA_JPEG_COMPRESSED: { 3976 if (isSupportedDataType(thumbnailData)) { 3977 handleThumbnailFromStrips(in, thumbnailData); 3978 } 3979 break; 3980 } 3981 } 3982 } else { 3983 // Thumbnail data may not contain Compression tag value 3984 handleThumbnailFromJfif(in, thumbnailData); 3985 } 3986 } 3987 3988 // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values 3989 // and reads the corresponding bytes if stream does not support seek function handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData)3990 private void handleThumbnailFromJfif(ByteOrderedDataInputStream in, HashMap thumbnailData) 3991 throws IOException { 3992 ExifAttribute jpegInterchangeFormatAttribute = 3993 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT); 3994 ExifAttribute jpegInterchangeFormatLengthAttribute = 3995 (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); 3996 if (jpegInterchangeFormatAttribute != null 3997 && jpegInterchangeFormatLengthAttribute != null) { 3998 int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); 3999 int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); 4000 4001 if (mMimeType == IMAGE_TYPE_ORF) { 4002 // Update offset value since RAF files have IFD data preceding MakerNote data. 4003 thumbnailOffset += mOrfMakerNoteOffset; 4004 } 4005 // The following code limits the size of thumbnail size not to overflow EXIF data area. 4006 thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset); 4007 4008 if (thumbnailOffset > 0 && thumbnailLength > 0) { 4009 mHasThumbnail = true; 4010 // Need to add mExifOffset, which is the offset to the EXIF data segment 4011 mThumbnailOffset = thumbnailOffset + mExifOffset; 4012 mThumbnailLength = thumbnailLength; 4013 mThumbnailCompression = DATA_JPEG; 4014 4015 if (mFilename == null && mAssetInputStream == null 4016 && mSeekableFileDescriptor == null) { 4017 // TODO: Need to handle potential OutOfMemoryError 4018 // Save the thumbnail in memory if the input doesn't support reading again. 4019 byte[] thumbnailBytes = new byte[mThumbnailLength]; 4020 in.seek(mThumbnailOffset); 4021 in.readFully(thumbnailBytes); 4022 mThumbnailBytes = thumbnailBytes; 4023 } 4024 } 4025 if (DEBUG) { 4026 Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset 4027 + ", length: " + thumbnailLength); 4028 } 4029 } 4030 } 4031 4032 // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData)4033 private void handleThumbnailFromStrips(ByteOrderedDataInputStream in, HashMap thumbnailData) 4034 throws IOException { 4035 ExifAttribute stripOffsetsAttribute = 4036 (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS); 4037 ExifAttribute stripByteCountsAttribute = 4038 (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS); 4039 4040 if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) { 4041 long[] stripOffsets = 4042 convertToLongArray(stripOffsetsAttribute.getValue(mExifByteOrder)); 4043 long[] stripByteCounts = 4044 convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder)); 4045 4046 if (stripOffsets == null || stripOffsets.length == 0) { 4047 Log.w(TAG, "stripOffsets should not be null or have zero length."); 4048 return; 4049 } 4050 if (stripByteCounts == null || stripByteCounts.length == 0) { 4051 Log.w(TAG, "stripByteCounts should not be null or have zero length."); 4052 return; 4053 } 4054 if (stripOffsets.length != stripByteCounts.length) { 4055 Log.w(TAG, "stripOffsets and stripByteCounts should have same length."); 4056 return; 4057 } 4058 4059 // TODO: Need to handle potential OutOfMemoryError 4060 // Set thumbnail byte array data for non-consecutive strip bytes 4061 byte[] totalStripBytes = 4062 new byte[(int) Arrays.stream(stripByteCounts).sum()]; 4063 4064 int bytesRead = 0; 4065 int bytesAdded = 0; 4066 mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true; 4067 for (int i = 0; i < stripOffsets.length; i++) { 4068 int stripOffset = (int) stripOffsets[i]; 4069 int stripByteCount = (int) stripByteCounts[i]; 4070 4071 // Check if strips are consecutive 4072 // TODO: Add test for non-consecutive thumbnail image 4073 if (i < stripOffsets.length - 1 4074 && stripOffset + stripByteCount != stripOffsets[i + 1]) { 4075 mAreThumbnailStripsConsecutive = false; 4076 } 4077 4078 // Skip to offset 4079 int skipBytes = stripOffset - bytesRead; 4080 if (skipBytes < 0) { 4081 Log.d(TAG, "Invalid strip offset value"); 4082 } 4083 in.seek(skipBytes); 4084 bytesRead += skipBytes; 4085 4086 // TODO: Need to handle potential OutOfMemoryError 4087 // Read strip bytes 4088 byte[] stripBytes = new byte[stripByteCount]; 4089 in.read(stripBytes); 4090 bytesRead += stripByteCount; 4091 4092 // Add bytes to array 4093 System.arraycopy(stripBytes, 0, totalStripBytes, bytesAdded, 4094 stripBytes.length); 4095 bytesAdded += stripBytes.length; 4096 } 4097 mThumbnailBytes = totalStripBytes; 4098 4099 if (mAreThumbnailStripsConsecutive) { 4100 // Need to add mExifOffset, which is the offset to the EXIF data segment 4101 mThumbnailOffset = (int) stripOffsets[0] + mExifOffset; 4102 mThumbnailLength = totalStripBytes.length; 4103 } 4104 } 4105 } 4106 4107 // Check if thumbnail data type is currently supported or not isSupportedDataType(HashMap thumbnailData)4108 private boolean isSupportedDataType(HashMap thumbnailData) throws IOException { 4109 ExifAttribute bitsPerSampleAttribute = 4110 (ExifAttribute) thumbnailData.get(TAG_BITS_PER_SAMPLE); 4111 if (bitsPerSampleAttribute != null) { 4112 int[] bitsPerSampleValue = (int[]) bitsPerSampleAttribute.getValue(mExifByteOrder); 4113 4114 if (Arrays.equals(BITS_PER_SAMPLE_RGB, bitsPerSampleValue)) { 4115 return true; 4116 } 4117 4118 // See DNG Specification 1.4.0.0. Section 3, Compression. 4119 if (mMimeType == IMAGE_TYPE_DNG) { 4120 ExifAttribute photometricInterpretationAttribute = 4121 (ExifAttribute) thumbnailData.get(TAG_PHOTOMETRIC_INTERPRETATION); 4122 if (photometricInterpretationAttribute != null) { 4123 int photometricInterpretationValue 4124 = photometricInterpretationAttribute.getIntValue(mExifByteOrder); 4125 if ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO 4126 && Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_GREYSCALE_2)) 4127 || ((photometricInterpretationValue == PHOTOMETRIC_INTERPRETATION_YCBCR) 4128 && (Arrays.equals(bitsPerSampleValue, BITS_PER_SAMPLE_RGB)))) { 4129 return true; 4130 } else { 4131 // TODO: Add support for lossless Huffman JPEG data 4132 } 4133 } 4134 } 4135 } 4136 if (DEBUG) { 4137 Log.d(TAG, "Unsupported data type value"); 4138 } 4139 return false; 4140 } 4141 4142 // Returns true if the image length and width values are <= 512. 4143 // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567 isThumbnail(HashMap map)4144 private boolean isThumbnail(HashMap map) throws IOException { 4145 ExifAttribute imageLengthAttribute = (ExifAttribute) map.get(TAG_IMAGE_LENGTH); 4146 ExifAttribute imageWidthAttribute = (ExifAttribute) map.get(TAG_IMAGE_WIDTH); 4147 4148 if (imageLengthAttribute != null && imageWidthAttribute != null) { 4149 int imageLengthValue = imageLengthAttribute.getIntValue(mExifByteOrder); 4150 int imageWidthValue = imageWidthAttribute.getIntValue(mExifByteOrder); 4151 if (imageLengthValue <= MAX_THUMBNAIL_SIZE && imageWidthValue <= MAX_THUMBNAIL_SIZE) { 4152 return true; 4153 } 4154 } 4155 return false; 4156 } 4157 4158 // Validate primary, preview, thumbnail image data by comparing image size validateImages()4159 private void validateImages() throws IOException { 4160 // Swap images based on size (primary > preview > thumbnail) 4161 swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW); 4162 swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL); 4163 swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL); 4164 4165 // TODO (b/142296453): Revise image width/height setting logic 4166 // Check if image has PixelXDimension/PixelYDimension tags, which contain valid image 4167 // sizes, excluding padding at the right end or bottom end of the image to make sure that 4168 // the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B. 4169 ExifAttribute pixelXDimAttribute = 4170 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_X_DIMENSION); 4171 ExifAttribute pixelYDimAttribute = 4172 (ExifAttribute) mAttributes[IFD_TYPE_EXIF].get(TAG_PIXEL_Y_DIMENSION); 4173 if (pixelXDimAttribute != null && pixelYDimAttribute != null) { 4174 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH, pixelXDimAttribute); 4175 mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH, pixelYDimAttribute); 4176 } 4177 4178 // Check whether thumbnail image exists and whether preview image satisfies the thumbnail 4179 // image requirements 4180 if (mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) { 4181 if (isThumbnail(mAttributes[IFD_TYPE_PREVIEW])) { 4182 mAttributes[IFD_TYPE_THUMBNAIL] = mAttributes[IFD_TYPE_PREVIEW]; 4183 mAttributes[IFD_TYPE_PREVIEW] = new HashMap(); 4184 } 4185 } 4186 4187 // Check if the thumbnail image satisfies the thumbnail size requirements 4188 if (!isThumbnail(mAttributes[IFD_TYPE_THUMBNAIL])) { 4189 Log.d(TAG, "No image meets the size requirements of a thumbnail image."); 4190 } 4191 } 4192 4193 /** 4194 * If image is uncompressed, ImageWidth/Length tags are used to store size info. 4195 * However, uncompressed images often store extra pixels around the edges of the final image, 4196 * which results in larger values for TAG_IMAGE_WIDTH and TAG_IMAGE_LENGTH tags. 4197 * This method corrects those tag values by checking first the values of TAG_DEFAULT_CROP_SIZE 4198 * See DNG Specification 1.4.0.0. Section 4. (DefaultCropSize) 4199 * 4200 * If image is a RW2 file, valid image sizes are stored in SensorBorder tags. 4201 * See tiff_parser.cc GetFullDimension32() 4202 * */ updateImageSizeValues(ByteOrderedDataInputStream in, int imageType)4203 private void updateImageSizeValues(ByteOrderedDataInputStream in, int imageType) 4204 throws IOException { 4205 // Uncompressed image valid image size values 4206 ExifAttribute defaultCropSizeAttribute = 4207 (ExifAttribute) mAttributes[imageType].get(TAG_DEFAULT_CROP_SIZE); 4208 // RW2 image valid image size values 4209 ExifAttribute topBorderAttribute = 4210 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_TOP_BORDER); 4211 ExifAttribute leftBorderAttribute = 4212 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_LEFT_BORDER); 4213 ExifAttribute bottomBorderAttribute = 4214 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_BOTTOM_BORDER); 4215 ExifAttribute rightBorderAttribute = 4216 (ExifAttribute) mAttributes[imageType].get(TAG_RW2_SENSOR_RIGHT_BORDER); 4217 4218 if (defaultCropSizeAttribute != null) { 4219 // Update for uncompressed image 4220 ExifAttribute defaultCropSizeXAttribute, defaultCropSizeYAttribute; 4221 if (defaultCropSizeAttribute.format == IFD_FORMAT_URATIONAL) { 4222 Rational[] defaultCropSizeValue = 4223 (Rational[]) defaultCropSizeAttribute.getValue(mExifByteOrder); 4224 defaultCropSizeXAttribute = 4225 ExifAttribute.createURational(defaultCropSizeValue[0], mExifByteOrder); 4226 defaultCropSizeYAttribute = 4227 ExifAttribute.createURational(defaultCropSizeValue[1], mExifByteOrder); 4228 } else { 4229 int[] defaultCropSizeValue = 4230 (int[]) defaultCropSizeAttribute.getValue(mExifByteOrder); 4231 defaultCropSizeXAttribute = 4232 ExifAttribute.createUShort(defaultCropSizeValue[0], mExifByteOrder); 4233 defaultCropSizeYAttribute = 4234 ExifAttribute.createUShort(defaultCropSizeValue[1], mExifByteOrder); 4235 } 4236 mAttributes[imageType].put(TAG_IMAGE_WIDTH, defaultCropSizeXAttribute); 4237 mAttributes[imageType].put(TAG_IMAGE_LENGTH, defaultCropSizeYAttribute); 4238 } else if (topBorderAttribute != null && leftBorderAttribute != null && 4239 bottomBorderAttribute != null && rightBorderAttribute != null) { 4240 // Update for RW2 image 4241 int topBorderValue = topBorderAttribute.getIntValue(mExifByteOrder); 4242 int bottomBorderValue = bottomBorderAttribute.getIntValue(mExifByteOrder); 4243 int rightBorderValue = rightBorderAttribute.getIntValue(mExifByteOrder); 4244 int leftBorderValue = leftBorderAttribute.getIntValue(mExifByteOrder); 4245 if (bottomBorderValue > topBorderValue && rightBorderValue > leftBorderValue) { 4246 int length = bottomBorderValue - topBorderValue; 4247 int width = rightBorderValue - leftBorderValue; 4248 ExifAttribute imageLengthAttribute = 4249 ExifAttribute.createUShort(length, mExifByteOrder); 4250 ExifAttribute imageWidthAttribute = 4251 ExifAttribute.createUShort(width, mExifByteOrder); 4252 mAttributes[imageType].put(TAG_IMAGE_LENGTH, imageLengthAttribute); 4253 mAttributes[imageType].put(TAG_IMAGE_WIDTH, imageWidthAttribute); 4254 } 4255 } else { 4256 retrieveJpegImageSize(in, imageType); 4257 } 4258 } 4259 4260 // Writes an Exif segment into the given output stream. writeExifSegment(ByteOrderedDataOutputStream dataOutputStream)4261 private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException { 4262 // The following variables are for calculating each IFD tag group size in bytes. 4263 int[] ifdOffsets = new int[EXIF_TAGS.length]; 4264 int[] ifdDataSizes = new int[EXIF_TAGS.length]; 4265 4266 // Remove IFD pointer tags (we'll re-add it later.) 4267 for (ExifTag tag : EXIF_POINTER_TAGS) { 4268 removeAttribute(tag.name); 4269 } 4270 // Remove old thumbnail data 4271 removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name); 4272 removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name); 4273 4274 // Remove null value tags. 4275 for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) { 4276 for (Object obj : mAttributes[ifdType].entrySet().toArray()) { 4277 final Map.Entry entry = (Map.Entry) obj; 4278 if (entry.getValue() == null) { 4279 mAttributes[ifdType].remove(entry.getKey()); 4280 } 4281 } 4282 } 4283 4284 // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD 4285 // offset when there is one or more tags in the thumbnail IFD. 4286 if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) { 4287 mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name, 4288 ExifAttribute.createULong(0, mExifByteOrder)); 4289 } 4290 if (!mAttributes[IFD_TYPE_GPS].isEmpty()) { 4291 mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name, 4292 ExifAttribute.createULong(0, mExifByteOrder)); 4293 } 4294 if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) { 4295 mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name, 4296 ExifAttribute.createULong(0, mExifByteOrder)); 4297 } 4298 if (mHasThumbnail) { 4299 mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name, 4300 ExifAttribute.createULong(0, mExifByteOrder)); 4301 mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, 4302 ExifAttribute.createULong(mThumbnailLength, mExifByteOrder)); 4303 } 4304 4305 // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry 4306 // value which has a bigger size than 4 bytes. 4307 for (int i = 0; i < EXIF_TAGS.length; ++i) { 4308 int sum = 0; 4309 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) { 4310 final ExifAttribute exifAttribute = (ExifAttribute) entry.getValue(); 4311 final int size = exifAttribute.size(); 4312 if (size > 4) { 4313 sum += size; 4314 } 4315 } 4316 ifdDataSizes[i] += sum; 4317 } 4318 4319 // Calculate IFD offsets. 4320 // 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes 4321 // (offset of IFDs) 4322 int position = 8; 4323 for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) { 4324 if (!mAttributes[ifdType].isEmpty()) { 4325 ifdOffsets[ifdType] = position; 4326 position += 2 + mAttributes[ifdType].size() * 12 + 4 + ifdDataSizes[ifdType]; 4327 } 4328 } 4329 if (mHasThumbnail) { 4330 int thumbnailOffset = position; 4331 mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name, 4332 ExifAttribute.createULong(thumbnailOffset, mExifByteOrder)); 4333 // Need to add mExifOffset, which is the offset to the EXIF data segment 4334 mThumbnailOffset = thumbnailOffset + mExifOffset; 4335 position += mThumbnailLength; 4336 } 4337 4338 int totalSize = position; 4339 if (mMimeType == IMAGE_TYPE_JPEG) { 4340 // Add 8 bytes for APP1 size and identifier data 4341 totalSize += 8; 4342 } 4343 if (DEBUG) { 4344 for (int i = 0; i < EXIF_TAGS.length; ++i) { 4345 Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d, " 4346 + "total size: %d", i, ifdOffsets[i], mAttributes[i].size(), 4347 ifdDataSizes[i], totalSize)); 4348 } 4349 } 4350 4351 // Update IFD pointer tags with the calculated offsets. 4352 if (!mAttributes[IFD_TYPE_EXIF].isEmpty()) { 4353 mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[1].name, 4354 ExifAttribute.createULong(ifdOffsets[IFD_TYPE_EXIF], mExifByteOrder)); 4355 } 4356 if (!mAttributes[IFD_TYPE_GPS].isEmpty()) { 4357 mAttributes[IFD_TYPE_PRIMARY].put(EXIF_POINTER_TAGS[2].name, 4358 ExifAttribute.createULong(ifdOffsets[IFD_TYPE_GPS], mExifByteOrder)); 4359 } 4360 if (!mAttributes[IFD_TYPE_INTEROPERABILITY].isEmpty()) { 4361 mAttributes[IFD_TYPE_EXIF].put(EXIF_POINTER_TAGS[3].name, ExifAttribute.createULong( 4362 ifdOffsets[IFD_TYPE_INTEROPERABILITY], mExifByteOrder)); 4363 } 4364 4365 if (mMimeType == IMAGE_TYPE_JPEG) { 4366 // Write JPEG specific data (APP1 size, APP1 identifier) 4367 dataOutputStream.writeUnsignedShort(totalSize); 4368 dataOutputStream.write(IDENTIFIER_EXIF_APP1); 4369 } else if (mMimeType == IMAGE_TYPE_PNG) { 4370 // Write PNG specific data (chunk size, chunk type) 4371 dataOutputStream.writeInt(totalSize); 4372 dataOutputStream.write(PNG_CHUNK_TYPE_EXIF); 4373 } 4374 4375 // Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1. 4376 dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN 4377 ? BYTE_ALIGN_MM : BYTE_ALIGN_II); 4378 dataOutputStream.setByteOrder(mExifByteOrder); 4379 dataOutputStream.writeUnsignedShort(START_CODE); 4380 dataOutputStream.writeUnsignedInt(IFD_OFFSET); 4381 4382 // Write IFD groups. See JEITA CP-3451C Section 4.5.8. Figure 9. 4383 for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) { 4384 if (!mAttributes[ifdType].isEmpty()) { 4385 // See JEITA CP-3451C Section 4.6.2: IFD structure. 4386 // Write entry count 4387 dataOutputStream.writeUnsignedShort(mAttributes[ifdType].size()); 4388 4389 // Write entry info 4390 int dataOffset = ifdOffsets[ifdType] + 2 + mAttributes[ifdType].size() * 12 + 4; 4391 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) { 4392 // Convert tag name to tag number. 4393 final ExifTag tag = 4394 (ExifTag) sExifTagMapsForWriting[ifdType].get(entry.getKey()); 4395 final int tagNumber = tag.number; 4396 final ExifAttribute attribute = (ExifAttribute) entry.getValue(); 4397 final int size = attribute.size(); 4398 4399 dataOutputStream.writeUnsignedShort(tagNumber); 4400 dataOutputStream.writeUnsignedShort(attribute.format); 4401 dataOutputStream.writeInt(attribute.numberOfComponents); 4402 if (size > 4) { 4403 dataOutputStream.writeUnsignedInt(dataOffset); 4404 dataOffset += size; 4405 } else { 4406 dataOutputStream.write(attribute.bytes); 4407 // Fill zero up to 4 bytes 4408 if (size < 4) { 4409 for (int i = size; i < 4; ++i) { 4410 dataOutputStream.writeByte(0); 4411 } 4412 } 4413 } 4414 } 4415 4416 // Write the next offset. It writes the offset of thumbnail IFD if there is one or 4417 // more tags in the thumbnail IFD when the current IFD is the primary image TIFF 4418 // IFD; Otherwise 0. 4419 if (ifdType == 0 && !mAttributes[IFD_TYPE_THUMBNAIL].isEmpty()) { 4420 dataOutputStream.writeUnsignedInt(ifdOffsets[IFD_TYPE_THUMBNAIL]); 4421 } else { 4422 dataOutputStream.writeUnsignedInt(0); 4423 } 4424 4425 // Write values of data field exceeding 4 bytes after the next offset. 4426 for (Map.Entry entry : (Set<Map.Entry>) mAttributes[ifdType].entrySet()) { 4427 ExifAttribute attribute = (ExifAttribute) entry.getValue(); 4428 4429 if (attribute.bytes.length > 4) { 4430 dataOutputStream.write(attribute.bytes, 0, attribute.bytes.length); 4431 } 4432 } 4433 } 4434 } 4435 4436 // Write thumbnail 4437 if (mHasThumbnail) { 4438 dataOutputStream.write(getThumbnailBytes()); 4439 } 4440 4441 // Reset the byte order to big endian in order to write remaining parts of the JPEG file. 4442 dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); 4443 4444 return totalSize; 4445 } 4446 4447 /** 4448 * Determines the data format of EXIF entry value. 4449 * 4450 * @param entryValue The value to be determined. 4451 * @return Returns two data formats guessed as a pair in integer. If there is no two candidate 4452 data formats for the given entry value, returns {@code -1} in the second of the pair. 4453 */ guessDataFormat(String entryValue)4454 private static Pair<Integer, Integer> guessDataFormat(String entryValue) { 4455 // See TIFF 6.0 Section 2, "Image File Directory". 4456 // Take the first component if there are more than one component. 4457 if (entryValue.contains(",")) { 4458 String[] entryValues = entryValue.split(","); 4459 Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]); 4460 if (dataFormat.first == IFD_FORMAT_STRING) { 4461 return dataFormat; 4462 } 4463 for (int i = 1; i < entryValues.length; ++i) { 4464 final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]); 4465 int first = -1, second = -1; 4466 if (guessDataFormat.first == dataFormat.first 4467 || guessDataFormat.second == dataFormat.first) { 4468 first = dataFormat.first; 4469 } 4470 if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second 4471 || guessDataFormat.second == dataFormat.second)) { 4472 second = dataFormat.second; 4473 } 4474 if (first == -1 && second == -1) { 4475 return new Pair<>(IFD_FORMAT_STRING, -1); 4476 } 4477 if (first == -1) { 4478 dataFormat = new Pair<>(second, -1); 4479 continue; 4480 } 4481 if (second == -1) { 4482 dataFormat = new Pair<>(first, -1); 4483 continue; 4484 } 4485 } 4486 return dataFormat; 4487 } 4488 4489 if (entryValue.contains("/")) { 4490 String[] rationalNumber = entryValue.split("/"); 4491 if (rationalNumber.length == 2) { 4492 try { 4493 long numerator = (long) Double.parseDouble(rationalNumber[0]); 4494 long denominator = (long) Double.parseDouble(rationalNumber[1]); 4495 if (numerator < 0L || denominator < 0L) { 4496 return new Pair<>(IFD_FORMAT_SRATIONAL, -1); 4497 } 4498 if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) { 4499 return new Pair<>(IFD_FORMAT_URATIONAL, -1); 4500 } 4501 return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL); 4502 } catch (NumberFormatException e) { 4503 // Ignored 4504 } 4505 } 4506 return new Pair<>(IFD_FORMAT_STRING, -1); 4507 } 4508 try { 4509 Long longValue = Long.parseLong(entryValue); 4510 if (longValue >= 0 && longValue <= 65535) { 4511 return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG); 4512 } 4513 if (longValue < 0) { 4514 return new Pair<>(IFD_FORMAT_SLONG, -1); 4515 } 4516 return new Pair<>(IFD_FORMAT_ULONG, -1); 4517 } catch (NumberFormatException e) { 4518 // Ignored 4519 } 4520 try { 4521 Double.parseDouble(entryValue); 4522 return new Pair<>(IFD_FORMAT_DOUBLE, -1); 4523 } catch (NumberFormatException e) { 4524 // Ignored 4525 } 4526 return new Pair<>(IFD_FORMAT_STRING, -1); 4527 } 4528 4529 // An input stream to parse EXIF data area, which can be written in either little or big endian 4530 // order. 4531 private static class ByteOrderedDataInputStream extends InputStream implements DataInput { 4532 private static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN; 4533 private static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN; 4534 4535 private DataInputStream mDataInputStream; 4536 private InputStream mInputStream; 4537 private ByteOrder mByteOrder = ByteOrder.BIG_ENDIAN; 4538 private final int mLength; 4539 private int mPosition; 4540 ByteOrderedDataInputStream(InputStream in)4541 public ByteOrderedDataInputStream(InputStream in) throws IOException { 4542 mInputStream = in; 4543 mDataInputStream = new DataInputStream(in); 4544 mLength = mDataInputStream.available(); 4545 mPosition = 0; 4546 // TODO (b/142218289): Need to handle case where input stream does not support mark 4547 mDataInputStream.mark(mLength); 4548 } 4549 ByteOrderedDataInputStream(byte[] bytes)4550 public ByteOrderedDataInputStream(byte[] bytes) throws IOException { 4551 this(new ByteArrayInputStream(bytes)); 4552 } 4553 setByteOrder(ByteOrder byteOrder)4554 public void setByteOrder(ByteOrder byteOrder) { 4555 mByteOrder = byteOrder; 4556 } 4557 seek(long byteCount)4558 public void seek(long byteCount) throws IOException { 4559 if (mPosition > byteCount) { 4560 mPosition = 0; 4561 mDataInputStream.reset(); 4562 // TODO (b/142218289): Need to handle case where input stream does not support mark 4563 mDataInputStream.mark(mLength); 4564 } else { 4565 byteCount -= mPosition; 4566 } 4567 4568 if (skipBytes((int) byteCount) != (int) byteCount) { 4569 throw new IOException("Couldn't seek up to the byteCount"); 4570 } 4571 } 4572 peek()4573 public int peek() { 4574 return mPosition; 4575 } 4576 4577 @Override available()4578 public int available() throws IOException { 4579 return mDataInputStream.available(); 4580 } 4581 4582 @Override read()4583 public int read() throws IOException { 4584 ++mPosition; 4585 return mDataInputStream.read(); 4586 } 4587 4588 @Override readUnsignedByte()4589 public int readUnsignedByte() throws IOException { 4590 ++mPosition; 4591 return mDataInputStream.readUnsignedByte(); 4592 } 4593 4594 @Override readLine()4595 public String readLine() throws IOException { 4596 Log.d(TAG, "Currently unsupported"); 4597 return null; 4598 } 4599 4600 @Override readBoolean()4601 public boolean readBoolean() throws IOException { 4602 ++mPosition; 4603 return mDataInputStream.readBoolean(); 4604 } 4605 4606 @Override readChar()4607 public char readChar() throws IOException { 4608 mPosition += 2; 4609 return mDataInputStream.readChar(); 4610 } 4611 4612 @Override readUTF()4613 public String readUTF() throws IOException { 4614 mPosition += 2; 4615 return mDataInputStream.readUTF(); 4616 } 4617 4618 @Override readFully(byte[] buffer, int offset, int length)4619 public void readFully(byte[] buffer, int offset, int length) throws IOException { 4620 mPosition += length; 4621 if (mPosition > mLength) { 4622 throw new EOFException(); 4623 } 4624 if (mDataInputStream.read(buffer, offset, length) != length) { 4625 throw new IOException("Couldn't read up to the length of buffer"); 4626 } 4627 } 4628 4629 @Override readFully(byte[] buffer)4630 public void readFully(byte[] buffer) throws IOException { 4631 mPosition += buffer.length; 4632 if (mPosition > mLength) { 4633 throw new EOFException(); 4634 } 4635 if (mDataInputStream.read(buffer, 0, buffer.length) != buffer.length) { 4636 throw new IOException("Couldn't read up to the length of buffer"); 4637 } 4638 } 4639 4640 @Override readByte()4641 public byte readByte() throws IOException { 4642 ++mPosition; 4643 if (mPosition > mLength) { 4644 throw new EOFException(); 4645 } 4646 int ch = mDataInputStream.read(); 4647 if (ch < 0) { 4648 throw new EOFException(); 4649 } 4650 return (byte) ch; 4651 } 4652 4653 @Override readShort()4654 public short readShort() throws IOException { 4655 mPosition += 2; 4656 if (mPosition > mLength) { 4657 throw new EOFException(); 4658 } 4659 int ch1 = mDataInputStream.read(); 4660 int ch2 = mDataInputStream.read(); 4661 if ((ch1 | ch2) < 0) { 4662 throw new EOFException(); 4663 } 4664 if (mByteOrder == LITTLE_ENDIAN) { 4665 return (short) ((ch2 << 8) + (ch1)); 4666 } else if (mByteOrder == BIG_ENDIAN) { 4667 return (short) ((ch1 << 8) + (ch2)); 4668 } 4669 throw new IOException("Invalid byte order: " + mByteOrder); 4670 } 4671 4672 @Override readInt()4673 public int readInt() throws IOException { 4674 mPosition += 4; 4675 if (mPosition > mLength) { 4676 throw new EOFException(); 4677 } 4678 int ch1 = mDataInputStream.read(); 4679 int ch2 = mDataInputStream.read(); 4680 int ch3 = mDataInputStream.read(); 4681 int ch4 = mDataInputStream.read(); 4682 if ((ch1 | ch2 | ch3 | ch4) < 0) { 4683 throw new EOFException(); 4684 } 4685 if (mByteOrder == LITTLE_ENDIAN) { 4686 return ((ch4 << 24) + (ch3 << 16) + (ch2 << 8) + ch1); 4687 } else if (mByteOrder == BIG_ENDIAN) { 4688 return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4); 4689 } 4690 throw new IOException("Invalid byte order: " + mByteOrder); 4691 } 4692 4693 @Override skipBytes(int byteCount)4694 public int skipBytes(int byteCount) throws IOException { 4695 int totalSkip = Math.min(byteCount, mLength - mPosition); 4696 int skipped = 0; 4697 while (skipped < totalSkip) { 4698 skipped += mDataInputStream.skipBytes(totalSkip - skipped); 4699 } 4700 mPosition += skipped; 4701 return skipped; 4702 } 4703 readUnsignedShort()4704 public int readUnsignedShort() throws IOException { 4705 mPosition += 2; 4706 if (mPosition > mLength) { 4707 throw new EOFException(); 4708 } 4709 int ch1 = mDataInputStream.read(); 4710 int ch2 = mDataInputStream.read(); 4711 if ((ch1 | ch2) < 0) { 4712 throw new EOFException(); 4713 } 4714 if (mByteOrder == LITTLE_ENDIAN) { 4715 return ((ch2 << 8) + (ch1)); 4716 } else if (mByteOrder == BIG_ENDIAN) { 4717 return ((ch1 << 8) + (ch2)); 4718 } 4719 throw new IOException("Invalid byte order: " + mByteOrder); 4720 } 4721 readUnsignedInt()4722 public long readUnsignedInt() throws IOException { 4723 return readInt() & 0xffffffffL; 4724 } 4725 4726 @Override readLong()4727 public long readLong() throws IOException { 4728 mPosition += 8; 4729 if (mPosition > mLength) { 4730 throw new EOFException(); 4731 } 4732 int ch1 = mDataInputStream.read(); 4733 int ch2 = mDataInputStream.read(); 4734 int ch3 = mDataInputStream.read(); 4735 int ch4 = mDataInputStream.read(); 4736 int ch5 = mDataInputStream.read(); 4737 int ch6 = mDataInputStream.read(); 4738 int ch7 = mDataInputStream.read(); 4739 int ch8 = mDataInputStream.read(); 4740 if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) { 4741 throw new EOFException(); 4742 } 4743 if (mByteOrder == LITTLE_ENDIAN) { 4744 return (((long) ch8 << 56) + ((long) ch7 << 48) + ((long) ch6 << 40) 4745 + ((long) ch5 << 32) + ((long) ch4 << 24) + ((long) ch3 << 16) 4746 + ((long) ch2 << 8) + (long) ch1); 4747 } else if (mByteOrder == BIG_ENDIAN) { 4748 return (((long) ch1 << 56) + ((long) ch2 << 48) + ((long) ch3 << 40) 4749 + ((long) ch4 << 32) + ((long) ch5 << 24) + ((long) ch6 << 16) 4750 + ((long) ch7 << 8) + (long) ch8); 4751 } 4752 throw new IOException("Invalid byte order: " + mByteOrder); 4753 } 4754 4755 @Override readFloat()4756 public float readFloat() throws IOException { 4757 return Float.intBitsToFloat(readInt()); 4758 } 4759 4760 @Override readDouble()4761 public double readDouble() throws IOException { 4762 return Double.longBitsToDouble(readLong()); 4763 } 4764 getLength()4765 public int getLength() { 4766 return mLength; 4767 } 4768 } 4769 4770 // An output stream to write EXIF data area, which can be written in either little or big endian 4771 // order. 4772 private static class ByteOrderedDataOutputStream extends FilterOutputStream { 4773 final OutputStream mOutputStream; 4774 private ByteOrder mByteOrder; 4775 ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder)4776 public ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder) { 4777 super(out); 4778 mOutputStream = out; 4779 mByteOrder = byteOrder; 4780 } 4781 setByteOrder(ByteOrder byteOrder)4782 public void setByteOrder(ByteOrder byteOrder) { 4783 mByteOrder = byteOrder; 4784 } 4785 write(byte[] bytes)4786 public void write(byte[] bytes) throws IOException { 4787 mOutputStream.write(bytes); 4788 } 4789 write(byte[] bytes, int offset, int length)4790 public void write(byte[] bytes, int offset, int length) throws IOException { 4791 mOutputStream.write(bytes, offset, length); 4792 } 4793 writeByte(int val)4794 public void writeByte(int val) throws IOException { 4795 mOutputStream.write(val); 4796 } 4797 writeShort(short val)4798 public void writeShort(short val) throws IOException { 4799 if (mByteOrder == ByteOrder.LITTLE_ENDIAN) { 4800 mOutputStream.write((val >>> 0) & 0xFF); 4801 mOutputStream.write((val >>> 8) & 0xFF); 4802 } else if (mByteOrder == ByteOrder.BIG_ENDIAN) { 4803 mOutputStream.write((val >>> 8) & 0xFF); 4804 mOutputStream.write((val >>> 0) & 0xFF); 4805 } 4806 } 4807 writeInt(int val)4808 public void writeInt(int val) throws IOException { 4809 if (mByteOrder == ByteOrder.LITTLE_ENDIAN) { 4810 mOutputStream.write((val >>> 0) & 0xFF); 4811 mOutputStream.write((val >>> 8) & 0xFF); 4812 mOutputStream.write((val >>> 16) & 0xFF); 4813 mOutputStream.write((val >>> 24) & 0xFF); 4814 } else if (mByteOrder == ByteOrder.BIG_ENDIAN) { 4815 mOutputStream.write((val >>> 24) & 0xFF); 4816 mOutputStream.write((val >>> 16) & 0xFF); 4817 mOutputStream.write((val >>> 8) & 0xFF); 4818 mOutputStream.write((val >>> 0) & 0xFF); 4819 } 4820 } 4821 writeUnsignedShort(int val)4822 public void writeUnsignedShort(int val) throws IOException { 4823 writeShort((short) val); 4824 } 4825 writeUnsignedInt(long val)4826 public void writeUnsignedInt(long val) throws IOException { 4827 writeInt((int) val); 4828 } 4829 } 4830 4831 // Swaps image data based on image size swapBasedOnImageSize(@fdType int firstIfdType, @IfdType int secondIfdType)4832 private void swapBasedOnImageSize(@IfdType int firstIfdType, @IfdType int secondIfdType) 4833 throws IOException { 4834 if (mAttributes[firstIfdType].isEmpty() || mAttributes[secondIfdType].isEmpty()) { 4835 if (DEBUG) { 4836 Log.d(TAG, "Cannot perform swap since only one image data exists"); 4837 } 4838 return; 4839 } 4840 4841 ExifAttribute firstImageLengthAttribute = 4842 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_LENGTH); 4843 ExifAttribute firstImageWidthAttribute = 4844 (ExifAttribute) mAttributes[firstIfdType].get(TAG_IMAGE_WIDTH); 4845 ExifAttribute secondImageLengthAttribute = 4846 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_LENGTH); 4847 ExifAttribute secondImageWidthAttribute = 4848 (ExifAttribute) mAttributes[secondIfdType].get(TAG_IMAGE_WIDTH); 4849 4850 if (firstImageLengthAttribute == null || firstImageWidthAttribute == null) { 4851 if (DEBUG) { 4852 Log.d(TAG, "First image does not contain valid size information"); 4853 } 4854 } else if (secondImageLengthAttribute == null || secondImageWidthAttribute == null) { 4855 if (DEBUG) { 4856 Log.d(TAG, "Second image does not contain valid size information"); 4857 } 4858 } else { 4859 int firstImageLengthValue = firstImageLengthAttribute.getIntValue(mExifByteOrder); 4860 int firstImageWidthValue = firstImageWidthAttribute.getIntValue(mExifByteOrder); 4861 int secondImageLengthValue = secondImageLengthAttribute.getIntValue(mExifByteOrder); 4862 int secondImageWidthValue = secondImageWidthAttribute.getIntValue(mExifByteOrder); 4863 4864 if (firstImageLengthValue < secondImageLengthValue && 4865 firstImageWidthValue < secondImageWidthValue) { 4866 HashMap tempMap = mAttributes[firstIfdType]; 4867 mAttributes[firstIfdType] = mAttributes[secondIfdType]; 4868 mAttributes[secondIfdType] = tempMap; 4869 } 4870 } 4871 } 4872 4873 // Checks if there is a match containsMatch(byte[] mainBytes, byte[] findBytes)4874 private boolean containsMatch(byte[] mainBytes, byte[] findBytes) { 4875 for (int i = 0; i < mainBytes.length - findBytes.length; i++) { 4876 for (int j = 0; j < findBytes.length; j++) { 4877 if (mainBytes[i + j] != findBytes[j]) { 4878 break; 4879 } 4880 if (j == findBytes.length - 1) { 4881 return true; 4882 } 4883 } 4884 } 4885 return false; 4886 } 4887 4888 /** 4889 * Copies the given number of the bytes from {@code in} to {@code out}. Neither stream is 4890 * closed. 4891 */ copy(InputStream in, OutputStream out, int numBytes)4892 private static void copy(InputStream in, OutputStream out, int numBytes) throws IOException { 4893 int remainder = numBytes; 4894 byte[] buffer = new byte[8192]; 4895 while (remainder > 0) { 4896 int bytesToRead = Math.min(remainder, 8192); 4897 int bytesRead = in.read(buffer, 0, bytesToRead); 4898 if (bytesRead != bytesToRead) { 4899 throw new IOException("Failed to copy the given amount of bytes from the input" 4900 + "stream to the output stream."); 4901 } 4902 remainder -= bytesRead; 4903 out.write(buffer, 0, bytesRead); 4904 } 4905 } 4906 4907 /** 4908 * Convert given int[] to long[]. If long[] is given, just return it. 4909 * Return null for other types of input. 4910 */ convertToLongArray(Object inputObj)4911 private static long[] convertToLongArray(Object inputObj) { 4912 if (inputObj instanceof int[]) { 4913 int[] input = (int[]) inputObj; 4914 long[] result = new long[input.length]; 4915 for (int i = 0; i < input.length; i++) { 4916 result[i] = input[i]; 4917 } 4918 return result; 4919 } else if (inputObj instanceof long[]) { 4920 return (long[]) inputObj; 4921 } 4922 return null; 4923 } 4924 byteArrayToHexString(byte[] bytes)4925 private static String byteArrayToHexString(byte[] bytes) { 4926 StringBuilder sb = new StringBuilder(bytes.length * 2); 4927 for (int i = 0; i < bytes.length; i++) { 4928 sb.append(String.format("%02x", bytes[i])); 4929 } 4930 return sb.toString(); 4931 } 4932 } 4933