Accept Flat Uint32Array, Float32Array, or 2d Float32Array as the color argument to MakeLinearGradient

TODO:
  1. Accept a Color builder or a TypeArray from CanvasKit.Malloc
  2. Apply the same treatment to all other gradient functions, MakeSkVertices, and drawAtlas
Change-Id: I94fa67a3c00d7b1ecdc004af4ffd3193404c1a30
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294707
Commit-Queue: Nathaniel Nifong <nifong@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index a14d54b..5047e3f 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -6,6 +6,15 @@
 
 ## [Unreleased]
 
+### Changed
+ - In all places where color arrays are accepted (gradient makers, drawAtlas, and MakeSkVertices),
+   You can now provide either flat Float32Arrays of float colors, Uint32Arrays of int colors, or 
+   2d Arrays of Float32Array(4) colors. The one thing you should not pass is an Array of numbers,
+   since canvaskit wouldn't be able to tell whether they're ints or floats without checking them all.
+   The fastest choice for gradients is the flat Float32Array, the fastest choice for drawAtlas and
+   MakeSkVertices is the flat Uint32Array.
+ - Color arrays may also be objects created with CanvasKit.Malloc
+
 ## [0.16.2] - 2020-06-05
 
 ### Fixed
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index f91090b..9365409 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -772,19 +772,33 @@
 
         return SkImage::MakeRasterData(info, pixelData, rowBytes);
     }), allow_raw_pointers());
+
+    // Here and in other gradient functions, cPtr is a pointer to an array of data
+    // representing colors. whether this is an array of SkColor or SkColor4f is indicated
+    // by the colorType argument. Only RGBA_8888 and RGBA_F32 are accepted.
     function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
-                                uintptr_t /* SkColor4f*  */ cPtr,
+                                uintptr_t cPtr, SkColorType colorType,
                                 uintptr_t /* SkScalar*  */ pPtr,
                                 int count, SkTileMode mode, uint32_t flags,
                                 uintptr_t /* SkScalar*  */ mPtr,
                                 sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
         SkPoint points[] = { start, end };
         // See comment above for uintptr_t explanation
-        const SkColor4f*  colors  = reinterpret_cast<const SkColor4f*>(cPtr);
         const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
         OptionalMatrix localMatrix(mPtr);
-        return SkGradientShader::MakeLinear(points, colors, colorSpace, positions, count,
+
+        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+            const SkColor4f* colors  = reinterpret_cast<const SkColor4f*>(cPtr);
+            return SkGradientShader::MakeLinear(points, colors, colorSpace, positions, count,
                                                 mode, flags, &localMatrix);
+        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+            const SkColor* colors  = reinterpret_cast<const SkColor*>(cPtr);
+            return SkGradientShader::MakeLinear(points, colors, positions, count,
+                                                mode, flags, &localMatrix);
+        } else {
+            SkDebugf("%d is not an accepted colorType\n", colorType);
+            return nullptr;
+        }
     }), allow_raw_pointers());
 #ifdef SK_SERIALIZE_SKP
     function("_MakeSkPicture", optional_override([](uintptr_t /* unint8_t* */ dPtr,
@@ -797,20 +811,29 @@
     }), allow_raw_pointers());
 #endif
     function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
-                                uintptr_t /* SkColor4f*  */ cPtr,
+                                uintptr_t cPtr, SkColorType colorType,
                                 uintptr_t /* SkScalar*  */ pPtr,
                                 int count, SkTileMode mode, uint32_t flags,
                                 uintptr_t /* SkScalar*  */ mPtr,
                                 sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
         // See comment above for uintptr_t explanation
-        const SkColor4f*  colors  = reinterpret_cast<const SkColor4f*>(cPtr);
         const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
         OptionalMatrix localMatrix(mPtr);
