From 17c1c3d5781e953379904a60bae08524632e5053 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Mon, 5 Feb 2018 07:02:01 -0800 Subject: [PATCH] Add support for Uri data uris. Previously we only supported data uris if they were provided as Strings. Fixes #556. --- .../java/com/bumptech/glide/DataUriTest.java | 105 ++++++++++++++++++ .../main/java/com/bumptech/glide/Glide.java | 3 +- .../glide/load/model/DataUrlLoader.java | 24 ++-- .../glide/load/model/DataUrlLoaderTest.java | 7 +- 4 files changed, 125 insertions(+), 14 deletions(-) create mode 100644 instrumentation/src/androidTest/java/com/bumptech/glide/DataUriTest.java diff --git a/instrumentation/src/androidTest/java/com/bumptech/glide/DataUriTest.java b/instrumentation/src/androidTest/java/com/bumptech/glide/DataUriTest.java new file mode 100644 index 0000000000..0b46ad79df --- /dev/null +++ b/instrumentation/src/androidTest/java/com/bumptech/glide/DataUriTest.java @@ -0,0 +1,105 @@ +package com.bumptech.glide; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Base64; +import com.bumptech.glide.test.ConcurrencyHelper; +import com.bumptech.glide.test.ResourceIds; +import com.bumptech.glide.test.TearDownGlide; +import java.io.ByteArrayOutputStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DataUriTest { + @Rule public TearDownGlide tearDownGlide = new TearDownGlide(); + private final ConcurrencyHelper concurrency = new ConcurrencyHelper(); + private final Context context = InstrumentationRegistry.getTargetContext(); + + @Test + public void load_withJpegAsDataUriString_returnsBitmap() { + Bitmap bitmap = + concurrency.get( + Glide.with(context) + .asBitmap() + .load(getDataUriString(CompressFormat.JPEG)) + .submit()); + assertThat(bitmap).isNotNull(); + } + + @Test + public void load_withPngDataUriString_returnsBitmap() { + Bitmap bitmap = + concurrency.get( + Glide.with(context) + .asBitmap() + .load(getDataUriString(CompressFormat.PNG)) + .submit()); + assertThat(bitmap).isNotNull(); + } + + @Test + public void load_withJpegAsDataUri_returnsBitmap() { + Bitmap bitmap = + concurrency.get( + Glide.with(context) + .asBitmap() + .load(getDataUri(CompressFormat.JPEG)) + .submit()); + assertThat(bitmap).isNotNull(); + } + + @Test + public void load_withPngAsDataUri_returnsBitmap() { + Bitmap bitmap = + concurrency.get( + Glide.with(context) + .asBitmap() + .load(getDataUri(CompressFormat.PNG)) + .submit()); + assertThat(bitmap).isNotNull(); + } + + private Uri getDataUri(CompressFormat format) { + return Uri.parse(getDataUriString(format)); + } + + private String getDataUriString(CompressFormat format) { + String bytes = getBase64BitmapBytes(format); + String imageType; + switch (format) { + case PNG: + imageType = "png"; + break; + case JPEG: + imageType = "jpeg"; + break; + case WEBP: + imageType = "webp"; + break; + default: + throw new IllegalArgumentException("Unrecognized format: " + format); + } + + String mimeType = "image/" + imageType; + return "data:" + mimeType + ";base64," + bytes; + } + + private String getBase64BitmapBytes(CompressFormat format) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Drawable drawable = context.getResources().getDrawable(ResourceIds.raw.canonical); + Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + bitmap.compress(format, 100, bos); + byte[] data = bos.toByteArray(); + return Base64.encodeToString(data, /*flags=*/ 0); + } +} diff --git a/library/src/main/java/com/bumptech/glide/Glide.java b/library/src/main/java/com/bumptech/glide/Glide.java index 63b0f12c7f..c7f7e45627 100644 --- a/library/src/main/java/com/bumptech/glide/Glide.java +++ b/library/src/main/java/com/bumptech/glide/Glide.java @@ -446,7 +446,8 @@ Uri.class, Bitmap.class, new ResourceBitmapDecoder(resourceDrawableDecoder, bitm AssetFileDescriptor.class, resourceLoaderAssetFileDescriptorFactory) .append(int.class, Uri.class, resourceLoaderUriFactory) - .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory()) + .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory()) + .append(Uri.class, InputStream.class, new DataUrlLoader.StreamFactory()) .append(String.class, InputStream.class, new StringLoader.StreamFactory()) .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory()) .append( diff --git a/library/src/main/java/com/bumptech/glide/load/model/DataUrlLoader.java b/library/src/main/java/com/bumptech/glide/load/model/DataUrlLoader.java index b5bddccaff..646c43907e 100644 --- a/library/src/main/java/com/bumptech/glide/load/model/DataUrlLoader.java +++ b/library/src/main/java/com/bumptech/glide/load/model/DataUrlLoader.java @@ -20,9 +20,10 @@ * *

