Add support for saving attributes for DNG files

Also add support for IMAGE_TYPE_UNKNOWN files that do not have an
identifiable file extension, but follows the TIFF specification.

Also fixes issue where certain thumbnail-only tags were not being
replaced after primary/preview/thumbnail image data was swapped.

Bug: 193010129, Bug: 193010735
Test: atest ExifInterfaceTest
Change-Id: I098cf1a0730d0cb0f1d6010a21b3ec7ada8dc6b7
This commit is contained in:
Jin Seok Park
2021-07-17 15:51:29 +09:00
parent 3180134fb0
commit 4dbaec627c

View File

@@ -82,7 +82,7 @@ import java.util.zip.CRC32;
* Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF,
* AVIF.
* <p>
* Supported for writing: JPEG, PNG, WebP.
* Supported for writing: JPEG, PNG, WebP, DNG.
* <p>
* Note: JPEG and HEIF files may contain XMP data either inside the Exif data chunk or outside of
* it. This class will search both locations for XMP data, but if XMP data exist both inside and
@@ -1294,6 +1294,7 @@ public class ExifInterface {
new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE),
new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
@@ -1361,12 +1362,6 @@ public class ExifInterface {
new ExifTag(TAG_ORF_IMAGE_PROCESSING_IFD_POINTER, 8256, IFD_FORMAT_BYTE)
};
// Tags for indicating the thumbnail offset and length
private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG);
private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG =
new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG);
// Mappings from tag number to tag name and each item represents one IFD tag group.
private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
// Mappings from tag name to tag number and each item represents one IFD tag group.
@@ -2078,7 +2073,7 @@ public class ExifInterface {
* {@link #setAttribute(String,String)} to set all attributes to write and
* make a single call rather than multiple calls for each attribute.
* <p>
* This method is supported for JPEG, PNG and WebP files.
* This method is supported for JPEG, PNG, WebP, and DNG files.
* <p class="note">
* Note: after calling this method, any attempts to obtain range information
* from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
@@ -2091,13 +2086,17 @@ public class ExifInterface {
*/
public void saveAttributes() throws IOException {
if (!isSupportedFormatForSavingAttributes()) {
throw new IOException("ExifInterface only supports saving attributes on JPEG, PNG, "
+ "or WebP formats.");
throw new IOException("ExifInterface only supports saving attributes for JPEG, PNG, "
+ "WebP, and DNG formats.");
}
if (mIsInputStream || (mSeekableFileDescriptor == null && mFilename == null)) {
throw new IOException(
"ExifInterface does not support saving attributes for the current input.");
}
if (mHasThumbnail && mHasThumbnailStrips && !mAreThumbnailStripsConsecutive) {
throw new IOException("ExifInterface does not support saving attributes when the image "
+ "file has non-consecutive thumbnail strips");
}
// Remember the fact that we've changed the file on disk from what was
// originally parsed, meaning we can't answer range questions
@@ -2146,6 +2145,10 @@ public class ExifInterface {
savePngAttributes(bufferedIn, bufferedOut);
} else if (mMimeType == IMAGE_TYPE_WEBP) {
saveWebpAttributes(bufferedIn, bufferedOut);
} else if (mMimeType == IMAGE_TYPE_DNG || mMimeType == IMAGE_TYPE_UNKNOWN) {
ByteOrderedDataOutputStream dataOutputStream =
new ByteOrderedDataOutputStream(bufferedOut, ByteOrder.BIG_ENDIAN);
writeExifSegment(dataOutputStream);
}
}
} catch (Exception e) {
@@ -2285,9 +2288,9 @@ public class ExifInterface {
}
ExifAttribute imageLengthAttribute =
(ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_LENGTH);
(ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_THUMBNAIL_IMAGE_LENGTH);
ExifAttribute imageWidthAttribute =
(ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_IMAGE_WIDTH);
(ExifAttribute) mAttributes[IFD_TYPE_THUMBNAIL].get(TAG_THUMBNAIL_IMAGE_WIDTH);
if (imageLengthAttribute != null && imageWidthAttribute != null) {
int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder);
int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder);
@@ -2559,7 +2562,12 @@ public class ExifInterface {
mIsInputStream = false;
try {
in = new FileInputStream(filename);
ParcelFileDescriptor modernFd = FileUtils.convertToModernFd(in.getFD());
ParcelFileDescriptor modernFd;
try {
modernFd = FileUtils.convertToModernFd(in.getFD());
} catch (IOException e) {
modernFd = null;
}
if (modernFd != null) {
closeQuietly(in);
in = new FileInputStream(modernFd.getFileDescriptor());
@@ -2927,10 +2935,12 @@ public class ExifInterface {
if (in.skipBytes(1) != 1) {
throw new IOException("Invalid SOFx");
}
mAttributes[imageType].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong(
in.readUnsignedShort(), mExifByteOrder));
mAttributes[imageType].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong(
in.readUnsignedShort(), mExifByteOrder));
mAttributes[imageType].put(imageType != IFD_TYPE_THUMBNAIL
? TAG_IMAGE_LENGTH : TAG_THUMBNAIL_IMAGE_LENGTH,
ExifAttribute.createULong(in.readUnsignedShort(), mExifByteOrder));
mAttributes[imageType].put(imageType != IFD_TYPE_THUMBNAIL
? TAG_IMAGE_WIDTH : TAG_THUMBNAIL_IMAGE_WIDTH,
ExifAttribute.createULong(in.readUnsignedShort(), mExifByteOrder));
length -= 5;
break;
}
@@ -4501,6 +4511,17 @@ public class ExifInterface {
if (!isThumbnail(mAttributes[IFD_TYPE_THUMBNAIL])) {
Log.d(TAG, "No image meets the size requirements of a thumbnail image.");
}
// TAG_THUMBNAIL_* tags should be replaced with TAG_* equivalents and vice versa if needed.
replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_ORIENTATION, TAG_ORIENTATION);
replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_IMAGE_LENGTH, TAG_IMAGE_LENGTH);
replaceInvalidTags(IFD_TYPE_PRIMARY, TAG_THUMBNAIL_IMAGE_WIDTH, TAG_IMAGE_WIDTH);
replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_ORIENTATION, TAG_ORIENTATION);
replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_IMAGE_LENGTH, TAG_IMAGE_LENGTH);
replaceInvalidTags(IFD_TYPE_PREVIEW, TAG_THUMBNAIL_IMAGE_WIDTH, TAG_IMAGE_WIDTH);
replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_ORIENTATION, TAG_THUMBNAIL_ORIENTATION);
replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_IMAGE_LENGTH, TAG_THUMBNAIL_IMAGE_LENGTH);
replaceInvalidTags(IFD_TYPE_THUMBNAIL, TAG_IMAGE_WIDTH, TAG_THUMBNAIL_IMAGE_WIDTH);
}
/**
@@ -4581,8 +4602,15 @@ public class ExifInterface {
removeAttribute(tag.name);
}
// Remove old thumbnail data
removeAttribute(JPEG_INTERCHANGE_FORMAT_TAG.name);
removeAttribute(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name);
if (mHasThumbnail) {
if (mHasThumbnailStrips) {
removeAttribute(TAG_STRIP_OFFSETS);
removeAttribute(TAG_STRIP_BYTE_COUNTS);
} else {
removeAttribute(TAG_JPEG_INTERCHANGE_FORMAT);
removeAttribute(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
}
}
// Remove null value tags.
for (int ifdType = 0; ifdType < EXIF_TAGS.length; ++ifdType) {
@@ -4609,10 +4637,17 @@ public class ExifInterface {
ExifAttribute.createULong(0, mExifByteOrder));
}
if (mHasThumbnail) {
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
ExifAttribute.createULong(0, mExifByteOrder));
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
if (mHasThumbnailStrips) {
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_OFFSETS,
ExifAttribute.createUShort(0, mExifByteOrder));
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_BYTE_COUNTS,
ExifAttribute.createUShort(mThumbnailLength, mExifByteOrder));
} else {
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
ExifAttribute.createULong(0, mExifByteOrder));
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
ExifAttribute.createULong(mThumbnailLength, mExifByteOrder));
}
}
// Calculate IFD group data area sizes. IFD group data area is assigned to save the entry
@@ -4641,8 +4676,13 @@ public class ExifInterface {
}
if (mHasThumbnail) {
int thumbnailOffset = position;
mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
if (mHasThumbnailStrips) {
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_STRIP_OFFSETS,
ExifAttribute.createUShort(thumbnailOffset, mExifByteOrder));
} else {
mAttributes[IFD_TYPE_THUMBNAIL].put(TAG_JPEG_INTERCHANGE_FORMAT,
ExifAttribute.createULong(thumbnailOffset, mExifByteOrder));
}
// Need to add mExifOffset, which is the offset to the EXIF data segment
mThumbnailOffset = thumbnailOffset + mExifOffset;
position += mThumbnailLength;
@@ -5206,9 +5246,20 @@ public class ExifInterface {
}
}
private void replaceInvalidTags(@IfdType int ifdType, String invalidTag, String validTag) {
if (!mAttributes[ifdType].isEmpty()) {
if (mAttributes[ifdType].get(invalidTag) != null) {
mAttributes[ifdType].put(validTag,
mAttributes[ifdType].get(invalidTag));
mAttributes[ifdType].remove(invalidTag);
}
}
}
private boolean isSupportedFormatForSavingAttributes() {
if (mIsSupportedFile && (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_PNG
|| mMimeType == IMAGE_TYPE_WEBP)) {
|| mMimeType == IMAGE_TYPE_WEBP || mMimeType == IMAGE_TYPE_DNG
|| mMimeType == IMAGE_TYPE_UNKNOWN)) {
return true;
}
return false;