diff --git a/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java b/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java index 03e7d5f9aa..bc04f78dcc 100644 --- a/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java +++ b/instrumentation/src/androidTest/java/com/bumptech/glide/NonBitmapDrawableResourcesTest.java @@ -22,13 +22,18 @@ import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class NonBitmapDrawableResourcesTest { private Context context; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Before public void setUp() { context = InstrumentationRegistry.getTargetContext(); @@ -70,6 +75,17 @@ public void load_withBitmapAliasResourceId_asDrawable_producesNonNullDrawable() assertThat(drawable).isNotNull(); } + @Test + public void load_withBitmapAliasResourceId_asBitmap_producesNonNullBitmap() + throws ExecutionException, InterruptedException { + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.bitmap_alias) + .submit() + .get(); + assertThat(bitmap).isNotNull(); + } + @Test public void load_withShapeDrawableResourceId_asDrawable_producesNonNullDrawable() throws ExecutionException, InterruptedException { @@ -80,6 +96,30 @@ public void load_withShapeDrawableResourceId_asDrawable_producesNonNullDrawable( assertThat(drawable).isNotNull(); } + @Test + public void load_withShapeDrawableResourceId_asBitmap_withSizeOriginal_fails() + throws ExecutionException, InterruptedException { + expectedException.expect(ExecutionException.class); + Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.shape_drawable) + .submit() + .get(); + } + + @Test + public void load_withShapeDrawableResourceId_asBitmap_withValidSize_returnsNonNullBitmap() + throws ExecutionException, InterruptedException { + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.shape_drawable) + .submit(100, 200) + .get(); + assertThat(bitmap).isNotNull(); + assertThat(bitmap.getWidth()).isEqualTo(100); + assertThat(bitmap.getHeight()).isEqualTo(200); + } + @Test public void load_withStateListDrawableResourceId_asDrawable_producesNonNullDrawable() throws ExecutionException, InterruptedException { @@ -90,6 +130,17 @@ public void load_withStateListDrawableResourceId_asDrawable_producesNonNullDrawa assertThat(drawable).isNotNull(); } + @Test + public void load_withStateListDrawableResourceId_asBitmap_producesNonNullBitmap() + throws ExecutionException, InterruptedException { + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.state_list_drawable) + .submit() + .get(); + assertThat(bitmap).isNotNull(); + } + @Test public void load_withVectorDrawableResourceId_asDrawable_producesNonNullDrawable() throws ExecutionException, InterruptedException { @@ -100,6 +151,17 @@ public void load_withVectorDrawableResourceId_asDrawable_producesNonNullDrawable assertThat(drawable).isNotNull(); } + @Test + public void load_withVectorDrawableResourceId_asBitmap_producesNonNullBitmap() + throws ExecutionException, InterruptedException { + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.vector_drawable) + .submit() + .get(); + assertThat(bitmap).isNotNull(); + } + @Test public void load_withNinePatchResourceId_asDrawable_producesNonNullDrawable() throws ExecutionException, InterruptedException { @@ -111,6 +173,19 @@ public void load_withNinePatchResourceId_asDrawable_producesNonNullDrawable() assertThat(drawable).isNotNull(); } + + @Test + public void load_withNinePatchResourceId_asBitmap_producesNonNullBitmap() + throws ExecutionException, InterruptedException { + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(ResourceIds.drawable.googlelogo_color_120x44dp) + .submit() + .get(); + + assertThat(bitmap).isNotNull(); + } + @Test public void load_withApplicationIconResourceIdUri_asDrawable_producesNonNullDrawable() throws NameNotFoundException, ExecutionException, InterruptedException { @@ -131,6 +206,27 @@ public void load_withApplicationIconResourceIdUri_asDrawable_producesNonNullDraw } } + @Test + public void load_withApplicationIconResourceIdUri_asBitmap_producesNonNullBitmap() + throws NameNotFoundException, ExecutionException, InterruptedException { + for (String packageName : getInstalledPackages()) { + int iconResourceId = getResourceId(packageName); + + Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(packageName) + .path(String.valueOf(iconResourceId)) + .build(); + + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(uri) + .submit() + .get(); + assertThat(bitmap).isNotNull(); + } + } + @Test public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDrawable() throws ExecutionException, InterruptedException, NameNotFoundException { @@ -155,6 +251,31 @@ public void load_withApplicationIconResourceNameUri_asDrawable_producesNonNullDr } } + @Test + public void load_withApplicationIconResourceNameUri_asBitmap_producesNonNullBitmap() + throws ExecutionException, InterruptedException, NameNotFoundException { + for (String packageName : getInstalledPackages()) { + int iconResourceId = getResourceId(packageName); + + Context toUse = context.createPackageContext(packageName, /*flags=*/ 0); + Resources resources = toUse.getResources(); + Uri uri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(packageName) + .path(resources.getResourceTypeName(iconResourceId)) + .path(resources.getResourceEntryName(iconResourceId)) + .path(String.valueOf(iconResourceId)) + .build(); + + Bitmap bitmap = Glide.with(context) + .asBitmap() + .load(uri) + .submit() + .get(); + assertThat(bitmap).isNotNull(); + } + } + private Set getInstalledPackages() { Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java index c1251eab79..4be37419b8 100644 --- a/library/src/main/java/com/bumptech/glide/Glide.java +++ b/library/src/main/java/com/bumptech/glide/Glide.java @@ -52,6 +52,7 @@ import com.bumptech.glide.load.resource.bitmap.ByteBufferBitmapDecoder; import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser; import com.bumptech.glide.load.resource.bitmap.Downsampler; +import com.bumptech.glide.load.resource.bitmap.ResourceBitmapDecoder; import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder; import com.bumptech.glide.load.resource.bitmap.VideoBitmapDecoder; import com.bumptech.glide.load.resource.bytes.ByteBufferRewinder; @@ -333,6 +334,8 @@ private static GeneratedAppGlideModule getAnnotationGeneratedGlideModules() { new GifFrameResourceDecoder(bitmapPool)) /* Drawables */ .append(Uri.class, Drawable.class, new ResourceDrawableDecoder(context)) + .append(Uri.class, Bitmap.class, + new ResourceBitmapDecoder(new ResourceDrawableDecoder(context), bitmapPool)) /* Files */ .register(new ByteBufferRewinder.Factory()) .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory()) diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ResourceBitmapDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ResourceBitmapDecoder.java new file mode 100644 index 0000000000..9fc5f3ee4d --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/ResourceBitmapDecoder.java @@ -0,0 +1,92 @@ +package com.bumptech.glide.load.resource.bitmap; + +import android.content.ContentResolver; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.annotation.Nullable; +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.ResourceDecoder; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.drawable.ResourceDrawableDecoder; +import com.bumptech.glide.request.target.Target; +import java.io.IOException; + +/** + * Decodes {@link Bitmap}s from resource ids. + * + *

The framework will decode some resources as {@link Drawable}s that do not wrap + * {@link Bitmap}s. This decoder will attempt to return a {@link Bitmap} for those + * {@link Drawable}s anyway by drawing the {@link Drawable} to a {@link Canvas}s using + * the {@link Drawable}'s intrinsic bounds or the dimensions provided to + * {@link #decode(Object, int, int, Options)}. + * + *

For non-{@link Bitmap} {@link Drawable}s that return <= 0 for + * {@link Drawable#getIntrinsicWidth()} and/or {@link Drawable#getIntrinsicHeight()}, this + * decoder will fail if the width and height provided to {@link #decode(Object, int, int, Options)} + * are {@link Target#SIZE_ORIGINAL}. + */ +public class ResourceBitmapDecoder implements ResourceDecoder { + + private final ResourceDrawableDecoder drawableDecoder; + private final BitmapPool bitmapPool; + + public ResourceBitmapDecoder(ResourceDrawableDecoder drawableDecoder, BitmapPool bitmapPool) { + this.drawableDecoder = drawableDecoder; + this.bitmapPool = bitmapPool; + } + + @Override + public boolean handles(Uri source, Options options) throws IOException { + return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(source.getScheme()); + } + + @Nullable + @Override + public Resource decode(Uri source, int width, int height, Options options) + throws IOException { + Resource drawableResource = drawableDecoder.decode(source, width, height, options); + if (drawableResource == null) { + return null; + } + + // Handle DrawableContainer or StateListDrawables that may contain one or more BitmapDrawables. + Drawable drawable = drawableResource.get().getCurrent(); + + Bitmap result = null; + if (drawable instanceof BitmapDrawable) { + result = ((BitmapDrawable) drawable).getBitmap(); + } else if (!(drawable instanceof Animatable)) { + result = drawToBitmap(drawable, width, height); + } + + if (result == null) { + return null; + } + return new BitmapResource(result, bitmapPool); + } + + private Bitmap drawToBitmap(Drawable drawable, int width, int height) { + if (width == Target.SIZE_ORIGINAL && drawable.getIntrinsicWidth() <= 0) { + throw new IllegalArgumentException("Unable to draw " + drawable + " to Bitmap with " + + "Target.SIZE_ORIGINAL because the Drawable has no intrinsic width"); + } + if (height == Target.SIZE_ORIGINAL && drawable.getIntrinsicHeight() <= 0) { + throw new IllegalArgumentException("Unable to draw " + drawable + " to Bitmap with " + + "Target.SIZE_ORIGINAL because the Drawable has no intrinsic height"); + } + int targetWidth = drawable.getIntrinsicWidth() > 0 ? drawable.getIntrinsicWidth() : width; + int targetHeight = drawable.getIntrinsicHeight() > 0 ? drawable.getIntrinsicHeight() : height; + Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Config.ARGB_8888); + Canvas canvas = new Canvas(result); + drawable.setBounds(0, 0, targetWidth, targetHeight); + drawable.draw(canvas); + canvas.setBitmap(null); + return result; + } +}