diff options
author | Alan Newberger <alann@google.com> | 2014-10-21 16:47:05 -0700 |
---|---|---|
committer | Alan Newberger <alann@google.com> | 2014-10-21 16:47:05 -0700 |
commit | d7429cebda1204623acc02825eaf50053a7ec3a9 (patch) | |
tree | 940e7ade07d68bfc15fb9f190a7429ab00372d17 /third_party/gif_decoder | |
parent | 934fc60e7f34cd2c4e8422a12c6015790a820b44 (diff) | |
parent | 39f12e09c7273b13604184e523846c1ef18ff311 (diff) | |
download | glide-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')
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(); } |