summaryrefslogtreecommitdiffstats
path: root/framesequence
diff options
context:
space:
mode:
authorChris Craik <ccraik@google.com>2014-01-13 19:37:04 -0800
committerChris Craik <ccraik@google.com>2014-01-14 12:14:16 -0800
commite36c5d675c8c2f900ef186a55edf71ce36ca9fa0 (patch)
tree7316be06e3fc377a1ce73672d88d8aeef2fc4ebd /framesequence
parent335482c41b2ac3b66da2e207b47306fb53f4b174 (diff)
downloadandroid_frameworks_ex-e36c5d675c8c2f900ef186a55edf71ce36ca9fa0.tar.gz
android_frameworks_ex-e36c5d675c8c2f900ef186a55edf71ce36ca9fa0.tar.bz2
android_frameworks_ex-e36c5d675c8c2f900ef186a55edf71ce36ca9fa0.zip
Add loop count control
-Adds callback into app for drawable that finishes animating -Fixes timestamp delay mapping (was previously off by one) -64 bit pointer storage Change-Id: I21cf7eb325fd58fb0aeda58f864d35fe483a89a7
Diffstat (limited to 'framesequence')
-rw-r--r--framesequence/jni/FrameSequence.h3
-rw-r--r--framesequence/jni/FrameSequenceJNI.cpp33
-rw-r--r--framesequence/jni/FrameSequence_gif.cpp23
-rw-r--r--framesequence/jni/FrameSequence_gif.h9
-rw-r--r--framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java8
-rw-r--r--framesequence/src/android/support/rastermill/FrameSequence.java29
-rw-r--r--framesequence/src/android/support/rastermill/FrameSequenceDrawable.java71
7 files changed, 142 insertions, 34 deletions
diff --git a/framesequence/jni/FrameSequence.h b/framesequence/jni/FrameSequence.h
index 781b7c6..6667cdd 100644
--- a/framesequence/jni/FrameSequence.h
+++ b/framesequence/jni/FrameSequence.h
@@ -46,8 +46,9 @@ public:
virtual ~FrameSequence() {}
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
- virtual int getFrameCount() const = 0;
virtual bool isOpaque() const = 0;
+ virtual int getFrameCount() const = 0;
+ virtual int getDefaultLoopCount() const = 0;
virtual FrameSequenceState* createState() const = 0;
};
diff --git a/framesequence/jni/FrameSequenceJNI.cpp b/framesequence/jni/FrameSequenceJNI.cpp
index 90d0465..369b481 100644
--- a/framesequence/jni/FrameSequenceJNI.cpp
+++ b/framesequence/jni/FrameSequenceJNI.cpp
@@ -37,11 +37,12 @@ static jobject createJavaFrameSequence(JNIEnv* env, FrameSequence* frameSequence
return NULL;
}
return env->NewObject(gFrameSequenceClassInfo.clazz, gFrameSequenceClassInfo.ctor,
- reinterpret_cast<jint>(frameSequence),
+ reinterpret_cast<jlong>(frameSequence),
frameSequence->getWidth(),
frameSequence->getHeight(),
+ frameSequence->isOpaque(),
frameSequence->getFrameCount(),
- frameSequence->isOpaque());
+ frameSequence->getDefaultLoopCount());
}
static jobject nativeDecodeByteArray(JNIEnv* env, jobject clazz,
@@ -67,15 +68,15 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
}
static void nativeDestroyFrameSequence(JNIEnv* env, jobject clazz,
- jint frameSequenceInt) {
- FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceInt);
+ jlong frameSequenceLong) {
+ FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
delete frameSequence;
}
-static jint nativeCreateState(JNIEnv* env, jobject clazz, jint frameSequenceInt) {
- FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceInt);
+static jlong nativeCreateState(JNIEnv* env, jobject clazz, jlong frameSequenceLong) {
+ FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
FrameSequenceState* state = frameSequence->createState();
- return reinterpret_cast<jint>(state);
+ return reinterpret_cast<jlong>(state);
}
////////////////////////////////////////////////////////////////////////////////
@@ -83,17 +84,17 @@ static jint nativeCreateState(JNIEnv* env, jobject clazz, jint frameSequenceInt)
////////////////////////////////////////////////////////////////////////////////
static void nativeDestroyState(
- JNIEnv* env, jobject clazz, jint frameSequenceStateInt) {
+ JNIEnv* env, jobject clazz, jlong frameSequenceStateLong) {
FrameSequenceState* frameSequenceState =
- reinterpret_cast<FrameSequenceState*>(frameSequenceStateInt);
+ reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
delete frameSequenceState;
}
static jlong JNICALL nativeGetFrame(
- JNIEnv* env, jobject clazz, jint frameSequenceStateInt, jint frameNr,
+ JNIEnv* env, jobject clazz, jlong frameSequenceStateLong, jint frameNr,
jobject bitmap, jint previousFrameNr) {
FrameSequenceState* frameSequenceState =
- reinterpret_cast<FrameSequenceState*>(frameSequenceStateInt);
+ reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
int ret;
AndroidBitmapInfo info;
void* pixels;
@@ -128,19 +129,19 @@ static JNINativeMethod gMethods[] = {
(void*) nativeDecodeStream
},
{ "nativeDestroyFrameSequence",
- "(I)V",
+ "(J)V",
(void*) nativeDestroyFrameSequence
},
{ "nativeCreateState",
- "(I)I",
+ "(J)J",
(void*) nativeCreateState
},
{ "nativeGetFrame",
- "(IILandroid/graphics/Bitmap;I)J",
+ "(JILandroid/graphics/Bitmap;I)J",
(void*) nativeGetFrame
},
{ "nativeDestroyFrameSequence",
- "(I)V",
+ "(J)V",
(void*) nativeDestroyState
},
};
@@ -155,7 +156,7 @@ jint FrameSequence_OnLoad(JNIEnv* env) {
}
gFrameSequenceClassInfo.clazz = (jclass)env->NewGlobalRef(gFrameSequenceClassInfo.clazz);
- gFrameSequenceClassInfo.ctor = env->GetMethodID(gFrameSequenceClassInfo.clazz, "<init>", "(IIIIZ)V");
+ gFrameSequenceClassInfo.ctor = env->GetMethodID(gFrameSequenceClassInfo.clazz, "<init>", "(JIIZII)V");
if (!gFrameSequenceClassInfo.ctor) {
ALOGW("Failed to find constructor for FrameSequence - was it stripped?");
return -1;
diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp
index e9f3ace..2402439 100644
--- a/framesequence/jni/FrameSequence_gif.cpp
+++ b/framesequence/jni/FrameSequence_gif.cpp
@@ -54,7 +54,7 @@ static bool willBeCleared(const GraphicsControlBlock& gcb) {
////////////////////////////////////////////////////////////////////////////////
FrameSequence_gif::FrameSequence_gif(Stream* stream) :
- mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) {
+ mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) {
mGif = DGifOpen(stream, streamReader, NULL);
if (!mGif) {
ALOGW("Gif load failed");
@@ -76,6 +76,23 @@ FrameSequence_gif::FrameSequence_gif(Stream* stream) :
GraphicsControlBlock gcb;
for (int i = 0; i < mGif->ImageCount; i++) {
const SavedImage& image = mGif->SavedImages[i];
+
+ // find the loop extension pair
+ for (int j = 0; (j + 1) < image.ExtensionBlockCount; j++) {
+ ExtensionBlock* eb1 = image.ExtensionBlocks + j;
+ ExtensionBlock* eb2 = image.ExtensionBlocks + j + 1;
+ if (eb1->Function == APPLICATION_EXT_FUNC_CODE &&
+ // look for "NETSCAPE2.0" app extension
+ eb1->ByteCount == 11 &&
+ !strcmp((const char*)(eb1->Bytes), "NETSCAPE2.0") &&
+ // verify extension contents and get loop count
+ eb2->Function == CONTINUE_EXT_FUNC_CODE &&
+ eb2->ByteCount == 3 &&
+ eb2->Bytes[0] == 1) {
+ mLoopCount = (int)(eb2->Bytes[2] & 0xff) + (int)(eb2->Bytes[1] & 0xff);
+ }
+ }
+
DGifSavedExtensionToGCB(mGif, i, &gcb);
// timing
@@ -316,6 +333,10 @@ long FrameSequenceState_gif::drawFrame(int frameNr,
}
}
+ // return last frame's delay
+ const int maxFrame = gif->ImageCount;
+ const int lastFrame = (frameNr + maxFrame - 1) % maxFrame;
+ DGifSavedExtensionToGCB(gif, lastFrame, &gcb);
return getDelayMs(gcb);
}
diff --git a/framesequence/jni/FrameSequence_gif.h b/framesequence/jni/FrameSequence_gif.h
index fbc4959..8bf57b6 100644
--- a/framesequence/jni/FrameSequence_gif.h
+++ b/framesequence/jni/FrameSequence_gif.h
@@ -37,12 +37,16 @@ public:
return mGif ? mGif->SHeight : 0;
}
+ virtual bool isOpaque() const {
+ return (mBgColor & COLOR_8888_ALPHA_MASK) == COLOR_8888_ALPHA_MASK;
+ }
+
virtual int getFrameCount() const {
return mGif ? mGif->ImageCount : 0;
}
- virtual bool isOpaque() const {
- return (mBgColor & COLOR_8888_ALPHA_MASK) == COLOR_8888_ALPHA_MASK;
+ virtual int getDefaultLoopCount() const {
+ return mLoopCount;
}
virtual FrameSequenceState* createState() const;
@@ -54,6 +58,7 @@ public:
private:
GifFileType* mGif;
+ int mLoopCount;
Color8888 mBgColor;
// array of bool per frame - if true, frame data is used by a later DISPOSE_PREVIOUS frame
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
index 45d3415..ea593dc 100644
--- a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
+++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
@@ -21,6 +21,7 @@ import android.support.rastermill.FrameSequence;
import android.support.rastermill.FrameSequenceDrawable;
import android.view.View;
import android.widget.ImageView;
+import android.widget.Toast;
import java.io.InputStream;
@@ -36,6 +37,13 @@ public class AnimatedGifTest extends Activity {
FrameSequence fs = FrameSequence.decodeStream(is);
final FrameSequenceDrawable drawable = new FrameSequenceDrawable(fs);
+ drawable.setOnFinishedListener(new FrameSequenceDrawable.OnFinishedListener() {
+ @Override
+ public void onFinished(FrameSequenceDrawable drawable) {
+ Toast.makeText(getApplicationContext(),
+ "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show();
+ }
+ });
imageView.setImageDrawable(drawable);
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java
index 5881ea9..d6bde0f 100644
--- a/framesequence/src/android/support/rastermill/FrameSequence.java
+++ b/framesequence/src/android/support/rastermill/FrameSequence.java
@@ -25,33 +25,36 @@ public class FrameSequence {
System.loadLibrary("framesequence");
}
- private final int mNativeFrameSequence;
+ private final long mNativeFrameSequence;
private final int mWidth;
private final int mHeight;
- private final int mFrameCount;
private final boolean mOpaque;
+ private final int mFrameCount;
+ private final int mDefaultLoopCount;
public int getWidth() { return mWidth; }
public int getHeight() { return mHeight; }
- public int getFrameCount() { return mFrameCount; }
public boolean isOpaque() { return mOpaque; }
+ public int getFrameCount() { return mFrameCount; }
+ public int getDefaultLoopCount() { return mDefaultLoopCount; }
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,
+ private static native void nativeDestroyFrameSequence(long nativeFrameSequence);
+ private static native long nativeCreateState(long nativeFrameSequence);
+ private static native void nativeDestroyState(long nativeState);
+ private static native long nativeGetFrame(long nativeState, int frameNr,
Bitmap output, int previousFrameNr);
@SuppressWarnings("unused") // called by native
- private FrameSequence(int nativeFrameSequence, int width, int height,
- int frameCount, boolean opaque) {
+ private FrameSequence(long nativeFrameSequence, int width, int height,
+ boolean opaque, int frameCount, int defaultLoopCount) {
mNativeFrameSequence = nativeFrameSequence;
mWidth = width;
mHeight = height;
- mFrameCount = frameCount;
mOpaque = opaque;
+ mFrameCount = frameCount;
+ mDefaultLoopCount = defaultLoopCount;
}
public static FrameSequence decodeByteArray(byte[] data) {
@@ -77,7 +80,7 @@ public class FrameSequence {
throw new IllegalStateException("attempted to use incorrectly built FrameSequence");
}
- int nativeState = nativeCreateState(mNativeFrameSequence);
+ long nativeState = nativeCreateState(mNativeFrameSequence);
if (nativeState == 0) {
return null;
}
@@ -106,9 +109,9 @@ public class FrameSequence {
* remain ref'd while it is in use
*/
static class State {
- private int mNativeState;
+ private long mNativeState;
- public State(int nativeState) {
+ public State(long nativeState) {
mNativeState = nativeState;
}
diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
index 94f4da0..f5f1f47 100644
--- a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
+++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
@@ -42,6 +42,49 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna
}
}
+ public static interface OnFinishedListener {
+ /**
+ * Called when a FrameSequenceDrawable has finished looping.
+ *
+ * Note that this is will not be called if the drawable is explicitly
+ * stopped, or marked invisible.
+ */
+ public abstract void onFinished(FrameSequenceDrawable drawable);
+ }
+
+ /**
+ * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
+ *
+ * @see setLoopBehavior()
+ */
+ public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
+ mOnFinishedListener = onFinishedListener;
+ }
+
+ /**
+ * Loop only once.
+ */
+ public static final int LOOP_ONCE = 1;
+
+ /**
+ * Loop continuously. The OnFinishedListener will never be called.
+ */
+ public static final int LOOP_INF = 2;
+
+ /**
+ * Use loop count stored in source data, or LOOP_ONCE if not present.
+ */
+ public static final int LOOP_DEFAULT = 3;
+
+ /**
+ * Define looping behavior of frame sequence.
+ *
+ * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
+ */
+ public void setLoopBehavior(int loopBehavior) {
+ mLoopBehavior = loopBehavior;
+ }
+
private final FrameSequence mFrameSequence;
private final FrameSequence.State mFrameSequenceState;
@@ -60,9 +103,12 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna
private static final int STATE_READY_TO_SWAP = 4;
private int mState;
+ private int mCurrentLoop;
+ private int mLoopBehavior = LOOP_DEFAULT;
private long mLastSwap;
private int mNextFrameToDecode;
+ private OnFinishedListener mOnFinishedListener;
/**
* Runs on decoding thread, only modifies mBackBitmap's pixels
@@ -93,6 +139,14 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna
}
};
+ private Runnable mCallbackRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mOnFinishedListener != null) {
+ mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
+ }
+ }
+ };
public FrameSequenceDrawable(FrameSequence frameSequence) {
if (frameSequence == null) throw new IllegalArgumentException();
@@ -138,7 +192,21 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna
mFrontBitmap = tmp;
mLastSwap = SystemClock.uptimeMillis();
- scheduleDecodeLocked();
+
+ boolean continueLooping = true;
+ if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
+ mCurrentLoop++;
+ if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
+ (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
+ continueLooping = false;
+ }
+ }
+
+ if (continueLooping) {
+ scheduleDecodeLocked();
+ } else {
+ scheduleSelf(mCallbackRunnable, 0);
+ }
}
}
@@ -166,6 +234,7 @@ public class FrameSequenceDrawable extends Drawable implements Animatable, Runna
if (!isRunning()) {
synchronized (mLock) {
if (mState == STATE_SCHEDULED) return; // already scheduled
+ mCurrentLoop = 0;
scheduleDecodeLocked();
}
}