Simplify SkAnimatedImage

Bug: b/63908092

Rather than keeping track of the time and whether the animation is
running, leave that up to the client. Offer a single method to decode
the next frame, allowing the client to stay one frame ahead.

Change-Id: I546013e32e3a0874181b0dce1349bbec07aaadd4
Reviewed-on: https://skia-review.googlesource.com/101544
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Leon Scroggins <scroggo@google.com>
This commit is contained in:
Leon Scroggins III 2018-01-29 19:35:55 -05:00 committed by Skia Commit-Bot
parent 4e95956f11
commit 495e0f079c
4 changed files with 121 additions and 184 deletions

View File

@ -26,7 +26,7 @@ public:
* Create an SkAnimatedImage from the SkAndroidCodec.
*
* Returns null on failure to allocate pixels. On success, this will
* decode the first frame. It will not animate until start() is called.
* decode the first frame.
*
* @param scaledSize Size to draw the image, possibly requiring scaling.
* @param cropRect Rectangle to crop to after scaling.
@ -42,30 +42,11 @@ public:
~SkAnimatedImage() override;
/**
* Start or resume the animation. update() must be called to advance the
* time.
*/
void start();
/**
* Stop the animation. update() has no effect while the animation is
* stopped.
*/
void stop();
/**
* Reset the animation to the beginning.
*/
void reset();
/**
* Whether the animation is active.
*
* If true, update() can be called to animate.
*/
bool isRunning() const { return fRunning && !fFinished; }
/**
* Whether the animation completed.
*
@ -75,18 +56,28 @@ public:
bool isFinished() const { return fFinished; }
/**
* Returned by update if the animation is not running.
* Returned by decodeNextFrame and currentFrameDuration if the animation
* is not running.
*/
static constexpr double kNotRunning = -2.0;
static constexpr int kFinished = -1;
/**
* Update the current time. If the image is animating, this may decode
* a new frame.
* Decode the next frame.
*
* @return the time to show the next frame, or kNotRunning if the animation
* is not running.
* If the animation is on the last frame or has hit an error, returns
* kFinished.
*/
double update(double msecs);
int decodeNextFrame();
/**
* How long to display the current frame.
*
* Useful for the first frame, for which decodeNextFrame is called
* internally.
*/
int currentFrameDuration() {
return fCurrentFrameDuration;
}
/**
* Change the repetition count.
@ -130,9 +121,7 @@ private:
SkMatrix fMatrix; // used only if !fSimple
bool fFinished;
bool fRunning;
double fNowMS;
double fRemainingMS;
int fCurrentFrameDuration;
Frame fActiveFrame;
Frame fRestoreFrame;
int fRepetitionCount;

View File

@ -62,7 +62,19 @@ protected:
return false;
}
fImage->update(animTimer.msec());
const double lastWallTime = fLastWallTime;
fLastWallTime = animTimer.msec();
if (fRunning) {
fCurrentTime += fLastWallTime - lastWallTime;
if (fCurrentTime > fTimeToShowNextFrame) {
fTimeToShowNextFrame += fImage->decodeNextFrame();
if (fImage->isFinished()) {
fRunning = false;
}
}
}
return true;
}
@ -78,6 +90,7 @@ protected:
return;
}
fTimeToShowNextFrame = fImage->currentFrameDuration();
SkPictureRecorder recorder;
auto canvas = recorder.beginRecording(fImage->getBounds());
canvas->drawDrawable(fImage.get());
@ -94,14 +107,16 @@ protected:
if (fImage && SampleCode::CharQ(*evt, &uni)) {
switch (uni) {
case kPauseKey:
if (fImage->isRunning()) {
fImage->stop();
fRunning = !fRunning;
if (fImage->isFinished()) {
// fall through
} else {
fImage->start();
return true;
}
return true;
case kResetKey:
fImage->reset();
fCurrentTime = fLastWallTime;
fTimeToShowNextFrame = fCurrentTime + fImage->currentFrameDuration();
return true;
default:
break;
@ -114,6 +129,10 @@ private:
sk_sp<SkAnimatedImage> fImage;
sk_sp<SkDrawable> fDrawable;
SkScalar fYOffset;
bool fRunning = false;
double fCurrentTime = 0.0;
double fLastWallTime = 0.0;
double fTimeToShowNextFrame = 0.0;
typedef SampleView INHERITED;
};

View File

@ -58,9 +58,6 @@ sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> cod
return image;
}
// Sentinel value for starting at the beginning.
static constexpr double kInit = -1.0;
SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize scaledSize,
SkImageInfo decodeInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess)
: fCodec(std::move(codec))
@ -72,9 +69,6 @@ SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize
, fSimple(fScaledSize == fDecodeInfo.dimensions() && !fPostProcess
&& fCropRect == fDecodeInfo.bounds())
, fFinished(false)
, fRunning(false)
, fNowMS(kInit)
, fRemainingMS(kInit)
, fRepetitionCount(fCodec->codec()->getRepetitionCount())
, fRepetitionsCompleted(0)
{
@ -88,7 +82,7 @@ SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize
float scaleY = (float) fScaledSize.height() / fDecodeInfo.height();
fMatrix.preConcat(SkMatrix::MakeScale(scaleX, scaleY));
}
this->update(kInit);
this->decodeNextFrame();
}
SkAnimatedImage::~SkAnimatedImage() { }
@ -114,21 +108,22 @@ bool SkAnimatedImage::Frame::copyTo(Frame* dst) const {
return true;
}
void SkAnimatedImage::start() {
fRunning = true;
if (fFinished) {
this->reset();
}
}
void SkAnimatedImage::stop() {
fRunning = false;
}
void SkAnimatedImage::reset() {
fFinished = false;
fRepetitionsCompleted = 0;
this->update(kInit);
if (fActiveFrame.fIndex == 0) {
// Already showing the first frame.
return;
}
if (fRestoreFrame.fIndex == 0) {
SkTSwap(fActiveFrame, fRestoreFrame);
// Now we're showing the first frame.
return;
}
fActiveFrame.fIndex = SkCodec::kNone;
this->decodeNextFrame();
}
static bool is_restore_previous(SkCodecAnimation::DisposalMethod dispose) {
@ -155,40 +150,17 @@ int SkAnimatedImage::computeNextFrame(int current, bool* animationEnded) {
double SkAnimatedImage::finish() {
fFinished = true;
fRunning = false;
return kNotRunning;
fCurrentFrameDuration = kFinished;
return kFinished;
}
double SkAnimatedImage::update(double msecs) {
int SkAnimatedImage::decodeNextFrame() {
if (fFinished) {
return kNotRunning;
return kFinished;
}
const double lastUpdateMS = fNowMS;
fNowMS = msecs;
const double msSinceLastUpdate = fNowMS - lastUpdateMS;
bool animationEnded = false;
int frameToDecode = SkCodec::kNone;
if (kInit == msecs) {
frameToDecode = 0;
fNowMS = lastUpdateMS;
} else {
if (!fRunning) {
return kNotRunning;
}
if (lastUpdateMS == kInit) {
return fRemainingMS + fNowMS;
}
if (msSinceLastUpdate < fRemainingMS) {
fRemainingMS -= msSinceLastUpdate;
return fRemainingMS + fNowMS;
} else {
frameToDecode = this->computeNextFrame(fActiveFrame.fIndex, &animationEnded);
}
}
int frameToDecode = this->computeNextFrame(fActiveFrame.fIndex, &animationEnded);
SkCodec::FrameInfo frameInfo;
if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
@ -197,28 +169,7 @@ double SkAnimatedImage::update(double msecs) {
return this->finish();
}
if (kInit == msecs) {
fRemainingMS = frameInfo.fDuration;
} else {
// Check to see whether we should skip this frame.
double pastUpdate = msSinceLastUpdate - fRemainingMS;
while (pastUpdate >= frameInfo.fDuration) {
SkCodecPrintf("Skipping frame %i\n", frameToDecode);
pastUpdate -= frameInfo.fDuration;
frameToDecode = computeNextFrame(frameToDecode, &animationEnded);
if (!fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
SkCodecPrintf("Could not getFrameInfo for frame %i",
frameToDecode);
// Prior call to getFrameInfo succeeded, so use that one.
frameToDecode--;
animationEnded = true;
if (frameToDecode < 0) {
return this->finish();
}
}
}
fRemainingMS = frameInfo.fDuration - pastUpdate;
}
fCurrentFrameDuration = frameInfo.fDuration;
} else {
animationEnded = true;
if (0 == frameToDecode) {
@ -228,6 +179,7 @@ double SkAnimatedImage::update(double msecs) {
// These fields won't be read.
frameInfo.fDuration = INT_MAX;
frameInfo.fFullyReceived = true;
fCurrentFrameDuration = kFinished;
} else {
SkCodecPrintf("Error getting frameInfo for frame %i\n",
frameToDecode);
@ -239,7 +191,7 @@ double SkAnimatedImage::update(double msecs) {
if (animationEnded) {
return this->finish();
}
return fRemainingMS + fNowMS;
return fCurrentFrameDuration;
}
if (frameToDecode == fRestoreFrame.fIndex) {
@ -247,7 +199,7 @@ double SkAnimatedImage::update(double msecs) {
if (animationEnded) {
return this->finish();
}
return fRemainingMS + fNowMS;
return fCurrentFrameDuration;
}
// The following code makes an effort to avoid overwriting a frame that will
@ -319,7 +271,7 @@ double SkAnimatedImage::update(double msecs) {
if (animationEnded) {
return this->finish();
}
return fRemainingMS + fNowMS;
return fCurrentFrameDuration;
}
void SkAnimatedImage::onDraw(SkCanvas* canvas) {

View File

@ -113,38 +113,25 @@ DEF_TEST(AnimatedImage, r) {
return true;
};
REPORTER_ASSERT(r, !animatedImage->isRunning());
REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration);
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "Did not start with frame 0");
continue;
}
animatedImage->start();
REPORTER_ASSERT(r, animatedImage->isRunning());
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "After starting, still not on frame 0");
continue;
}
// Start at an arbitrary time.
double currentTime = 100000;
bool failed = false;
for (size_t i = 0; i < frameInfos.size(); ++i) {
double next = animatedImage->update(currentTime);
for (size_t i = 1; i < frameInfos.size(); ++i) {
const int frameTime = animatedImage->decodeNextFrame();
REPORTER_ASSERT(r, frameTime == animatedImage->currentFrameDuration());
if (i == frameInfos.size() - 1 && defaultRepetitionCount == 0) {
REPORTER_ASSERT(r, next == SkAnimatedImage::kNotRunning);
REPORTER_ASSERT(r, !animatedImage->isRunning());
REPORTER_ASSERT(r, frameTime == SkAnimatedImage::kFinished);
REPORTER_ASSERT(r, animatedImage->isFinished());
} else {
REPORTER_ASSERT(r, animatedImage->isRunning());
REPORTER_ASSERT(r, frameTime == frameInfos[i].fDuration);
REPORTER_ASSERT(r, !animatedImage->isFinished());
double expectedNext = currentTime + frameInfos[i].fDuration;
if (next != expectedNext) {
ERRORF(r, "Time did not match for frame %i: next: %g expected: %g",
i, next, expectedNext);
failed = true;
break;
}
}
if (!testDraw(animatedImage, i)) {
@ -152,91 +139,81 @@ DEF_TEST(AnimatedImage, r) {
failed = true;
break;
}
// Update, but not to the next frame.
REPORTER_ASSERT(r, animatedImage->update((next - currentTime) / 2) == next);
if (!testDraw(animatedImage, i)) {
ERRORF(r, "Should still be on frame %i", i);
failed = true;
break;
}
currentTime = next;
}
if (failed) {
continue;
}
// Create a new animated image and test stop.
animatedImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(
SkCodec::MakeFromData(data)));
animatedImage->reset();
REPORTER_ASSERT(r, !animatedImage->isFinished());
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "reset failed");
continue;
}
animatedImage->start();
currentTime = 100000;
// Do not go to the last frame, so it should still be running after.
for (size_t i = 0; i < frameInfos.size() - 1; ++i) {
double next = animatedImage->update(currentTime);
if (!testDraw(animatedImage, i)) {
ERRORF(r, "Error during stop tests.");
failed = true;
// Test reset from all the frames.
// j is the frame to call reset on.
for (int j = 0; j < (int) frameInfos.size(); ++j) {
if (failed) {
break;
}
double interval = next - currentTime;
animatedImage->stop();
REPORTER_ASSERT(r, !animatedImage->isRunning());
REPORTER_ASSERT(r, !animatedImage->isFinished());
currentTime = next;
double stoppedNext = animatedImage->update(currentTime);
REPORTER_ASSERT(r, stoppedNext == SkAnimatedImage::kNotRunning);
if (!testDraw(animatedImage, i)) {
ERRORF(r, "Advanced the frame while stopped?");
failed = true;
break;
// i is the frame to decode.
for (int i = 0; i <= j; ++i) {
if (i == j) {
animatedImage->reset();
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "reset failed for image %s from frame %i",
file, i);
failed = true;
break;
}
} else if (i != 0) {
animatedImage->decodeNextFrame();
if (!testDraw(animatedImage, i)) {
ERRORF(r, "failed to match frame %i in %s on iteration %i",
i, file, j);
failed = true;
break;
}
}
}
animatedImage->start();
currentTime += interval;
}
if (failed) {
return;
}
REPORTER_ASSERT(r, animatedImage->isRunning());
REPORTER_ASSERT(r, !animatedImage->isFinished());
animatedImage->reset();
if (!testDraw(animatedImage, 0)) {
ERRORF(r, "reset failed");
continue;
}
for (int loopCount : { 0, 1, 2, 5 }) {
animatedImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(
SkCodec::MakeFromData(data)));
animatedImage->start();
animatedImage->setRepetitionCount(loopCount);
REPORTER_ASSERT(r, animatedImage->getRepetitionCount() == loopCount);
for (int loops = 0; loops <= loopCount; loops++) {
REPORTER_ASSERT(r, animatedImage->isRunning());
if (failed) {
break;
}
REPORTER_ASSERT(r, !animatedImage->isFinished());
for (size_t i = 0; i < frameInfos.size(); ++i) {
double next = animatedImage->update(currentTime);
if (animatedImage->isRunning()) {
currentTime = next;
} else {
REPORTER_ASSERT(r, next == SkAnimatedImage::kNotRunning);
for (size_t i = 1; i <= frameInfos.size(); ++i) {
const int frameTime = animatedImage->decodeNextFrame();
if (frameTime == SkAnimatedImage::kFinished) {
if (loops != loopCount) {
ERRORF(r, "%s animation stopped early: loops: %i\tloopCount: %i",
file, loops, loopCount);
failed = true;
}
if (i != frameInfos.size() - 1) {
ERRORF(r, "%s animation stopped early: i: %i\tsize: %i",
file, i, frameInfos.size());
failed = true;
}
break;
}
}
}
if (animatedImage->isRunning()) {
ERRORF(r, "%s animation still running after %i loops", file, loopCount);
}
if (!animatedImage->isFinished()) {
ERRORF(r, "%s animation should have finished with specified loop count (%i)",
file, loopCount);