Add support for readPixels on gpu backend ImageGenerators.

Currently SkImageGenerators that use GPU backend texture do not
implement onGetPixels and instead only implement onGenerateTexture.
The readPixels call on SkImage_Lazy only tries to use getPixels
call and thus fails for all GPU backend generators.

This CL changes the SkImage_Lazy::getROPixels to try and call
generateTexture if ImageGenerator::getPixels call fails.
If we get a texture we then use our normal
SurfaceContext::readPixels calls to read the pixels from the
entire texture into the target Bitmap.

Adding this logic to happen in getROPixels should make it
work for readPixels calls, as well as when drawing a
SkPicture containing a texture-backed lazy image
into a raster surface, provided GrDirectContext is passed
into the drawPicture and related calls (see follow-on CL).

Also add support for the use of this readPixels in CanvasKit.

Change-Id: Ia6fdf4a9216d4e6d136c13aac7820d986ced2fd9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/557100
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index f0a4f84..b8570f8 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -8,7 +8,7 @@
 
 ### Added
  - The following path methods: `addCircle`, `Path::CanInterpolate`, and `Path::MakeFromPathInterpolation`.
- - The following ImageFilter factory methods: `MakeBlend`, `MakeDilate`, `MakeDisplacementMap`, 
+ - The following ImageFilter factory methods: `MakeBlend`, `MakeDilate`, `MakeDisplacementMap`,
    `MakeDropShadow`, `MakeDropShadowOnly`, `MakeErode`, `MakeImage`, `MakeOffset`, and `MakeShader`.
  - The `MakeLuma` ColorFilter factory method.
  - The `fontVariations` TextStyle property.
@@ -16,6 +16,10 @@
 ### Changed
  - Updated `dtslint`, `typescript`, and `@webgpu/types` versions, used for testing index.d.ts types.
 
+### Fixed
+ - `Image.readPixels` should work on `Image`s created with `MakeLazyImageFromTextureSource`
+   (https://github.com/flutter/flutter/issues/103803)
+
 ### Known Issues
  - `ImageFilter.MakeDisplacementMap` is not behaving as expected in certain circumstances.
 
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index ffde1f4..e7d58b0 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -1284,7 +1284,7 @@
             SkImageInfo dstInfo = toSkImageInfo(di);
 
             return self.readPixels(dstInfo, pixels, dstRowBytes, srcX, srcY);
-        }))
+        }), allow_raw_pointers())
         .function("restore", &SkCanvas::restore)
         .function("restoreToCount", &SkCanvas::restoreToCount)
         .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
@@ -1516,17 +1516,22 @@
                                  WASMPointerF32 mPtr)->sk_sp<SkShader> {
             return self->makeShader(tx, ty, {filter, mipmap}, OptionalMatrix(mPtr));
         }), allow_raw_pointers())
+#if defined(ENABLE_GPU)
+        .function("_readPixels", optional_override([](sk_sp<SkImage> self,
+                                 SimpleImageInfo sii, WASMPointerU8 pPtr,
+                                 size_t dstRowBytes, int srcX, int srcY,
+                                 GrDirectContext* dContext)->bool {
+            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+            SkImageInfo ii = toSkImageInfo(sii);
+            return self->readPixels(dContext, ii, pixels, dstRowBytes, srcX, srcY);
+        }), allow_raw_pointers())
+#endif
         .function("_readPixels", optional_override([](sk_sp<SkImage> self,
                                  SimpleImageInfo sii, WASMPointerU8 pPtr,
                                  size_t dstRowBytes, int srcX, int srcY)->bool {
             uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
             SkImageInfo ii = toSkImageInfo(sii);
-            // TODO(adlai) Migrate CanvasKit API to require DirectContext arg here.
-            GrDirectContext* dContext = nullptr;
-#ifdef ENABLE_GPU
-            dContext = GrAsDirectContext(as_IB(self.get())->context());
-#endif
-            return self->readPixels(dContext, ii, pixels, dstRowBytes, srcX, srcY);
+            return self->readPixels(nullptr, ii, pixels, dstRowBytes, srcX, srcY);
         }), allow_raw_pointers())
         .function("width", &SkImage::width);
 
