From 41a383c1ac7ca65714fd6af0e80a9b930155a01d Mon Sep 17 00:00:00 2001 From: seven332 Date: Fri, 4 Mar 2016 15:29:10 +0800 Subject: [PATCH] Add animated image support --- .../ehviewer/gallery/DirGalleryProvider.java | 2 +- .../ehviewer/gallery/ZipGalleryProvider.java | 2 +- .../ehviewer/gallery/gl/GalleryPageView.java | 10 +- .../hippo/ehviewer/gallery/gl/ImageView.java | 61 ++- .../hippo/ehviewer/spider/SpiderQueen.java | 1 + .../com/hippo/gl/glrenderer/ImageTexture.java | 375 +++++++++++++----- yorozuya | 2 +- 7 files changed, 324 insertions(+), 129 deletions(-) diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java index 8ebbb010d..626afff13 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/DirGalleryProvider.java @@ -148,7 +148,7 @@ public class DirGalleryProvider extends GalleryProvider implements Runnable { try { InputStream is = new FileInputStream(new File(mDir, files[index])); - Image image = Image.decode(is, false); + Image image = Image.decode(is, true); mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX); if (image != null) { notifyPageSucceed(index, image); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java index 1d9f8e345..918651ca1 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ZipGalleryProvider.java @@ -175,7 +175,7 @@ public class ZipGalleryProvider extends GalleryProvider implements Runnable { ZipEntry zipEntry = zipFile.getEntry(filenames.get(index)); if (zipEntry != null) { InputStream is = zipFile.getInputStream(zipEntry); - Image image = Image.decode(is, false); + Image image = Image.decode(is, true); mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX); if (image != null) { notifyPageSucceed(index, image); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java index 245439470..f8691d12f 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/GalleryPageView.java @@ -126,19 +126,17 @@ public class GalleryPageView extends GLFrameLayout { } private void unbindImage() { - Texture texture = mImage.getTexture(); + ImageTexture texture = mImage.getImageTexture(); if (texture != null) { - mImage.setTexture(null); - if (texture instanceof ImageTexture) { - ((ImageTexture) texture).recycle(); - } + mImage.setImageTexture(null); + texture.recycle(); } } public void setImage(ImageTexture imageTexture) { unbindImage(); if (imageTexture != null) { - mImage.setTexture(imageTexture); + mImage.setImageTexture(imageTexture); } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java index 373064725..8e83afea8 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/gl/ImageView.java @@ -22,6 +22,7 @@ import android.support.annotation.IntDef; import com.hippo.gl.anim.AlphaAnimation; import com.hippo.gl.glrenderer.GLCanvas; +import com.hippo.gl.glrenderer.ImageTexture; import com.hippo.gl.glrenderer.Texture; import com.hippo.gl.view.GLView; import com.hippo.yorozuya.AnimationUtils; @@ -31,7 +32,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -public class ImageView extends GLView { +public class ImageView extends GLView implements ImageTexture.Callback { @IntDef({SCALE_ORIGIN, SCALE_FIT_WIDTH, SCALE_FIT_HEIGHT, SCALE_FIT, SCALE_FIXED}) @Retention(RetentionPolicy.SOURCE) @@ -60,7 +61,7 @@ public class ImageView extends GLView { private static final long ALPHA_ANIMATION_DURING = 300L; - private Texture mTexture; + private ImageTexture mImageTexture; private int mTextureWidth; private int mTextureHeight; @@ -115,18 +116,18 @@ public class ImageView extends GLView { @Override protected int getSuggestedMinimumWidth() { return Math.max(super.getSuggestedMinimumWidth(), - mTexture == null ? 0 : mTextureWidth); + mImageTexture == null ? 0 : mTextureWidth); } @Override protected int getSuggestedMinimumHeight() { return Math.max(super.getSuggestedMinimumHeight(), - mTexture == null ? 0 : mTextureHeight); + mImageTexture == null ? 0 : mTextureHeight); } @Override protected void onMeasure(int widthSpec, int heightSpec) { - if (mTexture == null) { + if (mImageTexture == null) { super.onMeasure(widthSpec, heightSpec); } else { float ratio = (float) mTextureWidth / mTextureHeight; @@ -172,10 +173,19 @@ public class ImageView extends GLView { @Override protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) { mPositionInRootDirty = true; + + if (mImageTexture != null) { + getValidRect(mValidRect); + if (!mValidRect.isEmpty()) { + mImageTexture.start(); + } else { + mImageTexture.stop(); + } + } } public void getScaleDefault(float[] scaleDefault) { - if (mTexture == null) { + if (mImageTexture == null) { return; } @@ -192,12 +202,18 @@ public class ImageView extends GLView { Arrays.sort(scaleDefault); } - public void setTexture(Texture texture) { - mTexture = texture; + public void setImageTexture(ImageTexture imageTexture) { + // Remove callback + if (mImageTexture != null) { + mImageTexture.setCallback(null); + } - if (texture != null) { - mTextureWidth = texture.getWidth(); - mTextureHeight = texture.getHeight(); + mImageTexture = imageTexture; + + if (imageTexture != null) { + imageTexture.setCallback(this); + mTextureWidth = imageTexture.getWidth(); + mTextureHeight = imageTexture.getHeight(); // Avoid zero and negative if (mTextureWidth <= 0) { mTextureWidth = 1; @@ -219,12 +235,12 @@ public class ImageView extends GLView { mPositionInRootDirty = true; } - public Texture getTexture() { - return mTexture; + public ImageTexture getImageTexture() { + return mImageTexture; } public boolean isLoaded() { - return mTexture != null; + return mImageTexture != null; } public boolean canFlingVertically() { @@ -351,7 +367,7 @@ public class ImageView extends GLView { int screenWidth = getWidth(); int screenHeight = getHeight(); - if (mTexture == null || screenWidth == 0 || screenHeight == 0) { + if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) { mScaleOffsetDirty = true; return; } @@ -530,8 +546,8 @@ public class ImageView extends GLView { float left = (focusX - ((focusX - dst.left) * scale)); float top = (focusY - ((focusY - dst.top) * scale)); dst.set(left, top, - (left + (mTexture.getWidth() * newScale)), - (top + (mTexture.getHeight() * newScale))); + (left + (mImageTexture.getWidth() * newScale)), + (top + (mImageTexture.getHeight() * newScale))); // adjust position adjustPosition(); @@ -541,8 +557,8 @@ public class ImageView extends GLView { } private void applyPositionInRoot() { - int width = mTexture.getWidth(); - int height = mTexture.getHeight(); + int width = mImageTexture.getWidth(); + int height = mImageTexture.getHeight(); RectF dst = mDst; RectF dstActual = mDstActual; RectF srcActual = mSrcActual; @@ -569,7 +585,7 @@ public class ImageView extends GLView { @Override public void onRender(GLCanvas canvas) { - Texture texture = mTexture; + Texture texture = mImageTexture; if (texture == null) { return; } @@ -586,4 +602,9 @@ public class ImageView extends GLView { texture.draw(canvas, mSrcActual, mDstActual); } } + + @Override + public void invalidateImageTexture(ImageTexture who) { + invalidate(); + } } diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index 18d75f1c3..05fd41164 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -1176,6 +1176,7 @@ public class SpiderQueen implements Runnable { try { pipe.obtain(); + // TODO how to keep stream open Image image = Image.decode(pipe.open(), false); if (image != null) { mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX); diff --git a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java index 86bbbc64f..d46cbce18 100644 --- a/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java +++ b/app/src/main/java/com/hippo/gl/glrenderer/ImageTexture.java @@ -19,6 +19,7 @@ package com.hippo.gl.glrenderer; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.RectF; +import android.graphics.drawable.Animatable; import android.os.SystemClock; import android.support.annotation.IntDef; import android.support.annotation.NonNull; @@ -26,17 +27,18 @@ import android.support.annotation.NonNull; import com.hippo.gl.annotation.RenderThread; import com.hippo.gl.view.GLRoot; import com.hippo.image.Image; -import com.hippo.yorozuya.InfiniteThreadExecutor; import com.hippo.yorozuya.PVLock; import com.hippo.yorozuya.PriorityThreadFactory; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; -public class ImageTexture implements Texture { +public class ImageTexture implements Texture, Animatable { @IntDef({TILE_SMALL, TILE_LARGE}) @Retention(RetentionPolicy.SOURCE) @@ -61,7 +63,7 @@ public class ImageTexture implements Texture { private static final Bitmap sSmallUploadBitmap; private static final Bitmap sLargeUploadBitmap; - private static final InfiniteThreadExecutor sThreadExecutor; + private static final PriorityThreadFactory sThreadFactory; private static final PVLock sPVLock; private static Tile sSmallFreeTileHead = null; @@ -79,15 +81,18 @@ public class ImageTexture implements Texture { private final RectF mSrcRect = new RectF(); private final RectF mDestRect = new RectF(); - private volatile boolean mFrameDirty = false; + private final AtomicBoolean mFrameDirty = new AtomicBoolean(); + private boolean mImageBusy = false; + private final AtomicBoolean mNeedRecycle = new AtomicBoolean(); + private final AtomicReference mAnimateThread = new AtomicReference<>(); + + private WeakReference mCallback; static { sSmallUploadBitmap = Bitmap.createBitmap(SMALL_TILE_SIZE, SMALL_TILE_SIZE, Bitmap.Config.ARGB_8888); sLargeUploadBitmap = Bitmap.createBitmap(LARGE_TILE_SIZE, LARGE_TILE_SIZE, Bitmap.Config.ARGB_8888); - sThreadExecutor = new InfiniteThreadExecutor(3000, new LinkedBlockingQueue(), - new PriorityThreadFactory(ImageTexture.class.getSimpleName(), - android.os.Process.THREAD_PRIORITY_BACKGROUND)); + sThreadFactory = new PriorityThreadFactory("ImageTexture$AnimateTask", android.os.Process.THREAD_PRIORITY_BACKGROUND); sPVLock = new PVLock(3); } @@ -177,31 +182,29 @@ public class ImageTexture implements Texture { protected Bitmap onGetBitmap() { final Image image = this.image; if (image != null) { - synchronized (image) { - int srcX; - int dstX; - if (offsetX - borderSize < 0) { - srcX = 0; - dstX = borderSize - offsetX; - } else { - srcX = offsetX - borderSize; - dstX = 0; - } - int srcY; - int dstY; - if (offsetY - borderSize < 0) { - srcY = 0; - dstY = borderSize - offsetY; - } else { - srcY = offsetY - borderSize; - dstY = 0; - } - - image.render(srcX, srcY, uploadBitmap, dstX, dstY, - Math.min(image.getWidth() - srcX, tileSize - dstX), - Math.min(image.getHeight() - srcY, tileSize - dstY), - true, Color.TRANSPARENT); + int srcX; + int dstX; + if (offsetX - borderSize < 0) { + srcX = 0; + dstX = borderSize - offsetX; + } else { + srcX = offsetX - borderSize; + dstX = 0; } + int srcY; + int dstY; + if (offsetY - borderSize < 0) { + srcY = 0; + dstY = borderSize - offsetY; + } else { + srcY = offsetY - borderSize; + dstY = 0; + } + + image.render(srcX, srcY, uploadBitmap, dstX, dstY, + Math.min(image.getWidth() - srcX, tileSize - dstX), + Math.min(image.getHeight() - srcY, tileSize - dstY), + true, Color.TRANSPARENT); } return uploadBitmap; } @@ -216,24 +219,12 @@ public class ImageTexture implements Texture { return false; } - private void invalidateInternal() { + private void invalidate() { invalidateContent(); image = null; uploadBitmap = null; } - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") - public void invalidate() { - final Image image = this.image; - if (image != null) { - synchronized (image) { - invalidateInternal(); - } - } else { - invalidateInternal(); - } - } - public void free() { switch (mTileType) { case TILE_SMALL: @@ -290,6 +281,131 @@ public class ImageTexture implements Texture { } } + private class AnimateRunnable implements Runnable { + + public void doRun() { + long lastTime = SystemClock.currentThreadTimeMillis(); + + synchronized (mImage) { + // Check recycled + if (mImage.isRecycled()) { + return; + } + // Check image busy + if (mImageBusy) { + // Image is busy, means it is recycling + return; + } + if (mNeedRecycle.get()) { + // Need recycle + 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 && !mNeedRecycle.get()) { + mImage.complete(); + } + sPVLock.v(); + } + } + + int frameCount = mImage.getFrameCount(); + + synchronized (mImage) { + // Release image + mImageBusy = false; + } + + if (interrupted || mNeedRecycle.get() || frameCount < 1) { + return; + } + + while (!Thread.currentThread().isInterrupted()) { + // Obtain + synchronized (mImage) { + // Check recycled + if (mImage.isRecycled()) { + return; + } + // Check image busy + if (mImageBusy) { + // Image is busy, means it is recycling + return; + } + if (mNeedRecycle.get()) { + // Need recycle + return; + } + // Obtain image + mImageBusy = true; + } + + mImage.advance(); + long delay = mImage.getDelay(); + long time = System.currentTimeMillis(); + delay -= time - lastTime; + lastTime = time; + mFrameDirty.lazySet(true); + invalidateSelf(); + + synchronized (mImage) { + // Release image + mImageBusy = false; + } + + if (delay > 0) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + break; + } + } + } + } + + @Override + public void run() { + doRun(); + + while (mNeedRecycle.get()) { + // Obtain + synchronized (mImage) { + // Check recycled + if (mImage.isRecycled()) { + break; + } + // Check image busy + if (mImageBusy) { + // Image is busy, means it is recycling + break; + } + // Obtain image + mImageBusy = true; + } + + mImage.recycle(); + + synchronized (mImage) { + // Release image + mImageBusy = false; + } + } + + mAnimateThread.lazySet(null); + } + } + public ImageTexture(@NonNull Image image) { mImage = image; int width = mWidth = image.getWidth(); @@ -354,13 +470,61 @@ public class ImageTexture implements Texture { mTiles = list.toArray(new Tile[list.size()]); - /* - if (image instanceof AnimatedImage) { - sThreadExecutor.execute(new DecodeTask()); + if (!mImage.isCompleted() || mImage.getFrameCount() >= 1) { + Thread thread = sThreadFactory.newThread(new AnimateRunnable()); + mAnimateThread.lazySet(thread); + thread.start(); } - */ } + public final void setCallback(Callback cb) { + mCallback = new WeakReference<>(cb); + } + + public Callback getCallback() { + if (mCallback != null) { + return mCallback.get(); + } + return null; + } + + public void invalidateSelf() { + final Callback callback = getCallback(); + if (callback != null) { + callback.invalidateImageTexture(this); + } + } + + @Override + public void start() { + if (mImage.isRecycled() || (mImage.isCompleted() && mImage.getFrameCount() < 1)) { + return; + } + + Thread thread = mAnimateThread.get(); + if (thread == null) { + thread = sThreadFactory.newThread(new AnimateRunnable()); + mAnimateThread.set(thread); + thread.start(); + } + } + + @Override + public void stop() { + if (!mImage.isRecycled() && (mImage.isCompleted() && mImage.getFrameCount() >= 1)) { + Thread thread = mAnimateThread.getAndSet(null); + if (null != thread) { + thread.interrupt(); + } + } + } + + @Override + public boolean isRunning() { + return false; + } + + private boolean uploadNextTile(GLCanvas canvas) { if (mUploadIndex == mTiles.length) return true; @@ -417,12 +581,9 @@ public class ImageTexture implements Texture { @RenderThread private void syncFrame() { - if (mFrameDirty) { - mFrameDirty = false; - + if (mFrameDirty.getAndSet(false)) { // invalid tiles - for (int i = 0, n = mTiles.length; i < n; i++) { - Tile tile = mTiles[i]; + for (Tile tile : mTiles) { int width = tile.mWidth; int height = tile.mHeight; tile.invalidateContent(); @@ -445,16 +606,13 @@ public class ImageTexture implements Texture { float scaleX = (float) w / mWidth; float scaleY = (float) h / mHeight; - synchronized (mImage) { - syncFrame(); - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); - src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); - canvas.drawTexture(t, src, dest); - } + syncFrame(); + for (Tile t : mTiles) { + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); + canvas.drawTexture(t, src, dest); } } @@ -470,19 +628,16 @@ public class ImageTexture implements Texture { float scaleX = target.width() / source.width(); float scaleY = target.height() / source.height(); - synchronized (mImage) { - syncFrame(); - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - if (!src.intersect(source)) { - continue; - } - mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); - src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); - canvas.drawTexture(t, src, dest); + syncFrame(); + for (Tile t : mTiles) { + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) { + continue; } + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); + canvas.drawTexture(t, src, dest); } } @@ -495,16 +650,13 @@ public class ImageTexture implements Texture { float scaleX = (float) width / mWidth; float scaleY = (float) height / mHeight; - synchronized (mImage) { - syncFrame(); - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); - src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); - canvas.drawMixed(t, color, ratio, src, dest); - } + syncFrame(); + for (Tile t : mTiles) { + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); + canvas.drawMixed(t, color, ratio, src, dest); } } @@ -519,19 +671,16 @@ public class ImageTexture implements Texture { float scaleX = target.width() / source.width(); float scaleY = target.height() / source.height(); - synchronized (mImage) { - syncFrame(); - for (int i = 0, n = mTiles.length; i < n; ++i) { - Tile t = mTiles[i]; - src.set(0, 0, t.contentWidth, t.contentHeight); - src.offset(t.offsetX, t.offsetY); - if (!src.intersect(source)) { - continue; - } - mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); - src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); - canvas.drawMixed(t, color, ratio, src, dest); + syncFrame(); + for (Tile t : mTiles) { + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) { + continue; } + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(t.borderSize - t.offsetX, t.borderSize - t.offsetY); + canvas.drawMixed(t, color, ratio, src, dest); } } @@ -545,11 +694,37 @@ public class ImageTexture implements Texture { } public void recycle() { - for (int i = 0, n = mTiles.length; i < n; ++i) { - mTiles[i].free(); + for (Tile mTile : mTiles) { + mTile.free(); } + + boolean recycleNow; + synchronized (mImage) { + if (!mImageBusy) { + recycleNow = true; + mImageBusy = true; + } else { + recycleNow = false; + } + } + + if (recycleNow) { mImage.recycle(); + synchronized (mImage) { + mImageBusy = false; + } + } else { + mNeedRecycle.set(true); + } + + Thread thread = mAnimateThread.getAndSet(null); + if (null != thread) { + thread.interrupt(); } } + + public interface Callback { + void invalidateImageTexture(ImageTexture who); + } } diff --git a/yorozuya b/yorozuya index 0ae84aa55..f509f306a 160000 --- a/yorozuya +++ b/yorozuya @@ -1 +1 @@ -Subproject commit 0ae84aa55e4c67aead261423938c31c7df44ba68 +Subproject commit f509f306a304c975779242794e0be7f3bb6b9df4