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