Use jni to load gif

This commit is contained in:
seven332 committed 2015-08-04 19:04:42 +08:00
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) {