Initial version of rescaling async readback API

This is implemented at backend-neutral level and so misses some
opportunities to reduce the number of passes in the GPU backend.

Filter quality is interpreted as:
none - single nearest neighbor resampling
low - chain of bilinear resamplings. 2x up/down except for one
step which may be smaller than 2x.
medium - same as low
high - when both scale factors are up then same as low but with bicubic
filtering rather than linear. Otherwise, same as low.

Bug: skia:8962

Change-Id: I4467636c14b802d6a0d9b5c363c1ad9e87a1a44b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/213831
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/gm/asyncrescaleandread.cpp b/gm/asyncrescaleandread.cpp
new file mode 100644
index 0000000..32e8eb3
--- /dev/null
+++ b/gm/asyncrescaleandread.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm/gm.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkSurface.h"
+#include "include/gpu/GrContext.h"
+#include "src/core/SkAutoPixmapStorage.h"
+#include "src/core/SkConvertPixels.h"
+#include "tools/Resources.h"
+#include "tools/ToolUtils.h"
+
+// Draws the image to a surface, does a asyncRescaleAndReadPixels of the image, and then sticks
+// the result in a raster image.
+static sk_sp<SkImage> do_read_and_scale(
+        SkImage* image, const SkIRect& srcRect, const SkImageInfo& ii,
+        SkSurface::RescaleGamma rescaleGamma, SkFilterQuality quality,
+        std::function<sk_sp<SkSurface>(const SkImageInfo&)> makeSurface) {
+    SkBitmap bmp;
+    bmp.allocPixels(ii);
+    // Turn the image into a surface in order to call the read and rescale API
+    auto surf = makeSurface(image->imageInfo().makeWH(image->width(), image->height()));
+    if (!surf) {
+        return nullptr;
+    }
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kSrc);
+    surf->getCanvas()->drawImage(image, 0, 0, &paint);
+    struct Context {
+        SkPixmap fPixmap;
+        bool fCalled = false;
+    } context;
+    SkAssertResult(bmp.peekPixels(&context.fPixmap));
+    auto callback = [](void* c, const void* data, size_t rowBytes) {
+        auto context = reinterpret_cast<Context*>(c);
+        context->fCalled = true;
+        if (!data) {
+            context->fPixmap.reset();
+            return;
+        }
+        SkRectMemcpy(context->fPixmap.writable_addr(), context->fPixmap.rowBytes(), data, rowBytes,
+                     context->fPixmap.info().minRowBytes(), context->fPixmap.height());
+    };
+    surf->asyncRescaleAndReadPixels(ii, srcRect, rescaleGamma, quality, callback, &context);
+    while (!context.fCalled) {
+        // Only GPU should actually be asynchronous.
+        SkASSERT(surf->getCanvas()->getGrContext());
+        surf->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
+    }
+    return SkImage::MakeFromBitmap(bmp);
+}
+
+// Draws a grid of rescales. The columns are none, low, and high filter quality. The rows are
+// rescale in src gamma and rescale in linear gamma.
+static skiagm::DrawResult do_rescale_grid(SkCanvas* canvas, const char* imageFile,
+                                          const SkIRect& srcRect, int newW, int newH,
+                                          SkString* errorMsg) {
+    if (canvas->imageInfo().colorType() == kUnknown_SkColorType) {
+        *errorMsg = "Not supported on recording/vector backends.";
+        return skiagm::DrawResult::kSkip;
+    }
+    auto image = GetResourceAsImage(imageFile);
+    if (!image) {
+        errorMsg->printf("Could not load image file %s.", imageFile);
+        return skiagm::DrawResult::kFail;
+    }
+    const auto ii = canvas->imageInfo().makeWH(newW, newH);
+    auto makeSurface = [canvas](const SkImageInfo& info) { return canvas->makeSurface(info); };
+
+    canvas->save();
+    for (auto linear : {SkSurface::RescaleGamma::kSrc, SkSurface::RescaleGamma::kLinear}) {
+        canvas->save();
+        for (auto quality : {kNone_SkFilterQuality, kLow_SkFilterQuality, kHigh_SkFilterQuality}) {
+            auto rescaled =
+                    do_read_and_scale(image.get(), srcRect, ii, linear, quality, makeSurface);
+            canvas->drawImage(rescaled, 0, 0);
+            canvas->translate(newW, 0);
+        }
+        canvas->restore();
+        canvas->translate(0, newH);
+    }
+    canvas->restore();
+    return skiagm::DrawResult::kOk;
+}
+
+#define DEF_RESCALE_AND_READ_GM(IMAGE_FILE, TAG, SRC_RECT, W, H)                           \
+    DEF_SIMPLE_GM_CAN_FAIL(async_rescale_and_read_##TAG, canvas, errorMsg, 3 * W, 2 * H) { \
+        ToolUtils::draw_checkerboard(canvas, SK_ColorDKGRAY, SK_ColorLTGRAY, 25);          \
+        return do_rescale_grid(canvas, #IMAGE_FILE, SRC_RECT, W, H, errorMsg);             \
+    }
+
+DEF_RESCALE_AND_READ_GM(images/yellow_rose.webp, rose, SkIRect::MakeXYWH(100, 20, 100, 100),
+                        410, 410)
+
+DEF_RESCALE_AND_READ_GM(images/dog.jpg, dog_down, SkIRect::MakeXYWH(0, 10, 180, 150), 45, 45)
+DEF_RESCALE_AND_READ_GM(images/dog.jpg, dog_up, SkIRect::MakeWH(180, 180), 680, 400)
+
+DEF_RESCALE_AND_READ_GM(images/text.png, text_down, SkIRect::MakeWH(637, 105), (int)(0.7 * 637),
+                        (int)(0.7 * 105))
+DEF_RESCALE_AND_READ_GM(images/text.png, text_up, SkIRect::MakeWH(637, 105), (int)(1.2 * 637),
+                        (int)(1.2 * 105))
+DEF_RESCALE_AND_READ_GM(images/text.png, text_up_large, SkIRect::MakeXYWH(300, 0, 300, 105),
+                        (int)(2.4 * 300), (int)(2.4 * 105))
diff --git a/gn/gm.gni b/gn/gm.gni
index 07e0aea..02d1f86 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -24,6 +24,7 @@
   "$_gm/arcofzorro.cpp",
   "$_gm/arcto.cpp",
   "$_gm/arithmode.cpp",
+  "$_gm/asyncrescaleandread.cpp",
   "$_gm/atlastext.cpp",
   "$_gm/b_119394958.cpp",
   "$_gm/backdrop.cpp",
diff --git a/include/core/SkSurface.h b/include/core/SkSurface.h
index 481081b..ec9a260 100644
--- a/include/core/SkSurface.h
+++ b/include/core/SkSurface.h
@@ -660,12 +660,16 @@
     */
     bool readPixels(const SkBitmap& dst, int srcX, int srcY);
 
-    /** Makes pixel data available to caller, possibly asynchronously.
+    /** Makes pixel data available to caller, possibly asynchronously. Can perform rescaling.
 
         Currently asynchronous reads are only supported on the GPU backend and only when the
         underlying 3D API supports transfer buffers and CPU/GPU synchronization primitives. In all
         other cases this operates synchronously.
 
+        Data is read from the source rectangle, is optionally converted to a linear gamma, is
+        rescaled to the size indicated by 'info', is then converted to the color space, color type,
+        and alpha type of 'info'.
+
         When the pixel data is ready the caller's ReadPixelsCallback is called with a pointer to
         the data in the requested color type, alpha type, and color space. The data pointer is
         only valid for the duration of the callback.
@@ -674,18 +678,22 @@
 
         If the src rectangle is not contained by the bounds of the surface then failure occurs.
 
-        @param ct       color type of the read data
-        @param at       alpha type of the read data
-        @param cs       color space of the read data
-        @param srcRect  a subrectangle of the surface to read
-        @param callback function to call with result of the read.
-        @param context  passed to callback.
+        Failure is indicated by calling callback with a nullptr for 'data'.
+
+        @param info             info of the requested pixels
+        @param srcRect          subrectangle of surface to read
+        @param rescaleGamma     controls whether rescaling is done in the surface's gamma or whether
+                                the source data is transformed to a linear gamma before rescaling.
+        @param rescaleQuality   controls the quality (and cost) of the rescaling
+        @param callback         function to call with result of the read
+        @param context          passed to callback
      */
     using ReadPixelsContext = void*;
     using ReadPixelsCallback = void(ReadPixelsContext, const void* data, size_t rowBytes);
-    void asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
-                         const SkIRect& srcRect, ReadPixelsCallback callback,
-                         ReadPixelsContext context);
+    enum RescaleGamma : bool { kSrc, kLinear };
+    void asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
+                                   RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
+                                   ReadPixelsCallback callback, ReadPixelsContext context);
 
     /** Copies SkRect of pixels from the src SkPixmap to the SkSurface.
 
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
index 1d8cf35..2488c9d 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json
@@ -436,6 +436,54 @@
       "gm",
       "_",
       "p3",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
       "tiles_rt-8888",
       "gm",
       "_",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
index e9a89ed..6122ac5 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json
@@ -512,6 +512,54 @@
       "gm",
       "_",
       "p3",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
       "tiles_rt-8888",
       "gm",
       "_",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
index b777803..e281f91 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json
@@ -430,6 +430,54 @@
       "gm",
       "_",
       "p3",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
       "tiles_rt-8888",
       "gm",
       "_",
diff --git a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
index 3a09a25..db76096 100644
--- a/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
+++ b/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json
@@ -431,6 +431,54 @@
       "gm",
       "_",
       "p3",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up_large",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_text_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_up",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_dog_down",
+      "pic-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
+      "serialize-8888",
+      "gm",
+      "_",
+      "async_rescale_and_read_rose",
       "tiles_rt-8888",
       "gm",
       "_",
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index 656d354..d867261 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -551,7 +551,13 @@
     blacklist(['serialize-8888', 'gm', '_', test])
 
   # GM that requires raster-backed canvas
-  for test in ['complexclip4_bw', 'complexclip4_aa', 'p3']:
+  for test in ['complexclip4_bw', 'complexclip4_aa', 'p3',
+               'async_rescale_and_read_text_up_large',
+               'async_rescale_and_read_text_up',
+               'async_rescale_and_read_text_down',
+               'async_rescale_and_read_dog_up',
+               'async_rescale_and_read_dog_down',
+               'async_rescale_and_read_rose']:
     blacklist([      'pic-8888', 'gm', '_', test])
     blacklist(['serialize-8888', 'gm', '_', test])
 
diff --git a/resources/images/text.png b/resources/images/text.png
new file mode 100644
index 0000000..baff8a4
--- /dev/null
+++ b/resources/images/text.png
Binary files differ
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index cd560d0..742280d 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -1772,8 +1772,8 @@
     this->getRTOpList()->addOp(std::move(op), *this->caps());
 }
 
-bool GrRenderTargetContext::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
-                                            const SkIRect& srcRect, ReadPixelsCallback callback,
+bool GrRenderTargetContext::asyncReadPixels(const SkImageInfo& info, int srcX, int srcY,
+                                            ReadPixelsCallback callback,
                                             ReadPixelsContext context) {
     auto direct = fContext->priv().asDirectContext();
     if (!direct) {
@@ -1787,10 +1787,11 @@
     }
     // We currently don't know our own alpha type, we assume it's premul if we have an alpha channel
     // and opaque otherwise.
-    if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) && at != kPremul_SkAlphaType) {
+    if (!GrPixelConfigIsAlphaOnly(fRenderTargetProxy->config()) &&
+        info.alphaType() != kPremul_SkAlphaType) {
         return false;
     }
-    auto dstCT = SkColorTypeToGrColorType(ct);
+    auto dstCT = SkColorTypeToGrColorType(info.colorType());
     auto readCT = this->caps()->supportedReadPixelsColorType(fRenderTargetProxy->config(), dstCT);
     // Fail if we can't do a CPU conversion from readCT to dstCT.
     if (GrColorTypeToSkColorType(readCT) == kUnknown_SkColorType) {
@@ -1805,9 +1806,10 @@
         return false;
     }
 
-    sk_sp<GrColorSpaceXform> xform = GrColorSpaceXform::Make(this->colorSpaceInfo().colorSpace(),
-                                                             kPremul_SkAlphaType, cs.get(), at);
-
+    sk_sp<GrColorSpaceXform> xform =
+            GrColorSpaceXform::Make(this->colorSpaceInfo().colorSpace(), kPremul_SkAlphaType,
+                                    info.colorSpace(), info.alphaType());
+    const auto srcRect = SkIRect::MakeXYWH(srcX, srcY, info.width(), info.height());
     // Insert a draw to a temporary surface if we need to do a y-flip or color space conversion.
     if (this->origin() == kBottomLeft_GrSurfaceOrigin || xform) {
         sk_sp<GrTextureProxy> texProxy = sk_ref_sp(fRenderTargetProxy->asTextureProxy());
@@ -1834,7 +1836,8 @@
         }
         auto rtc = direct->priv().makeDeferredRenderTargetContext(
                 backendFormat, SkBackingFit::kApprox, srcRect.width(), srcRect.height(),
-                fRenderTargetProxy->config(), cs, 1, GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin);
+                fRenderTargetProxy->config(), info.refColorSpace(), 1, GrMipMapped::kNo,
+                kTopLeft_GrSurfaceOrigin);
         if (!rtc) {
             return false;
         }
@@ -1843,9 +1846,7 @@
                          SkRect::MakeWH(srcRect.width(), srcRect.height()), GrAA::kNo,
                          GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint, SkMatrix::I(),
                          std::move(xform));
-        return rtc->asyncReadPixels(ct, at, std::move(cs),
-                                    SkIRect::MakeWH(srcRect.width(), srcRect.height()), callback,
-                                    context);
+        return rtc->asyncReadPixels(info, 0, 0, callback, context);
     }
 
     size_t rowBytes = GrColorTypeBytesPerPixel(readCT) * srcRect.width();
@@ -1865,17 +1866,11 @@
         sk_sp<GrGpuBuffer> fBuffer;
         size_t fRowBytes;
     };
+    const auto readInfo = info.makeColorType(GrColorTypeToSkColorType(readCT));
     // Assumption is that the caller would like to flush. We could take a parameter or require an
     // explicit flush from the caller. We'd have to have a way to defer attaching the finish
     // callback to GrGpu until after the next flush that flushes our op list, though.
-    auto* finishContext =
-            new FinishContext{SkImageInfo::Make(srcRect.width(), srcRect.height(),
-                                                GrColorTypeToSkColorType(readCT), at, cs),
-                              SkImageInfo::Make(srcRect.width(), srcRect.height(), ct, at, cs),
-                              callback,
-                              context,
-                              buffer,
-                              rowBytes};
+    auto* finishContext = new FinishContext{readInfo, info, callback, context, buffer, rowBytes};
     auto finishCallback = [](GrGpuFinishedContext c) {
         auto context = reinterpret_cast<const FinishContext*>(c);
         void* data = context->fBuffer->map();
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 6f39325..0174bbf 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -404,8 +404,8 @@
 
     using ReadPixelsCallback = SkSurface::ReadPixelsCallback;
     using ReadPixelsContext = SkSurface::ReadPixelsContext;
-    bool asyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& srcRect,
-                         ReadPixelsCallback, ReadPixelsContext);
+    bool asyncReadPixels(const SkImageInfo& info, int srcX, int srcY, ReadPixelsCallback,
+                         ReadPixelsContext);
 
     /**
      * After this returns any pending surface IO will be issued to the backend 3D API and
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index cd214e8..92a300f 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -6,6 +6,7 @@
  */
 
 #include <atomic>
