Merge "Add support for parsing Exif chunk in PNG"

This commit is contained in:
Jin Seok Park
2019-09-26 08:17:54 +00:00
committed by Android (Google) Code Review

View File

@@ -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;
@@ -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");
@@ -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:
@@ -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");
@@ -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;
@@ -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);
// 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;
}
@@ -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);
// 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.
*
@@ -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
@@ -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");
@@ -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 {
@@ -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);
@@ -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()];
@@ -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);
@@ -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();
}
}