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