Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4dbaec62 authored by Jin Seok Park's avatar Jin Seok Park
Browse files

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
parent 3180134f
Loading
Loading
Loading
Loading
+77 −26
Original line number Diff line number Diff line
@@ -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,11 +4637,18 @@ public class ExifInterface {
                    ExifAttribute.createULong(0, mExifByteOrder));
        }
        if (mHasThumbnail) {
            mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
            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(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name,
                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
        // value which has a bigger size than 4 bytes.
@@ -4641,8 +4676,13 @@ public class ExifInterface {
        }
        if (mHasThumbnail) {
            int thumbnailOffset = position;
            mAttributes[IFD_TYPE_THUMBNAIL].put(JPEG_INTERCHANGE_FORMAT_TAG.name,
            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;