aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/android/SkAnimatedImage.h49
-rw-r--r--samplecode/SampleAnimatedImage.cpp29
-rw-r--r--src/android/SkAnimatedImage.cpp96
-rw-r--r--tests/AnimatedImageTest.cpp131
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);