aboutsummaryrefslogtreecommitdiff
path: root/third_party/gif_decoder
diff options
context:
space:
mode:
authorAlan Newberger <alann@google.com>2014-10-21 16:47:05 -0700
committerAlan Newberger <alann@google.com>2014-10-21 16:47:05 -0700
commitd7429cebda1204623acc02825eaf50053a7ec3a9 (patch)
tree940e7ade07d68bfc15fb9f190a7429ab00372d17 /third_party/gif_decoder
parent934fc60e7f34cd2c4e8422a12c6015790a820b44 (diff)
parent39f12e09c7273b13604184e523846c1ef18ff311 (diff)
downloadglide-d7429cebda1204623acc02825eaf50053a7ec3a9.tar.gz
Merge remote-tracking branch 'bump/master' into HEAD
Bug: 18059638 Conflicts: .gitmodules library/src/androidTest/java/com/bumptech/glide/BitmapTypeRequestTest.java library/src/androidTest/java/com/bumptech/glide/DrawableTypeRequestTest.java library/src/androidTest/java/com/bumptech/glide/GenericRequestBuilderTest.java library/src/androidTest/java/com/bumptech/glide/GenericTranscodeRequestTest.java library/src/androidTest/java/com/bumptech/glide/GlideTest.java library/src/androidTest/java/com/bumptech/glide/ListPreloaderTest.java library/src/androidTest/java/com/bumptech/glide/RequestManagerTest.java library/src/androidTest/java/com/bumptech/glide/load/MultiTransformationTest.java library/src/androidTest/java/com/bumptech/glide/load/data/LocalUriFetcherTest.java library/src/androidTest/java/com/bumptech/glide/load/data/MediaStoreThumbFetcherTest.java library/src/androidTest/java/com/bumptech/glide/load/data/ThumbnailStreamOpenerFactoryTest.java library/src/androidTest/java/com/bumptech/glide/load/data/ThumbnailStreamOpenerTest.java library/src/androidTest/java/com/bumptech/glide/load/data/resource/ByteArrayFetcherTest.java library/src/androidTest/java/com/bumptech/glide/load/data/resource/FileDescriptorLocalUriFetcherTest.java library/src/androidTest/java/com/bumptech/glide/load/data/resource/StreamLocalUriFetcherTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/CacheLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/EngineTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/bitmap_recycle/AttributeStrategyTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/bitmap_recycle/LruBitmapPoolTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/cache/DiskLruCacheWrapperTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/cache/LruCacheTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/cache/LruResourceCacheTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/cache/MemorySizeCalculatorTest.java library/src/androidTest/java/com/bumptech/glide/load/engine/executor/FifoPriorityThreadPoolExecutorTest.java library/src/androidTest/java/com/bumptech/glide/load/model/ImageVideoModelLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/ImageVideoWrapperEncoderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/StreamEncoderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/stream/MediaStoreStreamLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/stream/ResourceLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/stream/StreamByteArrayLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/stream/StringLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/model/stream/UriLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/NullDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/UnitTransformationTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/BitmapDrawableResourceTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/BitmapResourceTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/CenterCropTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/DownsamplerTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/FitCenterTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/ImageVideoBitmapDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bitmap/VideoBitmapDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/bytes/BytesResourceTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gif/GifFrameModelLoaderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gif/GifFrameResourceDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapResourceEncoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapStreamResourceDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceEncoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperResourceTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperStreamResourceDecoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/gifbitmap/GifBitmapWrapperTransformationTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/transcode/BitmapBytesTranscoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/transcode/GifBitmapWrapperDrawableTranscoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/transcode/GlideBitmapDrawableTranscoderTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/transcode/TranscoderRegistryTest.java library/src/androidTest/java/com/bumptech/glide/load/resource/transcode/UnitTranscoderTest.java library/src/androidTest/java/com/bumptech/glide/manager/ConnectivityMonitorFactoryTest.java library/src/androidTest/java/com/bumptech/glide/manager/DefaultConnectivityMonitorTest.java library/src/androidTest/java/com/bumptech/glide/manager/RequestManagerFragmentTest.java library/src/androidTest/java/com/bumptech/glide/manager/RequestManagerRetrieverTest.java library/src/androidTest/java/com/bumptech/glide/provider/ChildLoadProviderTest.java library/src/androidTest/java/com/bumptech/glide/provider/DataLoadProviderRegistryTest.java library/src/androidTest/java/com/bumptech/glide/request/target/ViewTargetTest.java library/src/androidTest/java/com/bumptech/glide/resize/load/ExifTest.java library/src/androidTest/java/com/bumptech/glide/tests/ContentResolverShadow.java library/src/androidTest/java/com/bumptech/glide/tests/GlideShadowLooper.java library/src/androidTest/java/com/bumptech/glide/util/ByteArrayPoolTest.java library/src/androidTest/resources/org.robolectric.Config.properties library/src/main/java/com/bumptech/glide/BitmapRequestBuilder.java library/src/main/java/com/bumptech/glide/GenericRequestBuilder.java library/src/main/java/com/bumptech/glide/load/engine/Engine.java library/src/main/java/com/bumptech/glide/load/resource/bitmap/FileDescriptorBitmapDecoder.java library/src/main/java/com/bumptech/glide/load/resource/bitmap/StreamBitmapDecoder.java library/src/main/java/com/bumptech/glide/load/resource/bitmap/TransformationUtils.java library/src/main/java/com/bumptech/glide/load/resource/gif/GifFrameManager.java library/src/main/java/com/bumptech/glide/load/resource/transcode/TranscoderFactory.java library/src/main/java/com/bumptech/glide/provider/DataLoadProviderFactory.java library/src/main/java/com/bumptech/glide/request/ThumbnailRequestCoordinator.java samples/flickr/build.gradle samples/flickr/src/main/AndroidManifest.xml samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrModelLoader.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoGrid.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrPhotoList.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/FlickrSearchActivity.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/PhotoViewer.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Api.java samples/flickr/src/main/java/com/bumptech/glide/samples/flickr/api/Photo.java samples/flickr/src/main/res/anim/fade_in.xml samples/flickr/src/main/res/drawable-hdpi/ic_launcher.png samples/flickr/src/main/res/drawable-ldpi/ic_launcher.png samples/flickr/src/main/res/drawable-mdpi/ic_launcher.png samples/flickr/src/main/res/drawable-xhdpi/ic_launcher.png samples/flickr/src/main/res/layout/flickr_photo_grid.xml samples/flickr/src/main/res/layout/flickr_photo_grid_item.xml samples/flickr/src/main/res/layout/flickr_photo_list.xml samples/flickr/src/main/res/layout/flickr_photo_list_item.xml samples/flickr/src/main/res/layout/flickr_search_activity.xml samples/flickr/src/main/res/values/colors.xml samples/flickr/src/main/res/values/dimens.xml samples/flickr/src/main/res/values/strings.xml samples/svg/src/main/res/drawable-xhdpi/ic_launcher.png Change-Id: I27376c117937c6a308b1015313a91eedbf0e95b8
Diffstat (limited to 'third_party/gif_decoder')
-rw-r--r--third_party/gif_decoder/.gitignore4
-rw-r--r--third_party/gif_decoder/AndroidManifest.xml8
-rw-r--r--third_party/gif_decoder/LICENSE43
-rw-r--r--third_party/gif_decoder/README.third_party6
-rw-r--r--third_party/gif_decoder/build.gradle31
-rw-r--r--third_party/gif_decoder/build.xml92
-rw-r--r--third_party/gif_decoder/custom_rules.xml10
-rw-r--r--third_party/gif_decoder/pom.xml16
-rw-r--r--third_party/gif_decoder/project.properties6
-rw-r--r--third_party/gif_decoder/src/main/AndroidManifest.xml5
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java427
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java31
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeader.java68
-rw-r--r--third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifHeaderParser.java332
14 files changed, 515 insertions, 564 deletions
diff --git a/third_party/gif_decoder/.gitignore b/third_party/gif_decoder/.gitignore
deleted file mode 100644
index 8c5b3448..00000000
--- a/third_party/gif_decoder/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-local.properties
-bin/**/*
-gen/**/*
-target/**/*
diff --git a/third_party/gif_decoder/AndroidManifest.xml b/third_party/gif_decoder/AndroidManifest.xml
deleted file mode 100644
index 03f0e112..00000000
--- a/third_party/gif_decoder/AndroidManifest.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.bumptech.glide.gifdecoder"
- android:versionCode="1"
- android:versionName="1.0.0" >
- <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
- <application />
-</manifest>
diff --git a/third_party/gif_decoder/LICENSE b/third_party/gif_decoder/LICENSE
index d6add0e7..79da9d50 100644
--- a/third_party/gif_decoder/LICENSE
+++ b/third_party/gif_decoder/LICENSE
@@ -1,22 +1,21 @@
-/**
- * Copyright (c) 2013 Xcellent Creations, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
+Copyright (c) 2013 Xcellent Creations, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/third_party/gif_decoder/README.third_party b/third_party/gif_decoder/README.third_party
index 9e771c5b..e872bcd1 100644
--- a/third_party/gif_decoder/README.third_party
+++ b/third_party/gif_decoder/README.third_party
@@ -15,6 +15,6 @@ Adapted from:
http://show.docjava.com/book/cgij/exportToHTML/ip/gif/stills/GifDecoder.java.html
Local Modifications:
-Broke headers and frames out into separate files and added ability to share
-headers between multiple decoders. Added interface for reusing bitmaps each
-frame.
+Broke headers and frames out into separate files and added ability to share
+headers between multiple decoders. Added interface for reusing bitmaps each
+frame. Bugfixes.
diff --git a/third_party/gif_decoder/build.gradle b/third_party/gif_decoder/build.gradle
index 07bb766f..8834279f 100644
--- a/third_party/gif_decoder/build.gradle
+++ b/third_party/gif_decoder/build.gradle
@@ -1,21 +1,22 @@
-buildscript {
- repositories {
- mavenCentral()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:0.11.2'
- }
-}
+apply plugin: 'com.android.library'
+apply plugin: 'robolectric'
-apply plugin: 'android-library'
+dependencies {
+ androidTestCompile 'com.android.support:support-v4:19.1.0'
+ 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'
+ androidTestCompile 'org.robolectric:robolectric:2.4-SNAPSHOT'
+}
android {
compileSdkVersion 19
- buildToolsVersion = '19.1.0'
- sourceSets {
- main {
- java.srcDirs = ['src/main/java']
- manifest.srcFile 'AndroidManifest.xml'
- }
+ buildToolsVersion '19.1.0'
+
+ defaultConfig {
+ applicationId 'com.bumptech.glide.gifdecoder'
+ minSdkVersion 10
+ targetSdkVersion 19
}
}
diff --git a/third_party/gif_decoder/build.xml b/third_party/gif_decoder/build.xml
deleted file mode 100644
index a6e9e003..00000000
--- a/third_party/gif_decoder/build.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide" default="help">
-
- <!-- The local.properties file is created and updated by the 'android' tool.
- It contains the path to the SDK. It should *NOT* be checked into
- Version Control Systems. -->
- <property file="local.properties"/>
-
- <!-- The ant.properties file can be created by you. It is only edited by the
- 'android' tool to add properties to it.
- This is the place to change some Ant specific build properties.
- Here are some properties you may want to change/update:
-
- source.dir
- The name of the source directory. Default is 'src'.
- out.dir
- The name of the output directory. Default is 'bin'.
-
- For other overridable properties, look at the beginning of the rules
- files in the SDK, at tools/ant/build.xml
-
- Properties related to the SDK location or the project target should
- be updated using the 'android' tool with the 'update' action.
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems.
-
- -->
- <property file="ant.properties"/>
-
- <!-- if sdk.dir was not set from one of the property file, then
- get it from the ANDROID_HOME env var.
- This must be done before we load project.properties since
- the proguard config can use sdk.dir -->
- <property environment="env"/>
- <condition property="sdk.dir" value="${env.ANDROID_HOME}">
- <isset property="env.ANDROID_HOME"/>
- </condition>
-
- <!-- The project.properties file is created and updated by the 'android'
- tool, as well as ADT.
-
- This contains project specific properties such as project target, and library
- dependencies. Lower level build properties are stored in ant.properties
- (or in .classpath for Eclipse projects).
-
- This file is an integral part of the build system for your
- application and should be checked into Version Control Systems. -->
- <loadproperties srcFile="project.properties"/>
-
- <!-- quick check on sdk.dir -->
- <fail
- message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
- unless="sdk.dir"
- />
-
- <!--
- Import per project custom build rules if present at the root of the project.
- This is the place to put custom intermediary targets such as:
- -pre-build
- -pre-compile
- -post-compile (This is typically used for code obfuscation.
- Compiled code location: ${out.classes.absolute.dir}
- If this is not done in place, override ${out.dex.input.absolute.dir})
- -post-package
- -post-build
- -pre-clean
- -->
- <import file="custom_rules.xml" optional="true"/>
-
- <!-- Import the actual build file.
-
- To customize existing targets, there are two options:
- - Customize only one target:
- - copy/paste the target into this file, *before* the
- <import> task.
- - customize it to your needs.
- - Customize the whole content of build.xml
- - copy/paste the content of the rules files (minus the top node)
- into this file, replacing the <import> task.
- - customize to your needs.
-
- ***********************
- ****** IMPORTANT ******
- ***********************
- In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
- in order to avoid having your file be overridden by tools such as "android update project"
- -->
- <!-- version-tag: 1 -->
- <import file="${sdk.dir}/tools/ant/build.xml"/>
-
-</project>
diff --git a/third_party/gif_decoder/custom_rules.xml b/third_party/gif_decoder/custom_rules.xml
deleted file mode 100644
index dd568923..00000000
--- a/third_party/gif_decoder/custom_rules.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project name="glide-rules" default="help">
- <xmlproperty file="AndroidManifest.xml" prefix="mymanifest" collapseAttributes="true"/>
-
- <target name="jar" depends="-compile">
- <jar destfile="bin/gifdecoder-${mymanifest.manifest.android:versionName}.jar"
- basedir="bin/classes" >
- </jar>
- </target>
-</project>
diff --git a/third_party/gif_decoder/pom.xml b/third_party/gif_decoder/pom.xml
deleted file mode 100644
index 5315cc3f..00000000
--- a/third_party/gif_decoder/pom.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <parent>
- <groupId>com.bumptech.glide</groupId>
- <artifactId>glide-third-party</artifactId>
- <version>3.3.0-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
-
- <artifactId>glide-gif-decoder</artifactId>
- <packaging>aar</packaging>
- <name>Glide Gif Decoder</name>
-
-</project>
diff --git a/third_party/gif_decoder/project.properties b/third_party/gif_decoder/project.properties
deleted file mode 100644
index d226f51c..00000000
--- a/third_party/gif_decoder/project.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-target=android-19
-
-# https://code.google.com/p/android/issues/detail?id=40487
-renderscript.opt.level=O0
-
-android.library=true
diff --git a/third_party/gif_decoder/src/main/AndroidManifest.xml b/third_party/gif_decoder/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..2f91b04e
--- /dev/null
+++ b/third_party/gif_decoder/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.bumptech.glide.gifdecoder">
+ <application />
+</manifest>
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
index 2381a7a6..92dc3bfc 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifDecoder.java
@@ -24,8 +24,9 @@ package com.bumptech.glide.gifdecoder;
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
+import android.annotation.TargetApi;
import android.graphics.Bitmap;
-import android.graphics.Color;
+import android.os.Build;
import android.util.Log;
import java.io.ByteArrayOutputStream;
@@ -33,6 +34,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
/**
* Reads frame data from a GIF image source and decodes it into individual frames
@@ -59,7 +61,7 @@ public class GifDecoder {
*/
public static final int STATUS_OK = 0;
/**
- * File read status: Error decoding file (may be partially decoded)
+ * File read status: Error decoding file (may be partially decoded).
*/
public static final int STATUS_FORMAT_ERROR = 1;
/**
@@ -67,50 +69,82 @@ public class GifDecoder {
*/
public static final int STATUS_OPEN_ERROR = 2;
/**
- * max decoder pixel stack size
+ * Unable to fully decode the current frame.
+ */
+ public static final int STATUS_PARTIAL_DECODE = 3;
+ /**
+ * max decoder pixel stack size.
*/
private static final int MAX_STACK_SIZE = 4096;
/**
- * GIF Disposal Method meaning take no action
+ * GIF Disposal Method meaning take no action.
*/
private static final int DISPOSAL_UNSPECIFIED = 0;
/**
- * GIF Disposal Method meaning leave canvas from previous frame
+ * GIF Disposal Method meaning leave canvas from previous frame.
*/
private static final int DISPOSAL_NONE = 1;
/**
- * GIF Disposal Method meaning clear canvas to background color
+ * GIF Disposal Method meaning clear canvas to background color.
*/
private static final int DISPOSAL_BACKGROUND = 2;
/**
- * GIF Disposal Method meaning clear canvas to frame before last
+ * GIF Disposal Method meaning clear canvas to frame before last.
*/
private static final int DISPOSAL_PREVIOUS = 3;
- //Global File Header values and parsing flags
- private int[] act; // active color table
+ private static final int NULL_CODE = -1;
+
+ private static final int INITIAL_FRAME_POINTER = -1;
- // Raw GIF data from input source
+ // Global File Header values and parsing flags.
+ // Active color table.
+ private int[] act;
+
+ // Raw GIF data from input source.
private ByteBuffer rawData;
- // Raw data read working array
- private byte[] block = new byte[256]; // current data block
- // LZW decoder working arrays
+ // Raw data read working array.
+ private final byte[] block = new byte[256];
+
+ private GifHeaderParser parser;
+
+ // LZW decoder working arrays.
private short[] prefix;
private byte[] suffix;
private byte[] pixelStack;
private byte[] mainPixels;
private int[] mainScratch;
- private int framePointer = -1;
+ private int framePointer;
private byte[] data;
private GifHeader header;
- private String id;
private BitmapProvider bitmapProvider;
+ private Bitmap previousImage;
+ private boolean savePrevious;
+ private Bitmap.Config config;
+ private int status;
+ /**
+ * An interface that can be used to provide reused {@link android.graphics.Bitmap}s to avoid GCs from constantly
+ * allocating {@link android.graphics.Bitmap}s for every frame.
+ */
public interface BitmapProvider {
+ /**
+ * Returns an {@link Bitmap} with exactly the given dimensions and config, or null if no such {@link Bitmap}
+ * could be obtained.
+ *
+ * @param width The width of the desired {@link android.graphics.Bitmap}.
+ * @param height The height of the desired {@link android.graphics.Bitmap}.
+ * @param config The {@link android.graphics.Bitmap.Config} of the desired {@link android.graphics.Bitmap}.
+ */
public Bitmap obtain(int width, int height, Bitmap.Config config);
+
+ /**
+ * Releases the given Bitmap back to the pool.
+ */
+ public void release(Bitmap bitmap);
}
public GifDecoder(BitmapProvider provider) {
@@ -126,25 +160,28 @@ public class GifDecoder {
return header.height;
}
- public boolean isTransparent() {
- return header.isTransparent;
- }
-
- public int getGifByteSize() {
- return data.length;
- }
-
public byte[] getData() {
return data;
}
- public int getDecodedFramesByteSizeSum() {
- // 4 == ARGB_8888, 2 == RGB_565
- return header.frameCount * header.width * header.height * (header.isTransparent ? 4 : 2);
+ public void setPreferredConfig(Bitmap.Config config) {
+ this.config = config;
+ }
+
+ /**
+ * Returns the current status of the decoder.
+ *
+ * <p>
+ * Status will update per frame to allow the caller to tell whether or not the current frame was decoded
+ * successfully and/or completely. Format and open failures persist across frames.
+ * </p>
+ */
+ public int getStatus() {
+ return status;
}
/**
- * Move the animation frame counter forward
+ * Move the animation frame counter forward.
*/
public void advance() {
framePointer = (framePointer + 1) % header.frameCount;
@@ -153,8 +190,8 @@ public class GifDecoder {
/**
* Gets display duration for specified frame.
*
- * @param n int index of frame
- * @return delay in milliseconds
+ * @param n int index of frame.
+ * @return delay in milliseconds.
*/
public int getDelay(int n) {
int delay = -1;
@@ -165,7 +202,7 @@ public class GifDecoder {
}
/**
- * Gets display duration for the upcoming frame
+ * Gets display duration for the upcoming frame.
*/
public int getNextDelay() {
if (header.frameCount <= 0 || framePointer < 0) {
@@ -178,23 +215,27 @@ public class GifDecoder {
/**
* Gets the number of frames read from file.
*
- * @return frame count
+ * @return frame count.
*/
public int getFrameCount() {
return header.frameCount;
}
/**
- * Gets the current index of the animation frame, or -1 if animation hasn't not yet started
+ * Gets the current index of the animation frame, or -1 if animation hasn't not yet started.
*
- * @return frame index
+ * @return frame index.
*/
public int getCurrentFrameIndex() {
return framePointer;
}
+ public void resetFrameIndex() {
+ framePointer = -1;
+ }
+
/**
- * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitiely.
+ * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely.
*
* @return iteration count if one was specified, else 1.
*/
@@ -202,23 +243,23 @@ public class GifDecoder {
return header.loopCount;
}
- public String getId() {
- return id;
- }
-
/**
* Get the next frame in the animation sequence.
*
- * @return Bitmap representation of frame
+ * @return Bitmap representation of frame.
*/
public Bitmap getNextFrame() {
- if (header.frameCount <= 0 || framePointer < 0 ) {
+ if (header.frameCount <= 0 || framePointer < 0) {
+ status = STATUS_FORMAT_ERROR;
+ }
+ if (status == STATUS_FORMAT_ERROR || status == STATUS_OPEN_ERROR) {
return null;
}
+ status = STATUS_OK;
GifFrame frame = header.frames.get(framePointer);
- //Set the appropriate color table
+ // Set the appropriate color table.
if (frame.lct == null) {
act = header.gct;
} else {
@@ -231,15 +272,18 @@ public class GifDecoder {
int save = 0;
if (frame.transparency) {
save = act[frame.transIndex];
- act[frame.transIndex] = 0; // set transparent color if specified
+ // Set transparent color if specified.
+ act[frame.transIndex] = 0;
}
if (act == null) {
Log.w(TAG, "No Valid Color Table");
- header.status = STATUS_FORMAT_ERROR; // no color table defined
+ // No color table defined.
+ status = STATUS_FORMAT_ERROR;
return null;
}
- Bitmap result = setPixels(framePointer); // transfer pixel data to image
+ // Transfer pixel data to image.
+ Bitmap result = setPixels(framePointer);
// Reset the transparent pixel in the color table
if (frame.transparency) {
@@ -250,10 +294,10 @@ public class GifDecoder {
}
/**
- * Reads GIF image from stream
+ * Reads GIF image from stream.
*
* @param is containing GIF file.
- * @return read status code (0 = no errors)
+ * @return read status code (0 = no errors).
*/
public int read(InputStream is, int contentLength) {
if (is != null) {
@@ -272,7 +316,7 @@ public class GifDecoder {
Log.w(TAG, "Error reading data from stream", e);
}
} else {
- header.status = STATUS_OPEN_ERROR;
+ status = STATUS_OPEN_ERROR;
}
try {
@@ -283,44 +327,82 @@ public class GifDecoder {
Log.w(TAG, "Error closing stream", e);
}
- return header.status;
+ return status;
+ }
+
+ public void clear() {
+ header = null;
+ data = null;
+ mainPixels = null;
+ mainScratch = null;
+ if (previousImage != null) {
+ bitmapProvider.release(previousImage);
+ }
+ previousImage = null;
}
- public void setData(String id, GifHeader header, byte[] data) {
- this.id = id;
+ public void setData(GifHeader header, byte[] data) {
this.header = header;
this.data = data;
- //Initialize the raw data buffer
+ this.status = STATUS_OK;
+ framePointer = INITIAL_FRAME_POINTER;
+ // Initialize the raw data buffer.
rawData = ByteBuffer.wrap(data);
rawData.rewind();
rawData.order(ByteOrder.LITTLE_ENDIAN);
- //Now that we know the size, init scratch arrays
+
+ // No point in specially saving an old frame if we're never going to use it.
+ savePrevious = false;
+ for (GifFrame frame : header.frames) {
+ if (frame.dispose == DISPOSAL_PREVIOUS) {
+ savePrevious = true;
+ break;
+ }
+ }
+
+ // Now that we know the size, init scratch arrays.
mainPixels = new byte[header.width * header.height];
mainScratch = new int[header.width * header.height];
}
+ private GifHeaderParser getHeaderParser() {
+ if (parser == null) {
+ parser = new GifHeaderParser();
+ }
+ return parser;
+ }
+
/**
- * Reads GIF image from byte array
+ * Reads GIF image from byte array.
*
* @param data containing GIF file.
- * @return read status code (0 = no errors)
+ * @return read status code (0 = no errors).
*/
public int read(byte[] data) {
this.data = data;
- this.header = new GifHeaderParser(data).parseHeader();
+ this.header = getHeaderParser().setData(data).parseHeader();
if (data != null) {
- //Initialize the raw data buffer
+ // Initialize the raw data buffer.
rawData = ByteBuffer.wrap(data);
rawData.rewind();
rawData.order(ByteOrder.LITTLE_ENDIAN);
- //Now that we know the size, init scratch arrays
+ // Now that we know the size, init scratch arrays.
mainPixels = new byte[header.width * header.height];
mainScratch = new int[header.width * header.height];
+
+ // No point in specially saving an old frame if we're never going to use it.
+ savePrevious = false;
+ for (GifFrame frame : header.frames) {
+ if (frame.dispose == DISPOSAL_PREVIOUS) {
+ savePrevious = true;
+ break;
+ }
+ }
}
- return header.status;
+ return status;
}
/**
@@ -333,45 +415,33 @@ public class GifDecoder {
if (previousIndex >= 0) {
previousFrame = header.frames.get(previousIndex);
}
+ int width = header.width;
+ int height = header.height;
- // final location of blended pixels
+ // Final location of blended pixels.
final int[] dest = mainScratch;
// fill in starting image contents based on last image's dispose code
if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) {
-// if (previousFrame.dispose == DISPOSAL_NONE) {
-// We don't need to do anything for this case, mainScratch should already have the pixels of the
-// previous image.
-// currentImage.getPixels(dest, 0, header.width, 0, 0, header.width, header.height);
-// }
+ // We don't need to do anything for DISPOSAL_NONE, if it has the correct pixels so will our mainScratch
+ // and therefore so will our dest array.
if (previousFrame.dispose == DISPOSAL_BACKGROUND) {
// Start with a canvas filled with the background color
int c = 0;
if (!currentFrame.transparency) {
c = header.bgColor;
}
- for (int i = 0; i < previousFrame.ih; i++) {
- int n1 = (previousFrame.iy + i) * header.width + previousFrame.ix;
- int n2 = n1 + previousFrame.iw;
- for (int k = n1; k < n2; k++) {
- dest[k] = c;
- }
- }
- }
- } else {
- int c = 0;
- if (!currentFrame.transparency) {
- c = header.bgColor;
- }
- for (int i = 0; i < dest.length; i++) {
- dest[i] = c;
+ Arrays.fill(dest, c);
+ } else if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) {
+ // Start with the previous frame
+ previousImage.getPixels(dest, 0, width, 0, 0, width, height);
}
}
- // Decode pixels for this frame into the global pixels[] scratch
- decodeBitmapData(currentFrame, mainPixels); // decode pixel data
+ // Decode pixels for this frame into the global pixels[] scratch.
+ decodeBitmapData(currentFrame);
- // copy each source line to the appropriate place in the destination
+ // Copy each source line to the appropriate place in the destination.
int pass = 1;
int inc = 8;
int iline = 0;
@@ -402,14 +472,18 @@ public class GifDecoder {
line += currentFrame.iy;
if (line < header.height) {
int k = line * header.width;
- int dx = k + currentFrame.ix; // start of line in dest
- int dlim = dx + currentFrame.iw; // end of dest line
+ // Start of line in dest.
+ int dx = k + currentFrame.ix;
+ // End of dest line.
+ int dlim = dx + currentFrame.iw;
if ((k + header.width) < dlim) {
- dlim = k + header.width; // past dest edge
+ // Past dest edge.
+ dlim = k + header.width;
}
- int sx = i * currentFrame.iw; // start of line in source
+ // Start of line in source.
+ int sx = i * currentFrame.iw;
while (dx < dlim) {
- // map color and insert in destination
+ // Map color and insert in destination.
int index = ((int) mainPixels[sx++]) & 0xff;
int c = act[index];
if (c != 0) {
@@ -420,27 +494,36 @@ public class GifDecoder {
}
}
- //Set pixels for current image
+ // Copy pixels into previous image
+ if (savePrevious && currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE) {
+ if (previousImage == null) {
+ previousImage = getNextBitmap();
+ }
+ previousImage.setPixels(dest, 0, width, 0, 0, width, height);
+ }
+
+ // Set pixels for current image.
Bitmap result = getNextBitmap();
- result.setPixels(dest, 0, header.width, 0, 0, header.width, header.height);
+ result.setPixels(dest, 0, width, 0, 0, width, height);
return result;
}
/**
* Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
*/
- private void decodeBitmapData(GifFrame frame, byte[] dstPixels) {
+ private void decodeBitmapData(GifFrame frame) {
if (frame != null) {
- //Jump to the frame start position
+ // Jump to the frame start position.
rawData.position(frame.bufferFrameStart);
}
- int nullCode = -1;
int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih;
- int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
+ int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count, i, datum,
+ dataSize, first, top, bi, pi;
- if (dstPixels == null || dstPixels.length < npix) {
- dstPixels = new byte[npix]; // allocate new pixel array
+ if (mainPixels == null || mainPixels.length < npix) {
+ // Allocate new pixel array.
+ mainPixels = new byte[npix];
}
if (prefix == null) {
prefix = new short[MAX_STACK_SIZE];
@@ -453,92 +536,105 @@ public class GifDecoder {
}
// Initialize GIF data stream decoder.
- data_size = read();
- clear = 1 << data_size;
- end_of_information = clear + 1;
+ dataSize = read();
+ clear = 1 << dataSize;
+ endOfInformation = clear + 1;
available = clear + 2;
- old_code = nullCode;
- code_size = data_size + 1;
- code_mask = (1 << code_size) - 1;
+ oldCode = NULL_CODE;
+ codeSize = dataSize + 1;
+ codeMask = (1 << codeSize) - 1;
for (code = 0; code < clear; code++) {
- prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
+ // XXX ArrayIndexOutOfBoundsException.
+ prefix[code] = 0;
suffix[code] = (byte) code;
}
// Decode GIF pixel stream.
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix; ) {
- if (top == 0) {
- if (bits < code_size) {
- // Load bytes until there are enough bits for a code.
- if (count == 0) {
- // Read a new data block.
- count = readBlock();
- if (count <= 0) {
- break;
- }
- bi = 0;
- }
- datum += (((int) block[bi]) & 0xff) << bits;
- bits += 8;
- bi++;
- count--;
- continue;
- }
- // Get the next code.
- code = datum & code_mask;
- datum >>= code_size;
- bits -= code_size;
- // Interpret the code
- if ((code > available) || (code == end_of_information)) {
+ // Load bytes until there are enough bits for a code.
+ if (count == 0) {
+ // Read a new data block.
+ count = readBlock();
+ if (count <= 0) {
+ status = STATUS_PARTIAL_DECODE;
break;
}
+ bi = 0;
+ }
+
+ datum += (((int) block[bi]) & 0xff) << bits;
+ bits += 8;
+ bi++;
+ count--;
+
+ while (bits >= codeSize) {
+ // Get the next code.
+ code = datum & codeMask;
+ datum >>= codeSize;
+ bits -= codeSize;
+
+ // Interpret the code.
if (code == clear) {
// Reset decoder.
- code_size = data_size + 1;
- code_mask = (1 << code_size) - 1;
+ codeSize = dataSize + 1;
+ codeMask = (1 << codeSize) - 1;
available = clear + 2;
- old_code = nullCode;
+ oldCode = NULL_CODE;
continue;
}
- if (old_code == nullCode) {
+
+ if (code > available) {
+ status = STATUS_PARTIAL_DECODE;
+ break;
+ }
+
+ if (code == endOfInformation) {
+ break;
+ }
+
+ if (oldCode == NULL_CODE) {
pixelStack[top++] = suffix[code];
- old_code = code;
+ oldCode = code;
first = code;
continue;
}
- in_code = code;
- if (code == available) {
+ inCode = code;
+ if (code >= available) {
pixelStack[top++] = (byte) first;
- code = old_code;
+ code = oldCode;
}
- while (code > clear) {
+ while (code >= clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = ((int) suffix[code]) & 0xff;
- // Add a new string to the string table,
- if (available >= MAX_STACK_SIZE) {
- break;
- }
pixelStack[top++] = (byte) first;
- prefix[available] = (short) old_code;
- suffix[available] = (byte) first;
- available++;
- if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
- code_size++;
- code_mask += available;
+
+ // Add a new string to the string table.
+ if (available < MAX_STACK_SIZE) {
+ prefix[available] = (short) oldCode;
+ suffix[available] = (byte) first;
+ available++;
+ if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
+ codeSize++;
+ codeMask += available;
+ }
+ }
+ oldCode = inCode;
+
+ while (top > 0) {
+ // Pop a pixel off the pixel stack.
+ top--;
+ mainPixels[pi++] = pixelStack[top];
+ i++;
}
- old_code = in_code;
}
- // Pop a pixel off the pixel stack.
- top--;
- dstPixels[pi++] = pixelStack[top];
- i++;
}
+ // Clear missing pixels.
for (i = pi; i < npix; i++) {
- dstPixels[i] = 0; // clear missing pixels
+ mainPixels[i] = 0;
}
}
@@ -548,9 +644,9 @@ public class GifDecoder {
private int read() {
int curByte = 0;
try {
- curByte = (rawData.get() & 0xFF);
+ curByte = rawData.get() & 0xFF;
} catch (Exception e) {
- header.status = STATUS_FORMAT_ERROR;
+ status = STATUS_FORMAT_ERROR;
}
return curByte;
}
@@ -558,7 +654,7 @@ public class GifDecoder {
/**
* Reads next variable length block from input.
*
- * @return number of bytes stored in "buffer"
+ * @return number of bytes stored in "buffer".
*/
private int readBlock() {
int blockSize = read();
@@ -574,21 +670,36 @@ public class GifDecoder {
}
} catch (Exception e) {
Log.w(TAG, "Error Reading Block", e);
- header.status = STATUS_FORMAT_ERROR;
+ status = STATUS_FORMAT_ERROR;
}
}
return n;
}
+ private Bitmap.Config getPreferredConfig() {
+ // We can't tell if a gif has transparency to decode a partial frame on top of a previous frame, or if the final
+ // frame will actually have transparent pixels, so we must always use a format that supports transparency.
+ if (config == Bitmap.Config.RGB_565 || config == Bitmap.Config.ARGB_4444) {
+ return Bitmap.Config.ARGB_4444;
+ } else {
+ return Bitmap.Config.ARGB_8888;
+ }
+ }
+
private Bitmap getNextBitmap() {
- Bitmap.Config targetConfig = header.isTransparent ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
+ Bitmap.Config targetConfig = getPreferredConfig();
Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig);
if (result == null) {
result = Bitmap.createBitmap(header.width, header.height, targetConfig);
- } else {
- // If we're reusing a bitmap it may have other things drawn in it which we need to remove.
- result.eraseColor(Color.TRANSPARENT);
}
+ setAlpha(result);
return result;
}
+
+ @TargetApi(12)
+ private static void setAlpha(Bitmap bitmap) {
+ if (Build.VERSION.SDK_INT >= 12) {
+ bitmap.setHasAlpha(true);
+ }
+ }
}
diff --git a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
index a315350c..1cfa6847 100644
--- a/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
+++ b/third_party/gif_decoder/src/main/java/com/bumptech/glide/gifdecoder/GifFrame.java
@@ -1,21 +1,22 @@
package com.bumptech.glide.gifdecoder;
/**
- * Inner model class housing metadata for each frame
+ * Inner model class housing metadata for each frame.
*/
class GifFrame {
- public int ix, iy, iw, ih;
- /* Control Flags */
- public boolean interlace;
- public boolean transparency;
- /* Disposal Method */
- public int dispose;
- /* Transparency Index */
- public int transIndex;
- /* Delay, in ms, to next frame */
- public int delay;
- /* Index in the raw buffer where we need to start reading to decode */
- public int bufferFrameStart;
- /* Local Color Table */
- public int[] lct;
+ int ix, iy, iw, ih;
+ /** Control Flag. */
+ boolean interlace;
+ /** Control Flag. */
+ boolean transparency;
+ /** Disposal Method. */
+ int dispose;
+ /** Transparency Index. */
+ int transIndex;
+ /** Delay, in ms, to next frame. */
+ int delay;
+ /** Index in the raw buffer where we need to start reading to decode. */
+ int bufferFrameStart;
+ /** Local Color Table. */
+ int[] lct;
}
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 4c062088..96152901 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
@@ -3,29 +3,55 @@ package com.bumptech.glide.gifdecoder;
import java.util.ArrayList;
import java.util.List;
+/**
+ * A header object containing the number of frames in an animated GIF image as well as basic metadata like width and
+ * height that can be used to decode each individual frame of the GIF. Can be shared by one or more
+ * {@link com.bumptech.glide.gifdecoder.GifDecoder}s to play the same animated GIF in multiple views.
+ */
public class GifHeader {
- public int[] gct = null;
+ int[] gct = null;
+ int status = GifDecoder.STATUS_OK;
+ int frameCount = 0;
+
+ GifFrame currentFrame;
+ List<GifFrame> frames = new ArrayList<GifFrame>();
+ // Logical screen size.
+ // Full image width.
+ int width;
+ // Full image height.
+ int height;
+
+ // 1 : global color table flag.
+ boolean gctFlag;
+ // 2-4 : color resolution.
+ // 5 : gct sort flag.
+ // 6-8 : gct size.
+ int gctSize;
+ // Background color index.
+ int bgIndex;
+ // Pixel aspect ratio.
+ int pixelAspect;
+ //TODO: this is set both during reading the header and while decoding frames...
+ int bgColor;
+ int loopCount;
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getNumFrames() {
+ return frameCount;
+ }
+
/**
- * Global status code of GIF data parsing
+ * Global status code of GIF data parsing.
*/
- public int status = GifDecoder.STATUS_OK;
- public int frameCount = 0;
-
- public GifFrame currentFrame;
- public List<GifFrame> frames = new ArrayList<GifFrame>();
- // logical screen size
- public int width; // full image width
- public int height; // full image height
-
- public boolean gctFlag; // 1 : global color table flag
- // 2-4 : color resolution
- // 5 : gct sort flag
- public int gctSize; // 6-8 : gct size
- public int bgIndex; // background color index
- public int pixelAspect; // pixel aspect ratio
- //TODO: this is set both during reading the header and while decoding frames...
- public int bgColor;
- public boolean isTransparent;
- public int loopCount;
+ 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 6237c3cf..286a5602 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
@@ -5,29 +5,27 @@ import android.util.Log;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
import static com.bumptech.glide.gifdecoder.GifDecoder.STATUS_FORMAT_ERROR;
+/**
+ * A class responsible for creating {@link com.bumptech.glide.gifdecoder.GifHeader}s from data representing animated
+ * gifs.
+ */
public class GifHeaderParser {
public static final String TAG = "GifHeaderParser";
- /**
- * max decoder pixel stack size
- */
- private static final int MAX_STACK_SIZE = 4096;
- private final ByteBuffer rawData;
- private GifHeader header = new GifHeader();
+ private static final int MAX_BLOCK_SIZE = 256;
+ // Raw data read working array.
+ private final byte[] block = new byte[MAX_BLOCK_SIZE];
- // Raw data read working array
- protected byte[] block = new byte[256]; // current data block
- protected int blockSize = 0; // block size last graphic control extension info
- protected boolean lctFlag; // local color table flag
- protected int lctSize; // local color table size
- private short[] prefix;
- private byte[] suffix;
- private byte[] pixelStack;
+ private ByteBuffer rawData;
+ private GifHeader header;
+ private int blockSize = 0;
- public GifHeaderParser(byte[] data) {
+ public GifHeaderParser setData(byte[] data) {
+ reset();
if (data != null) {
rawData = ByteBuffer.wrap(data);
rawData.rewind();
@@ -36,9 +34,20 @@ public class GifHeaderParser {
rawData = null;
header.status = GifDecoder.STATUS_OPEN_ERROR;
}
+ return this;
+ }
+
+ private void reset() {
+ rawData = null;
+ Arrays.fill(block, (byte) 0);
+ header = new GifHeader();
+ blockSize = 0;
}
public GifHeader parseHeader() {
+ if (rawData == null) {
+ throw new IllegalStateException("You must call setData() before parseHeader()");
+ }
if (err()) {
return header;
}
@@ -57,24 +66,34 @@ public class GifHeaderParser {
/**
* Main file parser. Reads GIF content blocks.
*/
- protected void readContents() {
- // read GIF file content blocks
+ private void readContents() {
+ // Read GIF file content blocks.
boolean done = false;
while (!(done || err())) {
int code = read();
switch (code) {
- case 0x2C: // image separator
+ // 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;
- case 0x21: // extension
+ // Extension.
+ case 0x21:
code = read();
switch (code) {
- case 0xf9: // graphics control extension
- //Start a new frame
+ // Graphics control extension.
+ case 0xf9:
+ // Start a new frame.
header.currentFrame = new GifFrame();
readGraphicControlExt();
break;
- case 0xff: // application extension
+ // Application extension.
+ case 0xff:
readBlock();
String app = "";
for (int i = 0; i < 11; i++) {
@@ -83,23 +102,29 @@ public class GifHeaderParser {
if (app.equals("NETSCAPE2.0")) {
readNetscapeExt();
} else {
- skip(); // don't care
+ // Don't care.
+ skip();
}
break;
- case 0xfe:// comment extension
+ // Comment extension.
+ case 0xfe:
skip();
break;
- case 0x01:// plain text extension
+ // Plain text extension.
+ case 0x01:
skip();
break;
- default: // uninteresting extension
+ // Uninteresting extension.
+ default:
skip();
}
break;
- case 0x3b: // terminator
+ // Terminator.
+ case 0x3b:
done = true;
break;
- case 0x00: // bad byte, but keep going and see what happens break;
+ // Bad byte, but keep going and see what happens break;
+ case 0x00:
default:
header.status = STATUS_FORMAT_ERROR;
}
@@ -107,64 +132,76 @@ public class GifHeaderParser {
}
/**
- * Reads Graphics Control Extension values
+ * Reads Graphics Control Extension values.
*/
- protected void readGraphicControlExt() {
- read(); // block size
- int packed = read(); // packed fields
- header.currentFrame.dispose = (packed & 0x1c) >> 2; // disposal method
+ private void readGraphicControlExt() {
+ // Block size.
+ read();
+ // Packed fields.
+ int packed = read();
+ // Disposal method.
+ header.currentFrame.dispose = (packed & 0x1c) >> 2;
if (header.currentFrame.dispose == 0) {
- header.currentFrame.dispose = 1; // elect to keep old image if discretionary
+ // Elect to keep old image if discretionary.
+ header.currentFrame.dispose = 1;
}
header.currentFrame.transparency = (packed & 1) != 0;
- header.isTransparent |= header.currentFrame.transparency;
- header.currentFrame.delay = readShort() * 10; // delay in milliseconds
- header.currentFrame.transIndex = read(); // transparent color index
- read(); // block terminator
+ // Delay in milliseconds.
+ header.currentFrame.delay = readShort() * 10;
+ // Transparent color index
+ header.currentFrame.transIndex = read();
+ // Block terminator
+ read();
}
- /**
- * Reads next frame image
+ /**
+ * Reads next frame image.
*/
- protected void readBitmap() {
- header.currentFrame.ix = readShort(); // (sub)image position & size
+ private void readBitmap() {
+ // (sub)image position & size.
+ header.currentFrame.ix = readShort();
header.currentFrame.iy = readShort();
header.currentFrame.iw = readShort();
header.currentFrame.ih = readShort();
int packed = read();
- lctFlag = (packed & 0x80) != 0; // 1 - local color table flag interlace
- lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
+ // 1 - local color table flag interlace
+ boolean lctFlag = (packed & 0x80) != 0;
+ int lctSize = (int) Math.pow(2, (packed & 0x07) + 1);
// 3 - sort flag
// 4-5 - reserved lctSize = 2 << (packed & 7); // 6-8 - local color
// table size
header.currentFrame.interlace = (packed & 0x40) != 0;
if (lctFlag) {
- header.currentFrame.lct = readColorTable(lctSize); // read table
+ // Read table.
+ header.currentFrame.lct = readColorTable(lctSize);
} else {
- header.currentFrame.lct = null; //No local color table
+ // No local color table.
+ header.currentFrame.lct = null;
}
- header.currentFrame.bufferFrameStart = rawData.position(); //Save this as the decoding position pointer
+ // Save this as the decoding position pointer.
+ header.currentFrame.bufferFrameStart = rawData.position();
- skipBitmapData(); // false decode pixel data to advance buffer.
+ // False decode pixel data to advance buffer.
+ skipImageData();
- skip();
if (err()) {
return;
}
header.frameCount++;
- header.frames.add(header.currentFrame); // add image to frame
+ // Add image to frame.
+ header.frames.add(header.currentFrame);
}
- /**
- * Reads Netscape extenstion to obtain iteration count
+ /**
+ * Reads Netscape extension to obtain iteration count.
*/
- protected void readNetscapeExt() {
+ private void readNetscapeExt() {
do {
readBlock();
if (block[0] == 1) {
- // loop count sub-block
+ // Loop count sub-block.
int b1 = ((int) block[1]) & 0xff;
int b2 = ((int) block[2]) & 0xff;
header.loopCount = (b2 << 8) | b1;
@@ -173,7 +210,7 @@ public class GifHeaderParser {
}
- /**
+ /**
* Reads GIF file header information.
*/
private void readHeader() {
@@ -191,34 +228,34 @@ public class GifHeaderParser {
header.bgColor = header.gct[header.bgIndex];
}
}
- /**
- * Reads Logical Screen Descriptor
+ /**
+ * Reads Logical Screen Descriptor.
*/
- protected void readLSD() {
- // logical screen size
+ private void readLSD() {
+ // Logical screen size.
header.width = readShort();
header.height = readShort();
- // packed fields
+ // Packed fields
int packed = read();
- header.gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
- // 2-4 : color resolution
- // 5 : gct sort flag
- header.gctSize = 2 << (packed & 7); // 6-8 : gct size
- header.bgIndex = read(); // background color index
- header.pixelAspect = read(); // pixel aspect ratio
-
- //Now that we know the size, init scratch arrays
- //TODO: these shouldn't go here.
-// mainPixels = new byte[header.width * header.height];
-// mainScratch = new int[header.width * header.height];
+ // 1 : global color table flag.
+ header.gctFlag = (packed & 0x80) != 0;
+ // 2-4 : color resolution.
+ // 5 : gct sort flag.
+ // 6-8 : gct size.
+ header.gctSize = 2 << (packed & 7);
+ // Background color index.
+ header.bgIndex = read();
+ // Pixel aspect ratio
+ header.pixelAspect = read();
}
- /**
- * Reads color table as 256 RGB integer values
+
+ /**
+ * Reads color table as 256 RGB integer values.
*
- * @param ncolors int number of colors to read
- * @return int array containing 256 colors (packed ARGB with full alpha)
+ * @param ncolors int number of colors to read.
+ * @return int array containing 256 colors (packed ARGB with full alpha).
*/
- protected int[] readColorTable(int ncolors) {
+ private int[] readColorTable(int ncolors) {
int nbytes = 3 * ncolors;
int[] tab = null;
byte[] c = new byte[nbytes];
@@ -226,7 +263,9 @@ public class GifHeaderParser {
try {
rawData.get(c);
- tab = new int[256]; // max size to avoid bounds checks
+ // TODO: what bounds checks are we avoiding if we know the number of colors?
+ // Max size to avoid bounds checks.
+ tab = new int[MAX_BLOCK_SIZE];
int i = 0;
int j = 0;
while (i < ncolors) {
@@ -243,133 +282,38 @@ public class GifHeaderParser {
return tab;
}
- /**
- * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick.
+ /**
+ * Skips LZW image data for a single frame to advance buffer.
*/
- protected void skipBitmapData() {
- int nullCode = -1;
- int npix = header.width * header.height;
- int available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, count, i, datum, data_size, first, top, bi, pi;
-
- if (prefix == null) {
- prefix = new short[MAX_STACK_SIZE];
- }
- if (suffix == null) {
- suffix = new byte[MAX_STACK_SIZE];
- }
- if (pixelStack == null) {
- pixelStack = new byte[MAX_STACK_SIZE + 1];
- }
-
- // Initialize GIF data stream decoder.
- data_size = read();
- clear = 1 << data_size;
- end_of_information = clear + 1;
- available = clear + 2;
- old_code = nullCode;
- code_size = data_size + 1;
- code_mask = (1 << code_size) - 1;
- long start = System.currentTimeMillis();
- for (code = 0; code < clear; code++) {
- prefix[code] = 0; // XXX ArrayIndexOutOfBoundsException
- suffix[code] = (byte) code;
- }
-
- start = System.currentTimeMillis();
- // Decode GIF pixel stream.
- datum = bits = count = first = top = pi = bi = 0;
- int iterations = 0;
- for (i = 0; i < npix; ) {
- iterations++;
- if (top == 0) {
- if (bits < code_size) {
- // Load bytes until there are enough bits for a code.
- if (count == 0) {
- // Read a new data block.
- count = readBlock();
- if (count <= 0) {
- break;
- }
- bi = 0;
- }
- datum += (((int) block[bi]) & 0xff) << bits;
- bits += 8;
- bi++;
- count--;
- continue;
- }
- // Get the next code.
- code = datum & code_mask;
- datum >>= code_size;
- bits -= code_size;
- // Interpret the code
- if ((code > available) || (code == end_of_information)) {
- break;
- }
- if (code == clear) {
- // Reset decoder.
- code_size = data_size + 1;
- code_mask = (1 << code_size) - 1;
- available = clear + 2;
- old_code = nullCode;
- continue;
- }
- if (old_code == nullCode) {
- pixelStack[top++] = suffix[code];
- old_code = code;
- first = code;
- continue;
- }
- in_code = code;
- if (code == available) {
- pixelStack[top++] = (byte) first;
- code = old_code;
- }
- while (code > clear) {
- pixelStack[top++] = suffix[code];
- code = prefix[code];
- }
- first = ((int) suffix[code]) & 0xff;
- // Add a new string to the string table,
- if (available >= MAX_STACK_SIZE) {
- break;
- }
- pixelStack[top++] = (byte) first;
- prefix[available] = (short) old_code;
- suffix[available] = (byte) first;
- available++;
- if (((available & code_mask) == 0) && (available < MAX_STACK_SIZE)) {
- code_size++;
- code_mask += available;
- }
- old_code = in_code;
- }
- // Pop a pixel off the pixel stack.
- top--;
- i++;
- }
+ private void skipImageData() {
+ // lzwMinCodeSize
+ read();
+ // data sub-blocks
+ skip();
}
- /**
+ /**
* Skips variable length blocks up to and including next zero length block.
*/
- protected void skip() {
+ private void skip() {
+ int blockSize;
do {
- readBlock();
- } while ((blockSize > 0) && !err());
+ blockSize = read();
+ rawData.position(rawData.position() + blockSize);
+ } while (blockSize > 0);
}
- /**
+ /**
* Reads next variable length block from input.
*
* @return number of bytes stored in "buffer"
*/
- protected int readBlock() {
+ private int readBlock() {
blockSize = read();
int n = 0;
if (blockSize > 0) {
+ int count = 0;
try {
- int count;
while (n < blockSize) {
count = blockSize - n;
rawData.get(block, n, count);
@@ -377,20 +321,20 @@ public class GifHeaderParser {
n += count;
}
} catch (Exception e) {
- Log.w(TAG, "Error Reading Block", e);
+ Log.w(TAG, "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e);
header.status = STATUS_FORMAT_ERROR;
}
}
return n;
}
- /**
+ /**
* Reads a single byte from the input stream.
*/
private int read() {
int curByte = 0;
try {
- curByte = (rawData.get() & 0xFF);
+ curByte = rawData.get() & 0xFF;
} catch (Exception e) {
header.status = STATUS_FORMAT_ERROR;
}
@@ -398,10 +342,10 @@ public class GifHeaderParser {
}
/**
- * Reads next 16-bit value, LSB first
+ * Reads next 16-bit value, LSB first.
*/
- protected int readShort() {
- // read 16-bit value
+ private int readShort() {
+ // Read 16-bit value.
return rawData.getShort();
}