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

Commit 3985765a authored by Chong Zhang's avatar Chong Zhang Committed by android-build-merger
Browse files

Merge "Add heif format to ExifInterface" into oc-mr1-dev

am: e034a1f5

Change-Id: I36fb2559b52bb778acff72954012704da8a85ff9
parents e12e7bc5 e034a1f5
Loading
Loading
Loading
Loading
+179 −1
Original line number Diff line number Diff line
@@ -442,6 +442,10 @@ public class ExifInterface {
    private static final int RAF_INFO_SIZE = 160;
    private static final int RAF_JPEG_LENGTH_VALUE_SIZE = 4;

    private static final byte[] HEIF_TYPE_FTYP = new byte[] {'f', 't', 'y', 'p'};
    private static final byte[] HEIF_BRAND_MIF1 = new byte[] {'m', 'i', 'f', '1'};
    private static final byte[] HEIF_BRAND_HEIC = new byte[] {'h', 'e', 'i', 'c'};

    // See http://fileformats.archiveteam.org/wiki/Olympus_ORF
    private static final short ORF_SIGNATURE_1 = 0x4f52;
    private static final short ORF_SIGNATURE_2 = 0x5352;
@@ -1264,6 +1268,7 @@ public class ExifInterface {
    private static final int IMAGE_TYPE_RAF = 9;
    private static final int IMAGE_TYPE_RW2 = 10;
    private static final int IMAGE_TYPE_SRW = 11;
    private static final int IMAGE_TYPE_HEIF = 12;

    static {
        sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
@@ -1691,6 +1696,10 @@ public class ExifInterface {
                    getRafAttributes(inputStream);
                    break;
                }
                case IMAGE_TYPE_HEIF: {
                    getHeifAttributes(inputStream);
                    break;
                }
                case IMAGE_TYPE_ORF: {
                    getOrfAttributes(inputStream);
                    break;
@@ -2107,6 +2116,8 @@ public class ExifInterface {
            return IMAGE_TYPE_JPEG;
        } else if (isRafFormat(signatureCheckBytes)) {
            return IMAGE_TYPE_RAF;
        } else if (isHeifFormat(signatureCheckBytes)) {
            return IMAGE_TYPE_HEIF;
        } else if (isOrfFormat(signatureCheckBytes)) {
            return IMAGE_TYPE_ORF;
        } else if (isRw2Format(signatureCheckBytes)) {
@@ -2145,6 +2156,78 @@ public class ExifInterface {
        return true;
    }

    private boolean isHeifFormat(byte[] signatureCheckBytes) throws IOException {
        ByteOrderedDataInputStream signatureInputStream = null;
        try {
            signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
            signatureInputStream.setByteOrder(ByteOrder.BIG_ENDIAN);

            long chunkSize = signatureInputStream.readInt();
            byte[] chunkType = new byte[4];
            signatureInputStream.read(chunkType);

            if (!Arrays.equals(chunkType, HEIF_TYPE_FTYP)) {
                return false;
            }

            long chunkDataOffset = 8;
            if (chunkSize == 1) {
                // This indicates that the next 8 bytes represent the chunk size,
                // and chunk data comes after that.
                chunkSize = signatureInputStream.readLong();
                if (chunkSize < 16) {
                    // The smallest valid chunk is 16 bytes long in this case.
                    return false;
                }
                chunkDataOffset += 8;
            }

            // only sniff up to signatureCheckBytes.length
            if (chunkSize > signatureCheckBytes.length) {
                chunkSize = signatureCheckBytes.length;
            }

            long chunkDataSize = chunkSize - chunkDataOffset;

            // It should at least have major brand (4-byte) and minor version (4-byte).
            // The rest of the chunk (if any) is a list of (4-byte) compatible brands.
            if (chunkDataSize < 8) {
                return false;
            }

            byte[] brand = new byte[4];
            boolean isMif1 = false;
            boolean isHeic = false;
            for (long i = 0; i < chunkDataSize / 4;  ++i) {
                if (signatureInputStream.read(brand) != brand.length) {
                    return false;
                }
                if (i == 1) {
                    // Skip this index, it refers to the minorVersion, not a brand.
                    continue;
                }
                if (Arrays.equals(brand, HEIF_BRAND_MIF1)) {
                    isMif1 = true;
                } else if (Arrays.equals(brand, HEIF_BRAND_HEIC)) {
                    isHeic = true;
                }
                if (isMif1 && isHeic) {
                    return true;
                }
            }
        } catch (Exception e) {
            if (DEBUG) {
                Log.d(TAG, "Exception parsing HEIF file type box.", e);
            }
        } finally {
            if (signatureInputStream != null) {
                signatureInputStream.close();
                signatureInputStream = null;
            }
        }
        return false;
    }

    /**
     * ORF has a similar structure to TIFF but it contains a different signature at the TIFF Header.
     * This method looks at the 2 bytes following the Byte Order bytes to determine if this file is
@@ -2437,6 +2520,101 @@ public class ExifInterface {
        }
    }

    private void getHeifAttributes(ByteOrderedDataInputStream in) throws IOException {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        try {
            if (mSeekableFileDescriptor != null) {
                retriever.setDataSource(mSeekableFileDescriptor);
            } else {
                retriever.setDataSource(new MediaDataSource() {
                    long mPosition;

                    @Override
                    public void close() throws IOException {}

                    @Override
                    public int readAt(long position, byte[] buffer, int offset, int size)
                            throws IOException {
                        if (size == 0) {
                            return 0;
                        }
                        if (position < 0) {
                            return -1;
                        }
                        if (mPosition != position) {
                            in.seek(position);
                            mPosition = position;
                        }

                        int bytesRead = in.read(buffer, offset, size);
                        if (bytesRead < 0) {
                            mPosition = -1; // need to seek on next read
                            return -1;
                        }

                        mPosition += bytesRead;
                        return bytesRead;
                    }

                    @Override
                    public long getSize() throws IOException {
                        return -1;
                    }
                });
            }

            String hasVideo = retriever.extractMetadata(
                    MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);

            final String METADATA_HAS_VIDEO_VALUE_YES = "yes";
            if (METADATA_HAS_VIDEO_VALUE_YES.equals(hasVideo)) {
                String width = retriever.extractMetadata(
                        MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
                String height = retriever.extractMetadata(
                        MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);

                if (width != null) {
                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
                            ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
                }

                if (height != null) {
                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
                            ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
                }

                // Note that the rotation angle from MediaMetadataRetriever for heif images
                // are CCW, while rotation in ExifInterface orientations are CW.
                String rotation = retriever.extractMetadata(
                        MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
                if (rotation != null) {
                    int orientation = ExifInterface.ORIENTATION_NORMAL;

                    switch (Integer.parseInt(rotation)) {
                        case 90:
                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
                            break;
                        case 180:
                            orientation = ExifInterface.ORIENTATION_ROTATE_180;
                            break;
                        case 270:
                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
                            break;
                    }

                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
                            ExifAttribute.createUShort(orientation, mExifByteOrder));
                }

                if (DEBUG) {
                    Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
                }
            }
        } finally {
            retriever.release();
        }
    }

    /**
     * ORF files contains a primary image data and a MakerNote data that contains preview/thumbnail
     * images. Both data takes the form of IFDs and can therefore be read with the
@@ -2677,7 +2855,7 @@ public class ExifInterface {
        }
        if (getAttribute(TAG_ORIENTATION) == null) {
            mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
                    ExifAttribute.createULong(0, mExifByteOrder));
                    ExifAttribute.createUShort(0, mExifByteOrder));
        }
        if (getAttribute(TAG_LIGHT_SOURCE) == null) {
            mAttributes[IFD_TYPE_EXIF].put(TAG_LIGHT_SOURCE,