Add heif format to ExifInterface
Get heif dimension and rotation from MediaMetadataRetriever. Bug: 64077740 Change-Id: I711ac70d356dbfd9128a30ab33c0dfd81a4ae17e
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user