Loading core/jni/android/graphics/AnimatedImageDrawable.cpp +12 −6 Original line number Diff line number Diff line Loading @@ -16,16 +16,16 @@ #include "GraphicsJNI.h" #include "ImageDecoder.h" #include "core_jni_helpers.h" #include "Utils.h" #include "core_jni_helpers.h" #include <hwui/AnimatedImageDrawable.h> #include <hwui/Canvas.h> #include <SkAndroidCodec.h> #include <SkAnimatedImage.h> #include <SkColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <hwui/AnimatedImageDrawable.h> #include <hwui/Canvas.h> using namespace android; Loading Loading @@ -85,6 +85,9 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct)); } // Java's FINISHED relies on this being -1 static_assert(SkAnimatedImage::kFinished == -1); static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); Loading Loading @@ -115,9 +118,6 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, return drawable->isRunning(); } // Java's NOT_RUNNING relies on this being -2.0. static_assert(SkAnimatedImage::kNotRunning == -2.0); static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); return drawable->start(); Loading Loading @@ -172,6 +172,11 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/ return sizeof(drawable); } static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); drawable->markInvisible(); } static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, Loading @@ -185,6 +190,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, { "nMarkInvisible", "(J)V", (void*) AnimatedImageDrawable_nMarkInvisible }, }; int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { Loading graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +35 −13 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.graphics.drawable; import dalvik.annotation.optimization.FastNative; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -60,7 +62,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private int mIntrinsicHeight; private boolean mStarting; private boolean mRunning; private Handler mHandler; Loading Loading @@ -222,8 +223,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return mIntrinsicHeight; } // nDraw returns -2 if the animation is not running. private static final int NOT_RUNNING = -2; // nDraw returns -1 if the animation has finished. private static final int FINISHED = -1; @Override public void draw(@NonNull Canvas canvas) { Loading @@ -235,8 +236,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { mStarting = false; postOnAnimationStart(); mRunning = true; } long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper()); Loading @@ -244,12 +243,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { // will manage the animation if (nextUpdate > 0) { scheduleSelf(mRunnable, nextUpdate); } else if (nextUpdate == NOT_RUNNING) { // -2 means the animation ended, when drawn in software mode. if (mRunning) { } else if (nextUpdate == FINISHED) { // This means the animation was drawn in software mode and ended. postOnAnimationEnd(); mRunning = false; } } } Loading Loading @@ -292,6 +288,19 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return PixelFormat.TRANSLUCENT; } @Override public boolean setVisible(boolean visible, boolean restart) { if (!super.setVisible(visible, restart)) { return false; } if (!visible) { nMarkInvisible(mState.mNativePtr); } return true; } // Animatable overrides /** * Return whether the animation is currently running. Loading @@ -301,7 +310,10 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { */ @Override public boolean isRunning() { return mRunning; if (mState == null) { throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable"); } return nIsRunning(mState.mNativePtr); } /** Loading Loading @@ -336,7 +348,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); } nStop(mState.mNativePtr); mRunning = false; } // Animatable2 overrides Loading Loading @@ -405,18 +416,29 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private static native long nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) throws IOException; @FastNative private static native long nGetNativeFinalizer(); private static native long nDraw(long nativePtr, long canvasNativePtr); @FastNative private static native void nSetAlpha(long nativePtr, int alpha); @FastNative private static native int nGetAlpha(long nativePtr); @FastNative private static native void nSetColorFilter(long nativePtr, long nativeFilter); @FastNative private static native boolean nIsRunning(long nativePtr); // Return whether the animation started. @FastNative private static native boolean nStart(long nativePtr); @FastNative private static native void nStop(long nativePtr); @FastNative private static native void nSetLoopCount(long nativePtr, int loopCount); // Pass the drawable down to native so it can call onAnimationEnd. private static native void nSetOnAnimationEndListener(long nativePtr, @Nullable AnimatedImageDrawable drawable); @FastNative private static native long nNativeByteSize(long nativePtr); @FastNative private static native void nMarkInvisible(long nativePtr); } libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ cc_defaults { srcs: [ "hwui/AnimatedImageDrawable.cpp", "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "font/CacheTexture.cpp", "font/Font.cpp", Loading libs/hwui/hwui/AnimatedImageDrawable.cpp +167 −80 Original line number Diff line number Diff line Loading @@ -15,21 +15,21 @@ */ #include "AnimatedImageDrawable.h" #include "AnimatedImageThread.h" #include "thread/Task.h" #include "thread/TaskManager.h" #include "thread/TaskProcessor.h" #include "utils/TraceUtils.h" #include <SkPicture.h> #include <SkRefCnt.h> #include <SkTime.h> #include <SkTLazy.h> #include <SkTime.h> namespace android { AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage) : mSkAnimatedImage(std::move(animatedImage)) { } : mSkAnimatedImage(std::move(animatedImage)) { mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration(); } void AnimatedImageDrawable::syncProperties() { mAlpha = mStagingAlpha; Loading @@ -37,88 +37,78 @@ void AnimatedImageDrawable::syncProperties() { } bool AnimatedImageDrawable::start() { SkAutoExclusive lock(mLock); if (mSkAnimatedImage->isRunning()) { if (mRunning) { return false; } if (!mSnapshot) { mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); } // While stopped, update() does not decode, but it does advance the time. // This prevents us from skipping ahead when we resume. const double currentTime = SkTime::GetMSecs(); mSkAnimatedImage->update(currentTime); mSkAnimatedImage->start(); return mSkAnimatedImage->isRunning(); mRunning = true; return true; } void AnimatedImageDrawable::stop() { SkAutoExclusive lock(mLock); mSkAnimatedImage->stop(); mRunning = false; } bool AnimatedImageDrawable::isRunning() { return mSkAnimatedImage->isRunning(); return mRunning; } // This is really a Task<void> but that doesn't really work when Future<> // expects to be able to get/set a value class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> { public: AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable) : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {} sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable; bool mIsCompleted = false; }; bool AnimatedImageDrawable::nextSnapshotReady() const { return mNextSnapshot.valid() && mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> { public: explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager) : uirenderer::TaskProcessor<bool>(taskManager) {} ~AnimatedImageTaskProcessor() {} // Only called on the RenderThread while UI thread is locked. bool AnimatedImageDrawable::isDirty() { const double currentTime = SkTime::GetMSecs(); const double lastWallTime = mLastWallTime; virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override { ATRACE_NAME("Updating AnimatedImageDrawables"); AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get()); t->mAnimatedImageDrawable->update(); t->mIsCompleted = true; task->setResult(true); }; }; mLastWallTime = currentTime; if (!mRunning) { mDidDraw = false; return false; } void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) { if (!mSkAnimatedImage->isRunning() || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) { return; std::unique_lock lock{mSwapLock}; if (mDidDraw) { mCurrentTime += currentTime - lastWallTime; mDidDraw = false; } if (!mDecodeTaskProcessor.get()) { mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager); if (!mNextSnapshot.valid()) { // Need to trigger onDraw in order to start decoding the next frame. return true; } // TODO get one frame ahead and only schedule updates when you need to replenish mDecodeTask = new AnimatedImageTask(this); mDecodeTaskProcessor->add(mDecodeTask); return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot; } void AnimatedImageDrawable::update() { SkAutoExclusive lock(mLock); // Only called on the AnimatedImageThread. AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; snap.mDuration = mSkAnimatedImage->decodeNextFrame(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } if (!mSkAnimatedImage->isRunning()) { return; return snap; } const double currentTime = SkTime::GetMSecs(); if (currentTime >= mNextFrameTime) { mNextFrameTime = mSkAnimatedImage->update(currentTime); mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); mIsDirty = true; // Only called on the AnimatedImageThread. AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { Snapshot snap; { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); snap.mDuration = mSkAnimatedImage->currentFrameDuration(); } return snap; } // Only called on the RenderThread. void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { SkTLazy<SkPaint> lazyPaint; if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) { Loading @@ -128,25 +118,71 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality); } SkAutoExclusive lock(mLock); if (mSnapshot) { canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull()); } else { // TODO: we could potentially keep the cached surface around if there is a paint and we know // the drawable is attached to the view system mDidDraw = true; bool drewDirectly = false; if (!mSnapshot.mPic) { // The image is not animating, and never was. Draw directly from // mSkAnimatedImage. SkAutoCanvasRestore acr(canvas, false); if (lazyPaint.isValid()) { canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get()); } std::unique_lock lock{mImageLock}; mSkAnimatedImage->draw(canvas); drewDirectly = true; } if (mRunning && mFinished) { auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.reset(sk_ref_sp(this)); mFinished = false; } bool finalFrame = false; if (mRunning && nextSnapshotReady()) { std::unique_lock lock{mSwapLock}; if (mCurrentTime >= mTimeToShowNextSnapshot) { mSnapshot = mNextSnapshot.get(); const double timeToShowCurrentSnap = mTimeToShowNextSnapshot; if (mSnapshot.mDuration == SkAnimatedImage::kFinished) { finalFrame = true; mRunning = false; mFinished = true; } else { mTimeToShowNextSnapshot += mSnapshot.mDuration; if (mCurrentTime >= mTimeToShowNextSnapshot) { // This would mean showing the current frame very briefly. It's // possible that not being displayed for a time resulted in // mCurrentTime being far ahead. Prevent showing many frames // rapidly by going back to the beginning of this frame time. mCurrentTime = timeToShowCurrentSnap; } } } } if (mRunning && !mNextSnapshot.valid()) { auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); } mIsDirty = false; if (!drewDirectly) { // No other thread will modify mCurrentSnap so this should be safe to // use without locking. canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull()); } if (finalFrame) { if (mEndListener) { mEndListener->onAnimationEnd(); mEndListener = nullptr; } } } double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { // update the drawable with the current time double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs()); SkAutoCanvasRestore acr(canvas, false); if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) { SkPaint paint; Loading @@ -154,8 +190,59 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { paint.setColorFilter(mStagingColorFilter); canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint); } if (mFinished && !mRunning) { // Continue drawing the last frame, and return 0 to indicate no need to // redraw. std::unique_lock lock{mImageLock}; canvas->drawDrawable(mSkAnimatedImage.get()); return 0.0; } bool update = false; { const double currentTime = SkTime::GetMSecs(); std::unique_lock lock{mSwapLock}; // mLastWallTime starts off at 0. If it is still 0, just update it to // the current time and avoid updating if (mLastWallTime == 0.0) { mCurrentTime = currentTime; } else if (mRunning) { if (mFinished) { mCurrentTime = currentTime; { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); } mTimeToShowNextSnapshot = currentTime + mSkAnimatedImage->currentFrameDuration(); } else { mCurrentTime += currentTime - mLastWallTime; update = mCurrentTime >= mTimeToShowNextSnapshot; } } mLastWallTime = currentTime; } double duration = 0.0; { std::unique_lock lock{mImageLock}; if (update) { duration = mSkAnimatedImage->decodeNextFrame(); } canvas->drawDrawable(mSkAnimatedImage.get()); return nextUpdate; } }; // namespace android std::unique_lock lock{mSwapLock}; if (update) { if (duration == SkAnimatedImage::kFinished) { mRunning = false; mFinished = true; } else { mTimeToShowNextSnapshot += duration; } } return mTimeToShowNextSnapshot; } } // namespace android libs/hwui/hwui/AnimatedImageDrawable.h +63 −31 Original line number Diff line number Diff line Loading @@ -17,22 +17,20 @@ #pragma once #include <cutils/compiler.h> #include <utils/Macros.h> #include <utils/RefBase.h> #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> #include <SkMutex.h> #include <SkPicture.h> class SkPicture; #include <future> #include <mutex> namespace android { namespace uirenderer { class TaskManager; } class OnAnimationEndListener { public: virtual ~OnAnimationEndListener() {} Loading @@ -41,68 +39,102 @@ public: }; /** * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread. * Native component of android.graphics.drawable.AnimatedImageDrawables.java. * This class can be drawn into Canvas.h and maintains the state needed to drive * the animation from the RenderThread. */ class ANDROID_API AnimatedImageDrawable : public SkDrawable { public: AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage); /** * This returns true if the animation has updated and signals that the next draw will contain * new content. * This updates the internal time and returns true if the animation needs * to be redrawn. * * This is called on RenderThread, while the UI thread is locked. */ bool isDirty() const { return mIsDirty; } bool isDirty(); int getStagingAlpha() const { return mStagingAlpha; } void setStagingAlpha(int alpha) { mStagingAlpha = alpha; } void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; } void syncProperties(); virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } // Draw to software canvas, and return time to next draw. double drawStaging(SkCanvas* canvas); // Returns true if the animation was started; false otherwise (e.g. it was already running) // Returns true if the animation was started; false otherwise (e.g. it was // already running) bool start(); void stop(); bool isRunning(); void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) { mEndListener = std::move(listener); } void scheduleUpdate(uirenderer::TaskManager* taskManager); void markInvisible() { mDidDraw = false; } struct Snapshot { sk_sp<SkPicture> mPic; int mDuration; Snapshot() = default; Snapshot(Snapshot&&) = default; Snapshot& operator=(Snapshot&&) = default; PREVENT_COPY_AND_ASSIGN(Snapshot); }; // These are only called on AnimatedImageThread. Snapshot decodeNextFrame(); Snapshot reset(); protected: virtual void onDraw(SkCanvas* canvas) override; private: void update(); sk_sp<SkAnimatedImage> mSkAnimatedImage; sk_sp<SkPicture> mSnapshot; SkMutex mLock; bool mRunning = false; bool mFinished = false; // A snapshot of the current frame to draw. Snapshot mSnapshot; std::future<Snapshot> mNextSnapshot; bool nextSnapshotReady() const; // When to switch from mSnapshot to mNextSnapshot. double mTimeToShowNextSnapshot = 0.0; // The current time for the drawable itself. double mCurrentTime = 0.0; // The wall clock of the last time we called isDirty. double mLastWallTime = 0.0; // Whether we drew since the last call to isDirty. bool mDidDraw = false; // Locked when assigning snapshots and times. Operations while this is held // should be short. std::mutex mSwapLock; // Locked when mSkAnimatedImage is being updated or drawn. std::mutex mImageLock; int mStagingAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mStagingColorFilter; int mAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mColorFilter; double mNextFrameTime = 0.0; bool mIsDirty = false; class AnimatedImageTask; class AnimatedImageTaskProcessor; sp<AnimatedImageTask> mDecodeTask; sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor; std::unique_ptr<OnAnimationEndListener> mEndListener; }; }; // namespace android } // namespace android Loading
core/jni/android/graphics/AnimatedImageDrawable.cpp +12 −6 Original line number Diff line number Diff line Loading @@ -16,16 +16,16 @@ #include "GraphicsJNI.h" #include "ImageDecoder.h" #include "core_jni_helpers.h" #include "Utils.h" #include "core_jni_helpers.h" #include <hwui/AnimatedImageDrawable.h> #include <hwui/Canvas.h> #include <SkAndroidCodec.h> #include <SkAnimatedImage.h> #include <SkColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <hwui/AnimatedImageDrawable.h> #include <hwui/Canvas.h> using namespace android; Loading Loading @@ -85,6 +85,9 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct)); } // Java's FINISHED relies on this being -1 static_assert(SkAnimatedImage::kFinished == -1); static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); Loading Loading @@ -115,9 +118,6 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, return drawable->isRunning(); } // Java's NOT_RUNNING relies on this being -2.0. static_assert(SkAnimatedImage::kNotRunning == -2.0); static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); return drawable->start(); Loading Loading @@ -172,6 +172,11 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/ return sizeof(drawable); } static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); drawable->markInvisible(); } static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, Loading @@ -185,6 +190,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, { "nMarkInvisible", "(J)V", (void*) AnimatedImageDrawable_nMarkInvisible }, }; int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { Loading
graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +35 −13 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package android.graphics.drawable; import dalvik.annotation.optimization.FastNative; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -60,7 +62,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private int mIntrinsicHeight; private boolean mStarting; private boolean mRunning; private Handler mHandler; Loading Loading @@ -222,8 +223,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return mIntrinsicHeight; } // nDraw returns -2 if the animation is not running. private static final int NOT_RUNNING = -2; // nDraw returns -1 if the animation has finished. private static final int FINISHED = -1; @Override public void draw(@NonNull Canvas canvas) { Loading @@ -235,8 +236,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { mStarting = false; postOnAnimationStart(); mRunning = true; } long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper()); Loading @@ -244,12 +243,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { // will manage the animation if (nextUpdate > 0) { scheduleSelf(mRunnable, nextUpdate); } else if (nextUpdate == NOT_RUNNING) { // -2 means the animation ended, when drawn in software mode. if (mRunning) { } else if (nextUpdate == FINISHED) { // This means the animation was drawn in software mode and ended. postOnAnimationEnd(); mRunning = false; } } } Loading Loading @@ -292,6 +288,19 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return PixelFormat.TRANSLUCENT; } @Override public boolean setVisible(boolean visible, boolean restart) { if (!super.setVisible(visible, restart)) { return false; } if (!visible) { nMarkInvisible(mState.mNativePtr); } return true; } // Animatable overrides /** * Return whether the animation is currently running. Loading @@ -301,7 +310,10 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { */ @Override public boolean isRunning() { return mRunning; if (mState == null) { throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable"); } return nIsRunning(mState.mNativePtr); } /** Loading Loading @@ -336,7 +348,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); } nStop(mState.mNativePtr); mRunning = false; } // Animatable2 overrides Loading Loading @@ -405,18 +416,29 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private static native long nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) throws IOException; @FastNative private static native long nGetNativeFinalizer(); private static native long nDraw(long nativePtr, long canvasNativePtr); @FastNative private static native void nSetAlpha(long nativePtr, int alpha); @FastNative private static native int nGetAlpha(long nativePtr); @FastNative private static native void nSetColorFilter(long nativePtr, long nativeFilter); @FastNative private static native boolean nIsRunning(long nativePtr); // Return whether the animation started. @FastNative private static native boolean nStart(long nativePtr); @FastNative private static native void nStop(long nativePtr); @FastNative private static native void nSetLoopCount(long nativePtr, int loopCount); // Pass the drawable down to native so it can call onAnimationEnd. private static native void nSetOnAnimationEndListener(long nativePtr, @Nullable AnimatedImageDrawable drawable); @FastNative private static native long nNativeByteSize(long nativePtr); @FastNative private static native void nMarkInvisible(long nativePtr); }
libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ cc_defaults { srcs: [ "hwui/AnimatedImageDrawable.cpp", "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "font/CacheTexture.cpp", "font/Font.cpp", Loading
libs/hwui/hwui/AnimatedImageDrawable.cpp +167 −80 Original line number Diff line number Diff line Loading @@ -15,21 +15,21 @@ */ #include "AnimatedImageDrawable.h" #include "AnimatedImageThread.h" #include "thread/Task.h" #include "thread/TaskManager.h" #include "thread/TaskProcessor.h" #include "utils/TraceUtils.h" #include <SkPicture.h> #include <SkRefCnt.h> #include <SkTime.h> #include <SkTLazy.h> #include <SkTime.h> namespace android { AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage) : mSkAnimatedImage(std::move(animatedImage)) { } : mSkAnimatedImage(std::move(animatedImage)) { mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration(); } void AnimatedImageDrawable::syncProperties() { mAlpha = mStagingAlpha; Loading @@ -37,88 +37,78 @@ void AnimatedImageDrawable::syncProperties() { } bool AnimatedImageDrawable::start() { SkAutoExclusive lock(mLock); if (mSkAnimatedImage->isRunning()) { if (mRunning) { return false; } if (!mSnapshot) { mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); } // While stopped, update() does not decode, but it does advance the time. // This prevents us from skipping ahead when we resume. const double currentTime = SkTime::GetMSecs(); mSkAnimatedImage->update(currentTime); mSkAnimatedImage->start(); return mSkAnimatedImage->isRunning(); mRunning = true; return true; } void AnimatedImageDrawable::stop() { SkAutoExclusive lock(mLock); mSkAnimatedImage->stop(); mRunning = false; } bool AnimatedImageDrawable::isRunning() { return mSkAnimatedImage->isRunning(); return mRunning; } // This is really a Task<void> but that doesn't really work when Future<> // expects to be able to get/set a value class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> { public: AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable) : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {} sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable; bool mIsCompleted = false; }; bool AnimatedImageDrawable::nextSnapshotReady() const { return mNextSnapshot.valid() && mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> { public: explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager) : uirenderer::TaskProcessor<bool>(taskManager) {} ~AnimatedImageTaskProcessor() {} // Only called on the RenderThread while UI thread is locked. bool AnimatedImageDrawable::isDirty() { const double currentTime = SkTime::GetMSecs(); const double lastWallTime = mLastWallTime; virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override { ATRACE_NAME("Updating AnimatedImageDrawables"); AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get()); t->mAnimatedImageDrawable->update(); t->mIsCompleted = true; task->setResult(true); }; }; mLastWallTime = currentTime; if (!mRunning) { mDidDraw = false; return false; } void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) { if (!mSkAnimatedImage->isRunning() || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) { return; std::unique_lock lock{mSwapLock}; if (mDidDraw) { mCurrentTime += currentTime - lastWallTime; mDidDraw = false; } if (!mDecodeTaskProcessor.get()) { mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager); if (!mNextSnapshot.valid()) { // Need to trigger onDraw in order to start decoding the next frame. return true; } // TODO get one frame ahead and only schedule updates when you need to replenish mDecodeTask = new AnimatedImageTask(this); mDecodeTaskProcessor->add(mDecodeTask); return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot; } void AnimatedImageDrawable::update() { SkAutoExclusive lock(mLock); // Only called on the AnimatedImageThread. AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; snap.mDuration = mSkAnimatedImage->decodeNextFrame(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } if (!mSkAnimatedImage->isRunning()) { return; return snap; } const double currentTime = SkTime::GetMSecs(); if (currentTime >= mNextFrameTime) { mNextFrameTime = mSkAnimatedImage->update(currentTime); mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); mIsDirty = true; // Only called on the AnimatedImageThread. AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { Snapshot snap; { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); snap.mDuration = mSkAnimatedImage->currentFrameDuration(); } return snap; } // Only called on the RenderThread. void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { SkTLazy<SkPaint> lazyPaint; if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) { Loading @@ -128,25 +118,71 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality); } SkAutoExclusive lock(mLock); if (mSnapshot) { canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull()); } else { // TODO: we could potentially keep the cached surface around if there is a paint and we know // the drawable is attached to the view system mDidDraw = true; bool drewDirectly = false; if (!mSnapshot.mPic) { // The image is not animating, and never was. Draw directly from // mSkAnimatedImage. SkAutoCanvasRestore acr(canvas, false); if (lazyPaint.isValid()) { canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get()); } std::unique_lock lock{mImageLock}; mSkAnimatedImage->draw(canvas); drewDirectly = true; } if (mRunning && mFinished) { auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.reset(sk_ref_sp(this)); mFinished = false; } bool finalFrame = false; if (mRunning && nextSnapshotReady()) { std::unique_lock lock{mSwapLock}; if (mCurrentTime >= mTimeToShowNextSnapshot) { mSnapshot = mNextSnapshot.get(); const double timeToShowCurrentSnap = mTimeToShowNextSnapshot; if (mSnapshot.mDuration == SkAnimatedImage::kFinished) { finalFrame = true; mRunning = false; mFinished = true; } else { mTimeToShowNextSnapshot += mSnapshot.mDuration; if (mCurrentTime >= mTimeToShowNextSnapshot) { // This would mean showing the current frame very briefly. It's // possible that not being displayed for a time resulted in // mCurrentTime being far ahead. Prevent showing many frames // rapidly by going back to the beginning of this frame time. mCurrentTime = timeToShowCurrentSnap; } } } } if (mRunning && !mNextSnapshot.valid()) { auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); } mIsDirty = false; if (!drewDirectly) { // No other thread will modify mCurrentSnap so this should be safe to // use without locking. canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull()); } if (finalFrame) { if (mEndListener) { mEndListener->onAnimationEnd(); mEndListener = nullptr; } } } double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { // update the drawable with the current time double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs()); SkAutoCanvasRestore acr(canvas, false); if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) { SkPaint paint; Loading @@ -154,8 +190,59 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { paint.setColorFilter(mStagingColorFilter); canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint); } if (mFinished && !mRunning) { // Continue drawing the last frame, and return 0 to indicate no need to // redraw. std::unique_lock lock{mImageLock}; canvas->drawDrawable(mSkAnimatedImage.get()); return 0.0; } bool update = false; { const double currentTime = SkTime::GetMSecs(); std::unique_lock lock{mSwapLock}; // mLastWallTime starts off at 0. If it is still 0, just update it to // the current time and avoid updating if (mLastWallTime == 0.0) { mCurrentTime = currentTime; } else if (mRunning) { if (mFinished) { mCurrentTime = currentTime; { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); } mTimeToShowNextSnapshot = currentTime + mSkAnimatedImage->currentFrameDuration(); } else { mCurrentTime += currentTime - mLastWallTime; update = mCurrentTime >= mTimeToShowNextSnapshot; } } mLastWallTime = currentTime; } double duration = 0.0; { std::unique_lock lock{mImageLock}; if (update) { duration = mSkAnimatedImage->decodeNextFrame(); } canvas->drawDrawable(mSkAnimatedImage.get()); return nextUpdate; } }; // namespace android std::unique_lock lock{mSwapLock}; if (update) { if (duration == SkAnimatedImage::kFinished) { mRunning = false; mFinished = true; } else { mTimeToShowNextSnapshot += duration; } } return mTimeToShowNextSnapshot; } } // namespace android
libs/hwui/hwui/AnimatedImageDrawable.h +63 −31 Original line number Diff line number Diff line Loading @@ -17,22 +17,20 @@ #pragma once #include <cutils/compiler.h> #include <utils/Macros.h> #include <utils/RefBase.h> #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> #include <SkMutex.h> #include <SkPicture.h> class SkPicture; #include <future> #include <mutex> namespace android { namespace uirenderer { class TaskManager; } class OnAnimationEndListener { public: virtual ~OnAnimationEndListener() {} Loading @@ -41,68 +39,102 @@ public: }; /** * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread. * Native component of android.graphics.drawable.AnimatedImageDrawables.java. * This class can be drawn into Canvas.h and maintains the state needed to drive * the animation from the RenderThread. */ class ANDROID_API AnimatedImageDrawable : public SkDrawable { public: AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage); /** * This returns true if the animation has updated and signals that the next draw will contain * new content. * This updates the internal time and returns true if the animation needs * to be redrawn. * * This is called on RenderThread, while the UI thread is locked. */ bool isDirty() const { return mIsDirty; } bool isDirty(); int getStagingAlpha() const { return mStagingAlpha; } void setStagingAlpha(int alpha) { mStagingAlpha = alpha; } void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; } void syncProperties(); virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } // Draw to software canvas, and return time to next draw. double drawStaging(SkCanvas* canvas); // Returns true if the animation was started; false otherwise (e.g. it was already running) // Returns true if the animation was started; false otherwise (e.g. it was // already running) bool start(); void stop(); bool isRunning(); void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) { mEndListener = std::move(listener); } void scheduleUpdate(uirenderer::TaskManager* taskManager); void markInvisible() { mDidDraw = false; } struct Snapshot { sk_sp<SkPicture> mPic; int mDuration; Snapshot() = default; Snapshot(Snapshot&&) = default; Snapshot& operator=(Snapshot&&) = default; PREVENT_COPY_AND_ASSIGN(Snapshot); }; // These are only called on AnimatedImageThread. Snapshot decodeNextFrame(); Snapshot reset(); protected: virtual void onDraw(SkCanvas* canvas) override; private: void update(); sk_sp<SkAnimatedImage> mSkAnimatedImage; sk_sp<SkPicture> mSnapshot; SkMutex mLock; bool mRunning = false; bool mFinished = false; // A snapshot of the current frame to draw. Snapshot mSnapshot; std::future<Snapshot> mNextSnapshot; bool nextSnapshotReady() const; // When to switch from mSnapshot to mNextSnapshot. double mTimeToShowNextSnapshot = 0.0; // The current time for the drawable itself. double mCurrentTime = 0.0; // The wall clock of the last time we called isDirty. double mLastWallTime = 0.0; // Whether we drew since the last call to isDirty. bool mDidDraw = false; // Locked when assigning snapshots and times. Operations while this is held // should be short. std::mutex mSwapLock; // Locked when mSkAnimatedImage is being updated or drawn. std::mutex mImageLock; int mStagingAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mStagingColorFilter; int mAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mColorFilter; double mNextFrameTime = 0.0; bool mIsDirty = false; class AnimatedImageTask; class AnimatedImageTaskProcessor; sp<AnimatedImageTask> mDecodeTask; sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor; std::unique_ptr<OnAnimationEndListener> mEndListener; }; }; // namespace android } // namespace android