Add Android ImageDecoder features to SkAnimatedImage

Bug: b/63909536
Bug: b/63908092

- Scale to an arbitrary size, using the decoding library if it supports
  it, and Skia otherwise
- Crop to a subset
- Post-processing with an SkPicture, to facilitate circle masks etc
- isRunning, to implement Animatable2 interface in Java

Change-Id: I13dbabee8e4a22e5cc193856aa3e94ce23ae4cb5
Reviewed-on: https://skia-review.googlesource.com/94660
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Leon Scroggins <scroggo@google.com>
diff --git a/include/android/SkAnimatedImage.h b/include/android/SkAnimatedImage.h
index 1bd9291..1c1a2fc 100644
--- a/include/android/SkAnimatedImage.h
+++ b/include/android/SkAnimatedImage.h
@@ -11,8 +11,11 @@
 #include "SkBitmap.h"
 #include "SkCodecAnimation.h"
 #include "SkDrawable.h"
+#include "SkMatrix.h"
+#include "SkRect.h"
 
 class SkAndroidCodec;
+class SkPicture;
 
 /**
  *  Thread unsafe drawable for drawing animated images (e.g. GIF).
@@ -24,6 +27,16 @@
      *
      *  Returns null on failure to allocate pixels. On success, this will
      *  decode the first frame. It will not animate until start() is called.
+     *
+     *  @param scaledSize Size to draw the image, possibly requiring scaling.
+     *  @param cropRect Rectangle to crop to after scaling.
+     *  @param postProcess Picture to apply after scaling and cropping.
+     */
+    static sk_sp<SkAnimatedImage> Make(std::unique_ptr<SkAndroidCodec>,
+            SkISize scaledSize, SkIRect cropRect, sk_sp<SkPicture> postProcess);
+
+    /**
+     *  Simpler version that uses the default size, no cropping, and no postProcess.
      */
     static sk_sp<SkAnimatedImage> Make(std::unique_ptr<SkAndroidCodec>);
 
