diff options
author | Leon Scroggins III <scroggo@google.com> | 2018-01-29 19:35:55 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-01-30 18:46:35 +0000 |
commit | 495e0f079ced1d2d23368de263f50232eca1ab4a (patch) | |
tree | d9aaf4b24c48c71e4a71b94cc0ecbaacc8a52772 | |
parent | 4e95956f117f8c188b565e7baefc252085b02653 (diff) |
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>
-rw-r--r-- | include/android/SkAnimatedImage.h | 49 | ||||
-rw-r--r-- | samplecode/SampleAnimatedImage.cpp | 29 | ||||
-rw-r--r-- | src/android/SkAnimatedImage.cpp | 96 | ||||
-rw-r--r-- | tests/AnimatedImageTest.cpp | 131 |
4 files changed, 121 insertions, 184 deletions
diff --git a/include/android/SkAnimatedImage.h b/include/android/SkAnimatedImage.h index 4a61a8b62c..51f0e5b84b 100644 --- a/include/android/SkAnimatedImage.h +++ b/include/android/SkAnimatedImage.h @@ -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. @@ -43,30 +43,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. * * Returns true after all repetitions are complete, or an error stops the @@ -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; diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp index 5c02d29bbe..bea601860c 100644 --- a/samplecode/SampleAnimatedImage.cpp +++ b/samplecode/SampleAnimatedImage.cpp @@ -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; }; diff --git a/src/android/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp index 2989ade01d..2b63f035b2 100644 --- a/src/android/SkAnimatedImage.cpp +++ b/src/android/SkAnimatedImage.cpp @@ -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) { diff --git a/tests/AnimatedImageTest.cpp b/tests/AnimatedImageTest.cpp index 95d7fc6d2e..07a63474cf 100644 --- a/tests/AnimatedImageTest.cpp +++ b/tests/AnimatedImageTest.cpp @@ -113,38 +113,25 @@ DEF_TEST(AnimatedImage, r) { return true; }; - REPORTER_ASSERT(r, !animatedImage->isRunning()); - if (!testDraw(animatedImage, 0)) { - ERRORF(r, "Did not start with frame 0"); - continue; - } + REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration); - animatedImage->start(); - REPORTER_ASSERT(r, animatedImage->isRunning()); if (!testDraw(animatedImage, 0)) { - ERRORF(r, "After starting, still not on frame 0"); + ERRORF(r, "Did not start with 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); |