Skip to content

Commit 05c9983

Browse files
rharterJohn Rodriguez
authored andcommitted
Implements ImageDecoders to decouple decoding from downloading.
1 parent c57e557 commit 05c9983

27 files changed

Lines changed: 369 additions & 58 deletions

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ buildscript {
1111

1212
ext.deps = [
1313
androidPlugin: 'com.android.tools.build:gradle:3.2.0-rc02',
14+
androidSvg: 'com.caverock:androidsvg:1.2.2-beta-1@aar',
1415
okhttp: "com.squareup.okhttp3:okhttp:${versions.okhttp}",
1516
mockWebServer: "com.squareup.okhttp3:mockwebserver:${versions.okhttp}",
1617
pollexor: 'com.squareup:pollexor:2.0.4',

picasso-sample/src/main/java/com/example/picasso/Data.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ final class Data {
1515
BASE + "Q54zMKT" + EXT, BASE + "9t6hLbm" + EXT, BASE + "F8n3Ic6" + EXT,
1616
BASE + "P5ZRSvT" + EXT, BASE + "jbemFzr" + EXT, BASE + "8B7haIK" + EXT,
1717
BASE + "aSeTYQr" + EXT, BASE + "OKvWoTh" + EXT, BASE + "zD3gT4Z" + EXT,
18-
BASE + "z77CaIt" + EXT,
18+
BASE + "z77CaIt" + EXT, "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg"
1919
};
2020

2121
private Data() {

picasso/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ android {
2828

2929
dependencies {
3030
api deps.okhttp
31+
implementation deps.androidSvg
3132
implementation deps.supportAnnotations
3233
implementation deps.supportCompat
3334
implementation deps.supportExifInterface

picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717

1818
import android.content.Context;
1919
import android.content.res.AssetManager;
20-
import android.graphics.Bitmap;
2120
import android.net.Uri;
2221
import android.support.annotation.NonNull;
2322
import java.io.IOException;
23+
import okio.BufferedSource;
2424
import okio.Okio;
25-
import okio.Source;
2625

2726
import static android.content.ContentResolver.SCHEME_FILE;
2827
import static com.squareup.picasso3.BitmapUtils.decodeStream;
@@ -56,11 +55,17 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca
5655

5756
boolean signaledCallback = false;
5857
try {
59-
Source source = Okio.source(assetManager.open(getFilePath(request)));
58+
BufferedSource source = Okio.buffer(Okio.source(assetManager.open(getFilePath(request))));
6059
try {
61-
Bitmap bitmap = decodeStream(source, request);
60+
ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source);
61+
if (imageDecoder == null) {
62+
callback.onError(
63+
new IllegalStateException("No image decoder for source: " + getFilePath(request)));
64+
return;
65+
}
66+
ImageDecoder.Image image = imageDecoder.decodeImage(source, request);
6267
signaledCallback = true;
63-
callback.onSuccess(new Result(bitmap, DISK));
68+
callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, image.exifOrientation));
6469
} finally {
6570
try {
6671
source.close();
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.squareup.picasso3;
2+
3+
import android.graphics.Bitmap;
4+
import android.graphics.BitmapFactory;
5+
import android.os.Build;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import okio.BufferedSource;
9+
10+
import static com.squareup.picasso3.BitmapUtils.calculateInSampleSize;
11+
import static com.squareup.picasso3.BitmapUtils.createBitmapOptions;
12+
import static com.squareup.picasso3.BitmapUtils.requiresInSampleSize;
13+
14+
public final class BitmapImageDecoder implements ImageDecoder {
15+
16+
@Override public boolean canHandleSource(BufferedSource source) {
17+
try {
18+
if (Utils.isWebPFile(source)) {
19+
return true;
20+
}
21+
22+
InputStream stream = new SourceBufferingInputStream(source);
23+
BitmapFactory.Options options = new BitmapFactory.Options();
24+
options.inJustDecodeBounds = true;
25+
BitmapFactory.decodeStream(stream, null, options);
26+
// we successfully decoded the bounds
27+
return options.outWidth > 0 && options.outHeight > 0;
28+
} catch (IOException e) {
29+
return false;
30+
}
31+
}
32+
33+
/**
34+
* Decode a byte stream into a Bitmap. This method will take into account additional information
35+
* about the supplied request in order to do the decoding efficiently (such as through leveraging
36+
* {@code inSampleSize}).
37+
*/
38+
@Override public Image decodeImage(BufferedSource source, Request request) throws IOException {
39+
boolean isWebPFile = Utils.isWebPFile(source);
40+
boolean isPurgeable = request.purgeable && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
41+
BitmapFactory.Options options = createBitmapOptions(request);
42+
boolean calculateSize = requiresInSampleSize(options);
43+
44+
Bitmap bitmap;
45+
// We decode from a byte array because, a) when decoding a WebP network stream, BitmapFactory
46+
// throws a JNI Exception, so we workaround by decoding a byte array, or b) user requested
47+
// purgeable, which only affects bitmaps decoded from byte arrays.
48+
if (isWebPFile || isPurgeable) {
49+
byte[] bytes = source.readByteArray();
50+
if (calculateSize) {
51+
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
52+
calculateInSampleSize(request.targetWidth, request.targetHeight, options,
53+
request);
54+
}
55+
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
56+
} else {
57+
if (calculateSize) {
58+
InputStream stream = new SourceBufferingInputStream(source);
59+
BitmapFactory.decodeStream(stream, null, options);
60+
calculateInSampleSize(request.targetWidth, request.targetHeight, options,
61+
request);
62+
}
63+
bitmap = BitmapFactory.decodeStream(source.inputStream(), null, options);
64+
}
65+
if (bitmap == null) {
66+
// Treat null as an IO exception, we will eventually retry.
67+
throw new IOException("Failed to decode bitmap.");
68+
}
69+
return new Image(bitmap);
70+
}
71+
}

picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
import android.content.ContentResolver;
1919
import android.content.Context;
2020
import android.content.UriMatcher;
21-
import android.graphics.Bitmap;
2221
import android.net.Uri;
2322
import android.provider.ContactsContract;
2423
import android.support.annotation.NonNull;
2524
import java.io.FileNotFoundException;
2625
import java.io.IOException;
2726
import java.io.InputStream;
27+
import okio.BufferedSource;
2828
import okio.Okio;
2929
import okio.Source;
3030

@@ -78,9 +78,16 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca
7878
try {
7979
Uri requestUri = checkNotNull(request.uri, "request.uri == null");
8080
Source source = getSource(requestUri);
81-
Bitmap bitmap = decodeStream(source, request);
81+
82+
BufferedSource bufferedSource = Okio.buffer(source);
83+
ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(bufferedSource);
84+
if (imageDecoder == null) {
85+
callback.onError(new IllegalStateException("No image decoder for source: " + request));
86+
return;
87+
}
88+
ImageDecoder.Image image = imageDecoder.decodeImage(bufferedSource, request);
8289
signaledCallback = true;
83-
callback.onSuccess(new Result(bitmap, DISK));
90+
callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, image.exifOrientation));
8491
} catch (Exception e) {
8592
if (!signaledCallback) {
8693
callback.onError(e);

picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
import android.content.ContentResolver;
1919
import android.content.Context;
20-
import android.graphics.Bitmap;
2120
import android.net.Uri;
2221
import android.support.annotation.NonNull;
2322
import android.support.media.ExifInterface;
2423
import java.io.FileNotFoundException;
2524
import java.io.IOException;
2625
import java.io.InputStream;
26+
import okio.BufferedSource;
2727
import okio.Okio;
2828
import okio.Source;
2929

@@ -52,10 +52,17 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca
5252
try {
5353
Uri requestUri = checkNotNull(request.uri, "request.uri == null");
5454
Source source = getSource(requestUri);
55-
Bitmap bitmap = decodeStream(source, request);
55+
56+
BufferedSource bufferedSource = Okio.buffer(source);
57+
ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(bufferedSource);
58+
if (imageDecoder == null) {
59+
callback.onError(new IllegalStateException("No image decoder for request: " + request));
60+
return;
61+
}
62+
ImageDecoder.Image image = imageDecoder.decodeImage(bufferedSource, request);
5663
int exifRotation = getExifOrientation(requestUri);
5764
signaledCallback = true;
58-
callback.onSuccess(new Result(bitmap, DISK, exifRotation));
65+
callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifRotation));
5966
} catch (Exception e) {
6067
if (!signaledCallback) {
6168
callback.onError(e);

picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
package com.squareup.picasso3;
1717

1818
import android.content.Context;
19-
import android.graphics.Bitmap;
2019
import android.net.Uri;
2120
import android.support.annotation.NonNull;
2221
import android.support.media.ExifInterface;
2322
import java.io.FileNotFoundException;
2423
import java.io.IOException;
25-
import okio.Source;
24+
import okio.BufferedSource;
25+
import okio.Okio;
2626

2727
import static android.content.ContentResolver.SCHEME_FILE;
2828
import static android.support.media.ExifInterface.ORIENTATION_NORMAL;
@@ -47,11 +47,16 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca
4747
boolean signaledCallback = false;
4848
try {
4949
Uri requestUri = checkNotNull(request.uri, "request.uri == null");
50-
Source source = getSource(requestUri);
51-
Bitmap bitmap = decodeStream(source, request);
50+
BufferedSource source = Okio.buffer(getSource(requestUri));
51+
ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source);
52+
if (imageDecoder == null) {
53+
callback.onError(new IllegalStateException("No image decoder for request: " + request));
54+
return;
55+
}
56+
ImageDecoder.Image image = imageDecoder.decodeImage(source, request);
5257
int exifRotation = getExifOrientation(requestUri);
5358
signaledCallback = true;
54-
callback.onSuccess(new Result(bitmap, DISK, exifRotation));
59+
callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifRotation));
5560
} catch (Exception e) {
5661
if (!signaledCallback) {
5762
callback.onError(e);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.squareup.picasso3;
2+
3+
import android.graphics.Bitmap;
4+
import android.graphics.drawable.Drawable;
5+
import android.support.annotation.NonNull;
6+
import android.support.annotation.Nullable;
7+
import java.io.IOException;
8+
import okio.BufferedSource;
9+
10+
public interface ImageDecoder {
11+
12+
final class Image {
13+
@Nullable public final Bitmap bitmap;
14+
@Nullable public final Drawable drawable;
15+
public final int exifOrientation;
16+
17+
public Image(@NonNull Bitmap bitmap) {
18+
this(bitmap, null, 0);
19+
}
20+
21+
public Image(@NonNull Drawable drawable) {
22+
this(null, drawable, 0);
23+
}
24+
25+
public Image(@Nullable Bitmap bitmap, @Nullable Drawable drawable, int exifOrientation) {
26+
this.bitmap = bitmap;
27+
this.drawable = drawable;
28+
this.exifOrientation = exifOrientation;
29+
}
30+
}
31+
32+
boolean canHandleSource(BufferedSource source);
33+
Image decodeImage(BufferedSource source, Request request) throws IOException;
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.squareup.picasso3;
2+
3+
import android.support.annotation.Nullable;
4+
import java.util.List;
5+
import okio.BufferedSource;
6+
7+
final class ImageDecoderFactory {
8+
9+
private final List<ImageDecoder> decoders;
10+
11+
ImageDecoderFactory(List<ImageDecoder> decoders) {
12+
this.decoders = decoders;
13+
}
14+
15+
/**
16+
* Returns the first {@link ImageDecoder} that can handle the supplied <code>source</code>.
17+
* @param source The source of the image data.
18+
* @return The first ImageDecoder that can decode the source, or null.
19+
*/
20+
@Nullable ImageDecoder getImageDecoderForSource(BufferedSource source) {
21+
for (ImageDecoder decoder : decoders) {
22+
if (decoder.canHandleSource(source)) {
23+
return decoder;
24+
}
25+
}
26+
return null;
27+
}
28+
}

0 commit comments

Comments
 (0)