@@ -47,6 +60,13 @@
     void reset();
 
     /**
+     *  Whether the animation is active.
+     *
+     *  If true, update() can be called to animate.
+     */
+    bool isRunning() const { return fRunning && !fFinished; }
+
+    /**
      *  Update the current time. If the image is animating, this may decode
      *  a new frame.
      *
@@ -71,6 +91,13 @@
     };
 
     std::unique_ptr<SkAndroidCodec> fCodec;
+    const SkISize                   fScaledSize;
+    const SkImageInfo               fDecodeInfo;
+    const SkIRect                   fCropRect;
+    const sk_sp<SkPicture>          fPostProcess;
+    const bool                      fSimple;     // no crop, scale, or postprocess
+    SkMatrix                        fMatrix;     // used only if !fSimple
+
     bool                            fFinished;
     bool                            fRunning;
     double                          fNowMS;
@@ -78,7 +105,8 @@
     Frame                           fActiveFrame;
     Frame                           fRestoreFrame;
 
-    SkAnimatedImage(std::unique_ptr<SkAndroidCodec>);
+    SkAnimatedImage(std::unique_ptr<SkAndroidCodec>, SkISize scaledSize,
+            SkImageInfo decodeInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess);
 
     typedef SkDrawable INHERITED;
 };
diff --git a/src/android/SkAnimatedImage.cpp b/src/android/SkAnimatedImage.cpp
index 25c020f..4c87083 100644
--- a/src/android/SkAnimatedImage.cpp
+++ b/src/android/SkAnimatedImage.cpp
@@ -10,13 +10,26 @@
 #include "SkCanvas.h"
 #include "SkCodec.h"
 #include "SkCodecPriv.h"
+#include "SkPicture.h"
+#include "SkPictureRecorder.h"
 
-sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec) {
+sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec,
+        SkISize scaledSize, SkIRect cropRect, sk_sp<SkPicture> postProcess) {
     if (!codec) {
         return nullptr;
     }
 
-    auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec)));
+    SkISize decodeSize = scaledSize;
+    auto decodeInfo = codec->getInfo();
+    if (codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP
+            && scaledSize.width()  < decodeInfo.width()
+            && scaledSize.height() < decodeInfo.height()) {
+        // libwebp can decode to arbitrary smaller sizes.
+        decodeInfo = decodeInfo.makeWH(decodeSize.width(), decodeSize.height());
+    }
+
+    auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize,
+                decodeInfo, cropRect, std::move(postProcess)));
     if (!image->fActiveFrame.fBitmap.getPixels()) {
         // tryAllocPixels failed.
         return nullptr;
@@ -25,31 +38,60 @@
     return image;
 }
 
+sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec) {
+    if (!codec) {
+        return nullptr;
+    }
+
+    const auto decodeInfo = codec->getInfo();
+    const auto scaledSize = decodeInfo.dimensions();
+    const auto cropRect   = SkIRect::MakeSize(scaledSize);
+    auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize,
+                decodeInfo, cropRect, nullptr));
+
+    if (!image->fActiveFrame.fBitmap.getPixels()) {
+        // tryAllocPixels failed.
+        return nullptr;
+    }
+
+    SkASSERT(image->fSimple);
+    return image;
+}
+
 // Sentinel value for starting at the beginning.
 static constexpr double kInit = -1.0;
 
-SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec)
+SkAnimatedImage::SkAnimatedImage(std::unique_ptr<SkAndroidCodec> codec, SkISize scaledSize,
+        SkImageInfo decodeInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess)
     : fCodec(std::move(codec))
+    , fScaledSize(scaledSize)
+    , fDecodeInfo(decodeInfo)
+    , fCropRect(cropRect)
+    , fPostProcess(std::move(postProcess))
+    , fSimple(fScaledSize == fDecodeInfo.dimensions() && !fPostProcess
+              && fCropRect == fDecodeInfo.bounds())
     , fFinished(false)
     , fRunning(false)
     , fNowMS(kInit)
     , fRemainingMS(kInit)
 {
-    if (!fActiveFrame.fBitmap.tryAllocPixels(fCodec->getInfo())) {
+    if (!fActiveFrame.fBitmap.tryAllocPixels(fDecodeInfo)) {
         return;
     }
 
+    if (!fSimple) {
+        fMatrix = SkMatrix::MakeTrans(-fCropRect.fLeft, -fCropRect.fTop);
+        float scaleX = (float) fScaledSize.width()  / fDecodeInfo.width();
+        float scaleY = (float) fScaledSize.height() / fDecodeInfo.height();
+        fMatrix.preConcat(SkMatrix::MakeScale(scaleX, scaleY));
+    }
     this->update(kInit);
 }
 
 SkAnimatedImage::~SkAnimatedImage() { }
 
 SkRect SkAnimatedImage::onGetBounds() {
-    return SkRect::Make(fCodec->getInfo().bounds());
-}
-
-void SkAnimatedImage::onDraw(SkCanvas* canvas) {
-    canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0);
+    return SkRect::MakeIWH(fCropRect.width(), fCropRect.height());
 }
 
 SkAnimatedImage::Frame::Frame()
@@ -216,7 +258,7 @@
     if (dst->getPixels()) {
         SkAssertResult(dst->setAlphaType(alphaType));
     } else {
-        auto info = fCodec->getInfo().makeAlphaType(alphaType);
+        auto info = fDecodeInfo.makeAlphaType(alphaType);
         if (!dst->tryAllocPixels(info)) {
             fFinished = true;
             return std::numeric_limits<double>::max();
@@ -236,3 +278,27 @@
     fActiveFrame.fDisposalMethod = frameInfo.fDisposalMethod;
     return fRemainingMS + fNowMS;
 }
+
+void SkAnimatedImage::onDraw(SkCanvas* canvas) {
+    if (fSimple) {
+        canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0);
+        return;
+    }
+
+    SkRect bounds = this->getBounds();
+    if (fPostProcess) {
+        canvas->saveLayer(&bounds, nullptr);
+    }
+    {
+        SkAutoCanvasRestore acr(canvas, fPostProcess);
+        canvas->concat(fMatrix);
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        paint.setFilterQuality(kLow_SkFilterQuality);
+        canvas->drawBitmap(fActiveFrame.fBitmap, 0, 0, &paint);
+    }
+    if (fPostProcess) {
+        canvas->drawPicture(fPostProcess);
+        canvas->restore();
+    }
+}