| /* |
| * Copyright (C) 2018 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. |
| */ |
| |
| #include "AnimatedImageDrawable.h" |
| #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread |
| #include "AnimatedImageThread.h" |
| #endif |
| |
| #include <gui/TraceUtils.h> |
| #include "pipeline/skia/SkiaUtils.h" |
| |
| #include <SkPicture.h> |
| #include <SkRefCnt.h> |
| |
| #include <optional> |
| |
| namespace android { |
| |
| AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, |
| SkEncodedImageFormat format) |
| : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) { |
| mTimeToShowNextSnapshot = ms2ns(currentFrameDuration()); |
| setStagingBounds(mSkAnimatedImage->getBounds()); |
| } |
| |
| void AnimatedImageDrawable::syncProperties() { |
| mProperties = mStagingProperties; |
| } |
| |
| bool AnimatedImageDrawable::start() { |
| if (mRunning) { |
| return false; |
| } |
| |
| mStarting = true; |
| |
| mRunning = true; |
| return true; |
| } |
| |
| bool AnimatedImageDrawable::stop() { |
| bool wasRunning = mRunning; |
| mRunning = false; |
| return wasRunning; |
| } |
| |
| bool AnimatedImageDrawable::isRunning() { |
| return mRunning; |
| } |
| |
| bool AnimatedImageDrawable::nextSnapshotReady() const { |
| return mNextSnapshot.valid() && |
| mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready; |
| } |
| |
| // Only called on the RenderThread while UI thread is locked. |
| bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) { |
| *outDelay = 0; |
| const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| const nsecs_t lastWallTime = mLastWallTime; |
| |
| mLastWallTime = currentTime; |
| if (!mRunning) { |
| return false; |
| } |
| |
| std::unique_lock lock{mSwapLock}; |
| mCurrentTime += currentTime - lastWallTime; |
| |
| if (!mNextSnapshot.valid()) { |
| // Need to trigger onDraw in order to start decoding the next frame. |
| *outDelay = mTimeToShowNextSnapshot - mCurrentTime; |
| return true; |
| } |
| |
| if (mTimeToShowNextSnapshot > mCurrentTime) { |
| *outDelay = mTimeToShowNextSnapshot - mCurrentTime; |
| } else if (nextSnapshotReady()) { |
| // We have not yet updated mTimeToShowNextSnapshot. Read frame duration |
| // directly from mSkAnimatedImage. |
| lock.unlock(); |
| std::unique_lock imageLock{mImageLock}; |
| *outDelay = ms2ns(currentFrameDuration()); |
| return true; |
| } else { |
| // The next snapshot has not yet been decoded, but we've already passed |
| // time to draw it. There's not a good way to know when decoding will |
| // finish, so request an update immediately. |
| *outDelay = 0; |
| } |
| |
| return false; |
| } |
| |
| // Only called on the AnimatedImageThread. |
| AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { |
| Snapshot snap; |
| { |
| std::unique_lock lock{mImageLock}; |
| snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); |
| snap.mPic = mSkAnimatedImage->makePictureSnapshot(); |
| } |
| |
| return snap; |
| } |
| |
| // Only called on the AnimatedImageThread. |
| AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { |
| Snapshot snap; |
| { |
| std::unique_lock lock{mImageLock}; |
| mSkAnimatedImage->reset(); |
| snap.mPic = mSkAnimatedImage->makePictureSnapshot(); |
| snap.mDurationMS = currentFrameDuration(); |
| } |
| |
| return snap; |
| } |
| |
| // Update the matrix to map from the intrinsic bounds of the SkAnimatedImage to |
| // the bounds specified by Drawable#setBounds. |
| static void handleBounds(SkMatrix* matrix, const SkRect& intrinsicBounds, const SkRect& bounds) { |
| matrix->preTranslate(bounds.left(), bounds.top()); |
| matrix->preScale(bounds.width() / intrinsicBounds.width(), |
| bounds.height() / intrinsicBounds.height()); |
| } |
| |
| // Only called on the RenderThread. |
| void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { |
| // Store the matrix used to handle bounds and mirroring separate from the |
| // canvas. We may need to invert the matrix to determine the proper bounds |
| // to pass to saveLayer, and this matrix (as opposed to, potentially, the |
| // canvas' matrix) only uses scale and translate, so it must be invertible. |
| SkMatrix matrix; |
| SkAutoCanvasRestore acr(canvas, true); |
| handleBounds(&matrix, mSkAnimatedImage->getBounds(), mProperties.mBounds); |
| |
| if (mProperties.mMirrored) { |
| matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0); |
| matrix.preScale(-1, 1); |
| } |
| |
| std::optional<SkPaint> lazyPaint; |
| if (mProperties.mAlpha != SK_AlphaOPAQUE || mProperties.mColorFilter.get()) { |
| lazyPaint.emplace(); |
| lazyPaint->setAlpha(mProperties.mAlpha); |
| lazyPaint->setColorFilter(mProperties.mColorFilter); |
| } |
| |
| canvas->concat(matrix); |
| |
| const bool starting = mStarting; |
| mStarting = false; |
| |
| const bool drawDirectly = !mSnapshot.mPic; |
| if (drawDirectly) { |
| // The image is not animating, and never was. Draw directly from |
| // mSkAnimatedImage. |
| if (lazyPaint) { |
| SkMatrix inverse; |
| (void) matrix.invert(&inverse); |
| SkRect r = mProperties.mBounds; |
| inverse.mapRect(&r); |
| canvas->saveLayer(r, &*lazyPaint); |
| } |
| |
| std::unique_lock lock{mImageLock}; |
| mSkAnimatedImage->draw(canvas); |
| if (!mRunning) { |
| return; |
| } |
| } else if (starting) { |
| // The image has animated, and now is being reset. Queue up the first |
| // frame, but keep showing the current frame until the first is ready. |
| #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread |
| auto& thread = uirenderer::AnimatedImageThread::getInstance(); |
| mNextSnapshot = thread.reset(sk_ref_sp(this)); |
| #endif |
| } |
| |
| bool finalFrame = false; |
| if (mRunning && nextSnapshotReady()) { |
| std::unique_lock lock{mSwapLock}; |
| if (mCurrentTime >= mTimeToShowNextSnapshot) { |
| mSnapshot = mNextSnapshot.get(); |
| const nsecs_t timeToShowCurrentSnap = mTimeToShowNextSnapshot; |
| if (mSnapshot.mDurationMS == SkAnimatedImage::kFinished) { |
| finalFrame = true; |
| mRunning = false; |
| } else { |
| mTimeToShowNextSnapshot += ms2ns(mSnapshot.mDurationMS); |
| 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()) { |
| #ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread |
| auto& thread = uirenderer::AnimatedImageThread::getInstance(); |
| mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); |
| #endif |
| } |
| |
| if (!drawDirectly) { |
| // No other thread will modify mCurrentSnap so this should be safe to |
| // use without locking. |
| canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint ? &*lazyPaint : nullptr); |
| } |
| |
| if (finalFrame) { |
| if (mEndListener) { |
| mEndListener->onAnimationEnd(); |
| } |
| } |
| } |
| |
| int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { |
| // Store the matrix used to handle bounds and mirroring separate from the |
| // canvas. We may need to invert the matrix to determine the proper bounds |
| // to pass to saveLayer, and this matrix (as opposed to, potentially, the |
| // canvas' matrix) only uses scale and translate, so it must be invertible. |
| SkMatrix matrix; |
| SkAutoCanvasRestore acr(canvas, true); |
| handleBounds(&matrix, mSkAnimatedImage->getBounds(), mStagingProperties.mBounds); |
| |
| if (mStagingProperties.mMirrored) { |
| matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0); |
| matrix.preScale(-1, 1); |
| } |
| |
| canvas->concat(matrix); |
| |
| if (mStagingProperties.mAlpha != SK_AlphaOPAQUE || mStagingProperties.mColorFilter.get()) { |
| SkPaint paint; |
| paint.setAlpha(mStagingProperties.mAlpha); |
| paint.setColorFilter(mStagingProperties.mColorFilter); |
| |
| SkMatrix inverse; |
| (void) matrix.invert(&inverse); |
| SkRect r = mStagingProperties.mBounds; |
| inverse.mapRect(&r); |
| canvas->saveLayer(r, &paint); |
| } |
| |
| if (!mRunning) { |
| // Continue drawing the current frame, and return 0 to indicate no need |
| // to redraw. |
| std::unique_lock lock{mImageLock}; |
| canvas->drawDrawable(mSkAnimatedImage.get()); |
| return 0; |
| } |
| |
| if (mStarting) { |
| mStarting = false; |
| int durationMS = 0; |
| { |
| std::unique_lock lock{mImageLock}; |
| mSkAnimatedImage->reset(); |
| durationMS = currentFrameDuration(); |
| } |
| { |
| std::unique_lock lock{mSwapLock}; |
| mLastWallTime = 0; |
| // The current time will be added later, below. |
| mTimeToShowNextSnapshot = ms2ns(durationMS); |
| } |
| } |
| |
| bool update = false; |
| { |
| const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC); |
| 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) { |
| mCurrentTime = currentTime; |
| // mTimeToShowNextSnapshot is already set to the duration of the |
| // first frame. |
| mTimeToShowNextSnapshot += currentTime; |
| } else if (mRunning) { |
| mCurrentTime += currentTime - mLastWallTime; |
| update = mCurrentTime >= mTimeToShowNextSnapshot; |
| } |
| mLastWallTime = currentTime; |
| } |
| |
| int durationMS = 0; |
| { |
| std::unique_lock lock{mImageLock}; |
| if (update) { |
| durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); |
| } |
| |
| canvas->drawDrawable(mSkAnimatedImage.get()); |
| } |
| |
| std::unique_lock lock{mSwapLock}; |
| if (update) { |
| if (durationMS == SkAnimatedImage::kFinished) { |
| mRunning = false; |
| return SkAnimatedImage::kFinished; |
| } |
| |
| const nsecs_t timeToShowCurrentSnapshot = mTimeToShowNextSnapshot; |
| mTimeToShowNextSnapshot += ms2ns(durationMS); |
| if (mCurrentTime >= mTimeToShowNextSnapshot) { |
| // As in onDraw, prevent speedy catch-up behavior. |
| mCurrentTime = timeToShowCurrentSnapshot; |
| } |
| } |
| |
| return ns2ms(mTimeToShowNextSnapshot - mCurrentTime); |
| } |
| |
| SkRect AnimatedImageDrawable::onGetBounds() { |
| // This must return a bounds that is valid for all possible states, |
| // including if e.g. the client calls setBounds. |
| return SkRectMakeLargest(); |
| } |
| |
| int AnimatedImageDrawable::adjustFrameDuration(int durationMs) { |
| if (durationMs == SkAnimatedImage::kFinished) { |
| return SkAnimatedImage::kFinished; |
| } |
| |
| if (mFormat == SkEncodedImageFormat::kGIF) { |
| // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms |
| return durationMs <= 10 ? 100 : durationMs; |
| } |
| return durationMs; |
| } |
| |
| int AnimatedImageDrawable::currentFrameDuration() { |
| return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration()); |
| } |
| |
| } // namespace android |