From b555e3fc29e03198795f77313808a98c49de3799 Mon Sep 17 00:00:00 2001 From: seven332 Date: Tue, 4 Aug 2015 19:04:42 +0800 Subject: [PATCH] Use jni to load gif --- app/build.gradle | 69 +- .../hippo/ehviewer/gallery/GifDecoder.java | 138 +++ .../ehviewer/gallery/GifDecoderBuilder.java | 49 + .../hippo/ehviewer/gallery/ImageHandler.java | 122 +-- .../gallery/gifdecoder/ByteArrayPool.java | 73 -- .../gallery/gifdecoder/GifBitmapProvider.java | 47 - .../gallery/gifdecoder/GifDecoder.java | 720 ------------- .../ehviewer/gallery/gifdecoder/GifFrame.java | 36 - .../gallery/gifdecoder/GifHeader.java | 58 -- .../gallery/gifdecoder/GifHeaderParser.java | 381 ------- .../gallery/glrenderer/GifTexture.java | 154 ++- .../gallery/glrenderer/TiledTexture.java | 4 +- .../hippo/ehviewer/ui/GalleryActivity.java | 43 +- app/src/main/jni/gif_decoder.c | 491 +++++++++ app/src/main/jni/gif_decoder.h | 53 + app/src/main/jni/giflib/config.h | 5 + app/src/main/jni/giflib/dgif_lib.c | 956 ++++++++++++++++++ app/src/main/jni/giflib/gif_lib.h | 172 ++++ app/src/main/jni/giflib/gif_lib_private.h | 49 + app/src/main/jni/giflib/gifalloc.c | 188 ++++ beerbelly | 2 +- build.gradle | 2 +- cardsalon | 2 +- conaco | 2 +- hotspot | 2 +- unifile | 2 +- vectorold | 2 +- 27 files changed, 2350 insertions(+), 1472 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GifDecoder.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GifDecoderBuilder.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/ByteArrayPool.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifBitmapProvider.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifDecoder.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifFrame.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeader.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeaderParser.java create mode 100644 app/src/main/jni/gif_decoder.c create mode 100644 app/src/main/jni/gif_decoder.h create mode 100644 app/src/main/jni/giflib/config.h create mode 100644 app/src/main/jni/giflib/dgif_lib.c create mode 100644 app/src/main/jni/giflib/gif_lib.h create mode 100644 app/src/main/jni/giflib/gif_lib_private.h create mode 100644 app/src/main/jni/giflib/gifalloc.c diff --git a/app/build.gradle b/app/build.gradle index 627133efb..59cf0dee1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,49 +1,54 @@ -apply plugin: 'com.android.application' +apply plugin: 'com.android.model.application' -android { - compileSdkVersion 22 - buildToolsVersion "23.0.0 rc3" +model { + android { + compileSdkVersion = 22 + buildToolsVersion = "23.0.0 rc3" - defaultConfig { - applicationId "com.hippo.ehviewer.test" - minSdkVersion 14 - targetSdkVersion 22 - versionCode 1 - versionName "1.0" - } - - sourceSets { - main { - java.srcDirs = ['src/main/java', 'src/main/java-gen'] + defaultConfig.with { + applicationId = "com.hippo.ehviewer.test" + minSdkVersion.apiLevel = 14 + targetSdkVersion.apiLevel = 22 + versionCode = 1 + versionName = "1.0" } } - buildTypes { - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard-rules.pro', - '../rippleold/proguard-rules.pro', - '../vectorold/proguard-rules.pro', - '../conaco/proguard-rules.pro' - } + android.buildTypes { release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard-rules.pro', - '../rippleold/proguard-rules.pro', - '../vectorold/proguard-rules.pro', - '../conaco/proguard-rules.pro' + minifyEnabled = true + shrinkResources = true + proguardFiles += [file('proguard-rules.pro'), + file('../rippleold/proguard-rules.pro'), + file('../vectorold/proguard-rules.pro'), + file('../conaco/proguard-rules.pro')] } } + + android.sources { + main { + java { + source.srcDirs = ['src/main/java', 'src/main/java-gen'] + } + } + } + + android.ndk { + moduleName = "gifdecoder" + ldLibs += ["jnigraphics"] + } + + android.productFlavors { + // To include all cpu architectures, leaves abiFilters empty + create("all") + } } dependencies { compile 'com.android.support:support-v4:22.2.1' compile 'com.android.support:appcompat-v7:22.2.1' compile 'de.greenrobot:greendao:1.3.7' - debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' - releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' + compile 'com.squareup.leakcanary:leakcanary-android:1.3.1' compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':unifile') compile project(':vectorold') diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoder.java b/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoder.java new file mode 100644 index 000000000..6815bc868 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoder.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery; + +import android.graphics.Bitmap; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.hippo.conaco.BitmapPool; +import com.hippo.yorozuya.AssertUtils; + +import java.io.BufferedInputStream; +import java.io.InputStream; + +public class GifDecoder { + + private static final String TAG = GifDecoder.class.getSimpleName(); + + private static final int INITIAL_FRAME_INDEX = -1; + + private long mNativeGifDecoder; + private int mWidth; + private int mHeight; + private int mFrameCount; + private int[] mDelays; + + private int mFrameIndex = INITIAL_FRAME_INDEX; + + private static BitmapPool sBitmapPool; + + public static void setBitmapPool(BitmapPool bitmapPool) { + sBitmapPool = bitmapPool; + } + + GifDecoder(long nativeGifDecoder, int width, int height, int frameCount, int[] delays) { + mNativeGifDecoder = nativeGifDecoder; + mWidth = width; + mHeight = height; + mFrameCount = frameCount; + mDelays = delays; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public int getFrameCount() { + return mFrameCount; + } + + /** + * Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to + * decode any frames. + */ + public void resetFrameIndex() { + mFrameIndex = INITIAL_FRAME_INDEX; + } + + /** + * Move the animation frame counter forward. + */ + public void advance() { + mFrameIndex = (mFrameIndex + 1) % mFrameCount; + } + + public int getNextDelay() { + if (mFrameIndex == INITIAL_FRAME_INDEX) { + mFrameIndex = 0; + } + + return mDelays[mFrameIndex]; + } + + public @Nullable Bitmap getNextFrame() { + AssertUtils.assertNotEquals("The GifDecoder is recycled", 0, mNativeGifDecoder); + + if (mFrameIndex == INITIAL_FRAME_INDEX) { + mFrameIndex = 0; + } + + if (sBitmapPool != null) { + Bitmap bitmap = sBitmapPool.getBitmap(mWidth, mHeight); + if (bitmap == null) { + bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); + } + if (nativeRenderBitmap(mNativeGifDecoder, bitmap, mFrameIndex)) { + return bitmap; + } else { + if (sBitmapPool != null) { + sBitmapPool.addReusableBitmap(bitmap); + } + } + } + + Log.w(TAG, "Please set BitmaPool before getNextFrame"); + return null; + } + + public void recycle() { + if (mNativeGifDecoder != 0) { + nativeRecycle(mNativeGifDecoder); + mNativeGifDecoder = 0; + } + } + + public static GifDecoder decodeStream(InputStream is) { + BufferedInputStream bis = new BufferedInputStream(is, 256); + return nativeDecodeStream(bis); + } + + private static native GifDecoder nativeDecodeStream(InputStream is); + + private static native boolean nativeRenderBitmap(long mNativeGifDecoder, Bitmap bitmap, int index); + + private static native void nativeRecycle(long mNativeGifDecoder); + + static{ + System.loadLibrary("gifdecoder"); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoderBuilder.java b/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoderBuilder.java new file mode 100644 index 000000000..8b24d7bdb --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GifDecoderBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.hippo.yorozuya.IOUtils; + +import java.io.InputStream; + +public class GifDecoderBuilder { + + private InputStream mInputStream; + + public GifDecoderBuilder(@NonNull InputStream is) { + mInputStream = is; + } + + public void close() { + IOUtils.closeQuietly(mInputStream); + mInputStream = null; + } + + public @Nullable GifDecoder build() { + if (mInputStream != null) { + GifDecoder gifDecoder = GifDecoder.decodeStream(mInputStream); + IOUtils.closeQuietly(mInputStream); + mInputStream = null; + return gifDecoder; + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java index 79adddeba..cfd62d786 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ImageHandler.java @@ -18,7 +18,6 @@ package com.hippo.ehviewer.gallery; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.nfc.FormatException; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Pair; @@ -29,24 +28,20 @@ import com.hippo.conaco.BitmapPool; import com.hippo.ehviewer.AppConfig; import com.hippo.ehviewer.EhCacheKeyFactory; import com.hippo.ehviewer.client.data.GalleryBase; -import com.hippo.ehviewer.gallery.gifdecoder.ByteArrayPool; -import com.hippo.ehviewer.gallery.gifdecoder.GifBitmapProvider; -import com.hippo.ehviewer.gallery.gifdecoder.GifDecoder; -import com.hippo.ehviewer.gallery.gifdecoder.GifHeader; -import com.hippo.ehviewer.gallery.gifdecoder.GifHeaderParser; import com.hippo.httpclient.HttpClient; import com.hippo.io.UniFileInputStreamPipe; import com.hippo.io.UniFileOutputStreamPipe; import com.hippo.network.DownloadClient; import com.hippo.network.DownloadRequest; import com.hippo.unifile.UniFile; -import com.hippo.unifile.UniRandomReadFile; import com.hippo.yorozuya.FileUtils; import com.hippo.yorozuya.IOUtils; import com.hippo.yorozuya.io.InputStreamPipe; import com.hippo.yorozuya.io.OutputStreamPipe; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -70,7 +65,6 @@ public class ImageHandler { private volatile Mode mMode; private AtomicReferenceArray mFilenames; private BitmapPool mBitmapPool = new BitmapPool(); - private GifBitmapProvider mGifBitmapProvider = new GifBitmapProvider(new BitmapPool(), new ByteArrayPool()); public enum Mode { READ, @@ -379,52 +373,55 @@ public class ImageHandler { return null; } - options.inJustDecodeBounds = false; - options.inMutable = true; - options.inSampleSize = 1; - options.inBitmap = mBitmapPool.getInBitmap(options); - options.inPreferredConfig = Bitmap.Config.ARGB_8888; + if ("image/gif".equals(options.outMimeType)) { + options.inJustDecodeBounds = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; - is = isPipe.open(); - bitmap = BitmapFactory.decodeStream(is, null, options); + is = isPipe.open(); + bitmap = BitmapFactory.decodeStream(is, null, options); + isPipe.close(); - if (bitmap != null && "image/gif".equals(options.outMimeType)) { - // It is gif - UniRandomReadFile randomReadFile; - if (mMode == Mode.READ) { - // Copy the file to - File tempFile = AppConfig.createTempFile(); - if (tempFile == null) { - throw new IOException("Can't get temp dir or can't create temp dir"); - } - isPipe.close(); - is = isPipe.open(); - IOUtils.copy(is, new FileOutputStream(tempFile)); - randomReadFile = new TempRandomReadFile(tempFile, UniRandomReadFile.fromFile(tempFile)); - } else if (mMode == Mode.DOWNLOAD) { - if (mDownloadDir == null) { - throw new IOException("Can't find download dir"); - } - UniFile uniFile = mDownloadDir.findFile(getImageFilename(index)); - if (uniFile == null) { - throw new IOException("Can't find image file"); + if (bitmap != null) { + if (mMode == Mode.READ) { + // Copy the file to + File tempFile = AppConfig.createTempFile(); + if (tempFile == null) { + throw new IOException("Can't get temp dir or can't create temp dir"); + } + is = isPipe.open(); + IOUtils.copy(is, new FileOutputStream(tempFile)); + isPipe.close(); + is = new TempFileInputStream(tempFile); + } else if (mMode == Mode.DOWNLOAD) { + if (mDownloadDir == null) { + throw new IOException("Can't find download dir"); + } + UniFile uniFile = mDownloadDir.findFile(getImageFilename(index)); + if (uniFile == null) { + throw new IOException("Can't find image file"); + } + is = uniFile.openInputStream(); + } else { + throw new IllegalStateException("Unknown state"); } - isPipe.close(); - isPipe.release(); - randomReadFile = uniFile.createRandomReadFile(); - } else { - throw new IllegalStateException("Unknown state"); - } + GifDecoderBuilder gifDecoderBuilder = new GifDecoderBuilder(is); - GifHeader gifHeader = new GifHeaderParser().setData(randomReadFile).parseHeader(); - if (gifHeader.getStatus() == GifDecoder.STATUS_OK) { - return new Pair<>(new GifDecoder(mGifBitmapProvider, gifHeader, randomReadFile), bitmap); - } else { - throw new FormatException("Can't decode gif"); + return new Pair<>(gifDecoderBuilder, bitmap); } + return null; } else { - // It is not gif, just reture + // It is not gif + options.inJustDecodeBounds = false; + options.inMutable = true; + options.inSampleSize = 1; + options.inBitmap = mBitmapPool.getInBitmap(options); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + + is = isPipe.open(); + bitmap = BitmapFactory.decodeStream(is, null, options); + isPipe.close(); + return bitmap; } } catch (Exception e) { @@ -455,40 +452,19 @@ public class ImageHandler { } } - public static class TempRandomReadFile extends UniRandomReadFile { + public static class TempFileInputStream extends FileInputStream { private File mFile; - private UniRandomReadFile mRandomReadFile; - public TempRandomReadFile(File file, UniRandomReadFile randomReadFile) { + public TempFileInputStream(File file) throws FileNotFoundException { + super(file); mFile = file; - mRandomReadFile = randomReadFile; - } - - @Override - public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { - return mRandomReadFile.read(buffer, byteOffset, byteCount); - } - - @Override - public void seek(long offset) throws IOException { - mRandomReadFile.seek(offset); - } - - @Override - public long position() throws IOException { - return mRandomReadFile.position(); - } - - @Override - public long length() throws IOException { - return mRandomReadFile.length(); } @Override public void close() throws IOException { - mRandomReadFile.close(); - FileUtils.delete(mFile); + super.close(); + mFile.delete(); } } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/ByteArrayPool.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/ByteArrayPool.java deleted file mode 100644 index abb1ac13b..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/ByteArrayPool.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2015 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hippo.ehviewer.gallery.gifdecoder; - -import java.lang.ref.WeakReference; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -public class ByteArrayPool { - - private final Set> mReusableSet = new LinkedHashSet<>(); - - public synchronized void add(byte[] bytes) { - if (bytes != null) { - mReusableSet.add(new WeakReference<>(bytes)); - } - } - - public synchronized byte[] getAtLeast(int size) { - final Iterator> iterator = mReusableSet.iterator(); - byte[] item; - while (iterator.hasNext()) { - item = iterator.next().get(); - if (item != null) { - if (item.length >= size) { - // Remove from reusable set so it can't be used again. - iterator.remove(); - return item; - } - } else { - // Remove from the set if the reference has been cleared or - // it can't be used. - iterator.remove(); - } - } - return null; - } - - public synchronized byte[] getExactly(int size) { - final Iterator> iterator = mReusableSet.iterator(); - byte[] item; - while (iterator.hasNext()) { - item = iterator.next().get(); - if (item != null) { - if (item.length == size) { - // Remove from reusable set so it can't be used again. - iterator.remove(); - return item; - } - } else { - // Remove from the set if the reference has been cleared or - // it can't be used. - iterator.remove(); - } - } - return null; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifBitmapProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifBitmapProvider.java deleted file mode 100644 index 68b817531..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifBitmapProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.hippo.ehviewer.gallery.gifdecoder; - -import android.graphics.Bitmap; -import android.support.annotation.NonNull; - -import com.hippo.conaco.BitmapPool; - -public class GifBitmapProvider implements GifDecoder.BitmapProvider { - - private BitmapPool mBitmapPool; - private ByteArrayPool mByteArrayPool; - - public GifBitmapProvider(BitmapPool bitmapPool, ByteArrayPool byteArrayPool) { - mBitmapPool = bitmapPool; - mByteArrayPool = byteArrayPool; - } - - @NonNull - @Override - public Bitmap obtain(int width, int height) { - Bitmap bitmap = mBitmapPool.getBitmap(width, height); - if (bitmap == null) { - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setHasAlpha(true); - } - return bitmap; - } - - @Override - public void release(Bitmap bitmap) { - mBitmapPool.addReusableBitmap(bitmap); - } - - @Override - public byte[] obtainByteArray(int size) { - byte[] bytes = mByteArrayPool.getAtLeast(size); - if (bytes == null) { - bytes = new byte[size]; - } - return bytes; - } - - @Override - public void release(byte[] bytes) { - mByteArrayPool.add(bytes); - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifDecoder.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifDecoder.java deleted file mode 100644 index 466f26a1e..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifDecoder.java +++ /dev/null @@ -1,720 +0,0 @@ -package com.hippo.ehviewer.gallery.gifdecoder; - -/** - * 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. - */ - -import android.graphics.Bitmap; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.hippo.unifile.UniRandomReadFile; -import com.hippo.yorozuya.IOUtils; - -import java.io.IOException; -import java.util.Arrays; - -/** - * Reads frame data from a GIF image source and decodes it into individual frames for animation - * purposes. Image data can be read from either and InputStream source or a byte[]. - *

- * This class is optimized for running animations with the frames, there are no methods to get - * individual frame images, only to decode the next frame in the animation sequence. Instead, it - * lowers its memory footprint by only housing the minimum data necessary to decode the next frame - * in the animation sequence. - *

- * The animation must be manually moved forward using {@link #advance()} before requesting the next - * frame. This method must also be called before you request the first frame or an error will - * occur. - *

- * Implementation adapted from sample code published in Lyons. (2004). Java for - * Programmers, republished under the MIT Open Source License - */ -public class GifDecoder { - private static final String TAG = GifDecoder.class.getSimpleName(); - - /** - * File read status: No errors. - */ - public static final int STATUS_OK = 0; - /** - * File read status: Error decoding file (may be partially decoded). - */ - public static final int STATUS_FORMAT_ERROR = 1; - /** - * File read status: Unable to read source. - */ - public static final int STATUS_READ_ERROR = 2; - /** - * 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. - */ - private static final int DISPOSAL_UNSPECIFIED = 0; - /** - * 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. - */ - private static final int DISPOSAL_BACKGROUND = 2; - /** - * GIF Disposal Method meaning clear canvas to frame before last. - */ - private static final int DISPOSAL_PREVIOUS = 3; - - private static final int NULL_CODE = -1; - - private static final int INITIAL_FRAME_POINTER = -1; - - // Global File Header values and parsing flags. - // Active color table. - private int[] act; - - // Raw GIF data from input source. - private UniRandomReadFile rawData; - - // Raw data read working array. - private byte[] block; - - // Temporary buffer for block reading. Reads 16k chunks from the native buffer for processing, - // to greatly reduce JNI overhead. - private static final int WORK_BUFFER_SIZE = 16384; - @Nullable - private byte[] workBuffer; - private int workBufferSize = 0; - private int workBufferPosition = 0; - - // LZW decoder working arrays. - private short[] prefix; - private byte[] suffix; - private byte[] pixelStack; - private byte[] mainPixels; - private int[] mainScratch; - - private int framePointer; - private GifHeader header; - private BitmapProvider bitmapProvider; - private Bitmap previousImage; - private boolean savePrevious; - private int status; - private int sampleSize; - private int downsampledHeight; - private int downsampledWidth; - private boolean isFirstFrameTransparent; - - /** - * An interface that can be used to provide reused {@link Bitmap}s to avoid GCs - * from constantly allocating {@link Bitmap}s for every frame. - */ - public interface BitmapProvider { - /** - * Returns an {@link Bitmap} with exactly the given dimensions and config. - * - * @param width The width in pixels of the desired {@link android.graphics.Bitmap}. - * @param height The height in pixels of the desired {@link android.graphics.Bitmap}. - */ - @NonNull - Bitmap obtain(int width, int height); - - /** - * Releases the given Bitmap back to the pool. - */ - void release(Bitmap bitmap); - - /** - * Returns a byte array used for decoding and generating the frame bitmap. - * - * @param size the size of the byte array to obtain - */ - byte[] obtainByteArray(int size); - - /** - * Releases the given byte array back to the pool. - */ - void release(byte[] bytes); - - } - - public GifDecoder(BitmapProvider provider, GifHeader gifHeader, UniRandomReadFile rawData) { - this(provider, gifHeader, rawData, 1 /*sampleSize*/); - } - - public GifDecoder(BitmapProvider provider, GifHeader gifHeader, UniRandomReadFile rawData, int sampleSize) { - this.bitmapProvider = provider; - setData(gifHeader, rawData, sampleSize); - } - - public int getWidth() { - return header.width; - } - - public int getHeight() { - return header.height; - } - - /** - * Returns the current status of the decoder. - *

- *

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. - *

- */ - public int getStatus() { - return status; - } - - /** - * Move the animation frame counter forward. - */ - public void advance() { - framePointer = (framePointer + 1) % header.frameCount; - } - - /** - * Gets display duration for specified frame. - * - * @param n int index of frame. - * @return delay in milliseconds. - */ - public int getDelay(int n) { - int delay = -1; - if ((n >= 0) && (n < header.frameCount)) { - delay = header.frames.get(n).delay; - } - return delay; - } - - /** - * Gets display duration for the upcoming frame in ms. - */ - public int getNextDelay() { - if (header.frameCount <= 0 || framePointer < 0) { - return 0; - } - - return getDelay(framePointer); - } - - /** - * Gets the number of frames read from file. - * - * @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. - * - * @return frame index. - */ - public int getCurrentFrameIndex() { - return framePointer; - } - - /** - * Resets the frame pointer to before the 0th frame, as if we'd never used this decoder to - * decode any frames. - */ - public void resetFrameIndex() { - framePointer = INITIAL_FRAME_POINTER; - } - - /** - * Gets the "Netscape" iteration count, if any. A count of 0 means repeat indefinitely. - * - * @return iteration count if one was specified, else 1. - */ - public int getLoopCount() { - return header.loopCount; - } - - /** - * Get the next frame in the animation sequence. - * - * @return Bitmap representation of frame. - */ - public synchronized Bitmap getNextFrame() { - if (header.frameCount <= 0 || framePointer < 0) { - Log.d(TAG, "unable to decode frame, frameCount=" + header.frameCount + " framePointer=" + framePointer); - status = STATUS_FORMAT_ERROR; - } - if (status == STATUS_FORMAT_ERROR || status == STATUS_READ_ERROR) { - Log.d(TAG, "Unable to decode frame, status=" + status); - return null; - } - status = STATUS_OK; - - GifFrame currentFrame = header.frames.get(framePointer); - GifFrame previousFrame = null; - int previousIndex = framePointer - 1; - if (previousIndex >= 0) { - previousFrame = header.frames.get(previousIndex); - } - - final int savedBgColor = header.bgColor; - - // Set the appropriate color table. - if (currentFrame.lct == null) { - act = header.gct; - } else { - act = currentFrame.lct; - if (header.bgIndex == currentFrame.transIndex) { - header.bgColor = 0; - } - } - - int save = 0; - if (currentFrame.transparency) { - save = act[currentFrame.transIndex]; - // Set transparent color if specified. - act[currentFrame.transIndex] = 0; - } - if (act == null) { - Log.d(TAG, "No Valid Color Table"); - // No color table defined. - status = STATUS_FORMAT_ERROR; - return null; - } - - // Transfer pixel data to image. - Bitmap result = setPixels(currentFrame, previousFrame); - - // Reset the transparent pixel in the color table - if (currentFrame.transparency) { - act[currentFrame.transIndex] = save; - } - header.bgColor = savedBgColor; - - return result; - } - - public void clear() { - header = null; - mainPixels = null; - mainScratch = null; - if (previousImage != null) { - bitmapProvider.release(previousImage); - } - previousImage = null; - IOUtils.closeQuietly(rawData); - rawData = null; - isFirstFrameTransparent = false; - if (block != null) { - bitmapProvider.release(block); - } - if (workBuffer != null) { - bitmapProvider.release(workBuffer); - } - } - - public synchronized void setData(GifHeader header, UniRandomReadFile file, int sampleSize) { - if (sampleSize <= 0) { - throw new IllegalArgumentException("Sample size must be >=0, not: " + sampleSize); - } - // Make sure sample size is a power of 2. - sampleSize = Integer.highestOneBit(sampleSize); - this.status = STATUS_OK; - this.header = header; - isFirstFrameTransparent = false; - framePointer = INITIAL_FRAME_POINTER; - // Initialize the raw data buffer. - rawData = file; - - // 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; - } - } - - this.sampleSize = sampleSize; - // Now that we know the size, init scratch arrays. - // TODO: Find a way to avoid this entirely or at least downsample it - // (either should be possible). - mainPixels = new byte[header.width * header.height]; - mainScratch = new int[(header.width / sampleSize) * (header.height / sampleSize)]; - downsampledWidth = header.width / sampleSize; - downsampledHeight = header.height / sampleSize; - } - - /** - * Creates new frame image from current data (and previous frames as specified by their - * disposition codes). - */ - private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) { - // 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) { - // 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; - } else if (framePointer == 0) { - // TODO: We should check and see if all individual pixels are replaced. If they are, the - // first frame isn't actually transparent. For now, it's simpler and safer to assume - // drawing a transparent background means the GIF contains transparency. - isFirstFrameTransparent = true; - } - Arrays.fill(dest, c); - } else if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) { - // Start with the previous frame - previousImage.getPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); - } - } - - // Decode pixels for this frame into the global pixels[] scratch. - decodeBitmapData(currentFrame); - - int downsampledIH = currentFrame.ih / sampleSize; - int downsampledIY = currentFrame.iy / sampleSize; - int downsampledIW = currentFrame.iw / sampleSize; - int downsampledIX = currentFrame.ix / sampleSize; - // Copy each source line to the appropriate place in the destination. - int pass = 1; - int inc = 8; - int iline = 0; - boolean isFirstFrame = framePointer == 0; - for (int i = 0; i < downsampledIH; i++) { - int line = i; - if (currentFrame.interlace) { - if (iline >= downsampledIH) { - pass++; - switch (pass) { - case 2: - iline = 4; - break; - case 3: - iline = 2; - inc = 4; - break; - case 4: - iline = 1; - inc = 2; - break; - default: - break; - } - } - line = iline; - iline += inc; - } - line += downsampledIY; - if (line < downsampledHeight) { - int k = line * downsampledWidth; - // Start of line in dest. - int dx = k + downsampledIX; - // End of dest line. - int dlim = dx + downsampledIW; - if (k + downsampledWidth < dlim) { - // Past dest edge. - dlim = k + downsampledWidth; - } - // Start of line in source. - int sx = i * sampleSize * currentFrame.iw; - int maxPositionInSource = sx + ((dlim - dx) * sampleSize); - while (dx < dlim) { - // Map color and insert in destination. - int averageColor = averageColorsNear(sx, maxPositionInSource, currentFrame.iw); - if (averageColor != 0) { - dest[dx] = averageColor; - } else if (!isFirstFrameTransparent && isFirstFrame) { - isFirstFrameTransparent = true; - } - sx += sampleSize; - dx++; - } - } - } - - // Copy pixels into previous image - if (savePrevious && (currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE)) { - if (previousImage == null) { - previousImage = bitmapProvider.obtain(downsampledWidth, downsampledHeight); - } - previousImage.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); - } - - // Set pixels for current image. - Bitmap result = bitmapProvider.obtain(downsampledWidth, downsampledHeight); - result.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); - return result; - } - - private int averageColorsNear(int positionInMainPixels, int maxPositionInMainPixels, int currentFrameIw) { - int alphaSum = 0; - int redSum = 0; - int greenSum = 0; - int blueSum = 0; - - int totalAdded = 0; - // Find the pixels in the current row. - for (int i = positionInMainPixels; i < positionInMainPixels + sampleSize && i < mainPixels.length && i < maxPositionInMainPixels; i++) { - int currentColorIndex = ((int) mainPixels[i]) & 0xff; - int currentColor = act[currentColorIndex]; - if (currentColor != 0) { - alphaSum += currentColor >> 24 & 0x000000ff; - redSum += currentColor >> 16 & 0x000000ff; - greenSum += currentColor >> 8 & 0x000000ff; - blueSum += currentColor & 0x000000ff; - totalAdded++; - } - } - // Find the pixels in the next row. - for (int i = positionInMainPixels + currentFrameIw; i < positionInMainPixels + currentFrameIw + sampleSize && i < mainPixels.length && i < maxPositionInMainPixels; i++) { - int currentColorIndex = ((int) mainPixels[i]) & 0xff; - int currentColor = act[currentColorIndex]; - if (currentColor != 0) { - alphaSum += currentColor >> 24 & 0x000000ff; - redSum += currentColor >> 16 & 0x000000ff; - greenSum += currentColor >> 8 & 0x000000ff; - blueSum += currentColor & 0x000000ff; - totalAdded++; - } - } - if (totalAdded == 0) { - return 0; - } else { - return ((alphaSum / totalAdded) << 24) | ((redSum / totalAdded) << 16) | ((greenSum / totalAdded) << 8) | (blueSum / totalAdded); - } - } - - /** - * Decodes LZW image data into pixel array. Adapted from John Cristy's BitmapMagick. - */ - private void decodeBitmapData(GifFrame frame) { - workBufferSize = 0; - workBufferPosition = 0; - if (frame != null) { - // Jump to the frame start position. - try { - rawData.seek(frame.bufferFrameStart); - } catch (IOException e) { - e.printStackTrace(); // TODO what if get exception - } - } - - int npix = (frame == null) ? header.width * header.height : frame.iw * frame.ih; - int available, clear, codeMask, codeSize, endOfInformation, inCode, oldCode, bits, code, count, - i, datum, - dataSize, first, top, bi, pi; - - if (mainPixels == null || mainPixels.length < npix) { - // Allocate new pixel array. - mainPixels = new byte[npix]; - } - 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. - dataSize = readByte(); - clear = 1 << dataSize; - endOfInformation = clear + 1; - available = clear + 2; - oldCode = NULL_CODE; - codeSize = dataSize + 1; - codeMask = (1 << codeSize) - 1; - for (code = 0; code < clear; code++) { - // 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; ) { - // 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. - codeSize = dataSize + 1; - codeMask = (1 << codeSize) - 1; - available = clear + 2; - oldCode = NULL_CODE; - continue; - } - - if (code > available) { - status = STATUS_PARTIAL_DECODE; - break; - } - - if (code == endOfInformation) { - break; - } - - if (oldCode == NULL_CODE) { - pixelStack[top++] = suffix[code]; - oldCode = code; - first = code; - continue; - } - inCode = code; - if (code >= available) { - pixelStack[top++] = (byte) first; - code = oldCode; - } - while (code >= clear) { - pixelStack[top++] = suffix[code]; - code = prefix[code]; - } - first = ((int) suffix[code]) & 0xff; - pixelStack[top++] = (byte) first; - - // 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. - mainPixels[pi++] = pixelStack[--top]; - i++; - } - } - } - - // Clear missing pixels. - for (i = pi; i < npix; i++) { - mainPixels[i] = 0; - } - } - - /** - * Reads the next chunk for the intermediate work buffer. - */ - private void readChunkIfNeeded() throws IOException { - if (workBufferSize > workBufferPosition) { - return; - } - if (workBuffer == null) { - workBuffer = bitmapProvider.obtainByteArray(WORK_BUFFER_SIZE); - } - workBufferPosition = 0; - workBufferSize = Math.min((int) rawData.remaining(), WORK_BUFFER_SIZE); - rawData.read(workBuffer, 0, workBufferSize); - } - - /** - * Reads a single byte from the input stream. - */ - private int readByte() { - try { - readChunkIfNeeded(); - return workBuffer[workBufferPosition++] & 0xFF; - } catch (Exception e) { - e.printStackTrace(); - status = STATUS_FORMAT_ERROR; - return 0; - } - } - - /** - * Reads next variable length block from input. - * - * @return number of bytes stored in "buffer". - */ - private int readBlock() { - int blockSize = readByte(); - if (blockSize > 0) { - try { - if (block == null) { - block = bitmapProvider.obtainByteArray(255); - } - final int remaining = workBufferSize - workBufferPosition; - if (remaining >= blockSize) { - // Block can be read from the current work buffer. - System.arraycopy(workBuffer, workBufferPosition, block, 0, blockSize); - workBufferPosition += blockSize; - } else if (rawData.remaining() + remaining >= blockSize) { - // Block can be read in two passes. - System.arraycopy(workBuffer, workBufferPosition, block, 0, remaining); - workBufferPosition = workBufferSize; - readChunkIfNeeded(); - final int secondHalfRemaining = blockSize - remaining; - System.arraycopy(workBuffer, 0, block, remaining, secondHalfRemaining); - workBufferPosition += secondHalfRemaining; - } else { - Log.d(TAG, "Can't read block"); - status = STATUS_FORMAT_ERROR; - } - } catch (Exception e) { - Log.w(TAG, "Error Reading Block", e); - status = STATUS_FORMAT_ERROR; - } - } - return blockSize; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifFrame.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifFrame.java deleted file mode 100644 index 89af6f0fb..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifFrame.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.hippo.ehviewer.gallery.gifdecoder; - -/** - * Inner model class housing metadata for each frame. - */ -class GifFrame { - 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/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeader.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeader.java deleted file mode 100644 index 4659efb3b..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeader.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.hippo.ehviewer.gallery.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 GifDecoder}s to play the same - * animated GIF in multiple views. - */ -public class GifHeader { - - int[] gct = null; - int status = GifDecoder.STATUS_OK; - int frameCount = 0; - - GifFrame currentFrame; - List frames = new ArrayList(); - // 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. - */ - public int getStatus() { - return status; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeaderParser.java b/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeaderParser.java deleted file mode 100644 index 65d679555..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gifdecoder/GifHeaderParser.java +++ /dev/null @@ -1,381 +0,0 @@ -package com.hippo.ehviewer.gallery.gifdecoder; - -import android.util.Log; - -import com.hippo.unifile.UniRandomReadFile; - -import java.io.IOException; -import java.util.Arrays; - -/** - * A class responsible for creating {@link GifHeader}s from data - * representing animated gifs. - */ -public class GifHeaderParser { - public static final String TAG = "GifHeaderParser"; - - // The minimum frame delay in hundredths of a second. - static final int MIN_FRAME_DELAY = 3; - // The default frame delay in hundredths of a second for GIFs with frame delays less than the - // minimum. - static final int DEFAULT_FRAME_DELAY = 10; - - private static final int MAX_BLOCK_SIZE = 256; - // Raw data read working array. - private final byte[] block = new byte[MAX_BLOCK_SIZE]; - - private UniRandomReadFile rawData; - private GifHeader header; - private int blockSize = 0; - - private byte[] mTemp = new byte[2]; - - public GifHeaderParser setData(UniRandomReadFile data) { - reset(); - rawData = data; - return this; - } - - public void clear() { - rawData = null; - header = null; - } - - 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; - } - - try { - readHeader(); - if (!err()) { - readContents(); - if (header.frameCount < 0) { - header.status = GifDecoder.STATUS_FORMAT_ERROR; - } - } - } catch (IOException e) { - e.printStackTrace(); - header.status = GifDecoder.STATUS_READ_ERROR; - } - - return header; - } - - /** - * Main file parser. Reads GIF content blocks. - */ - private void readContents() throws IOException { - // Read GIF file content blocks. - boolean done = false; - while (!(done || err())) { - int code = read(); - switch (code) { - // Image separator. - case 0x2C: - // The graphics control extension is optional, but will always come first if it exists. - // If one did - // exist, there will be a non-null current frame which we should use. However if one - // did not exist, - // the current frame will be null and we must create it here. See issue #134. - if (header.currentFrame == null) { - header.currentFrame = new GifFrame(); - } - readBitmap(); - break; - // Extension. - case 0x21: - code = read(); - switch (code) { - // Graphics control extension. - case 0xf9: - // Start a new frame. - header.currentFrame = new GifFrame(); - readGraphicControlExt(); - break; - // Application extension. - case 0xff: - readBlock(); - String app = ""; - for (int i = 0; i < 11; i++) { - app += (char) block[i]; - } - if (app.equals("NETSCAPE2.0")) { - readNetscapeExt(); - } else { - // Don't care. - skip(); - } - break; - // Comment extension. - case 0xfe: - skip(); - break; - // Plain text extension. - case 0x01: - skip(); - break; - // Uninteresting extension. - default: - skip(); - } - break; - // Terminator. - case 0x3b: - done = true; - break; - // Bad byte, but keep going and see what happens break; - case 0x00: - default: - header.status = GifDecoder.STATUS_FORMAT_ERROR; - break; - } - } - } - - /** - * Reads Graphics Control Extension values. - */ - private void readGraphicControlExt() throws IOException { - // Block size. - read(); - // Packed fields. - int packed = read(); - // Disposal method. - header.currentFrame.dispose = (packed & 0x1c) >> 2; - if (header.currentFrame.dispose == 0) { - // Elect to keep old image if discretionary. - header.currentFrame.dispose = 1; - } - header.currentFrame.transparency = (packed & 1) != 0; - // Delay in milliseconds. - int delayInHundredthsOfASecond = readShort(); - // TODO: consider allowing -1 to indicate show forever. - if (delayInHundredthsOfASecond < MIN_FRAME_DELAY) { - delayInHundredthsOfASecond = DEFAULT_FRAME_DELAY; - } - header.currentFrame.delay = delayInHundredthsOfASecond * 10; - // Transparent color index - header.currentFrame.transIndex = read(); - // Block terminator - read(); - } - - /** - * Reads next frame image. - */ - private void readBitmap() throws IOException { - // (sub)image position & size. - header.currentFrame.ix = readShort(); - header.currentFrame.iy = readShort(); - header.currentFrame.iw = readShort(); - header.currentFrame.ih = readShort(); - - int packed = read(); - // 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) { - // Read table. - header.currentFrame.lct = readColorTable(lctSize); - } else { - // No local color table. - header.currentFrame.lct = null; - } - - // Save this as the decoding position pointer. - header.currentFrame.bufferFrameStart = (int) rawData.position(); - - // False decode pixel data to advance buffer. - skipImageData(); - - if (err()) { - return; - } - - header.frameCount++; - // Add image to frame. - header.frames.add(header.currentFrame); - } - - /** - * Reads Netscape extension to obtain iteration count. - */ - private void readNetscapeExt() throws IOException { - do { - readBlock(); - if (block[0] == 1) { - // Loop count sub-block. - int b1 = ((int) block[1]) & 0xff; - int b2 = ((int) block[2]) & 0xff; - header.loopCount = (b2 << 8) | b1; - } - } while ((blockSize > 0) && !err()); - } - - - /** - * Reads GIF file header information. - */ - private void readHeader() throws IOException { - String id = ""; - for (int i = 0; i < 6; i++) { - id += (char) read(); - } - if (!id.startsWith("GIF")) { - header.status = GifDecoder.STATUS_FORMAT_ERROR; - return; - } - readLSD(); - if (header.gctFlag && !err()) { - header.gct = readColorTable(header.gctSize); - header.bgColor = header.gct[header.bgIndex]; - } - } - - /** - * Reads Logical Screen Descriptor. - */ - private void readLSD() throws IOException { - // Logical screen size. - header.width = readShort(); - header.height = readShort(); - // Packed fields - int packed = read(); - // 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. - * - * @param ncolors int number of colors to read. - * @return int array containing 256 colors (packed ARGB with full alpha). - */ - private int[] readColorTable(int ncolors) throws IOException { - int nbytes = 3 * ncolors; - int[] tab; - byte[] c = new byte[nbytes]; - - try { - rawData.read(c); - - // 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) { - int r = ((int) c[j++]) & 0xff; - int g = ((int) c[j++]) & 0xff; - int b = ((int) c[j++]) & 0xff; - tab[i++] = 0xff000000 | (r << 16) | (g << 8) | b; - } - } catch (IOException e) { - Log.d(TAG, "Format Error Reading Color Table", e); - header.status = GifDecoder.STATUS_FORMAT_ERROR; - throw e; - } - - return tab; - } - - /** - * Skips LZW image data for a single frame to advance buffer. - */ - private void skipImageData() throws IOException { - // lzwMinCodeSize - read(); - // data sub-blocks - skip(); - } - - /** - * Skips variable length blocks up to and including next zero length block. - */ - private void skip() throws IOException { - int blockSize; - do { - blockSize = read(); - rawData.seek(rawData.position() + blockSize); - } while (blockSize > 0); - } - - /** - * Reads next variable length block from input. - * - * @return number of bytes stored in "buffer" - */ - private int readBlock() throws IOException { - blockSize = read(); - int n = 0; - if (blockSize > 0) { - int count = 0; - try { - while (n < blockSize) { - count = blockSize - n; - rawData.read(block, n, count); - - n += count; - } - } catch (Exception e) { - Log.d(TAG, "Error Reading Block n: " + n + " count: " + count + " blockSize: " + blockSize, e); - header.status = GifDecoder.STATUS_FORMAT_ERROR; - } - } - return n; - } - - /** - * Reads a single byte from the input stream. - */ - private int read() throws IOException { - try { - rawData.read(mTemp, 0, 1); - return mTemp[0] & 0xff; - } catch (IOException e) { - header.status = GifDecoder.STATUS_READ_ERROR; - throw e; - } - } - - /** - * Reads next 16-bit value, LSB first. - */ - private int readShort() throws IOException { - // Read 16-bit value. - try { - rawData.read(mTemp, 0, 2); - return (mTemp[1] << 8 & 0xff00) | (mTemp[0] & 0xff); - } catch (IOException e) { - header.status = GifDecoder.STATUS_READ_ERROR; - throw e; - } - } - - private boolean err() { - return header.status != GifDecoder.STATUS_OK; - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GifTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GifTexture.java index 84a3152ba..7e97cdf5d 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GifTexture.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GifTexture.java @@ -18,9 +18,9 @@ package com.hippo.ehviewer.gallery.glrenderer; import android.graphics.Bitmap; import android.support.annotation.NonNull; -import android.util.Log; -import com.hippo.ehviewer.gallery.gifdecoder.GifDecoder; +import com.hippo.ehviewer.gallery.GifDecoder; +import com.hippo.ehviewer.gallery.GifDecoderBuilder; // TODO read file to memory or do jni public class GifTexture extends TiledTexture { @@ -29,10 +29,13 @@ public class GifTexture extends TiledTexture { private static TiledTexture.Uploader sUploader; private static InfiniteThreadExecutor sThreadExecutor; + private static PVLock mPVLock = new PVLock(3); - private GifDecoder mGifDecoder; + private volatile GifDecoderBuilder mGifDecoderBuilder; + private volatile GifDecoder mGifDecoder; private boolean mRunning = false; private volatile boolean mDecoding = false; + private volatile boolean mRecycle =false; private GifDecodeTask mGifDecodeTask; @@ -49,49 +52,79 @@ public class GifTexture extends TiledTexture { class GifDecodeTask implements Runnable { private long mLastTime = 0; - private volatile boolean mClear = false; + private volatile boolean mRest = false; - public void clearByMyself() { - mClear = true; + public void setRest() { + mRest = true; } @Override public void run() { + mLastTime = System.currentTimeMillis(); + GifDecoder gifDecoder = mGifDecoder; + if (gifDecoder == null) { + if (mGifDecoderBuilder == null) { + // It is recycled + mRunning = false; + mGifDecodeTask = null; + return; + } else { + mDecoding = true; + + mPVLock.p(); + if (!mRecycle) { + gifDecoder = mGifDecoderBuilder.build(); + } + mPVLock.v(); + + mGifDecoderBuilder.close(); + mGifDecoderBuilder = null; + + // First image is loaded, so go to next + if (gifDecoder != null) { + gifDecoder.advance(); + } + } + } + + if (mRecycle || gifDecoder == null) { + // Can't get GifDecoder + mDecoding = false; + mRunning = false; + mGifDecodeTask = null; + if (gifDecoder != null) { + gifDecoder.recycle(); + } + return; + } else { + mGifDecoder = gifDecoder; + } + while (mRunning && sUploader != null) { mDecoding = true; gifDecoder.advance(); Bitmap bitmap = gifDecoder.getNextFrame(); - if (bitmap == null) { - Log.d(TAG, "Can't get bitmap, GifDecoder state = " + gifDecoder.getStatus()); - mRunning = false; - break; - } - setBitmap(bitmap); - if (sUploader == null) { - mRunning = false; - break; + if (bitmap != null) { + setBitmap(bitmap); + if (sUploader == null) { + break; + } + sUploader.addTexture(GifTexture.this); } - sUploader.addTexture(GifTexture.this); long delay = gifDecoder.getNextDelay(); long time = System.currentTimeMillis(); - if (mLastTime != 0) { - delay -= time - mLastTime; - } - - - //Say.d(TAG, "time - mLastTime = " + (time - mLastTime)); - //Say.d(TAG, "gifDecoder.getNextDelay() = " + (gifDecoder.getNextDelay())); + delay -= time - mLastTime; mLastTime = time; mDecoding = false; - if (delay > 5) { + if (delay > 0) { try { Thread.sleep(delay); } catch (InterruptedException e) { @@ -99,18 +132,30 @@ public class GifTexture extends TiledTexture { } } } + + mRunning = false; + mDecoding = false; mGifDecodeTask = null; - if (mClear) { - gifDecoder.clear(); + if (mRest) { + mRest = false; + gifDecoder.resetFrameIndex(); + } + + if (mRecycle) { + gifDecoder.recycle(); } } } - public GifTexture(@NonNull GifDecoder gifDecoder, @NonNull Bitmap bitmap) { + public GifTexture(@NonNull GifDecoderBuilder gifDecoderBuilder, @NonNull Bitmap bitmap) { super(bitmap); - mGifDecoder = gifDecoder; + mGifDecoderBuilder = gifDecoderBuilder; + + //start to buid GifDecode + mGifDecodeTask = new GifDecodeTask(); + sThreadExecutor.execute(mGifDecodeTask); } public void start() { @@ -126,9 +171,8 @@ public class GifTexture extends TiledTexture { public void stop() { if (mRunning) { mRunning = false; - mGifDecodeTask = null; - if (!mDecoding) { + if (!mDecoding && mGifDecoder != null) { mGifDecoder.resetFrameIndex(); mGifDecoder.advance(); Bitmap bitmap = mGifDecoder.getNextFrame(); @@ -138,18 +182,23 @@ public class GifTexture extends TiledTexture { sUploader.addTexture(GifTexture.this); } } + } else { + if (mGifDecodeTask != null) { + mGifDecodeTask.setRest(); + } } + + mGifDecodeTask = null; } } @Override public void recycle() { + mRecycle = true; mRunning = false; - if (mDecoding) { - mGifDecodeTask.clearByMyself(); - } else { - mGifDecoder.clear(); + if (!mDecoding && mGifDecoder != null) { + mGifDecoder.recycle(); } mGifDecoder = null; @@ -157,4 +206,41 @@ public class GifTexture extends TiledTexture { super.recycle(); } + + static class PVLock { + + private int mCounter; + + public PVLock(int count) { + mCounter = count; + } + + /** + * Obtain + */ + public synchronized void p() { + while (true) { + if (mCounter > 0) { + mCounter--; + break; + } else { + try { + this.wait(); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + + /** + * Release + */ + public synchronized void v() { + mCounter++; + if (mCounter > 0) { + this.notify(); + } + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java index 36cd12569..06e592aed 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java @@ -72,7 +72,7 @@ public class TiledTexture implements Texture { public interface OnFreeBitmapListener { - void onFreeBitmapListener(Bitmap bitmap); + void onFreeBitmap(TiledTexture tiledTexture, Bitmap bitmap); } public static void setOnFreeBitmapListener(OnFreeBitmapListener listener) { @@ -290,7 +290,7 @@ public class TiledTexture implements Texture { private void onFreeBitmap(Bitmap bitmap) { if (sOnFreeBitmapListener != null) { - sOnFreeBitmapListener.onFreeBitmapListener(bitmap); + sOnFreeBitmapListener.onFreeBitmap(this, bitmap); } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 634e61e8c..1ee6879d2 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -27,14 +27,16 @@ import android.support.annotation.Nullable; import android.util.Pair; import com.hippo.app.StatsActivity; +import com.hippo.conaco.BitmapPool; import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.data.GalleryBase; import com.hippo.ehviewer.gallery.GalleryProvider; import com.hippo.ehviewer.gallery.GalleryProviderListener; import com.hippo.ehviewer.gallery.GallerySpider; +import com.hippo.ehviewer.gallery.GifDecoder; +import com.hippo.ehviewer.gallery.GifDecoderBuilder; import com.hippo.ehviewer.gallery.ImageHandler; import com.hippo.ehviewer.gallery.ZipGalleryProvider; -import com.hippo.ehviewer.gallery.gifdecoder.GifDecoder; import com.hippo.ehviewer.gallery.glrenderer.GifTexture; import com.hippo.ehviewer.gallery.glrenderer.InfiniteThreadExecutor; import com.hippo.ehviewer.gallery.glrenderer.TextTexture; @@ -81,6 +83,8 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre private int mSize; + private BitmapPool mGifBitmapPool; + private boolean getGalleryProvider(Intent intent) { if (intent != null) { String action = intent.getAction(); @@ -136,6 +140,8 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre mThreadExecutor = new InfiniteThreadExecutor(3000, new LinkedBlockingQueue(), new PriorityThreadFactory("GifDecode", Process.THREAD_PRIORITY_BACKGROUND)); GifTexture.initialize(mUploader, mThreadExecutor); + mGifBitmapPool = new BitmapPool(); + GifDecoder.setBitmapPool(mGifBitmapPool); Typeface tf = Typeface.createFromAsset(mResources.getAssets(), "fonts/number.ttf"); mTextTexture = TextTexture.create(tf, mResources.getDimensionPixelSize(R.dimen.gallery_index_text), @@ -174,6 +180,7 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre GalleryPageView.setTextTexture(null); mUploader.clear(); mTextTexture.recycle(); + GifDecoder.setBitmapPool(null); GifTexture.uninitialize(); TiledTexture.setOnFreeBitmapListener(null); TiledTexture.freeResources(); @@ -205,9 +212,15 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre } @Override - public void onFreeBitmapListener(Bitmap bitmap) { - if (mGalleryProvider != null) { - mGalleryProvider.releaseBitmap(bitmap); + public void onFreeBitmap(TiledTexture tiledTexture, Bitmap bitmap) { + if (tiledTexture instanceof GifTexture) { + if (mGifBitmapPool != null ) { + mGifBitmapPool.addReusableBitmap(bitmap); + } + } else { + if (mGalleryProvider != null) { + mGalleryProvider.releaseBitmap(bitmap); + } } } @@ -230,11 +243,17 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre } public void releaseImage(Object obj) { - if (mGalleryProvider != null && obj != null) { + if (obj != null) { if (obj instanceof Bitmap) { - mGalleryProvider.releaseBitmap((Bitmap) obj); + if (mGalleryProvider != null) { + mGalleryProvider.releaseBitmap((Bitmap) obj); + } } else if (obj instanceof Pair) { - mGalleryProvider.releaseBitmap((Bitmap) ((Pair) obj).second); + Pair pair = (Pair) obj; + ((GifDecoderBuilder) pair.first).close(); + if (mGifBitmapPool != null) { + mGifBitmapPool.addReusableBitmap((Bitmap) pair.second); + } } } } @@ -300,7 +319,7 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre mUploader.addTexture(tiledTexture); } else if (obj instanceof Pair) { Pair pair = (Pair) obj; - tiledTexture = new GifTexture((GifDecoder) pair.first, (Bitmap) pair.second); + tiledTexture = new GifTexture((GifDecoderBuilder) pair.first, (Bitmap) pair.second); mUploader.addTexture(tiledTexture); } else { bindBadImage(view); @@ -321,6 +340,12 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre view.mIndexView.setVisibility(GLView.VISIBLE); } + private void bindWait(GalleryPageView view) { + clearTiledTextureInPage(view); + view.mProgressView.setVisibility(GLView.INVISIBLE); + view.mIndexView.setVisibility(GLView.VISIBLE); + } + private void bindNone(GalleryPageView view) { clearTiledTextureInPage(view); view.mProgressView.setVisibility(GLView.VISIBLE); @@ -347,7 +372,7 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre if (result instanceof Float) { bindPercent(view, (Float) result); } else if (result == GalleryProvider.RESULT_WAIT) { - // Just wait + bindWait(view); } else if (result == GalleryProvider.RESULT_NONE) { bindNone(view); } else if (result == GalleryProvider.RESULT_FAILED) { diff --git a/app/src/main/jni/gif_decoder.c b/app/src/main/jni/gif_decoder.c new file mode 100644 index 000000000..bbad1aba4 --- /dev/null +++ b/app/src/main/jni/gif_decoder.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include "gif_decoder.h" + +#define USE_PIXELS 0 +#define USE_BAK 1 +#define SET_BG 2 + +/** +* Global VM reference, initialized in JNI_OnLoad +*/ +static JavaVM *g_jvm; + +static int errorCode; + +static inline void clearException(JNIEnv* env) { + if ((*env)->ExceptionOccurred(env)) { + (*env)->ExceptionClear(env); + } +} + +static inline StreamContainer *createStreamContainer(JNIEnv* env, jobject is) { + StreamContainer *container = NULL; + jobject stream = NULL; + jclass streamClazz = NULL; + jbyteArray buffer = NULL; + + while (true) { + container = malloc(sizeof(StreamContainer)); + if (container == NULL) break; + + stream = (*env)->NewGlobalRef(env, is); + if (stream == NULL) break; + container->stream = stream; + + streamClazz = (*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, is)); + if (streamClazz == NULL) break; + container->streamClazz = streamClazz; + + jmethodID readMID = (*env)->GetMethodID(env, streamClazz, "read", "([BII)I"); + jmethodID resetMID = (*env)->GetMethodID(env, streamClazz, "reset", "()V"); + if (readMID == 0 || resetMID == 0) break; + container->readMID = readMID; + container->resetMID = resetMID; + + buffer = (*env)->NewByteArray(env, 256); + buffer = (*env)->NewGlobalRef(env, buffer); + if (buffer == NULL) break; + container->buffer = buffer; + + return container; + } + + free(container); + (*env)->DeleteGlobalRef(env, stream); + (*env)->DeleteGlobalRef(env, streamClazz); + (*env)->DeleteGlobalRef(env, buffer); + return NULL; +} + +static inline void freeStreamContainer(JNIEnv* env, StreamContainer* container) { + (*env)->DeleteGlobalRef(env, container->buffer); + (*env)->DeleteGlobalRef(env, container->streamClazz); + (*env)->DeleteGlobalRef(env, container->stream); + free(container); +} + +inline inline JNIEnv *getEnv() { + JNIEnv *env; + + if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) == JNI_OK) { + return env; + } else { + return NULL; + } +} + +static int streamReadFun(GifFileType *gif, GifByteType *bytes, int size) { + StreamContainer *sc = gif->UserData; + JNIEnv *env = getEnv(); + if (env == NULL || (*env)->MonitorEnter(env, sc->stream) != 0) { + return 0; + } + + jint len = (*env)->CallIntMethod(env, sc->stream, sc->readMID, sc->buffer, 0, size); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionClear(env); + len = 0; + } + else if (len > 0) { + (*env)->GetByteArrayRegion(env, sc->buffer, 0, len, (jbyte *) bytes); + } + if ((*env)->MonitorExit(env, sc->stream) != 0) { + len = 0; + } + + return len >= 0 ? len : 0; +} + +static inline GIF* analysisGifFileType(GifFileType* gifFile, int* delays) { + + int i; + int* trans; + int* disposals; + GIF* gif; + GraphicsControlBlock gcb; + + int imageCount = gifFile->ImageCount; + + gif = (GIF*) malloc(sizeof(GIF)); + if (gif == NULL) { + return NULL; + } + + gif->trans = (int*) malloc(imageCount * sizeof(int)); + gif->disposals = (int*) malloc(imageCount * sizeof(int)); + gif->pixels = malloc(gifFile->SWidth * gifFile->SHeight * sizeof(RGBA)); + if (gif->trans == NULL || gif->disposals == NULL || gif->pixels == NULL) { + free(gif->pixels); + free(gif->disposals); + free(gif->trans); + free(gif); + return NULL; + } + + gif->gifFile = gifFile; + gif->pxlIndex = -1; + gif->bak = NULL; + gif->bakIndex = -1; + + trans = gif->trans; + disposals = gif->disposals; + for (i = 0; i < imageCount; i++) { + if (DGifSavedExtensionToGCB(gifFile, i, &gcb) != GIF_OK) { + delays[i] = MIN_DELAY; + trans[i] = -1; + disposals[i] = 0; + } else { + delays[i] = gcb.DelayTime * 10; + trans[i] = gcb.TransparentColor; + disposals[i] = gcb.DisposalMode; + if (delays[i] < MIN_DELAY) { + delays[i] = MIN_DELAY; + } + } + } + + return gif; +} + +static inline void freeGif(GIF* gif) { + DGifCloseFile(gif->gifFile, &errorCode); + free(gif->trans); + free(gif->disposals); + free(gif->pixels); + free(gif->bak); + free(gif); +} + +static inline jobject createObjFromGifFileType(JNIEnv* env, GifFileType* gifFile) { + + int slurp; + int frameCount; + int* delays; + GIF* gif; + + jintArray delayArray; + jclass gifClazz; + jmethodID constructor; + + // Slurp + slurp = DGifSlurp(gifFile); + if (slurp != GIF_OK) + return NULL; + + // Check frame count + frameCount = gifFile->ImageCount; + if (frameCount < 1) { + return NULL; + } + + // delays malloc + delays = (int*) malloc(frameCount * sizeof(int)); + if (delays == NULL) { + return NULL; + } + + // analysis GifFileType + gif = analysisGifFileType(gifFile, delays); + if (gif == NULL) { + free(delays); + return NULL; + } + + // Copy value + delayArray = (*env)->NewIntArray(env, frameCount); + (*env)->SetIntArrayRegion(env, delayArray, 0, frameCount, delays); + free(delays); + + gifClazz = (*env)->FindClass(env, "com/hippo/ehviewer/gallery/GifDecoder"); + constructor = (*env)->GetMethodID(env, gifClazz, "", + "(JIII[I)V"); + if (constructor == 0) { + freeGif(gif); + return NULL; + } else { + return (*env)->NewObject(env, gifClazz, constructor, (jlong) gif, + gifFile->SWidth, gifFile->SHeight, frameCount, + delayArray); + } +} + +/* + * stack store the index to draw + * stack capacity should be imageCount * sizeof(int). + * When read it, use from tail to head + */ +static void getDrawStack(GIF *gif, int targetIndex, int *which, int *stack, + int *stackSize) { + int i; + int *disposals = gif->disposals; + int pxlIndex = gif->pxlIndex; + int bakIndex = gif->bakIndex; + + *which = SET_BG; + *stackSize = 0; + + for (i = targetIndex; i >= 0;) { + if (i == pxlIndex) { + *which = USE_PIXELS; + break; + } else if (i == bakIndex) { + *which = USE_BAK; + break; + } + + // Add index to stack + stack[*stackSize] = i; + (*stackSize)++; + + // If it is first index, break; + if (i == 0) + break; + + switch (disposals[--i]) { + default: + case DISPOSAL_UNSPECIFIED: + case DISPOSE_DO_NOT: + break; + case DISPOSE_BACKGROUND: + // Just out of while(), + i = -1; + break; + case DISPOSE_PREVIOUS: + i--; + break; + } + } +} + +static inline bool getColorFromTable(const ColorMapObject* cmap, int index, + RGBA* color) { + if (cmap == NULL || index < 0 || index >= cmap->ColorCount) { + return false; + } + GifColorType gct = cmap->Colors[index]; + color->alpha = 0xff; + color->red = gct.Red; + color->green = gct.Green; + color->blue = gct.Blue; + return true; +} + +static inline void erase(RGBA* pixels, int num, RGBA color) { + int i; + for (i = 0; i < num; i++) { + *(pixels + i) = color; + } +} + +static void setBg(GIF* gif, void* pixels) { + GifFileType* gifFile = gif->gifFile; + int num = gifFile->SWidth * gifFile->SHeight; + RGBA color; + if (!getColorFromTable(gifFile->SColorMap, gifFile->SBackGroundColor, &color)) { + color.alpha = 0x00; + color.red = 0x00; + color.green = 0x00; + color.blue = 0x00; + } + erase(pixels, num, color); +} + +static inline void copyLine(GifByteType* src, RGBA* dst, + const ColorMapObject* cmap, int tran, int width) { + for (; width > 0; width--, src++, dst++) { + int index = *src; + if (tran == -1 || index != tran) { + getColorFromTable(cmap, index, dst); + } + } +} + +static void drawFrame(GIF* gif, void* pixels, int index) { + GifFileType* gifFile = gif->gifFile; + int ScreenWidth = gifFile->SWidth; + int ScreenHeight = gifFile->SHeight; + SavedImage cur = gifFile->SavedImages[index]; + GifImageDesc gid = cur.ImageDesc; + ColorMapObject *cmap = gid.ColorMap; + int tran = gif->trans[index]; + GifByteType* src; + RGBA* dst; + + if (cmap == NULL) + cmap = gifFile->SColorMap; + if (cmap != NULL) { + src = cur.RasterBits; + int copyWidth = gid.Width; + int copyHeight = gid.Height; + if (copyWidth + gid.Left > ScreenWidth) { + copyWidth = ScreenWidth - gid.Left; + } + if (copyHeight + gid.Top > ScreenHeight) { + copyHeight = ScreenHeight - gid.Top; + } + + if (copyWidth > 0 && copyHeight > 0) { + dst = (RGBA*) pixels + gid.Top * ScreenWidth + gid.Left; + for (; copyHeight > 0; copyHeight--, src += gid.Width, dst += ScreenWidth) { + copyLine(src, dst, cmap, tran, copyWidth); + } + } + } +} + +static void renderPixels(GIF *gif, int index) { + + int width = gif->gifFile->SWidth; + int height = gif->gifFile->SHeight; + int imageCount = gif->gifFile->ImageCount; + int which, stackSize; + int stack[imageCount]; + void* tempPoint; + int tempInt; + + getDrawStack(gif, index, &which, stack, &stackSize); + + // prepare pixels + switch (which) { + case USE_PIXELS: + break; + case USE_BAK: + // switch pixel and bak + tempPoint = gif->bak; + gif->bak = gif->pixels; + gif->pixels = tempPoint; + tempInt = gif->bakIndex; + gif->bakIndex = gif->pxlIndex; + gif->pxlIndex = tempInt; + break; + default: + case SET_BG: + // Set bg + setBg(gif, gif->pixels); + break; + } + + // Draw frame + for (stackSize--; stackSize >= 0; stackSize--) { + drawFrame(gif, gif->pixels, stack[stackSize]); + } + + // update pxlIndex + gif->pxlIndex = index; + + // Set back if necessary + if (index + 1 < imageCount && gif->disposals[index + 1] == DISPOSE_PREVIOUS && + gif->bakIndex != gif->pxlIndex) { + size_t size = width * height * sizeof(RGBA); + if (gif->bak == NULL) { + gif->bak = malloc(size); + } + if (gif->bak != NULL) { + memcpy(gif->bak, gif->pixels, size); + gif->bakIndex = index; + } + } +} + +jobject Java_com_hippo_ehviewer_gallery_GifDecoder_nativeDecodeStream + (JNIEnv* env, jclass clazz, jobject stream) { + + StreamContainer* container = createStreamContainer(env, stream); + + GIF* gif = (GIF*) malloc(sizeof(GIF)); + if (gif == NULL) { + freeStreamContainer(env, container); + return NULL; + } + + GifFileType *gifFile = DGifOpen(container, &streamReadFun, &errorCode); + if (gifFile == NULL) { + freeStreamContainer(env, container); + clearException(env); + return NULL; + } + + GIF *gifImage = createObjFromGifFileType(env, gifFile); + if (gifImage == NULL) { + DGifCloseFile(gifFile, &errorCode); + } + + freeStreamContainer(env, container); + clearException(env); + + return gifImage; +} + +static inline void copy(RGBA *src, RGBA *dst, int size) { + int i = 0; + while(i++ < size) { + *(dst++) = *(src++); + } +} + +jboolean Java_com_hippo_ehviewer_gallery_GifDecoder_nativeRenderBitmap + (JNIEnv* env, jclass clazz, jlong nativeGif, jobject bitmap, jint index) { + GIF *gif = (GIF *) nativeGif; + AndroidBitmapInfo info; + int width = gif->gifFile->SWidth; + int height = gif->gifFile->SHeight; + void *pixels = NULL; + + if (index < 0 || index >= gif->gifFile->ImageCount) { + return false; + } + + AndroidBitmap_getInfo(env, bitmap, &info); + if (width != info.width || height != info.height || width * sizeof(RGBA) != info.stride) { + return false; + } + + renderPixels(gif, index); + + AndroidBitmap_lockPixels(env, bitmap, &pixels); + if (pixels == NULL) { + return false; + } + copy(gif->pixels, pixels, width * height); + pixels = NULL; + AndroidBitmap_unlockPixels(env, bitmap); + + return true; +} + +void Java_com_hippo_ehviewer_gallery_GifDecoder_nativeRecycle + (JNIEnv* env, jclass clazz, jlong nativeGif) { + freeGif((GIF *) nativeGif); +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) (&env), JNI_VERSION_1_6) != JNI_OK) + return -1; + g_jvm = vm; + return JNI_VERSION_1_6; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { +} diff --git a/app/src/main/jni/gif_decoder.h b/app/src/main/jni/gif_decoder.h new file mode 100644 index 000000000..f87858855 --- /dev/null +++ b/app/src/main/jni/gif_decoder.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EHVIEWER_GIF_DECODER_H +#define EHVIEWER_GIF_DECODER_H + +#include + +#include "giflib/gif_lib.h" + +#define MIN_DELAY 16 + +typedef struct { + jobject stream; + jclass streamClazz; + jmethodID readMID; + jmethodID resetMID; + jbyteArray buffer; +} StreamContainer; + +typedef struct { + GifFileType* gifFile; + int* trans; + int* disposals; + void* pixels; + int pxlIndex; + void* bak; + int bakIndex; +} GIF; + +typedef unsigned char byte; + +typedef struct { + byte red; + byte green; + byte blue; + byte alpha; +} RGBA; + +#endif //EHVIEWER_GIF_DECODER_H diff --git a/app/src/main/jni/giflib/config.h b/app/src/main/jni/giflib/config.h new file mode 100644 index 000000000..3da12c003 --- /dev/null +++ b/app/src/main/jni/giflib/config.h @@ -0,0 +1,5 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Version number of package */ +#define VERSION "5.1.1" diff --git a/app/src/main/jni/giflib/dgif_lib.c b/app/src/main/jni/giflib/dgif_lib.c new file mode 100644 index 000000000..06df1ab54 --- /dev/null +++ b/app/src/main/jni/giflib/dgif_lib.c @@ -0,0 +1,956 @@ +/****************************************************************************** + +dgif_lib.c - GIF decoding + +The functions here and in egif_lib.c are partitioned carefully so that +if you only require one of read and write capability, only one of these +two modules will be linked. Preserve this property! + +*****************************************************************************/ + +#include +#include +#include + +#ifdef _WIN32 +#include +#endif /* _WIN32 */ + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* compose unsigned little endian value */ +#define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8)) + +/* avoid extra function call in case we use fread (TVT) */ +#define READ(_gif,_buf,_len) \ + (((GifFilePrivateType*)_gif->Private)->Read ? \ + ((GifFilePrivateType*)_gif->Private)->Read(_gif,_buf,_len) : \ + fread(_buf,1,_len,((GifFilePrivateType*)_gif->Private)->File)) + +static int DGifGetWord(GifFileType *GifFile, GifWord *Word); +static int DGifSetupDecompress(GifFileType *GifFile); +static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, + int LineLen); +static int DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode); +static int DGifDecompressInput(GifFileType *GifFile, int *Code); +static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, + GifByteType *NextByte); + +/****************************************************************************** + GifFileType constructor with user supplied input function (TVT) +******************************************************************************/ +GifFileType * +DGifOpen(void *userData, InputFunc readFunc, int *Error) +{ + char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + /* Belt and suspenders, in case the null pointer isn't zero */ + GifFile->SavedImages = NULL; + GifFile->SColorMap = NULL; + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (!Private) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + free((char *)GifFile); + return NULL; + } + + GifFile->Private = (void *)Private; + Private->File = NULL; + Private->FileState = FILE_STATE_READ; + + Private->Read = readFunc; /* TVT */ + GifFile->UserData = userData; /* TVT */ + + /* Lets see if this is a GIF file: */ + /* coverity[check_return] */ + if (READ(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) { + if (Error != NULL) + *Error = D_GIF_ERR_READ_FAILED; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* Check for GIF prefix at start of file */ + Buf[GIF_STAMP_LEN] = '\0'; + if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_GIF_FILE; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + free((char *)Private); + free((char *)GifFile); + if (Error != NULL) + *Error = D_GIF_ERR_NO_SCRN_DSCR; + return NULL; + } + + GifFile->Error = 0; + + return GifFile; +} + +/****************************************************************************** + This routine should be called before any other DGif calls. Note that + this routine is called automatically from DGif file open routines. +******************************************************************************/ +int +DGifGetScreenDesc(GifFileType *GifFile) +{ + int BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* Put the screen descriptor into the file: */ + if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR) + return GIF_ERROR; + + if (READ(GifFile, Buf, 3) != 3) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + return GIF_ERROR; + } + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->SBackGroundColor = Buf[1]; + if (Buf[0] & 0x80) { /* Do we have global color map? */ + int i; + + GifFile->SColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->SColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the global color map: */ + for (i = 0; i < GifFile->SColorMap->ColorCount; i++) { + /* coverity[check_return] */ + if (READ(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + GifFile->SColorMap->Colors[i].Red = Buf[0]; + GifFile->SColorMap->Colors[i].Green = Buf[1]; + GifFile->SColorMap->Colors[i].Blue = Buf[2]; + } + } else { + GifFile->SColorMap = NULL; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. +******************************************************************************/ +int +DGifGetRecordType(GifFileType *GifFile, GifRecordType* Type) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* coverity[check_return] */ + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + switch (Buf) { + case DESCRIPTOR_INTRODUCER: + *Type = IMAGE_DESC_RECORD_TYPE; + break; + case EXTENSION_INTRODUCER: + *Type = EXTENSION_RECORD_TYPE; + break; + case TERMINATOR_INTRODUCER: + *Type = TERMINATE_RECORD_TYPE; + break; + default: + *Type = UNDEFINED_RECORD_TYPE; + GifFile->Error = D_GIF_ERR_WRONG_RECORD; + return GIF_ERROR; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. + Note it is assumed the Image desc. header has been read. +******************************************************************************/ +int +DGifGetImageDesc(GifFileType *GifFile) +{ + unsigned int BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + SavedImage *sp; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifGetWord(GifFile, &GifFile->Image.Left) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Top) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Width) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Height) == GIF_ERROR) + return GIF_ERROR; + if (READ(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->Image.Interlace = (Buf[0] & 0x40) ? true : false; + + /* Setup the colormap */ + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + /* Does this image have local color map? */ + if (Buf[0] & 0x80) { + unsigned int i; + + GifFile->Image.ColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->Image.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the image local color map: */ + for (i = 0; i < GifFile->Image.ColorMap->ColorCount; i++) { + /* coverity[check_return] */ + if (READ(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + GifFile->Image.ColorMap->Colors[i].Red = Buf[0]; + GifFile->Image.ColorMap->Colors[i].Green = Buf[1]; + GifFile->Image.ColorMap->Colors[i].Blue = Buf[2]; + } + } + + if (GifFile->SavedImages) { + SavedImage* new_saved_images = + (SavedImage *)realloc(GifFile->SavedImages, + sizeof(SavedImage) * (GifFile->ImageCount + 1)); + if (new_saved_images == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + GifFile->SavedImages = new_saved_images; + } else { + if ((GifFile->SavedImages = + (SavedImage *) malloc(sizeof(SavedImage))) == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + + sp = &GifFile->SavedImages[GifFile->ImageCount]; + memcpy(&sp->ImageDesc, &GifFile->Image, sizeof(GifImageDesc)); + if (GifFile->Image.ColorMap != NULL) { + sp->ImageDesc.ColorMap = GifMakeMapObject( + GifFile->Image.ColorMap->ColorCount, + GifFile->Image.ColorMap->Colors); + if (sp->ImageDesc.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + sp->RasterBits = (unsigned char *)NULL; + sp->ExtensionBlockCount = 0; + sp->ExtensionBlocks = (ExtensionBlock *) NULL; + + GifFile->ImageCount++; + + Private->PixelCount = (long)GifFile->Image.Width * + (long)GifFile->Image.Height; + + /* Reset decompress algorithm parameters. */ + return DGifSetupDecompress(GifFile); +} + +/****************************************************************************** + Get one full scanned line (Line) of length LineLen from GIF file. +******************************************************************************/ +int +DGifGetLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) +{ + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (!LineLen) + LineLen = GifFile->Image.Width; + + if ((Private->PixelCount -= LineLen) > 0xffff0000UL) { + GifFile->Error = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, Line, LineLen) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably won't be called any more, so let's clean up + * everything before we return: need to flush out all the + * rest of image until an empty block (size 0) + * detected. We use GetCodeNext. + */ + do + if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR) + return GIF_ERROR; + while (Dummy != NULL) ; + } + return GIF_OK; + } else + return GIF_ERROR; +} + +/****************************************************************************** + Get an extension block (see GIF manual) from GIF file. This routine only + returns the first data block, and DGifGetExtensionNext should be called + after this one until NULL extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). + Note it is assumed the Extension description header has been read. +******************************************************************************/ +int +DGifGetExtension(GifFileType *GifFile, int *ExtCode, GifByteType **Extension) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* coverity[check_return] */ + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *ExtCode = Buf; + + return DGifGetExtensionNext(GifFile, Extension); +} + +/****************************************************************************** + Get a following extension block (see GIF manual) from GIF file. This + routine should be called until NULL Extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +DGifGetExtensionNext(GifFileType *GifFile, GifByteType ** Extension) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + if (Buf > 0) { + *Extension = Private->Buf; /* Use private unused buffer. */ + (*Extension)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data,check_return] */ + if (READ(GifFile, &((*Extension)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else + *Extension = NULL; + + return GIF_OK; +} + +/****************************************************************************** + Extract a Graphics Control Block from raw extension data +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB) +{ + if (GifExtensionLength != 4) { + return GIF_ERROR; + } + + GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07; + GCB->DelayTime = UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]); + if (GifExtension[0] & 0x01) + GCB->TransparentColor = (int)GifExtension[3]; + else + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + + return GIF_OK; +} + +/****************************************************************************** + Extract the Graphics Control Block for a saved image, if it exists. +******************************************************************************/ + +int DGifSavedExtensionToGCB(GifFileType *GifFile, + int ImageIndex, GraphicsControlBlock *GCB) +{ + int i; + + if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) + return GIF_ERROR; + + GCB->DisposalMode = DISPOSAL_UNSPECIFIED; + GCB->DelayTime = 0; + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + + for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) { + ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; + if (ep->Function == GRAPHICS_EXT_FUNC_CODE) + return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, GCB); + } + + return GIF_ERROR; +} + +/****************************************************************************** + This routine should be called last, to close the GIF file. +******************************************************************************/ +int +DGifCloseFile(GifFileType *GifFile, int *ErrorCode) +{ + GifFilePrivateType *Private; + + if (GifFile == NULL || GifFile->Private == NULL) + return GIF_ERROR; + + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + + if (GifFile->SColorMap) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + + if (GifFile->SavedImages) { + GifFreeSavedImages(GifFile); + GifFile->SavedImages = NULL; + } + + GifFreeExtensions(&GifFile->ExtensionBlockCount, &GifFile->ExtensionBlocks); + + Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + if (ErrorCode != NULL) + *ErrorCode = D_GIF_ERR_NOT_READABLE; + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + if (Private->File && (fclose(Private->File) != 0)) { + if (ErrorCode != NULL) + *ErrorCode = D_GIF_ERR_CLOSE_FAILED; + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + free((char *)GifFile->Private); + free(GifFile); + if (ErrorCode != NULL) + *ErrorCode = D_GIF_SUCCEEDED; + return GIF_OK; +} + +/****************************************************************************** + Get 2 bytes (word) from the given file: +******************************************************************************/ +static int +DGifGetWord(GifFileType *GifFile, GifWord *Word) +{ + unsigned char c[2]; + + /* coverity[check_return] */ + if (READ(GifFile, c, 2) != 2) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + *Word = (GifWord)UNSIGNED_LITTLE_ENDIAN(c[0], c[1]); + return GIF_OK; +} + +/****************************************************************************** + Continue to get the image code in compressed form. This routine should be + called until NULL block is returned. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +DGifGetCodeNext(GifFileType *GifFile, GifByteType **CodeBlock) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* coverity[tainted_data_argument] */ + /* coverity[check_return] */ + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + /* coverity[lower_bounds] */ + if (Buf > 0) { + *CodeBlock = Private->Buf; /* Use private unused buffer. */ + (*CodeBlock)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data] */ + if (READ(GifFile, &((*CodeBlock)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else { + *CodeBlock = NULL; + Private->Buf[0] = 0; /* Make sure the buffer is empty! */ + Private->PixelCount = 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + Setup the LZ decompression for this image: +******************************************************************************/ +static int +DGifSetupDecompress(GifFileType *GifFile) +{ + int i, BitsPerPixel; + GifByteType CodeSize; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* coverity[check_return] */ + if (READ(GifFile, &CodeSize, 1) < 1) { /* Read Code size from file. */ + return GIF_ERROR; /* Failed to read Code size. */ + } + BitsPerPixel = CodeSize; + + Private->Buf[0] = 0; /* Input Buffer empty. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->StackPtr = 0; /* No pixels on the pixel stack. */ + Private->LastCode = NO_SUCH_CODE; + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + Prefix = Private->Prefix; + for (i = 0; i <= LZ_MAX_CODE; i++) + Prefix[i] = NO_SUCH_CODE; + + return GIF_OK; +} + +/****************************************************************************** + The LZ decompression routine: + This version decompress the given GIF file into Line of length LineLen. + This routine can be called few times (one per scan line, for example), in + order the complete the whole image. +******************************************************************************/ +static int +DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) +{ + int i = 0; + int j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr; + GifByteType *Stack, *Suffix; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + StackPtr = Private->StackPtr; + Prefix = Private->Prefix; + Suffix = Private->Suffix; + Stack = Private->Stack; + EOFCode = Private->EOFCode; + ClearCode = Private->ClearCode; + LastCode = Private->LastCode; + + if (StackPtr > LZ_MAX_CODE) { + return GIF_ERROR; + } + + if (StackPtr != 0) { + /* Let pop the stack off before continueing to read the GIF file: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + + while (i < LineLen) { /* Decode LineLen items. */ + if (DGifDecompressInput(GifFile, &CrntCode) == GIF_ERROR) + return GIF_ERROR; + + if (CrntCode == EOFCode) { + /* Note however that usually we will not be here as we will stop + * decoding as soon as we got all the pixel, or EOF code will + * not be read at all, and DGifGetLine/Pixel clean everything. */ + GifFile->Error = D_GIF_ERR_EOF_TOO_SOON; + return GIF_ERROR; + } else if (CrntCode == ClearCode) { + /* We need to start over again: */ + for (j = 0; j <= LZ_MAX_CODE; j++) + Prefix[j] = NO_SUCH_CODE; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + LastCode = Private->LastCode = NO_SUCH_CODE; + } else { + /* Its regular code - if in pixel range simply add it to output + * stream, otherwise trace to codes linked list until the prefix + * is in pixel range: */ + if (CrntCode < ClearCode) { + /* This is simple - its pixel scalar, so add it to output: */ + Line[i++] = CrntCode; + } else { + /* Its a code to needed to be traced: trace the linked list + * until the prefix is a pixel, while pushing the suffix + * pixels on our stack. If we done, pop the stack in reverse + * (thats what stack is good for!) order to output. */ + if (Prefix[CrntCode] == NO_SUCH_CODE) { + CrntPrefix = LastCode; + + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + if (CrntCode == Private->RunningCode - 2) { + Suffix[Private->RunningCode - 2] = + Stack[StackPtr++] = DGifGetPrefixChar(Prefix, + LastCode, + ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + Stack[StackPtr++] = DGifGetPrefixChar(Prefix, + CrntCode, + ClearCode); + } + } else + CrntPrefix = CrntCode; + + /* Now (if image is O.K.) we should not get a NO_SUCH_CODE + * during the trace. As we might loop forever, in case of + * defective image, we use StackPtr as loop counter and stop + * before overflowing Stack[]. */ + while (StackPtr < LZ_MAX_CODE && + CrntPrefix > ClearCode && CrntPrefix <= LZ_MAX_CODE) { + Stack[StackPtr++] = Suffix[CrntPrefix]; + CrntPrefix = Prefix[CrntPrefix]; + } + if (StackPtr >= LZ_MAX_CODE || CrntPrefix > LZ_MAX_CODE) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + /* Push the last character on stack: */ + Stack[StackPtr++] = CrntPrefix; + + /* Now lets pop all the stack into output: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + if (LastCode != NO_SUCH_CODE && Prefix[Private->RunningCode - 2] == NO_SUCH_CODE) { + Prefix[Private->RunningCode - 2] = LastCode; + + if (CrntCode == Private->RunningCode - 2) { + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, LastCode, ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, CrntCode, ClearCode); + } + } + LastCode = CrntCode; + } + } + + Private->LastCode = LastCode; + Private->StackPtr = StackPtr; + + return GIF_OK; +} + +/****************************************************************************** + Routine to trace the Prefixes linked list until we get a prefix which is + not code, but a pixel value (less than ClearCode). Returns that pixel value. + If image is defective, we might loop here forever, so we limit the loops to + the maximum possible if image O.k. - LZ_MAX_CODE times. +******************************************************************************/ +static int +DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode) +{ + int i = 0; + + while (Code > ClearCode && i++ <= LZ_MAX_CODE) { + if (Code > LZ_MAX_CODE) { + return NO_SUCH_CODE; + } + Code = Prefix[Code]; + } + return Code; +} + +/****************************************************************************** + The LZ decompression input routine: + This routine is responsable for the decompression of the bit stream from + 8 bits (bytes) packets, into the real codes. + Returns GIF_OK if read successfully. +******************************************************************************/ +static int +DGifDecompressInput(GifFileType *GifFile, int *Code) +{ + static const unsigned short CodeMasks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000f, 0x001f, 0x003f, 0x007f, + 0x00ff, 0x01ff, 0x03ff, 0x07ff, + 0x0fff + }; + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + GifByteType NextByte; + + /* The image can't contain more than LZ_BITS per code. */ + if (Private->RunningBits > LZ_BITS) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + + while (Private->CrntShiftState < Private->RunningBits) { + /* Needs to get more bytes from input stream for next code: */ + if (DGifBufferedInput(GifFile, Private->Buf, &NextByte) == GIF_ERROR) { + return GIF_ERROR; + } + Private->CrntShiftDWord |= + ((unsigned long)NextByte) << Private->CrntShiftState; + Private->CrntShiftState += 8; + } + *Code = Private->CrntShiftDWord & CodeMasks[Private->RunningBits]; + + Private->CrntShiftDWord >>= Private->RunningBits; + Private->CrntShiftState -= Private->RunningBits; + + /* If code cannot fit into RunningBits bits, must raise its size. Note + * however that codes above 4095 are used for special signaling. + * If we're using LZ_BITS bits already and we're at the max code, just + * keep using the table as it is, don't increment Private->RunningCode. + */ + if (Private->RunningCode < LZ_MAX_CODE + 2 && + ++Private->RunningCode > Private->MaxCode1 && + Private->RunningBits < LZ_BITS) { + Private->MaxCode1 <<= 1; + Private->RunningBits++; + } + return GIF_OK; +} + +/****************************************************************************** + This routines read one GIF data block at a time and buffers it internally + so that the decompression routine could access it. + The routine returns the next byte from its internal buffer (or read next + block in if buffer empty) and returns GIF_OK if succesful. +******************************************************************************/ +static int +DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, GifByteType *NextByte) +{ + if (Buf[0] == 0) { + /* Needs to read the next buffer - this one is empty: */ + /* coverity[check_return] */ + if (READ(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + /* There shouldn't be any empty data blocks here as the LZW spec + * says the LZW termination code should come first. Therefore we + * shouldn't be inside this routine at that point. + */ + if (Buf[0] == 0) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + if (READ(GifFile, &Buf[1], Buf[0]) != Buf[0]) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *NextByte = Buf[1]; + Buf[1] = 2; /* We use now the second place as last char read! */ + Buf[0]--; + } else { + *NextByte = Buf[Buf[1]++]; + Buf[0]--; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine reads an entire GIF into core, hanging all its state info off + the GifFileType pointer. Call DGifOpenFileName() or DGifOpenFileHandle() + first to initialize I/O. Its inverse is EGifSpew(). +*******************************************************************************/ +int +DGifSlurp(GifFileType *GifFile) +{ + size_t ImageSize; + GifRecordType RecordType; + SavedImage *sp; + GifByteType *ExtData; + int ExtFunction; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) + return (GIF_ERROR); + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) + return (GIF_ERROR); + + sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; + /* Allocate memory for the image */ + if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 && + (sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height))) { + return GIF_ERROR; + } + ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; + + if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) { + return GIF_ERROR; + } + sp->RasterBits = (unsigned char *)malloc(ImageSize * + sizeof(GifPixelType)); + + if (sp->RasterBits == NULL) { + return GIF_ERROR; + } + + if (sp->ImageDesc.Interlace) { + int i, j; + /* + * The way an interlaced image should be read - + * offsets and jumps... + */ + int InterlacedOffset[] = { 0, 4, 2, 1 }; + int InterlacedJumps[] = { 8, 8, 4, 2 }; + /* Need to perform 4 passes on the image */ + for (i = 0; i < 4; i++) + for (j = InterlacedOffset[i]; + j < sp->ImageDesc.Height; + j += InterlacedJumps[i]) { + if (DGifGetLine(GifFile, + sp->RasterBits+j*sp->ImageDesc.Width, + sp->ImageDesc.Width) == GIF_ERROR) + return GIF_ERROR; + } + } + else { + if (DGifGetLine(GifFile,sp->RasterBits,ImageSize)==GIF_ERROR) + return (GIF_ERROR); + } + + if (GifFile->ExtensionBlocks) { + sp->ExtensionBlocks = GifFile->ExtensionBlocks; + sp->ExtensionBlockCount = GifFile->ExtensionBlockCount; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + } + break; + + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(GifFile,&ExtFunction,&ExtData) == GIF_ERROR) + return (GIF_ERROR); + /* Create an extension block with our data */ + if (ExtData != NULL) { + if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, + ExtFunction, ExtData[0], &ExtData[1]) + == GIF_ERROR) + return (GIF_ERROR); + } + while (ExtData != NULL) { + if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR) + return (GIF_ERROR); + /* Continue the extension block */ + if (ExtData != NULL) + if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, + CONTINUE_EXT_FUNC_CODE, + ExtData[0], &ExtData[1]) == GIF_ERROR) + return (GIF_ERROR); + } + break; + + case TERMINATE_RECORD_TYPE: + break; + + default: /* Should be trapped by DGifGetRecordType */ + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + return (GIF_OK); +} + +/* end */ diff --git a/app/src/main/jni/giflib/gif_lib.h b/app/src/main/jni/giflib/gif_lib.h new file mode 100644 index 000000000..ed8477799 --- /dev/null +++ b/app/src/main/jni/giflib/gif_lib.h @@ -0,0 +1,172 @@ +/****************************************************************************** + +gif_lib.h - service library for decoding and encoding GIF images + +*****************************************************************************/ + +#ifndef _GIF_LIB_H_ +#define _GIF_LIB_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define GIF_ERROR 0 +#define GIF_OK 1 + +#include +#include + +#define GIF_STAMP "GIFVER" /* First chars in file - GIF stamp. */ +#define GIF_STAMP_LEN sizeof(GIF_STAMP) - 1 +#define GIF_VERSION_POS 3 /* Version first character in stamp. */ + +typedef unsigned char GifPixelType; +typedef unsigned char GifByteType; +typedef unsigned int GifPrefixType; +typedef int GifWord; + +typedef struct GifColorType { + GifByteType Red, Green, Blue; +} GifColorType; + +typedef struct ColorMapObject { + int ColorCount; + GifColorType *Colors; /* on malloc(3) heap */ +} ColorMapObject; + +typedef struct GifImageDesc { + GifWord Left, Top, Width, Height; /* Current image dimensions. */ + bool Interlace; /* Sequential/Interlaced lines. */ + ColorMapObject *ColorMap; /* The local color map */ +} GifImageDesc; + +typedef struct ExtensionBlock { + int ByteCount; + GifByteType *Bytes; /* on malloc(3) heap */ + int Function; /* The block function code */ +#define CONTINUE_EXT_FUNC_CODE 0x00 /* continuation subblock */ +#define GRAPHICS_EXT_FUNC_CODE 0xf9 /* graphics control (GIF89) */ +} ExtensionBlock; + +typedef struct SavedImage { + GifImageDesc ImageDesc; + GifByteType *RasterBits; /* on malloc(3) heap */ + int ExtensionBlockCount; /* Count of extensions before image */ + ExtensionBlock *ExtensionBlocks; /* Extensions before image */ +} SavedImage; + +typedef struct GifFileType { + GifWord SWidth, SHeight; /* Size of virtual canvas */ + GifWord SBackGroundColor; /* Background color for virtual canvas */ + ColorMapObject *SColorMap; /* Global colormap, NULL if nonexistent. */ + int ImageCount; /* Number of current image (both APIs) */ + GifImageDesc Image; /* Current image (low-level API) */ + SavedImage *SavedImages; /* Image sequence (high-level API) */ + int ExtensionBlockCount; /* Count extensions past last image */ + ExtensionBlock *ExtensionBlocks; /* Extensions past last image */ + int Error; /* Last error condition reported */ + void *UserData; /* hook to attach user data (TVT) */ + void *Private; /* Don't mess with this! */ +} GifFileType; + +typedef enum { + UNDEFINED_RECORD_TYPE, + IMAGE_DESC_RECORD_TYPE, /* Begin with ',' */ + EXTENSION_RECORD_TYPE, /* Begin with '!' */ + TERMINATE_RECORD_TYPE /* Begin with ';' */ +} GifRecordType; + +/* func type to read gif data from arbitrary sources (TVT) */ +typedef int (*InputFunc) (GifFileType *, GifByteType *, int); + +/****************************************************************************** + GIF89 structures +******************************************************************************/ + +typedef struct GraphicsControlBlock { + int DisposalMode; +#define DISPOSAL_UNSPECIFIED 0 /* No disposal specified. */ +#define DISPOSE_DO_NOT 1 /* Leave image in place */ +#define DISPOSE_BACKGROUND 2 /* Set area too background color */ +#define DISPOSE_PREVIOUS 3 /* Restore to previous content */ + int DelayTime; /* pre-display delay in 0.01sec units */ + int TransparentColor; /* Palette index for transparency, -1 if none */ +#define NO_TRANSPARENT_COLOR -1 +} GraphicsControlBlock; + +/****************************************************************************** + GIF decoding routines +******************************************************************************/ + +/* Main entry points */ +int DGifSlurp(GifFileType * GifFile); +GifFileType *DGifOpen(void *userPtr, InputFunc readFunc, int *Error); /* new one (TVT) */ + int DGifCloseFile(GifFileType * GifFile, int *ErrorCode); + +#define D_GIF_SUCCEEDED 0 +#define D_GIF_ERR_READ_FAILED 102 +#define D_GIF_ERR_NOT_GIF_FILE 103 +#define D_GIF_ERR_NO_SCRN_DSCR 104 +#define D_GIF_ERR_WRONG_RECORD 107 +#define D_GIF_ERR_DATA_TOO_BIG 108 +#define D_GIF_ERR_NOT_ENOUGH_MEM 109 +#define D_GIF_ERR_CLOSE_FAILED 110 +#define D_GIF_ERR_NOT_READABLE 111 +#define D_GIF_ERR_IMAGE_DEFECT 112 +#define D_GIF_ERR_EOF_TOO_SOON 113 + +/* These are legacy. You probably do not want to call them directly */ +int DGifGetScreenDesc(GifFileType *GifFile); +int DGifGetRecordType(GifFileType *GifFile, GifRecordType *GifType); +int DGifGetImageDesc(GifFileType *GifFile); +int DGifGetLine(GifFileType *GifFile, GifPixelType *GifLine, int GifLineLen); +int DGifGetExtension(GifFileType *GifFile, int *GifExtCode, + GifByteType **GifExtension); +int DGifGetExtensionNext(GifFileType *GifFile, GifByteType **GifExtension); +int DGifGetCodeNext(GifFileType *GifFile, GifByteType **GifCodeBlock); + +/***************************************************************************** + Everything below this point is new after version 1.2, supporting `slurp + mode' for doing I/O in two big belts with all the image-bashing in core. +******************************************************************************/ + +/****************************************************************************** + Color map handling from gif_alloc.c +******************************************************************************/ + +extern ColorMapObject *GifMakeMapObject(int ColorCount, + const GifColorType *ColorMap); +extern void GifFreeMapObject(ColorMapObject *Object); +extern int GifBitSize(int n); + +/****************************************************************************** + Support for the in-core structures allocation (slurp mode). +******************************************************************************/ + +extern int GifAddExtensionBlock(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks, + int Function, + unsigned int Len, unsigned char ExtData[]); +extern void GifFreeExtensions(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks); +extern void GifFreeSavedImages(GifFileType *GifFile); + +/****************************************************************************** + 5.x functions for GIF89 graphics control blocks +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB); + +int DGifSavedExtensionToGCB(GifFileType *GifFile, + int ImageIndex, + GraphicsControlBlock *GCB); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _GIF_LIB_H */ + +/* end */ diff --git a/app/src/main/jni/giflib/gif_lib_private.h b/app/src/main/jni/giflib/gif_lib_private.h new file mode 100644 index 000000000..ce38bbb0a --- /dev/null +++ b/app/src/main/jni/giflib/gif_lib_private.h @@ -0,0 +1,49 @@ +/**************************************************************************** + +gif_lib_private.h - internal giflib routines and structures + +****************************************************************************/ + +#ifndef _GIF_LIB_PRIVATE_H +#define _GIF_LIB_PRIVATE_H + +#include +#include "gif_lib.h" + +#define EXTENSION_INTRODUCER 0x21 +#define DESCRIPTOR_INTRODUCER 0x2c +#define TERMINATOR_INTRODUCER 0x3b + +#define LZ_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ +#define LZ_BITS 12 + +#define NO_SUCH_CODE 4098 /* Impossible code, to signal empty. */ + +#define FILE_STATE_READ 0x08 + +#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) + +typedef struct GifFilePrivateType { + GifWord FileState, /* Where all this data goes to! */ + BitsPerPixel, /* Bits per pixel (Codes uses at least this + 1). */ + ClearCode, /* The CLEAR LZ code. */ + EOFCode, /* The EOF LZ code. */ + RunningCode, /* The next code algorithm can generate. */ + RunningBits, /* The number of bits required to represent RunningCode. */ + MaxCode1, /* 1 bigger than max. possible code, in RunningBits bits. */ + LastCode, /* The code before the current code. */ + StackPtr, /* For character stack (see below). */ + CrntShiftState; /* Number of bits in CrntShiftDWord. */ + unsigned long CrntShiftDWord; /* For bytes decomposition into codes. */ + unsigned long PixelCount; /* Number of pixels in image. */ + FILE *File; /* File as stream. */ + InputFunc Read; /* function to read gif input (TVT) */ + GifByteType Buf[256]; /* Compressed input is buffered here. */ + GifByteType Stack[LZ_MAX_CODE]; /* Decoded pixels are stacked here. */ + GifByteType Suffix[LZ_MAX_CODE + 1]; /* So we can trace the codes. */ + GifPrefixType Prefix[LZ_MAX_CODE + 1]; +} GifFilePrivateType; + +#endif /* _GIF_LIB_PRIVATE_H */ + +/* end */ diff --git a/app/src/main/jni/giflib/gifalloc.c b/app/src/main/jni/giflib/gifalloc.c new file mode 100644 index 000000000..f5becd58e --- /dev/null +++ b/app/src/main/jni/giflib/gifalloc.c @@ -0,0 +1,188 @@ +/***************************************************************************** + + GIF construction tools + +****************************************************************************/ + +#include +#include + +#include "gif_lib.h" + +/****************************************************************************** + Miscellaneous utility functions +******************************************************************************/ + +/* return smallest bitfield size n will fit in */ +int +GifBitSize(int n) +{ + register int i; + + for (i = 1; i <= 8; i++) + if ((1 << i) >= n) + break; + return (i); +} + +/****************************************************************************** + Color map object functions +******************************************************************************/ + +/* + * Allocate a color map of given size; initialize with contents of + * ColorMap if that pointer is non-NULL. + */ +ColorMapObject * +GifMakeMapObject(int ColorCount, const GifColorType *ColorMap) +{ + ColorMapObject *Object; + + /*** FIXME: Our ColorCount has to be a power of two. Is it necessary to + * make the user know that or should we automatically round up instead? */ + if (ColorCount != (1 << GifBitSize(ColorCount))) { + return ((ColorMapObject *) NULL); + } + + Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); + if (Object == (ColorMapObject *) NULL) { + return ((ColorMapObject *) NULL); + } + + Object->Colors = (GifColorType *)calloc(ColorCount, sizeof(GifColorType)); + if (Object->Colors == (GifColorType *) NULL) { + free(Object); + return ((ColorMapObject *) NULL); + } + + Object->ColorCount = ColorCount; + + if (ColorMap != NULL) { + memcpy((char *)Object->Colors, + (char *)ColorMap, ColorCount * sizeof(GifColorType)); + } + + return (Object); +} + +/******************************************************************************* +Free a color map object +*******************************************************************************/ +void +GifFreeMapObject(ColorMapObject *Object) +{ + if (Object != NULL) { + free(Object->Colors); + free(Object); + } +} + +#ifdef DEBUG +void +DumpColorMap(ColorMapObject *Object, + FILE * fp) +{ + if (Object != NULL) { + int i, j, Len = Object->ColorCount; + + for (i = 0; i < Len; i += 4) { + for (j = 0; j < 4 && j < Len; j++) { + (void)fprintf(fp, "%3d: %02x %02x %02x ", i + j, + Object->Colors[i + j].Red, + Object->Colors[i + j].Green, + Object->Colors[i + j].Blue); + } + (void)fprintf(fp, "\n"); + } + } +} +#endif /* DEBUG */ + +/****************************************************************************** + Extension record functions +******************************************************************************/ +int +GifAddExtensionBlock(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks, + int Function, + unsigned int Len, + unsigned char ExtData[]) +{ + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) + *ExtensionBlocks=(ExtensionBlock *)malloc(sizeof(ExtensionBlock)); + else { + ExtensionBlock* ep_new = (ExtensionBlock *)realloc(*ExtensionBlocks, + sizeof(ExtensionBlock) * + (*ExtensionBlockCount + 1)); + if( ep_new == NULL ) + return (GIF_ERROR); + *ExtensionBlocks = ep_new; + } + + if (*ExtensionBlocks == NULL) + return (GIF_ERROR); + + ep = &(*ExtensionBlocks)[(*ExtensionBlockCount)++]; + + ep->Function = Function; + ep->ByteCount=Len; + ep->Bytes = (GifByteType *)malloc(ep->ByteCount); + if (ep->Bytes == NULL) + return (GIF_ERROR); + + if (ExtData != NULL) { + memcpy(ep->Bytes, ExtData, Len); + } + + return (GIF_OK); +} + +void +GifFreeExtensions(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks) +{ + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) + return; + + for (ep = *ExtensionBlocks; + ep < (*ExtensionBlocks + *ExtensionBlockCount); + ep++) + free((char *)ep->Bytes); + free((char *)*ExtensionBlocks); + *ExtensionBlocks = NULL; + *ExtensionBlockCount = 0; +} + +/****************************************************************************** + Image block allocation functions +******************************************************************************/ + +void +GifFreeSavedImages(GifFileType *GifFile) +{ + SavedImage *sp; + + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) { + return; + } + for (sp = GifFile->SavedImages; + sp < GifFile->SavedImages + GifFile->ImageCount; sp++) { + if (sp->ImageDesc.ColorMap != NULL) { + GifFreeMapObject(sp->ImageDesc.ColorMap); + sp->ImageDesc.ColorMap = NULL; + } + + if (sp->RasterBits != NULL) + free((char *)sp->RasterBits); + + GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks); + } + free((char *)GifFile->SavedImages); + GifFile->SavedImages = NULL; +} + +/* end */ diff --git a/beerbelly b/beerbelly index b3370ac00..f2b6766de 160000 --- a/beerbelly +++ b/beerbelly @@ -1 +1 @@ -Subproject commit b3370ac00710f77401f745c8610dfd03e6a9d633 +Subproject commit f2b6766de55bdbe46d09be40179f8fa602567783 diff --git a/build.gradle b/build.gradle index e280923ca..4864901cf 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0-beta4' + classpath 'com.android.tools.build:gradle-experimental:0.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/cardsalon b/cardsalon index a0184dfbd..97d1d3082 160000 --- a/cardsalon +++ b/cardsalon @@ -1 +1 @@ -Subproject commit a0184dfbd182aeeb5dede0b888fa27a9dd5ad5cc +Subproject commit 97d1d3082284eac5e36543303ee6433e3e2bbe33 diff --git a/conaco b/conaco index 005035b94..eb4faa703 160000 --- a/conaco +++ b/conaco @@ -1 +1 @@ -Subproject commit 005035b946ce9ff4f0affb0d2bc17930b512cc06 +Subproject commit eb4faa703f3c93b2c9ff8a61d66321051a46caf2 diff --git a/hotspot b/hotspot index 4bda57f50..04129925b 160000 --- a/hotspot +++ b/hotspot @@ -1 +1 @@ -Subproject commit 4bda57f50a58d8ba1ec4bfe2282623fd8bbfa7b3 +Subproject commit 04129925be33a1c84b3df51c8a8cb2b22fb907a6 diff --git a/unifile b/unifile index a4330e1cb..f068bc951 160000 --- a/unifile +++ b/unifile @@ -1 +1 @@ -Subproject commit a4330e1cb7530ac8bf068ca494bda2e2e4c1f4e1 +Subproject commit f068bc951b2fc9045d50a375b97530b95df49013 diff --git a/vectorold b/vectorold index 63d37f5ad..82fa5be4a 160000 --- a/vectorold +++ b/vectorold @@ -1 +1 @@ -Subproject commit 63d37f5ad6bc90cee7c4fc08fe6572289a6223e1 +Subproject commit 82fa5be4abab7ae497b72676f2f7c83f154a521e