Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: bumptech/glide
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 72de5e5ada4f5ecac1260ff9e860a2d63d6ed3c2
Choose a base ref
..
head repository: bumptech/glide
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 02c5087e3d18e1df7f521510cea3a177439f5de0
Choose a head ref
Original file line number Diff line number Diff line change
@@ -78,4 +78,17 @@ public boolean isWebp() {

int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)
throws IOException;

/**
* Returns whether the {@link InputStream} has associated multi-picture-format (MPF) data. Only
* JPEGs have MPF data.
*/
boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool) throws IOException;

/**
* Returns whether the {@link ByteBuffer} has associated multi-picture-format (MPF) data. Only
* JPEGs have MPF data.
*/
boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)
throws IOException;
}
Original file line number Diff line number Diff line change
@@ -235,11 +235,130 @@ private static int getOrientationInternal(
return ImageHeaderParser.UNKNOWN_ORIENTATION;
}

/**
* Returns the result from the first of {@code parsers} that returns true when MPF is detected, if
* any..
*
* <p>If {@code buffer} is null, the parers list is empty, or none of the parsers returns a valid
* value, false is returned.
*/
public static boolean hasJpegMpf(
@NonNull List<ImageHeaderParser> parsers,
@Nullable final ByteBuffer buffer,
@NonNull ArrayPool byteArrayPool)
throws IOException {
if (buffer == null) {
return false;
}

return hasJpegMpfInternal(
parsers,
new JpegMpfReader() {
@Override
public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {
try {
return parser.hasJpegMpf(buffer, byteArrayPool);
} finally {
ByteBufferUtil.rewind(buffer);
}
}
});
}

/** Returns whether the given {@link InputStream} references MPF. */
public static boolean hasJpegMpf(
@NonNull List<ImageHeaderParser> parsers,
@Nullable InputStream is,
@NonNull final ArrayPool byteArrayPool)
throws IOException {
if (is == null) {
return false;
}

if (!is.markSupported()) {
is = new RecyclableBufferedInputStream(is, byteArrayPool);
}

is.mark(MARK_READ_LIMIT);
final InputStream finalIs = is;
return hasJpegMpfInternal(
parsers,
new JpegMpfReader() {
@Override
public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {
try {
return parser.hasJpegMpf(finalIs, byteArrayPool);
} finally {
finalIs.reset();
}
}
});
}

/** Returns whether the given {@link ParcelFileDescriptorRewinder} references MPF. */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean hasJpegMpf(
@NonNull List<ImageHeaderParser> parsers,
@NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,
@NonNull final ArrayPool byteArrayPool)
throws IOException {
return hasJpegMpfInternal(
parsers,
new JpegMpfReader() {
@Override
public boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException {
// Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O
// performance
RecyclableBufferedInputStream is = null;
try {
is =
new RecyclableBufferedInputStream(
new FileInputStream(
parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),
byteArrayPool);
return parser.hasJpegMpf(is, byteArrayPool);
} finally {
// If we close the stream, we'll close the file descriptor as well, so we can't do
// that. We do however want to make sure we release any buffers we used back to the
// pool so we call release instead of close.
if (is != null) {
is.release();
}
parcelFileDescriptorRewinder.rewindAndGet();
}
}
});
}

private static boolean hasJpegMpfInternal(
@NonNull List<ImageHeaderParser> parsers, JpegMpfReader reader) throws IOException {
//noinspection ForLoopReplaceableByForEach to improve perf
for (int i = 0, size = parsers.size(); i < size; i++) {
ImageHeaderParser parser = parsers.get(i);
if (reader.getHasJpegMpfAndRewind(parser)) {
return true;
}
}

return false;
}

private interface TypeReader {
ImageType getTypeAndRewind(ImageHeaderParser parser) throws IOException;
}

private interface OrientationReader {
int getOrientationAndRewind(ImageHeaderParser parser) throws IOException;
}

/** Reads JPEG multi-picture format (MPF) data. */
private interface JpegMpfReader {

/**
* Returns whether the image is JPEG and has MPF data.
*
* <p>The parser is guaranteed to be rewound upon termination of the method.
*/
boolean getHasJpegMpfAndRewind(ImageHeaderParser parser) throws IOException;
}
}
Original file line number Diff line number Diff line change
@@ -36,10 +36,14 @@ public final class DefaultImageHeaderParser implements ImageHeaderParser {
private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES =
JPEG_EXIF_SEGMENT_PREAMBLE.getBytes(Charset.forName("UTF-8"));
private static final String JPEG_MPF_SEGMENT_PREAMBLE = "MPF";
static final byte[] JPEG_MPF_SEGMENT_PREAMBLE_BYTES =
JPEG_MPF_SEGMENT_PREAMBLE.getBytes(Charset.forName("UTF-8"));
private static final int SEGMENT_SOS = 0xDA;
private static final int MARKER_EOI = 0xD9;
static final int SEGMENT_START_ID = 0xFF;
static final int EXIF_SEGMENT_TYPE = 0xE1;
static final int APP2_SEGMENT_TYPE = 0xE2;
private static final int ORIENTATION_TAG_TYPE = 0x0112;
private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
// WebP-related
@@ -94,6 +98,49 @@ public int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byt
Preconditions.checkNotNull(byteArrayPool));
}

