diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index d746c850a0181..41e443fff6597 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -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. *
- * Supported for writing: JPEG, PNG, WebP. + * Supported for writing: JPEG, PNG, WebP, DNG. *
* 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. *
- * This method is supported for JPEG, PNG and WebP files. + * This method is supported for JPEG, PNG, WebP, and DNG files. *
* 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;