ExifInterface upgrade

This commit is contained in:
Thibault Deckers 2025-01-21 14:31:33 +01:00
parent a0f7af96e0
commit 8e5d971a6f

View file

@ -26,6 +26,7 @@ import static androidx.exifinterface.media.ExifInterfaceUtilsFork.startsWith;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.BIG_ENDIAN;
import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.res.AssetManager; import android.content.res.AssetManager;
@ -54,6 +55,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInput; import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.File; import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
@ -89,7 +91,7 @@ import java.util.regex.Pattern;
import java.util.zip.CRC32; import java.util.zip.CRC32;
/* /*
* Forked from 'androidx.exifinterface:exifinterface:1.4.0-alpha01' on 2024/11/17 * Forked from 'androidx.exifinterface:exifinterface:1.4.0-beta01' on 2025/01/21
* Named differently to let ExifInterface be loaded as subdependency. * Named differently to let ExifInterface be loaded as subdependency.
* cf https://github.com/androidx/androidx/tree/androidx-main/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media * cf https://github.com/androidx/androidx/tree/androidx-main/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media
*/ */
@ -190,6 +192,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #DATA_UNCOMPRESSED * @see #DATA_UNCOMPRESSED
* @see #DATA_JPEG * @see #DATA_JPEG
*/ */
@ -205,6 +208,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #PHOTOMETRIC_INTERPRETATION_RGB * @see #PHOTOMETRIC_INTERPRETATION_RGB
* @see #PHOTOMETRIC_INTERPRETATION_YCBCR * @see #PHOTOMETRIC_INTERPRETATION_YCBCR
*/ */
@ -219,6 +223,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #ORIENTATION_NORMAL}</li> * <li>Default = {@link #ORIENTATION_NORMAL}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #ORIENTATION_UNDEFINED * @see #ORIENTATION_UNDEFINED
* @see #ORIENTATION_NORMAL * @see #ORIENTATION_NORMAL
* @see #ORIENTATION_FLIP_HORIZONTAL * @see #ORIENTATION_FLIP_HORIZONTAL
@ -254,6 +259,7 @@ public class ExifInterfaceFork {
* <li>Count = 1</li> * <li>Count = 1</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #FORMAT_CHUNKY * @see #FORMAT_CHUNKY
* @see #FORMAT_PLANAR * @see #FORMAT_PLANAR
*/ */
@ -294,6 +300,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #Y_CB_CR_POSITIONING_CENTERED}</li> * <li>Default = {@link #Y_CB_CR_POSITIONING_CENTERED}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #Y_CB_CR_POSITIONING_CENTERED * @see #Y_CB_CR_POSITIONING_CENTERED
* @see #Y_CB_CR_POSITIONING_CO_SITED * @see #Y_CB_CR_POSITIONING_CO_SITED
*/ */
@ -309,6 +316,7 @@ public class ExifInterfaceFork {
* <li>Default = 72</li> * <li>Default = 72</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #TAG_Y_RESOLUTION * @see #TAG_Y_RESOLUTION
* @see #TAG_RESOLUTION_UNIT * @see #TAG_RESOLUTION_UNIT
*/ */
@ -324,6 +332,7 @@ public class ExifInterfaceFork {
* <li>Default = 72</li> * <li>Default = 72</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #TAG_X_RESOLUTION * @see #TAG_X_RESOLUTION
* @see #TAG_RESOLUTION_UNIT * @see #TAG_RESOLUTION_UNIT
*/ */
@ -340,6 +349,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #RESOLUTION_UNIT_INCHES}</li> * <li>Default = {@link #RESOLUTION_UNIT_INCHES}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #RESOLUTION_UNIT_INCHES * @see #RESOLUTION_UNIT_INCHES
* @see #RESOLUTION_UNIT_CENTIMETERS * @see #RESOLUTION_UNIT_CENTIMETERS
* @see #TAG_X_RESOLUTION * @see #TAG_X_RESOLUTION
@ -365,6 +375,7 @@ public class ExifInterfaceFork {
* <p>StripsPerImage = floor(({@link #TAG_IMAGE_LENGTH} + {@link #TAG_ROWS_PER_STRIP} - 1) * <p>StripsPerImage = floor(({@link #TAG_IMAGE_LENGTH} + {@link #TAG_ROWS_PER_STRIP} - 1)
* / {@link #TAG_ROWS_PER_STRIP})</p> * / {@link #TAG_ROWS_PER_STRIP})</p>
* <p> * <p>
*
* @see #TAG_ROWS_PER_STRIP * @see #TAG_ROWS_PER_STRIP
* @see #TAG_STRIP_BYTE_COUNTS * @see #TAG_STRIP_BYTE_COUNTS
*/ */
@ -381,6 +392,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #TAG_STRIP_OFFSETS * @see #TAG_STRIP_OFFSETS
* @see #TAG_STRIP_BYTE_COUNTS * @see #TAG_STRIP_BYTE_COUNTS
*/ */
@ -656,6 +668,7 @@ public class ExifInterfaceFork {
* <li>Count = 1</li> * <li>Count = 1</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #COLOR_SPACE_S_RGB * @see #COLOR_SPACE_S_RGB
* @see #COLOR_SPACE_UNCALIBRATED * @see #COLOR_SPACE_UNCALIBRATED
*/ */
@ -962,6 +975,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #EXPOSURE_PROGRAM_NOT_DEFINED}</li> * <li>Default = {@link #EXPOSURE_PROGRAM_NOT_DEFINED}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #EXPOSURE_PROGRAM_NOT_DEFINED * @see #EXPOSURE_PROGRAM_NOT_DEFINED
* @see #EXPOSURE_PROGRAM_MANUAL * @see #EXPOSURE_PROGRAM_MANUAL
* @see #EXPOSURE_PROGRAM_NORMAL * @see #EXPOSURE_PROGRAM_NORMAL
@ -1031,6 +1045,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SENSITIVITY_TYPE_UNKNOWN * @see #SENSITIVITY_TYPE_UNKNOWN
* @see #SENSITIVITY_TYPE_SOS * @see #SENSITIVITY_TYPE_SOS
* @see #SENSITIVITY_TYPE_REI * @see #SENSITIVITY_TYPE_REI
@ -1197,6 +1212,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #METERING_MODE_UNKNOWN}</li> * <li>Default = {@link #METERING_MODE_UNKNOWN}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #METERING_MODE_UNKNOWN * @see #METERING_MODE_UNKNOWN
* @see #METERING_MODE_AVERAGE * @see #METERING_MODE_AVERAGE
* @see #METERING_MODE_CENTER_WEIGHT_AVERAGE * @see #METERING_MODE_CENTER_WEIGHT_AVERAGE
@ -1217,6 +1233,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #LIGHT_SOURCE_UNKNOWN}</li> * <li>Default = {@link #LIGHT_SOURCE_UNKNOWN}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #LIGHT_SOURCE_UNKNOWN * @see #LIGHT_SOURCE_UNKNOWN
* @see #LIGHT_SOURCE_DAYLIGHT * @see #LIGHT_SOURCE_DAYLIGHT
* @see #LIGHT_SOURCE_FLUORESCENT * @see #LIGHT_SOURCE_FLUORESCENT
@ -1253,6 +1270,7 @@ public class ExifInterfaceFork {
* <li>Count = 1</li> * <li>Count = 1</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #FLAG_FLASH_FIRED * @see #FLAG_FLASH_FIRED
* @see #FLAG_FLASH_RETURN_LIGHT_NOT_DETECTED * @see #FLAG_FLASH_RETURN_LIGHT_NOT_DETECTED
* @see #FLAG_FLASH_RETURN_LIGHT_DETECTED * @see #FLAG_FLASH_RETURN_LIGHT_DETECTED
@ -1365,6 +1383,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #RESOLUTION_UNIT_INCHES}</li> * <li>Default = {@link #RESOLUTION_UNIT_INCHES}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #TAG_RESOLUTION_UNIT * @see #TAG_RESOLUTION_UNIT
* @see #RESOLUTION_UNIT_INCHES * @see #RESOLUTION_UNIT_INCHES
* @see #RESOLUTION_UNIT_CENTIMETERS * @see #RESOLUTION_UNIT_CENTIMETERS
@ -1407,6 +1426,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SENSOR_TYPE_NOT_DEFINED * @see #SENSOR_TYPE_NOT_DEFINED
* @see #SENSOR_TYPE_ONE_CHIP * @see #SENSOR_TYPE_ONE_CHIP
* @see #SENSOR_TYPE_TWO_CHIP * @see #SENSOR_TYPE_TWO_CHIP
@ -1427,6 +1447,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #FILE_SOURCE_DSC}</li> * <li>Default = {@link #FILE_SOURCE_DSC}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #FILE_SOURCE_OTHER * @see #FILE_SOURCE_OTHER
* @see #FILE_SOURCE_TRANSPARENT_SCANNER * @see #FILE_SOURCE_TRANSPARENT_SCANNER
* @see #FILE_SOURCE_REFLEX_SCANNER * @see #FILE_SOURCE_REFLEX_SCANNER
@ -1444,6 +1465,7 @@ public class ExifInterfaceFork {
* <li>Default = 1</li> * <li>Default = 1</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SCENE_TYPE_DIRECTLY_PHOTOGRAPHED * @see #SCENE_TYPE_DIRECTLY_PHOTOGRAPHED
*/ */
public static final String TAG_SCENE_TYPE = "SceneType"; public static final String TAG_SCENE_TYPE = "SceneType";
@ -1457,6 +1479,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #TAG_SENSING_METHOD * @see #TAG_SENSING_METHOD
* @see #SENSOR_TYPE_ONE_CHIP * @see #SENSOR_TYPE_ONE_CHIP
*/ */
@ -1473,6 +1496,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #RENDERED_PROCESS_NORMAL}</li> * <li>Default = {@link #RENDERED_PROCESS_NORMAL}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #RENDERED_PROCESS_NORMAL * @see #RENDERED_PROCESS_NORMAL
* @see #RENDERED_PROCESS_CUSTOM * @see #RENDERED_PROCESS_CUSTOM
*/ */
@ -1489,6 +1513,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #EXPOSURE_MODE_AUTO * @see #EXPOSURE_MODE_AUTO
* @see #EXPOSURE_MODE_MANUAL * @see #EXPOSURE_MODE_MANUAL
* @see #EXPOSURE_MODE_AUTO_BRACKET * @see #EXPOSURE_MODE_AUTO_BRACKET
@ -1504,6 +1529,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #WHITEBALANCE_AUTO * @see #WHITEBALANCE_AUTO
* @see #WHITEBALANCE_MANUAL * @see #WHITEBALANCE_MANUAL
*/ */
@ -1553,6 +1579,7 @@ public class ExifInterfaceFork {
* <li>Default = 0</li> * <li>Default = 0</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SCENE_CAPTURE_TYPE_STANDARD * @see #SCENE_CAPTURE_TYPE_STANDARD
* @see #SCENE_CAPTURE_TYPE_LANDSCAPE * @see #SCENE_CAPTURE_TYPE_LANDSCAPE
* @see #SCENE_CAPTURE_TYPE_PORTRAIT * @see #SCENE_CAPTURE_TYPE_PORTRAIT
@ -1569,6 +1596,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GAIN_CONTROL_NONE * @see #GAIN_CONTROL_NONE
* @see #GAIN_CONTROL_LOW_GAIN_UP * @see #GAIN_CONTROL_LOW_GAIN_UP
* @see #GAIN_CONTROL_HIGH_GAIN_UP * @see #GAIN_CONTROL_HIGH_GAIN_UP
@ -1587,6 +1615,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #CONTRAST_NORMAL}</li> * <li>Default = {@link #CONTRAST_NORMAL}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #CONTRAST_NORMAL * @see #CONTRAST_NORMAL
* @see #CONTRAST_SOFT * @see #CONTRAST_SOFT
* @see #CONTRAST_HARD * @see #CONTRAST_HARD
@ -1603,6 +1632,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #SATURATION_NORMAL}</li> * <li>Default = {@link #SATURATION_NORMAL}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SATURATION_NORMAL * @see #SATURATION_NORMAL
* @see #SATURATION_LOW * @see #SATURATION_LOW
* @see #SATURATION_HIGH * @see #SATURATION_HIGH
@ -1619,6 +1649,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #SHARPNESS_NORMAL}</li> * <li>Default = {@link #SHARPNESS_NORMAL}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SHARPNESS_NORMAL * @see #SHARPNESS_NORMAL
* @see #SHARPNESS_SOFT * @see #SHARPNESS_SOFT
* @see #SHARPNESS_HARD * @see #SHARPNESS_HARD
@ -1646,6 +1677,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #SUBJECT_DISTANCE_RANGE_UNKNOWN * @see #SUBJECT_DISTANCE_RANGE_UNKNOWN
* @see #SUBJECT_DISTANCE_RANGE_MACRO * @see #SUBJECT_DISTANCE_RANGE_MACRO
* @see #SUBJECT_DISTANCE_RANGE_CLOSE_VIEW * @see #SUBJECT_DISTANCE_RANGE_CLOSE_VIEW
@ -1675,6 +1707,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @deprecated Use {@link #TAG_CAMERA_OWNER_NAME} instead. * @deprecated Use {@link #TAG_CAMERA_OWNER_NAME} instead.
*/ */
@Deprecated @Deprecated
@ -1780,6 +1813,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #LATITUDE_NORTH * @see #LATITUDE_NORTH
* @see #LATITUDE_SOUTH * @see #LATITUDE_SOUTH
*/ */
@ -1809,6 +1843,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #LONGITUDE_EAST * @see #LONGITUDE_EAST
* @see #LONGITUDE_WEST * @see #LONGITUDE_WEST
*/ */
@ -1841,6 +1876,7 @@ public class ExifInterfaceFork {
* <li>Default = 0</li> * <li>Default = 0</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #ALTITUDE_ABOVE_SEA_LEVEL * @see #ALTITUDE_ABOVE_SEA_LEVEL
* @see #ALTITUDE_BELOW_SEA_LEVEL * @see #ALTITUDE_BELOW_SEA_LEVEL
*/ */
@ -1899,6 +1935,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_MEASUREMENT_IN_PROGRESS * @see #GPS_MEASUREMENT_IN_PROGRESS
* @see #GPS_MEASUREMENT_INTERRUPTED * @see #GPS_MEASUREMENT_INTERRUPTED
*/ */
@ -1915,6 +1952,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_MEASUREMENT_2D * @see #GPS_MEASUREMENT_2D
* @see #GPS_MEASUREMENT_3D * @see #GPS_MEASUREMENT_3D
*/ */
@ -1941,6 +1979,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #GPS_SPEED_KILOMETERS_PER_HOUR}</li> * <li>Default = {@link #GPS_SPEED_KILOMETERS_PER_HOUR}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_SPEED_KILOMETERS_PER_HOUR * @see #GPS_SPEED_KILOMETERS_PER_HOUR
* @see #GPS_SPEED_MILES_PER_HOUR * @see #GPS_SPEED_MILES_PER_HOUR
* @see #GPS_SPEED_KNOTS * @see #GPS_SPEED_KNOTS
@ -1968,6 +2007,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #GPS_DIRECTION_TRUE}</li> * <li>Default = {@link #GPS_DIRECTION_TRUE}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_DIRECTION_TRUE * @see #GPS_DIRECTION_TRUE
* @see #GPS_DIRECTION_MAGNETIC * @see #GPS_DIRECTION_MAGNETIC
*/ */
@ -1994,6 +2034,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #GPS_DIRECTION_TRUE}</li> * <li>Default = {@link #GPS_DIRECTION_TRUE}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_DIRECTION_TRUE * @see #GPS_DIRECTION_TRUE
* @see #GPS_DIRECTION_MAGNETIC * @see #GPS_DIRECTION_MAGNETIC
*/ */
@ -2032,6 +2073,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #LATITUDE_NORTH * @see #LATITUDE_NORTH
* @see #LATITUDE_SOUTH * @see #LATITUDE_SOUTH
*/ */
@ -2061,6 +2103,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #LONGITUDE_EAST * @see #LONGITUDE_EAST
* @see #LONGITUDE_WEST * @see #LONGITUDE_WEST
*/ */
@ -2090,6 +2133,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #GPS_DIRECTION_TRUE}</li> * <li>Default = {@link #GPS_DIRECTION_TRUE}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_DIRECTION_TRUE * @see #GPS_DIRECTION_TRUE
* @see #GPS_DIRECTION_MAGNETIC * @see #GPS_DIRECTION_MAGNETIC
*/ */
@ -2116,6 +2160,7 @@ public class ExifInterfaceFork {
* <li>Default = {@link #GPS_DISTANCE_KILOMETERS}</li> * <li>Default = {@link #GPS_DISTANCE_KILOMETERS}</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_DISTANCE_KILOMETERS * @see #GPS_DISTANCE_KILOMETERS
* @see #GPS_DISTANCE_MILES * @see #GPS_DISTANCE_MILES
* @see #GPS_DISTANCE_NAUTICAL_MILES * @see #GPS_DISTANCE_NAUTICAL_MILES
@ -2177,6 +2222,7 @@ public class ExifInterfaceFork {
* <li>Default = None</li> * <li>Default = None</li>
* </ul> * </ul>
* <p> * <p>
*
* @see #GPS_MEASUREMENT_NO_DIFFERENTIAL * @see #GPS_MEASUREMENT_NO_DIFFERENTIAL
* @see #GPS_MEASUREMENT_DIFFERENTIAL_CORRECTED * @see #GPS_MEASUREMENT_DIFFERENTIAL_CORRECTED
*/ */
@ -3132,11 +3178,18 @@ public class ExifInterfaceFork {
// See "Extensions to the PNG 1.2 Specification, Version 1.5.0", // See "Extensions to the PNG 1.2 Specification, Version 1.5.0",
// 3.7. eXIf Exchangeable Image File (Exif) Profile // 3.7. eXIf Exchangeable Image File (Exif) Profile
private static final int PNG_CHUNK_TYPE_EXIF = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f'; private static final int PNG_CHUNK_TYPE_EXIF = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f';
// See "XMP Specification Part 3: Storage in Files" section 1.1.5
private static final int PNG_CHUNK_TYPE_ITXT = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't';
private static final int PNG_CHUNK_TYPE_IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R'; private static final int PNG_CHUNK_TYPE_IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R';
private static final int PNG_CHUNK_TYPE_IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D'; private static final int PNG_CHUNK_TYPE_IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D';
private static final int PNG_CHUNK_TYPE_BYTE_LENGTH = 4;
private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4; private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
/**
* The keyword and 5 null bytes defined by XMP spec part 3 table 9 (section 1.1.5).
*/
@VisibleForTesting
static final byte[] PNG_ITXT_XMP_KEYWORD = "XML:com.adobe.xmp\0\0\0\0\0".getBytes(UTF_8);
// See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header" // See https://developers.google.com/speed/webp/docs/riff_container, Section "WebP File Header"
private static final byte[] WEBP_SIGNATURE_1 = new byte[]{'R', 'I', 'F', 'F'}; private static final byte[] WEBP_SIGNATURE_1 = new byte[]{'R', 'I', 'F', 'F'};
private static final byte[] WEBP_SIGNATURE_2 = new byte[]{'W', 'E', 'B', 'P'}; private static final byte[] WEBP_SIGNATURE_2 = new byte[]{'W', 'E', 'B', 'P'};
@ -4069,20 +4122,33 @@ public class ExifInterfaceFork {
// Used to indicate offset from the start of the original input stream to EXIF data // Used to indicate offset from the start of the original input stream to EXIF data
private int mOffsetToExifData; private int mOffsetToExifData;
private int mOrfMakerNoteOffset; private int mOrfMakerNoteOffset;
/**
* The position of the thumbnail within the Exif data (from {@link #mOffsetToExifData}).
*/
private int mOrfThumbnailOffset; private int mOrfThumbnailOffset;
private int mOrfThumbnailLength; private int mOrfThumbnailLength;
private boolean mModified; private boolean mModified;
/** /**
* XMP data can occur as either part of the TIFF/Exif data (tag number 700), or as a separate * XMP data can occur as either part of the TIFF/Exif data (tag number 700), or as a separate
* section of the file (e.g. a separate APP1 segment in JPEG). XMP read from within the * section of the file (e.g. a separate APP1 segment in JPEG, or an iTXt chunk in PNG). XMP read
* TIFF/Exif data is stored in {@link #mAttributes}, while XMP read from a separate section is * from within the TIFF/Exif data is stored in {@link #mAttributes}, while XMP read from a
* here. If both are present, the disambiguation rules vary per file format, see * separate section is here. If both are present, the disambiguation rules vary per file format,
* {@link #getXmpHandlingForImageType(int)}. * see {@link #getXmpHandlingForImageType(int)}.
*/ */
@Nullable @Nullable
private ExifAttribute mXmpFromSeparateMarker; private ExifAttribute mXmpFromSeparateMarker;
/**
* True if the file on disk contains XMP in a separate section.
*
* <p>This means the file the instance was loaded with, or the file created by the last call to
* {@link #saveAttributes()}.
*/
private boolean mFileOnDiskContainsSeparateXmpMarker;
// Pattern to check non zero timestamp // Pattern to check non zero timestamp
private static final Pattern NON_ZERO_TIME_PATTERN = Pattern.compile(".*[1-9].*"); private static final Pattern NON_ZERO_TIME_PATTERN = Pattern.compile(".*[1-9].*");
// Pattern to check gps timestamp // Pattern to check gps timestamp
@ -4300,6 +4366,7 @@ public class ExifInterfaceFork {
return XMP_HANDLING_PREFER_TIFF_700_IF_PRESENT; return XMP_HANDLING_PREFER_TIFF_700_IF_PRESENT;
case IMAGE_TYPE_AVIF: case IMAGE_TYPE_AVIF:
case IMAGE_TYPE_HEIC: case IMAGE_TYPE_HEIC:
case IMAGE_TYPE_PNG:
// RAF stores XMP/Exif in JPEG, but we have no documented backwards-compat obligations // RAF stores XMP/Exif in JPEG, but we have no documented backwards-compat obligations
// so we can implement the spec to store XMP in a separate APP1 segment. // so we can implement the spec to store XMP in a separate APP1 segment.
case IMAGE_TYPE_RAF: case IMAGE_TYPE_RAF:
@ -4309,10 +4376,8 @@ public class ExifInterfaceFork {
case IMAGE_TYPE_PEF: case IMAGE_TYPE_PEF:
case IMAGE_TYPE_RW2: case IMAGE_TYPE_RW2:
case IMAGE_TYPE_UNKNOWN: case IMAGE_TYPE_UNKNOWN:
// PNG and WebP support a separate XMP chunk (so should be // WebP supports a separate XMP chunk (so should be XMP_HANDLING_PREFER_SEPARATE), but
// XMP_HANDLING_PREFER_SEPARATE), but ExifInterface doesn't currently read or write // ExifInterface doesn't currently read or write it.
// them.
case IMAGE_TYPE_PNG:
case IMAGE_TYPE_WEBP: case IMAGE_TYPE_WEBP:
default: default:
return XMP_HANDLING_TIFF_700_ONLY; return XMP_HANDLING_TIFF_700_ONLY;
@ -5160,14 +5225,18 @@ public class ExifInterfaceFork {
} }
/** /**
* Returns the offset and length of the requested tag inside the image file, * Returns the offset and length of the requested tag inside the image file, or {@code null} if
* or {@code null} if the tag is not contained. * the tag is not contained.
* *
* @return two-element array, the offset in the first value, and length in * <p>If the attribute has been modified with {@link #setAttribute(String, String)} but not yet
* the second, or {@code null} if no tag was found. * written to disk with {@link #saveAttributes()}, the returned range will have the correct
* @throws IllegalStateException if {@link #saveAttributes()} has been * length for the modified value, but an offset of {@code -1} to indicate its position in the
* called since the underlying file was initially parsed, since * file isn't known.
* that means offsets may have changed. *
* @return two-element array, the offset in the first value, and length in the second, or {@code
* null} if no tag was found.
* @throws IllegalStateException if {@link #saveAttributes()} has been called since the
* underlying file was initially parsed, since that means offsets may have changed.
*/ */
public long @Nullable [] getAttributeRange(@NonNull String tag) { public long @Nullable [] getAttributeRange(@NonNull String tag) {
if (tag == null) { if (tag == null) {
@ -5841,6 +5910,7 @@ public class ExifInterfaceFork {
IDENTIFIER_XMP_APP1.length, bytes.length); IDENTIFIER_XMP_APP1.length, bytes.length);
mXmpFromSeparateMarker = mXmpFromSeparateMarker =
new ExifAttribute(IFD_FORMAT_BYTE, value.length, offset, value); new ExifAttribute(IFD_FORMAT_BYTE, value.length, offset, value);
mFileOnDiskContainsSeparateXmpMarker = true;
} }
break; break;
} }
@ -6165,6 +6235,7 @@ public class ExifInterfaceFork {
in.readFully(xmpBytes); in.readFully(xmpBytes);
mXmpFromSeparateMarker = mXmpFromSeparateMarker =
new ExifAttribute(IFD_FORMAT_BYTE, xmpBytes.length, offset, xmpBytes); new ExifAttribute(IFD_FORMAT_BYTE, xmpBytes.length, offset, xmpBytes);
mFileOnDiskContainsSeparateXmpMarker = true;
} }
if (DEBUG) { if (DEBUG) {
@ -6352,10 +6423,12 @@ public class ExifInterfaceFork {
// See PNG (Portable Network Graphics) Specification, Version 1.2, // See PNG (Portable Network Graphics) Specification, Version 1.2,
// 3.2. Chunk layout // 3.2. Chunk layout
try { try {
while (true) { boolean foundExif = false;
boolean foundXmpItxt = false;
while (!foundExif || !foundXmpItxt) {
int length = in.readInt(); int length = in.readInt();
int type = in.readInt(); int type = in.readInt();
int startOfNextChunk = in.position() + length + PNG_CHUNK_CRC_BYTE_LENGTH;
// The first chunk must be the IHDR chunk // The first chunk must be the IHDR chunk
if (in.position() - startPosition == 16 && type != PNG_CHUNK_TYPE_IHDR) { if (in.position() - startPosition == 16 && type != PNG_CHUNK_TYPE_IHDR) {
@ -6367,7 +6440,7 @@ public class ExifInterfaceFork {
if (type == PNG_CHUNK_TYPE_IEND) { if (type == PNG_CHUNK_TYPE_IEND) {
// IEND marks the end of the image. // IEND marks the end of the image.
break; break;
} else if (type == PNG_CHUNK_TYPE_EXIF) { } else if (type == PNG_CHUNK_TYPE_EXIF && !foundExif) {
// Save offset to EXIF data for handling thumbnail and attribute offsets. // Save offset to EXIF data for handling thumbnail and attribute offsets.
mOffsetToExifData = in.position() - startPosition; mOffsetToExifData = in.position() - startPosition;
@ -6388,20 +6461,40 @@ public class ExifInterfaceFork {
updateCrcWithInt(crc, type); updateCrcWithInt(crc, type);
crc.update(data); crc.update(data);
if ((int) crc.getValue() != dataCrcValue) { if ((int) crc.getValue() != dataCrcValue) {
throw new IOException("Encountered invalid CRC value for PNG-EXIF chunk." throw new IOException(
+ "\n recorded CRC value: " + dataCrcValue + ", calculated CRC " "Encountered invalid CRC value for PNG-EXIF chunk."
+ "value: " + crc.getValue()); + "\n recorded CRC value: "
+ dataCrcValue
+ ", calculated CRC "
+ "value: "
+ crc.getValue());
} }
readExifSegment(data, IFD_TYPE_PRIMARY); readExifSegment(data, IFD_TYPE_PRIMARY);
validateImages(); validateImages();
setThumbnailData(new ByteOrderedDataInputStream(data)); setThumbnailData(new ByteOrderedDataInputStream(data));
break; foundExif = true;
} else { } else if (type == PNG_CHUNK_TYPE_ITXT
&& !foundXmpItxt
&& length >= PNG_ITXT_XMP_KEYWORD.length) {
// Read the 17 byte keyword and 5 expected null bytes.
byte[] keyword = new byte[PNG_ITXT_XMP_KEYWORD.length];
in.readFully(keyword);
if (Arrays.equals(keyword, PNG_ITXT_XMP_KEYWORD)) {
int xmpDataOffset = in.position() - startPosition;
int xmpLength = length - keyword.length;
byte[] xmpData = new byte[xmpLength];
in.readFully(xmpData);
mXmpFromSeparateMarker =
new ExifAttribute(
IFD_FORMAT_BYTE, xmpLength, xmpDataOffset, xmpData);
foundXmpItxt = true;
}
}
// Skip to next chunk // Skip to next chunk
in.skipFully(length + PNG_CHUNK_CRC_BYTE_LENGTH); in.skipFully(startOfNextChunk - in.position());
}
} }
mFileOnDiskContainsSeparateXmpMarker = foundXmpItxt;
} catch (EOFException e) { } catch (EOFException e) {
// Should not reach here. Will only reach here if the file is corrupted or // Should not reach here. Will only reach here if the file is corrupted or
// does not follow the PNG specifications // does not follow the PNG specifications
@ -6464,9 +6557,8 @@ public class ExifInterfaceFork {
// Exif data in WebP images (e.g. // Exif data in WebP images (e.g.
// https://github.com/ImageMagick/ImageMagick/issues/3140) // https://github.com/ImageMagick/ImageMagick/issues/3140)
if (startsWith(payload, IDENTIFIER_EXIF_APP1)) { if (startsWith(payload, IDENTIFIER_EXIF_APP1)) {
int adjustedChunkSize = chunkSize - IDENTIFIER_EXIF_APP1.length;
payload = Arrays.copyOfRange(payload, IDENTIFIER_EXIF_APP1.length, payload = Arrays.copyOfRange(payload, IDENTIFIER_EXIF_APP1.length,
adjustedChunkSize); payload.length);
} }
// Save offset to EXIF data for handling thumbnail and attribute offsets. // Save offset to EXIF data for handling thumbnail and attribute offsets.
@ -6522,7 +6614,7 @@ public class ExifInterfaceFork {
// Write EXIF APP1 segment // Write EXIF APP1 segment
dataOutputStream.writeByte(MARKER); dataOutputStream.writeByte(MARKER);
dataOutputStream.writeByte(MARKER_APP1); dataOutputStream.writeByte(MARKER_APP1);
writeExifSegment(dataOutputStream); mOffsetToExifData = writeExifSegment(dataOutputStream);
if (mXmpFromSeparateMarker != null) { if (mXmpFromSeparateMarker != null) {
// Write XMP APP1 segment. The XMP spec (part 3, section 1.1.3) recommends for this to // Write XMP APP1 segment. The XMP spec (part 3, section 1.1.3) recommends for this to
@ -6533,6 +6625,7 @@ public class ExifInterfaceFork {
dataOutputStream.writeUnsignedShort(length); dataOutputStream.writeUnsignedShort(length);
dataOutputStream.write(IDENTIFIER_XMP_APP1); dataOutputStream.write(IDENTIFIER_XMP_APP1);
dataOutputStream.write(mXmpFromSeparateMarker.bytes); dataOutputStream.write(mXmpFromSeparateMarker.bytes);
mFileOnDiskContainsSeparateXmpMarker = true;
} }
byte[] bytes = new byte[4096]; byte[] bytes = new byte[4096];
@ -6627,60 +6720,76 @@ public class ExifInterfaceFork {
// Copy PNG signature bytes // Copy PNG signature bytes
copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length); copy(dataInputStream, dataOutputStream, PNG_SIGNATURE.length);
// EXIF chunk can appear anywhere between the first (IHDR) and last (IEND) chunks, except boolean needToWriteExif = true;
// between IDAT chunks. boolean needToWriteXmp = mXmpFromSeparateMarker != null;
// Adhering to these rules, while (needToWriteExif || needToWriteXmp) {
// 1) if EXIF chunk did not exist in the original file, it will be stored right after the int chunkLength = dataInputStream.readInt();
// first chunk, int chunkType = dataInputStream.readInt();
// 2) if EXIF chunk existed in the original file, it will be stored in the same location. if (chunkType == PNG_CHUNK_TYPE_IHDR) {
dataOutputStream.writeInt(chunkLength);
dataOutputStream.writeInt(chunkType);
copy(dataInputStream, dataOutputStream, chunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
if (mOffsetToExifData == 0) { if (mOffsetToExifData == 0) {
// Copy IHDR chunk bytes // There was no Exif segment in the original file, so we put it directly
int ihdrChunkLength = dataInputStream.readInt(); // after the IHDR chunk.
dataOutputStream.writeInt(ihdrChunkLength); writePngExifChunk(dataOutputStream);
copy(dataInputStream, dataOutputStream, PNG_CHUNK_TYPE_BYTE_LENGTH needToWriteExif = false;
+ ihdrChunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
} else {
// Copy up until the point where EXIF chunk length information is stored.
int copyLength = mOffsetToExifData - PNG_SIGNATURE.length
- 4 /* PNG EXIF chunk length bytes */
- PNG_CHUNK_TYPE_BYTE_LENGTH;
copy(dataInputStream, dataOutputStream, copyLength);
// Skip to the start of the chunk after the EXIF chunk
int exifChunkLength = dataInputStream.readInt();
dataInputStream.skipFully(PNG_CHUNK_TYPE_BYTE_LENGTH + exifChunkLength
+ PNG_CHUNK_CRC_BYTE_LENGTH);
} }
if (mXmpFromSeparateMarker != null && !mFileOnDiskContainsSeparateXmpMarker) {
// Write EXIF data writePngXmpItxtChunk(dataOutputStream);
ByteArrayOutputStream exifByteArrayOutputStream = null; needToWriteXmp = false;
try { }
// A byte array is needed to calculate the CRC value of this chunk which requires continue;
// the chunk type bytes and the chunk data bytes. } else if (chunkType == PNG_CHUNK_TYPE_EXIF && needToWriteExif) {
exifByteArrayOutputStream = new ByteArrayOutputStream(); writePngExifChunk(dataOutputStream);
ByteOrderedDataOutputStream exifDataOutputStream = dataInputStream.skipFully(chunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
new ByteOrderedDataOutputStream(exifByteArrayOutputStream, BIG_ENDIAN); needToWriteExif = false;
continue;
// Store Exif data in separate byte array } else if (chunkType == PNG_CHUNK_TYPE_ITXT && needToWriteXmp) {
writeExifSegment(exifDataOutputStream); writePngXmpItxtChunk(dataOutputStream);
byte[] exifBytes = dataInputStream.skipFully(chunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
((ByteArrayOutputStream) exifDataOutputStream.mOutputStream).toByteArray(); needToWriteXmp = false;
continue;
// Write EXIF chunk data }
dataOutputStream.write(exifBytes); dataOutputStream.writeInt(chunkLength);
dataOutputStream.writeInt(chunkType);
// Write EXIF chunk CRC copy(dataInputStream, dataOutputStream, chunkLength + PNG_CHUNK_CRC_BYTE_LENGTH);
CRC32 crc = new CRC32();
crc.update(exifBytes, 4 /* skip length bytes */, exifBytes.length - 4);
dataOutputStream.writeInt((int) crc.getValue());
} finally {
closeQuietly(exifByteArrayOutputStream);
} }
// Copy the rest of the file // Copy the rest of the file
copy(dataInputStream, dataOutputStream); copy(dataInputStream, dataOutputStream);
} }
private void writePngExifChunk(ByteOrderedDataOutputStream dataOutputStream)
throws IOException {
// Write the eXIF chunk out to an intermediate byte array so we can calculate the CRC value.
ByteArrayOutputStream exifByteArrayOutputStream = new ByteArrayOutputStream();
// Write eXIF chunk data (including chunk type & length).
int exifOffset =
writeExifSegment(
new ByteOrderedDataOutputStream(exifByteArrayOutputStream, BIG_ENDIAN));
mOffsetToExifData = dataOutputStream.mOutputStream.size() + exifOffset;
byte[] exifBytes = exifByteArrayOutputStream.toByteArray();
dataOutputStream.write(exifBytes);
CRC32 crc = new CRC32();
crc.update(exifBytes, 4 /* skip length bytes */, exifBytes.length - 4);
dataOutputStream.writeInt((int) crc.getValue());
}
private void writePngXmpItxtChunk(ByteOrderedDataOutputStream dataOutputStream)
throws IOException {
dataOutputStream.writeInt(mXmpFromSeparateMarker.bytes.length + 22);
CRC32 crc = new CRC32();
dataOutputStream.writeInt(PNG_CHUNK_TYPE_ITXT);
updateCrcWithInt(crc, PNG_CHUNK_TYPE_ITXT);
dataOutputStream.write(PNG_ITXT_XMP_KEYWORD);
crc.update(PNG_ITXT_XMP_KEYWORD);
dataOutputStream.write(mXmpFromSeparateMarker.bytes);
crc.update(mXmpFromSeparateMarker.bytes);
dataOutputStream.writeInt((int) crc.getValue());
mFileOnDiskContainsSeparateXmpMarker = true;
}
// A WebP file has a header and a series of chunks. // A WebP file has a header and a series of chunks.
// The header is composed of: // The header is composed of:
// "RIFF" + File Size + "WEBP" // "RIFF" + File Size + "WEBP"
@ -6726,11 +6835,12 @@ public class ExifInterfaceFork {
// WebP signature // WebP signature
copy(totalInputStream, totalOutputStream, WEBP_SIGNATURE_1.length); copy(totalInputStream, totalOutputStream, WEBP_SIGNATURE_1.length);
// File length will be written after all the chunks have been written int riffLength = totalInputStream.readInt();
totalInputStream.skipFully(WEBP_FILE_SIZE_BYTE_LENGTH + WEBP_SIGNATURE_2.length); totalInputStream.skipFully(WEBP_SIGNATURE_2.length);
// Create a separate byte array to calculate file length // Create a separate byte array to calculate file length
ByteArrayOutputStream nonHeaderByteArrayOutputStream = null; ByteArrayOutputStream nonHeaderByteArrayOutputStream = null;
int exifOffset = -1;
try { try {
nonHeaderByteArrayOutputStream = new ByteArrayOutputStream(); nonHeaderByteArrayOutputStream = new ByteArrayOutputStream();
ByteOrderedDataOutputStream nonHeaderOutputStream = ByteOrderedDataOutputStream nonHeaderOutputStream =
@ -6756,7 +6866,7 @@ public class ExifInterfaceFork {
totalInputStream.skipFully(exifChunkLength); totalInputStream.skipFully(exifChunkLength);
// Write new EXIF chunk to output stream // Write new EXIF chunk to output stream
writeExifSegment(nonHeaderOutputStream); exifOffset = writeExifSegment(nonHeaderOutputStream);
} else { } else {
// EXIF chunk does not exist in the original file // EXIF chunk does not exist in the original file
byte[] firstChunkType = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH]; byte[] firstChunkType = new byte[WEBP_CHUNK_TYPE_BYTE_LENGTH];
@ -6801,7 +6911,7 @@ public class ExifInterfaceFork {
animationFinished = true; animationFinished = true;
} }
if (animationFinished) { if (animationFinished) {
writeExifSegment(nonHeaderOutputStream); exifOffset = writeExifSegment(nonHeaderOutputStream);
break; break;
} }
copyWebPChunk(totalInputStream, nonHeaderOutputStream, type); copyWebPChunk(totalInputStream, nonHeaderOutputStream, type);
@ -6810,7 +6920,7 @@ public class ExifInterfaceFork {
// Skip until we find the VP8 or VP8L chunk // Skip until we find the VP8 or VP8L chunk
copyChunksUpToGivenChunkType(totalInputStream, nonHeaderOutputStream, copyChunksUpToGivenChunkType(totalInputStream, nonHeaderOutputStream,
WEBP_CHUNK_TYPE_VP8, WEBP_CHUNK_TYPE_VP8L); WEBP_CHUNK_TYPE_VP8, WEBP_CHUNK_TYPE_VP8L);
writeExifSegment(nonHeaderOutputStream); exifOffset = writeExifSegment(nonHeaderOutputStream);
} }
} else if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8) } else if (Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8)
|| Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8L)) { || Arrays.equals(firstChunkType, WEBP_CHUNK_TYPE_VP8L)) {
@ -6897,18 +7007,24 @@ public class ExifInterfaceFork {
copy(totalInputStream, nonHeaderOutputStream, bytesToRead); copy(totalInputStream, nonHeaderOutputStream, bytesToRead);
// Write EXIF chunk // Write EXIF chunk
writeExifSegment(nonHeaderOutputStream); exifOffset = writeExifSegment(nonHeaderOutputStream);
} }
} }
// Copy the rest of the file // Copy the rest of the RIFF part of the file
copy(totalInputStream, nonHeaderOutputStream); int remainingRiffBytes = riffLength + 8 - totalInputStream.position();
copy(totalInputStream, nonHeaderOutputStream, remainingRiffBytes);
// Write file length + second signature // Write file length + second signature
totalOutputStream.writeInt(nonHeaderByteArrayOutputStream.size() totalOutputStream.writeInt(nonHeaderByteArrayOutputStream.size()
+ WEBP_SIGNATURE_2.length); + WEBP_SIGNATURE_2.length);
totalOutputStream.write(WEBP_SIGNATURE_2); totalOutputStream.write(WEBP_SIGNATURE_2);
if (exifOffset != -1) {
mOffsetToExifData = totalOutputStream.mOutputStream.size() + exifOffset;
}
nonHeaderByteArrayOutputStream.writeTo(totalOutputStream); nonHeaderByteArrayOutputStream.writeTo(totalOutputStream);
// Copy any non-RIFF trailing data
copy(totalInputStream, totalOutputStream);
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Failed to save WebP file", e); throw new IOException("Failed to save WebP file", e);
} finally { } finally {
@ -7624,7 +7740,12 @@ public class ExifInterfaceFork {
} }
} }
// Writes an Exif segment into the given output stream. /**
* Writes an Exif segment into the given output stream.
*
* @return The offset of the start of the Exif data (the byte-order marker) written into {@code
* dataOutputStream}.
*/
private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException { private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException {
// The following variables are for calculating each IFD tag group size in bytes. // The following variables are for calculating each IFD tag group size in bytes.
int[] ifdOffsets = new int[EXIF_TAGS.length]; int[] ifdOffsets = new int[EXIF_TAGS.length];
@ -7772,6 +7893,8 @@ public class ExifInterfaceFork {
break; break;
} }
int offsetToExifData = dataOutputStream.mOutputStream.size();
// Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1. // Write TIFF Headers. See JEITA CP-3451C Section 4.5.2. Table 1.
dataOutputStream.writeShort(mExifByteOrder == BIG_ENDIAN ? BYTE_ALIGN_MM : BYTE_ALIGN_II); dataOutputStream.writeShort(mExifByteOrder == BIG_ENDIAN ? BYTE_ALIGN_MM : BYTE_ALIGN_II);
dataOutputStream.setByteOrder(mExifByteOrder); dataOutputStream.setByteOrder(mExifByteOrder);
@ -7844,7 +7967,7 @@ public class ExifInterfaceFork {
// Reset the byte order to big endian in order to write remaining parts of the JPEG file. // Reset the byte order to big endian in order to write remaining parts of the JPEG file.
dataOutputStream.setByteOrder(BIG_ENDIAN); dataOutputStream.setByteOrder(BIG_ENDIAN);
return totalSize; return offsetToExifData;
} }
/** /**
@ -8240,12 +8363,12 @@ public class ExifInterfaceFork {
// An output stream to write EXIF data area, which can be written in either little or big endian // An output stream to write EXIF data area, which can be written in either little or big endian
// order. // order.
private static class ByteOrderedDataOutputStream extends FilterOutputStream { private static class ByteOrderedDataOutputStream extends FilterOutputStream {
final OutputStream mOutputStream; final DataOutputStream mOutputStream;
private ByteOrder mByteOrder; private ByteOrder mByteOrder;
public ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder) { public ByteOrderedDataOutputStream(OutputStream out, ByteOrder byteOrder) {
super(out); super(out);
mOutputStream = out; mOutputStream = new DataOutputStream(out);
mByteOrder = byteOrder; mByteOrder = byteOrder;
} }