Use SkAndroidCodec in SkAnimatedImage
Bug: b/63909536
Bug: b/63908092
SkAnimatedImage is designed around a specific Android use case, so move
it into the android folders.
Make SkAnimatedImage hold an SkAndroidCodec (instead of an SkCodec).
Expose fCodec so that SkAnimatedImage can animate by using the internal
SkCodec.
Update the sample to use SkAndroidCodec.
Allow webp to decode a scaled down animation. For RestoreBG frames,
adjust the frameRect (which is erased) to account for the scaling. Add
a test to verify that we decode a webp with a RestoreBG frame
successfully. Disable scaling for later frames in other formats (GIF,
for now), since the code for erasing a RestoreBG frame is currently
unaware of the sampling.
Change-Id: I5dd2b86138f2c7f6adcd08dce1bd49040f7dc224
Reviewed-on: https://skia-review.googlesource.com/94621
Commit-Queue: Leon Scroggins <scroggo@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 36005a9..b17544f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -771,10 +771,10 @@
sources += skia_xps_sources
sources += [
"src/android/SkAndroidFrameworkUtils.cpp",
+ "src/android/SkAnimatedImage.cpp",
"src/android/SkBitmapRegionCodec.cpp",
"src/android/SkBitmapRegionDecoder.cpp",
"src/codec/SkAndroidCodec.cpp",
- "src/codec/SkAnimatedImage.cpp",
"src/codec/SkBmpBaseCodec.cpp",
"src/codec/SkBmpCodec.cpp",
"src/codec/SkBmpMaskCodec.cpp",
diff --git a/include/codec/SkAnimatedImage.h b/include/android/SkAnimatedImage.h
similarity index 75%
rename from include/codec/SkAnimatedImage.h
rename to include/android/SkAnimatedImage.h
index aece2f6..1bd9291 100644
--- a/include/codec/SkAnimatedImage.h
+++ b/include/android/SkAnimatedImage.h
@@ -12,7 +12,7 @@
#include "SkCodecAnimation.h"
#include "SkDrawable.h"
-class SkCodec;
+class SkAndroidCodec;
/**
* Thread unsafe drawable for drawing animated images (e.g. GIF).
@@ -20,12 +20,12 @@
class SK_API SkAnimatedImage : public SkDrawable {
public:
/**
- * Create an SkAnimatedImage from the SkCodec.
+ * 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.
*/
- static sk_sp<SkAnimatedImage> MakeFromCodec(std::unique_ptr<SkCodec>);
+ static sk_sp<SkAnimatedImage> Make(std::unique_ptr<SkAndroidCodec>);
~SkAnimatedImage() override;
@@ -70,15 +70,15 @@
bool copyTo(Frame*) const;
};
- std::unique_ptr<SkCodec> fCodec;
- bool fFinished;
- bool fRunning;
- double fNowMS;
- double fRemainingMS;
- Frame fActiveFrame;
- Frame fRestoreFrame;
+ std::unique_ptr<SkAndroidCodec> fCodec;
+ bool fFinished;
+ bool fRunning;
+ double fNowMS;
+ double fRemainingMS;
+ Frame fActiveFrame;
+ Frame fRestoreFrame;
- SkAnimatedImage(std::unique_ptr<SkCodec>);
+ SkAnimatedImage(std::unique_ptr<SkAndroidCodec>);
typedef SkDrawable INHERITED;
};
diff --git a/include/codec/SkAndroidCodec.h b/include/codec/SkAndroidCodec.h
index e1e2843..2bf3802 100644
--- a/include/codec/SkAndroidCodec.h
+++ b/include/codec/SkAndroidCodec.h
@@ -231,12 +231,12 @@
return this->getAndroidPixels(info, pixels, rowBytes);
}
+ SkCodec* codec() const { return fCodec.get(); }
+
protected:
SkAndroidCodec(SkCodec*);
- SkCodec* codec() const { return fCodec.get(); }
-
virtual SkISize onGetSampledDimensions(int sampleSize) const = 0;
virtual bool onGetSupportedSubset(SkIRect* desiredSubset) const = 0;
diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp
index 96e53b8..bb1dd5a 100644
--- a/samplecode/SampleAnimatedImage.cpp
+++ b/samplecode/SampleAnimatedImage.cpp
@@ -5,10 +5,10 @@
* found in the LICENSE file.
*/
+#include "SkAndroidCodec.h"
#include "SkAnimatedImage.h"
#include "SkAnimTimer.h"
#include "SkCanvas.h"
-#include "SkCodec.h"
#include "SkPaint.h"
#include "SkPictureRecorder.h"
#include "SkRect.h"
@@ -74,7 +74,7 @@
return;
}
- fImage = SkAnimatedImage::MakeFromCodec(std::move(codec));
+ fImage = SkAnimatedImage::Make(SkAndroidCodec::MakeFromCodec(std::move(codec)));
if (!fImage) {
return;
}
diff --git a/src/codec/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp
similarity index 92%
rename from src/codec/SkAnimatedImage.cpp
rename to src/android/SkAnimatedImage.cpp
index dcb4bb9..25c020f 100644
--- a/src/codec/SkAnimatedImage.cpp
+++ b/src/android/SkAnimatedImage.cpp
@@ -5,12 +5,13 @@
* found in the LICENSE file.
*/
+#include "SkAndroidCodec.h"
#include "SkAnimatedImage.h"
#include "SkCanvas.h"
#include "SkCodec.h"
#include "SkCodecPriv.h"
-sk_sp<SkAnimatedImage> SkAnimatedImage::MakeFromCodec(std::unique_ptr<SkCodec> codec) {
+sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec) {
if (!codec) {
return nullptr;
}
@@ -27,7 +28,7 @@
// Sentinel value for starting at the beginning.
static constexpr double kInit = -1.0;
-SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkCodec> codec)
+SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec)
: fCodec(std::move(codec))
, fFinished(false)
, fRunning(false)
@@ -93,7 +94,7 @@
fNowMS = msecs;
const double msSinceLastUpdate = fNowMS - lastUpdateMS;
- const int frameCount = fCodec->getFrameCount();
+ const int frameCount = fCodec->codec()->getFrameCount();
int frameToDecode = SkCodec::kNone;
if (kInit == msecs) {
frameToDecode = 0;
@@ -110,7 +111,7 @@
}
SkCodec::FrameInfo frameInfo;
- if (fCodec->getFrameInfo(frameToDecode, &frameInfo)) {
+ if (fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
if (!frameInfo.fFullyReceived) {
SkCodecPrintf("Frame %i not fully received\n", frameToDecode);
fFinished = true;
@@ -126,7 +127,7 @@
SkCodecPrintf("Skipping frame %i\n", frameToDecode);
pastUpdate -= frameInfo.fDuration;
frameToDecode = (frameToDecode + 1) % frameCount;
- if (!fCodec->getFrameInfo(frameToDecode, &frameInfo)) {
+ if (!fCodec->codec()->getFrameInfo(frameToDecode, &frameInfo)) {
SkCodecPrintf("Could not getFrameInfo for frame %i",
frameToDecode);
// Prior call to getFrameInfo succeeded, so use that one.
@@ -222,9 +223,10 @@
}
}
- auto result = fCodec->getPixels(dst->info(), dst->getPixels(), dst->rowBytes(), &options);
+ 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, fCodec->getFrameCount());
+ SkCodecPrintf("error %i, frame %i of %i\n", result, frameToDecode, frameCount);
// Reset to the beginning.
fActiveFrame.fIndex = SkCodec::kNone;
return 0.0;
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index d0fb43a..b2902ca 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -237,8 +237,8 @@
return kInvalidParameters;
}
- if (options.fSubset || info.dimensions() != fSrcInfo.dimensions()) {
- // If we add support for these, we need to update the code that zeroes
+ if (options.fSubset) {
+ // If we add support for this, we need to update the code that zeroes
// a kRestoreBGColor frame.
return kInvalidParameters;
}
@@ -275,7 +275,29 @@
// If a frame after the required frame is provided, there is no
// need to clear, since it must be covered by the desired frame.
if (options.fPriorFrame == requiredFrame) {
- zero_rect(info, pixels, rowBytes, prevFrame->frameRect());
+ SkIRect prevRect = prevFrame->frameRect();
+ if (info.dimensions() != fSrcInfo.dimensions()) {
+ auto src = SkRect::Make(fSrcInfo.dimensions());
+ auto dst = SkRect::Make(info.dimensions());
+ SkMatrix map = SkMatrix::MakeRectToRect(src, dst,
+ SkMatrix::kCenter_ScaleToFit);
+ SkRect asRect = SkRect::Make(prevRect);
+ if (!map.mapRect(&asRect)) {
+ return kInternalError;
+ }
+ asRect.roundIn(&prevRect);
+ if (prevRect.isEmpty()) {
+ // Down-scaling shrank the empty portion to nothing,
+ // so nothing to zero.
+ break;
+ }
+ if (!prevRect.intersect(SkIRect::MakeSize(info.dimensions()))) {
+ SkCodecPrintf("rectangles do not intersect!");
+ SkASSERT(false);
+ break;
+ }
+ }
+ zero_rect(info, pixels, rowBytes, prevRect);
}
break;
default:
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index ca63349..aa3547d 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -402,7 +402,7 @@
SkASSERT(0 == index || index < fFrameHolder.size());
const auto& srcInfo = this->getInfo();
- SkASSERT(0 == index || (!options.fSubset && dstInfo.dimensions() == srcInfo.dimensions()));
+ SkASSERT(0 == index || !options.fSubset);
WebPDecoderConfig config;
if (0 == WebPInitDecoderConfig(&config)) {
diff --git a/tests/CodecAnimTest.cpp b/tests/CodecAnimTest.cpp
index b09c925..f707f1e 100644
--- a/tests/CodecAnimTest.cpp
+++ b/tests/CodecAnimTest.cpp
@@ -5,6 +5,7 @@
* found in the LICENSE file.
*/
+#include "SkAndroidCodec.h"
#include "SkBitmap.h"
#include "SkCodec.h"
#include "SkCommonFlags.h"
@@ -386,3 +387,52 @@
}
}
}
+
+// Verify that a webp image can be animated scaled down. This image has a
+// kRestoreBG frame, so it is an interesting image to test. After decoding that
+// frame, we have to erase its rectangle. The rectangle has to be adjusted
+// based on the scaled size.
+DEF_TEST(AndroidCodec_animated, r) {
+ if (GetResourcePath().isEmpty()) {
+ return;
+ }
+
+ const char* file = "images/required.webp";
+ sk_sp<SkData> data(GetResourceAsData(file));
+ if (!data) {
+ ERRORF(r, "Missing %s", file);
+ return;
+ }
+
+ auto codec = SkAndroidCodec::MakeFromCodec(SkCodec::MakeFromData(std::move(data)));
+ if (!codec) {
+ ERRORF(r, "Failed to decode %s", file);
+ return;
+ }
+
+ auto info = codec->getInfo().makeAlphaType(kPremul_SkAlphaType);
+
+ for (int sampleSize : { 8, 32, 100 }) {
+ auto dimensions = codec->codec()->getScaledDimensions(1.0f / sampleSize);
+ info = info.makeWH(dimensions.width(), dimensions.height());
+ SkBitmap bm;
+ bm.allocPixels(info);
+
+ SkCodec::Options options;
+ for (int i = 0; i < codec->codec()->getFrameCount(); ++i) {
+ SkCodec::FrameInfo frameInfo;
+ REPORTER_ASSERT(r, codec->codec()->getFrameInfo(i, &frameInfo));
+ if (5 == i) {
+ REPORTER_ASSERT(r, frameInfo.fDisposalMethod
+ == SkCodecAnimation::DisposalMethod::kRestoreBGColor);
+ }
+ options.fFrameIndex = i;
+ options.fPriorFrame = i - 1;
+ info = info.makeAlphaType(frameInfo.fAlphaType);
+
+ const auto result = codec->codec()->getPixels(info, bm.getPixels(), bm.rowBytes(),
+ &options);
+ REPORTER_ASSERT(r, result == SkCodec::kSuccess);
+ }
+ }
+}