summaryrefslogtreecommitdiffstats
path: root/framesequence/src
diff options
context:
space:
mode:
authorChris Craik <ccraik@google.com>2014-01-06 12:43:42 -0800
committerChris Craik <ccraik@google.com>2014-01-13 17:56:03 +0000
commita3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4 (patch)
treea46a47658126ecf2f3365eab0c944b80bead0dd3 /framesequence/src
parent58ce8de3fbb0cfed369b6a38d0f5c92104745eb4 (diff)
downloadandroid_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.java135
-rw-r--r--framesequence/src/android/support/rastermill/FrameSequenceDrawable.java246
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;
+ }
+}