340 lines
11 KiB
C++
340 lines
11 KiB
C++
/*
|
|
* 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)
|
|
: mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
|
|
mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->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(mSkAnimatedImage->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 = mSkAnimatedImage->decodeNextFrame();
|
|
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
|
|
}
|
|
|
|
return snap;
|
|
}
|
|
|
|
// Only called on the AnimatedImageThread.
|
|
AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
|
|
Snapshot snap;
|
|
{
|
|
std::unique_lock lock{mImageLock};
|
|
mSkAnimatedImage->reset();
|
|
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
|
|
snap.mDurationMS = mSkAnimatedImage->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 = mSkAnimatedImage->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 = 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();
|
|
}
|
|
|
|
} // namespace android
|