diff --git a/modules/canvaskit/cpu.js b/modules/canvaskit/cpu.js
index 9f029aa..1aed6ce 100644
--- a/modules/canvaskit/cpu.js
+++ b/modules/canvaskit/cpu.js
@@ -104,5 +104,10 @@
     CanvasKit.setCurrentContext = CanvasKit.setCurrentContext || function() {
        // no op if this is a cpu-only build.
     };
+
+    CanvasKit.getCurrentGrDirectContext = CanvasKit.getCurrentGrDirectContext || function() {
+      // No GrDirectContexts without a GPU backend.
+      return null;
+    };
   });
 }(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index aa5a0dc..0c03eb7 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -38,7 +38,8 @@
   GetWebGLContext: function() {},
   MakeCanvas: function() {},
   MakeCanvasSurface: function() {},
-  MakeGrContext: function() {},
+  MakeGrContext: function() {}, // deprecated
+  MakeWebGLContext: function() {},
   /** @return {CanvasKit.AnimatedImage} */
   MakeAnimatedImageFromEncoded: function() {},
   /** @return {CanvasKit.Image} */
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
index 51ce68b..241afd2 100644
--- a/modules/canvaskit/interface.js
+++ b/modules/canvaskit/interface.js
@@ -396,7 +396,7 @@
     return this._makeShaderOptions(xTileMode, yTileMode, filterMode, mipmapMode, localMatrixPtr);
   };
 
-  function readPixels(source, srcX, srcY, imageInfo, destMallocObj, bytesPerRow) {
+  function readPixels(source, srcX, srcY, imageInfo, destMallocObj, bytesPerRow, grCtx) {
     if (!bytesPerRow) {
       bytesPerRow = 4 * imageInfo['width'];
       if (imageInfo['colorType'] === CanvasKit.ColorType.RGBA_F16) {
@@ -414,7 +414,13 @@
       pPtr = CanvasKit._malloc(pBytes);
     }
 
-    if (!source._readPixels(imageInfo, pPtr, bytesPerRow, srcX, srcY)) {
+    var rv;
+    if (grCtx) {
+      rv = source._readPixels(imageInfo, pPtr, bytesPerRow, srcX, srcY, grCtx);
+    } else {
+      rv = source._readPixels(imageInfo, pPtr, bytesPerRow, srcX, srcY);
+    }
+    if (!rv) {
       Debug('Could not read pixels with the given inputs');
       if (!destMallocObj) {
         CanvasKit._free(pPtr);
@@ -450,7 +456,8 @@
 
   CanvasKit.Image.prototype.readPixels = function(srcX, srcY, imageInfo, destMallocObj,
                                                   bytesPerRow) {
-    return readPixels(this, srcX, srcY, imageInfo, destMallocObj, bytesPerRow);
+    var grCtx = CanvasKit.getCurrentGrDirectContext();
+    return readPixels(this, srcX, srcY, imageInfo, destMallocObj, bytesPerRow, grCtx);
   };
 
   // Accepts an array of four numbers in the range of 0-1 representing a 4f color
diff --git a/modules/canvaskit/tests/bazel/core_test.js b/modules/canvaskit/tests/bazel/core_test.js
index ec62cd2..d1c5183 100644
--- a/modules/canvaskit/tests/bazel/core_test.js
+++ b/modules/canvaskit/tests/bazel/core_test.js
@@ -1479,6 +1479,48 @@
         });
     });
 
+    gm('MakeLazyImageFromTextureSource_readPixels', (canvas) => {
+        if (!CanvasKit.gpu) {
+            return;
+        }
+
+        // This makes an offscreen <img> with the provided source.
+        const imageEle = new Image();
+        imageEle.src = '/assets/mandrill_512.png';
+
+        // We need to wait until the image is loaded before the texture can use it. For good
+        // measure, we also wait for it to be decoded.
+        return imageEle.decode().then(() => {
+            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
+            const imgInfo = {
+              'width': 512,
+              'height': 512,
+              'alphaType': CanvasKit.AlphaType.Unpremul,
+              'colorType': CanvasKit.ColorType.RGBA_8888,
+              'colorSpace': CanvasKit.ColorSpace.SRGB
+            };
+            const src = CanvasKit.XYWHRect(0, 0, 512, 512);
+            const pixels = img.readPixels(0, 0, imgInfo);
+            expect(pixels).toBeTruthy();
+            // Make a new image from reading the pixels of the texture-backed image,
+            // then draw that new image to a canvas and verify it works.
+            const newImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4);
+            canvas.drawImageRectCubic(newImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
+            canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3);
+
+            const font = new CanvasKit.Font(null, 20);
+            const paint = new CanvasKit.Paint();
+            paint.setColor(CanvasKit.BLACK);
+            canvas.drawText('original', 100, 280, paint, font);
+            canvas.drawText('readPixels', 356, 280, paint, font);
+
+            img.delete();
+            newImg.delete();
+            font.delete();
+            paint.delete();
+        });
+    })
+
     it('encodes images in three different ways', () => {
         // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with
         // the colors listed below.
diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js
index 19c8c3d..590d63f 100644
--- a/modules/canvaskit/tests/core.spec.js
+++ b/modules/canvaskit/tests/core.spec.js
@@ -1501,6 +1501,48 @@
         });
     });
 
