diff options
Diffstat (limited to 'framesequence')
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(); } } |