-        return SkGradientShader::MakeRadial(center, radius, colors, colorSpace, positions, count,
-                                            mode, flags, &localMatrix);
+        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+            const SkColor4f* colors  = reinterpret_cast<const SkColor4f*>(cPtr);
+            return SkGradientShader::MakeRadial(center, radius, colors, colorSpace, positions, count,
+                                                mode, flags, &localMatrix);
+        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+            const SkColor* colors  = reinterpret_cast<const SkColor*>(cPtr);
+            return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+                                                mode, flags, &localMatrix);
+        } else {
+            SkDebugf("%d is not an accepted colorType\n", colorType);
+            return nullptr;
+        }
     }), allow_raw_pointers());
     function("_MakeSweepGradientShader", optional_override([](SkScalar cx, SkScalar cy,
-                                uintptr_t /* SkColor4f*  */ cPtr,
+                                uintptr_t cPtr, SkColorType colorType,
                                 uintptr_t /* SkScalar*  */ pPtr,
                                 int count, SkTileMode mode,
                                 SkScalar startAngle, SkScalar endAngle,
@@ -818,28 +841,49 @@
                                 uintptr_t /* SkScalar*  */ mPtr,
                                 sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
         // See comment above for uintptr_t explanation
-        const SkColor4f*  colors  = reinterpret_cast<const SkColor4f*>(cPtr);
         const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
         OptionalMatrix localMatrix(mPtr);
-        return SkGradientShader::MakeSweep(cx, cy, colors, colorSpace, positions, count,
-                                           mode, startAngle, endAngle, flags,
-                                           &localMatrix);
+        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+            const SkColor4f* colors  = reinterpret_cast<const SkColor4f*>(cPtr);
+            return SkGradientShader::MakeSweep(cx, cy, colors, colorSpace, positions, count,
+                                               mode, startAngle, endAngle, flags,
+                                               &localMatrix);
+        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+            const SkColor* colors  = reinterpret_cast<const SkColor*>(cPtr);
+            return SkGradientShader::MakeSweep(cx, cy, colors, positions, count,
+                                               mode, startAngle, endAngle, flags,
+                                               &localMatrix);
+        } else {
+            SkDebugf("%d is not an accepted colorType\n", colorType);
+            return nullptr;
+        }
     }), allow_raw_pointers());
     function("_MakeTwoPointConicalGradientShader", optional_override([](
                                 SkPoint start, SkScalar startRadius,
                                 SkPoint end, SkScalar endRadius,
-                                uintptr_t /* SkColor4f*  */ cPtr,
+                                uintptr_t cPtr, SkColorType colorType,
                                 uintptr_t /* SkScalar*  */ pPtr,
                                 int count, SkTileMode mode, uint32_t flags,
                                 uintptr_t /* SkScalar*  */ mPtr,
                                 sk_sp<SkColorSpace> colorSpace)->sk_sp<SkShader> {
         // See comment above for uintptr_t explanation
-        const SkColor4f*  colors  = reinterpret_cast<const SkColor4f*> (cPtr);
         const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
         OptionalMatrix localMatrix(mPtr);
-        return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
-                                                     colors, colorSpace, positions, count, mode,
-                                                     flags, &localMatrix);
+
+        if (colorType == SkColorType::kRGBA_F32_SkColorType) {
+            const SkColor4f* colors  = reinterpret_cast<const SkColor4f*>(cPtr);
+            return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+                                                         colors, colorSpace, positions, count, mode,
+                                                         flags, &localMatrix);
+        } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
+            const SkColor* colors  = reinterpret_cast<const SkColor*>(cPtr);
+            return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
+                                                         colors, positions, count, mode,
+                                                         flags, &localMatrix);
+        } else {
+            SkDebugf("%d is not an accepted colorType\n", colorType);
+            return nullptr;
+        }
     }), allow_raw_pointers());
 
 #ifdef SK_GL
@@ -898,9 +942,7 @@
             self.concat(m);
         }))
         .function("drawArc", &SkCanvas::drawArc)
