package com.bumptech.glide.gifdecoder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.robolectric.Shadows.shadowOf; import android.graphics.Bitmap; import android.support.annotation.NonNull; import com.bumptech.glide.testutil.TestUtil; import java.io.IOException; import java.util.Arrays; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.Shadows; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowBitmap; /** * Tests for {@link com.bumptech.glide.gifdecoder.GifDecoder}. */ @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 18) public class GifDecoderTest { private MockProvider provider; @Before public void setUp() { provider = new MockProvider(); } @Test public void testCanDecodeFramesFromTestGif() throws IOException { byte[] data = TestUtil.resourceToBytes(getClass(), "partial_gif_decode.gif"); GifHeaderParser headerParser = new GifHeaderParser(); headerParser.setData(data); GifHeader header = headerParser.parseHeader(); GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(header, data); decoder.advance(); Bitmap bitmap = decoder.getNextFrame(); assertNotNull(bitmap); assertEquals(GifDecoder.STATUS_OK, decoder.getStatus()); } @Test public void testFrameIndexStartsAtNegativeOne() { GifHeader gifheader = new GifHeader(); gifheader.frameCount = 4; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); assertEquals(-1, decoder.getCurrentFrameIndex()); } @Test public void testTotalIterationCountIsOneIfNetscapeLoopCountDoesntExist() { GifHeader gifheader = new GifHeader(); gifheader.loopCount = GifHeader.NETSCAPE_LOOP_COUNT_DOES_NOT_EXIST; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); assertEquals(1, decoder.getTotalIterationCount()); } @Test public void testTotalIterationCountIsForeverIfNetscapeLoopCountIsForever() { GifHeader gifheader = new GifHeader(); gifheader.loopCount = GifHeader.NETSCAPE_LOOP_COUNT_FOREVER; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); assertEquals(GifDecoder.TOTAL_ITERATION_COUNT_FOREVER, decoder.getTotalIterationCount()); } @Test public void testTotalIterationCountIsTwoIfNetscapeLoopCountIsOne() { GifHeader gifheader = new GifHeader(); gifheader.loopCount = 1; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); assertEquals(2, decoder.getTotalIterationCount()); } @Test public void testAdvanceIncrementsFrameIndex() { GifHeader gifheader = new GifHeader(); gifheader.frameCount = 4; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); decoder.advance(); assertEquals(0, decoder.getCurrentFrameIndex()); } @Test public void testAdvanceWrapsIndexBackToZero() { GifHeader gifheader = new GifHeader(); gifheader.frameCount = 2; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); decoder.advance(); decoder.advance(); decoder.advance(); assertEquals(0, decoder.getCurrentFrameIndex()); } @Test public void testSettingDataResetsFramePointer() { GifHeader gifheader = new GifHeader(); gifheader.frameCount = 4; byte[] data = new byte[0]; GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(gifheader, data); decoder.advance(); decoder.advance(); assertEquals(1, decoder.getCurrentFrameIndex()); decoder.setData(gifheader, data); assertEquals(-1, decoder.getCurrentFrameIndex()); } @Test @Config(shadows = {CustomShadowBitmap.class}) public void testFirstFrameMustClearBeforeDrawingWhenLastFrameIsDisposalBackground() throws IOException { byte[] data = TestUtil.resourceToBytes(getClass(), "transparent_disposal_background.gif"); GifHeaderParser headerParser = new GifHeaderParser(); headerParser.setData(data); GifHeader header = headerParser.parseHeader(); GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(header, data); decoder.advance(); Bitmap firstFrame = decoder.getNextFrame(); decoder.advance(); decoder.getNextFrame(); decoder.advance(); Bitmap firstFrameTwice = decoder.getNextFrame(); assertTrue(Arrays.equals((((CustomShadowBitmap) shadowOf(firstFrame))).getPixels(), (((CustomShadowBitmap) shadowOf(firstFrameTwice))).getPixels())); } @Test @Config(shadows = {CustomShadowBitmap.class}) public void testFirstFrameMustClearBeforeDrawingWhenLastFrameIsDisposalNone() throws IOException { byte[] data = TestUtil.resourceToBytes(getClass(), "transparent_disposal_none.gif"); GifHeaderParser headerParser = new GifHeaderParser(); headerParser.setData(data); GifHeader header = headerParser.parseHeader(); GifDecoder decoder = new StandardGifDecoder(provider); decoder.setData(header, data); decoder.advance(); Bitmap firstFrame = decoder.getNextFrame(); decoder.advance(); decoder.getNextFrame(); decoder.advance(); Bitmap firstFrameTwice = decoder.getNextFrame(); assertTrue(Arrays.equals((((CustomShadowBitmap) shadowOf(firstFrame))).getPixels(), (((CustomShadowBitmap) shadowOf(firstFrameTwice))).getPixels())); } /** * Preserve generated bitmap data for checking. */ @Implements(Bitmap.class) public static class CustomShadowBitmap extends ShadowBitmap { private int[] pixels; @Implementation public void setPixels(int[] pixels, int offset, int stride, int x, int y, int width, int height) { this.pixels = new int[pixels.length]; System.arraycopy(pixels, 0, this.pixels, 0, this.pixels.length); } public int[] getPixels() { return pixels; } } private static class MockProvider implements GifDecoder.BitmapProvider { @NonNull @Override public Bitmap obtain(int width, int height, Bitmap.Config config) { Bitmap result = Bitmap.createBitmap(width, height, config); Shadows.shadowOf(result).setMutable(true); return result; } @Override public void release(Bitmap bitmap) { // Do nothing. } @Override public byte[] obtainByteArray(int size) { return new byte[size]; } @Override public void release(byte[] bytes) { // Do nothing. } @Override public int[] obtainIntArray(int size) { return new int[size]; } @Override public void release(int[] array) { // Do Nothing } } }