[DO NOT MERGE] Return correct offsets for getAttributeRange()
Cherry-picked from ag/9745767 Currently, the offset value returned from getAttributeRange() does not take into account the offset to the EXIF data within the image file. This CL fixes the code to return the correct offsets. The existing test code could not detect this issue because it does not explicitly seek to the given offset and retrieve/compare the data. This CL also adds test code to do this. Make tag information is also added because all test files have this information, while only a select few have thumbnail/GPS tag information. This CL also fixes a bug where the thumbnail offset was not being updated if the data was passed with StripOffsets tag. Bug: 143118731, Bug: 143663411 Test: atest ExifInterfaceTest Change-Id: Ia147cdcacf4699c5e7efc43f52e58b945032a51f
This commit is contained in:
@@ -1345,7 +1345,9 @@ public class ExifInterface {
|
|||||||
private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
|
private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN;
|
||||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
||||||
private boolean mHasThumbnail;
|
private boolean mHasThumbnail;
|
||||||
// The following values used for indicating a thumbnail position.
|
private boolean mHasThumbnailStrips;
|
||||||
|
private boolean mAreThumbnailStripsConsecutive;
|
||||||
|
// Used to indicate the position of the thumbnail (includes offset to EXIF data segment).
|
||||||
private int mThumbnailOffset;
|
private int mThumbnailOffset;
|
||||||
private int mThumbnailLength;
|
private int mThumbnailLength;
|
||||||
private byte[] mThumbnailBytes;
|
private byte[] mThumbnailBytes;
|
||||||
@@ -2043,10 +2045,12 @@ public class ExifInterface {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset and length of thumbnail inside the image file, or
|
* Returns the offset and length of thumbnail inside the image file, or
|
||||||
* {@code null} if there is no thumbnail.
|
* {@code null} if either there is no thumbnail or the thumbnail bytes are stored
|
||||||
|
* non-consecutively.
|
||||||
*
|
*
|
||||||
* @return two-element array, the offset in the first value, and length in
|
* @return two-element array, the offset in the first value, and length in
|
||||||
* the second, or {@code null} if no thumbnail was found.
|
* the second, or {@code null} if no thumbnail was found or the thumbnail strips are
|
||||||
|
* not placed consecutively.
|
||||||
* @throws IllegalStateException if {@link #saveAttributes()} has been
|
* @throws IllegalStateException if {@link #saveAttributes()} has been
|
||||||
* called since the underlying file was initially parsed, since
|
* called since the underlying file was initially parsed, since
|
||||||
* that means offsets may have changed.
|
* that means offsets may have changed.
|
||||||
@@ -2058,10 +2062,12 @@ public class ExifInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mHasThumbnail) {
|
if (mHasThumbnail) {
|
||||||
|
if (mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return new long[] { mThumbnailOffset, mThumbnailLength };
|
return new long[] { mThumbnailOffset, mThumbnailLength };
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2536,10 +2542,9 @@ public class ExifInterface {
|
|||||||
final byte[] value = Arrays.copyOfRange(bytes,
|
final byte[] value = Arrays.copyOfRange(bytes,
|
||||||
IDENTIFIER_EXIF_APP1.length, bytes.length);
|
IDENTIFIER_EXIF_APP1.length, bytes.length);
|
||||||
|
|
||||||
readExifSegment(value, imageType);
|
|
||||||
|
|
||||||
// Save offset values for createJpegThumbnailBitmap() function
|
// Save offset values for createJpegThumbnailBitmap() function
|
||||||
mExifOffset = (int) offset;
|
mExifOffset = (int) offset;
|
||||||
|
readExifSegment(value, imageType);
|
||||||
} else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
|
} else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
|
||||||
// See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
|
// See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
|
||||||
final long offset = start + IDENTIFIER_XMP_APP1.length;
|
final long offset = start + IDENTIFIER_XMP_APP1.length;
|
||||||
@@ -2843,6 +2848,8 @@ public class ExifInterface {
|
|||||||
if (in.read(bytes) != length) {
|
if (in.read(bytes) != length) {
|
||||||
throw new IOException("Can't read exif");
|
throw new IOException("Can't read exif");
|
||||||
}
|
}
|
||||||
|
// Save offset values for handling thumbnail and attribute offsets.
|
||||||
|
mExifOffset = offset;
|
||||||
readExifSegment(bytes, IFD_TYPE_PRIMARY);
|
readExifSegment(bytes, IFD_TYPE_PRIMARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2988,7 +2995,7 @@ public class ExifInterface {
|
|||||||
// Write EXIF APP1 segment
|
// Write EXIF APP1 segment
|
||||||
dataOutputStream.writeByte(MARKER);
|
dataOutputStream.writeByte(MARKER);
|
||||||
dataOutputStream.writeByte(MARKER_APP1);
|
dataOutputStream.writeByte(MARKER_APP1);
|
||||||
writeExifSegment(dataOutputStream, 6);
|
writeExifSegment(dataOutputStream);
|
||||||
|
|
||||||
byte[] bytes = new byte[4096];
|
byte[] bytes = new byte[4096];
|
||||||
|
|
||||||
@@ -3319,7 +3326,7 @@ public class ExifInterface {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int bytesOffset = dataInputStream.peek();
|
final int bytesOffset = dataInputStream.peek() + mExifOffset;
|
||||||
final byte[] bytes = new byte[(int) byteCount];
|
final byte[] bytes = new byte[(int) byteCount];
|
||||||
dataInputStream.readFully(bytes);
|
dataInputStream.readFully(bytes);
|
||||||
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
|
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
|
||||||
@@ -3451,31 +3458,28 @@ public class ExifInterface {
|
|||||||
|
|
||||||
// The following code limits the size of thumbnail size not to overflow EXIF data area.
|
// The following code limits the size of thumbnail size not to overflow EXIF data area.
|
||||||
thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset);
|
thumbnailLength = Math.min(thumbnailLength, in.getLength() - thumbnailOffset);
|
||||||
if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF
|
if (mMimeType == IMAGE_TYPE_ORF) {
|
||||||
|| mMimeType == IMAGE_TYPE_RW2) {
|
|
||||||
thumbnailOffset += mExifOffset;
|
|
||||||
} else if (mMimeType == IMAGE_TYPE_ORF) {
|
|
||||||
// Update offset value since RAF files have IFD data preceding MakerNote data.
|
// Update offset value since RAF files have IFD data preceding MakerNote data.
|
||||||
thumbnailOffset += mOrfMakerNoteOffset;
|
thumbnailOffset += mOrfMakerNoteOffset;
|
||||||
}
|
}
|
||||||
if (DEBUG) {
|
|
||||||
Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
|
|
||||||
+ ", length: " + thumbnailLength);
|
|
||||||
}
|
|
||||||
if (thumbnailOffset > 0 && thumbnailLength > 0) {
|
if (thumbnailOffset > 0 && thumbnailLength > 0) {
|
||||||
mHasThumbnail = true;
|
mHasThumbnail = true;
|
||||||
mThumbnailOffset = thumbnailOffset;
|
mThumbnailOffset = thumbnailOffset + mExifOffset;
|
||||||
mThumbnailLength = thumbnailLength;
|
mThumbnailLength = thumbnailLength;
|
||||||
mThumbnailCompression = DATA_JPEG;
|
mThumbnailCompression = DATA_JPEG;
|
||||||
|
|
||||||
if (mFilename == null && mAssetInputStream == null
|
if (mFilename == null && mAssetInputStream == null
|
||||||
&& mSeekableFileDescriptor == null) {
|
&& mSeekableFileDescriptor == null) {
|
||||||
// Save the thumbnail in memory if the input doesn't support reading again.
|
// Save the thumbnail in memory if the input doesn't support reading again.
|
||||||
byte[] thumbnailBytes = new byte[thumbnailLength];
|
byte[] thumbnailBytes = new byte[mThumbnailLength];
|
||||||
in.seek(thumbnailOffset);
|
in.seek(mThumbnailOffset);
|
||||||
in.readFully(thumbnailBytes);
|
in.readFully(thumbnailBytes);
|
||||||
mThumbnailBytes = thumbnailBytes;
|
mThumbnailBytes = thumbnailBytes;
|
||||||
}
|
}
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset
|
||||||
|
+ ", length: " + thumbnailLength);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3494,12 +3498,16 @@ public class ExifInterface {
|
|||||||
long[] stripByteCounts =
|
long[] stripByteCounts =
|
||||||
convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder));
|
convertToLongArray(stripByteCountsAttribute.getValue(mExifByteOrder));
|
||||||
|
|
||||||
if (stripOffsets == null) {
|
if (stripOffsets == null || stripOffsets.length == 0) {
|
||||||
Log.w(TAG, "stripOffsets should not be null.");
|
Log.w(TAG, "stripOffsets should not be null or have zero length.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stripByteCounts == null) {
|
if (stripByteCounts == null || stripByteCounts.length == 0) {
|
||||||
Log.w(TAG, "stripByteCounts should not be null.");
|
Log.w(TAG, "stripByteCounts should not be null or have zero length.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stripOffsets.length != stripByteCounts.length) {
|
||||||
|
Log.w(TAG, "stripOffsets and stripByteCounts should have same length.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3509,10 +3517,18 @@ public class ExifInterface {
|
|||||||
|
|
||||||
int bytesRead = 0;
|
int bytesRead = 0;
|
||||||
int bytesAdded = 0;
|
int bytesAdded = 0;
|
||||||
|
mHasThumbnail = mHasThumbnailStrips = mAreThumbnailStripsConsecutive = true;
|
||||||
for (int i = 0; i < stripOffsets.length; i++) {
|
for (int i = 0; i < stripOffsets.length; i++) {
|
||||||
int stripOffset = (int) stripOffsets[i];
|
int stripOffset = (int) stripOffsets[i];
|
||||||
int stripByteCount = (int) stripByteCounts[i];
|
int stripByteCount = (int) stripByteCounts[i];
|
||||||
|
|
||||||
|
// Check if strips are consecutive
|
||||||
|
// TODO: Add test for non-consecutive thumbnail image
|
||||||
|
if (i < stripOffsets.length - 1
|
||||||
|
&& stripOffset + stripByteCount != stripOffsets[i + 1]) {
|
||||||
|
mAreThumbnailStripsConsecutive = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip to offset
|
// Skip to offset
|
||||||
int skipBytes = stripOffset - bytesRead;
|
int skipBytes = stripOffset - bytesRead;
|
||||||
if (skipBytes < 0) {
|
if (skipBytes < 0) {
|
||||||
@@ -3531,10 +3547,13 @@ public class ExifInterface {
|
|||||||
stripBytes.length);
|
stripBytes.length);
|
||||||
bytesAdded += stripBytes.length;
|
bytesAdded += stripBytes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
mHasThumbnail = true;
|
|
||||||
mThumbnailBytes = totalStripBytes;
|
mThumbnailBytes = totalStripBytes;
|
||||||
mThumbnailLength = totalStripBytes.length;
|
|
||||||
|
if (mAreThumbnailStripsConsecutive) {
|
||||||
|
// Need to add mExifOffset, which is the offset to the EXIF data segment
|
||||||
|
mThumbnailOffset = (int) stripOffsets[0] + mExifOffset;
|
||||||
|
mThumbnailLength = totalStripBytes.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3691,8 +3710,7 @@ public class ExifInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Writes an Exif segment into the given output stream.
|
// Writes an Exif segment into the given output stream.
|
||||||
private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream,
|
private int writeExifSegment(ByteOrderedDataOutputStream dataOutputStream) throws IOException {
|
||||||
int exifOffsetFromBeginning) 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];
|
||||||
int[] ifdDataSizes = new int[EXIF_TAGS.length];
|
int[] ifdDataSizes = new int[EXIF_TAGS.length];
|
||||||
@@ -3751,6 +3769,8 @@ public class ExifInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate IFD offsets.
|
// Calculate IFD offsets.
|
||||||
|
// 8 bytes are for TIFF headers: 2 bytes (byte order) + 2 bytes (identifier) + 4 bytes
|
||||||
|
// (offset of IFDs)
|
||||||
int position = 8;
|
int position = 8;
|
||||||
for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
|
for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
|
||||||
if (!mAttributes[ifdType].isEmpty()) {
|
if (!mAttributes[ifdType].isEmpty()) {
|
||||||
@@ -3762,7 +3782,8 @@ public class ExifInterface {
|
|||||||
int thumbnailOffset = position;
|
int thumbnailOffset = position;
|
||||||
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
|
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
|
||||||
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
|
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
|
||||||
mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset;
|
// Need to add mExifOffset, which is the offset to the EXIF data segment
|
||||||
|
mThumbnailOffset = thumbnailOffset + mExifOffset;
|
||||||
position += mThumbnailLength;
|
position += mThumbnailLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user