-        // _drawAtlas takes an SkColor, unlike most private functions handling color.
-        // This is because it takes an array of colors. Converting it on the Javascript side allows
-        // an allocation to be avoided here.
+        // _drawAtlas takes an array of SkColor. There is no SkColor4f override.
         .function("_drawAtlas", optional_override([](SkCanvas& self,
                 const sk_sp<SkImage>& atlas, uintptr_t /* SkRSXform* */ xptr,
                 uintptr_t /* SkRect* */ rptr, uintptr_t /* SkColor* */ cptr, int count,
diff --git a/modules/canvaskit/helper.js b/modules/canvaskit/helper.js
index cceb909..1fe2c40 100644
--- a/modules/canvaskit/helper.js
+++ b/modules/canvaskit/helper.js
@@ -153,6 +153,23 @@
 function toUint32Color(c) {
   return ((clamp(c[3]*255) << 24) | (clamp(c[0]*255) << 16) | (clamp(c[1]*255) << 8) | (clamp(c[2]*255) << 0)) >>> 0;
 }
+// Accepts various colors representations and converts them to an array of int colors.
+// Does not handle builders.
+function assureIntColors(arr) {
+  if (arr instanceof Float32Array) {
+    var count = Math.floor(arr.length / 4);
+    var result = new Uint32Array(count);
+    for (var i = 0; i < count; i ++) {
+      result[i] = toUint32Color(arr.slice(i*4, (i+1)*4));
+    }
+    return result;
+  } else if (arr instanceof Uint32Array) {
+    return arr;
+  } else if (arr instanceof Array && arr[0] instanceof Float32Array) {
+    return arr.map(toUint32Color);
+  }
+  
+}
 function uIntColorToCanvasKitColor(c) {
     return CanvasKit.Color(
      (c >> 16) & 0xFF,
@@ -259,6 +276,36 @@
   return ptr;
 }
 
+// Copies an array of colors to wasm, returning an object with the pointer
+// and info necessary to use the copied colors.
+// Accepts either a flat Float32Array, flat Uint32Array or Array of Float32Arrays.
+// If color is an object that was allocated with CanvasKit.Malloc, it's pointer is
+// returned and no extra copy is performed.
+// Array of Float32Arrays is deprecated and planned to be removed, prefer flat
+// Float32Array
+// TODO(nifong): have this accept color builders.
+function copyFlexibleColorArray(colors) {
+  var result = {
+    colorPtr: nullptr,
+    count: colors.length,
+    colorType: CanvasKit.ColorType.RGBA_F32,
+  }
+  if (colors instanceof Float32Array) {
+    result.colorPtr = copy1dArray(colors, "HEAPF32");
+    result.count = colors.length / 4;
+
+  } else if (colors instanceof Uint32Array) {
+    result.colorPtr = copy1dArray(colors, "HEAPU32");
+    result.colorType = CanvasKit.ColorType.RGBA_8888;
+
+  } else if (colors instanceof Array && colors[0] instanceof Float32Array) {
+    result.colorPtr = copy2dArray(colors, "HEAPF32");
+  } else {
+    throw('Invalid argument to copyFlexibleColorArray, Not a color array '+typeof(colors));
+  }
+  return result;
+}
+
 var defaultPerspective = Float32Array.of(0, 0, 1);
 
 var _scratch3x3MatrixPtr = nullptr;
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
index 23a4c4a..183f988 100644
--- a/modules/canvaskit/interface.js
+++ b/modules/canvaskit/interface.js
@@ -881,8 +881,8 @@
   // and CanvasKit.SkColorBuilder (fastest)
   // Or they can be an array of floats of length 4*number of destinations.
   // colors are optional and used to tint the drawn images using the optional blend mode
-  // drawAtlas ONLY accepts uint colors such as those created with CanvasKit.ColorAsInt(r, g, b, a)
-  // whether they are provided as an array or a builder.
+  // Colors may be an SkColorBuilder, a Uint32Array of int colors,
+  // a Flat Float32Array of float colors or a 2d Array of Float32Array(4) (deprecated)
   CanvasKit.SkCanvas.prototype.drawAtlas = function(atlas, srcRects, dstXforms, paint,
                                        /*optional*/ blendMode, colors) {
     if (!atlas || !paint || !srcRects || !dstXforms) {
@@ -924,7 +924,7 @@
       if (colors.build) {
         colorPtr = colors.build();
       } else {
-        colorPtr = copy1dArray(colors, "HEAPU32");
+        colorPtr = copy1dArray(assureIntColors(colors), "HEAPU32");
       }
     }
 
@@ -1192,49 +1192,49 @@
 
   CanvasKit.SkShader.MakeLinearGradient = function(start, end, colors, pos, mode, localMatrix, flags, colorSpace) {
     colorSpace = colorSpace || null
-    var colorPtr = copy2dArray(colors, "HEAPF32");
-    var posPtr =   copy1dArray(pos,    "HEAPF32");
+    var cPtrInfo = copyFlexibleColorArray(colors);
+    var posPtr =   copy1dArray(pos, "HEAPF32");
     flags = flags || 0;
     var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
 
-    var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
-                                                  colors.length, mode, flags, localMatrixPtr, colorSpace);
+    var lgs = CanvasKit._MakeLinearGradientShader(start, end, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+                                                  cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
 
-    CanvasKit._free(colorPtr);
+    freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
     pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
     return lgs;
   }
 
   CanvasKit.SkShader.MakeRadialGradient = function(center, radius, colors, pos, mode, localMatrix, flags, colorSpace) {
     colorSpace = colorSpace || null
-    var colorPtr = copy2dArray(colors, "HEAPF32");
+    var cPtrInfo = copyFlexibleColorArray(colors);
     var posPtr =   copy1dArray(pos,    "HEAPF32");
     flags = flags || 0;
     var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
 
-    var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
-                                                  colors.length, mode, flags, localMatrixPtr, colorSpace);
+    var rgs = CanvasKit._MakeRadialGradientShader(center, radius, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+                                                  cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
 
-    CanvasKit._free(colorPtr);
+    freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
     pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
     return rgs;
   }
 
   CanvasKit.SkShader.MakeSweepGradient = function(cx, cy, colors, pos, mode, localMatrix, flags, startAngle, endAngle, colorSpace) {
     colorSpace = colorSpace || null
-    var colorPtr = copy2dArray(colors, "HEAPF32");
+    var cPtrInfo = copyFlexibleColorArray(colors);
     var posPtr =   copy1dArray(pos,    "HEAPF32");
     flags = flags || 0;
     startAngle = startAngle || 0;
     endAngle = endAngle || 360;
     var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
 
-    var sgs = CanvasKit._MakeSweepGradientShader(cx, cy, colorPtr, posPtr,
-                                                 colors.length, mode,
+    var sgs = CanvasKit._MakeSweepGradientShader(cx, cy, cPtrInfo.colorPtr, cPtrInfo.colorType, posPtr,
+                                                 cPtrInfo.count, mode,
                                                  startAngle, endAngle, flags,
                                                  localMatrixPtr, colorSpace);
 
-    CanvasKit._free(colorPtr);
+    freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
     pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
     return sgs;
   }
@@ -1242,16 +1242,16 @@
   CanvasKit.SkShader.MakeTwoPointConicalGradient = function(start, startRadius, end, endRadius,
                                                             colors, pos, mode, localMatrix, flags, colorSpace) {
     colorSpace = colorSpace || null
-    var colorPtr = copy2dArray(colors, "HEAPF32");
+    var cPtrInfo = copyFlexibleColorArray(colors);
     var posPtr =   copy1dArray(pos,    "HEAPF32");
     flags = flags || 0;
     var localMatrixPtr = copy3x3MatrixToWasm(localMatrix);
 
     var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
-                          start, startRadius, end, endRadius,
-                          colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr, colorSpace);
+                          start, startRadius, end, endRadius, cPtrInfo.colorPtr, cPtrInfo.colorType,
+                          posPtr, cPtrInfo.count, mode, flags, localMatrixPtr, colorSpace);
 
-    CanvasKit._free(colorPtr);
+    freeArraysThatAreNotMallocedByUsers(cPtrInfo.colorPtr, colors);
     pos && freeArraysThatAreNotMallocedByUsers(posPtr, pos);
     return rgs;
   }
@@ -1381,7 +1381,11 @@
   return CanvasKit._MakeImage(info, pptr, pixels.length, width * bytesPerPixel);
 }
 
-// colors is an array of float color arrays
+// Colors may be a Uint32Array of int colors, a Flat Float32Array of float colors
+// or a 2d Array of Float32Array(4) (deprecated)
+// the underlying skia function accepts only int colors so it is recommended
+// to pass an array of int colors to avoid an extra conversion.
+// SkColorBuilder is not accepted.
 CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
                                     indices, isVolatile) {
   // Default isVolitile to true if not set
@@ -1400,15 +1404,18 @@
     flags |= (1 << 2);
   }
 
-  var builder = new CanvasKit._SkVerticesBuilder(mode,  positions.length, idxCount, flags);
+  var builder = new CanvasKit._SkVerticesBuilder(mode, positions.length, idxCount, flags);
 
   copy2dArray(positions,            "HEAPF32", builder.positions());
   if (builder.texCoords()) {
     copy2dArray(textureCoordinates, "HEAPF32", builder.texCoords());
   }
   if (builder.colors()) {
-    // Convert from canvaskit 4f colors to 32 bit uint colors which builder supports.
-    copy1dArray(colors.map(toUint32Color), "HEAPU32", builder.colors());
+    if (colors.build) {
+      throw('Color builder not accepted by MakeSkVertices, use array of ints');
+    } else {
+      copy1dArray(assureIntColors(colors), "HEAPU32", builder.colors());
+    }
   }
   if (builder.indices()) {
     copy1dArray(indices, "HEAPU16", builder.indices());
diff --git a/modules/canvaskit/tests/canvas.spec.js b/modules/canvaskit/tests/canvas.spec.js
index a5851e4..bb283ed 100644
--- a/modules/canvaskit/tests/canvas.spec.js
+++ b/modules/canvaskit/tests/canvas.spec.js
@@ -583,8 +583,31 @@
         paint.setAntiAlias(true);
 
         const points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+        // 2d float color array
         const colors = [CanvasKit.RED, CanvasKit.BLUE,
-                      CanvasKit.YELLOW, CanvasKit.CYAN];
+                        CanvasKit.YELLOW, CanvasKit.CYAN];
+        const vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+            points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
+
+        const bounds = vertices.bounds();
+        expect(bounds.fLeft).toEqual(0);
+        expect(bounds.fTop).toEqual(0);
+        expect(bounds.fRight).toEqual(250);
+        expect(bounds.fBottom).toEqual(250);
+
+        canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
+        vertices.delete();
+        paint.delete();
+    });
+
+    gm('drawvertices_canvas_flat_floats', (canvas) => {
+        const paint = new CanvasKit.SkPaint();
+        paint.setAntiAlias(true);
+
+        const points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+        // 1d float color array
+        const colors = Float32Array.of(...CanvasKit.RED, ...CanvasKit.BLUE,
+                                       ...CanvasKit.YELLOW, ...CanvasKit.CYAN);
         const vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
             points, null /*textureCoordinates*/, colors, false /*isVolatile*/);
 
diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js
index c6346ac..f314776 100644
--- a/modules/canvaskit/tests/core.spec.js
+++ b/modules/canvaskit/tests/core.spec.js
@@ -265,12 +265,12 @@
             0.5, 0, 300, 300,
         ];
 
