blob: 27773a60355a47cd342065a1082fa5618dc5716d [file] [log] [blame]
/*
* 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