diff options
| -rw-r--r-- | media/java/android/media/ExifInterface.java | 159 |
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(); + } } |