+    gm('MakeLazyImageFromTextureSource_readPixels', (canvas) => {
+        if (!CanvasKit.gpu) {
+            return;
+        }
+
+        // This makes an offscreen <img> with the provided source.
+        const imageEle = new Image();
+        imageEle.src = '/assets/mandrill_512.png';
+
+        // We need to wait until the image is loaded before the texture can use it. For good
+        // measure, we also wait for it to be decoded.
+        return imageEle.decode().then(() => {
+            const img = CanvasKit.MakeLazyImageFromTextureSource(imageEle);
+            const imgInfo = {
+              'width': 512,
+              'height': 512,
+              'alphaType': CanvasKit.AlphaType.Unpremul,
+              'colorType': CanvasKit.ColorType.RGBA_8888,
+              'colorSpace': CanvasKit.ColorSpace.SRGB
+            };
+            const src = CanvasKit.XYWHRect(0, 0, 512, 512);
+            const pixels = img.readPixels(0, 0, imgInfo);
+            expect(pixels).toBeTruthy();
+            // Make a new image from reading the pixels of the texture-backed image,
+            // then draw that new image to a canvas and verify it works.
+            const newImg = CanvasKit.MakeImage(imgInfo, pixels, 512 * 4);
+            canvas.drawImageRectCubic(newImg, src, CanvasKit.XYWHRect(256, 0, 256, 256), 1/3, 1/3);
+            canvas.drawImageRectCubic(img, src, CanvasKit.XYWHRect(0, 0, 256, 256), 1/3, 1/3);
+
+            const font = new CanvasKit.Font(null, 20);
+            const paint = new CanvasKit.Paint();
+            paint.setColor(CanvasKit.BLACK);
+            canvas.drawText('original', 100, 280, paint, font);
+            canvas.drawText('readPixels', 356, 280, paint, font);
+
+            img.delete();
+            newImg.delete();
+            font.delete();
+            paint.delete();
+        });
+    })
+
     it('encodes images in three different ways', () => {
         // This creates and draws an Image that is 1 pixel wide, 4 pixels tall with
         // the colors listed below.
diff --git a/modules/canvaskit/webgl.js b/modules/canvaskit/webgl.js
index 0e1a6a6..27773d7 100644
--- a/modules/canvaskit/webgl.js
+++ b/modules/canvaskit/webgl.js
@@ -75,6 +75,8 @@
         }
         // This context is an index into the emscripten-provided GL wrapper.
         grCtx._context = ctx;
+        // Save this so it is easy to access (e.g. Image.readPixels)
+        GL.currentContext.grDirectContext = grCtx;
         return grCtx;
       }
 
@@ -142,7 +144,7 @@
           throw 'failed to create webgl context: err ' + ctx;
         }
 
-        var grcontext = this.MakeGrContext(ctx);
+        var grcontext = this.MakeWebGLContext(ctx);
 
         // Note that canvas.width/height here is used because it gives the size of the buffer we're
         // rendering into. This may not be the same size the element is displayed on the page, which
@@ -346,5 +348,12 @@
         }
         return GL.makeContextCurrent(ctx);
       };