-        const colors = [
+        const colors = Uint32Array.of(
             CanvasKit.ColorAsInt( 85, 170,  10, 128), // light green
             CanvasKit.ColorAsInt( 51,  51, 191, 128), // light blue
             CanvasKit.ColorAsInt(  0,   0,   0, 128),
             CanvasKit.ColorAsInt(256, 256, 256, 128),
-        ];
+        );
 
         canvas.drawAtlas(atlas, srcs, dsts, paint, CanvasKit.BlendMode.Modulate, colors);
 
@@ -322,7 +322,10 @@
 
         const lgsPremul = CanvasKit.SkShader.MakeLinearGradient(
             [100, 0], [150, 100], // start and stop points
-            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+            Uint32Array.of(
+                CanvasKit.ColorAsInt(0, 255, 255, 0),
+                CanvasKit.ColorAsInt(0, 0, 255, 255),
+                CanvasKit.ColorAsInt(255, 0, 0, 255)),
             [0, 0.65, 1.0],
             CanvasKit.TileMode.Mirror,
             null, // no local matrix
@@ -335,7 +338,7 @@
 
         const lgs45 = CanvasKit.SkShader.MakeLinearGradient(
             [0, 100], [50, 200], // start and stop points
-            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+            Float32Array.of(...transparentGreen, ...CanvasKit.BLUE, ...CanvasKit.RED),
             [0, 0.65, 1.0],
             CanvasKit.TileMode.Mirror,
             CanvasKit.SkMatrix.rotated(Math.PI/4, 0, 100),
@@ -345,14 +348,21 @@
         canvas.drawRect(r, paint);
         canvas.drawRect(r, strokePaint);
 
+        // malloc'd color array
+        const colors = CanvasKit.Malloc(Float32Array, 12);
+        const typedColorsArray = colors.toTypedArray();
+        typedColorsArray.set(transparentGreen, 0);
+        typedColorsArray.set(CanvasKit.BLUE, 4);
+        typedColorsArray.set(CanvasKit.RED, 8);
         const lgs45Premul = CanvasKit.SkShader.MakeLinearGradient(
             [100, 100], [150, 200], // start and stop points
-            [transparentGreen, CanvasKit.BLUE, CanvasKit.RED],
+            typedColorsArray,
             [0, 0.65, 1.0],
             CanvasKit.TileMode.Mirror,
             CanvasKit.SkMatrix.rotated(Math.PI/4, 100, 100),
             1 // interpolate colors in premul
         );
+        CanvasKit.Free(colors);
         paint.setShader(lgs45Premul);
         r = CanvasKit.LTRBRect(100, 100, 200, 200);
         canvas.drawRect(r, paint);