diff options
author | Sam Judd <judds@google.com> | 2014-09-26 18:55:41 -0700 |
---|---|---|
committer | Sam Judd <judds@google.com> | 2014-09-28 11:26:26 -0700 |
commit | 4185efc3779f8caf2dd8103aa84705e7a88f679c (patch) | |
tree | a9ce24e71701d788375af1976619d6b285145c96 /library/src/main/java/com/bumptech | |
parent | e08fca6797945ad47b4d895a63bf5b3edc08c3be (diff) | |
download | glide-4185efc3779f8caf2dd8103aa84705e7a88f679c.tar.gz |
Decode first gif frame before returning resource.
Fixes #159.
Diffstat (limited to 'library/src/main/java/com/bumptech')
4 files changed, 91 insertions, 79 deletions
diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java index 146a8a4b..3014c4d0 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawable.java @@ -14,6 +14,7 @@ import android.os.Build; import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifHeader; import com.bumptech.glide.load.Transformation; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.resource.drawable.GlideDrawable; /** @@ -47,11 +48,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC /** * Constructor for GifDrawable. * - * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, int, int) + * @see #setFrameTransformation(com.bumptech.glide.load.Transformation, android.graphics.Bitmap) * * @param context A context. * @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to * retrieve re-usable {@link android.graphics.Bitmap}s. + * @param bitmapPool A {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} that can be used to return + * the first frame when this drawable is recycled. * @param frameTransformation An {@link com.bumptech.glide.load.Transformation} that can be applied to each frame. * @param targetFrameWidth The desired width of the frames displayed by this drawable (the width of the view or * {@link com.bumptech.glide.request.target.Target} this drawable is being loaded into). @@ -60,15 +63,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC * @param id An id that uniquely identifies this particular gif. * @param gifHeader The header data for this gif. * @param data The full bytes of the gif. - * @param finalFrameWidth The final width of the frames displayed by this drawable after they have been transformed. - * @param finalFrameHeight The final height of the frames displayed by this drwaable after they have been - * transformed. + * @param firstFrame The decoded and transformed first frame of this gif. */ - public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider, + public GifDrawable(Context context, GifDecoder.BitmapProvider bitmapProvider, BitmapPool bitmapPool, Transformation<Bitmap> frameTransformation, int targetFrameWidth, int targetFrameHeight, String id, - GifHeader gifHeader, byte[] data, int finalFrameWidth, int finalFrameHeight) { + GifHeader gifHeader, byte[] data, Bitmap firstFrame) { this(new GifState(id, gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight, - bitmapProvider, finalFrameWidth, finalFrameHeight)); + bitmapProvider, bitmapPool, firstFrame)); } private GifDrawable(GifState state) { @@ -76,23 +77,25 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC this.decoder = new GifDecoder(state.bitmapProvider); decoder.setData(state.id, state.gifHeader, state.data); frameManager = new GifFrameManager(state.context, decoder, state.frameTransformation, state.targetWidth, - state.targetHeight, state.finalFrameWidth, state.finalFrameHeight); + state.targetHeight, state.firstFrame.getWidth(), state.firstFrame.getHeight()); } - // For testing. - GifDrawable(GifDecoder decoder, GifFrameManager frameManager, int finalFrameWidth, int finalFrameHeight) { + // Visible for testing. + GifDrawable(GifDecoder decoder, GifFrameManager frameManager, Bitmap firstFrame, BitmapPool bitmapPool) { this.decoder = decoder; this.frameManager = frameManager; this.state = new GifState(null); - state.finalFrameWidth = finalFrameWidth; - state.finalFrameHeight = finalFrameHeight; + state.bitmapPool = bitmapPool; + state.firstFrame = firstFrame; + } + + public Bitmap getFirstFrame() { + return state.firstFrame; } - public void setFrameTransformation(Transformation<Bitmap> frameTransformation, int finalFrameWidth, - int finalFrameHeight) { + public void setFrameTransformation(Transformation<Bitmap> frameTransformation, Bitmap firstFrame) { state.frameTransformation = frameTransformation; - state.finalFrameWidth = finalFrameWidth; - state.finalFrameHeight = finalFrameHeight; + state.firstFrame = firstFrame; } public Transformation<Bitmap> getFrameTransformation() { @@ -147,12 +150,12 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC @Override public int getIntrinsicWidth() { - return state.finalFrameWidth; + return state.firstFrame.getWidth(); } @Override public int getIntrinsicHeight() { - return state.finalFrameHeight; + return state.firstFrame.getHeight(); } @Override @@ -167,9 +170,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC @Override public void draw(Canvas canvas) { - if (currentFrame != null) { - canvas.drawBitmap(currentFrame, 0, 0, paint); - } + Bitmap toDraw = currentFrame != null ? currentFrame : state.firstFrame; + canvas.drawBitmap(toDraw, 0, 0, paint); } @Override @@ -224,6 +226,7 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC */ public void recycle() { isRecycled = true; + state.bitmapPool.put(state.firstFrame); frameManager.clear(); } @@ -255,22 +258,22 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC String id; GifHeader gifHeader; byte[] data; - int finalFrameWidth; - int finalFrameHeight; Context context; Transformation<Bitmap> frameTransformation; int targetWidth; int targetHeight; GifDecoder.BitmapProvider bitmapProvider; + BitmapPool bitmapPool; + Bitmap firstFrame; public GifState(String id, GifHeader header, byte[] data, Context context, Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight, - GifDecoder.BitmapProvider provider, int finalFrameWidth, int finalFrameHeight) { + GifDecoder.BitmapProvider provider, BitmapPool bitmapPool, Bitmap firstFrame) { this.id = id; gifHeader = header; this.data = data; - this.finalFrameWidth = finalFrameWidth; - this.finalFrameHeight = finalFrameHeight; + this.bitmapPool = bitmapPool; + this.firstFrame = firstFrame; this.context = context.getApplicationContext(); this.frameTransformation = frameTransformation; this.targetWidth = targetWidth; @@ -288,8 +291,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC targetWidth = original.targetWidth; targetHeight = original.targetHeight; bitmapProvider = original.bitmapProvider; - finalFrameWidth = original.finalFrameWidth; - finalFrameHeight = original.finalFrameHeight; + bitmapPool = original.bitmapPool; + firstFrame = original.firstFrame; } } diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java index 7123834c..7d2b67f2 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableResource.java @@ -1,6 +1,7 @@ package com.bumptech.glide.load.resource.gif; import com.bumptech.glide.load.resource.drawable.DrawableResource; +import com.bumptech.glide.util.Util; /** * A resource wrapping an {@link com.bumptech.glide.load.resource.gif.GifDrawable}. @@ -12,7 +13,7 @@ public class GifDrawableResource extends DrawableResource<GifDrawable> { @Override public int getSize() { - return drawable.getData().length; + return drawable.getData().length + Util.getBitmapByteSize(drawable.getFirstFrame()); } @Override diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java index db1c48d3..5f7b21f6 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifDrawableTransformation.java @@ -1,8 +1,6 @@ package com.bumptech.glide.load.resource.gif; import android.graphics.Bitmap; - -import com.bumptech.glide.load.MultiTransformation; import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; @@ -24,32 +22,20 @@ public class GifDrawableTransformation implements Transformation<GifDrawable> { @Override public Resource<GifDrawable> transform(Resource<GifDrawable> resource, int outWidth, int outHeight) { GifDrawable drawable = resource.get(); - @SuppressWarnings("unchecked") - Transformation<Bitmap> newTransformation = - new MultiTransformation<Bitmap>(drawable.getFrameTransformation(), wrapped); // The drawable needs to be initialized with the correct width and height in order for a view displaying it // to end up with the right dimensions. Since our transformations may arbitrarily modify the dimensions of // our gif, here we create a stand in for a frame and pass it to the transformation to see what the final - // transformed dimensions will be so that our drawable can report the correct intrinsict width and height. - Bitmap toTest = bitmapPool.get(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), - Bitmap.Config.RGB_565); - if (toTest == null) { - toTest = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), - Bitmap.Config.RGB_565); - } - - Resource<Bitmap> bitmapResource = new BitmapResource(toTest, bitmapPool); - Resource<Bitmap> transformed = newTransformation.transform(bitmapResource, outWidth, outHeight); + // transformed dimensions will be so that our drawable can report the correct intrinsic width and height. + Bitmap firstFrame = resource.get().getFirstFrame(); + Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool); + Resource<Bitmap> transformed = wrapped.transform(bitmapResource, outWidth, outHeight); if (bitmapResource != transformed) { bitmapResource.recycle(); } - Bitmap bitmap = transformed.get(); - final int transformedWidth = bitmap.getWidth(); - final int transformedHeight = bitmap.getHeight(); - transformed.recycle(); + Bitmap transformedFrame = transformed.get(); - drawable.setFrameTransformation(newTransformation, transformedWidth, transformedHeight); + drawable.setFrameTransformation(wrapped, transformedFrame); return resource; } diff --git a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java index f2725efb..cce52419 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/gif/GifResourceDecoder.java @@ -3,7 +3,6 @@ package com.bumptech.glide.load.resource.gif; import android.content.Context; import android.graphics.Bitmap; import android.util.Log; - import com.bumptech.glide.Glide; import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifHeader; @@ -28,22 +27,30 @@ import java.util.UUID; */ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> { private static final String TAG = "GifResourceDecoder"; - private static final GifHeaderParserPool PARSER_POOL = new DefaultGifHeaderParserPool(); + private static final GifHeaderParserPool PARSER_POOL = new GifHeaderParserPool(); + private static final GifDecoderPool DECODER_POOL = new GifDecoderPool(); + private final Context context; - private final BitmapPool bitmapPool; private final GifHeaderParserPool parserPool; + private BitmapPool bitmapPool; + private final GifDecoderPool decoderPool; + private final GifBitmapProvider provider; public GifResourceDecoder(Context context) { this(context, Glide.get(context).getBitmapPool()); } public GifResourceDecoder(Context context, BitmapPool bitmapPool) { - this(context, bitmapPool, PARSER_POOL); + this(context, bitmapPool, PARSER_POOL, DECODER_POOL); } - GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool) { + // Visible for testing. + GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool, + GifDecoderPool decoderPool) { this.context = context; this.bitmapPool = bitmapPool; + this.decoderPool = decoderPool; + this.provider = new GifBitmapProvider(bitmapPool); this.parserPool = parserPool; } @@ -51,14 +58,16 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa public GifDrawableResource decode(InputStream source, int width, int height) { byte[] data = inputStreamToBytes(source); final GifHeaderParser parser = parserPool.obtain(data); + final GifDecoder decoder = decoderPool.obtain(provider); try { - return decode(data, width, height, parser); + return decode(data, width, height, parser, decoder); } finally { parserPool.release(parser); + decoderPool.release(decoder); } } - private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser) { + private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) { final GifHeader header = parser.parseHeader(); if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) { // If we couldn't decode the GIF, we will end up with a frame count of 0. @@ -66,14 +75,21 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa } String id = getGifId(data); + Bitmap firstFrame = decodeFirstFrame(decoder, id, header, data); + Transformation<Bitmap> unitTransformation = UnitTransformation.get(); - Transformation<Bitmap> transformation = UnitTransformation.get(); - GifDrawable gifDrawable = new GifDrawable(context, new GifBitmapProvider(bitmapPool), transformation, width, - height, id, header, data, header.getWidth(), header.getHeight()); + GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height, id, + header, data, firstFrame); return new GifDrawableResource(gifDrawable); } + private Bitmap decodeFirstFrame(GifDecoder decoder, String id, GifHeader header, byte[] data) { + decoder.setData(id, header, data); + decoder.advance(); + return decoder.getNextFrame(); + } + @Override public String getId() { return ""; @@ -94,12 +110,12 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa } private static byte[] inputStreamToBytes(InputStream is) { - final int bufferSize = 16384, initialCapacity = bufferSize; - ByteArrayOutputStream buffer = new ByteArrayOutputStream(initialCapacity); + final int bufferSize = 16384; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize); try { int nRead; byte[] data = new byte[bufferSize]; - while ((nRead = is.read(data, 0, data.length)) != -1) { + while ((nRead = is.read(data)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); @@ -110,32 +126,38 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa return buffer.toByteArray(); } - interface GifHeaderParserPool { - public GifHeaderParser obtain(byte[] data); - public void release(GifHeaderParser parser); + // Visible for testing. + static class GifDecoderPool { + private final Queue<GifDecoder> pool = Util.createQueue(0); + + public synchronized GifDecoder obtain(GifDecoder.BitmapProvider bitmapProvider) { + GifDecoder result = pool.poll(); + if (result == null) { + result = new GifDecoder(bitmapProvider); + } + return result; + } + + public synchronized void release(GifDecoder decoder) { + decoder.clear(); + pool.offer(decoder); + } } - private static class DefaultGifHeaderParserPool implements GifHeaderParserPool { - private static final Queue<GifHeaderParser> POOL = Util.createQueue(0); + // Visible for testing. + static class GifHeaderParserPool { + private final Queue<GifHeaderParser> pool = Util.createQueue(0); - @Override - public GifHeaderParser obtain(byte[] data) { - GifHeaderParser result; - synchronized (POOL) { - result = POOL.poll(); - } + public synchronized GifHeaderParser obtain(byte[] data) { + GifHeaderParser result = pool.poll(); if (result == null) { result = new GifHeaderParser(); } - return result.setData(data); } - @Override - public void release(GifHeaderParser parser) { - synchronized (POOL) { - POOL.offer(parser); - } + public synchronized void release(GifHeaderParser parser) { + pool.offer(parser); } } |