Add heif format to ExifInterface

Get heif dimension and rotation from MediaMetadataRetriever.

Bug: 64077740
Change-Id: I711ac70d356dbfd9128a30ab33c0dfd81a4ae17e
This commit is contained in:
Chong Zhang
2017-08-25 16:20:01 -07:00
parent 6a1dad9194
commit 6f80587e8b

View File

@@ -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,