Skip to content

Commit 9281d8e

Browse files
sjuddglide-copybara-robot
authored andcommittedNov 20, 2019
Roll forward "Try using ParcelFileDescriptor for local files" with fixes
PiperOrigin-RevId: 281546605
1 parent 7ff5dc3 commit 9281d8e

File tree

7 files changed

+448
-69
lines changed

7 files changed

+448
-69
lines changed
 

‎library/src/main/java/com/bumptech/glide/Glide.java

+25-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.bumptech.glide.load.ImageHeaderParser;
2828
import com.bumptech.glide.load.ResourceDecoder;
2929
import com.bumptech.glide.load.data.InputStreamRewinder;
30+
import com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;
3031
import com.bumptech.glide.load.engine.Engine;
3132
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
3233
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
@@ -63,6 +64,7 @@
6364
import com.bumptech.glide.load.resource.bitmap.Downsampler;
6465
import com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser;
6566
import com.bumptech.glide.load.resource.bitmap.InputStreamBitmapImageDecoderResourceDecoder;
67+
import com.bumptech.glide.load.resource.bitmap.ParcelFileDescriptorBitmapDecoder;
6668
import com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder;
6769
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
6870
import com.bumptech.glide.load.resource.bitmap.UnitBitmapDecoder;
@@ -386,18 +388,17 @@ private static void throwIncorrectGlideModule(Exception e) {
386388
ResourceDecoder<ParcelFileDescriptor, Bitmap> parcelFileDescriptorVideoDecoder =
387389
VideoDecoder.parcel(bitmapPool);
388390

391+
// TODO(judds): Make ParcelFileDescriptorBitmapDecoder work with ImageDecoder.
392+
Downsampler downsampler =
393+
new Downsampler(
394+
registry.getImageHeaderParsers(), resources.getDisplayMetrics(), bitmapPool, arrayPool);
395+
389396
ResourceDecoder<ByteBuffer, Bitmap> byteBufferBitmapDecoder;
390397
ResourceDecoder<InputStream, Bitmap> streamBitmapDecoder;
391398
if (isImageDecoderEnabledForBitmaps && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
392399
streamBitmapDecoder = new InputStreamBitmapImageDecoderResourceDecoder();
393400
byteBufferBitmapDecoder = new ByteBufferBitmapImageDecoderResourceDecoder();
394401
} else {
395-
Downsampler downsampler =
396-
new Downsampler(
397-
registry.getImageHeaderParsers(),
398-
resources.getDisplayMetrics(),
399-
bitmapPool,
400-
arrayPool);
401402
byteBufferBitmapDecoder = new ByteBufferBitmapDecoder(downsampler);
402403
streamBitmapDecoder = new StreamBitmapDecoder(downsampler, arrayPool);
403404
}
@@ -422,7 +423,17 @@ private static void throwIncorrectGlideModule(Exception e) {
422423
.append(InputStream.class, new StreamEncoder(arrayPool))
423424
/* Bitmaps */
424425
.append(Registry.BUCKET_BITMAP, ByteBuffer.class, Bitmap.class, byteBufferBitmapDecoder)
425-
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder)
426+
.append(Registry.BUCKET_BITMAP, InputStream.class, Bitmap.class, streamBitmapDecoder);
427+
428+
if (ParcelFileDescriptorRewinder.isSupported()) {
429+
registry.append(
430+
Registry.BUCKET_BITMAP,
431+
ParcelFileDescriptor.class,
432+
Bitmap.class,
433+
new ParcelFileDescriptorBitmapDecoder(downsampler));
434+
}
435+
436+
registry
426437
.append(
427438
Registry.BUCKET_BITMAP,
428439
ParcelFileDescriptor.class,
@@ -483,7 +494,13 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm
483494
// Compilation with Gradle requires the type to be specified for UnitModelLoader here.
484495
.append(File.class, File.class, UnitModelLoader.Factory.<File>getInstance())
485496
/* Models */
486-
.register(new InputStreamRewinder.Factory(arrayPool))
497+
.register(new InputStreamRewinder.Factory(arrayPool));
498+
499+
if (ParcelFileDescriptorRewinder.isSupported()) {
500+
registry.register(new ParcelFileDescriptorRewinder.Factory());
501+
}
502+
503+
registry
487504
.append(int.class, InputStream.class, resourceLoaderStreamFactory)
488505
.append(int.class, ParcelFileDescriptor.class, resourceLoaderFileDescriptorFactory)
489506
.append(Integer.class, InputStream.class, resourceLoaderStreamFactory)

‎library/src/main/java/com/bumptech/glide/load/ImageHeaderParserUtils.java

+133-24
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.bumptech.glide.load;
22

3+
import android.os.Build;
34
import androidx.annotation.NonNull;
45
import androidx.annotation.Nullable;
6+
import androidx.annotation.RequiresApi;
57
import com.bumptech.glide.load.ImageHeaderParser.ImageType;
8+
import com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;
69
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
710
import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
11+
import java.io.FileInputStream;
812
import java.io.IOException;
913
import java.io.InputStream;
1014
import java.nio.ByteBuffer;
@@ -34,34 +38,83 @@ public static ImageType getType(
3438
}
3539

3640
is.mark(MARK_READ_LIMIT);
37-
//noinspection ForLoopReplaceableByForEach to improve perf
38-
for (int i = 0, size = parsers.size(); i < size; i++) {
39-
ImageHeaderParser parser = parsers.get(i);
40-
try {
41-
ImageType type = parser.getType(is);
42-
if (type != ImageType.UNKNOWN) {
43-
return type;
44-
}
45-
} finally {
46-
is.reset();
47-
}
48-
}
49-
50-
return ImageType.UNKNOWN;
41+
final InputStream finalIs = is;
42+
return getTypeInternal(
43+
parsers,
44+
new TypeReader() {
45+
@Override
46+
public ImageType getType(ImageHeaderParser parser) throws IOException {
47+
try {
48+
return parser.getType(finalIs);
49+
} finally {
50+
finalIs.reset();
51+
}
52+
}
53+
});
5154
}
5255

5356
/** Returns the ImageType for the given ByteBuffer. */
5457
@NonNull
5558
public static ImageType getType(
56-
@NonNull List<ImageHeaderParser> parsers, @Nullable ByteBuffer buffer) throws IOException {
59+
@NonNull List<ImageHeaderParser> parsers, @Nullable final ByteBuffer buffer)
60+
throws IOException {
5761
if (buffer == null) {
5862
return ImageType.UNKNOWN;
5963
}
6064

65+
return getTypeInternal(
66+
parsers,
67+
new TypeReader() {
68+
@Override
69+
public ImageType getType(ImageHeaderParser parser) throws IOException {
70+
return parser.getType(buffer);
71+
}
72+
});
73+
}
74+
75+
@NonNull
76+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
77+
public static ImageType getType(
78+
@NonNull List<ImageHeaderParser> parsers,
79+
@NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,
80+
@NonNull final ArrayPool byteArrayPool)
81+
throws IOException {
82+
return getTypeInternal(
83+
parsers,
84+
new TypeReader() {
85+
@Override
86+
public ImageType getType(ImageHeaderParser parser) throws IOException {
87+
// Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O
88+
// performance
89+
InputStream is = null;
90+
try {
91+
is =
92+
new RecyclableBufferedInputStream(
93+
new FileInputStream(
94+
parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),
95+
byteArrayPool);
96+
return parser.getType(is);
97+
} finally {
98+
try {
99+
if (is != null) {
100+
is.close();
101+
}
102+
} catch (IOException e) {
103+
// Ignored.
104+
}
105+
parcelFileDescriptorRewinder.rewindAndGet();
106+
}
107+
}
108+
});
109+
}
110+
111+
@NonNull
112+
private static ImageType getTypeInternal(
113+
@NonNull List<ImageHeaderParser> parsers, TypeReader reader) throws IOException {
61114
//noinspection ForLoopReplaceableByForEach to improve perf
62115
for (int i = 0, size = parsers.size(); i < size; i++) {
63116
ImageHeaderParser parser = parsers.get(i);
64-
ImageType type = parser.getType(buffer);
117+
ImageType type = reader.getType(parser);
65118
if (type != ImageType.UNKNOWN) {
66119
return type;
67120
}
@@ -74,7 +127,7 @@ public static ImageType getType(
74127
public static int getOrientation(
75128
@NonNull List<ImageHeaderParser> parsers,
76129
@Nullable InputStream is,
77-
@NonNull ArrayPool byteArrayPool)
130+
@NonNull final ArrayPool byteArrayPool)
78131
throws IOException {
79132
if (is == null) {
80133
return ImageHeaderParser.UNKNOWN_ORIENTATION;
@@ -85,19 +138,75 @@ public static int getOrientation(
85138
}
86139

87140
is.mark(MARK_READ_LIMIT);
141+
final InputStream finalIs = is;
142+
return getOrientationInternal(
143+
parsers,
144+
new OrientationReader() {
145+
@Override
146+
public int getOrientation(ImageHeaderParser parser) throws IOException {
147+
try {
148+
return parser.getOrientation(finalIs, byteArrayPool);
149+
} finally {
150+
finalIs.reset();
151+
}
152+
}
153+
});
154+
}
155+
156+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
157+
public static int getOrientation(
158+
@NonNull List<ImageHeaderParser> parsers,
159+
@NonNull final ParcelFileDescriptorRewinder parcelFileDescriptorRewinder,
160+
@NonNull final ArrayPool byteArrayPool)
161+
throws IOException {
162+
return getOrientationInternal(
163+
parsers,
164+
new OrientationReader() {
165+
@Override
166+
public int getOrientation(ImageHeaderParser parser) throws IOException {
167+
// Wrap the FileInputStream into a RecyclableBufferedInputStream to optimize I/O
168+
// performance
169+
InputStream is = null;
170+
try {
171+
is =
172+
new RecyclableBufferedInputStream(
173+
new FileInputStream(
174+
parcelFileDescriptorRewinder.rewindAndGet().getFileDescriptor()),
175+
byteArrayPool);
176+
return parser.getOrientation(is, byteArrayPool);
177+
} finally {
178+
try {
179+
if (is != null) {
180+
is.close();
181+
}
182+
} catch (IOException e) {
183+
// Ignored.
184+
}
185+
parcelFileDescriptorRewinder.rewindAndGet();
186+
}
187+
}
188+
});
189+
}
190+
191+
private static int getOrientationInternal(
192+
@NonNull List<ImageHeaderParser> parsers, OrientationReader reader) throws IOException {
88193
//noinspection ForLoopReplaceableByForEach to improve perf
89194
for (int i = 0, size = parsers.size(); i < size; i++) {
90195
ImageHeaderParser parser = parsers.get(i);
91-
try {
92-
int orientation = parser.getOrientation(is, byteArrayPool);
93-
if (orientation != ImageHeaderParser.UNKNOWN_ORIENTATION) {
94-
return orientation;
95-
}
96-
} finally {
97-
is.reset();
196+
int orientation = reader.getOrientation(parser);
197+
if (orientation != ImageHeaderParser.UNKNOWN_ORIENTATION) {
198+
return orientation;
98199
}
99200
}
100201

101202
return ImageHeaderParser.UNKNOWN_ORIENTATION;
102203
}
204+
205+
private interface TypeReader {
206+
ImageType getType(ImageHeaderParser parser) throws IOException;
207+
}
208+
209+
private interface OrientationReader {
210+
int getOrientation(ImageHeaderParser parser) throws IOException;
211+
}
103212
}

‎library/src/main/java/com/bumptech/glide/load/data/InputStreamRewinder.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public final class InputStreamRewinder implements DataRewinder<InputStream> {
1818
private final RecyclableBufferedInputStream bufferedStream;
1919

2020
@Synthetic
21-
InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {
21+
public InputStreamRewinder(InputStream is, ArrayPool byteArrayPool) {
2222
// We don't check is.markSupported() here because RecyclableBufferedInputStream allows resetting
2323
// after exceeding MARK_READ_LIMIT, which other InputStreams don't guarantee.
2424
bufferedStream = new RecyclableBufferedInputStream(is, byteArrayPool);
@@ -37,6 +37,10 @@ public void cleanup() {
3737
bufferedStream.release();
3838
}
3939

40+
public void fixMarkLimits() {
41+
bufferedStream.fixMarkLimit();
42+
}
43+
4044
/**
4145
* Factory for producing {@link com.bumptech.glide.load.data.InputStreamRewinder}s from {@link
4246
* java.io.InputStream}s.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.bumptech.glide.load.data;
2+
3+
import static android.system.OsConstants.SEEK_SET;
4+
5+
import android.os.Build;
6+
import android.os.ParcelFileDescriptor;
7+
import android.system.ErrnoException;
8+
import android.system.Os;
9+
import androidx.annotation.NonNull;
10+
import androidx.annotation.RequiresApi;
11+
import java.io.IOException;
12+
13+
/**
14+
* Implementation for {@link ParcelFileDescriptor}s that rewinds file descriptors by seeking to 0.
15+
*/
16+
public final class ParcelFileDescriptorRewinder implements DataRewinder<ParcelFileDescriptor> {
17+
18+
private final InternalRewinder rewinder;
19+
20+
public static boolean isSupported() {
21+
// Os.lseek() is only supported on API 21+.
22+
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
23+
}
24+
25+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
26+
public ParcelFileDescriptorRewinder(ParcelFileDescriptor parcelFileDescriptor) {
27+
rewinder = new InternalRewinder(parcelFileDescriptor);
28+
}
29+
30+
@NonNull
31+
@Override
32+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
33+
public ParcelFileDescriptor rewindAndGet() throws IOException {
34+
return rewinder.rewind();
35+
}
36+
37+
@Override
38+
public void cleanup() {
39+
// Do nothing.
40+
}
41+
42+
/**
43+
* Factory for producing {@link ParcelFileDescriptorRewinder}s from {@link ParcelFileDescriptor}s.
44+
*/
45+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
46+
public static final class Factory implements DataRewinder.Factory<ParcelFileDescriptor> {
47+
48+
@NonNull
49+
@Override
50+
public DataRewinder<ParcelFileDescriptor> build(
51+
@NonNull ParcelFileDescriptor parcelFileDescriptor) {
52+
return new ParcelFileDescriptorRewinder(parcelFileDescriptor);
53+
}
54+
55+
@NonNull
56+
@Override
57+
public Class<ParcelFileDescriptor> getDataClass() {
58+
return ParcelFileDescriptor.class;
59+
}
60+
}
61+
62+
/**
63+
* Catching ErrnoException cannot be done in classes that are loaded on APIs < Lollipop. To make
64+
* sure that we do not do so, we catch inside this inner class instead of the outer class. The
65+
* only reason this class exists is to avoid VerifyError on older APIs.
66+
*/
67+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
68+
private static final class InternalRewinder {
69+
private final ParcelFileDescriptor parcelFileDescriptor;
70+
71+
InternalRewinder(ParcelFileDescriptor parcelFileDescriptor) {
72+
this.parcelFileDescriptor = parcelFileDescriptor;
73+
}
74+
75+
ParcelFileDescriptor rewind() throws IOException {
76+
try {
77+
Os.lseek(parcelFileDescriptor.getFileDescriptor(), 0, SEEK_SET);
78+
} catch (ErrnoException e) {
79+
throw new IOException(e);
80+
}
81+
return parcelFileDescriptor;
82+
}
83+
}
84+
}

‎library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java

+56-36
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
import android.graphics.BitmapFactory;
77
import android.graphics.ColorSpace;
88
import android.os.Build;
9+
import android.os.ParcelFileDescriptor;
910
import android.util.DisplayMetrics;
1011
import android.util.Log;
1112
import androidx.annotation.Nullable;
13+
import androidx.annotation.RequiresApi;
1214
import com.bumptech.glide.load.DecodeFormat;
1315
import com.bumptech.glide.load.ImageHeaderParser;
1416
import com.bumptech.glide.load.ImageHeaderParser.ImageType;
15-
import com.bumptech.glide.load.ImageHeaderParserUtils;
1617
import com.bumptech.glide.load.Option;
1718
import com.bumptech.glide.load.Options;
1819
import com.bumptech.glide.load.PreferredColorSpace;
20+
import com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;
1921
import com.bumptech.glide.load.engine.Resource;
2022
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
2123
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
@@ -42,6 +44,7 @@
4244
*/
4345
public final class Downsampler {
4446
static final String TAG = "Downsampler";
47+
4548
/**
4649
* Indicates the {@link com.bumptech.glide.load.DecodeFormat} that will be used in conjunction
4750
* with the image format to determine the {@link android.graphics.Bitmap.Config} to provide to
@@ -130,9 +133,6 @@ public void onDecodeComplete(BitmapPool bitmapPool, Bitmap downsampled) {
130133
ImageHeaderParser.ImageType.PNG_A,
131134
ImageHeaderParser.ImageType.PNG));
132135
private static final Queue<BitmapFactory.Options> OPTIONS_QUEUE = Util.createQueue(0);
133-
// 10MB. This is the max image header size we can handle, we preallocate a much smaller buffer
134-
// but will resize up to this amount if necessary.
135-
private static final int MARK_POSITION = 10 * 1024 * 1024;
136136

137137
private final BitmapPool bitmapPool;
138138
private final DisplayMetrics displayMetrics;
@@ -161,6 +161,10 @@ public boolean handles(@SuppressWarnings("unused") ByteBuffer byteBuffer) {
161161
return true;
162162
}
163163

164+
public boolean handles(@SuppressWarnings("unused") ParcelFileDescriptor source) {
165+
return ParcelFileDescriptorRewinder.isSupported();
166+
}
167+
164168
/**
165169
* Returns a Bitmap decoded from the given {@link InputStream} that is rotated to match any EXIF
166170
* data present in the stream and that is downsampled according to the given dimensions and any
@@ -183,10 +187,6 @@ public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight, Opti
183187
* of the image for the given InputStream is available, the operation is much less expensive in
184188
* terms of memory.
185189
*
186-
* <p>The provided {@link java.io.InputStream} must return <code>true</code> from {@link
187-
* java.io.InputStream#markSupported()} and is expected to support a reasonably large mark limit
188-
* to accommodate reading large image headers (~5MB).
189-
*
190190
* @param is An {@link InputStream} to the data for the image.
191191
* @param requestedWidth The width the final image should be close to.
192192
* @param requestedHeight The height the final image should be close to.
@@ -197,17 +197,41 @@ public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight, Opti
197197
* @return A new bitmap containing the image from the given InputStream, or recycle if recycle is
198198
* not null.
199199
*/
200-
@SuppressWarnings({"resource", "deprecation"})
201200
public Resource<Bitmap> decode(
202201
InputStream is,
203202
int requestedWidth,
204203
int requestedHeight,
205204
Options options,
206205
DecodeCallbacks callbacks)
207206
throws IOException {
208-
Preconditions.checkArgument(
209-
is.markSupported(), "You must provide an InputStream that supports" + " mark()");
207+
return decode(
208+
new ImageReader.InputStreamImageReader(is, parsers, byteArrayPool),
209+
requestedWidth,
210+
requestedHeight,
211+
options,
212+
callbacks);
213+
}
214+
215+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
216+
public Resource<Bitmap> decode(
217+
ParcelFileDescriptor parcelFileDescriptor, int outWidth, int outHeight, Options options)
218+
throws IOException {
219+
return decode(
220+
new ImageReader.ParcelFileDescriptorImageReader(
221+
parcelFileDescriptor, parsers, byteArrayPool),
222+
outWidth,
223+
outHeight,
224+
options,
225+
EMPTY_CALLBACKS);
226+
}
210227

228+
private Resource<Bitmap> decode(
229+
ImageReader imageReader,
230+
int requestedWidth,
231+
int requestedHeight,
232+
Options options,
233+
DecodeCallbacks callbacks)
234+
throws IOException {
211235
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
212236
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
213237
bitmapFactoryOptions.inTempStorage = bytesForOptions;
@@ -222,7 +246,7 @@ public Resource<Bitmap> decode(
222246
try {
223247
Bitmap result =
224248
decodeFromWrappedStreams(
225-
is,
249+
imageReader,
226250
bitmapFactoryOptions,
227251
downsampleStrategy,
228252
decodeFormat,
@@ -240,7 +264,7 @@ public Resource<Bitmap> decode(
240264
}
241265

242266
private Bitmap decodeFromWrappedStreams(
243-
InputStream is,
267+
ImageReader imageReader,
244268
BitmapFactory.Options options,
245269
DownsampleStrategy downsampleStrategy,
246270
DecodeFormat decodeFormat,
@@ -253,7 +277,7 @@ private Bitmap decodeFromWrappedStreams(
253277
throws IOException {
254278
long startTime = LogTime.getLogTime();
255279

256-
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
280+
int[] sourceDimensions = getDimensions(imageReader, options, callbacks, bitmapPool);
257281
int sourceWidth = sourceDimensions[0];
258282
int sourceHeight = sourceDimensions[1];
259283
String sourceMimeType = options.outMimeType;
@@ -266,7 +290,7 @@ private Bitmap decodeFromWrappedStreams(
266290
isHardwareConfigAllowed = false;
267291
}
268292

269-
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
293+
int orientation = imageReader.getImageOrientation();
270294
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
271295
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
272296

@@ -279,11 +303,11 @@ private Bitmap decodeFromWrappedStreams(
279303
? (isRotationRequired(degreesToRotate) ? sourceWidth : sourceHeight)
280304
: requestedHeight;
281305

282-
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
306+
ImageType imageType = imageReader.getImageType();
283307

284308
calculateScaling(
285309
imageType,
286-
is,
310+
imageReader,
287311
callbacks,
288312
bitmapPool,
289313
downsampleStrategy,
@@ -294,7 +318,7 @@ private Bitmap decodeFromWrappedStreams(
294318
targetHeight,
295319
options);
296320
calculateConfig(
297-
is,
321+
imageReader,
298322
decodeFormat,
299323
isHardwareConfigAllowed,
300324
isExifOrientationRequired,
@@ -363,7 +387,7 @@ private Bitmap decodeFromWrappedStreams(
363387
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
364388
}
365389

366-
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
390+
Bitmap downsampled = decodeStream(imageReader, options, callbacks, bitmapPool);
367391
callbacks.onDecodeComplete(bitmapPool, downsampled);
368392

369393
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -395,7 +419,7 @@ private Bitmap decodeFromWrappedStreams(
395419

396420
private static void calculateScaling(
397421
ImageType imageType,
398-
InputStream is,
422+
ImageReader imageReader,
399423
DecodeCallbacks decodeCallbacks,
400424
BitmapPool bitmapPool,
401425
DownsampleStrategy downsampleStrategy,
@@ -524,7 +548,7 @@ private static void calculateScaling(
524548
|| orientedSourceHeight % powerOfTwoSampleSize != 0) {
525549
// If we're not confident the image is in one of our types, fall back to checking the
526550
// dimensions again. inJustDecodeBounds decodes do obey inSampleSize.
527-
int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
551+
int[] dimensions = getDimensions(imageReader, options, decodeCallbacks, bitmapPool);
528552
// Power of two downsampling in BitmapFactory uses a variety of random factors to determine
529553
// rounding that we can't reliably replicate for all image formats. Use ceiling here to make
530554
// sure that we at least provide a Bitmap that's large enough to fit the content we're going
@@ -626,7 +650,7 @@ private boolean shouldUsePool(ImageType imageType) {
626650

627651
@SuppressWarnings("deprecation")
628652
private void calculateConfig(
629-
InputStream is,
653+
ImageReader imageReader,
630654
DecodeFormat format,
631655
boolean isHardwareConfigAllowed,
632656
boolean isExifOrientationRequired,
@@ -652,7 +676,7 @@ private void calculateConfig(
652676

653677
boolean hasAlpha = false;
654678
try {
655-
hasAlpha = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool).hasAlpha();
679+
hasAlpha = imageReader.getImageType().hasAlpha();
656680
} catch (IOException e) {
657681
if (Log.isLoggable(TAG, Log.DEBUG)) {
658682
Log.d(
@@ -674,39 +698,39 @@ private void calculateConfig(
674698
/**
675699
* A method for getting the dimensions of an image from the given InputStream.
676700
*
677-
* @param is The InputStream representing the image.
701+
* @param imageReader The {@link ImageReader} representing the image.
678702
* @param options The options to pass to {@link BitmapFactory#decodeStream(java.io.InputStream,
679703
* android.graphics.Rect, android.graphics.BitmapFactory.Options)}.
680704
* @return an array containing the dimensions of the image in the form {width, height}.
681705
*/
682706
private static int[] getDimensions(
683-
InputStream is,
707+
ImageReader imageReader,
684708
BitmapFactory.Options options,
685709
DecodeCallbacks decodeCallbacks,
686710
BitmapPool bitmapPool)
687711
throws IOException {
688712
options.inJustDecodeBounds = true;
689-
decodeStream(is, options, decodeCallbacks, bitmapPool);
713+
decodeStream(imageReader, options, decodeCallbacks, bitmapPool);
690714
options.inJustDecodeBounds = false;
691715
return new int[] {options.outWidth, options.outHeight};
692716
}
693717

694718
private static Bitmap decodeStream(
695-
InputStream is,
719+
ImageReader imageReader,
696720
BitmapFactory.Options options,
697721
DecodeCallbacks callbacks,
698722
BitmapPool bitmapPool)
699723
throws IOException {
700-
if (options.inJustDecodeBounds) {
701-
is.mark(MARK_POSITION);
702-
} else {
724+
if (!options.inJustDecodeBounds) {
703725
// Once we've read the image header, we no longer need to allow the buffer to expand in
704726
// size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it
705727
// is no larger than our current buffer size here. We need to do so immediately before
706728
// decoding the full image to avoid having our mark limit overridden by other calls to
707729
// mark and reset. See issue #225.
708730
callbacks.onObtainBounds();
731+
imageReader.stopGrowingBuffers();
709732
}
733+
710734
// BitmapFactory.Options out* variables are reset by most calls to decodeStream, successful or
711735
// otherwise, so capture here in case we log below.
712736
int sourceWidth = options.outWidth;
@@ -715,7 +739,7 @@ private static Bitmap decodeStream(
715739
final Bitmap result;
716740
TransformationUtils.getBitmapDrawableLock().lock();
717741
try {
718-
result = BitmapFactory.decodeStream(is, null, options);
742+
result = imageReader.decodeBitmap(options);
719743
} catch (IllegalArgumentException e) {
720744
IOException bitmapAssertionException =
721745
newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
@@ -727,10 +751,9 @@ private static Bitmap decodeStream(
727751
}
728752
if (options.inBitmap != null) {
729753
try {
730-
is.reset();
731754
bitmapPool.put(options.inBitmap);
732755
options.inBitmap = null;
733-
return decodeStream(is, options, callbacks, bitmapPool);
756+
return decodeStream(imageReader, options, callbacks, bitmapPool);
734757
} catch (IOException resetException) {
735758
throw bitmapAssertionException;
736759
}
@@ -740,9 +763,6 @@ private static Bitmap decodeStream(
740763
TransformationUtils.getBitmapDrawableLock().unlock();
741764
}
742765

743-
if (options.inJustDecodeBounds) {
744-
is.reset();
745-
}
746766
return result;
747767
}
748768

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.bumptech.glide.load.resource.bitmap;
2+
3+
import android.graphics.Bitmap;
4+
import android.graphics.BitmapFactory;
5+
import android.os.Build;
6+
import android.os.ParcelFileDescriptor;
7+
import androidx.annotation.Nullable;
8+
import androidx.annotation.RequiresApi;
9+
import com.bumptech.glide.load.ImageHeaderParser;
10+
import com.bumptech.glide.load.ImageHeaderParserUtils;
11+
import com.bumptech.glide.load.data.DataRewinder;
12+
import com.bumptech.glide.load.data.InputStreamRewinder;
13+
import com.bumptech.glide.load.data.ParcelFileDescriptorRewinder;
14+
import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool;
15+
import com.bumptech.glide.util.Preconditions;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.util.List;
19+
20+
/**
21+
* This is a helper class for {@link Downsampler} that abstracts out image operations from the input
22+
* type wrapped into a {@link DataRewinder}.
23+
*/
24+
interface ImageReader {
25+
@Nullable
26+
Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException;
27+
28+
ImageHeaderParser.ImageType getImageType() throws IOException;
29+
30+
int getImageOrientation() throws IOException;
31+
32+
void stopGrowingBuffers();
33+
34+
final class InputStreamImageReader implements ImageReader {
35+
private final InputStreamRewinder dataRewinder;
36+
private final ArrayPool byteArrayPool;
37+
private final List<ImageHeaderParser> parsers;
38+
39+
InputStreamImageReader(
40+
InputStream is, List<ImageHeaderParser> parsers, ArrayPool byteArrayPool) {
41+
this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);
42+
this.parsers = Preconditions.checkNotNull(parsers);
43+
44+
dataRewinder = new InputStreamRewinder(is, byteArrayPool);
45+
}
46+
47+
@Nullable
48+
@Override
49+
public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
50+
return BitmapFactory.decodeStream(dataRewinder.rewindAndGet(), null, options);
51+
}
52+
53+
@Override
54+
public ImageHeaderParser.ImageType getImageType() throws IOException {
55+
return ImageHeaderParserUtils.getType(parsers, dataRewinder.rewindAndGet(), byteArrayPool);
56+
}
57+
58+
@Override
59+
public int getImageOrientation() throws IOException {
60+
return ImageHeaderParserUtils.getOrientation(
61+
parsers, dataRewinder.rewindAndGet(), byteArrayPool);
62+
}
63+
64+
@Override
65+
public void stopGrowingBuffers() {
66+
dataRewinder.fixMarkLimits();
67+
}
68+
}
69+
70+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
71+
final class ParcelFileDescriptorImageReader implements ImageReader {
72+
private final ArrayPool byteArrayPool;
73+
private final List<ImageHeaderParser> parsers;
74+
private final ParcelFileDescriptorRewinder dataRewinder;
75+
76+
ParcelFileDescriptorImageReader(
77+
ParcelFileDescriptor parcelFileDescriptor,
78+
List<ImageHeaderParser> parsers,
79+
ArrayPool byteArrayPool) {
80+
this.byteArrayPool = Preconditions.checkNotNull(byteArrayPool);
81+
this.parsers = Preconditions.checkNotNull(parsers);
82+
83+
dataRewinder = new ParcelFileDescriptorRewinder(parcelFileDescriptor);
84+
}
85+
86+
@Nullable
87+
@Override
88+
public Bitmap decodeBitmap(BitmapFactory.Options options) throws IOException {
89+
return BitmapFactory.decodeFileDescriptor(
90+
dataRewinder.rewindAndGet().getFileDescriptor(), null, options);
91+
}
92+
93+
@Override
94+
public ImageHeaderParser.ImageType getImageType() throws IOException {
95+
return ImageHeaderParserUtils.getType(parsers, dataRewinder, byteArrayPool);
96+
}
97+
98+
@Override
99+
public int getImageOrientation() throws IOException {
100+
return ImageHeaderParserUtils.getOrientation(parsers, dataRewinder, byteArrayPool);
101+
}
102+
103+
@Override
104+
public void stopGrowingBuffers() {
105+
// Nothing to do here.
106+
}
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.bumptech.glide.load.resource.bitmap;
2+
3+
import android.graphics.Bitmap;
4+
import android.os.Build;
5+
import android.os.ParcelFileDescriptor;
6+
import androidx.annotation.NonNull;
7+
import androidx.annotation.Nullable;
8+
import androidx.annotation.RequiresApi;
9+
import com.bumptech.glide.load.Options;
10+
import com.bumptech.glide.load.ResourceDecoder;
11+
import com.bumptech.glide.load.engine.Resource;
12+
import java.io.IOException;
13+
14+
/** Decodes {@link Bitmap}s from {@link ParcelFileDescriptor}s. */
15+
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
16+
public final class ParcelFileDescriptorBitmapDecoder
17+
implements ResourceDecoder<ParcelFileDescriptor, Bitmap> {
18+
19+
private final Downsampler downsampler;
20+
21+
public ParcelFileDescriptorBitmapDecoder(Downsampler downsampler) {
22+
this.downsampler = downsampler;
23+
}
24+
25+
@Override
26+
public boolean handles(@NonNull ParcelFileDescriptor source, @NonNull Options options) {
27+
return downsampler.handles(source);
28+
}
29+
30+
@Nullable
31+
@Override
32+
public Resource<Bitmap> decode(
33+
@NonNull ParcelFileDescriptor source, int width, int height, @NonNull Options options)
34+
throws IOException {
35+
return downsampler.decode(source, width, height, options);
36+
}
37+
}

0 commit comments

Comments
 (0)
Please sign in to comment.