Add support for parsing Exif chunk in PNG
Bug: 138376314
Test: Cannot find PNG file with Exif chunk but manually checked that
the chunks were being read correctly
Change-Id: Ifa54a4bb9bfde7ea02d82821a5a3b2b4cb5a8f71
This commit is contained in:
@@ -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,20 +2494,42 @@ 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.
|
||||
*
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user