aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gn/tests.gni1
-rw-r--r--include/android/SkAnimatedImage.h18
-rw-r--r--samplecode/SampleAnimatedImage.cpp6
-rw-r--r--src/android/SkAnimatedImage.cpp85
-rw-r--r--tests/AnimatedImageTest.cpp229
-rw-r--r--tests/CodecAnimTest.cpp17
-rw-r--r--tests/CodecPriv.h19
7 files changed, 335 insertions, 40 deletions
diff --git a/gn/tests.gni b/gn/tests.gni
index ebe715adeb..e4519d9678 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -8,6 +8,7 @@ _tests = get_path_info("../tests", "abspath")
tests_sources = [
"$_tests/AndroidCodecTest.cpp",
+ "$_tests/AnimatedImageTest.cpp",
"$_tests/AAClipTest.cpp",
"$_tests/AnnotationTest.cpp",
"$_tests/ApplyGammaTest.cpp",
diff --git a/include/android/SkAnimatedImage.h b/include/android/SkAnimatedImage.h
index 1c1a2fc039..d74c6bec49 100644
--- a/include/android/SkAnimatedImage.h
+++ b/include/android/SkAnimatedImage.h
@@ -76,6 +76,17 @@ public:
*/
double update(double msecs);
+ /**
+ * Change the repetition count.
+ *
+ * By default, the image will repeat the number of times indicated in the
+ * encoded data.
+ *
+ * Use SkCodec::kRepetitionCountInfinite for infinite, and 0 to show all
+ * frames once and then stop.
+ */
+ void setRepetitionCount(int count);
+
protected:
SkRect onGetBounds() override;
void onDraw(SkCanvas*) override;
@@ -95,6 +106,7 @@ private:
const SkImageInfo fDecodeInfo;
const SkIRect fCropRect;
const sk_sp<SkPicture> fPostProcess;
+ const int fFrameCount;
const bool fSimple; // no crop, scale, or postprocess
SkMatrix fMatrix; // used only if !fSimple
@@ -104,9 +116,15 @@ private:
double fRemainingMS;
Frame fActiveFrame;
Frame fRestoreFrame;
+ int fRepetitionCount;
+ int fRepetitionsCompleted;
SkAnimatedImage(std::unique_ptr<SkAndroidCodec>, SkISize scaledSize,
SkImageInfo decodeInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess);
+ SkAnimatedImage(std::unique_ptr<SkAndroidCodec>);
+
+ int computeNextFrame(int current, bool* animationEnded);
+ double finish();
typedef SkDrawable INHERITED;
};
diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp
index bb1dd5a3f1..5c02d29bbe 100644
--- a/samplecode/SampleAnimatedImage.cpp
+++ b/samplecode/SampleAnimatedImage.cpp
@@ -25,7 +25,6 @@ class SampleAnimatedImage : public SampleView {
public:
SampleAnimatedImage()
: INHERITED()
- , fRunning(false)
, fYOffset(0)
{}
@@ -95,12 +94,10 @@ protected:
if (fImage && SampleCode::CharQ(*evt, &uni)) {
switch (uni) {
case kPauseKey:
- if (fRunning) {
+ if (fImage->isRunning()) {
fImage->stop();
- fRunning = false;
} else {
fImage->start();
- fRunning = true;
}
return true;
case kResetKey:
@@ -116,7 +113,6 @@ protected:
private:
sk_sp<SkAnimatedImage> fImage;
sk_sp<SkDrawable> fDrawable;
- bool fRunning;
SkScalar fYOffset;
typedef SampleView INHERITED;
};
diff --git a/src/android/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp
index 4c87083957..577aa725d2 100644
--- a/src/android/SkAnimatedImage.cpp
+++ b/src/android/SkAnimatedImage.cpp
@@ -68,12 +68,15 @@ SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize
, fDecodeInfo(decodeInfo)
, fCropRect(cropRect)
, fPostProcess(std::move(postProcess))
+ , fFrameCount(fCodec->codec()->getFrameCount())
, fSimple(fScaledSize == fDecodeInfo.dimensions() && !fPostProcess
&& fCropRect == fDecodeInfo.bounds())
, fFinished(false)
, fRunning(false)
, fNowMS(kInit)
, fRemainingMS(kInit)
+ , fRepetitionCount(fCodec->codec()->getRepetitionCount())
+ , fRepetitionsCompleted(0)
{
if (!fActiveFrame.fBitmap.tryAllocPixels(fDecodeInfo)) {
return;
@@ -113,6 +116,9 @@ bool SkAnimatedImage::Frame::copyTo(Frame* dst) const {
void SkAnimatedImage::start() {
fRunning = true;
+ if (fFinished) {
+ this->reset();
+ }
}
void SkAnimatedImage::stop() {
@@ -120,6 +126,8 @@ void SkAnimatedImage::stop() {
}
void SkAnimatedImage::reset() {
+ fFinished = false;
+ fRepetitionsCompleted = 0;
this->update(kInit);
}
@@ -127,6 +135,30 @@ static bool is_restore_previous(SkCodecAnimation::DisposalMethod dispose) {
return SkCodecAnimation::DisposalMethod::kRestorePrevious == dispose;
}
+int SkAnimatedImage::computeNextFrame(int current, bool* animationEnded) {
+ SkASSERT(animationEnded != nullptr);
+ *animationEnded = false;
+
+ const int frameToDecode = current + 1;
+ if (frameToDecode == fFrameCount - 1) {
+ // Final frame. Check to determine whether to stop.
+ fRepetitionsCompleted++;
+ if (fRepetitionCount != SkCodec::kRepetitionCountInfinite
+ && fRepetitionsCompleted > fRepetitionCount) {
+ *animationEnded = true;
+ }
+ } else if (frameToDecode == fFrameCount) {
+ return 0;
+ }
+ return frameToDecode;
+}
+
+double SkAnimatedImage::finish() {
+ fFinished = true;
+ fRunning = false;
+ return std::numeric_limits<double>::max();
+}
+
double SkAnimatedImage::update(double msecs) {
if (fFinished) {
return std::numeric_limits<double>::max();
@@ -136,19 +168,25 @@ double SkAnimatedImage::update(double msecs) {
fNowMS = msecs;
const double msSinceLastUpdate = fNowMS - lastUpdateMS;
- const int frameCount = fCodec->codec()->getFrameCount();
+ bool animationEnded = false;
int frameToDecode = SkCodec::kNone;
if (kInit == msecs) {
frameToDecode = 0;
+ fNowMS = lastUpdateMS;
} else {
- if (!fRunning || lastUpdateMS == kInit) {
- return kInit;
+ if (!fRunning) {
+ return std::numeric_limits<double>::max();
}
+
+ if (lastUpdateMS == kInit) {
+ return fRemainingMS + fNowMS;
+ }
+
if (msSinceLastUpdate < fRemainingMS) {
fRemainingMS -= msSinceLastUpdate;
return fRemainingMS + fNowMS;
} else {
- frameToDecode = (fActiveFrame.fIndex + 1) % frameCount;
+ frameToDecode = this->computeNextFrame(fActiveFrame.fIndex, &animationEnded);
}
}
@@ -156,8 +194,7 @@ double SkAnimatedImage::update(double msecs) {
if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
if (!frameInfo.fFullyReceived) {
SkCodecPrintf("Frame %i not fully received\n", frameToDecode);
- fFinished = true;
- return std::numeric_limits<double>::max();
+ return this->finish();
}
if (kInit == msecs) {
@@ -168,22 +205,22 @@ double SkAnimatedImage::update(double msecs) {
while (pastUpdate >= frameInfo.fDuration) {
SkCodecPrintf("Skipping frame %i\n", frameToDecode);
pastUpdate -= frameInfo.fDuration;
- frameToDecode = (frameToDecode + 1) % frameCount;
+ 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--;
- fFinished = true;
+ animationEnded = true;
if (frameToDecode < 0) {
- return std::numeric_limits<double>::max();
+ return this->finish();
}
}
}
fRemainingMS = frameInfo.fDuration - pastUpdate;
}
} else {
- fFinished = true;
+ animationEnded = true;
if (0 == frameToDecode) {
// Static image. This is okay.
frameInfo.fRequiredFrame = SkCodec::kNone;
@@ -194,16 +231,22 @@ double SkAnimatedImage::update(double msecs) {
} else {
SkCodecPrintf("Error getting frameInfo for frame %i\n",
frameToDecode);
- return std::numeric_limits<double>::max();
+ return this->finish();
}
}
if (frameToDecode == fActiveFrame.fIndex) {
+ if (animationEnded) {
+ return this->finish();
+ }
return fRemainingMS + fNowMS;
}
if (frameToDecode == fRestoreFrame.fIndex) {
SkTSwap(fActiveFrame, fRestoreFrame);
+ if (animationEnded) {
+ return this->finish();
+ }
return fRemainingMS + fNowMS;
}
@@ -245,8 +288,7 @@ double SkAnimatedImage::update(double msecs) {
SkTSwap(fActiveFrame, fRestoreFrame);
} else if (!fRestoreFrame.copyTo(&fActiveFrame)) {
SkCodecPrintf("Failed to restore frame\n");
- fFinished = true;
- return std::numeric_limits<double>::max();
+ return this->finish();
}
options.fPriorFrame = fActiveFrame.fIndex;
}
@@ -260,22 +302,23 @@ double SkAnimatedImage::update(double msecs) {
} else {
auto info = fDecodeInfo.makeAlphaType(alphaType);
if (!dst->tryAllocPixels(info)) {
- fFinished = true;
- return std::numeric_limits<double>::max();
+ return this->finish();
}
}
auto result = fCodec->codec()->getPixels(dst->info(), dst->getPixels(), dst->rowBytes(),
&options);
if (result != SkCodec::kSuccess) {
- SkCodecPrintf("error %i, frame %i of %i\n", result, frameToDecode, frameCount);
- // Reset to the beginning.
- fActiveFrame.fIndex = SkCodec::kNone;
- return 0.0;
+ SkCodecPrintf("error %i, frame %i of %i\n", result, frameToDecode, fFrameCount);
+ return this->finish();
}
fActiveFrame.fIndex = frameToDecode;
fActiveFrame.fDisposalMethod = frameInfo.fDisposalMethod;
+
+ if (animationEnded) {
+ return this->finish();
+ }
return fRemainingMS + fNowMS;
}
@@ -302,3 +345,7 @@ void SkAnimatedImage::onDraw(SkCanvas* canvas) {
canvas->restore();
}
}
+
+void SkAnimatedImage::setRepetitionCount(int newCount) {
+ fRepetitionCount = newCount;
+}
diff --git a/tests/AnimatedImageTest.cpp b/tests/AnimatedImageTest.cpp
new file mode 100644
index 0000000000..c8113d3fb1
--- /dev/null
+++ b/tests/AnimatedImageTest.cpp
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAndroidCodec.h"
+#include "SkAnimatedImage.h"
+#include "SkCanvas.h"
+#include "SkCodec.h"
+#include "SkUnPreMultiply.h"
+
+#include "CodecPriv.h"
+#include "Resources.h"
+#include "Test.h"
+#include "sk_tool_utils.h"
+
+#include <vector>
+
+DEF_TEST(AnimatedImage, r) {
+ if (GetResourcePath().isEmpty()) {
+ return;
+ }
+ for (const char* file : { "images/alphabetAnim.gif",
+ "images/colorTables.gif",
+ "images/webp-animated.webp",
+ "images/required.webp",
+ }) {
+ auto data = GetResourceAsData(file);
+ if (!data) {
+ ERRORF(r, "Could not get %s", file);
+ continue;
+ }
+
+ auto codec = SkCodec::MakeFromData(data);
+ if (!codec) {
+ ERRORF(r, "Could not create codec for %s", file);
+ continue;
+ }
+
+ const int defaultRepetitionCount = codec->getRepetitionCount();
+ std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
+ std::vector<SkBitmap> frames(frameInfos.size());
+ // Used down below for our test image.
+ const auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
+
+ for (size_t i = 0; i < frameInfos.size(); ++i) {
+ auto info = codec->getInfo().makeAlphaType(frameInfos[i].fAlphaType);
+ auto& bm = frames[i];
+
+ SkCodec::Options options;
+ options.fFrameIndex = (int) i;
+ options.fPriorFrame = frameInfos[i].fRequiredFrame;
+ if (options.fPriorFrame == SkCodec::kNone) {
+ bm.allocPixels(info);
+ bm.eraseColor(0);
+ } else {
+ const SkBitmap& priorFrame = frames[options.fPriorFrame];
+ if (!sk_tool_utils::copy_to(&bm, priorFrame.colorType(), priorFrame)) {
+ ERRORF(r, "Failed to copy %s frame %i", file, options.fPriorFrame);
+ options.fPriorFrame = SkCodec::kNone;
+ }
+ REPORTER_ASSERT(r, bm.setAlphaType(frameInfos[i].fAlphaType));
+ }
+
+ auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), &options);
+ if (result != SkCodec::kSuccess) {
+ ERRORF(r, "error in %s frame %zu: %s", file, i, SkCodec::ResultToString(result));
+ }
+ }
+
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
+ if (!androidCodec) {
+ ERRORF(r, "Could not create androidCodec for %s", file);
+ continue;
+ }
+
+ auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec));
+ if (!animatedImage) {
+ ERRORF(r, "Could not create animated image for %s", file);
+ continue;
+ }
+
+ auto testDraw = [r, &frames, &imageInfo, file](const sk_sp<SkAnimatedImage>& animatedImage,
+ int expectedFrame) {
+ SkBitmap test;
+ test.allocPixels(imageInfo);
+ test.eraseColor(0);
+ SkCanvas c(test);
+ animatedImage->draw(&c);
+
+ const SkBitmap& frame = frames[expectedFrame];
+ REPORTER_ASSERT(r, frame.colorType() == test.colorType());
+ REPORTER_ASSERT(r, frame.dimensions() == test.dimensions());
+ for (int i = 0; i < test.width(); ++i)
+ for (int j = 0; j < test.height(); ++j) {
+ SkColor expected = SkUnPreMultiply::PMColorToColor(*frame.getAddr32(i, j));
+ SkColor actual = SkUnPreMultiply::PMColorToColor(*test .getAddr32(i, j));
+ if (expected != actual) {
+ ERRORF(r, "frame %i of %s does not match at pixel %i, %i!"
+ " expected %x\tactual: %x",
+ expectedFrame, file, i, j, expected, actual);
+ SkString expected_name = SkStringPrintf("expected_%c", '0' + expectedFrame);
+ SkString actual_name = SkStringPrintf("actual_%c", '0' + expectedFrame);
+ write_bm(expected_name.c_str(), frame);
+ write_bm(actual_name.c_str(), test);
+ return false;
+ }
+ }
+ return true;
+ };
+
+ REPORTER_ASSERT(r, !animatedImage->isRunning());
+ 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);
+ if (i == frameInfos.size() - 1 && defaultRepetitionCount == 0) {
+ REPORTER_ASSERT(r, next == std::numeric_limits<double>::max());
+ REPORTER_ASSERT(r, !animatedImage->isRunning());
+ } else {
+ REPORTER_ASSERT(r, animatedImage->isRunning());
+ 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)) {
+ ERRORF(r, "Did not update to %i properly", i);
+ 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->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;
+ break;
+ }
+
+ double interval = next - currentTime;
+ animatedImage->stop();
+ REPORTER_ASSERT(r, !animatedImage->isRunning());
+
+ currentTime = next;
+ double stoppedNext = animatedImage->update(currentTime);
+ REPORTER_ASSERT(r, stoppedNext == std::numeric_limits<double>::max());
+ if (!testDraw(animatedImage, i)) {
+ ERRORF(r, "Advanced the frame while stopped?");
+ failed = true;
+ break;
+ }
+
+ animatedImage->start();
+ currentTime += interval;
+ }
+
+ if (failed) {
+ return;
+ }
+
+ REPORTER_ASSERT(r, animatedImage->isRunning());
+ 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);
+ for (int loops = 0; loops <= loopCount; loops++) {
+ REPORTER_ASSERT(r, animatedImage->isRunning());
+ for (size_t i = 0; i < frameInfos.size(); ++i) {
+ double next = animatedImage->update(currentTime);
+ if (animatedImage->isRunning()) {
+ currentTime = next;
+ }
+ }
+ }
+ if (animatedImage->isRunning()) {
+ ERRORF(r, "%s animation still running after %i loops", file, loopCount);
+ }
+ }
+ }
+}
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index f707f1e04c..c5c65f8468 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -8,11 +8,9 @@
#include "SkAndroidCodec.h"
#include "SkBitmap.h"
#include "SkCodec.h"
-#include "SkCommonFlags.h"
-#include "SkImageEncoder.h"
-#include "SkOSPath.h"
#include "SkStream.h"
+#include "CodecPriv.h"
#include "Resources.h"
#include "Test.h"
#include "sk_tool_utils.h"
@@ -20,19 +18,6 @@
#include <initializer_list>
#include <vector>
-static void write_bm(const char* name, const SkBitmap& bm) {
- if (FLAGS_writePath.isEmpty()) {
- return;
- }
-
- SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
- filename.appendf(".png");
- SkFILEWStream file(filename.c_str());
- if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
- SkDebugf("failed to write '%s'\n", filename.c_str());
- }
-}
-
DEF_TEST(Codec_trunc, r) {
sk_sp<SkData> data(GetResourceAsData("images/box.gif"));
if (!data) {
diff --git a/tests/CodecPriv.h b/tests/CodecPriv.h
index 8362599e95..88de79c65e 100644
--- a/tests/CodecPriv.h
+++ b/tests/CodecPriv.h
@@ -9,7 +9,12 @@
#include "SkBitmap.h"
#include "SkCodec.h"
+#include "SkCommonFlags.h"
#include "SkData.h"
+#include "SkEncodedImageFormat.h"
+#include "SkImageEncoder.h"
+#include "SkOSPath.h"
+#include "SkStream.h"
inline bool decode_memory(const void* mem, size_t size, SkBitmap* bm) {
std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(SkData::MakeWithoutCopy(mem, size)));
@@ -22,4 +27,18 @@ inline bool decode_memory(const void* mem, size_t size, SkBitmap* bm) {
bm->rowBytes());
return result == SkCodec::kSuccess || result == SkCodec::kIncompleteInput;
}
+
+inline void write_bm(const char* name, const SkBitmap& bm) {
+ if (FLAGS_writePath.isEmpty()) {
+ return;
+ }
+
+ SkString filename = SkOSPath::Join(FLAGS_writePath[0], name);
+ filename.appendf(".png");
+ SkFILEWStream file(filename.c_str());
+ if (!SkEncodeImage(&file, bm, SkEncodedImageFormat::kPNG, 100)) {
+ SkDebugf("failed to write '%s'\n", filename.c_str());
+ }
+}
+
#endif // CodecPriv_DEFINED