+
+      CanvasKit.getCurrentGrDirectContext = function() {
+        if (GL.currentContext) {
+          return GL.currentContext.grDirectContext;
+        }
+        return null;
+      };
     });
 }(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/src/image/SkImage_Lazy.cpp b/src/image/SkImage_Lazy.cpp
index fb394dc..96bb33b 100644
--- a/src/image/SkImage_Lazy.cpp
+++ b/src/image/SkImage_Lazy.cpp
@@ -24,6 +24,7 @@
 #include "src/gpu/ResourceKey.h"
 #include "src/gpu/ganesh/GrCaps.h"
 #include "src/gpu/ganesh/GrColorSpaceXform.h"
+#include "src/gpu/ganesh/GrDirectContextPriv.h"
 #include "src/gpu/ganesh/GrGpuResourcePriv.h"
 #include "src/gpu/ganesh/GrPaint.h"
 #include "src/gpu/ganesh/GrProxyProvider.h"
@@ -128,7 +129,7 @@
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
-bool SkImage_Lazy::getROPixels(GrDirectContext*, SkBitmap* bitmap,
+bool SkImage_Lazy::getROPixels(GrDirectContext* ctx, SkBitmap* bitmap,
                                SkImage::CachingHint chint) const {
     auto check_output_bitmap = [bitmap]() {
         SkASSERT(bitmap->isImmutable());
@@ -145,23 +146,61 @@
     if (SkImage::kAllow_CachingHint == chint) {
         SkPixmap pmap;
         SkBitmapCache::RecPtr cacheRec = SkBitmapCache::Alloc(desc, this->imageInfo(), &pmap);
-        if (!cacheRec || !ScopedGenerator(fSharedGenerator)->getPixels(pmap)) {
+        if (!cacheRec) {
+            return false;
+        }
+        bool success = false;
+        {   // make sure ScopedGenerator goes out of scope before we try readPixelsProxy
+            success = ScopedGenerator(fSharedGenerator)->getPixels(pmap);
+        }
+        if (!success && !this->readPixelsProxy(ctx, pmap)) {
             return false;
         }
         SkBitmapCache::Add(std::move(cacheRec), bitmap);
         this->notifyAddedToRasterCache();
     } else {
-        if (!bitmap->tryAllocPixels(this->imageInfo()) ||
-            !ScopedGenerator(fSharedGenerator)->getPixels(bitmap->pixmap())) {
+        if (!bitmap->tryAllocPixels(this->imageInfo())) {
+            return false;
+        }
+        bool success = false;
+        {   // make sure ScopedGenerator goes out of scope before we try readPixelsProxy
+            success = ScopedGenerator(fSharedGenerator)->getPixels(bitmap->pixmap());
+        }
+        if (!success && !this->readPixelsProxy(ctx, bitmap->pixmap())) {
             return false;
         }
         bitmap->setImmutable();
     }
-
     check_output_bitmap();
     return true;
 }
 
+bool SkImage_Lazy::readPixelsProxy(GrDirectContext* ctx, const SkPixmap& pixmap) const {
+#if SK_SUPPORT_GPU
+    if (!ctx) {
+        return false;
+    }
+    GrSurfaceProxyView view = this->lockTextureProxyView(ctx,
+                                                         GrImageTexGenPolicy::kDraw,
+                                                         GrMipmapped::kNo);
+
+    if (!view) {
+        return false;
+    }
+
+    GrColorType ct = this->colorTypeOfLockTextureProxy(ctx->priv().caps());
+    GrColorInfo colorInfo(ct, this->alphaType(), this->refColorSpace());
+    auto sContext = ctx->priv().makeSC(std::move(view), colorInfo);
+    if (!sContext) {
+        return false;
+    }
+    size_t rowBytes = this->imageInfo().minRowBytes();
+    return sContext->readPixels(ctx, {this->imageInfo(), pixmap.writable_addr(), rowBytes}, {0, 0});
+#else
+    return false;
+#endif // SK_SUPPORT_GPU
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////////////
 
 bool SkImage_Lazy::onReadPixels(GrDirectContext* dContext,
diff --git a/src/image/SkImage_Lazy.h b/src/image/SkImage_Lazy.h
index c15ed17..1c59b7d 100644
--- a/src/image/SkImage_Lazy.h
+++ b/src/image/SkImage_Lazy.h
@@ -67,6 +67,7 @@
 
 private:
     void addUniqueIDListener(sk_sp<SkIDChangeListener>) const;
+    bool readPixelsProxy(GrDirectContext*, const SkPixmap&) const;
 #if SK_SUPPORT_GPU
     std::tuple<GrSurfaceProxyView, GrColorType> onAsView(GrRecordingContext*,
                                                          GrMipmapped,