Use jni to load gif
This commit is contained in:
1 parent
f833bd829e
commit
b555e3fc29
27 files changed
+2350
-1472
No files matched your search
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WeakReference<byte[]>> 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<WeakReference<byte[]>> 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<WeakReference<byte[]>> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,720 +0,0 @@
|
||||
package com.hippo.ehviewer.gallery.gifdecoder;
|
||||
|
||||
/**
|
||||
* Copyright (c) 2013 Xcellent Creations, Inc.
|
||||
* <p/>
|
||||
* 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:
|
||||
* <p/>
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* <p/>
|
||||
* 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[].
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* 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.
|
||||
* <p/>
|
||||
* Implementation adapted from sample code published in Lyons. (2004). <em>Java for
|
||||
* Programmers</em>, 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.
|
||||
* <p/>
|
||||
* <p> Status will update per frame to allow the caller to tell whether or not the current frame
|
||||
* was decoded successfully and/or completely. Format and open failures persist across frames.
|
||||
* </p>
|
||||
*/
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the animation frame counter forward.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<GifFrame> frames = new ArrayList<GifFrame>();
|
||||
// Logical screen size.
|
||||
// Full image width.
|
||||
int width;
|
||||
// Full image height.
|
||||
int height;
|
||||
|
||||
// 1 : global color table flag.
|
||||
boolean gctFlag;
|
||||
// 2-4 : color resolution.
|
||||
// 5 : gct sort flag.
|
||||
// 6-8 : gct size.
|
||||
int gctSize;
|
||||
// Background color index.
|
||||
int bgIndex;
|
||||
// Pixel aspect ratio.
|
||||
int pixelAspect;
|
||||
//TODO: this is set both during reading the header and while decoding frames...
|
||||
int bgColor;
|
||||
int loopCount;
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getNumFrames() {
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global status code of GIF data parsing.
|
||||
*/
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Runnable>(),
|
||||
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) {
|
||||
|
||||
Reference in new issue
Block a user