Decode image partly

This commit is contained in:
seven332 committed 2016-05-09 18:47:03 +08:00
1 parent 0a472d28b5
commit b8189d76d6
11 files changed
+262 -104

No files matched your search

@@ -20,7 +20,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.hippo.conaco.ValueHelper;
import com.hippo.image.Image;
import com.hippo.image.ImageBitmap;
import com.hippo.streampipe.InputStreamPipe;
@@ -35,12 +34,7 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
try {
isPipe.obtain();
Image image = Image.decode(isPipe.open(), false);
if (image != null) {
return new ImageBitmap(image);
} else {
return null;
}
return ImageBitmap.decode(isPipe.open());
} catch (OutOfMemoryError e) {
return null;
} catch (IOException e) {
@@ -23,9 +23,9 @@ import android.support.annotation.UiThread;
import com.hippo.beerbelly.LruCache;
import com.hippo.gl.glrenderer.GLCanvas;
import com.hippo.gl.glrenderer.ImageWrapper;
import com.hippo.gl.view.GLRoot;
import com.hippo.image.Image;
import com.hippo.image.ImageWrapper;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.IOUtils;
@@ -16,7 +16,7 @@
package com.hippo.ehviewer.gallery;
import com.hippo.image.ImageWrapper;
import com.hippo.gl.glrenderer.ImageWrapper;
public interface GalleryProviderListener {
@@ -206,6 +206,7 @@ public class ImageView extends GLView implements ImageTexture.Callback {
// Remove callback
if (mImageTexture != null) {
mImageTexture.setCallback(null);
mImageTexture.stop();
}
int oldTextureWidth = mTextureWidth;
@@ -229,6 +230,7 @@ public class ImageView extends GLView implements ImageTexture.Callback {
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
startAnimation(mAlphaAnimation, true);
mImageTexture.start();
}
} else {
mTextureWidth = 1;
@@ -1347,27 +1347,34 @@ public final class SpiderQueen implements Runnable {
Image image = null;
String error = null;
InputStream is;
pipe.obtain();
try {
pipe.obtain();
// TODO how to keep stream open
image = Image.decode(pipe.open(), false);
is = new AutoCloseInputStream(pipe, pipe.open());
} catch (IOException e) {
// Can't open pipe
error = GetText.getString(R.string.error_reading_failed);
is = null;
pipe.close();
pipe.release();
}
if (is != null) {
image = Image.decode(is, true);
if (image == null) {
error = GetText.getString(R.string.error_decoding_failed);
}
resetDecodeIndex();
} catch (IOException e) {
resetDecodeIndex();
error = GetText.getString(R.string.error_reading_failed);
} finally {
pipe.close();
pipe.release();
// Notify
if (image != null) {
notifyGetImageSuccess(index, image);
} else {
notifyGetImageFailure(index, error);
}
}
// Notify
if (image != null) {
notifyGetImageSuccess(index, image);
} else {
notifyGetImageFailure(index, error);
}
resetDecodeIndex();
}
if (DEBUG_LOG) {
@@ -1376,6 +1383,33 @@ public final class SpiderQueen implements Runnable {
}
}
private class AutoCloseInputStream extends InputStream {
private final InputStreamPipe mPipe;
private final InputStream mIs;
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
mPipe = pipe;
mIs = is;
}
@Override
public int read() throws IOException {
return mIs.read();
}
@Override
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
return mIs.read(buffer, byteOffset, byteCount);
}
@Override
public void close() throws IOException {
mPipe.close();
mPipe.release();
}
}
public interface OnSpiderListener {
void onGetPages(int pages);
@@ -61,8 +61,8 @@ import com.hippo.ehviewer.gallery.gl.ImageView;
import com.hippo.ehviewer.widget.GalleryGuideView;
import com.hippo.ehviewer.widget.ReversibleSeekBar;
import com.hippo.gl.glrenderer.ImageTexture;
import com.hippo.gl.glrenderer.ImageWrapper;
import com.hippo.gl.view.GLRootView;
import com.hippo.image.ImageWrapper;
import com.hippo.unifile.UniFile;
import com.hippo.util.SystemUiHelper;
import com.hippo.widget.ColorView;
@@ -24,7 +24,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.hippo.image.Image;
import com.hippo.image.ImageWrapper;
import java.util.Arrays;
@@ -19,7 +19,6 @@ package com.hippo.gl.glrenderer;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import com.hippo.image.ImageWrapper;
import com.hippo.yorozuya.AssertUtils;
public class ImageSpriteTexture extends ImageTexture {
@@ -18,13 +18,14 @@ package com.hippo.gl.glrenderer;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.os.Process;
import android.os.SystemClock;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import com.hippo.gl.annotation.RenderThread;
import com.hippo.gl.view.GLRoot;
import com.hippo.image.ImageWrapper;
import com.hippo.yorozuya.thread.InfiniteThreadExecutor;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThreadFactory;
@@ -33,8 +34,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class ImageTexture implements Texture, Animatable {
@@ -59,7 +61,7 @@ public class ImageTexture implements Texture, Animatable {
// In this 16ms, we use about 4~8 ms to upload tiles.
private static final long UPLOAD_TILE_LIMIT = 4; // ms
private static final PriorityThreadFactory sThreadFactory;
private static final Executor sThreadExecutor;
private static final PVLock sPVLock;
private static Tile sSmallFreeTileHead = null;
@@ -77,17 +79,19 @@ public class ImageTexture implements Texture, Animatable {
private final RectF mSrcRect = new RectF();
private final RectF mDestRect = new RectF();
private final AtomicBoolean mFrameDirty = new AtomicBoolean();
private boolean mImageBusy = false;
private final AtomicBoolean mRunning = new AtomicBoolean();
private final AtomicBoolean mFrameDirty = new AtomicBoolean();
private final AtomicBoolean mNeedRelease = new AtomicBoolean();
private final AtomicBoolean mReleased = new AtomicBoolean();
private final AtomicReference<Thread> mAnimateThread = new AtomicReference<>();
private Runnable mAnimateRunnable = null;
private WeakReference<Callback> mCallback;
static {
sThreadFactory = new PriorityThreadFactory("ImageTexture$AnimateTask", android.os.Process.THREAD_PRIORITY_BACKGROUND);
sThreadExecutor = new InfiniteThreadExecutor(10 * 1000, new LinkedList<>(),
new PriorityThreadFactory("ImageTexture$AnimateTask", Process.THREAD_PRIORITY_BACKGROUND));
sPVLock = new PVLock(3);
}
@@ -253,37 +257,25 @@ public class ImageTexture implements Texture, Animatable {
long lastDelay = -1L;
synchronized (mImage) {
// Check released
if (mReleased.get()) {
return;
}
// Check image busy
if (mImageBusy) {
// Image is busy, means it is recycling
return;
}
if (mNeedRelease.get()) {
// Need release
// Check released, image busy, Need release
if (mReleased.get() || mImageBusy || mNeedRelease.get()) {
mAnimateRunnable = null;
return;
}
// Obtain image
mImageBusy = true;
}
boolean interrupted = Thread.currentThread().isInterrupted();
if (!mImage.isCompleted()) {
if (!interrupted) {
try {
sPVLock.p();
} catch (InterruptedException e) {
interrupted = true;
}
if (!interrupted && !mNeedRelease.get()) {
mImage.complete();
}
sPVLock.v();
try {
sPVLock.p();
} catch (InterruptedException e) {
// Ignore
}
if (!mNeedRelease.get()) {
mImage.complete();
}
sPVLock.v();
}
int frameCount = mImage.getFrameCount();
@@ -291,26 +283,19 @@ public class ImageTexture implements Texture, Animatable {
synchronized (mImage) {
// Release image
mImageBusy = false;
// Check need release, frameCount <= 1
if (mNeedRelease.get() || frameCount <= 1) {
mAnimateRunnable = null;
return;
}
}
if (interrupted || mNeedRelease.get() || frameCount <= 1) {
return;
}
while (!Thread.currentThread().isInterrupted()) {
for (;;) {
// Obtain
synchronized (mImage) {
// Check released
if (mReleased.get()) {
return;
}
// Check image busy
if (mImageBusy) {
// Image is busy, means it is recycling
return;
}
if (mNeedRelease.get()) {
// Need release
// Check released, image busy, Need release, not running
if (mReleased.get() || mImageBusy || mNeedRelease.get() || !mRunning.get()) {
mAnimateRunnable = null;
return;
}
// Obtain image
@@ -337,7 +322,7 @@ public class ImageTexture implements Texture, Animatable {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
break;
// Ignore
}
}
}
@@ -371,8 +356,6 @@ public class ImageTexture implements Texture, Animatable {
mImageBusy = false;
}
}
mAnimateThread.lazySet(null);
}
}
@@ -443,10 +426,10 @@ public class ImageTexture implements Texture, Animatable {
mTiles = list.toArray(new Tile[list.size()]);
if (!mImage.isCompleted() || mImage.getFrameCount() > 1) {
Thread thread = sThreadFactory.newThread(new AnimateRunnable());
mAnimateThread.lazySet(thread);
thread.start();
if (!mImage.isCompleted()) {
Runnable runnable = new AnimateRunnable();
mAnimateRunnable = runnable;
sThreadExecutor.execute(runnable);
}
}
@@ -470,33 +453,31 @@ public class ImageTexture implements Texture, Animatable {
@Override
public void start() {
if (mReleased.get() || (mImage.isCompleted() && mImage.getFrameCount() <= 1)) {
if (mReleased.get() || mNeedRelease.get() || (mImage.isCompleted() && mImage.getFrameCount() <= 1) ||
mRunning.get()) {
return;
}
Thread thread = mAnimateThread.get();
if (thread == null) {
thread = sThreadFactory.newThread(new AnimateRunnable());
mAnimateThread.set(thread);
thread.start();
}
}
mRunning.lazySet(true);
@Override
public void stop() {
if (!mReleased.get() && (mImage.isCompleted() && mImage.getFrameCount() > 1)) {
Thread thread = mAnimateThread.getAndSet(null);
if (null != thread) {
thread.interrupt();
synchronized (mImage) {
if (mAnimateRunnable == null) {
Runnable runnable = new AnimateRunnable();
mAnimateRunnable = runnable;
sThreadExecutor.execute(runnable);
}
}
}
@Override
public boolean isRunning() {
return mAnimateThread.get() != null;
public void stop() {
mRunning.lazySet(false);
}
@Override
public boolean isRunning() {
return mRunning.get();
}
private boolean uploadNextTile(GLCanvas canvas) {
if (mUploadIndex == mTiles.length) return true;
@@ -663,6 +644,8 @@ public class ImageTexture implements Texture, Animatable {
}
public void recycle() {
mRunning.lazySet(false);
for (Tile mTile : mTiles) {
mTile.free();
}
@@ -675,6 +658,7 @@ public class ImageTexture implements Texture, Animatable {
mImageBusy = true;
} else {
releaseNow = false;
mNeedRelease.set(true);
}
}
@@ -684,13 +668,6 @@ public class ImageTexture implements Texture, Animatable {
synchronized (mImage) {
mImageBusy = false;
}
} else {
mNeedRelease.set(true);
}
Thread thread = mAnimateThread.getAndSet(null);
if (null != thread) {
thread.interrupt();
}
}
@@ -0,0 +1,153 @@
/*
* Copyright 2016 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.gl.glrenderer;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import com.hippo.image.Image;
/**
* A wrapper for {@link Image}. It is useful for multi-usage.
* It handles image recycle automatically.
*/
public class ImageWrapper {
private final Image mImage;
private int mReferences;
/**
* Create ImageWrapper
*
* @param image the image should not be obtained or recycled.
*/
public ImageWrapper(@NonNull Image image) {
mImage = image;
}
/**
* Obtain the image
*
* @return false for the image is recycled and obtain failed
*/
public synchronized boolean obtain() {
if (mImage.isRecycled()) {
return false;
} else {
++mReferences;
return true;
}
}
/**
* Release the image
*/
public synchronized void release() {
--mReferences;
if (mReferences <= 0 && !mImage.isRecycled()) {
mImage.recycle();
}
}
/**
* @see Image#getFormat()
*/
public int getFormat() {
return mImage.getFormat();
}
/**
* @see Image#getFormat()
*/
public int getWidth() {
return mImage.getWidth();
}
/**
* @see Image#getHeight()
*/
public int getHeight() {
return mImage.getHeight();
}
/**
* @see Image#complete()
*/
public boolean complete() {
synchronized (mImage) {
return mImage.complete();
}
}
/**
* @see Image#isCompleted()
*/
public boolean isCompleted() {
return mImage.isCompleted();
}
/**
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
*/
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor) {
mImage.render(srcX, srcY, dst, dstX, dstY,
width, height, fillBlank, defaultColor);
}
/**
* @see Image#texImage(boolean, int, int, int, int)
*/
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
mImage.texImage(init, offsetX, offsetY, width, height);
}
/**
* @see Image#advance()
*/
public void advance() {
mImage.advance();
}
/**
* @see Image#getDelay()
*/
public int getDelay() {
return mImage.getDelay();
}
/**
* @see Image#getFrameCount()
*/
public int getFrameCount() {
return mImage.getFrameCount();
}
/**
* @see Image#isOpaque()
*/
public boolean isOpaque() {
return mImage.isOpaque();
}
/**
* @see Image#isRecycled()
*/
public boolean isRecycled() {
return mImage.isRecycled();
}
}