提交 4185efc3 编写于 作者: S Sam Judd

Decode first gif frame before returning resource.

Fixes #159.
上级 e08fca67
...@@ -49,6 +49,7 @@ import org.robolectric.RobolectricTestRunner; ...@@ -49,6 +49,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowBitmap;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
...@@ -79,7 +80,7 @@ import static org.mockito.Mockito.when; ...@@ -79,7 +80,7 @@ import static org.mockito.Mockito.when;
*/ */
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = { GlideTest.ShadowFileDescriptorContentResolver.class, GlideTest.ShadowMediaMetadataRetriever.class, @Config(shadows = { GlideTest.ShadowFileDescriptorContentResolver.class, GlideTest.ShadowMediaMetadataRetriever.class,
GlideShadowLooper.class }) GlideShadowLooper.class, GlideTest.MutableShadowBitmap.class })
public class GlideTest { public class GlideTest {
private Target target = null; private Target target = null;
private ImageView imageView; private ImageView imageView;
...@@ -808,6 +809,17 @@ public class GlideTest { ...@@ -808,6 +809,17 @@ public class GlideTest {
} }
} }
@Implements(Bitmap.class)
public static class MutableShadowBitmap extends ShadowBitmap {
@Implementation
public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
Bitmap bitmap = ShadowBitmap.createBitmap(width, height, config);
Robolectric.shadowOf(bitmap).setMutable(true);
return bitmap;
}
}
@Implements(MediaMetadataRetriever.class) @Implements(MediaMetadataRetriever.class)
public static class ShadowMediaMetadataRetriever { public static class ShadowMediaMetadataRetriever {
......
package com.bumptech.glide.load.resource.gif; package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import com.bumptech.glide.util.Util;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -41,11 +42,13 @@ public class GifDrawableResourceTest { ...@@ -41,11 +42,13 @@ public class GifDrawableResourceTest {
} }
@Test @Test
public void testReturnsDrawableSize() { public void testReturnsDrawableSizePlusFirstFrameSize() {
final int size = 2134; final int size = 2134;
Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(drawable.getFirstFrame()).thenReturn(firstFrame);
when(drawable.getData()).thenReturn(new byte[size]); when(drawable.getData()).thenReturn(new byte[size]);
assertEquals(size, resource.getSize()); assertEquals(size + Util.getBitmapByteSize(firstFrame), resource.getSize());
} }
@Test @Test
......
...@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable; ...@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable;
import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader; import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.tests.GlideShadowLooper; import com.bumptech.glide.tests.GlideShadowLooper;
...@@ -42,24 +43,47 @@ public class GifDrawableTest { ...@@ -42,24 +43,47 @@ public class GifDrawableTest {
private Drawable.Callback cb = mock(Drawable.Callback.class); private Drawable.Callback cb = mock(Drawable.Callback.class);
private int frameHeight; private int frameHeight;
private int frameWidth; private int frameWidth;
private Bitmap firstFrame;
private BitmapPool bitmapPool;
@Before @Before
public void setUp() { public void setUp() {
frameWidth = 120; frameWidth = 120;
frameHeight = 450; frameHeight = 450;
gifDecoder = mock(GifDecoder.class); gifDecoder = mock(GifDecoder.class);
drawable = new GifDrawable(gifDecoder, frameManager, frameWidth, frameHeight); firstFrame = Bitmap.createBitmap(frameWidth, frameHeight, Bitmap.Config.RGB_565);
bitmapPool = mock(BitmapPool.class);
drawable = new GifDrawable(gifDecoder, frameManager, firstFrame, bitmapPool);
drawable.setCallback(cb); drawable.setCallback(cb);
} }
@Test @Test
public void testShouldNotDrawNullBitmap() { public void testShouldDrawFirstFrameBeforeAnyFrameRead() {
Canvas canvas = mock(Canvas.class); Canvas canvas = mock(Canvas.class);
drawable.draw(canvas); drawable.draw(canvas);
verify(canvas).drawBitmap(eq(firstFrame), anyInt(), anyInt(), any(Paint.class));
}
@Test
public void testShouldNotDrawNullBitmapFrame() {
Canvas canvas = mock(Canvas.class);
drawable = new GifDrawable(gifDecoder, frameManager, firstFrame, bitmapPool);
drawable.onFrameRead(null, 0);
drawable.draw(canvas);
verify(canvas).drawBitmap(eq(firstFrame), anyInt(), anyInt(), any(Paint.class));
verify(canvas, never()).drawBitmap((Bitmap) isNull(), anyInt(), anyInt(), any(Paint.class)); verify(canvas, never()).drawBitmap((Bitmap) isNull(), anyInt(), anyInt(), any(Paint.class));
} }
@Test
public void testDoesNotDrawNullFirstFrame() {
drawable = new GifDrawable(gifDecoder, frameManager, null, bitmapPool);
Canvas canvas = mock(Canvas.class);
verify(canvas, never()).drawBitmap(any(Bitmap.class), anyInt(), anyInt(), any(Paint.class));
}
@Test @Test
public void testRequestsNextFrameOnStart() { public void testRequestsNextFrameOnStart() {
drawable.setVisible(true, true); drawable.setVisible(true, true);
...@@ -80,7 +104,7 @@ public class GifDrawableTest { ...@@ -80,7 +104,7 @@ public class GifDrawableTest {
drawable.setVisible(false, false); drawable.setVisible(false, false);
drawable.start(); drawable.start();
verify(frameManager, never()).getNextFrame(eq(drawable)); verify(frameManager, never()).getNextFrame(any(GifFrameManager.FrameCallback.class));
} }
@Test @Test
...@@ -226,6 +250,13 @@ public class GifDrawableTest { ...@@ -226,6 +250,13 @@ public class GifDrawableTest {
verify(frameManager).clear(); verify(frameManager).clear();
} }
@Test
public void testRecycleReturnsFirstFrameToPool() {
drawable.recycle();
verify(bitmapPool).put(eq(firstFrame));
}
@Test @Test
public void testIsNotRecycledIfNotRecycled() { public void testIsNotRecycledIfNotRecycled() {
assertFalse(drawable.isRecycled()); assertFalse(drawable.isRecycled());
...@@ -248,8 +279,9 @@ public class GifDrawableTest { ...@@ -248,8 +279,9 @@ public class GifDrawableTest {
GifHeader gifHeader = new GifHeader(); GifHeader gifHeader = new GifHeader();
Transformation<Bitmap> transformation = mock(Transformation.class); Transformation<Bitmap> transformation = mock(Transformation.class);
GifDecoder.BitmapProvider provider = mock(GifDecoder.BitmapProvider.class); GifDecoder.BitmapProvider provider = mock(GifDecoder.BitmapProvider.class);
drawable = new GifDrawable(Robolectric.application, provider, transformation, 100, 100, "fakeId", gifHeader, Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
new byte[0], 100, 100); drawable = new GifDrawable(Robolectric.application, provider, bitmapPool, transformation, 100, 100, "fakeId",
gifHeader, new byte[0], firstFrame);
assertNotNull(drawable.getConstantState().newDrawable()); assertNotNull(drawable.getConstantState().newDrawable());
assertNotNull(drawable.getConstantState().newDrawable(Robolectric.application.getResources())); assertNotNull(drawable.getConstantState().newDrawable(Robolectric.application.getResources()));
......
...@@ -51,6 +51,9 @@ public class GifDrawableTransformationTest { ...@@ -51,6 +51,9 @@ public class GifDrawableTransformationTest {
when(gifDrawable.getIntrinsicHeight()).thenReturn(500); when(gifDrawable.getIntrinsicHeight()).thenReturn(500);
when(resource.get()).thenReturn(gifDrawable); when(resource.get()).thenReturn(gifDrawable);
Bitmap firstFrame = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
when(gifDrawable.getFirstFrame()).thenReturn(firstFrame);
final int width = 123; final int width = 123;
final int height = 456; final int height = 456;
Bitmap expectedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Bitmap expectedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
...@@ -60,6 +63,6 @@ public class GifDrawableTransformationTest { ...@@ -60,6 +63,6 @@ public class GifDrawableTransformationTest {
transformation.transform(resource, width, height); transformation.transform(resource, width, height);
verify(gifDrawable).setFrameTransformation(any(Transformation.class), eq(width), eq(height)); verify(gifDrawable).setFrameTransformation(any(Transformation.class), eq(expectedBitmap));
} }
} }
package com.bumptech.glide.load.resource.gif; package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap;
import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader; import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.gifdecoder.GifHeaderParser; import com.bumptech.glide.gifdecoder.GifHeaderParser;
...@@ -9,6 +10,8 @@ import com.bumptech.glide.tests.GlideShadowLooper; ...@@ -9,6 +10,8 @@ import com.bumptech.glide.tests.GlideShadowLooper;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
...@@ -17,10 +20,12 @@ import java.io.ByteArrayInputStream; ...@@ -17,10 +20,12 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
...@@ -31,48 +36,50 @@ public class GifResourceDecoderTest { ...@@ -31,48 +36,50 @@ public class GifResourceDecoderTest {
private GifResourceDecoder decoder; private GifResourceDecoder decoder;
private GifHeaderParser parser; private GifHeaderParser parser;
private GifResourceDecoder.GifHeaderParserPool parserPool; private GifResourceDecoder.GifHeaderParserPool parserPool;
private GifResourceDecoder.GifDecoderPool decoderPool;
private GifDecoder gifDecoder;
private GifHeader gifHeader;
@Before @Before
public void setUp() { public void setUp() {
BitmapPool bitmapPool = mock(BitmapPool.class); BitmapPool bitmapPool = mock(BitmapPool.class);
gifHeader = Mockito.spy(new GifHeader());
parser = mock(GifHeaderParser.class); parser = mock(GifHeaderParser.class);
when(parser.parseHeader()).thenReturn(gifHeader);
parserPool = mock(GifResourceDecoder.GifHeaderParserPool.class); parserPool = mock(GifResourceDecoder.GifHeaderParserPool.class);
when(parserPool.obtain(any(byte[].class))).thenReturn(parser); when(parserPool.obtain(any(byte[].class))).thenReturn(parser);
decoder = new GifResourceDecoder(Robolectric.application, bitmapPool, parserPool);
gifDecoder = mock(GifDecoder.class);
decoderPool = mock(GifResourceDecoder.GifDecoderPool.class);
when(decoderPool.obtain(any(GifDecoder.BitmapProvider.class))).thenReturn(gifDecoder);
decoder = new GifResourceDecoder(Robolectric.application, bitmapPool, parserPool, decoderPool);
} }
@Test @Test
public void testReturnsNullIfParsedHeaderHasZeroFrames() throws IOException { public void testReturnsNullIfParsedHeaderHasZeroFrames() throws IOException {
GifHeader header = mock(GifHeader.class); when(gifHeader.getNumFrames()).thenReturn(0);
when(parser.parseHeader()).thenReturn(header);
when(header.getNumFrames()).thenReturn(0);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100)); assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
} }
@Test @Test
public void testReturnsNullIfParsedHeaderHasFormatError() { public void testReturnsNullIfParsedHeaderHasFormatError() {
GifHeader header = mock(GifHeader.class); when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_FORMAT_ERROR);
when(parser.parseHeader()).thenReturn(header);
when(header.getStatus()).thenReturn(GifDecoder.STATUS_FORMAT_ERROR);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100)); assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
} }
@Test @Test
public void testReturnsNullIfParsedHeaderHasOpenError() { public void testReturnsNullIfParsedHeaderHasOpenError() {
GifHeader header = mock(GifHeader.class); when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OPEN_ERROR);
when(parser.parseHeader()).thenReturn(header);
when(header.getStatus()).thenReturn(GifDecoder.STATUS_OPEN_ERROR);
assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100)); assertNull(decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100));
} }
@Test @Test
public void testReturnsParserToPool() throws IOException { public void testReturnsParserToPool() throws IOException {
when(parserPool.obtain(any(byte[].class))).thenReturn(parser);
when(parser.parseHeader()).thenReturn(mock(GifHeader.class));
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100); decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
verify(parserPool).release(eq(parser)); verify(parserPool).release(eq(parser));
} }
...@@ -82,7 +89,7 @@ public class GifResourceDecoderTest { ...@@ -82,7 +89,7 @@ public class GifResourceDecoderTest {
when(parser.parseHeader()).thenThrow(new RuntimeException("Test")); when(parser.parseHeader()).thenThrow(new RuntimeException("Test"));
try { try {
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100); decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
fail("Expected exception is not thrown."); fail("Failed to receive expected exception");
} catch (RuntimeException e) { } catch (RuntimeException e) {
// Expected. // Expected.
} }
...@@ -90,6 +97,60 @@ public class GifResourceDecoderTest { ...@@ -90,6 +97,60 @@ public class GifResourceDecoderTest {
verify(parserPool).release(eq(parser)); verify(parserPool).release(eq(parser));
} }
@Test
public void testDecodesFirstFrameAndReturnsGifDecoderToPool() {
when(gifHeader.getNumFrames()).thenReturn(1);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OK);
when(gifDecoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
byte[] data = new byte[100];
decoder.decode(new ByteArrayInputStream(data), 100, 100);
InOrder order = inOrder(decoderPool, gifDecoder);
order.verify(decoderPool).obtain(any(GifDecoder.BitmapProvider.class));
order.verify(gifDecoder).setData(any(String.class), eq(gifHeader), eq(data));
order.verify(gifDecoder).advance();
order.verify(gifDecoder).getNextFrame();
order.verify(decoderPool).release(eq(gifDecoder));
}
@Test
public void testReturnsGifDecoderToPoolWhenDecoderThrows() {
when(gifHeader.getNumFrames()).thenReturn(1);
when(gifHeader.getStatus()).thenReturn(GifDecoder.STATUS_OK);
when(gifDecoder.getNextFrame()).thenThrow(new RuntimeException("test"));
try {
decoder.decode(new ByteArrayInputStream(new byte[0]), 100, 100);
fail("Failed to receive expected exception");
} catch (RuntimeException e) {
// Expected.
}
verify(decoderPool).release(eq(gifDecoder));
}
@Test
public void testCanObtainNonNullDecoderFromPool() {
GifDecoder.BitmapProvider provider = mock(GifDecoder.BitmapProvider.class);
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
assertNotNull(pool.obtain(provider));
}
@Test
public void testCanPutAndObtainDecoderFromPool() {
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
pool.release(gifDecoder);
GifDecoder fromPool = pool.obtain(mock(GifDecoder.BitmapProvider.class));
assertEquals(gifDecoder, fromPool);
}
@Test
public void testDecoderPoolClearsDecoders() {
GifResourceDecoder.GifDecoderPool pool = new GifResourceDecoder.GifDecoderPool();
pool.release(gifDecoder);
verify(gifDecoder).clear();
}
@Test @Test
public void testHasValidId() { public void testHasValidId() {
assertEquals("", decoder.getId()); assertEquals("", decoder.getId());
......
...@@ -14,6 +14,7 @@ import android.os.Build; ...@@ -14,6 +14,7 @@ import android.os.Build;
import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader; import com.bumptech.glide.gifdecoder.GifHeader;
import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.load.resource.drawable.GlideDrawable;
/** /**
...@@ -47,11 +48,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -47,11 +48,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
/** /**
* Constructor for GifDrawable. * 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 context A context.
* @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to * @param bitmapProvider An {@link com.bumptech.glide.gifdecoder.GifDecoder.BitmapProvider} that can be used to
* retrieve re-usable {@link android.graphics.Bitmap}s. * 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 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 * @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). * {@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 ...@@ -60,15 +63,13 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
* @param id An id that uniquely identifies this particular gif. * @param id An id that uniquely identifies this particular gif.
* @param gifHeader The header data for this gif. * @param gifHeader The header data for this gif.
* @param data The full bytes of the 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 firstFrame The decoded and transformed first frame of this gif.
* @param finalFrameHeight The final height of the frames displayed by this drwaable after they have been
* transformed.
*/ */
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, 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, this(new GifState(id, gifHeader, data, context, frameTransformation, targetFrameWidth, targetFrameHeight,
bitmapProvider, finalFrameWidth, finalFrameHeight)); bitmapProvider, bitmapPool, firstFrame));
} }
private GifDrawable(GifState state) { private GifDrawable(GifState state) {
...@@ -76,23 +77,25 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -76,23 +77,25 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
this.decoder = new GifDecoder(state.bitmapProvider); this.decoder = new GifDecoder(state.bitmapProvider);
decoder.setData(state.id, state.gifHeader, state.data); decoder.setData(state.id, state.gifHeader, state.data);
frameManager = new GifFrameManager(state.context, decoder, state.frameTransformation, state.targetWidth, 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. // Visible for testing.
GifDrawable(GifDecoder decoder, GifFrameManager frameManager, int finalFrameWidth, int finalFrameHeight) { GifDrawable(GifDecoder decoder, GifFrameManager frameManager, Bitmap firstFrame, BitmapPool bitmapPool) {
this.decoder = decoder; this.decoder = decoder;
this.frameManager = frameManager; this.frameManager = frameManager;
this.state = new GifState(null); this.state = new GifState(null);
state.finalFrameWidth = finalFrameWidth; state.bitmapPool = bitmapPool;
state.finalFrameHeight = finalFrameHeight; state.firstFrame = firstFrame;
}
public Bitmap getFirstFrame() {
return state.firstFrame;
} }
public void setFrameTransformation(Transformation<Bitmap> frameTransformation, int finalFrameWidth, public void setFrameTransformation(Transformation<Bitmap> frameTransformation, Bitmap firstFrame) {
int finalFrameHeight) {
state.frameTransformation = frameTransformation; state.frameTransformation = frameTransformation;
state.finalFrameWidth = finalFrameWidth; state.firstFrame = firstFrame;
state.finalFrameHeight = finalFrameHeight;
} }
public Transformation<Bitmap> getFrameTransformation() { public Transformation<Bitmap> getFrameTransformation() {
...@@ -147,12 +150,12 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -147,12 +150,12 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
@Override @Override
public int getIntrinsicWidth() { public int getIntrinsicWidth() {
return state.finalFrameWidth; return state.firstFrame.getWidth();
} }
@Override @Override
public int getIntrinsicHeight() { public int getIntrinsicHeight() {
return state.finalFrameHeight; return state.firstFrame.getHeight();
} }
@Override @Override
...@@ -167,9 +170,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -167,9 +170,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
if (currentFrame != null) { Bitmap toDraw = currentFrame != null ? currentFrame : state.firstFrame;
canvas.drawBitmap(currentFrame, 0, 0, paint); canvas.drawBitmap(toDraw, 0, 0, paint);
}
} }
@Override @Override
...@@ -224,6 +226,7 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -224,6 +226,7 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
*/ */
public void recycle() { public void recycle() {
isRecycled = true; isRecycled = true;
state.bitmapPool.put(state.firstFrame);
frameManager.clear(); frameManager.clear();
} }
...@@ -255,22 +258,22 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -255,22 +258,22 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
String id; String id;
GifHeader gifHeader; GifHeader gifHeader;
byte[] data; byte[] data;
int finalFrameWidth;
int finalFrameHeight;
Context context; Context context;
Transformation<Bitmap> frameTransformation; Transformation<Bitmap> frameTransformation;
int targetWidth; int targetWidth;
int targetHeight; int targetHeight;
GifDecoder.BitmapProvider bitmapProvider; GifDecoder.BitmapProvider bitmapProvider;
BitmapPool bitmapPool;
Bitmap firstFrame;
public GifState(String id, GifHeader header, byte[] data, Context context, public GifState(String id, GifHeader header, byte[] data, Context context,
Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight, Transformation<Bitmap> frameTransformation, int targetWidth, int targetHeight,
GifDecoder.BitmapProvider provider, int finalFrameWidth, int finalFrameHeight) { GifDecoder.BitmapProvider provider, BitmapPool bitmapPool, Bitmap firstFrame) {
this.id = id; this.id = id;
gifHeader = header; gifHeader = header;
this.data = data; this.data = data;
this.finalFrameWidth = finalFrameWidth; this.bitmapPool = bitmapPool;
this.finalFrameHeight = finalFrameHeight; this.firstFrame = firstFrame;
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.frameTransformation = frameTransformation; this.frameTransformation = frameTransformation;
this.targetWidth = targetWidth; this.targetWidth = targetWidth;
...@@ -288,8 +291,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -288,8 +291,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
targetWidth = original.targetWidth; targetWidth = original.targetWidth;
targetHeight = original.targetHeight; targetHeight = original.targetHeight;
bitmapProvider = original.bitmapProvider; bitmapProvider = original.bitmapProvider;
finalFrameWidth = original.finalFrameWidth; bitmapPool = original.bitmapPool;
finalFrameHeight = original.finalFrameHeight; firstFrame = original.firstFrame;
} }
} }
......
package com.bumptech.glide.load.resource.gif; package com.bumptech.glide.load.resource.gif;
import com.bumptech.glide.load.resource.drawable.DrawableResource; 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}. * A resource wrapping an {@link com.bumptech.glide.load.resource.gif.GifDrawable}.
...@@ -12,7 +13,7 @@ public class GifDrawableResource extends DrawableResource<GifDrawable> { ...@@ -12,7 +13,7 @@ public class GifDrawableResource extends DrawableResource<GifDrawable> {
@Override @Override
public int getSize() { public int getSize() {
return drawable.getData().length; return drawable.getData().length + Util.getBitmapByteSize(drawable.getFirstFrame());
} }
@Override @Override
......
package com.bumptech.glide.load.resource.gif; package com.bumptech.glide.load.resource.gif;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
...@@ -24,32 +22,20 @@ public class GifDrawableTransformation implements Transformation<GifDrawable> { ...@@ -24,32 +22,20 @@ public class GifDrawableTransformation implements Transformation<GifDrawable> {
@Override @Override
public Resource<GifDrawable> transform(Resource<GifDrawable> resource, int outWidth, int outHeight) { public Resource<GifDrawable> transform(Resource<GifDrawable> resource, int outWidth, int outHeight) {
GifDrawable drawable = resource.get(); 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 // 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 // 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 // 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. // transformed dimensions will be so that our drawable can report the correct intrinsic width and height.
Bitmap toTest = bitmapPool.get(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap firstFrame = resource.get().getFirstFrame();
Bitmap.Config.RGB_565); Resource<Bitmap> bitmapResource = new BitmapResource(firstFrame, bitmapPool);
if (toTest == null) { Resource<Bitmap> transformed = wrapped.transform(bitmapResource, outWidth, outHeight);
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);
if (bitmapResource != transformed) { if (bitmapResource != transformed) {
bitmapResource.recycle(); bitmapResource.recycle();
} }
Bitmap bitmap = transformed.get(); Bitmap transformedFrame = transformed.get();
final int transformedWidth = bitmap.getWidth();
final int transformedHeight = bitmap.getHeight();
transformed.recycle();
drawable.setFrameTransformation(newTransformation, transformedWidth, transformedHeight); drawable.setFrameTransformation(wrapped, transformedFrame);
return resource; return resource;
} }
......
...@@ -3,7 +3,6 @@ package com.bumptech.glide.load.resource.gif; ...@@ -3,7 +3,6 @@ package com.bumptech.glide.load.resource.gif;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.util.Log; import android.util.Log;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.gifdecoder.GifHeader; import com.bumptech.glide.gifdecoder.GifHeader;
...@@ -28,22 +27,30 @@ import java.util.UUID; ...@@ -28,22 +27,30 @@ import java.util.UUID;
*/ */
public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> { public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> {
private static final String TAG = "GifResourceDecoder"; 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 Context context;
private final BitmapPool bitmapPool;
private final GifHeaderParserPool parserPool; private final GifHeaderParserPool parserPool;
private BitmapPool bitmapPool;
private final GifDecoderPool decoderPool;
private final GifBitmapProvider provider;
public GifResourceDecoder(Context context) { public GifResourceDecoder(Context context) {
this(context, Glide.get(context).getBitmapPool()); this(context, Glide.get(context).getBitmapPool());
} }
public GifResourceDecoder(Context context, BitmapPool bitmapPool) { 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.context = context;
this.bitmapPool = bitmapPool; this.bitmapPool = bitmapPool;
this.decoderPool = decoderPool;
this.provider = new GifBitmapProvider(bitmapPool);
this.parserPool = parserPool; this.parserPool = parserPool;
} }
...@@ -51,14 +58,16 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa ...@@ -51,14 +58,16 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
public GifDrawableResource decode(InputStream source, int width, int height) { public GifDrawableResource decode(InputStream source, int width, int height) {
byte[] data = inputStreamToBytes(source); byte[] data = inputStreamToBytes(source);
final GifHeaderParser parser = parserPool.obtain(data); final GifHeaderParser parser = parserPool.obtain(data);
final GifDecoder decoder = decoderPool.obtain(provider);
try { try {
return decode(data, width, height, parser); return decode(data, width, height, parser, decoder);
} finally { } finally {
parserPool.release(parser); 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(); final GifHeader header = parser.parseHeader();
if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) { 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. // 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 ...@@ -66,14 +75,21 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
} }
String id = getGifId(data); 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, provider, bitmapPool, unitTransformation, width, height, id,
GifDrawable gifDrawable = new GifDrawable(context, new GifBitmapProvider(bitmapPool), transformation, width, header, data, firstFrame);
height, id, header, data, header.getWidth(), header.getHeight());
return new GifDrawableResource(gifDrawable); 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 @Override
public String getId() { public String getId() {
return ""; return "";
...@@ -94,12 +110,12 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa ...@@ -94,12 +110,12 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
} }
private static byte[] inputStreamToBytes(InputStream is) { private static byte[] inputStreamToBytes(InputStream is) {
final int bufferSize = 16384, initialCapacity = bufferSize; final int bufferSize = 16384;
ByteArrayOutputStream buffer = new ByteArrayOutputStream(initialCapacity); ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
try { try {
int nRead; int nRead;
byte[] data = new byte[bufferSize]; 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.write(data, 0, nRead);
} }
buffer.flush(); buffer.flush();
...@@ -110,32 +126,38 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa ...@@ -110,32 +126,38 @@ public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawa
return buffer.toByteArray(); return buffer.toByteArray();
} }
interface GifHeaderParserPool { // Visible for testing.
public GifHeaderParser obtain(byte[] data); static class GifDecoderPool {
public void release(GifHeaderParser parser); 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 { // Visible for testing.
private static final Queue<GifHeaderParser> POOL = Util.createQueue(0); static class GifHeaderParserPool {
private final Queue<GifHeaderParser> pool = Util.createQueue(0);
@Override public synchronized GifHeaderParser obtain(byte[] data) {
public GifHeaderParser obtain(byte[] data) { GifHeaderParser result = pool.poll();
GifHeaderParser result;
synchronized (POOL) {
result = POOL.poll();
}
if (result == null) { if (result == null) {
result = new GifHeaderParser(); result = new GifHeaderParser();
} }
return result.setData(data); return result.setData(data);
} }
@Override public synchronized void release(GifHeaderParser parser) {
public void release(GifHeaderParser parser) { pool.offer(parser);
synchronized (POOL) {
POOL.offer(parser);
}
} }
} }
......
...@@ -8,10 +8,13 @@ import android.view.ViewGroup; ...@@ -8,10 +8,13 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import com.bumptech.glide.GenericRequestBuilder;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.ListPreloader;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/** /**
...@@ -37,8 +40,11 @@ public class MainActivity extends Activity implements Api.Monitor { ...@@ -37,8 +40,11 @@ public class MainActivity extends Activity implements Api.Monitor {
.into(giphyLogoView); .into(giphyLogoView);
ListView gifList = (ListView) findViewById(R.id.gif_list); ListView gifList = (ListView) findViewById(R.id.gif_list);
adapter = new GifAdapter(this); GiphyPreloader preloader = new GiphyPreloader(2);
adapter = new GifAdapter(this, preloader);
gifList.setAdapter(adapter); gifList.setAdapter(adapter);
gifList.setOnScrollListener(preloader);
} }
@Override @Override
...@@ -58,14 +64,47 @@ public class MainActivity extends Activity implements Api.Monitor { ...@@ -58,14 +64,47 @@ public class MainActivity extends Activity implements Api.Monitor {
adapter.setResults(result.data); adapter.setResults(result.data);
} }
private class GiphyPreloader extends ListPreloader<Api.GifResult> {
private int[] dimensions;
public GiphyPreloader(int maxPreload) {
super(maxPreload);
}
@Override
protected int[] getDimensions(Api.GifResult item) {
return dimensions;
}
@Override
protected List<Api.GifResult> getItems(int start, int end) {
List<Api.GifResult> items = new ArrayList<Api.GifResult>(end - start);
for (int i = start; i < end; i++) {
items.add(adapter.getItem(i));
}
return items;
}
@Override
protected GenericRequestBuilder getRequestBuilder(Api.GifResult item) {
return Glide.with(MainActivity.this)
.load(item)
.fitCenter();
}
}
private static class GifAdapter extends BaseAdapter { private static class GifAdapter extends BaseAdapter {
private static final Api.GifResult[] EMPTY_RESULTS = new Api.GifResult[0]; private static final Api.GifResult[] EMPTY_RESULTS = new Api.GifResult[0];
private final Activity activity;
private final GiphyPreloader preloader;
private Api.GifResult[] results = EMPTY_RESULTS; private Api.GifResult[] results = EMPTY_RESULTS;
private Activity activity;
public GifAdapter(Activity activity) { public GifAdapter(Activity activity, GiphyPreloader preloader) {
this.activity = activity; this.activity = activity;
this.preloader = preloader;
} }
public void setResults(Api.GifResult[] results) { public void setResults(Api.GifResult[] results) {
...@@ -83,8 +122,8 @@ public class MainActivity extends Activity implements Api.Monitor { ...@@ -83,8 +122,8 @@ public class MainActivity extends Activity implements Api.Monitor {
} }
@Override @Override
public Object getItem(int i) { public Api.GifResult getItem(int i) {
return null; return results[i];
} }
@Override @Override
...@@ -102,13 +141,26 @@ public class MainActivity extends Activity implements Api.Monitor { ...@@ -102,13 +141,26 @@ public class MainActivity extends Activity implements Api.Monitor {
if (Log.isLoggable(TAG, Log.DEBUG)) { if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "load result: " + result); Log.d(TAG, "load result: " + result);
} }
ImageView gifView = (ImageView) convertView.findViewById(R.id.gif_view); final ImageView gifView = (ImageView) convertView.findViewById(R.id.gif_view);
Glide.with(activity) Glide.with(activity)
.load(result) .load(result)
.fitCenter() .fitCenter()
.into(gifView); .into(gifView);
if (preloader.dimensions == null) {
gifView.post(new Runnable() {
@Override
public void run() {
if (gifView.getWidth() > 0 && gifView.getHeight() > 0) {
preloader.dimensions = new int[2];
preloader.dimensions[0] = gifView.getWidth();
preloader.dimensions[1] = gifView.getHeight();
}
}
});
}
return convertView; return convertView;
} }
} }
......
...@@ -297,6 +297,14 @@ public class GifDecoder { ...@@ -297,6 +297,14 @@ public class GifDecoder {
return header.status; return header.status;
} }
public void clear() {
id = null;
header = null;
data = null;
mainPixels = null;
mainScratch = null;
}
public void setData(String id, GifHeader header, byte[] data) { public void setData(String id, GifHeader header, byte[] data) {
this.id = id; this.id = id;
this.header = header; this.header = header;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册