summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/ExifInterface.java159
1 files changed, 140 insertions, 19 deletions
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 55583d58e0c2..9963fce6cc83 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -493,6 +493,19 @@ public class ExifInterface {
// See http://www.exiv2.org/makernote.html#R11
private static final int PEF_MAKER_NOTE_SKIP_SIZE = 6;
+ // See PNG (Portable Network Graphics) Specification, Version 1.2,
+ // 3.1. PNG file signature
+ private static final byte[] PNG_SIGNATURE = new byte[] {(byte) 0x89, (byte) 0x50, (byte) 0x4e,
+ (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a};
+ // See PNG (Portable Network Graphics) Specification, Version 1.2,
+ // 3.7. eXIf Exchangeable Image File (Exif) Profile
+ private static final byte[] PNG_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x65, (byte) 0x58,
+ (byte) 0x49, (byte) 0x66};
+ private static final byte[] PNG_CHUNK_TYPE_IEND = new byte[]{(byte) 0x49, (byte) 0x45,
+ (byte) 0x4e, (byte) 0x44};
+ private static final int PNG_CHUNK_LENGTH_BYTE_LENGTH = 4;
+ private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private static SimpleDateFormat sFormatter;
private static SimpleDateFormat sFormatterTz;
@@ -1310,6 +1323,7 @@ public class ExifInterface {
private static final int IMAGE_TYPE_RW2 = 10;
private static final int IMAGE_TYPE_SRW = 11;
private static final int IMAGE_TYPE_HEIF = 12;
+ private static final int IMAGE_TYPE_PNG = 13;
static {
sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
@@ -1779,6 +1793,10 @@ public class ExifInterface {
getRw2Attributes(inputStream);
break;
}
+ case IMAGE_TYPE_PNG: {
+ getPngAttributes(inputStream);
+ break;
+ }
case IMAGE_TYPE_ARW:
case IMAGE_TYPE_CR2:
case IMAGE_TYPE_DNG:
@@ -1992,6 +2010,7 @@ public class ExifInterface {
if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
throw new IOException("Corrupted image");
}
+ // TODO: Need to handle potential OutOfMemoryError
byte[] buffer = new byte[mThumbnailLength];
if (in.read(buffer) != mThumbnailLength) {
throw new IOException("Corrupted image");
@@ -2331,6 +2350,8 @@ public class ExifInterface {
return IMAGE_TYPE_ORF;
} else if (isRw2Format(signatureCheckBytes)) {
return IMAGE_TYPE_RW2;
+ } else if (isPngFormat(signatureCheckBytes)) {
+ return IMAGE_TYPE_PNG;
}
// Certain file formats (PEF) are identified in readImageFileDirectory()
return IMAGE_TYPE_UNKNOWN;
@@ -2446,16 +2467,24 @@ public class ExifInterface {
* http://fileformats.archiveteam.org/wiki/Olympus_ORF
*/
private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException {
- ByteOrderedDataInputStream signatureInputStream =
- new ByteOrderedDataInputStream(signatureCheckBytes);
- // Read byte order
- mExifByteOrder = readByteOrder(signatureInputStream);
- // Set byte order
- signatureInputStream.setByteOrder(mExifByteOrder);
+ ByteOrderedDataInputStream signatureInputStream = null;
- short orfSignature = signatureInputStream.readShort();
- if (orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2) {
- return true;
+ try {
+ signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
+
+ // Read byte order
+ mExifByteOrder = readByteOrder(signatureInputStream);
+ // Set byte order
+ signatureInputStream.setByteOrder(mExifByteOrder);
+
+ short orfSignature = signatureInputStream.readShort();
+ return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2;
+ } catch (Exception e) {
+ // Do nothing
+ } finally {
+ if (signatureInputStream != null) {
+ signatureInputStream.close();
+ }
}
return false;
}
@@ -2465,21 +2494,43 @@ public class ExifInterface {
* See http://lclevy.free.fr/raw/
*/
private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException {
- ByteOrderedDataInputStream signatureInputStream =
- new ByteOrderedDataInputStream(signatureCheckBytes);
- // Read byte order
- mExifByteOrder = readByteOrder(signatureInputStream);
- // Set byte order
- signatureInputStream.setByteOrder(mExifByteOrder);
+ ByteOrderedDataInputStream signatureInputStream = null;
- short signatureByte = signatureInputStream.readShort();
- if (signatureByte == RW2_SIGNATURE) {
- return true;
+ try {
+ signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
+
+ // Read byte order
+ mExifByteOrder = readByteOrder(signatureInputStream);
+ // Set byte order
+ signatureInputStream.setByteOrder(mExifByteOrder);
+
+ short signatureByte = signatureInputStream.readShort();
+ signatureInputStream.close();
+ return signatureByte == RW2_SIGNATURE;
+ } catch (Exception e) {
+ // Do nothing
+ } finally {
+ if (signatureInputStream != null) {
+ signatureInputStream.close();
+ }
}
return false;
}
/**
+ * PNG's file signature is first 8 bytes.
+ * See PNG (Portable Network Graphics) Specification, Version 1.2, 3.1. PNG file signature
+ */
+ private boolean isPngFormat(byte[] signatureCheckBytes) throws IOException {
+ for (int i = 0; i < PNG_SIGNATURE.length; i++) {
+ if (signatureCheckBytes[i] != PNG_SIGNATURE[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* Loads EXIF attributes from a JPEG input stream.
*
* @param in The input stream that starts with the JPEG data.
@@ -2553,7 +2604,7 @@ public class ExifInterface {
readExifSegment(value, imageType);
- // Save offset values for createJpegThumbnailBitmap() function
+ // Save offset values for handleThumbnailFromJfif() function
mExifOffset = (int) offset;
} else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
// See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
@@ -2854,6 +2905,7 @@ public class ExifInterface {
throw new IOException("Invalid identifier");
}
+ // TODO: Need to handle potential OutOfMemoryError
byte[] bytes = new byte[length];
if (in.read(bytes) != length) {
throw new IOException("Can't read exif");
@@ -2980,6 +3032,64 @@ public class ExifInterface {
}
}
+ // PNG contains the EXIF data as a Special-Purpose Chunk
+ private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException {
+ if (DEBUG) {
+ Log.d(TAG, "getPngAttributes starting with: " + in);
+ }
+
+ // PNG uses Big Endian by default.
+ // See PNG (Portable Network Graphics) Specification, Version 1.2,
+ // 2.1. Integers and byte order
+ in.setByteOrder(ByteOrder.BIG_ENDIAN);
+
+ // Skip the signature bytes
+ in.seek(PNG_SIGNATURE.length);
+
+ try {
+ while (true) {
+ // Each chunk is made up of four parts:
+ // 1) Length: 4-byte unsigned integer indicating the number of bytes in the
+ // Chunk Data field. Excludes Chunk Type and CRC bytes.
+ // 2) Chunk Type: 4-byte chunk type code.
+ // 3) Chunk Data: The data bytes. Can be zero-length.
+ // 4) CRC: 4-byte data calculated on the preceding bytes in the chunk. Always
+ // present.
+ // --> 4 (length bytes) + 4 (type bytes) + X (data bytes) + 4 (CRC bytes)
+ // See PNG (Portable Network Graphics) Specification, Version 1.2,
+ // 3.2. Chunk layout
+ int length = in.readInt();
+
+ byte[] type = new byte[PNG_CHUNK_LENGTH_BYTE_LENGTH];
+ if (in.read(type) != type.length) {
+ throw new IOException("Encountered invalid length while parsing PNG chunk"
+ + "type");
+ }
+
+ if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
+ // IEND marks the end of the image.
+ break;
+ } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
+ // TODO: Need to handle potential OutOfMemoryError
+ byte[] data = new byte[length];
+ if (in.read(data) != length) {
+ throw new IOException("Failed to read given length for given PNG chunk "
+ + "type: " + byteArrayToHexString(type));
+ }
+ readExifSegment(data, IFD_TYPE_PRIMARY);
+ break;
+ } else {
+ // Skip to next chunk
+ in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
+ }
+ }
+ } catch (EOFException e) {
+ // Should not reach here. Will only reach here if the file is corrupted or
+ // does not follow the PNG specifications
+ throw new IOException("Encountered corrupt PNG file.");
+ }
+ }
+
// Stores a new JPEG image with EXIF attributes into a given output stream.
private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
throws IOException {
@@ -3485,6 +3595,7 @@ public class ExifInterface {
if (mFilename == null && mAssetInputStream == null
&& mSeekableFileDescriptor == null) {
+ // TODO: Need to handle potential OutOfMemoryError
// Save the thumbnail in memory if the input doesn't support reading again.
byte[] thumbnailBytes = new byte[thumbnailLength];
in.seek(thumbnailOffset);
@@ -3518,6 +3629,7 @@ public class ExifInterface {
return;
}
+ // TODO: Need to handle potential OutOfMemoryError
// Set thumbnail byte array data for non-consecutive strip bytes
byte[] totalStripBytes =
new byte[(int) Arrays.stream(stripByteCounts).sum()];
@@ -3536,6 +3648,7 @@ public class ExifInterface {
in.seek(skipBytes);
bytesRead += skipBytes;
+ // TODO: Need to handle potential OutOfMemoryError
// Read strip bytes
byte[] stripBytes = new byte[stripByteCount];
in.read(stripBytes);
@@ -4335,4 +4448,12 @@ public class ExifInterface {
}
return null;
}
+
+ private static String byteArrayToHexString(byte[] bytes) {
+ StringBuilder sb = new StringBuilder(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ sb.append(String.format("%02x", bytes[i]));
+ }
+ return sb.toString();
+ }
}