@Override
public boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)
throws IOException {
return hasJpegMpf(
new StreamReader(Preconditions.checkNotNull(is)),
Preconditions.checkNotNull(byteArrayPool));
}

@Override
public boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)
throws IOException {
return hasJpegMpf(
new ByteBufferReader(Preconditions.checkNotNull(byteBuffer)),
Preconditions.checkNotNull(byteArrayPool));
}

private boolean hasJpegMpf(@NonNull Reader reader, @NonNull ArrayPool byteArrayPool)
throws IOException {
if (getType(reader) != JPEG) {
return false;
}
int app2SegmentLength = moveToApp2SegmentAndGetLength(reader);
while (app2SegmentLength > 0) {
byte[] app2Data = byteArrayPool.get(app2SegmentLength, byte[].class);
try {
boolean hasJpegMpfPreamble = hasJpegMpfPreamble(reader, app2Data, app2SegmentLength);
if (hasJpegMpfPreamble) {
return true;
}
} finally {
byteArrayPool.put(app2Data);
}
app2SegmentLength = moveToApp2SegmentAndGetLength(reader);
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(
TAG,
"hasMpf: Failed to parse APP2 segment length, or no APP2 segment with MPF metadata not"
+ " found");
}
return false;
}

@NonNull
private ImageType getType(Reader reader) throws IOException {
try {
@@ -283,11 +330,14 @@ private int parseExifSegment(Reader reader, byte[] tempArray, int exifSegmentLen
}

private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) {
boolean result =
exifData != null && exifSegmentLength > JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
return hasMatchingBytes(exifData, exifSegmentLength, JPEG_EXIF_SEGMENT_PREAMBLE_BYTES);
}

private boolean hasMatchingBytes(byte[] bytes, int byteLength, byte[] bytesToMatch) {
boolean result = bytes != null && bytesToMatch != null && byteLength > bytesToMatch.length;
if (result) {
for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
for (int i = 0; i < bytesToMatch.length; i++) {
if (bytes[i] != bytesToMatch[i]) {
result = false;
break;
}
@@ -301,6 +351,37 @@ private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) {
* {@code -1} if no exif segment is found.
*/
private int moveToExifSegmentAndGetLength(Reader reader) throws IOException {
return moveToSegmentAndGetLength(reader, EXIF_SEGMENT_TYPE);
}

private boolean hasJpegMpfPreamble(Reader reader, byte[] tempArray, int mpfSegmentLength)
throws IOException {
int read = reader.read(tempArray, mpfSegmentLength);
if (read != mpfSegmentLength) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(
TAG,
"Unable to read MPF segment data"
+ ", length: "
+ mpfSegmentLength
+ ", actually read: "
+ read);
}
return false;
}
return hasMatchingBytes(tempArray, mpfSegmentLength, JPEG_MPF_SEGMENT_PREAMBLE_BYTES);
}

private int moveToApp2SegmentAndGetLength(Reader reader) throws IOException {
return moveToSegmentAndGetLength(reader, APP2_SEGMENT_TYPE);
}

/**
* Moves reader to the start of the segment identified by the segment type (e.g., "0xE1" for APP1
* and returns the length of the exif segment or {@code -1} if no segment of that type is found.
*/
private int moveToSegmentAndGetLength(Reader reader, int requestedSegmentType)
throws IOException {
while (true) {
short segmentId = reader.getUInt8();
if (segmentId != SEGMENT_START_ID) {
@@ -315,15 +396,15 @@ private int moveToExifSegmentAndGetLength(Reader reader) throws IOException {
return -1;
} else if (segmentType == MARKER_EOI) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Found MARKER_EOI in exif segment");
Log.d(TAG, "Found MARKER_EOI in " + requestedSegmentType + " segment");
}
return -1;
}

int segmentLength = reader.getUInt16();
// A segment includes the bytes that specify its length.
int segmentContentsLength = segmentLength - 2;
if (segmentType != EXIF_SEGMENT_TYPE) {
if (segmentType != requestedSegmentType) {
long skipped = reader.skip(segmentContentsLength);
if (skipped != segmentContentsLength) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Original file line number Diff line number Diff line change
@@ -52,4 +52,16 @@ public int getOrientation(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byt
throws IOException {
return getOrientation(ByteBufferUtil.toStream(byteBuffer), byteArrayPool);
}

@Override
public boolean hasJpegMpf(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool)
throws IOException {
return false;
}

@Override
public boolean hasJpegMpf(@NonNull ByteBuffer byteBuffer, @NonNull ArrayPool byteArrayPool)
throws IOException {
return false;
}
}
Loading