aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/android/SkAnimatedImage.h16
-rw-r--r--src/android/SkAnimatedImage.cpp120
-rw-r--r--tests/AnimatedImageTest.cpp115
3 files changed, 185 insertions, 66 deletions
diff --git a/include/android/SkAnimatedImage.h b/include/android/SkAnimatedImage.h
index 51f0e5b84b..983a57bd5c 100644
--- a/include/android/SkAnimatedImage.h
+++ b/include/android/SkAnimatedImage.h
@@ -107,7 +107,20 @@ private:
int fIndex;
SkCodecAnimation::DisposalMethod fDisposalMethod;
+ // init() may have to create a new SkPixelRef, if the
+ // current one is already in use by another owner (e.g.
+ // an SkPicture). This determines whether to copy the
+ // existing one to the new one.
+ enum class OnInit {
+ // Restore the image from the old SkPixelRef to the
+ // new one.
+ kRestoreIfNecessary,
+ // No need to restore.
+ kNoRestore,
+ };
+
Frame();
+ bool init(const SkImageInfo& info, OnInit);
bool copyTo(Frame*) const;
};
@@ -122,7 +135,8 @@ private:
bool fFinished;
int fCurrentFrameDuration;
- Frame fActiveFrame;
+ Frame fDisplayFrame;
+ Frame fDecodingFrame;
Frame fRestoreFrame;
int fRepetitionCount;
int fRepetitionsCompleted;
diff --git a/src/android/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp
index de25f309e4..daeec81256 100644
--- a/src/android/SkAnimatedImage.cpp
+++ b/src/android/SkAnimatedImage.cpp
@@ -10,8 +10,10 @@
#include "SkCanvas.h"
#include "SkCodec.h"
#include "SkCodecPriv.h"
+#include "SkImagePriv.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
+#include "SkPixelRef.h"
sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec,
SkISize scaledSize, SkIRect cropRect, sk_sp<SkPicture> postProcess) {
@@ -30,7 +32,7 @@ sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> cod
auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize,
decodeInfo, cropRect, std::move(postProcess)));
- if (!image->fActiveFrame.fBitmap.getPixels()) {
+ if (!image->fDisplayFrame.fBitmap.getPixels()) {
// tryAllocPixels failed.
return nullptr;
}
@@ -49,7 +51,7 @@ sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> cod
auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize,
decodeInfo, cropRect, nullptr));
- if (!image->fActiveFrame.fBitmap.getPixels()) {
+ if (!image->fDisplayFrame.fBitmap.getPixels()) {
// tryAllocPixels failed.
return nullptr;
}
@@ -72,7 +74,7 @@ SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize
, fRepetitionCount(fCodec->codec()->getRepetitionCount())
, fRepetitionsCompleted(0)
{
- if (!fActiveFrame.fBitmap.tryAllocPixels(fDecodeInfo)) {
+ if (!fDecodingFrame.fBitmap.tryAllocPixels(fDecodeInfo)) {
return;
}
@@ -95,10 +97,33 @@ SkAnimatedImage::Frame::Frame()
: fIndex(SkCodec::kNone)
{}
+bool SkAnimatedImage::Frame::init(const SkImageInfo& info, OnInit onInit) {
+ if (fBitmap.getPixels()) {
+ if (fBitmap.pixelRef()->unique()) {
+ SkAssertResult(fBitmap.setAlphaType(info.alphaType()));
+ return true;
+ }
+
+ // An SkCanvas provided to onDraw is still holding a reference.
+ // Copy before we decode to ensure that we don't overwrite the
+ // expected contents of the image.
+ if (OnInit::kRestoreIfNecessary == onInit) {
+ SkBitmap tmp;
+ if (!tmp.tryAllocPixels(info)) {
+ return false;
+ }
+
+ memcpy(tmp.getPixels(), fBitmap.getPixels(), fBitmap.computeByteSize());
+ SkTSwap(tmp, fBitmap);
+ return true;
+ }
+ }
+
+ return fBitmap.tryAllocPixels(info);
+}
+
bool SkAnimatedImage::Frame::copyTo(Frame* dst) const {
- if (dst->fBitmap.getPixels()) {
- dst->fBitmap.setAlphaType(fBitmap.alphaType());
- } else if (!dst->fBitmap.tryAllocPixels(fBitmap.info())) {
+ if (!dst->init(fBitmap.info(), OnInit::kNoRestore)) {
return false;
}
@@ -111,19 +136,10 @@ bool SkAnimatedImage::Frame::copyTo(Frame* dst) const {
void SkAnimatedImage::reset() {
fFinished = false;
fRepetitionsCompleted = 0;
- if (fActiveFrame.fIndex == 0) {
- // Already showing the first frame.
- return;
+ if (fDisplayFrame.fIndex != 0) {
+ fDisplayFrame.fIndex = SkCodec::kNone;
+ this->decodeNextFrame();
}
-
- 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) {
@@ -160,7 +176,7 @@ int SkAnimatedImage::decodeNextFrame() {
}
bool animationEnded = false;
- int frameToDecode = this->computeNextFrame(fActiveFrame.fIndex, &animationEnded);
+ int frameToDecode = this->computeNextFrame(fDisplayFrame.fIndex, &animationEnded);
SkCodec::FrameInfo frameInfo;
if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
@@ -188,19 +204,21 @@ int SkAnimatedImage::decodeNextFrame() {
}
}
- if (frameToDecode == fActiveFrame.fIndex) {
+ if (frameToDecode == fDisplayFrame.fIndex) {
if (animationEnded) {
return this->finish();
}
return fCurrentFrameDuration;
}
- if (frameToDecode == fRestoreFrame.fIndex) {
- SkTSwap(fActiveFrame, fRestoreFrame);
- if (animationEnded) {
- return this->finish();
+ for (Frame* frame : { &fRestoreFrame, &fDecodingFrame }) {
+ if (frameToDecode == frame->fIndex) {
+ SkTSwap(fDisplayFrame, *frame);
+ if (animationEnded) {
+ return this->finish();
+ }
+ return fCurrentFrameDuration;
}
- return fCurrentFrameDuration;
}
// The following code makes an effort to avoid overwriting a frame that will
@@ -216,9 +234,9 @@ int SkAnimatedImage::decodeNextFrame() {
// frameToDecode will be discarded immediately after drawing, so
// do not overwrite a frame which could possibly be used in the
// future.
- if (fActiveFrame.fIndex != SkCodec::kNone &&
- !is_restore_previous(fActiveFrame.fDisposalMethod)) {
- SkTSwap(fActiveFrame, fRestoreFrame);
+ if (fDecodingFrame.fIndex != SkCodec::kNone &&
+ !is_restore_previous(fDecodingFrame.fDisposalMethod)) {
+ SkTSwap(fDecodingFrame, fRestoreFrame);
}
}
} else {
@@ -229,34 +247,36 @@ int SkAnimatedImage::decodeNextFrame() {
return frame.fIndex >= frameInfo.fRequiredFrame && frame.fIndex < frameToDecode;
};
- if (validPriorFrame(fActiveFrame)) {
+ if (validPriorFrame(fDecodingFrame)) {
if (is_restore_previous(frameInfo.fDisposalMethod)) {
- // fActiveFrame is a good frame to use for this one, but we
+ // fDecodingFrame is a good frame to use for this one, but we
// don't want to overwrite it.
- fActiveFrame.copyTo(&fRestoreFrame);
+ fDecodingFrame.copyTo(&fRestoreFrame);
+ }
+ options.fPriorFrame = fDecodingFrame.fIndex;
+ } else if (validPriorFrame(fDisplayFrame)) {
+ if (!fDisplayFrame.copyTo(&fDecodingFrame)) {
+ SkCodecPrintf("Failed to allocate pixels for frame\n");
+ return this->finish();
}
- options.fPriorFrame = fActiveFrame.fIndex;
+ options.fPriorFrame = fDecodingFrame.fIndex;
} else if (validPriorFrame(fRestoreFrame)) {
if (!is_restore_previous(frameInfo.fDisposalMethod)) {
- SkTSwap(fActiveFrame, fRestoreFrame);
- } else if (!fRestoreFrame.copyTo(&fActiveFrame)) {
+ SkTSwap(fDecodingFrame, fRestoreFrame);
+ } else if (!fRestoreFrame.copyTo(&fDecodingFrame)) {
SkCodecPrintf("Failed to restore frame\n");
return this->finish();
}
- options.fPriorFrame = fActiveFrame.fIndex;
+ options.fPriorFrame = fDecodingFrame.fIndex;
}
}
auto alphaType = kOpaque_SkAlphaType == frameInfo.fAlphaType ?
kOpaque_SkAlphaType : kPremul_SkAlphaType;
- SkBitmap* dst = &fActiveFrame.fBitmap;
- if (dst->getPixels()) {
- SkAssertResult(dst->setAlphaType(alphaType));
- } else {
- auto info = fDecodeInfo.makeAlphaType(alphaType);
- if (!dst->tryAllocPixels(info)) {
- return this->finish();
- }
+ auto info = fDecodeInfo.makeAlphaType(alphaType);
+ SkBitmap* dst = &fDecodingFrame.fBitmap;
+ if (!fDecodingFrame.init(info, Frame::OnInit::kRestoreIfNecessary)) {
+ return this->finish();
}
auto result = fCodec->codec()->getPixels(dst->info(), dst->getPixels(), dst->rowBytes(),
@@ -266,8 +286,11 @@ int SkAnimatedImage::decodeNextFrame() {
return this->finish();
}
- fActiveFrame.fIndex = frameToDecode;
- fActiveFrame.fDisposalMethod = frameInfo.fDisposalMethod;
+ fDecodingFrame.fIndex = frameToDecode;
+ fDecodingFrame.fDisposalMethod = frameInfo.fDisposalMethod;
+
+ SkTSwap(fDecodingFrame, fDisplayFrame);
+ fDisplayFrame.fBitmap.notifyPixelsChanged();
if (animationEnded) {
return this->finish();
@@ -276,8 +299,11 @@ int SkAnimatedImage::decodeNextFrame() {
}
void SkAnimatedImage::onDraw(SkCanvas* canvas) {
+ auto image = SkMakeImageFromRasterBitmap(fDisplayFrame.fBitmap,
+ kNever_SkCopyPixelsMode);
+
if (fSimple) {
- canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0);
+ canvas->drawImage(image, 0, 0);
return;
}
@@ -290,7 +316,7 @@ void SkAnimatedImage::onDraw(SkCanvas* canvas) {
canvas->concat(fMatrix);
SkPaint paint;
paint.setFilterQuality(kLow_SkFilterQuality);
- canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0, &paint);
+ canvas->drawImage(image, 0, 0, &paint);
}
if (fPostProcess) {
canvas->drawPicture(fPostProcess);
diff --git a/tests/AnimatedImageTest.cpp b/tests/AnimatedImageTest.cpp
index 6c7b0e5a55..13ea80804c 100644
--- a/tests/AnimatedImageTest.cpp
+++ b/tests/AnimatedImageTest.cpp
@@ -72,6 +72,102 @@ DEF_TEST(AnimatedImage_scaled, r) {
}
}
+static bool compare_bitmaps(skiatest::Reporter* r,
+ const char* file,
+ int expectedFrame,
+ const SkBitmap& expectedBm,
+ const SkBitmap& actualBm) {
+ REPORTER_ASSERT(r, expectedBm.colorType() == actualBm.colorType());
+ REPORTER_ASSERT(r, expectedBm.dimensions() == actualBm.dimensions());
+ for (int i = 0; i < actualBm.width(); ++i)
+ for (int j = 0; j < actualBm.height(); ++j) {
+ SkColor expected = SkUnPreMultiply::PMColorToColor(*expectedBm.getAddr32(i, j));
+ SkColor actual = SkUnPreMultiply::PMColorToColor(*actualBm .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(), expectedBm);
+ write_bm(actual_name.c_str(), actualBm);
+ return false;
+ }
+ }
+ return true;
+}
+
+DEF_TEST(AnimatedImage_copyOnWrite, 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 auto imageInfo = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
+ const int frameCount = codec->getFrameCount();
+ 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;
+ }
+ animatedImage->setRepetitionCount(0);
+
+ std::vector<SkBitmap> expected(frameCount);
+ std::vector<sk_sp<SkPicture>> pictures(frameCount);
+ for (int i = 0; i < frameCount; i++) {
+ SkBitmap& bm = expected[i];
+ bm.allocPixels(imageInfo);
+ bm.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas canvas(bm);
+
+ pictures[i].reset(animatedImage->newPictureSnapshot());
+ canvas.drawPicture(pictures[i]);
+
+ const auto duration = animatedImage->decodeNextFrame();
+ // We're attempting to decode i + 1, so decodeNextFrame will return
+ // kFinished if that is the last frame (or we attempt to decode one
+ // more).
+ if (i >= frameCount - 2) {
+ REPORTER_ASSERT(r, duration == SkAnimatedImage::kFinished);
+ } else {
+ REPORTER_ASSERT(r, duration != SkAnimatedImage::kFinished);
+ }
+ }
+
+ for (int i = 0; i < frameCount; i++) {
+ SkBitmap test;
+ test.allocPixels(imageInfo);
+ test.eraseColor(SK_ColorTRANSPARENT);
+ SkCanvas canvas(test);
+
+ canvas.drawPicture(pictures[i]);
+
+ compare_bitmaps(r, file, i, expected[i], test);
+ }
+ }
+}
+
DEF_TEST(AnimatedImage, r) {
if (GetResourcePath().isEmpty()) {
return;
@@ -147,24 +243,7 @@ DEF_TEST(AnimatedImage, r) {
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;
+ return compare_bitmaps(r, file, expectedFrame, frame, test);
};
REPORTER_ASSERT(r, animatedImage->currentFrameDuration() == frameInfos[0].fDuration);