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