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:
parent
4e95956f11
commit
495e0f079c
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user