diff options
Diffstat (limited to 'framesequence/src/android/support/rastermill/FrameSequenceDrawable.java')
-rw-r--r-- | framesequence/src/android/support/rastermill/FrameSequenceDrawable.java | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java new file mode 100644 index 0000000..94f4da0 --- /dev/null +++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * 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 android.support.rastermill; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.SystemClock; + +public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable { + private static final Object sLock = new Object(); + private static HandlerThread sDecodingThread; + private static Handler sDecodingThreadHandler; + private static void initializeDecodingThread() { + synchronized (sLock) { + if (sDecodingThread != null) return; + + sDecodingThread = new HandlerThread("FrameSequence decoding thread"); + sDecodingThread.start(); + sDecodingThreadHandler = new Handler(sDecodingThread.getLooper()); + } + } + + private final FrameSequence mFrameSequence; + private final FrameSequence.State mFrameSequenceState; + + private final Paint mPaint; + private final Rect mSrcRect; + + //Protects the fields below + private final Object mLock = new Object(); + + private Bitmap mFrontBitmap; + private Bitmap mBackBitmap; + + private static final int STATE_SCHEDULED = 1; + private static final int STATE_DECODING = 2; + private static final int STATE_WAITING_TO_SWAP = 3; + private static final int STATE_READY_TO_SWAP = 4; + + private int mState; + + private long mLastSwap; + private int mNextFrameToDecode; + + /** + * Runs on decoding thread, only modifies mBackBitmap's pixels + */ + private Runnable mDecodeRunnable = new Runnable() { + @Override + public void run() { + int nextFrame; + Bitmap bitmap; + synchronized (mLock) { + nextFrame = mNextFrameToDecode; + if (nextFrame < 0) { + return; + } + bitmap = mBackBitmap; + mState = STATE_DECODING; + } + int lastFrame = nextFrame - 2; + long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame); + + synchronized (mLock) { + if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return; + invalidateTimeMs += mLastSwap; + + mState = STATE_WAITING_TO_SWAP; + } + scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs); + } + }; + + + public FrameSequenceDrawable(FrameSequence frameSequence) { + if (frameSequence == null) throw new IllegalArgumentException(); + + mFrameSequence = frameSequence; + mFrameSequenceState = frameSequence.createState(); + // TODO: add callback for requesting bitmaps, to allow for reuse + final int width = frameSequence.getWidth(); + final int height = frameSequence.getHeight(); + + mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mSrcRect = new Rect(0, 0, width, height); + mPaint = new Paint(); + mPaint.setFilterBitmap(true); + + mLastSwap = 0; + + mNextFrameToDecode = -1; + mFrameSequenceState.getFrame(0, mFrontBitmap, -1); + initializeDecodingThread(); + } + + @Override + protected void finalize() throws Throwable { + try { + mFrontBitmap.recycle(); + mBackBitmap.recycle(); + mFrameSequenceState.recycle(); + } finally { + super.finalize(); + } + } + + @Override + public void draw(Canvas canvas) { + synchronized (mLock) { + if (isRunning() && mState == STATE_READY_TO_SWAP) { + // Because draw has occurred, the view system is guaranteed to no longer hold a + // reference to the old mFrontBitmap, so we now use it to produce the next frame + Bitmap tmp = mBackBitmap; + mBackBitmap = mFrontBitmap; + mFrontBitmap = tmp; + + mLastSwap = SystemClock.uptimeMillis(); + scheduleDecodeLocked(); + } + } + + canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint); + } + + private void scheduleDecodeLocked() { + mState = STATE_SCHEDULED; + mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount(); + sDecodingThreadHandler.post(mDecodeRunnable); + } + + @Override + public void run() { + // set ready to swap + synchronized (mLock) { + if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return; + mState = STATE_READY_TO_SWAP; + } + invalidateSelf(); + } + + @Override + public void start() { + if (!isRunning()) { + synchronized (mLock) { + if (mState == STATE_SCHEDULED) return; // already scheduled + scheduleDecodeLocked(); + } + } + } + + @Override + public void stop() { + if (isRunning()) { + unscheduleSelf(this); + } + } + + @Override + public boolean isRunning() { + synchronized (mLock) { + return mNextFrameToDecode > -1; + } + } + + @Override + public void scheduleSelf(Runnable what, long when) { + super.scheduleSelf(what, when); + } + + @Override + public void unscheduleSelf(Runnable what) { + synchronized (mLock) { + mNextFrameToDecode = -1; + } + super.unscheduleSelf(what); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + + if (!visible) { + stop(); + } else if (restart || changed) { + stop(); + start(); + } + + return changed; + } + + // drawing properties + + @Override + public void setFilterBitmap(boolean filter) { + mPaint.setFilterBitmap(filter); + } + + @Override + public void setAlpha(int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mPaint.setColorFilter(colorFilter); + } + + @Override + public int getIntrinsicWidth() { + return mFrameSequence.getWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mFrameSequence.getHeight(); + } + + @Override + public int getOpacity() { + return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT; + } +} |