Briefly, a 'data' URL has the form:

data:[mediatype][;base64],some_data
* - * @param The type of data that can be opened. + * @param The type of Model that we can retrieve data for, e.g. {@link String}. + * @param The type of data that can be opened, e.g. {@link InputStream}. */ -public final class DataUrlLoader implements ModelLoader { +public final class DataUrlLoader implements ModelLoader { private static final String DATA_SCHEME_IMAGE = "data:image"; private static final String BASE64_TAG = ";base64"; @@ -35,14 +36,17 @@ public DataUrlLoader(DataDecoder dataDecoder) { } @Override - public LoadData buildLoadData(@NonNull String model, int width, int height, + public LoadData buildLoadData(@NonNull Model model, int width, int height, @NonNull Options options) { - return new LoadData<>(new ObjectKey(model), new DataUriFetcher<>(model, dataDecoder)); + return new LoadData<>( + new ObjectKey(model), new DataUriFetcher<>(model.toString(), dataDecoder)); } @Override - public boolean handles(@NonNull String url) { - return url.startsWith(DATA_SCHEME_IMAGE); + public boolean handles(@NonNull Model model) { + // We expect Model to be a Uri or a String, both of which implement toString() efficiently. We + // should reconsider this implementation before adding any new Model types. + return model.toString().startsWith(DATA_SCHEME_IMAGE); } /** @@ -108,9 +112,11 @@ public DataSource getDataSource() { } /** - * Factory for loading {@link InputStream} from Data URL string. + * Factory for loading {@link InputStream}s from data uris. + * + * @param The type of Model we can obtain data for, e.g. String. */ - public static final class StreamFactory implements ModelLoaderFactory { + public static final class StreamFactory implements ModelLoaderFactory { private final DataDecoder opener; @@ -152,7 +158,7 @@ public Class getDataClass() { @NonNull @Override - public ModelLoader build( + public ModelLoader build( @NonNull MultiModelLoaderFactory multiFactory) { return new DataUrlLoader<>(opener); } diff --git a/library/test/src/test/java/com/bumptech/glide/load/model/DataUrlLoaderTest.java b/library/test/src/test/java/com/bumptech/glide/load/model/DataUrlLoaderTest.java index d88efdcb82..440516b266 100644 --- a/library/test/src/test/java/com/bumptech/glide/load/model/DataUrlLoaderTest.java +++ b/library/test/src/test/java/com/bumptech/glide/load/model/DataUrlLoaderTest.java @@ -56,18 +56,17 @@ public class DataUrlLoaderTest { @Mock private MultiModelLoaderFactory multiFactory; - private DataUrlLoader dataUrlLoader; + private DataUrlLoader dataUrlLoader; private DataFetcher fetcher; private Options options; @Before public void setUp() { MockitoAnnotations.initMocks(this); - DataUrlLoader.StreamFactory factory = new DataUrlLoader.StreamFactory(); + DataUrlLoader.StreamFactory factory = new DataUrlLoader.StreamFactory<>(); options = new Options(); - dataUrlLoader = (DataUrlLoader) factory.build(multiFactory); + dataUrlLoader = (DataUrlLoader) factory.build(multiFactory); fetcher = dataUrlLoader.buildLoadData(VALID_PNG, -1, -1, options).fetcher; - } @Test