From 7a58fe728839fce9519e9b0656668b316db52777 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Mon, 19 Aug 2013 09:56:41 -0700 Subject: [PATCH] Allow chaining of arbitrary # of transformations Also includes some other cleanup, including allowing arbitrary downsamplers. --- library/src/com/bumptech/glide/Glide.java | 132 ++++++++++-------- .../loader/transformation/CenterCrop.java | 5 + .../loader/transformation/FitCenter.java | 5 + .../transformation/MultiTransformation.java | 44 ++++++ .../MultiTransformationLoader.java | 48 +++++++ .../glide/loader/transformation/None.java | 5 + .../transformation/TransformationLoader.java | 7 + .../src/com/bumptech/glide/GlideTest.java | 96 +++++++------ 8 files changed, 240 insertions(+), 102 deletions(-) create mode 100644 library/src/com/bumptech/glide/loader/transformation/MultiTransformation.java create mode 100644 library/src/com/bumptech/glide/loader/transformation/MultiTransformationLoader.java diff --git a/library/src/com/bumptech/glide/Glide.java b/library/src/com/bumptech/glide/Glide.java index 609fd8006c..c7f6b5d43c 100644 --- a/library/src/com/bumptech/glide/Glide.java +++ b/library/src/com/bumptech/glide/Glide.java @@ -18,6 +18,8 @@ import com.bumptech.glide.loader.stream.StreamLoader; import com.bumptech.glide.loader.transformation.CenterCrop; import com.bumptech.glide.loader.transformation.FitCenter; +import com.bumptech.glide.loader.transformation.MultiTransformationLoader; +import com.bumptech.glide.loader.transformation.None; import com.bumptech.glide.loader.transformation.TransformationLoader; import com.bumptech.glide.presenter.ImagePresenter; import com.bumptech.glide.presenter.ImageReadyCallback; @@ -32,6 +34,7 @@ import java.io.File; import java.lang.ref.WeakReference; import java.net.URL; +import java.util.ArrayList; import java.util.Map; import java.util.WeakHashMap; @@ -398,22 +401,14 @@ public static class Request { private Context context; private Target target; - private enum ResizeOption { - APPROXIMATE, - CENTER_CROP, - FIT_CENTER, - AS_IS, - } - private ModelLoaderFactory modelLoaderFactory; private final T model; private int animationId = -1; private int placeholderId = -1; private int errorId = -1; - private Transformation transformation = Transformation.NONE; private Downsampler downsampler = Downsampler.AT_LEAST; - private TransformationLoader transformationLoader = null; + private ArrayList> transformationLoaders = new ArrayList>(); private Request(T model) { this(model, GLIDE.getFactory(model)); @@ -432,77 +427,93 @@ private Request(T model, ModelLoaderFactory factory) { } /** - * Resize models using {@link CenterCrop}. Replaces any existing resize style + * Load images at a size near the size of the target using {@link Downsampler#AT_LEAST}. + * + * @see #downsample(com.bumptech.glide.resize.load.Downsampler) * * @return This Request */ - public Request centerCrop() { - transformation = Transformation.CENTER_CROP; - downsampler = Downsampler.AT_LEAST; - transformationLoader = null; - - return this; + public Request approximate() { + return downsample(Downsampler.AT_LEAST); } /** - * Resize models using {@link FitCenter}. Replaces any existing resize style + * Load images at their original size using {@link Downsampler#NONE}. + * + * @see #downsample(com.bumptech.glide.resize.load.Downsampler) * * @return This Request */ - public Request fitCenter() { - transformation = Transformation.FIT_CENTER; - downsampler = Downsampler.AT_LEAST; - transformationLoader = null; - - return this; + public Request asIs() { + return downsample(Downsampler.NONE); } /** - * Load images at a size near the size of the target using {@link Downsampler#AT_LEAST}. Replaces any existing resize style + * Load images using the given {@link Downsampler}. Replaces any existing downsampler. Defaults to + * {@link Downsampler#AT_LEAST} * + * @param downsampler The downsampler * @return This Request */ - public Request approximate() { - transformation = Transformation.NONE; - downsampler = Downsampler.AT_LEAST; - transformationLoader = null; + public Request downsample(Downsampler downsampler) { + this.downsampler = downsampler; return this; } /** - * Load images at their original size using {@link Downsampler#NONE}. Replaces any existing - * resize style + * Transform images using {@link com.bumptech.glide.loader.transformation.CenterCrop}. + * + * @see #transform(com.bumptech.glide.loader.transformation.TransformationLoader) * * @return This Request */ - public Request asIs() { - transformation = Transformation.NONE; - downsampler = Downsampler.NONE; - transformationLoader = null; + public Request centerCrop() { + return transform(new CenterCrop()); + } - return this; + /** + * Transform images using {@link com.bumptech.glide.loader.transformation.FitCenter}. + * + * @see #transform(com.bumptech.glide.loader.transformation.TransformationLoader) + * + * @return This Request + */ + public Request fitCenter() { + return transform(new FitCenter()); } /** - * Set an arbitrary transformation to apply after an image has been loaded into memory. Replaces any existing - * resize style + * Set an arbitrary transformation to apply after an image has been loaded into memory. + * + * @see #transform(com.bumptech.glide.loader.transformation.TransformationLoader) * * @param transformation The transformation to use * @return This Request */ public Request transform(final Transformation transformation) { - this.transformation = transformation; - downsampler = Downsampler.AT_LEAST; - transformationLoader = null; + return transform(new TransformationLoader() { + @Override + public Transformation getTransformation(T model) { + return transformation; + } - return this; + @Override + public String getId() { + return transformation.getId(); + } + }); } + /** + * Transform images with the given {@link TransformationLoader}. Appends this transformation onto any existing + * transformations + * + * @param transformationLoader The loader to obtaian a transformation for a given model + * @return This Request + */ public Request transform(TransformationLoader transformationLoader) { - this.transformationLoader = transformationLoader; - transformation = null; - downsampler = Downsampler.AT_LEAST; + transformationLoaders.add(transformationLoader); return this; } @@ -601,7 +612,7 @@ private ImagePresenter getImagePresenter(Target target) { } private ImagePresenter buildImagePresenter(Target target) { - transformationLoader = getFinalTransformationLoader(); + TransformationLoader transformationLoader = getFinalTransformationLoader(); ImagePresenter.Builder builder = new ImagePresenter.Builder() .setTarget(target, context) @@ -633,23 +644,28 @@ public void onImageReady(Target target, boolean fromCache) { } private TransformationLoader getFinalTransformationLoader() { - if (transformationLoader != null) { - return transformationLoader; - } else { - return new TransformationLoader() { - @Override - public Transformation getTransformation(T model) { - return transformation; - } - }; + switch (transformationLoaders.size()) { + case 0: + return new None(); + case 1: + return transformationLoaders.get(0); + default: + return new MultiTransformationLoader(transformationLoaders); } } private String getFinalTransformationId() { - if (transformationLoader != null) { - return transformationLoader.getClass().toString(); - } else { - return transformation.getId(); + switch (transformationLoaders.size()) { + case 0: + return Transformation.NONE.getId(); + case 1: + return transformationLoaders.get(0).getId(); + default: + StringBuilder sb = new StringBuilder(); + for (TransformationLoader transformationLoader : transformationLoaders) { + sb.append(transformationLoader.getId()); + } + return sb.toString(); } } } diff --git a/library/src/com/bumptech/glide/loader/transformation/CenterCrop.java b/library/src/com/bumptech/glide/loader/transformation/CenterCrop.java index f047d76150..088f8bc0ce 100644 --- a/library/src/com/bumptech/glide/loader/transformation/CenterCrop.java +++ b/library/src/com/bumptech/glide/loader/transformation/CenterCrop.java @@ -12,4 +12,9 @@ public class CenterCrop implements TransformationLoader{ public Transformation getTransformation(T model) { return Transformation.CENTER_CROP; } + + @Override + public String getId() { + return Transformation.CENTER_CROP.getId(); + } } diff --git a/library/src/com/bumptech/glide/loader/transformation/FitCenter.java b/library/src/com/bumptech/glide/loader/transformation/FitCenter.java index 9a65b40610..9434dfade8 100644 --- a/library/src/com/bumptech/glide/loader/transformation/FitCenter.java +++ b/library/src/com/bumptech/glide/loader/transformation/FitCenter.java @@ -13,4 +13,9 @@ public class FitCenter implements TransformationLoader { public Transformation getTransformation(T model) { return Transformation.FIT_CENTER; } + + @Override + public String getId() { + return Transformation.FIT_CENTER.getId(); + } } diff --git a/library/src/com/bumptech/glide/loader/transformation/MultiTransformation.java b/library/src/com/bumptech/glide/loader/transformation/MultiTransformation.java new file mode 100644 index 0000000000..ddaef5dc81 --- /dev/null +++ b/library/src/com/bumptech/glide/loader/transformation/MultiTransformation.java @@ -0,0 +1,44 @@ +package com.bumptech.glide.loader.transformation; + +import android.graphics.Bitmap; +import com.bumptech.glide.resize.bitmap_recycle.BitmapPool; +import com.bumptech.glide.resize.load.Transformation; + +/** + * A transformation that applies an ordered array of one or more transformations to an image + */ +public class MultiTransformation extends Transformation { + private final Transformation[] transformations; + + public MultiTransformation(Transformation... transformations) { + if (transformations.length < 1) { + throw new IllegalArgumentException("MultiTransformation must contain at least one Transformation"); + } + this.transformations = transformations; + } + + @Override + public Bitmap transform(Bitmap bitmap, BitmapPool pool, int outWidth, int outHeight) { + Bitmap current = null; //we don't want to recycle the original image since that + //will be done by the ImageResizer + Bitmap transformed; + for (Transformation transformation : transformations) { + transformed = transformation.transform(bitmap, pool, outWidth, outHeight); + if (current != null && current != transformed) { + pool.put(current); + } + + current = transformed; + } + return current; + } + + @Override + public String getId() { + StringBuilder sb = new StringBuilder(); + for (Transformation transformation : transformations) { + sb.append(transformation.getId()); + } + return sb.toString(); + } +} diff --git a/library/src/com/bumptech/glide/loader/transformation/MultiTransformationLoader.java b/library/src/com/bumptech/glide/loader/transformation/MultiTransformationLoader.java new file mode 100644 index 0000000000..05e18b13bd --- /dev/null +++ b/library/src/com/bumptech/glide/loader/transformation/MultiTransformationLoader.java @@ -0,0 +1,48 @@ +package com.bumptech.glide.loader.transformation; + +import com.bumptech.glide.resize.load.Transformation; + +import java.util.Arrays; +import java.util.List; + +/** + * A TransformationLoader that uses an ordered list of one or more transformation loaders to produce + * a single transformation that applies each of the {@link Transformation}s produced by the loaders + * in order. + */ +public class MultiTransformationLoader implements TransformationLoader { + private final List> transformationLoaders; + + @SuppressWarnings("unused") + public MultiTransformationLoader(TransformationLoader... transformationLoaders) { + this(Arrays.asList(transformationLoaders)); + } + + public MultiTransformationLoader(List> transformationLoaders) { + if (transformationLoaders.size() < 1) { + throw new IllegalArgumentException("MultiTransformationLoader must contain at least one " + + "TransformationLoader"); + } + this.transformationLoaders = transformationLoaders; + } + + @Override + public Transformation getTransformation(T model) { + int num = transformationLoaders.size(); + Transformation[] transformations = new Transformation[num]; + for (int i = 0; i < num; i++) { + transformations[i] = transformationLoaders.get(i).getTransformation(model); + } + + return new MultiTransformation(transformations); + } + + @Override + public String getId() { + StringBuilder sb = new StringBuilder(); + for (TransformationLoader transformationLoader : transformationLoaders) { + sb.append(transformationLoader.getId()); + } + return sb.toString(); + } +} diff --git a/library/src/com/bumptech/glide/loader/transformation/None.java b/library/src/com/bumptech/glide/loader/transformation/None.java index f54d3b78d3..2577b0de7b 100644 --- a/library/src/com/bumptech/glide/loader/transformation/None.java +++ b/library/src/com/bumptech/glide/loader/transformation/None.java @@ -12,4 +12,9 @@ public class None implements TransformationLoader { public Transformation getTransformation(T model) { return Transformation.NONE; } + + @Override + public String getId() { + return Transformation.NONE.getId(); + } } diff --git a/library/src/com/bumptech/glide/loader/transformation/TransformationLoader.java b/library/src/com/bumptech/glide/loader/transformation/TransformationLoader.java index fbd212ac45..8baf42200a 100644 --- a/library/src/com/bumptech/glide/loader/transformation/TransformationLoader.java +++ b/library/src/com/bumptech/glide/loader/transformation/TransformationLoader.java @@ -16,4 +16,11 @@ public interface TransformationLoader { * @return A new or static (if the transformation is type/model agnostic) {@link Transformation} */ public Transformation getTransformation(T model); + + /** + * Get a unique identifier for this TransformationLoader. + * + * @return A unique String identifier + */ + public String getId(); } diff --git a/library/tests/src/com/bumptech/glide/GlideTest.java b/library/tests/src/com/bumptech/glide/GlideTest.java index ef90c4b8fb..f192caee77 100644 --- a/library/tests/src/com/bumptech/glide/GlideTest.java +++ b/library/tests/src/com/bumptech/glide/GlideTest.java @@ -7,6 +7,7 @@ import com.bumptech.glide.loader.model.ModelLoader; import com.bumptech.glide.loader.stream.StreamLoader; import com.bumptech.glide.presenter.ImagePresenter; +import com.bumptech.glide.tests.R; import java.io.File; import java.net.MalformedURLException; @@ -80,6 +81,7 @@ public void testIntegerDefaultLoader() { public void testGlideDoesNotReplaceIdenticalPresenters() { Glide.load("fake") + .asIs() .centerCrop() .animate(android.R.anim.fade_in) .placeholder(com.bumptech.glide.tests.R.raw.ic_launcher) @@ -88,6 +90,7 @@ public void testGlideDoesNotReplaceIdenticalPresenters() { ImagePresenter first = getImagePresenterFromView(); Glide.load("fake2") + .asIs() .centerCrop() .animate(android.R.anim.fade_in) .placeholder(com.bumptech.glide.tests.R.raw.ic_launcher) @@ -106,18 +109,15 @@ public void testCanHandleWrapContent() { assertNotNull(getImagePresenterFromView()); } - public void testDifferentModelsReplacesPresenters() { - Glide.load("fake").into(imageView); - - ImagePresenter first = getImagePresenterFromView(); - Glide.load(4).into(imageView); - ImagePresenter second = getImagePresenterFromView(); - - assertNotSame(first, second); + public void testDifferentModelTypesReplacesPresenters() { + assertDifferentPresenters( + Glide.load(4), + Glide.load("fake") + ); } public void testDifferentModelLoadersReplacesPresenter() { - Glide.using(new ModelLoader() { + ModelLoader first = new ModelLoader() { @Override public StreamLoader getStreamLoader(Object model, int width, int height) { return new StreamLoader() { @@ -133,14 +133,12 @@ public void cancel() { @Override public String getId(Object model) { - return String.valueOf(model.hashCode()); + return model.toString(); } - }).load(new Object()).into(imageView); - - ImagePresenter first = getImagePresenterFromView(); + }; - Glide.using(new ModelLoader() { + ModelLoader second = new ModelLoader() { @Override public StreamLoader getStreamLoader(Object model, int width, int height) { return new StreamLoader() { @@ -156,58 +154,68 @@ public void cancel() { @Override public String getId(Object model) { - return String.valueOf(model.hashCode()); + return model.toString(); } + }; - }).load(new Object()).into(imageView); - - ImagePresenter second = getImagePresenterFromView(); + final Object object = new Object(); + assertDifferentPresenters( + Glide.using(first).load(object), + Glide.using(second).load(object) + ); + } - assertNotSame(first, second); + public void testDifferentDownsamplersReplacesPresenter() { + assertDifferentPresenters( + Glide.load("a").approximate(), + Glide.load("a").asIs() + ); } - public void testDifferentImageLoadersReplacesPresenter() { + public void testDifferentTransformationsReplacesPresenter() { final File file = new File("fake"); - Glide.load(file).centerCrop().into(imageView); - ImagePresenter first = getImagePresenterFromView(); - - Glide.load(file).into(imageView); - ImagePresenter second = getImagePresenterFromView(); - - assertNotSame(first, second); + assertDifferentPresenters( + Glide.load(file).centerCrop().fitCenter(), + Glide.load(file).centerCrop() + ); } public void testDifferentPlaceholdersReplacesPresenter() { final File file = new File("fake"); - Glide.load(file).placeholder(com.bumptech.glide.tests.R.raw.ic_launcher).into(imageView); - ImagePresenter first = getImagePresenterFromView(); + assertDifferentPresenters( + Glide.load(file).placeholder(com.bumptech.glide.tests.R.raw.ic_launcher), + Glide.load(file) - Glide.load(file).into(imageView); - ImagePresenter second = getImagePresenterFromView(); - - assertNotSame(first, second); + ); } public void testDifferentAnimationsReplacesPresenter() { final File file = new File("fake"); - Glide.load(file).animate(android.R.anim.fade_in).into(imageView); - ImagePresenter first = getImagePresenterFromView(); - - Glide.load(file).animate(android.R.anim.fade_out).into(imageView); - ImagePresenter second = getImagePresenterFromView(); - - assertNotSame(first, second); + assertDifferentPresenters( + Glide.load(file).animate(android.R.anim.fade_in), + Glide.load(file).animate(android.R.anim.fade_out) + ); } public void testDifferentErrorIdsReplacesPresenter() { - final File file = new File("fake"); - Glide.load(file).error(com.bumptech.glide.tests.R.raw.ic_launcher).into(imageView); + assertDifferentPresenters( + Glide.load("b").error(R.raw.ic_launcher), + Glide.load("b").error(android.R.drawable.btn_star) + ); + } + + private void assertDifferentPresenters(Glide.Request a, Glide.Request b) { + a.into(imageView); ImagePresenter first = getImagePresenterFromView(); - Glide.load(file).error(android.R.drawable.btn_star).into(imageView); + a.into(imageView); ImagePresenter second = getImagePresenterFromView(); - assertNotSame(first, second); + b.into(imageView); + ImagePresenter third = getImagePresenterFromView(); + + assertSame(first, second); + assertNotSame(first, third); } }