aboutsummaryrefslogtreecommitdiff
path: root/third_party
diff options
context:
space:
mode:
authorSam Judd <judds@google.com>2014-09-14 14:45:29 -0700
committerSam Judd <judds@google.com>2014-09-14 14:45:58 -0700
commit34cde327bc68d61e7aa64ed122b1d60250bc28d7 (patch)
treefb3dac002b91d744de2701b213c3628fd86f9a01 /third_party
parentc168678af320bc0616635f1704f2b914799c4dec (diff)
downloadglide-34cde327bc68d61e7aa64ed122b1d60250bc28d7.tar.gz
Allow GIFs without Graphics Control Extensions.
The GCE is an optional extension that may or may not be included per frame (it's slightly more complicated than this), we shouldn't throw if it isn't present. Fixes #134.
Diffstat (limited to 'third_party')
-rw-r--r--third_party/gif_decoder/build.gradle8
-rw-r--r--third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/GifHeaderParserTest.java106
-rw-r--r--third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtil.java87
-rw-r--r--third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtilTest.java54
-rw-r--r--third_party/gif_decoder/src/androidTest/resources/gif_without_graphical_control_extension.gifbin0 -> 1173 bytes
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java10
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java6
7 files changed, 268 insertions, 3 deletions
diff --git a/third_party/gif_decoder/build.gradle b/third_party/gif_decoder/build.gradle
index 90c5ef71..f7b4d4ab 100644
--- a/third_party/gif_decoder/build.gradle
+++ b/third_party/gif_decoder/build.gradle
@@ -1,4 +1,12 @@
apply plugin: 'com.android.library'
+apply plugin: 'robolectric'
+
+dependencies {
+ androidTestCompile 'org.hamcrest:hamcrest-core:1.3'
+ androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
+ androidTestCompile 'junit:junit:4.11'
+ androidTestCompile 'org.mockito:mockito-all:1.9.5'
+}
android {
compileSdkVersion 19
diff --git a/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/GifHeaderParserTest.java b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/GifHeaderParserTest.java
new file mode 100644
index 00000000..ca9879f2
--- /dev/null
+++ b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/GifHeaderParserTest.java
@@ -0,0 +1,106 @@
+package com.bumptech.glide.gifdecoder;
+
+import com.bumptech.glide.gifdecoder.test.GifBytesTestUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Tests for {@link com.bumptech.glide.gifdecoder.GifHeaderParser}.
+ */
+public class GifHeaderParserTest {
+ private GifHeaderParser parser;
+
+ @Before
+ public void setUp() {
+ parser = new GifHeaderParser();
+ }
+
+ @Test
+ public void testReturnsHeaderWithFormatErrorIfDoesNotStartWithGifHeader() {
+ parser.setData("wrong_header".getBytes());
+ GifHeader result = parser.parseHeader();
+ assertEquals(GifDecoder.STATUS_FORMAT_ERROR, result.status);
+ }
+
+ @Test
+ public void testCanReadValidHeaderAndLSD() {
+ final int width = 10;
+ final int height = 20;
+ ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
+ GifBytesTestUtil.writeHeaderAndLsd(buffer, width, height, false, 0);
+
+ parser.setData(buffer.array());
+ GifHeader header = parser.parseHeader();
+ assertEquals(width, header.width);
+ assertEquals(height, header.height);
+ assertFalse(header.gctFlag);
+ // 2^(1+0) == 2^1 == 2.
+ assertEquals(2, header.gctSize);
+ assertEquals(0, header.bgIndex);
+ assertEquals(0, header.pixelAspect);
+ }
+
+ @Test
+ public void testCanParseHeaderOfTestImageWithoutGraphicalExtension() throws IOException {
+ byte[] data = readResourceData("gif_without_graphical_control_extension.gif");
+ parser.setData(data);
+ GifHeader header = parser.parseHeader();
+ assertEquals(1, header.frameCount);
+ assertNotNull(header.frames.get(0));
+ assertEquals(GifDecoder.STATUS_OK, header.status);
+ }
+
+ @Test
+ public void testCanReadImageDescriptorWithoutGraphicalExtension() {
+ ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH
+ + GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH + 4).order(ByteOrder.LITTLE_ENDIAN);
+ GifBytesTestUtil.writeHeaderAndLsd(buffer, 1, 1, false, 0);
+ GifBytesTestUtil.writeImageDescriptor(buffer, 0, 0, 1, 1);
+ GifBytesTestUtil.writeFakeImageData(buffer, 2);
+
+ parser.setData(buffer.array());
+ GifHeader header = parser.parseHeader();
+ assertEquals(1, header.width);
+ assertEquals(1, header.height);
+ assertEquals(1, header.frameCount);
+ assertNotNull(header.frames.get(0));
+ }
+
+ private InputStream openResource(String imageName) throws IOException {
+ return getClass().getResourceAsStream("/" + imageName);
+ }
+
+ private byte[] readResourceData(String imageName) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ InputStream is = null;
+ try {
+ is = openResource(imageName);
+ int read;
+ while ((read = is.read(buffer)) != -1) {
+ os.write(buffer, 0, read);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // Ignore.
+ }
+ }
+ }
+ return os.toByteArray();
+ }
+} \ No newline at end of file
diff --git a/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtil.java b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtil.java
new file mode 100644
index 00000000..97c3d4fa
--- /dev/null
+++ b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtil.java
@@ -0,0 +1,87 @@
+package com.bumptech.glide.gifdecoder.test;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utils for writing the bytes of various parts of GIFs to byte buffers.
+ */
+public class GifBytesTestUtil {
+ // Length in bytes.
+ public static final int HEADER_LENGTH = 13;
+ // Length in bytes.
+ public static final int IMAGE_DESCRIPTOR_LENGTH = 10;
+
+ public static void writeFakeImageData(ByteBuffer out, int lzwMinCodeSize) {
+ // 1 for lzwMinCodeSize, 1 for length, 1 for min content, 1 for block terminator.
+ verifyRemaining(out, 4);
+ verifyShortValues(lzwMinCodeSize);
+
+ out.put((byte) lzwMinCodeSize);
+ // Block length.
+ out.put((byte) 0x01);
+ // Block content.
+ out.put((byte) 0x01);
+ // End of block.
+ out.put((byte) 0x00);
+ }
+
+ public static void writeImageDescriptor(ByteBuffer out, int imageLeft, int imageTop, int imageWidth,
+ int imageHeight) {
+ verifyRemaining(out, IMAGE_DESCRIPTOR_LENGTH);
+ verifyShortValues(imageLeft, imageTop, imageWidth, imageHeight);
+
+ // Image separator
+ out.put((byte) 0x2C);
+
+ out.putShort((short) imageLeft).putShort((short) imageTop).putShort((short) imageWidth)
+ .putShort((short) imageHeight);
+ }
+
+ public static void writeHeaderAndLsd(ByteBuffer out, int width, int height, boolean hasGct, int gctSize) {
+ verifyRemaining(out, HEADER_LENGTH);
+ verifyShortValues(width, height);
+
+ // GIF
+ out.put((byte) 0x47).put((byte) 0x49).put((byte) 0x46);
+ // Version - 89a.
+ out.put((byte) 0x38).put((byte) 0x39).put((byte) 0x61);
+
+ /** LSD (Logical Screen Descriptor) **/
+ // Width.
+ out.putShort((short) width);
+ // Height.
+ out.putShort((short) height);
+ // Packed GCT (Global Color Table) flag + color resolution + sort flag + size of GCT.
+ // GCT flag (false) - most significant bit.
+ byte gctFlag = (byte) ((hasGct ? 1 : 0) << 7);
+ // Color resolution - next three bits.
+ byte colorResolution = 1 << 5;
+ // Sort flag - next bit;
+ byte sortFlag = 0 << 4;
+ // exponent of size of color table, size = 2^(1 + exponent) - least significant 3 bits.
+ byte size = (byte) gctSize;
+
+ byte packed = (byte) (gctFlag | colorResolution | sortFlag | size);
+ out.put(packed);
+
+ // Background color index.
+ out.put((byte) 0);
+
+ // Pixel aspect ratio.
+ out.put((byte) 0);
+ }
+
+ private static void verifyRemaining(ByteBuffer buffer, int expected) {
+ if (buffer.remaining() < expected) {
+ throw new IllegalArgumentException("Must have at least " + expected + " bytes to write");
+ }
+ }
+
+ private static void verifyShortValues(int... shortValues) {
+ for (int dimen : shortValues) {
+ if (dimen > Short.MAX_VALUE || dimen < 0) {
+ throw new IllegalArgumentException("Must pass in non-negative short dimensions, not: " + dimen);
+ }
+ }
+ }
+}
diff --git a/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtilTest.java b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtilTest.java
new file mode 100644
index 00000000..7b25c96b
--- /dev/null
+++ b/third_party/gif_decoder/src/androidTest/java/com/bumptech/glide/gifdecoder/test/GifBytesTestUtilTest.java
@@ -0,0 +1,54 @@
+package com.bumptech.glide.gifdecoder.test;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertArrayEquals;
+
+/**
+ * Tests for {@link com.bumptech.glide.gifdecoder.test.GifBytesTestUtil}.
+ */
+public class GifBytesTestUtilTest {
+
+ @Test
+ public void testWriteHeaderAndLsdWithoutGct() {
+ ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH);
+ GifBytesTestUtil.writeHeaderAndLsd(buffer, 8, 16, false, 0);
+
+ byte[] expected = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x08, 0x00, 0x10, 0x20, 0x00, 0x00};
+
+ assertArrayEquals(expected, buffer.array());
+ }
+
+ @Test
+ public void testWriteHeaderAndLsdWithGct() {
+ ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.HEADER_LENGTH);
+ GifBytesTestUtil.writeHeaderAndLsd(buffer, 8, 16, true, 4);
+
+ byte[] expected = new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x08, 0x00, 0x10, (byte) 0xA4, 0x00,
+ 0x00};
+
+ assertArrayEquals(expected, buffer.array());
+ }
+
+ @Test
+ public void testWriteImageDescriptor() {
+ ByteBuffer buffer = ByteBuffer.allocate(GifBytesTestUtil.IMAGE_DESCRIPTOR_LENGTH);
+ GifBytesTestUtil.writeImageDescriptor(buffer, 10, 9, 8, 7);
+
+ byte[] expected = new byte[] { 0x2C, 0x00, 0x0A, 0x00, 0X09, 0x00, 0x08, 0x000, 0x07, 0x00 };
+
+ assertArrayEquals(expected, buffer.array());
+ }
+
+ @Test
+ public void testWriteFakeImageData() {
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ GifBytesTestUtil.writeFakeImageData(buffer, 2);
+
+ byte[] expected = new byte[] { 0x02, 0x01, 0x01, 0x00 };
+
+ assertArrayEquals(expected, buffer.array());
+ }
+}
diff --git a/third_party/gif_decoder/src/androidTest/resources/gif_without_graphical_control_extension.gif b/third_party/gif_decoder/src/androidTest/resources/gif_without_graphical_control_extension.gif
new file mode 100644
index 00000000..f949829d
--- /dev/null
+++ b/third_party/gif_decoder/src/androidTest/resources/gif_without_graphical_control_extension.gif
Binary files differ
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
index 62879660..157ea57d 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java
@@ -11,9 +11,6 @@ import java.util.List;
public class GifHeader {
int[] gct = null;
- /**
- * Global status code of GIF data parsing.
- */
int status = GifDecoder.STATUS_OK;
int frameCount = 0;
@@ -51,4 +48,11 @@ public class GifHeader {
public int getNumFrames() {
return frameCount;
}
+
+ /**
+ * Global status code of GIF data parsing.
+ */
+ public int getStatus() {
+ return status;
+ }
}
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
index da723f5c..33c0c6c6 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java
@@ -77,6 +77,12 @@ public class GifHeaderParser {
switch (code) {
// Image separator.
case 0x2C:
+ // The graphics control extension is optional, but will always come first if it exists. If one did
+ // exist, there will be a non-null current frame which we should use. However if one did not exist,
+ // the current frame will be null and we must create it here. See issue #134.
+ if (header.currentFrame == null) {
+ header.currentFrame = new GifFrame();
+ }
readBitmap();
break;
// Extension.