Loading media/java/android/media/ExifInterface.java +140 −19 Original line number Diff line number Diff line Loading @@ -494,6 +494,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; Loading Loading @@ -1311,6 +1324,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"); Loading Loading @@ -1811,6 +1825,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: Loading Loading @@ -2024,6 +2042,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"); Loading Loading @@ -2363,6 +2382,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; Loading Loading @@ -2478,16 +2499,24 @@ public class ExifInterface { * http://fileformats.archiveteam.org/wiki/Olympus_ORF */ private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException { ByteOrderedDataInputStream signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); ByteOrderedDataInputStream signatureInputStream = null; try { signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); // Read byte order mExifByteOrder = readByteOrder(signatureInputStream); // Set byte order signatureInputStream.setByteOrder(mExifByteOrder); short orfSignature = signatureInputStream.readShort(); if (orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2) { return true; return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2; } catch (Exception e) { // Do nothing } finally { if (signatureInputStream != null) { signatureInputStream.close(); } } return false; } Loading @@ -2497,20 +2526,42 @@ public class ExifInterface { * See http://lclevy.free.fr/raw/ */ private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException { ByteOrderedDataInputStream signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); ByteOrderedDataInputStream signatureInputStream = null; try { signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); // Read byte order mExifByteOrder = readByteOrder(signatureInputStream); // Set byte order signatureInputStream.setByteOrder(mExifByteOrder); short signatureByte = signatureInputStream.readShort(); if (signatureByte == RW2_SIGNATURE) { return true; 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. * Loading Loading @@ -2585,7 +2636,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 Loading Loading @@ -2886,6 +2937,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"); Loading Loading @@ -3012,6 +3064,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 { Loading Loading @@ -3517,6 +3627,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); Loading Loading @@ -3550,6 +3661,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()]; Loading @@ -3568,6 +3680,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); Loading Loading @@ -4367,4 +4480,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(); } } Loading
media/java/android/media/ExifInterface.java +140 −19 Original line number Diff line number Diff line Loading @@ -494,6 +494,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; Loading Loading @@ -1311,6 +1324,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"); Loading Loading @@ -1811,6 +1825,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: Loading Loading @@ -2024,6 +2042,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"); Loading Loading @@ -2363,6 +2382,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; Loading Loading @@ -2478,16 +2499,24 @@ public class ExifInterface { * http://fileformats.archiveteam.org/wiki/Olympus_ORF */ private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException { ByteOrderedDataInputStream signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); ByteOrderedDataInputStream signatureInputStream = null; try { signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); // Read byte order mExifByteOrder = readByteOrder(signatureInputStream); // Set byte order signatureInputStream.setByteOrder(mExifByteOrder); short orfSignature = signatureInputStream.readShort(); if (orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2) { return true; return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2; } catch (Exception e) { // Do nothing } finally { if (signatureInputStream != null) { signatureInputStream.close(); } } return false; } Loading @@ -2497,20 +2526,42 @@ public class ExifInterface { * See http://lclevy.free.fr/raw/ */ private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException { ByteOrderedDataInputStream signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); ByteOrderedDataInputStream signatureInputStream = null; try { signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes); // Read byte order mExifByteOrder = readByteOrder(signatureInputStream); // Set byte order signatureInputStream.setByteOrder(mExifByteOrder); short signatureByte = signatureInputStream.readShort(); if (signatureByte == RW2_SIGNATURE) { return true; 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. * Loading Loading @@ -2585,7 +2636,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 Loading Loading @@ -2886,6 +2937,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"); Loading Loading @@ -3012,6 +3064,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 { Loading Loading @@ -3517,6 +3627,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); Loading Loading @@ -3550,6 +3661,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()]; Loading @@ -3568,6 +3680,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); Loading Loading @@ -4367,4 +4480,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(); } }