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