+#include <cmath>
 #include "include/core/SkCanvas.h"
 #include "include/core/SkFontLCDConfig.h"
 #include "include/gpu/GrBackendSurface.h"
@@ -87,13 +88,13 @@
     }
 }
 
-void SkSurface_Base::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
-                                       const SkIRect& rect, ReadPixelsCallback callback,
-                                       ReadPixelsContext context) {
-    auto info = SkImageInfo::Make(rect.width(), rect.height(), ct, at, std::move(cs));
+void SkSurface_Base::onAsyncReadPixels(const SkImageInfo& info, int srcX, int srcY,
+                                       ReadPixelsCallback callback, ReadPixelsContext context) {
+    SkASSERT(SkIRect::MakeWH(this->width(), this->height())
+                     .contains(SkIRect::MakeXYWH(srcX, srcY, info.width(), info.height())));
     SkAutoPixmapStorage pm;
     pm.alloc(info);
-    if (this->readPixels(pm, rect.fLeft, rect.fTop)) {
+    if (this->readPixels(pm, srcX, srcY)) {
         callback(context, pm.addr(), pm.rowBytes());
     } else {
         callback(context, nullptr, 0);
@@ -221,16 +222,104 @@
     return bitmap.peekPixels(&pm) && this->readPixels(pm, srcX, srcY);
 }
 
-void SkSurface::asyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
-                                const SkIRect& srcRect, ReadPixelsCallback callback,
-                                ReadPixelsContext context) {
-    auto dstII = SkImageInfo::Make(srcRect.width(), srcRect.height(), ct, at, cs);
+void SkSurface::asyncRescaleAndReadPixels(const SkImageInfo& info, const SkIRect& srcRect,
+                                          RescaleGamma rescaleGamma, SkFilterQuality rescaleQuality,
+                                          ReadPixelsCallback callback, ReadPixelsContext context) {
     if (!SkIRect::MakeWH(this->width(), this->height()).contains(srcRect) ||
-        !SkImageInfoIsValid(dstII)) {
+        !SkImageInfoIsValid(info)) {
         callback(context, nullptr, 0);
         return;
     }
-    asSB(this)->onAsyncReadPixels(ct, at, std::move(cs), srcRect, callback, context);
+    int srcW = srcRect.width();
+    int srcH = srcRect.height();
+    float sx = (float)info.width() / srcW;
+    float sy = (float)info.height() / srcH;
+    // How many bilerp/bicubic steps to do in X and Y. + means upscaling, - means downscaling.
+    int stepsX;
+    int stepsY;
+    if (rescaleQuality > kNone_SkFilterQuality) {
+        stepsX = static_cast<int>((sx > 1.f) ? std::ceil(std::log2f(sx))
+                                             : std::floor(std::log2f(sx)));
+        stepsY = static_cast<int>((sy > 1.f) ? std::ceil(std::log2f(sy))
+                                             : std::floor(std::log2f(sy)));
+    } else {
+        stepsX = sx != 1.f;
+        stepsY = sy != 1.f;
+    }
+
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kSrc);
+    if (stepsX < 0 || stepsY < 0) {
+        // Don't trigger MIP generation. We don't currently have a way to trigger bicubic for
+        // downscaling draws.
+        rescaleQuality = std::min(rescaleQuality, kLow_SkFilterQuality);
+    }
+    paint.setFilterQuality(rescaleQuality);
+    sk_sp<SkSurface> src(SkRef(this));
+    int srcX = srcRect.fLeft;
+    int srcY = srcRect.fTop;
+    if (rescaleGamma == SkSurface::RescaleGamma::kLinear &&
+        !this->getCanvas()->imageInfo().colorSpace()->gammaIsLinear()) {
+        auto cs = this->getCanvas()->imageInfo().colorSpace()->makeLinearGamma();
+        // Promote to F16 color type to preserve precision.
+        auto ii = SkImageInfo::Make(srcW, srcH, kRGBA_F16_SkColorType,
+                                    this->getCanvas()->imageInfo().alphaType(), std::move(cs));
+        auto linearSurf = this->makeSurface(ii);
+        if (!linearSurf) {
+            // Maybe F16 isn't supported? Try again with original color type.
+            ii = ii.makeColorType(this->getCanvas()->imageInfo().colorType());
+            linearSurf = this->makeSurface(ii);
+            if (!linearSurf) {
+                callback(context, nullptr, 0);
+                return;
+            }
+        }
+        this->draw(linearSurf->getCanvas(), -srcX, -srcY, &paint);
+        src = std::move(linearSurf);
+        srcX = 0;
+        srcY = 0;
+    }
+    while (stepsX || stepsY) {
+        int nextW = info.width();
+        int nextH = info.height();
+        if (stepsX < 0) {
+            nextW = info.width() << (-stepsX - 1);
+            stepsX++;
+        } else if (stepsX != 0) {
+            if (stepsX > 1) {
+                nextW = srcW * 2;
+            }
+            --stepsX;
+        }
+        if (stepsY < 0) {
+            nextH = info.height() << (-stepsY - 1);
+            stepsY++;
+        } else if (stepsY != 0) {
+            if (stepsY > 1) {
+                nextH = srcH * 2;
+            }
+            --stepsY;
+        }
+        auto ii = src->getCanvas()->imageInfo().makeWH(nextW, nextH);
+        if (!stepsX && !stepsY) {
+            // Might as well fold conversion to final info in the last step.
+            ii = info;
+        }
+        auto next = this->makeSurface(ii);
+        if (!next) {
+            callback(context, nullptr, 0);
+            return;
+        }
+        next->getCanvas()->drawImageRect(src->makeImageSnapshot(),
+                                         SkIRect::MakeXYWH(srcX, srcY, srcW, srcH),
+                                         SkRect::MakeWH((float)nextW, (float)nextH), &paint,
+                                         SkCanvas::kFast_SrcRectConstraint);
+        src = std::move(next);
+        srcX = srcY = 0;
+        srcW = nextW;
+        srcH = nextH;
+    }
+    static_cast<SkSurface_Base*>(src.get())->onAsyncReadPixels(info, srcX, srcY, callback, context);
 }
 
 void SkSurface::writePixels(const SkPixmap& pmap, int x, int y) {
diff --git a/src/image/SkSurface_Base.h b/src/image/SkSurface_Base.h
index 1c895ee..37583a2 100644
--- a/src/image/SkSurface_Base.h
+++ b/src/image/SkSurface_Base.h
@@ -46,11 +46,10 @@
     virtual void onWritePixels(const SkPixmap&, int x, int y) = 0;
 
     /**
-     * Default implementation does a synchronous read and calls the callback.
+     * Default implementation does a read and then calls the callback.
      */
-    virtual void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>,
-                                   const SkIRect& srcRect, ReadPixelsCallback callback,
-                                   ReadPixelsContext context);
+    virtual void onAsyncReadPixels(const SkImageInfo&, int srcX, int srcY,
+                                   ReadPixelsCallback callback, ReadPixelsContext context);
 
     /**
      *  Default implementation:
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index 2690a6d..47fbad3 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -132,17 +132,16 @@
     fDevice->writePixels(src, x, y);
 }
 
-void SkSurface_Gpu::onAsyncReadPixels(SkColorType ct, SkAlphaType at, sk_sp<SkColorSpace> cs,
-                                      const SkIRect& srcRect, ReadPixelsCallback callback,
-                                      ReadPixelsContext context) {
+void SkSurface_Gpu::onAsyncReadPixels(const SkImageInfo& info, int srcX, int srcY,
+                                      ReadPixelsCallback callback, ReadPixelsContext context) {
+    SkASSERT(SkIRect::MakeWH(this->width(), this->height())
+                     .contains(SkIRect::MakeXYWH(srcX, srcY, info.width(), info.height())));
     auto* rtc = fDevice->accessRenderTargetContext();
-    if (!rtc->caps()->transferBufferSupport()) {
-        INHERITED::onAsyncReadPixels(ct, at, cs, srcRect, callback, context);
+    if (!rtc->caps()->transferBufferSupport() ||
+        !rtc->asyncReadPixels(info, srcX, srcY, callback, context)) {
+        INHERITED::onAsyncReadPixels(info, srcX, srcY, callback, context);
         return;
     }
-    if (!rtc->asyncReadPixels(ct, at, std::move(cs), srcRect, callback, context)) {
-        callback(context, nullptr, 0);
-    }
 }
 
 // Create a new render target and, if necessary, copy the contents of the old
diff --git a/src/image/SkSurface_Gpu.h b/src/image/SkSurface_Gpu.h
index d10947c..42641f6 100644
--- a/src/image/SkSurface_Gpu.h
+++ b/src/image/SkSurface_Gpu.h
@@ -30,8 +30,8 @@
     sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override;
     sk_sp<SkImage> onNewImageSnapshot(const SkIRect* subset) override;
     void onWritePixels(const SkPixmap&, int x, int y) override;
-    void onAsyncReadPixels(SkColorType, SkAlphaType, sk_sp<SkColorSpace>, const SkIRect& rect,
-                           ReadPixelsCallback, ReadPixelsContext) override;
+    void onAsyncReadPixels(const SkImageInfo& info, int srcX, int srcY, ReadPixelsCallback,
+                           ReadPixelsContext) override;
 
     void onCopyOnWrite(ContentChangeMode) override;
     void onDiscard() override;
diff --git a/tests/ReadPixelsTest.cpp b/tests/ReadPixelsTest.cpp
index dcb90d4..96c435f 100644
--- a/tests/ReadPixelsTest.cpp
+++ b/tests/ReadPixelsTest.cpp
@@ -803,8 +803,10 @@
                                                  kPremul_SkAlphaType, readCS);
                         result.alloc(info);
                         memset(result.writable_addr(), 0xAB, result.computeByteSize());
-                        surf->asyncReadPixels(readCT, kPremul_SkAlphaType, readCS, rect, callback,
-                                              &context);
+                        // Rescale quality and linearity don't matter since we're doing a non-
+                        // scaling readback.
+                        surf->asyncRescaleAndReadPixels(info, rect, SkSurface::RescaleGamma::kSrc,
+                                                        kNone_SkFilterQuality, callback, &context);
                         while (!context.fCalled) {
                             ctxInfo.grContext()->checkAsyncWorkCompletion();
                         }