diff options
author | Chris Craik <ccraik@google.com> | 2014-01-06 12:43:42 -0800 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2014-01-13 17:56:03 +0000 |
commit | a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4 (patch) | |
tree | a46a47658126ecf2f3365eab0c944b80bead0dd3 /framesequence/src | |
parent | 58ce8de3fbb0cfed369b6a38d0f5c92104745eb4 (diff) | |
download | android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.tar.gz android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.tar.bz2 android_frameworks_ex-a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4.zip |
Import FrameSequence
Change-Id: I09b668925366a22e8e7e80e4abeae24b3a98c639
(cherry picked from commit a1265c3d8a20e805e0c45083d5c7d728d4b70009)
Diffstat (limited to 'framesequence/src')
-rw-r--r-- | framesequence/src/android/support/rastermill/FrameSequence.java | 135 | ||||
-rw-r--r-- | framesequence/src/android/support/rastermill/FrameSequenceDrawable.java | 246 |
2 files changed, 381 insertions, 0 deletions
diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java new file mode 100644 index 0000000..5881ea9 --- /dev/null +++ b/framesequence/src/android/support/rastermill/FrameSequence.java @@ -0,0 +1,135 @@ +/* + * 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 java.io.InputStream; + +public class FrameSequence { + static { + System.loadLibrary("framesequence"); + } + + private final int mNativeFrameSequence; + private final int mWidth; + private final int mHeight; + private final int mFrameCount; + private final boolean mOpaque; + + public int getWidth() { return mWidth; } + public int getHeight() { return mHeight; } + public int getFrameCount() { return mFrameCount; } + public boolean isOpaque() { return mOpaque; } + + private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length); + private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage); + private static native void nativeDestroyFrameSequence(int nativeFrameSequence); + private static native int nativeCreateState(int nativeFrameSequence); + private static native void nativeDestroyState(int nativeState); + private static native long nativeGetFrame(int nativeState, int frameNr, + Bitmap output, int previousFrameNr); + + @SuppressWarnings("unused") // called by native + private FrameSequence(int nativeFrameSequence, int width, int height, + int frameCount, boolean opaque) { + mNativeFrameSequence = nativeFrameSequence; + mWidth = width; + mHeight = height; + mFrameCount = frameCount; + mOpaque = opaque; + } + + public static FrameSequence decodeByteArray(byte[] data) { + return decodeByteArray(data, 0, data.length); + } + + public static FrameSequence decodeByteArray(byte[] data, int offset, int length) { + if (data == null) throw new IllegalArgumentException(); + if (offset < 0 || length < 0 || (offset + length > data.length)) { + throw new IllegalArgumentException("invalid offset/length parameters"); + } + return nativeDecodeByteArray(data, offset, length); + } + + public static FrameSequence decodeStream(InputStream stream) { + if (stream == null) throw new IllegalArgumentException(); + byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool + return nativeDecodeStream(stream, tempStorage); + } + + State createState() { + if (mNativeFrameSequence == 0) { + throw new IllegalStateException("attempted to use incorrectly built FrameSequence"); + } + + int nativeState = nativeCreateState(mNativeFrameSequence); + if (nativeState == 0) { + return null; + } + return new State(nativeState); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence); + } finally { + super.finalize(); + } + } + + /** + * Playback state used when moving frames forward in a frame sequence. + * + * Note that this doesn't require contiguous frames to be rendered, it just stores + * information (in the case of gif, a recall buffer) that will be used to construct + * frames based upon data recorded before previousFrameNr. + * + * Note: {@link #recycle()} *must* be called before the object is GC'd to free native resources + * + * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should + * remain ref'd while it is in use + */ + static class State { + private int mNativeState; + + public State(int nativeState) { + mNativeState = nativeState; + } + + public void recycle() { + if (mNativeState != 0) { + nativeDestroyState(mNativeState); + mNativeState = 0; + } + } + + // TODO: consider adding alternate API for drawing into a SurfaceTexture + public long getFrame(int frameNr, Bitmap output, int previousFrameNr) { + if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) { + throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888"); + } + if (mNativeState == 0) { + throw new IllegalStateException("attempted to draw recycled FrameSequenceState"); + } + return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr); + } + } + + // TODO: add recycle() cleanup method +} 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; + } +} |