From 53948f0186e52a9ac3ea90e42d562627b2a26f48 Mon Sep 17 00:00:00 2001 From: John Rodriguez Date: Sun, 26 Aug 2018 01:28:14 -0400 Subject: [PATCH 01/16] Add Picasso.newBuilder() --- .../java/com/squareup/picasso3/Picasso.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index a916842333..57414a0ed8 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -670,11 +670,30 @@ public static class Builder { private boolean indicatorsEnabled; private boolean loggingEnabled; + private boolean isChild; /** Start building a new {@link Picasso} instance. */ public Builder(@NonNull Context context) { checkNotNull(context, "context == null"); this.context = context.getApplicationContext(); + + isChild = false; + } + + Builder(Picasso picasso) { + context = picasso.context; + callFactory = picasso.callFactory; + service = picasso.dispatcher.service; + cache = picasso.cache; + listener = picasso.listener; + requestTransformers.addAll(picasso.requestTransformers); + requestHandlers.addAll(picasso.requestHandlers); + defaultBitmapConfig = picasso.defaultBitmapConfig; + + indicatorsEnabled = picasso.indicatorsEnabled; + loggingEnabled = picasso.loggingEnabled; + + isChild = true; } Builder(Picasso picasso) { @@ -737,6 +756,9 @@ public Builder callFactory(@NonNull Call.Factory factory) { @NonNull public Builder executor(@NonNull ExecutorService executorService) { checkNotNull(executorService, "executorService == null"); + if (!isChild && this.service != null) { + throw new IllegalStateException("Executor service already set."); + } this.service = executorService; return this; } @@ -770,6 +792,9 @@ public Builder withCacheSize(int maxByteCount) { @NonNull public Builder listener(@NonNull Listener listener) { checkNotNull(listener, "listener == null"); + if (!isChild && this.listener != null) { + throw new IllegalStateException("Listener already set."); + } this.listener = listener; return this; } @@ -778,6 +803,9 @@ public Builder listener(@NonNull Listener listener) { @NonNull public Builder addRequestTransformer(@NonNull RequestTransformer transformer) { checkNotNull(transformer, "transformer == null"); + if (!isChild && requestTransformers.contains(transformer)) { + throw new IllegalStateException("Transformer already set."); + } requestTransformers.add(transformer); return this; } @@ -786,6 +814,9 @@ public Builder addRequestTransformer(@NonNull RequestTransformer transformer) { @NonNull public Builder addRequestHandler(@NonNull RequestHandler requestHandler) { checkNotNull(requestHandler, "requestHandler == null"); + if (!isChild && requestHandlers.contains(requestHandler)) { + throw new IllegalStateException("RequestHandler already registered."); + } requestHandlers.add(requestHandler); return this; } From 4d686a21418e3a57d6e09026b18c8a8b6a2a7ada Mon Sep 17 00:00:00 2001 From: John Rodriguez Date: Sun, 26 Aug 2018 12:10:16 -0400 Subject: [PATCH 02/16] Address feedback --- .../java/com/squareup/picasso3/Picasso.java | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index 57414a0ed8..8923e87b63 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -138,6 +138,7 @@ public enum Priority { final Stats stats; final Map targetToAction; final Map targetToDeferredRequestCreator; + final List extraRequestHandlers; @Nullable final Bitmap.Config defaultBitmapConfig; boolean indicatorsEnabled; @@ -157,6 +158,7 @@ public enum Priority { this.cache = cache; this.listener = listener; this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers)); + this.extraRequestHandlers = Collections.unmodifiableList(new ArrayList<>(extraRequestHandlers)); this.defaultBitmapConfig = defaultBitmapConfig; // Adjust this and Builder(Picasso) as internal handlers are added or removed. @@ -676,8 +678,6 @@ public static class Builder { public Builder(@NonNull Context context) { checkNotNull(context, "context == null"); this.context = context.getApplicationContext(); - - isChild = false; } Builder(Picasso picasso) { @@ -687,13 +687,11 @@ public Builder(@NonNull Context context) { cache = picasso.cache; listener = picasso.listener; requestTransformers.addAll(picasso.requestTransformers); - requestHandlers.addAll(picasso.requestHandlers); + requestHandlers.addAll(picasso.extraRequestHandlers); defaultBitmapConfig = picasso.defaultBitmapConfig; indicatorsEnabled = picasso.indicatorsEnabled; loggingEnabled = picasso.loggingEnabled; - - isChild = true; } Builder(Picasso picasso) { @@ -756,9 +754,6 @@ public Builder callFactory(@NonNull Call.Factory factory) { @NonNull public Builder executor(@NonNull ExecutorService executorService) { checkNotNull(executorService, "executorService == null"); - if (!isChild && this.service != null) { - throw new IllegalStateException("Executor service already set."); - } this.service = executorService; return this; } @@ -792,9 +787,6 @@ public Builder withCacheSize(int maxByteCount) { @NonNull public Builder listener(@NonNull Listener listener) { checkNotNull(listener, "listener == null"); - if (!isChild && this.listener != null) { - throw new IllegalStateException("Listener already set."); - } this.listener = listener; return this; } @@ -803,9 +795,6 @@ public Builder listener(@NonNull Listener listener) { @NonNull public Builder addRequestTransformer(@NonNull RequestTransformer transformer) { checkNotNull(transformer, "transformer == null"); - if (!isChild && requestTransformers.contains(transformer)) { - throw new IllegalStateException("Transformer already set."); - } requestTransformers.add(transformer); return this; } @@ -814,9 +803,6 @@ public Builder addRequestTransformer(@NonNull RequestTransformer transformer) { @NonNull public Builder addRequestHandler(@NonNull RequestHandler requestHandler) { checkNotNull(requestHandler, "requestHandler == null"); - if (!isChild && requestHandlers.contains(requestHandler)) { - throw new IllegalStateException("RequestHandler already registered."); - } requestHandlers.add(requestHandler); return this; } From a448949449542e869f2d9d6e3596569dbd6e5925 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Thu, 19 Apr 2018 09:09:11 -0500 Subject: [PATCH 03/16] Implements ImageDecoders to decouple decoding from downloading. --- build.gradle | 1 + .../main/java/com/example/picasso/Data.java | 2 +- picasso/build.gradle | 1 + .../picasso3/AssetRequestHandler.java | 15 ++-- .../squareup/picasso3/BitmapImageDecoder.java | 71 +++++++++++++++++++ .../picasso3/ContactsPhotoRequestHandler.java | 13 +++- .../picasso3/ContentStreamRequestHandler.java | 13 +++- .../squareup/picasso3/FileRequestHandler.java | 15 ++-- .../com/squareup/picasso3/ImageDecoder.java | 34 +++++++++ .../picasso3/ImageDecoderFactory.java | 28 ++++++++ .../picasso3/MediaStoreRequestHandler.java | 26 ++++--- .../picasso3/NetworkRequestHandler.java | 12 +++- .../java/com/squareup/picasso3/Picasso.java | 26 ++++++- .../java/com/squareup/picasso3/Request.java | 28 +++++++- .../com/squareup/picasso3/RequestCreator.java | 27 ++++++- .../com/squareup/picasso3/RequestHandler.java | 2 +- .../ResourceDrawableRequestHandler.java | 1 + .../squareup/picasso3/SvgImageDecoder.java | 48 +++++++++++++ .../squareup/picasso3/BitmapHunterTest.java | 6 +- .../picasso3/BitmapTargetActionTest.java | 6 +- .../picasso3/ImageViewActionTest.java | 3 +- .../MediaStoreRequestHandlerTest.java | 5 +- .../com/squareup/picasso3/PicassoTest.java | 12 ++-- .../picasso3/RemoteViewsActionTest.java | 6 +- .../squareup/picasso3/RequestCreatorTest.java | 17 ++--- .../java/com/squareup/picasso3/TestUtils.java | 5 +- 26 files changed, 365 insertions(+), 58 deletions(-) create mode 100644 picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java create mode 100644 picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java create mode 100644 picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java create mode 100644 picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java diff --git a/build.gradle b/build.gradle index 8ba06304a6..eba86035e4 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { ext.deps = [ androidPlugin: 'com.android.tools.build:gradle:3.2.1', + androidSvg: 'com.caverock:androidsvg:1.2.2-beta-1@aar', okhttp: "com.squareup.okhttp3:okhttp:${versions.okhttp}", okio: "com.squareup.okio:okio:${versions.okio}", mockWebServer: "com.squareup.okhttp3:mockwebserver:${versions.okhttp}", diff --git a/picasso-sample/src/main/java/com/example/picasso/Data.java b/picasso-sample/src/main/java/com/example/picasso/Data.java index 7eab87b3fc..90f6398404 100644 --- a/picasso-sample/src/main/java/com/example/picasso/Data.java +++ b/picasso-sample/src/main/java/com/example/picasso/Data.java @@ -15,7 +15,7 @@ final class Data { BASE + "Q54zMKT" + EXT, BASE + "9t6hLbm" + EXT, BASE + "F8n3Ic6" + EXT, BASE + "P5ZRSvT" + EXT, BASE + "jbemFzr" + EXT, BASE + "8B7haIK" + EXT, BASE + "aSeTYQr" + EXT, BASE + "OKvWoTh" + EXT, BASE + "zD3gT4Z" + EXT, - BASE + "z77CaIt" + EXT, + BASE + "z77CaIt" + EXT, "https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg" }; private Data() { diff --git a/picasso/build.gradle b/picasso/build.gradle index 32698a2fa2..1f0923187a 100644 --- a/picasso/build.gradle +++ b/picasso/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation deps.androidxAnnotations implementation deps.androidxCore implementation deps.androidxExifInterface + implementation deps.androidSvg testImplementation deps.junit testImplementation deps.truth testImplementation deps.robolectric diff --git a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java index 714a20572f..ce43c2dd90 100644 --- a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java @@ -17,12 +17,11 @@ import android.content.Context; import android.content.res.AssetManager; -import android.graphics.Bitmap; import android.net.Uri; import androidx.annotation.NonNull; import java.io.IOException; +import okio.BufferedSource; import okio.Okio; -import okio.Source; import static android.content.ContentResolver.SCHEME_FILE; import static com.squareup.picasso3.BitmapUtils.decodeStream; @@ -56,11 +55,17 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca boolean signaledCallback = false; try { - Source source = Okio.source(assetManager.open(getFilePath(request))); + BufferedSource source = Okio.buffer(Okio.source(assetManager.open(getFilePath(request)))); try { - Bitmap bitmap = decodeStream(source, request); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); + if (imageDecoder == null) { + callback.onError( + new IllegalStateException("No image decoder for source: " + getFilePath(request))); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(source, request); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, image.exifOrientation)); } finally { try { source.close(); diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java new file mode 100644 index 0000000000..e7bb572f2e --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java @@ -0,0 +1,71 @@ +package com.squareup.picasso3; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; +import java.io.IOException; +import java.io.InputStream; +import okio.BufferedSource; + +import static com.squareup.picasso3.BitmapUtils.calculateInSampleSize; +import static com.squareup.picasso3.BitmapUtils.createBitmapOptions; +import static com.squareup.picasso3.BitmapUtils.requiresInSampleSize; + +public final class BitmapImageDecoder implements ImageDecoder { + + @Override public boolean canHandleSource(BufferedSource source) { + try { + if (Utils.isWebPFile(source)) { + return true; + } + + InputStream stream = new SourceBufferingInputStream(source); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(stream, null, options); + // we successfully decoded the bounds + return options.outWidth > 0 && options.outHeight > 0; + } catch (IOException e) { + return false; + } + } + + /** + * Decode a byte stream into a Bitmap. This method will take into account additional information + * about the supplied request in order to do the decoding efficiently (such as through leveraging + * {@code inSampleSize}). + */ + @Override public Image decodeImage(BufferedSource source, Request request) throws IOException { + boolean isWebPFile = Utils.isWebPFile(source); + boolean isPurgeable = request.purgeable && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; + BitmapFactory.Options options = createBitmapOptions(request); + boolean calculateSize = requiresInSampleSize(options); + + Bitmap bitmap; + // We decode from a byte array because, a) when decoding a WebP network stream, BitmapFactory + // throws a JNI Exception, so we workaround by decoding a byte array, or b) user requested + // purgeable, which only affects bitmaps decoded from byte arrays. + if (isWebPFile || isPurgeable) { + byte[] bytes = source.readByteArray(); + if (calculateSize) { + BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); + calculateInSampleSize(request.targetWidth, request.targetHeight, options, + request); + } + bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); + } else { + if (calculateSize) { + InputStream stream = new SourceBufferingInputStream(source); + BitmapFactory.decodeStream(stream, null, options); + calculateInSampleSize(request.targetWidth, request.targetHeight, options, + request); + } + bitmap = BitmapFactory.decodeStream(source.inputStream(), null, options); + } + if (bitmap == null) { + // Treat null as an IO exception, we will eventually retry. + throw new IOException("Failed to decode bitmap."); + } + return new Image(bitmap); + } +} diff --git a/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java index dcc4698b2d..7e422752e2 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java @@ -18,13 +18,13 @@ import android.content.ContentResolver; import android.content.Context; import android.content.UriMatcher; -import android.graphics.Bitmap; import android.net.Uri; import android.provider.ContactsContract; import androidx.annotation.NonNull; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import okio.BufferedSource; import okio.Okio; import okio.Source; @@ -78,9 +78,16 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca try { Uri requestUri = checkNotNull(request.uri, "request.uri == null"); Source source = getSource(requestUri); - Bitmap bitmap = decodeStream(source, request); + + BufferedSource bufferedSource = Okio.buffer(source); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(bufferedSource); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for source: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(bufferedSource, request); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, image.exifOrientation)); } catch (Exception e) { if (!signaledCallback) { callback.onError(e); diff --git a/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java index 4a3057dc0a..6f4a8b73e5 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java @@ -17,13 +17,13 @@ import android.content.ContentResolver; import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import androidx.annotation.NonNull; import androidx.exifinterface.media.ExifInterface; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import okio.BufferedSource; import okio.Okio; import okio.Source; @@ -52,10 +52,17 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca try { Uri requestUri = checkNotNull(request.uri, "request.uri == null"); Source source = getSource(requestUri); - Bitmap bitmap = decodeStream(source, request); + + BufferedSource bufferedSource = Okio.buffer(source); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(bufferedSource); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for request: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(bufferedSource, request); int exifRotation = getExifOrientation(requestUri); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK, exifRotation)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifRotation)); } catch (Exception e) { if (!signaledCallback) { callback.onError(e); diff --git a/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java index 7fa0f92a00..afecba00b0 100644 --- a/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java @@ -16,13 +16,13 @@ package com.squareup.picasso3; import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import androidx.annotation.NonNull; import androidx.exifinterface.media.ExifInterface; import java.io.FileNotFoundException; import java.io.IOException; -import okio.Source; +import okio.BufferedSource; +import okio.Okio; import static android.content.ContentResolver.SCHEME_FILE; import static androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL; @@ -47,11 +47,16 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca boolean signaledCallback = false; try { Uri requestUri = checkNotNull(request.uri, "request.uri == null"); - Source source = getSource(requestUri); - Bitmap bitmap = decodeStream(source, request); + BufferedSource source = Okio.buffer(getSource(requestUri)); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for request: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(source, request); int exifRotation = getExifOrientation(requestUri); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK, exifRotation)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifRotation)); } catch (Exception e) { if (!signaledCallback) { callback.onError(e); diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java new file mode 100644 index 0000000000..e940e4d20b --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java @@ -0,0 +1,34 @@ +package com.squareup.picasso3; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.io.IOException; +import okio.BufferedSource; + +public interface ImageDecoder { + + final class Image { + @Nullable public final Bitmap bitmap; + @Nullable public final Drawable drawable; + public final int exifOrientation; + + public Image(@NonNull Bitmap bitmap) { + this(bitmap, null, 0); + } + + public Image(@NonNull Drawable drawable) { + this(null, drawable, 0); + } + + public Image(@Nullable Bitmap bitmap, @Nullable Drawable drawable, int exifOrientation) { + this.bitmap = bitmap; + this.drawable = drawable; + this.exifOrientation = exifOrientation; + } + } + + boolean canHandleSource(BufferedSource source); + Image decodeImage(BufferedSource source, Request request) throws IOException; +} diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java new file mode 100644 index 0000000000..e1d76c00cf --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java @@ -0,0 +1,28 @@ +package com.squareup.picasso3; + +import android.support.annotation.Nullable; +import java.util.List; +import okio.BufferedSource; + +final class ImageDecoderFactory { + + private final List decoders; + + ImageDecoderFactory(List decoders) { + this.decoders = decoders; + } + + /** + * Returns the first {@link ImageDecoder} that can handle the supplied source. + * @param source The source of the image data. + * @return The first ImageDecoder that can decode the source, or null. + */ + @Nullable ImageDecoder getImageDecoderForSource(BufferedSource source) { + for (ImageDecoder decoder : decoders) { + if (decoder.canHandleSource(source)) { + return decoder; + } + } + return null; + } +} diff --git a/picasso/src/main/java/com/squareup/picasso3/MediaStoreRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/MediaStoreRequestHandler.java index e2a5fd2aa5..286e5f5051 100644 --- a/picasso/src/main/java/com/squareup/picasso3/MediaStoreRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/MediaStoreRequestHandler.java @@ -23,7 +23,8 @@ import android.net.Uri; import android.provider.MediaStore; import androidx.annotation.NonNull; -import okio.Source; +import okio.BufferedSource; +import okio.Okio; import static android.content.ContentResolver.SCHEME_CONTENT; import static android.content.ContentUris.parseId; @@ -34,7 +35,6 @@ import static android.provider.MediaStore.Video; import static com.squareup.picasso3.BitmapUtils.calculateInSampleSize; import static com.squareup.picasso3.BitmapUtils.createBitmapOptions; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.MediaStoreRequestHandler.PicassoKind.FULL; import static com.squareup.picasso3.MediaStoreRequestHandler.PicassoKind.MICRO; import static com.squareup.picasso3.MediaStoreRequestHandler.PicassoKind.MINI; @@ -71,10 +71,15 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca if (request.hasSize()) { PicassoKind picassoKind = getPicassoKind(request.targetWidth, request.targetHeight); if (!isVideo && picassoKind == FULL) { - Source source = getSource(requestUri); - Bitmap bitmap = decodeStream(source, request); + BufferedSource source = Okio.buffer(getSource(requestUri)); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for request: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(source, request); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK, exifOrientation)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifOrientation)); return; } @@ -106,10 +111,15 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca } } - Source source = getSource(requestUri); - Bitmap bitmap = decodeStream(source, request); + BufferedSource source = Okio.buffer(getSource(requestUri)); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for request: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(source, request); signaledCallback = true; - callback.onSuccess(new Result(bitmap, DISK, exifOrientation)); + callback.onSuccess(new Result(image.bitmap, image.drawable, DISK, exifOrientation)); } catch (Exception e) { if (!signaledCallback) { callback.onError(e); diff --git a/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java index d937f6918f..d5da748e9a 100644 --- a/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java @@ -15,7 +15,6 @@ */ package com.squareup.picasso3; -import android.graphics.Bitmap; import android.net.NetworkInfo; import android.net.Uri; import androidx.annotation.NonNull; @@ -25,6 +24,7 @@ import okhttp3.Call; import okhttp3.Response; import okhttp3.ResponseBody; +import okio.BufferedSource; import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; @@ -78,8 +78,14 @@ final class NetworkRequestHandler extends RequestHandler { stats.dispatchDownloadFinished(body.contentLength()); } try { - Bitmap bitmap = decodeStream(body.source(), request); - callback.onSuccess(new Result(bitmap, loadedFrom)); + BufferedSource source = body.source(); + ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); + if (imageDecoder == null) { + callback.onError(new IllegalStateException("No image decoder for request: " + request)); + return; + } + ImageDecoder.Image image = imageDecoder.decodeImage(source, request); + callback.onSuccess(new Result(image.bitmap, image.drawable, loadedFrom, 0)); } catch (IOException e) { body.close(); callback.onError(e); diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index 8923e87b63..bd361856f5 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -136,6 +136,7 @@ public enum Priority { private final @Nullable okhttp3.Cache closeableCache; final PlatformLruCache cache; final Stats stats; + final ImageDecoderFactory imageDecoderFactory; final Map targetToAction; final Map targetToDeferredRequestCreator; final List extraRequestHandlers; @@ -148,7 +149,8 @@ public enum Priority { Picasso(Context context, Dispatcher dispatcher, Call.Factory callFactory, @Nullable okhttp3.Cache closeableCache, PlatformLruCache cache, @Nullable Listener listener, - List requestTransformers, List extraRequestHandlers, + ImageDecoderFactory imageDecoderFactory, List requestTransformers, + List extraRequestHandlers, Stats stats, @Nullable Bitmap.Config defaultBitmapConfig, boolean indicatorsEnabled, boolean loggingEnabled) { this.context = context; @@ -157,6 +159,7 @@ public enum Priority { this.closeableCache = closeableCache; this.cache = cache; this.listener = listener; + this.imageDecoderFactory = imageDecoderFactory; this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers)); this.extraRequestHandlers = Collections.unmodifiableList(new ArrayList<>(extraRequestHandlers)); this.defaultBitmapConfig = defaultBitmapConfig; @@ -666,6 +669,7 @@ public static class Builder { @Nullable private ExecutorService service; @Nullable private PlatformLruCache cache; @Nullable private Listener listener; + private final List imageDecoders = new ArrayList<>(); private final List requestTransformers = new ArrayList<>(); private final List requestHandlers = new ArrayList<>(); @Nullable private Bitmap.Config defaultBitmapConfig; @@ -791,6 +795,17 @@ public Builder listener(@NonNull Listener listener) { return this; } + /** Add an decoder that can decode custom image formats. */ + @NonNull + public Builder addImageDecoder(@NonNull ImageDecoder imageDecoder) { + checkNotNull(imageDecoder, "imageDecoder == null"); + if (imageDecoders.contains(imageDecoder)) { + throw new IllegalStateException("ImageDecoder already set."); + } + imageDecoders.add(imageDecoder); + return this; + } + /** Add a transformer that observes and potentially modify all incoming requests. */ @NonNull public Builder addRequestTransformer(@NonNull RequestTransformer transformer) { @@ -847,13 +862,18 @@ public Picasso build() { service = new PicassoExecutorService(new PicassoThreadFactory()); } + ArrayList decoders = new ArrayList<>(imageDecoders); + decoders.add(new BitmapImageDecoder()); + decoders.add(new SvgImageDecoder()); + ImageDecoderFactory decoderFactory = new ImageDecoderFactory(decoders); + Stats stats = new Stats(cache); Dispatcher dispatcher = new Dispatcher(context, service, HANDLER, cache, stats); return new Picasso(context, dispatcher, callFactory, unsharedCache, cache, listener, - requestTransformers, requestHandlers, stats, defaultBitmapConfig, indicatorsEnabled, - loggingEnabled); + decoderFactory, requestTransformers, requestHandlers, stats, defaultBitmapConfig, + indicatorsEnabled, loggingEnabled); } } diff --git a/picasso/src/main/java/com/squareup/picasso3/Request.java b/picasso/src/main/java/com/squareup/picasso3/Request.java index bd5cb9a103..1e198f3bc8 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Request.java +++ b/picasso/src/main/java/com/squareup/picasso3/Request.java @@ -66,6 +66,9 @@ public final class Request { */ @Nullable public final String stableKey; + /** The image decoder factory to use to decode the image. */ + @NonNull + public final ImageDecoderFactory decoderFactory; /** List of custom transformations to be applied after the built-in transformations. */ final List transformations; /** Target image width for resizing. */ @@ -114,6 +117,7 @@ public final class Request { this.uri = builder.uri; this.resourceId = builder.resourceId; this.stableKey = builder.stableKey; + this.decoderFactory = builder.decoderFactory; if (builder.transformations == null) { this.transformations = Collections.emptyList(); } else { @@ -282,6 +286,7 @@ public static final class Builder { float rotationPivotY; boolean hasRotationPivot; boolean purgeable; + @Nullable ImageDecoderFactory decoderFactory; @Nullable List transformations; @Nullable Bitmap.Config config; @Nullable Priority priority; @@ -299,9 +304,11 @@ public Builder(@DrawableRes int resourceId) { setResourceId(resourceId); } - Builder(@Nullable Uri uri, int resourceId, @Nullable Bitmap.Config bitmapConfig) { + Builder(@Nullable Uri uri, int resourceId, @Nullable ImageDecoderFactory decoderFactory, + @Nullable Bitmap.Config bitmapConfig) { this.uri = uri; this.resourceId = resourceId; + this.decoderFactory = decoderFactory; this.config = bitmapConfig; } @@ -320,6 +327,7 @@ public Builder(@DrawableRes int resourceId) { hasRotationPivot = request.hasRotationPivot; purgeable = request.purgeable; onlyScaleDown = request.onlyScaleDown; + decoderFactory = request.decoderFactory; if (request.transformations != null) { transformations = new ArrayList<>(request.transformations); } @@ -562,6 +570,24 @@ public Builder priority(@NonNull Priority priority) { return this; } + @NonNull + public Builder asBitmap() { + return imageDecoderFactory(new ImageDecoderFactory( + Collections.singletonList(new BitmapImageDecoder()))); + } + + @NonNull + public Builder asSvg() { + return imageDecoderFactory(new ImageDecoderFactory( + Collections.singletonList(new SvgImageDecoder()))); + } + + @NonNull + public Builder imageDecoderFactory(ImageDecoderFactory factory) { + decoderFactory = factory; + return this; + } + /** * Add a custom transformation to be applied to the image. *

diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java index 9d686a8692..88165f26f5 100644 --- a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java +++ b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java @@ -32,6 +32,7 @@ import androidx.core.content.ContextCompat; import com.squareup.picasso3.RemoteViewsAction.RemoteViewsTarget; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -74,13 +75,14 @@ public class RequestCreator { "Picasso instance already shut down. Cannot submit new requests."); } this.picasso = picasso; - this.data = new Request.Builder(uri, resourceId, picasso.defaultBitmapConfig); + this.data = new Request.Builder(uri, resourceId, picasso.imageDecoderFactory, + picasso.defaultBitmapConfig); } @SuppressWarnings("NullAway") @VisibleForTesting RequestCreator() { this.picasso = null; - this.data = new Request.Builder(null, 0, null); + this.data = new Request.Builder(null, 0, null, null); } /** @@ -334,6 +336,27 @@ public RequestCreator priority(@NonNull Priority priority) { return this; } + /** + * Specify that the image should be decoded as a bitmap. + */ + @NonNull + public RequestCreator asBitmap() { + return imageDecoderFactory(new ImageDecoderFactory( + Collections.singletonList(new BitmapImageDecoder()))); + } + + @NonNull + public RequestCreator asSvg() { + return imageDecoderFactory(new ImageDecoderFactory( + Collections.singletonList(new SvgImageDecoder()))); + } + + @NonNull + public RequestCreator imageDecoderFactory(ImageDecoderFactory imageDecoderFactory) { + this.data.imageDecoderFactory(imageDecoderFactory); + return this; + } + /** * Add a custom transformation to be applied to the image. *

diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/RequestHandler.java index f6c94765af..9257757a8f 100644 --- a/picasso/src/main/java/com/squareup/picasso3/RequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/RequestHandler.java @@ -68,7 +68,7 @@ public Result(@NonNull Drawable drawable, @NonNull Picasso.LoadedFrom loadedFrom this(null, checkNotNull(drawable, "drawable == null"), loadedFrom, 0); } - private Result( + Result( @Nullable Bitmap bitmap, @Nullable Drawable drawable, @NonNull Picasso.LoadedFrom loadedFrom, diff --git a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java index cee2beaa91..110400e501 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java @@ -61,4 +61,5 @@ public static ResourceDrawableRequestHandler create(@NonNull final Context conte } }); } + } diff --git a/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java new file mode 100644 index 0000000000..75e62b11e7 --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java @@ -0,0 +1,48 @@ +package com.squareup.picasso3; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.util.Log; +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; +import java.io.IOException; +import okio.BufferedSource; + +class SvgImageDecoder implements ImageDecoder { + + @Override public boolean canHandleSource(BufferedSource source) { + SourceBufferingInputStream wrapped = new SourceBufferingInputStream(source); + try { + SVG svg = SVG.getFromInputStream(wrapped); + return true; + } catch (SVGParseException e) { + Log.e("Test", "Failed to parse SVG: " + e.getMessage(), e); + return false; + } + } + + @Override public Image decodeImage(BufferedSource source, Request request) throws IOException { + SourceBufferingInputStream wrapped = new SourceBufferingInputStream(source); + try { + SVG svg = SVG.getFromInputStream(wrapped); + if (request.hasSize()) { + if (request.targetWidth != 0) { + svg.setDocumentWidth(request.targetWidth); + } + if (request.targetHeight != 0) { + svg.setDocumentHeight(request.targetHeight); + } + } + + final int width = (int) svg.getDocumentWidth(); + final int height = (int) svg.getDocumentHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + svg.renderToCanvas(canvas); + + return new Image(bitmap); + } catch (SVGParseException e) { + throw new IOException(e); + } + } +} diff --git a/picasso/src/test/java/com/squareup/picasso3/BitmapHunterTest.java b/picasso/src/test/java/com/squareup/picasso3/BitmapHunterTest.java index 696fe935fd..ad7e47b8f1 100644 --- a/picasso/src/test/java/com/squareup/picasso3/BitmapHunterTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/BitmapHunterTest.java @@ -60,6 +60,7 @@ import static com.squareup.picasso3.TestUtils.CONTENT_KEY_1; import static com.squareup.picasso3.TestUtils.CUSTOM_URI; import static com.squareup.picasso3.TestUtils.CUSTOM_URI_KEY; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.FILE_1_URL; import static com.squareup.picasso3.TestUtils.FILE_KEY_1; import static com.squareup.picasso3.TestUtils.MEDIA_STORE_CONTENT_1_URL; @@ -371,8 +372,9 @@ public final class BitmapHunterTest { List handlers = Collections.singletonList(handler); // Must use non-mock constructor because that is where Picasso's list of handlers is created. Picasso picasso = - new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, null, NO_TRANSFORMERS, - handlers, stats, ARGB_8888, false, false); + new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, null, + DEFAULT_DECODERS, NO_TRANSFORMERS, handlers, stats, ARGB_8888, false, + false); BitmapHunter hunter = forRequest(picasso, dispatcher, cache, stats, action); assertThat(hunter.requestHandler).isEqualTo(handler); } diff --git a/picasso/src/test/java/com/squareup/picasso3/BitmapTargetActionTest.java b/picasso/src/test/java/com/squareup/picasso3/BitmapTargetActionTest.java index 105b5b24a7..ace6a7a61f 100644 --- a/picasso/src/test/java/com/squareup/picasso3/BitmapTargetActionTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/BitmapTargetActionTest.java @@ -25,6 +25,7 @@ import static android.graphics.Bitmap.Config.ARGB_8888; import static com.squareup.picasso3.Picasso.LoadedFrom.MEMORY; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.NO_HANDLERS; import static com.squareup.picasso3.TestUtils.NO_TRANSFORMERS; import static com.squareup.picasso3.TestUtils.RESOURCE_ID_1; @@ -77,8 +78,9 @@ public void invokesOnBitmapFailedIfTargetIsNotNullWithErrorResourceId() { Dispatcher dispatcher = mock(Dispatcher.class); PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = - new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, null, NO_TRANSFORMERS, - NO_HANDLERS, mock(Stats.class), ARGB_8888, false, false); + new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, + null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), + ARGB_8888, false, false); Resources res = mock(Resources.class); BitmapTargetAction request = new BitmapTargetAction(picasso, target, null, null, RESOURCE_ID_1); diff --git a/picasso/src/test/java/com/squareup/picasso3/ImageViewActionTest.java b/picasso/src/test/java/com/squareup/picasso3/ImageViewActionTest.java index cc74d5abe0..f93a7228f8 100644 --- a/picasso/src/test/java/com/squareup/picasso3/ImageViewActionTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/ImageViewActionTest.java @@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.squareup.picasso3.Picasso.LoadedFrom.MEMORY; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.NO_HANDLERS; import static com.squareup.picasso3.TestUtils.NO_TRANSFORMERS; import static com.squareup.picasso3.TestUtils.RESOURCE_ID_1; @@ -56,7 +57,7 @@ public void invokesTargetAndCallbackSuccessIfTargetIsNotNull() { PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = new Picasso(RuntimeEnvironment.application, dispatcher, UNUSED_CALL_FACTORY, null, cache, - null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), Bitmap.Config.ARGB_8888, false, + null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), Bitmap.Config.ARGB_8888, false, false); ImageView target = mockImageViewTarget(); Callback callback = mockCallback(); diff --git a/picasso/src/test/java/com/squareup/picasso3/MediaStoreRequestHandlerTest.java b/picasso/src/test/java/com/squareup/picasso3/MediaStoreRequestHandlerTest.java index 025da393ff..848c371232 100644 --- a/picasso/src/test/java/com/squareup/picasso3/MediaStoreRequestHandlerTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/MediaStoreRequestHandlerTest.java @@ -21,6 +21,7 @@ import static com.squareup.picasso3.MediaStoreRequestHandler.PicassoKind.MICRO; import static com.squareup.picasso3.MediaStoreRequestHandler.PicassoKind.MINI; import static com.squareup.picasso3.MediaStoreRequestHandler.getPicassoKind; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.MEDIA_STORE_CONTENT_1_URL; import static com.squareup.picasso3.TestUtils.MEDIA_STORE_CONTENT_KEY_1; import static com.squareup.picasso3.TestUtils.makeBitmap; @@ -45,7 +46,7 @@ public class MediaStoreRequestHandlerTest { @Test public void decodesVideoThumbnailWithVideoMimeType() { final Bitmap bitmap = makeBitmap(); Request request = - new Request.Builder(MEDIA_STORE_CONTENT_1_URL, 0, ARGB_8888) + new Request.Builder(MEDIA_STORE_CONTENT_1_URL, 0, DEFAULT_DECODERS, ARGB_8888) .stableKey(MEDIA_STORE_CONTENT_KEY_1).resize(100, 100).build(); Action action = mockAction(request); MediaStoreRequestHandler requestHandler = create("video/"); @@ -63,7 +64,7 @@ public class MediaStoreRequestHandlerTest { @Test public void decodesImageThumbnailWithImageMimeType() { final Bitmap bitmap = makeBitmap(20, 20); Request request = - new Request.Builder(MEDIA_STORE_CONTENT_1_URL, 0, ARGB_8888) + new Request.Builder(MEDIA_STORE_CONTENT_1_URL, 0, DEFAULT_DECODERS, ARGB_8888) .stableKey(MEDIA_STORE_CONTENT_KEY_1).resize(100, 100).build(); Action action = mockAction(request); MediaStoreRequestHandler requestHandler = create("image/png"); diff --git a/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java b/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java index e5f22b1c0c..fa79f17c18 100644 --- a/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java @@ -42,6 +42,7 @@ import static com.squareup.picasso3.Picasso.Listener; import static com.squareup.picasso3.Picasso.LoadedFrom.MEMORY; import static com.squareup.picasso3.RemoteViewsAction.RemoteViewsTarget; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.NOOP_REQUEST_HANDLER; import static com.squareup.picasso3.TestUtils.NOOP_TRANSFORMER; import static com.squareup.picasso3.TestUtils.NO_HANDLERS; @@ -88,7 +89,7 @@ public final class PicassoTest { @Before public void setUp() { initMocks(this); picasso = new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, listener, - NO_TRANSFORMERS, NO_HANDLERS, stats, ARGB_8888, false, false); + DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, stats, ARGB_8888, false, false); } @Test public void submitWithTargetInvokesDispatcher() { @@ -192,7 +193,7 @@ public final class PicassoTest { } @Test public void resumeActionTriggersSubmitOnPausedAction() { - Request request = new Request.Builder(URI_1, 0, ARGB_8888).build(); + Request request = new Request.Builder(URI_1, 0, DEFAULT_DECODERS, ARGB_8888).build(); Action action = new Action(mockPicasso(), request) { @Override void complete(RequestHandler.Result result) { @@ -213,7 +214,7 @@ public final class PicassoTest { @Test public void resumeActionImmediatelyCompletesCachedRequest() { cache.set(URI_KEY_1, bitmap); - Request request = new Request.Builder(URI_1, 0, ARGB_8888).build(); + Request request = new Request.Builder(URI_1, 0, DEFAULT_DECODERS, ARGB_8888).build(); Action action = new Action(mockPicasso(), request) { @Override void complete(RequestHandler.Result result) { @@ -371,7 +372,7 @@ public final class PicassoTest { okhttp3.Cache cache = new okhttp3.Cache(temporaryFolder.getRoot(), 100); Picasso picasso = new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, cache, this.cache, listener, - NO_TRANSFORMERS, NO_HANDLERS, stats, ARGB_8888, false, false); + DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, stats, ARGB_8888, false, false); picasso.shutdown(); assertThat(cache.isClosed()).isTrue(); } @@ -403,7 +404,8 @@ public final class PicassoTest { } }; Picasso picasso = new Picasso(context, dispatcher, UNUSED_CALL_FACTORY, null, cache, listener, - Collections.singletonList(brokenTransformer), NO_HANDLERS, stats, ARGB_8888, false, false); + DEFAULT_DECODERS, Collections.singletonList(brokenTransformer), NO_HANDLERS, stats, + ARGB_8888, false, false); Request request = new Request.Builder(URI_1).build(); try { picasso.transformRequest(request); diff --git a/picasso/src/test/java/com/squareup/picasso3/RemoteViewsActionTest.java b/picasso/src/test/java/com/squareup/picasso3/RemoteViewsActionTest.java index f96753da52..e311e8a11a 100644 --- a/picasso/src/test/java/com/squareup/picasso3/RemoteViewsActionTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/RemoteViewsActionTest.java @@ -30,6 +30,7 @@ import static android.graphics.Bitmap.Config.ARGB_8888; import static com.google.common.truth.Truth.assertThat; import static com.squareup.picasso3.Picasso.LoadedFrom.NETWORK; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.NO_HANDLERS; import static com.squareup.picasso3.TestUtils.NO_TRANSFORMERS; import static com.squareup.picasso3.TestUtils.UNUSED_CALL_FACTORY; @@ -102,8 +103,9 @@ private TestableRemoteViewsAction createAction(int errorResId, Callback callback private Picasso createPicasso() { Dispatcher dispatcher = mock(Dispatcher.class); PlatformLruCache cache = new PlatformLruCache(0); - return new Picasso(RuntimeEnvironment.application, dispatcher, UNUSED_CALL_FACTORY, null, cache, - null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), ARGB_8888, false, false); + return new Picasso(RuntimeEnvironment.application, dispatcher, UNUSED_CALL_FACTORY, + null, cache, null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, + mock(Stats.class), ARGB_8888, false, false); } static class TestableRemoteViewsAction extends RemoteViewsAction { diff --git a/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java b/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java index 636b7f6474..46ddd67ba3 100644 --- a/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/RequestCreatorTest.java @@ -42,6 +42,7 @@ import static com.squareup.picasso3.Picasso.Priority.NORMAL; import static com.squareup.picasso3.RemoteViewsAction.AppWidgetAction; import static com.squareup.picasso3.RemoteViewsAction.NotificationAction; +import static com.squareup.picasso3.TestUtils.DEFAULT_DECODERS; import static com.squareup.picasso3.TestUtils.NO_HANDLERS; import static com.squareup.picasso3.TestUtils.NO_TRANSFORMERS; import static com.squareup.picasso3.TestUtils.STABLE_1; @@ -273,8 +274,8 @@ public void intoImageViewWithQuickMemoryCacheCheckDoesNotSubmit() { PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = spy(new Picasso(RuntimeEnvironment.application, mock(Dispatcher.class), UNUSED_CALL_FACTORY, - null, cache, null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), ARGB_8888, false, - false)); + null, cache, null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, + mock(Stats.class), ARGB_8888, false, false)); doReturn(bitmap).when(picasso).quickMemoryCacheCheck(URI_KEY_1); ImageView target = mockImageViewTarget(); Callback callback = mockCallback(); @@ -290,8 +291,8 @@ public void intoImageViewSetsPlaceholderDrawable() { PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = spy(new Picasso(RuntimeEnvironment.application, mock(Dispatcher.class), UNUSED_CALL_FACTORY, - null, cache, null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), ARGB_8888, false, - false)); + null, cache, null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, + mock(Stats.class), ARGB_8888, false, false)); ImageView target = mockImageViewTarget(); Drawable placeHolderDrawable = mock(Drawable.class); new RequestCreator(picasso, URI_1, 0).placeholder(placeHolderDrawable).into(target); @@ -305,8 +306,8 @@ public void intoImageViewNoPlaceholderDrawable() { PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = spy(new Picasso(RuntimeEnvironment.application, mock(Dispatcher.class), UNUSED_CALL_FACTORY, - null, cache, null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), ARGB_8888, false, - false)); + null, cache, null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, + mock(Stats.class), ARGB_8888, false, false)); ImageView target = mockImageViewTarget(); new RequestCreator(picasso, URI_1, 0).noPlaceholder().into(target); verifyNoMoreInteractions(target); @@ -319,8 +320,8 @@ public void intoImageViewSetsPlaceholderWithResourceId() { PlatformLruCache cache = new PlatformLruCache(0); Picasso picasso = spy(new Picasso(RuntimeEnvironment.application, mock(Dispatcher.class), UNUSED_CALL_FACTORY, - null, cache, null, NO_TRANSFORMERS, NO_HANDLERS, mock(Stats.class), ARGB_8888, false, - false)); + null, cache, null, DEFAULT_DECODERS, NO_TRANSFORMERS, NO_HANDLERS, + mock(Stats.class), ARGB_8888, false, false)); ImageView target = mockImageViewTarget(); new RequestCreator(picasso, URI_1, 0).placeholder(android.R.drawable.picture_frame) .into(target); diff --git a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java index 00d5faf341..55d81624e1 100644 --- a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java +++ b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java @@ -34,6 +34,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; import java.util.Collections; import java.util.List; import okhttp3.Call; @@ -166,7 +167,7 @@ static Action mockAction(String key, Uri uri, Object target, int resourceId) { static Action mockAction(String key, Uri uri, Object target, int resourceId, Priority priority, String tag) { - Request.Builder builder = new Request.Builder(uri, resourceId, DEFAULT_CONFIG).stableKey(key); + Request.Builder builder = new Request.Builder(uri, resourceId, DEFAULT_DECODERS, DEFAULT_CONFIG).stableKey(key); if (priority != null) { builder.priority(priority); } @@ -337,6 +338,8 @@ static DrawableLoader makeLoaderWithDrawable(final Drawable drawable) { } }; + static final ImageDecoderFactory DEFAULT_DECODERS = new ImageDecoderFactory( + Arrays.asList(new BitmapImageDecoder(), new SvgImageDecoder())); static final List NO_TRANSFORMERS = Collections.emptyList(); static final List NO_HANDLERS = Collections.emptyList(); From bac7e19b76129edc6601c56fd41e4dec3ae2eb1f Mon Sep 17 00:00:00 2001 From: John Rodriguez Date: Sun, 26 Aug 2018 12:44:19 -0400 Subject: [PATCH 04/16] Cleanup BitmapUtils, etc. --- .../picasso3/AssetRequestHandler.java | 1 - .../squareup/picasso3/BitmapImageDecoder.java | 50 +++---------------- .../com/squareup/picasso3/BitmapUtils.java | 12 ++--- .../picasso3/ContactsPhotoRequestHandler.java | 1 - .../picasso3/ContentStreamRequestHandler.java | 1 - .../squareup/picasso3/FileRequestHandler.java | 1 - .../com/squareup/picasso3/ImageDecoder.java | 6 ++- .../picasso3/ImageDecoderFactory.java | 2 +- .../picasso3/NetworkRequestHandler.java | 1 - .../java/com/squareup/picasso3/Picasso.java | 4 +- .../java/com/squareup/picasso3/Request.java | 8 ++- .../com/squareup/picasso3/RequestCreator.java | 2 +- .../ResourceDrawableRequestHandler.java | 1 - 13 files changed, 25 insertions(+), 65 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java index ce43c2dd90..45ff85396d 100644 --- a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java @@ -24,7 +24,6 @@ import okio.Okio; import static android.content.ContentResolver.SCHEME_FILE; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; import static com.squareup.picasso3.Utils.checkNotNull; diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java index e7bb572f2e..53bfd20859 100644 --- a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java @@ -1,19 +1,16 @@ package com.squareup.picasso3; -import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; +import android.support.annotation.NonNull; import java.io.IOException; import java.io.InputStream; import okio.BufferedSource; -import static com.squareup.picasso3.BitmapUtils.calculateInSampleSize; -import static com.squareup.picasso3.BitmapUtils.createBitmapOptions; -import static com.squareup.picasso3.BitmapUtils.requiresInSampleSize; +import static com.squareup.picasso3.BitmapUtils.decodeStream; public final class BitmapImageDecoder implements ImageDecoder { - @Override public boolean canHandleSource(BufferedSource source) { + @Override public boolean canHandleSource(@NonNull BufferedSource source) { try { if (Utils.isWebPFile(source)) { return true; @@ -30,42 +27,9 @@ public final class BitmapImageDecoder implements ImageDecoder { } } - /** - * Decode a byte stream into a Bitmap. This method will take into account additional information - * about the supplied request in order to do the decoding efficiently (such as through leveraging - * {@code inSampleSize}). - */ - @Override public Image decodeImage(BufferedSource source, Request request) throws IOException { - boolean isWebPFile = Utils.isWebPFile(source); - boolean isPurgeable = request.purgeable && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; - BitmapFactory.Options options = createBitmapOptions(request); - boolean calculateSize = requiresInSampleSize(options); - - Bitmap bitmap; - // We decode from a byte array because, a) when decoding a WebP network stream, BitmapFactory - // throws a JNI Exception, so we workaround by decoding a byte array, or b) user requested - // purgeable, which only affects bitmaps decoded from byte arrays. - if (isWebPFile || isPurgeable) { - byte[] bytes = source.readByteArray(); - if (calculateSize) { - BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - calculateInSampleSize(request.targetWidth, request.targetHeight, options, - request); - } - bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - } else { - if (calculateSize) { - InputStream stream = new SourceBufferingInputStream(source); - BitmapFactory.decodeStream(stream, null, options); - calculateInSampleSize(request.targetWidth, request.targetHeight, options, - request); - } - bitmap = BitmapFactory.decodeStream(source.inputStream(), null, options); - } - if (bitmap == null) { - // Treat null as an IO exception, we will eventually retry. - throw new IOException("Failed to decode bitmap."); - } - return new Image(bitmap); + @NonNull @Override + public Image decodeImage(@NonNull BufferedSource source, @NonNull Request request) + throws IOException { + return new Image(decodeStream(source, request)); } } diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java b/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java index a92e0aa83c..092ea682ab 100644 --- a/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java @@ -15,7 +15,6 @@ */ package com.squareup.picasso3; -import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -113,15 +112,14 @@ static Bitmap decodeStream(Source source, Request request) throws IOException { } @RequiresApi(28) - @SuppressLint("Override") - private static Bitmap decodeStreamP(Request request, BufferedSource bufferedSource) - throws IOException { - ImageDecoder.Source imageSource = - ImageDecoder.createSource(ByteBuffer.wrap(bufferedSource.readByteArray())); + private static Bitmap decodeStreamP(BufferedSource source, Request request) throws IOException { + android.graphics.ImageDecoder.Source imageSource = + android.graphics.ImageDecoder.createSource(ByteBuffer.wrap(source.readByteArray())); return decodeImageSource(imageSource, request); } - private static Bitmap decodeStreamPreP(Request request, BufferedSource bufferedSource) + @NonNull + private static Bitmap decodeStreamPreP(BufferedSource bufferedSource, Request request) throws IOException { boolean isWebPFile = Utils.isWebPFile(bufferedSource); boolean isPurgeable = request.purgeable && SDK_INT < Build.VERSION_CODES.LOLLIPOP; diff --git a/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java index 7e422752e2..e4c71c4170 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ContactsPhotoRequestHandler.java @@ -30,7 +30,6 @@ import static android.content.ContentResolver.SCHEME_CONTENT; import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; import static com.squareup.picasso3.Utils.checkNotNull; diff --git a/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java index 6f4a8b73e5..76cbfc9392 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ContentStreamRequestHandler.java @@ -30,7 +30,6 @@ import static android.content.ContentResolver.SCHEME_CONTENT; import static androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL; import static androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; import static com.squareup.picasso3.Utils.checkNotNull; diff --git a/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java index afecba00b0..a800acaf80 100644 --- a/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/FileRequestHandler.java @@ -27,7 +27,6 @@ import static android.content.ContentResolver.SCHEME_FILE; import static androidx.exifinterface.media.ExifInterface.ORIENTATION_NORMAL; import static androidx.exifinterface.media.ExifInterface.TAG_ORIENTATION; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; import static com.squareup.picasso3.Utils.checkNotNull; diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java index e940e4d20b..e508657d30 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java @@ -29,6 +29,8 @@ public Image(@Nullable Bitmap bitmap, @Nullable Drawable drawable, int exifOrien } } - boolean canHandleSource(BufferedSource source); - Image decodeImage(BufferedSource source, Request request) throws IOException; + boolean canHandleSource(@NonNull BufferedSource source); + + @NonNull Image decodeImage(@NonNull BufferedSource source, @NonNull Request request) + throws IOException; } diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java index e1d76c00cf..c927fbbff6 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java @@ -6,7 +6,7 @@ final class ImageDecoderFactory { - private final List decoders; + final List decoders; ImageDecoderFactory(List decoders) { this.decoders = decoders; diff --git a/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java index d5da748e9a..26b04f2fc4 100644 --- a/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/NetworkRequestHandler.java @@ -26,7 +26,6 @@ import okhttp3.ResponseBody; import okio.BufferedSource; -import static com.squareup.picasso3.BitmapUtils.decodeStream; import static com.squareup.picasso3.Picasso.LoadedFrom.DISK; import static com.squareup.picasso3.Picasso.LoadedFrom.NETWORK; import static com.squareup.picasso3.Utils.checkNotNull; diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index bd361856f5..7d6ad62ecc 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -690,6 +690,7 @@ public Builder(@NonNull Context context) { service = picasso.dispatcher.service; cache = picasso.cache; listener = picasso.listener; + imageDecoders.addAll(picasso.imageDecoderFactory.decoders); requestTransformers.addAll(picasso.requestTransformers); requestHandlers.addAll(picasso.extraRequestHandlers); defaultBitmapConfig = picasso.defaultBitmapConfig; @@ -799,9 +800,6 @@ public Builder listener(@NonNull Listener listener) { @NonNull public Builder addImageDecoder(@NonNull ImageDecoder imageDecoder) { checkNotNull(imageDecoder, "imageDecoder == null"); - if (imageDecoders.contains(imageDecoder)) { - throw new IllegalStateException("ImageDecoder already set."); - } imageDecoders.add(imageDecoder); return this; } diff --git a/picasso/src/main/java/com/squareup/picasso3/Request.java b/picasso/src/main/java/com/squareup/picasso3/Request.java index 1e198f3bc8..0067e90dd5 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Request.java +++ b/picasso/src/main/java/com/squareup/picasso3/Request.java @@ -117,7 +117,7 @@ public final class Request { this.uri = builder.uri; this.resourceId = builder.resourceId; this.stableKey = builder.stableKey; - this.decoderFactory = builder.decoderFactory; + this.decoderFactory = checkNotNull(builder.decoderFactory, "decoderFactory == null"); if (builder.transformations == null) { this.transformations = Collections.emptyList(); } else { @@ -583,7 +583,7 @@ public Builder asSvg() { } @NonNull - public Builder imageDecoderFactory(ImageDecoderFactory factory) { + public Builder imageDecoderFactory(@NonNull ImageDecoderFactory factory) { decoderFactory = factory; return this; } @@ -683,6 +683,10 @@ public Request build() { if (priority == null) { priority = Priority.NORMAL; } + if (decoderFactory == null) { + decoderFactory = new ImageDecoderFactory( + Collections.singletonList(new BitmapImageDecoder())); + } return new Request(this); } diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java index 88165f26f5..25d3da38a2 100644 --- a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java +++ b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java @@ -352,7 +352,7 @@ public RequestCreator asSvg() { } @NonNull - public RequestCreator imageDecoderFactory(ImageDecoderFactory imageDecoderFactory) { + public RequestCreator imageDecoderFactory(@NonNull ImageDecoderFactory imageDecoderFactory) { this.data.imageDecoderFactory(imageDecoderFactory); return this; } diff --git a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java index 110400e501..cee2beaa91 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java @@ -61,5 +61,4 @@ public static ResourceDrawableRequestHandler create(@NonNull final Context conte } }); } - } From fb2508f1951a461862928c3d06ce3e40459d7db4 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Thu, 19 Apr 2018 09:09:11 -0500 Subject: [PATCH 05/16] Implements ImageDecoders to decouple decoding from downloading. --- build.gradle | 2 +- .../main/java/com/squareup/picasso3/AssetRequestHandler.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index eba86035e4..061e8eb4fd 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ buildscript { } dependencies { - classpath deps.androidPlugin + classpath 'com.android.tools.build:gradle:3.3.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16' } diff --git a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java index 45ff85396d..78f6089a57 100644 --- a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java @@ -58,8 +58,9 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca try { ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); if (imageDecoder == null) { - callback.onError( - new IllegalStateException("No image decoder for source: " + getFilePath(request))); + callback.onError(new IllegalStateException( + "No image decoder for source: " + getFilePath(request)) + ); return; } ImageDecoder.Image image = imageDecoder.decodeImage(source, request); From b14c340efa7df1185386a47e8f086c9d9ba43e0e Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Thu, 19 Apr 2018 10:12:40 -0500 Subject: [PATCH 06/16] Fixes tests and cleans up some methods on ReqeustHandler --- .../main/java/com/squareup/picasso3/AssetRequestHandler.java | 4 ++-- .../com/squareup/picasso3/ResourceDrawableRequestHandler.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java index 78f6089a57..f18751c7f1 100644 --- a/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/AssetRequestHandler.java @@ -58,8 +58,8 @@ public void load(@NonNull Picasso picasso, @NonNull Request request, @NonNull Ca try { ImageDecoder imageDecoder = request.decoderFactory.getImageDecoderForSource(source); if (imageDecoder == null) { - callback.onError(new IllegalStateException( - "No image decoder for source: " + getFilePath(request)) + callback.onError( + new IllegalStateException("No image decoder for source: " + getFilePath(request)) ); return; } diff --git a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java index cee2beaa91..110400e501 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso3/ResourceDrawableRequestHandler.java @@ -61,4 +61,5 @@ public static ResourceDrawableRequestHandler create(@NonNull final Context conte } }); } + } From 90e9044609bf803576fba2b526b93e4b6aec92d0 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 22 Jan 2019 13:54:44 -0600 Subject: [PATCH 07/16] Updates based on rebase: Removes SourceBufferingInputStream and updates imports. --- .../com/squareup/picasso3/BitmapImageDecoder.java | 6 ++---- .../java/com/squareup/picasso3/BitmapUtils.java | 4 ++-- .../java/com/squareup/picasso3/ImageDecoder.java | 4 ++-- .../squareup/picasso3/ImageDecoderFactory.java | 2 +- .../main/java/com/squareup/picasso3/Picasso.java | 15 --------------- .../com/squareup/picasso3/SvgImageDecoder.java | 6 ++---- 6 files changed, 9 insertions(+), 28 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java index 53bfd20859..6ff596b24e 100644 --- a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java @@ -1,9 +1,8 @@ package com.squareup.picasso3; import android.graphics.BitmapFactory; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.io.IOException; -import java.io.InputStream; import okio.BufferedSource; import static com.squareup.picasso3.BitmapUtils.decodeStream; @@ -16,10 +15,9 @@ public final class BitmapImageDecoder implements ImageDecoder { return true; } - InputStream stream = new SourceBufferingInputStream(source); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(stream, null, options); + BitmapFactory.decodeStream(source.peek().inputStream(), null, options); // we successfully decoded the bounds return options.outWidth > 0 && options.outHeight > 0; } catch (IOException e) { diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java b/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java index 092ea682ab..1dcc3be5bb 100644 --- a/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapUtils.java @@ -105,8 +105,8 @@ static Bitmap decodeStream(Source source, Request request) throws IOException { ExceptionCatchingSource exceptionCatchingSource = new ExceptionCatchingSource(source); BufferedSource bufferedSource = Okio.buffer(exceptionCatchingSource); Bitmap bitmap = SDK_INT >= 28 - ? decodeStreamP(request, bufferedSource) - : decodeStreamPreP(request, bufferedSource); + ? decodeStreamP(bufferedSource, request) + : decodeStreamPreP(bufferedSource, request); exceptionCatchingSource.throwIfCaught(); return bitmap; } diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java index e508657d30..558388e6cc 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoder.java @@ -2,8 +2,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.IOException; import okio.BufferedSource; diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java index c927fbbff6..fe80b6261c 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java @@ -1,6 +1,6 @@ package com.squareup.picasso3; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import java.util.List; import okio.BufferedSource; diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index 7d6ad62ecc..efd75cd597 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -684,21 +684,6 @@ public Builder(@NonNull Context context) { this.context = context.getApplicationContext(); } - Builder(Picasso picasso) { - context = picasso.context; - callFactory = picasso.callFactory; - service = picasso.dispatcher.service; - cache = picasso.cache; - listener = picasso.listener; - imageDecoders.addAll(picasso.imageDecoderFactory.decoders); - requestTransformers.addAll(picasso.requestTransformers); - requestHandlers.addAll(picasso.extraRequestHandlers); - defaultBitmapConfig = picasso.defaultBitmapConfig; - - indicatorsEnabled = picasso.indicatorsEnabled; - loggingEnabled = picasso.loggingEnabled; - } - Builder(Picasso picasso) { context = picasso.context; callFactory = picasso.callFactory; diff --git a/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java index 75e62b11e7..c95daacfea 100644 --- a/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java @@ -11,9 +11,8 @@ class SvgImageDecoder implements ImageDecoder { @Override public boolean canHandleSource(BufferedSource source) { - SourceBufferingInputStream wrapped = new SourceBufferingInputStream(source); try { - SVG svg = SVG.getFromInputStream(wrapped); + SVG svg = SVG.getFromInputStream(source.peek().inputStream()); return true; } catch (SVGParseException e) { Log.e("Test", "Failed to parse SVG: " + e.getMessage(), e); @@ -22,9 +21,8 @@ class SvgImageDecoder implements ImageDecoder { } @Override public Image decodeImage(BufferedSource source, Request request) throws IOException { - SourceBufferingInputStream wrapped = new SourceBufferingInputStream(source); try { - SVG svg = SVG.getFromInputStream(wrapped); + SVG svg = SVG.getFromInputStream(source.peek().inputStream()); if (request.hasSize()) { if (request.targetWidth != 0) { svg.setDocumentWidth(request.targetWidth); From 77b57c3f8f4bcdcf05154083d17c5253aadf59a4 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 22 Jan 2019 15:07:04 -0600 Subject: [PATCH 08/16] Extracts SVG decoder into separate artifact. --- build.gradle | 4 +- decoders/svg/README.md | 15 ++++ decoders/svg/build.gradle | 34 +++++++++ decoders/svg/gradle.properties | 4 ++ decoders/svg/src/main/AndroidManifest.xml | 1 + .../decoder/svg}/SvgImageDecoder.java | 17 +++-- .../decoder/svg/SvgImageDecoderTest.java | 67 ++++++++++++++++++ decoders/svg/src/test/resources/android.svg | 11 +++ decoders/svg/src/test/resources/image.jpg | Bin 0 -> 38170 bytes .../src/test/resources/robolectric.properties | 3 + picasso/build.gradle | 1 - .../java/com/squareup/picasso3/Picasso.java | 1 - .../java/com/squareup/picasso3/Request.java | 6 -- .../com/squareup/picasso3/RequestCreator.java | 6 -- .../java/com/squareup/picasso3/TestUtils.java | 2 +- settings.gradle | 1 + 16 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 decoders/svg/README.md create mode 100644 decoders/svg/build.gradle create mode 100644 decoders/svg/gradle.properties create mode 100644 decoders/svg/src/main/AndroidManifest.xml rename {picasso/src/main/java/com/squareup/picasso3 => decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg}/SvgImageDecoder.java (71%) create mode 100644 decoders/svg/src/test/java/com/squareup/picasso3/decoder/svg/SvgImageDecoderTest.java create mode 100644 decoders/svg/src/test/resources/android.svg create mode 100644 decoders/svg/src/test/resources/image.jpg create mode 100644 decoders/svg/src/test/resources/robolectric.properties diff --git a/build.gradle b/build.gradle index 061e8eb4fd..7c87c7dcfc 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ext.deps = [ androidPlugin: 'com.android.tools.build:gradle:3.2.1', - androidSvg: 'com.caverock:androidsvg:1.2.2-beta-1@aar', + androidSvg: 'com.caverock:androidsvg-aar:1.3', okhttp: "com.squareup.okhttp3:okhttp:${versions.okhttp}", okio: "com.squareup.okio:okio:${versions.okio}", mockWebServer: "com.squareup.okhttp3:mockwebserver:${versions.okhttp}", @@ -100,7 +100,7 @@ subprojects { version = VERSION_NAME afterEvaluate { - tasks.findByName('check').dependsOn('checkstyle') + tasks.findByName('check')?.dependsOn('checkstyle') } apply plugin: 'net.ltgt.errorprone' diff --git a/decoders/svg/README.md b/decoders/svg/README.md new file mode 100644 index 0000000000..077b372dd4 --- /dev/null +++ b/decoders/svg/README.md @@ -0,0 +1,15 @@ +Picasso SVG Image Decoder +==================================== + +An image decoder that allows Picasso to decode SVG images. + +Usage +----- + +Provide an instance of `SvgImageDecoder` when creating a `Picasso` instance. + +```java +Picasso p = new Picasso.Builder(context) + .addImageDecoder(new SvgImageDecoder()) + .build(); +``` diff --git a/decoders/svg/build.gradle b/decoders/svg/build.gradle new file mode 100644 index 0000000000..6c0c84b6ee --- /dev/null +++ b/decoders/svg/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility versions.sourceCompatibility + targetCompatibility versions.targetCompatibility + } + + lintOptions { + textOutput 'stdout' + textReport true + lintConfig file('lint.xml') + } +} + +dependencies { + api project(':picasso') + implementation deps.androidSvg + compileOnly deps.androidxAnnotations + testImplementation deps.junit + testImplementation deps.robolectric + testImplementation deps.truth + testImplementation deps.mockito + + annotationProcessor deps.nullaway +} + +apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/decoders/svg/gradle.properties b/decoders/svg/gradle.properties new file mode 100644 index 0000000000..1111190602 --- /dev/null +++ b/decoders/svg/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=picasso-decoder-svg +POM_NAME=Picasso SVG Descoder +POM_DESCRIPTION=An image decoder that supports SVG images. +POM_PACKAGING=aar diff --git a/decoders/svg/src/main/AndroidManifest.xml b/decoders/svg/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c5575853a8 --- /dev/null +++ b/decoders/svg/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java similarity index 71% rename from picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java rename to decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java index c95daacfea..f496fadd39 100644 --- a/picasso/src/main/java/com/squareup/picasso3/SvgImageDecoder.java +++ b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java @@ -1,10 +1,12 @@ -package com.squareup.picasso3; +package com.squareup.picasso3.decoder.svg; import android.graphics.Bitmap; import android.graphics.Canvas; import android.util.Log; import com.caverock.androidsvg.SVG; import com.caverock.androidsvg.SVGParseException; +import com.squareup.picasso3.ImageDecoder; +import com.squareup.picasso3.Request; import java.io.IOException; import okio.BufferedSource; @@ -12,10 +14,9 @@ class SvgImageDecoder implements ImageDecoder { @Override public boolean canHandleSource(BufferedSource source) { try { - SVG svg = SVG.getFromInputStream(source.peek().inputStream()); + SVG.getFromInputStream(source.peek().inputStream()); return true; } catch (SVGParseException e) { - Log.e("Test", "Failed to parse SVG: " + e.getMessage(), e); return false; } } @@ -32,8 +33,14 @@ class SvgImageDecoder implements ImageDecoder { } } - final int width = (int) svg.getDocumentWidth(); - final int height = (int) svg.getDocumentHeight(); + int width = (int) svg.getDocumentWidth(); + if (width == -1) { + width = (int) svg.getDocumentViewBox().width(); + } + int height = (int) svg.getDocumentHeight(); + if (height == -1) { + height = (int) svg.getDocumentViewBox().height(); + } Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); svg.renderToCanvas(canvas); diff --git a/decoders/svg/src/test/java/com/squareup/picasso3/decoder/svg/SvgImageDecoderTest.java b/decoders/svg/src/test/java/com/squareup/picasso3/decoder/svg/SvgImageDecoderTest.java new file mode 100644 index 0000000000..a2b2870b1d --- /dev/null +++ b/decoders/svg/src/test/java/com/squareup/picasso3/decoder/svg/SvgImageDecoderTest.java @@ -0,0 +1,67 @@ +package com.squareup.picasso3.decoder.svg; + +import android.net.Uri; +import com.squareup.picasso3.Request; +import java.io.IOException; +import java.io.InputStream; +import okio.BufferedSource; +import okio.Okio; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static com.google.common.truth.Truth.assertThat; +import static com.squareup.picasso3.ImageDecoder.Image; +import static org.mockito.Mockito.mock; + +@RunWith(RobolectricTestRunner.class) +public class SvgImageDecoderTest { + + private SvgImageDecoder decoder; + + @Before public void setup() { + decoder = new SvgImageDecoder(); + } + + @Test public void canHandleSource_forSvg_returnsTrue() { + BufferedSource svg = bufferResource("/android.svg"); + assertThat(decoder.canHandleSource(svg)).isTrue(); + } + + @Test public void canHandleSource_forBitmap_returnsFalse() { + BufferedSource jpg = bufferResource("/image.jpg"); + assertThat(decoder.canHandleSource(jpg)).isFalse(); + } + + @Test public void decodeImage_withoutTargetSize_returnsNativelySizedImage() throws IOException { + BufferedSource svg = bufferResource("/android.svg"); + Request request = new Request.Builder(mock(Uri.class)).build(); + Image image = decoder.decodeImage(svg, request); + + assertThat(image.bitmap).isNotNull(); + assertThat(image.bitmap.getWidth()).isEqualTo(96); + assertThat(image.bitmap.getHeight()).isEqualTo(105); + } + + @Test public void decodeImage_withTargetSize_returnsResizedImage() throws IOException { + BufferedSource svg = bufferResource("/android.svg"); + Request request = new Request.Builder(mock(Uri.class)) + .resize(50, 50) + .build(); + Image image = decoder.decodeImage(svg, request); + + assertThat(image.bitmap).isNotNull(); + assertThat(image.bitmap.getWidth()).isEqualTo(50); + assertThat(image.bitmap.getHeight()).isEqualTo(50); + } + + private BufferedSource bufferResource(String name) { + InputStream in = SvgImageDecoderTest.class.getResourceAsStream(name); + if (in == null) { + throw new IllegalArgumentException("Unknown resource for name: " + name); + } + return Okio.buffer(Okio.source(in)); + } + +} \ No newline at end of file diff --git a/decoders/svg/src/test/resources/android.svg b/decoders/svg/src/test/resources/android.svg new file mode 100644 index 0000000000..1db6886aaf --- /dev/null +++ b/decoders/svg/src/test/resources/android.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/decoders/svg/src/test/resources/image.jpg b/decoders/svg/src/test/resources/image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a3de9ea0b96a1b13792242d69902f654a9c99ed4 GIT binary patch literal 38170 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�g2u$945VhV?}v5b88l(stO$GO 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 L;GUrU_wN4y3=lD+ literal 0 HcmV?d00001 diff --git a/decoders/svg/src/test/resources/robolectric.properties b/decoders/svg/src/test/resources/robolectric.properties new file mode 100644 index 0000000000..84ab549693 --- /dev/null +++ b/decoders/svg/src/test/resources/robolectric.properties @@ -0,0 +1,3 @@ +sdk: 18 +constants: com.squareup.picasso3.BuildConfig +manifest: --default diff --git a/picasso/build.gradle b/picasso/build.gradle index 1f0923187a..32698a2fa2 100644 --- a/picasso/build.gradle +++ b/picasso/build.gradle @@ -33,7 +33,6 @@ dependencies { implementation deps.androidxAnnotations implementation deps.androidxCore implementation deps.androidxExifInterface - implementation deps.androidSvg testImplementation deps.junit testImplementation deps.truth testImplementation deps.robolectric diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index efd75cd597..90d3064356 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -847,7 +847,6 @@ public Picasso build() { ArrayList decoders = new ArrayList<>(imageDecoders); decoders.add(new BitmapImageDecoder()); - decoders.add(new SvgImageDecoder()); ImageDecoderFactory decoderFactory = new ImageDecoderFactory(decoders); Stats stats = new Stats(cache); diff --git a/picasso/src/main/java/com/squareup/picasso3/Request.java b/picasso/src/main/java/com/squareup/picasso3/Request.java index 0067e90dd5..8f89a977be 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Request.java +++ b/picasso/src/main/java/com/squareup/picasso3/Request.java @@ -576,12 +576,6 @@ public Builder asBitmap() { Collections.singletonList(new BitmapImageDecoder()))); } - @NonNull - public Builder asSvg() { - return imageDecoderFactory(new ImageDecoderFactory( - Collections.singletonList(new SvgImageDecoder()))); - } - @NonNull public Builder imageDecoderFactory(@NonNull ImageDecoderFactory factory) { decoderFactory = factory; diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java index 25d3da38a2..4acf1b3f36 100644 --- a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java +++ b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.java @@ -345,12 +345,6 @@ public RequestCreator asBitmap() { Collections.singletonList(new BitmapImageDecoder()))); } - @NonNull - public RequestCreator asSvg() { - return imageDecoderFactory(new ImageDecoderFactory( - Collections.singletonList(new SvgImageDecoder()))); - } - @NonNull public RequestCreator imageDecoderFactory(@NonNull ImageDecoderFactory imageDecoderFactory) { this.data.imageDecoderFactory(imageDecoderFactory); diff --git a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java index 55d81624e1..9a629b5ca8 100644 --- a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java +++ b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java @@ -339,7 +339,7 @@ static DrawableLoader makeLoaderWithDrawable(final Drawable drawable) { }; static final ImageDecoderFactory DEFAULT_DECODERS = new ImageDecoderFactory( - Arrays.asList(new BitmapImageDecoder(), new SvgImageDecoder())); + Collections.singletonList(new BitmapImageDecoder())); static final List NO_TRANSFORMERS = Collections.emptyList(); static final List NO_HANDLERS = Collections.emptyList(); diff --git a/settings.gradle b/settings.gradle index 94c282c758..875720cb7b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,4 @@ include 'picasso' include 'picasso-pollexor' include 'picasso-provider' include 'picasso-sample' +include 'decoders:svg' From a2f09cfe6377891dcd60c28baa9e46807e469df7 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 22 Jan 2019 15:13:07 -0600 Subject: [PATCH 09/16] Fixes annotation issue in StatsSnapshot. --- picasso/src/main/java/com/squareup/picasso3/StatsSnapshot.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/StatsSnapshot.java b/picasso/src/main/java/com/squareup/picasso3/StatsSnapshot.java index 3cf4ff1015..5bca870e75 100644 --- a/picasso/src/main/java/com/squareup/picasso3/StatsSnapshot.java +++ b/picasso/src/main/java/com/squareup/picasso3/StatsSnapshot.java @@ -17,7 +17,6 @@ import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import java.io.IOException; import okio.Buffer; import okio.BufferedSink; @@ -129,7 +128,7 @@ public void dump(@NonNull BufferedSink sink) throws IOException { sink.writeUtf8("\n"); } - @Nullable + @NonNull @Override public String toString() { return "StatsSnapshot{" + "maxSize=" From df5b56fd8bf9c64a39ff427321b6022a9e2c950e Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 22 Jan 2019 15:44:02 -0600 Subject: [PATCH 10/16] Removes erroneous import in SVG image decoder. --- .../java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java index f496fadd39..71cfefcd39 100644 --- a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java +++ b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java @@ -2,7 +2,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; -import android.util.Log; import com.caverock.androidsvg.SVG; import com.caverock.androidsvg.SVGParseException; import com.squareup.picasso3.ImageDecoder; From 21aa3c87ba6bb1374ebf7dd7eeda941057fc6704 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 5 Feb 2019 09:36:33 -0600 Subject: [PATCH 11/16] Removes an erroneous `peek()`. --- .../java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java index 71cfefcd39..e03811922b 100644 --- a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java +++ b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java @@ -22,7 +22,7 @@ class SvgImageDecoder implements ImageDecoder { @Override public Image decodeImage(BufferedSource source, Request request) throws IOException { try { - SVG svg = SVG.getFromInputStream(source.peek().inputStream()); + SVG svg = SVG.getFromInputStream(source.inputStream()); if (request.hasSize()) { if (request.targetWidth != 0) { svg.setDocumentWidth(request.targetWidth); From c2fa402642dce92314833e8b210315d1f2e041db Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 5 Feb 2019 09:43:31 -0600 Subject: [PATCH 12/16] Updates ImageDecoderFactory to use a fori loop to avoid an iterator. --- .../main/java/com/squareup/picasso3/ImageDecoderFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java index fe80b6261c..d0161d3cce 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java @@ -18,7 +18,8 @@ final class ImageDecoderFactory { * @return The first ImageDecoder that can decode the source, or null. */ @Nullable ImageDecoder getImageDecoderForSource(BufferedSource source) { - for (ImageDecoder decoder : decoders) { + for (int i = 0, n = decoders.size(); i < n; i++) { + ImageDecoder decoder = decoders.get(i); if (decoder.canHandleSource(source)) { return decoder; } From 9aa2877d0ea7ea20e5a00e5dbf139411d0e0b572 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 5 Feb 2019 09:54:47 -0600 Subject: [PATCH 13/16] Removes erroneous properties. --- picasso/src/main/java/com/squareup/picasso3/Picasso.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index 90d3064356..db040b991e 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -139,7 +139,6 @@ public enum Priority { final ImageDecoderFactory imageDecoderFactory; final Map targetToAction; final Map targetToDeferredRequestCreator; - final List extraRequestHandlers; @Nullable final Bitmap.Config defaultBitmapConfig; boolean indicatorsEnabled; @@ -161,7 +160,6 @@ public enum Priority { this.listener = listener; this.imageDecoderFactory = imageDecoderFactory; this.requestTransformers = Collections.unmodifiableList(new ArrayList<>(requestTransformers)); - this.extraRequestHandlers = Collections.unmodifiableList(new ArrayList<>(extraRequestHandlers)); this.defaultBitmapConfig = defaultBitmapConfig; // Adjust this and Builder(Picasso) as internal handlers are added or removed. @@ -676,7 +674,6 @@ public static class Builder { private boolean indicatorsEnabled; private boolean loggingEnabled; - private boolean isChild; /** Start building a new {@link Picasso} instance. */ public Builder(@NonNull Context context) { From 7efb773b6b91a636ab6aad9d12406bcf10b366f2 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Tue, 5 Feb 2019 10:00:19 -0600 Subject: [PATCH 14/16] Retains image decoders when calling `newBuilder()`. --- .../java/com/squareup/picasso3/Picasso.java | 3 ++ .../com/squareup/picasso3/PicassoTest.java | 30 +++++++++++++++++++ .../java/com/squareup/picasso3/TestUtils.java | 14 +++++++++ 3 files changed, 47 insertions(+) diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.java b/picasso/src/main/java/com/squareup/picasso3/Picasso.java index db040b991e..d6bb9e0b04 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.java +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.java @@ -692,6 +692,9 @@ public Builder(@NonNull Context context) { int numRequestHandlers = picasso.requestHandlers.size(); requestHandlers.addAll(picasso.requestHandlers.subList(2, numRequestHandlers - 6)); + int numImageDecoders = picasso.imageDecoderFactory.decoders.size(); + imageDecoders.addAll(picasso.imageDecoderFactory.decoders.subList(0, numImageDecoders - 1)); + defaultBitmapConfig = picasso.defaultBitmapConfig; indicatorsEnabled = picasso.indicatorsEnabled; loggingEnabled = picasso.loggingEnabled; diff --git a/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java b/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java index fa79f17c18..0dc825d8c6 100644 --- a/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java +++ b/picasso/src/test/java/com/squareup/picasso3/PicassoTest.java @@ -23,8 +23,10 @@ import androidx.annotation.NonNull; import com.squareup.picasso3.Picasso.RequestTransformer; import java.io.File; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import okio.BufferedSource; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -559,6 +561,31 @@ public final class PicassoTest { assertThat(original.requestHandlers).hasSize(NUM_BUILTIN_HANDLERS); } + @Test public void clonedImageDecodersAreRetained() { + Picasso parent = defaultPicasso(RuntimeEnvironment.application, false, false); + + ImageDecoder newDecoder = new ImageDecoder() { + @Override public boolean canHandleSource(@NonNull BufferedSource source) { + return false; + } + + @NonNull @Override + public Image decodeImage(@NonNull BufferedSource source, @NonNull Request request) + throws IOException { + return null; + } + }; + + Picasso child = parent.newBuilder() + .addImageDecoder(newDecoder) + .build(); + + assertThat(child.imageDecoderFactory.decoders).hasSize(3); + ImageDecoder parentCustomDecoder = parent.imageDecoderFactory.decoders.get(0); + assertThat(child.imageDecoderFactory.decoders).contains(parentCustomDecoder); + assertThat(child.imageDecoderFactory.decoders).contains(newDecoder); + } + @Test public void cloneSharesStatefulInstances() { Picasso parent = defaultPicasso(RuntimeEnvironment.application, true, true); @@ -578,6 +605,9 @@ public final class PicassoTest { parent.requestHandlers.get(i).getClass()); } + assertThat(child.imageDecoderFactory.decoders).hasSize( + parent.imageDecoderFactory.decoders.size()); + assertThat(child.defaultBitmapConfig).isEqualTo(parent.defaultBitmapConfig); assertThat(child.indicatorsEnabled).isEqualTo(parent.indicatorsEnabled); assertThat(child.loggingEnabled).isEqualTo(parent.loggingEnabled); diff --git a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java index 9a629b5ca8..ecd138843c 100644 --- a/picasso/src/test/java/com/squareup/picasso3/TestUtils.java +++ b/picasso/src/test/java/com/squareup/picasso3/TestUtils.java @@ -39,6 +39,7 @@ import java.util.List; import okhttp3.Call; import okhttp3.Response; +import okio.BufferedSource; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -338,6 +339,18 @@ static DrawableLoader makeLoaderWithDrawable(final Drawable drawable) { } }; + static final ImageDecoder NOOP_IMAGE_DECODER = new ImageDecoder() { + @Override public boolean canHandleSource(@NonNull BufferedSource source) { + return false; + } + + @NonNull @Override + public Image decodeImage(@NonNull BufferedSource source, @NonNull Request request) + throws IOException { + return null; + } + }; + static final ImageDecoderFactory DEFAULT_DECODERS = new ImageDecoderFactory( Collections.singletonList(new BitmapImageDecoder())); static final List NO_TRANSFORMERS = Collections.emptyList(); @@ -356,6 +369,7 @@ static Picasso defaultPicasso(Context context, boolean hasRequestHandlers, return builder .callFactory(UNUSED_CALL_FACTORY) .defaultBitmapConfig(DEFAULT_CONFIG) + .addImageDecoder(NOOP_IMAGE_DECODER) .executor(new PicassoExecutorService(new PicassoThreadFactory())) .indicatorsEnabled(true) .listener(NOOP_LISTENER) From ac7cfe95755d3ae621c43327e4361499c707f2b3 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Wed, 6 Feb 2019 08:59:28 -0600 Subject: [PATCH 15/16] Updates build.gradle to clean up some merge issues. --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 7c87c7dcfc..ca7fa49e9b 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { ] ext.deps = [ - androidPlugin: 'com.android.tools.build:gradle:3.2.1', + androidPlugin: 'com.android.tools.build:gradle:3.3.0', androidSvg: 'com.caverock:androidsvg-aar:1.3', okhttp: "com.squareup.okhttp3:okhttp:${versions.okhttp}", okio: "com.squareup.okio:okio:${versions.okio}", @@ -37,7 +37,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath deps.androidPlugin classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16' } @@ -100,7 +100,7 @@ subprojects { version = VERSION_NAME afterEvaluate { - tasks.findByName('check')?.dependsOn('checkstyle') + tasks.getByName('check').dependsOn('checkstyle') } apply plugin: 'net.ltgt.errorprone' From 89081a39dc544513b3e93937c0fba57cefb8d570 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Wed, 6 Feb 2019 09:11:47 -0600 Subject: [PATCH 16/16] Pass in a peeked source to `canHandleSource` so clients don't need to worry about it. --- .../java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java | 2 +- .../src/main/java/com/squareup/picasso3/BitmapImageDecoder.java | 2 +- .../main/java/com/squareup/picasso3/ImageDecoderFactory.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java index e03811922b..8eb8610dfd 100644 --- a/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java +++ b/decoders/svg/src/main/java/com/squareup/picasso3/decoder/svg/SvgImageDecoder.java @@ -13,7 +13,7 @@ class SvgImageDecoder implements ImageDecoder { @Override public boolean canHandleSource(BufferedSource source) { try { - SVG.getFromInputStream(source.peek().inputStream()); + SVG.getFromInputStream(source.inputStream()); return true; } catch (SVGParseException e) { return false; diff --git a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java index 6ff596b24e..93d3b0aa34 100644 --- a/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java +++ b/picasso/src/main/java/com/squareup/picasso3/BitmapImageDecoder.java @@ -17,7 +17,7 @@ public final class BitmapImageDecoder implements ImageDecoder { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(source.peek().inputStream(), null, options); + BitmapFactory.decodeStream(source.inputStream(), null, options); // we successfully decoded the bounds return options.outWidth > 0 && options.outHeight > 0; } catch (IOException e) { diff --git a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java index d0161d3cce..e6666f95b4 100644 --- a/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java +++ b/picasso/src/main/java/com/squareup/picasso3/ImageDecoderFactory.java @@ -20,7 +20,7 @@ final class ImageDecoderFactory { @Nullable ImageDecoder getImageDecoderForSource(BufferedSource source) { for (int i = 0, n = decoders.size(); i < n; i++) { ImageDecoder decoder = decoders.get(i); - if (decoder.canHandleSource(source)) { + if (decoder.canHandleSource(source.peek())) { return decoder; } }