[canvaskit] Add drawVertices API

This also does some clean up to how we name enums - the caps felt a bit
obnoxious. CAPS are reserved now for constants (like colors).

Small bug fix with leaking memory on discrete path effects

This also adds a few more things from PathKit

Bug: skia:
Change-Id: Iad7e21ac36d35a36a8b255dc82b1dcc886344db1
Reviewed-on: https://skia-review.googlesource.com/c/166804
Commit-Queue: Kevin Lubick <kjlubick@google.com>
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 8df01c7..6173ff9 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -17,6 +17,9 @@
 </style>
 
 <h2> CanvasKit draws Paths to the browser</h2>
+<canvas id=vertex1 width=300 height=300></canvas>
+<canvas id=vertex2 width=300 height=300></canvas>
+<canvas id=gradient1 width=300 height=300></canvas>
 <canvas id=patheffect width=300 height=300></canvas>
 <canvas id=paths width=200 height=200></canvas>
 <canvas id=ink width=300 height=300></canvas>
@@ -50,24 +53,40 @@
 
   var nimaFile = null;
   var nimaTexture = null;
+
+  var bonesImage = null;
   CanvasKitInit({
     locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
   }).then((CK) => {
-    CK.initFonts();
-    CanvasKit = CK;
-    DrawingExample(CanvasKit);
-    PathExample(CanvasKit);
-    InkExample(CanvasKit);
-    // Set bounds to fix the 4:3 resolution of the legos
-    addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
-                            {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
-    // Re-size to fit
-    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
-    NimaExample(CanvasKit, nimaFile, nimaTexture);
+     // when debugging, it can be handy to not run directly in the then, because if there
+     // is a failure (for example, miscalling an API), the WASM loader tries to re-load
+     // the web assembly in the (much slower) ArrayBuffer version. This will also fail
+     // and thus there is a lot of extra log spew.
+     // Thus, the setTimeout to run on the next microtask avoids this second loading
+     // and the log spew.
+    setTimeout(() => {
+      CK.initFonts();
+      CanvasKit = CK;
+      DrawingExample(CanvasKit);
+      PathExample(CanvasKit);
+      InkExample(CanvasKit);
+      // Set bounds to fix the 4:3 resolution of the legos
+      addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                              {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
+      // Re-size to fit
+      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
+      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
+      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
 
-    CanvasAPI1(CanvasKit);
+      NimaExample(CanvasKit, nimaFile, nimaTexture);
+
+      CanvasAPI1(CanvasKit);
+
+      VertexAPI1(CanvasKit);
+      VertexAPI2(CanvasKit, bonesImage);
+
+      GradiantAPI1(CanvasKit);
+    }, 0);
   });
 
   fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
@@ -121,6 +140,17 @@
     });
   });
 
+  fetch('https://storage.googleapis.com/skia-cdn/misc/bones.jpg').then((resp) => {
+    resp.blob().then((blob) => {
+      let reader = new FileReader();
+      reader.addEventListener("loadend", function() {
+          bonesImage = reader.result;
+          VertexAPI2(CanvasKit, bonesImage);
+      });
+      reader.readAsArrayBuffer(blob);
+    });
+  });
+
   function addScreenshotListener(surface) {
     if (!surface) {
       return;
@@ -156,7 +186,7 @@
     const paint = new CanvasKit.SkPaint();
 
     const textPaint = new CanvasKit.SkPaint();
-    textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+    textPaint.setColor(CanvasKit.RED);
     textPaint.setTextSize(30);
     textPaint.setAntiAlias(true);
 
@@ -172,12 +202,12 @@
       i++;
 
       paint.setPathEffect(dpe);
-      paint.setStyle(CanvasKit.PaintStyle.STROKE);
+      paint.setStyle(CanvasKit.PaintStyle.Stroke);
       paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
       paint.setAntiAlias(true);
       paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
 
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+      canvas.clear(CanvasKit.TRANSPARENT);
 
       canvas.drawPath(path, paint);
       canvas.drawText('Try Clicking!', 10, 280, textPaint);
@@ -221,7 +251,7 @@
       paint.setStrokeWidth(1.0);
       paint.setAntiAlias(true);
       paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-      paint.setStyle(CanvasKit.PaintStyle.STROKE);
+      paint.setStyle(CanvasKit.PaintStyle.Stroke);
 
       const path = new CanvasKit.SkPath();
       path.moveTo(20, 5);
@@ -277,7 +307,7 @@
     let paint = new CanvasKit.SkPaint();
     paint.setAntiAlias(true);
     paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
-    paint.setStyle(CanvasKit.PaintStyle.STROKE);
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
     paint.setStrokeWidth(4.0);
     paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
 
@@ -381,7 +411,7 @@
       let seek = ((Date.now() - firstFrame) / duration) % 1.0;
       CanvasKit.setCurrentContext(context);
       animation.seek(seek);
-      canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
+      canvas.clear(CanvasKit.TRANSPARENT);
       animation.render(canvas, bounds);
       surface.flush();
       window.requestAnimationFrame(drawFrame);
@@ -443,7 +473,7 @@
     function drawFrame() {
       let seek = ((Date.now() - firstFrame) / 1000.0);
       CanvasKit.setCurrentContext(context);
-      canvas.clear(CanvasKit.Color(255, 255, 255, 0.0));
+      canvas.clear(CanvasKit.TRANSPARENT);
       animation.seek(seek);
       animation.render(canvas);
       surface.flush();
@@ -452,4 +482,144 @@
     window.requestAnimationFrame(drawFrame);
   }
 
+  function VertexAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('vertex1');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+    let paint = new CanvasKit.SkPaint();
+
+    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
+    // for original c++ version.
+    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+    let colors = [CanvasKit.RED, CanvasKit.BLUE,
+                  CanvasKit.YELLOW, CanvasKit.CYAN];
+    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, null, colors);
+
+    canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
+    surface.flush();
+
+    vertices.delete();
+
+    // See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
+    // for original c++ version.
+    points   = [[ 300, 300 ], [ 50, 300 ], [ 200, 200 ], [ 300, 50 ]];
+    let texs = [[   0,   0 ], [  0, 250 ], [ 250, 250 ], [ 250,  0 ]];
+    vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, texs, colors);
+
+    let shader = CanvasKit.MakeLinearGradientShader([0, 0], [250, 0],
+            colors, null, CanvasKit.TileMode.Clamp);
+    paint.setShader(shader);
+
+    canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
+    surface.flush();
+
+    shader.delete();
+    paint.delete();
+    surface.delete();
+
+  }
+
+  function VertexAPI2(CanvasKit, bonesImage) {
+    if (!CanvasKit || !bonesImage) {
+      return;
+    }
+    const surface = CanvasKit.MakeCanvasSurface('vertex2');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+    let paint = new CanvasKit.SkPaint();
+
+    let shader = CanvasKit.MakeImageShader(bonesImage,
+                    CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp);
+
+    // comment this out to see just the triangles move.
+    paint.setShader(shader);
+
+    // points is the destination location on the canvas  We want the output
+    // to be a 280x280 box (to start).
+    let points   = [[ 0, 0 ],  [ 280, 0 ], [ 280, 280 ], [ 0, 280 ]];
+    // texs is the coordinates of the source in the texture
+    // (provided by the image shader). The image is 334x226 px big.
+    let texs     = [[ 0, 0 ],  [ 334, 0 ], [ 334, 226 ], [ 0, 226 ]];
+    let boneidxs = [[1,0,0,0], [2,0,0,0],  [3,0,0,0],    [2,3,0,0]];
+    let bonewts  = [[1,0,0,0], [1,0,0,0],  [1,0,0,0],    [.5,.5,0,0]];
+    let vertices = CanvasKit.MakeSkVertices(CanvasKit.VertexMode.TriangleFan,
+                                            points, texs, null, boneidxs, bonewts);
+
+    function drawFrame() {
+      let now = Date.now();
+      let bones = [
+        [[1,0, // world bone (move 10px down and to the right to center)
+          0,1,
+          10,10]],
+        [[1,0, // identity bone (bone for vertices that are static)
+          0,1,
+          0,0]],
+        [[1,0, // ossilate in x bone
+          0,1,
+          10*Math.sin(now/500),0]],
+        [[1,0, // ossilate in y bone
+          0,1,
+          0,30*Math.cos(now/500)]],
+      ];
+      let tVerts = vertices.applyBones(bones);
+      CanvasKit.setCurrentContext(context);
+      //canvas.clear(CanvasKit.TRANSPARENT);
+      canvas.drawVertices(tVerts, CanvasKit.BlendMode.Src, paint);
+      surface.flush();
+
+      tVerts.delete();
+      window.requestAnimationFrame(drawFrame);
+    }
+    window.requestAnimationFrame(drawFrame);
+    //tVerts.delete();
+    //vertices.delete();
+
+    //shader && shader.delete();
+    //paint.delete();
+    //surface.delete();
+
+  }
+  /**
+  SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
+    SkPaint paint;
+    paint.setShader(SkGradientShader::MakeRadial(
+                SkPoint::Make(128.0f, 128.0f), 180.0f,
+                colors, nullptr, 2, SkShader::kClamp_TileMode, 0, nullptr));
+    canvas->drawPaint(paint);*/
+  function GradiantAPI1(CanvasKit) {
+    const surface = CanvasKit.MakeCanvasSurface('gradient1');
+    if (!surface) {
+      console.error('Could not make surface');
+      return;
+    }
+    const context = CanvasKit.currentContext();
+    const canvas = surface.getCanvas();
+    let paint = new CanvasKit.SkPaint();
+
+    // See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
+    // for original c++ version.
+    let points = [[ 0, 0 ], [ 250, 0 ], [ 100, 100 ], [ 0, 250 ]];
+    let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
+    let pos =    [0, .7, 1.0];
+    let transform = [2, 0, 0,
+                     0, 2, 0,
+                     0, 0, 1]
+    let shader = CanvasKit.MakeRadialGradientShader([150,150], 130, colors,
+                              pos, CanvasKit.TileMode.Mirror, transform);
+
+    paint.setShader(shader);
+    paint.setTextSize(100);
+    canvas.drawText("Radial", 10, 200, paint);
+    surface.flush();
+  }
 </script>
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 129ee50..9f3db2b 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -12,19 +12,33 @@
 #include "GrGLTypes.h"
 #endif
 
+#include "SkBlendMode.h"
 #include "SkCanvas.h"
-#include "SkDashPathEffect.h"
+#include "SkColor.h"
 #include "SkCornerPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkDashPathEffect.h"
+#include "SkData.h"
 #include "SkDiscretePathEffect.h"
 #include "SkFontMgr.h"
 #include "SkFontMgrPriv.h"
+#include "SkGradientShader.h"
+#include "SkImageShader.h"
 #include "SkPaint.h"
+#include "SkParsePath.h"
 #include "SkPath.h"
 #include "SkPathEffect.h"
+#include "SkPathOps.h"
 #include "SkScalar.h"
+#include "SkShader.h"
+#include "SkString.h"
+#include "SkStrokeRec.h"
 #include "SkSurface.h"
 #include "SkSurfaceProps.h"
 #include "SkTestFontMgr.h"
+#include "SkTrimPathEffect.h"
+#include "SkVertices.h"
+
 #if SK_INCLUDE_SKOTTIE
 #include "Skottie.h"
 #endif
@@ -47,6 +61,14 @@
 // Self-documenting types
 using JSArray = emscripten::val;
 using JSColor = int32_t;
+using JSString = emscripten::val;
+using SkPathOrNull = emscripten::val;
+using Uint8Array = emscripten::val;
+
+// Aliases for less typing
+using BoneIndices = SkVertices::BoneIndices;
+using BoneWeights = SkVertices::BoneWeights;
+using Bone        = SkVertices::Bone;
 
 void EMSCRIPTEN_KEEPALIVE initFonts() {
     gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
@@ -106,11 +128,17 @@
 }
 #endif
 
-#if SK_INCLUDE_SKOTTIE
-sk_sp<skottie::Animation> MakeAnimation(std::string json) {
-    return skottie::Animation::Make(json.c_str(), json.length());
+struct SimpleMatrix {
+    SkScalar scaleX, skewX,  transX;
+    SkScalar skewY,  scaleY, transY;
+    SkScalar pers0,  pers1,  pers2;
+};
+
+SkMatrix toSkMatrix(const SimpleMatrix& sm) {
+    return SkMatrix::MakeAll(sm.scaleX, sm.skewX , sm.transX,
+                             sm.skewY , sm.scaleY, sm.transY,
+                             sm.pers0 , sm.pers1 , sm.pers2);
 }
-#endif
 
 //========================================================================================
 // Path things
@@ -173,6 +201,28 @@
     orig.transform(m);
 }
 
+bool EMSCRIPTEN_KEEPALIVE ApplySimplify(SkPath& path) {
+    return Simplify(path, &path);
+}
+
+bool EMSCRIPTEN_KEEPALIVE ApplyPathOp(SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+    return Op(pathOne, pathTwo, op, &pathOne);
+}
+
+JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
+    SkString s;
+    SkParsePath::ToSVGString(path, &s);
+    return emscripten::val(s.c_str());
+}
+
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+    SkPath out;
+    if (Op(pathOne, pathTwo, op, &out)) {
+        return emscripten::val(out);
+    }
+    return emscripten::val::null();
+}
+
 SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
     SkPath copy(a);
     return copy;
@@ -182,12 +232,68 @@
     return a == b;
 }
 
-// to map from raw memory to a uint8array
-val getSkDataBytes(const SkData *data) {
-    return val(typed_memory_view(data->size(), data->bytes()));
+//========================================================================================
+// Path Effects
+//========================================================================================
+
+bool ApplyDash(SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
+    SkScalar intervals[] = { on, off };
+    auto pe = SkDashPathEffect::Make(intervals, 2, phase);
+    if (!pe) {
+        SkDebugf("Invalid args to dash()\n");
+        return false;
+    }
+    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
+    if (pe->filterPath(&path, path, &rec, nullptr)) {
+        return true;
+    }
+    SkDebugf("Could not make dashed path\n");
+    return false;
 }
 
-// Hack to avoid embind creating a binding for SkData destructor
+bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
+    auto mode = isComplement ? SkTrimPathEffect::Mode::kInverted : SkTrimPathEffect::Mode::kNormal;
+    auto pe = SkTrimPathEffect::Make(startT, stopT, mode);
+    if (!pe) {
+        SkDebugf("Invalid args to trim(): startT and stopT must be in [0,1]\n");
+        return false;
+    }
+    SkStrokeRec rec(SkStrokeRec::InitStyle::kHairline_InitStyle);
+    if (pe->filterPath(&path, path, &rec, nullptr)) {
+        return true;
+    }
+    SkDebugf("Could not trim path\n");
+    return false;
+}
+
+struct StrokeOpts {
+    // Default values are set in chaining.js which allows clients
+    // to set any number of them. Otherwise, the binding code complains if
+    // any are omitted.
+    SkScalar width;
+    SkScalar miter_limit;
+    SkPaint::Join join;
+    SkPaint::Cap cap;
+};
+
+bool ApplyStroke(SkPath& path, StrokeOpts opts) {
+    SkPaint p;
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeCap(opts.cap);
+    p.setStrokeJoin(opts.join);
+    p.setStrokeWidth(opts.width);
+    p.setStrokeMiter(opts.miter_limit);
+
+    return p.getFillPath(path, &path);
+}
+
+// to map from raw memory to a uint8array
+Uint8Array getSkDataBytes(const SkData *data) {
+    return Uint8Array(typed_memory_view(data->size(), data->bytes()));
+}
+
+// These objects have private destructors / delete mthods - I don't think
+// we need to do anything other than tell emscripten to do nothing.
 namespace emscripten {
     namespace internal {
         template<typename ClassType>
@@ -196,6 +302,10 @@
         template<>
         void raw_destructor<SkData>(SkData *ptr) {
         }
+
+        template<>
+        void raw_destructor<SkVertices>(SkVertices *ptr) {
+        }
     }
 }
 
@@ -222,21 +332,100 @@
         return SkSurface::MakeRasterN32Premul(width, height, nullptr);
     }), allow_raw_pointers());
 #endif
+    function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
     function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
     function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
-    // Won't be called directly, there's a JS helper to deal with typed arrays.
+    function("MakePathFromOp", &MakePathFromOp);
+
+    // These won't be called directly, there's a JS helper to deal with typed arrays.
     function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> {
         // See comment above for uintptr_t explanation
         const float* intervals = reinterpret_cast<const float*>(cptr);
         return SkDashPathEffect::Make(intervals, count, phase);
     }), allow_raw_pointers());
-    function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
+    function("_MakeImageShader", optional_override([](uintptr_t /* uint8_t*  */ iPtr, int ilen,
+                                SkShader::TileMode tx, SkShader::TileMode ty)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const uint8_t* imgBytes = reinterpret_cast<const uint8_t*>(iPtr);
+
+        auto imgData = SkData::MakeFromMalloc(imgBytes, ilen);
+        auto img = SkImage::MakeFromEncoded(imgData);
+        if (!img) {
+            SkDebugf("Could not decode image\n");
+            return nullptr;
+        }
+
+        return SkImageShader::Make(img, tx, ty, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
+        SkPoint points[] = { start, end };
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        return SkGradientShader::MakeLinear(points, colors, positions, count,
+                                            mode, flags, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkShader::TileMode mode, uint32_t flags,
+                                const SimpleMatrix& lm)->sk_sp<SkShader> {
+        SkPoint points[] = { start, end };
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        SkMatrix localMatrix = toSkMatrix(lm);
+
+        return SkGradientShader::MakeLinear(points, colors, positions, count,
+                                            mode, flags, &localMatrix);
+    }), allow_raw_pointers());
+    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+                                            mode, flags, nullptr);
+    }), allow_raw_pointers());
+    function("_MakeRadialGradientShader", optional_override([](SkPoint center, SkScalar radius,
+                                uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
+                                int count, SkShader::TileMode mode, uint32_t flags,
+                                const SimpleMatrix& lm)->sk_sp<SkShader> {
+        // See comment above for uintptr_t explanation
+        const SkColor*  colors    = reinterpret_cast<const SkColor*> (cPtr);
+        const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
+
+        SkMatrix localMatrix = toSkMatrix(lm);
+        return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
+                                            mode, flags, &localMatrix);
+    }), allow_raw_pointers());
+    function("_MakeSkVertices", optional_override([](SkVertices::VertexMode mode, int vertexCount,
+                                uintptr_t /* SkPoint*     */ pPtr,  uintptr_t /* SkPoint*     */ tPtr,
+                                uintptr_t /* SkColor*     */ cPtr,
+                                uintptr_t /* BoneIndices* */ biPtr, uintptr_t /* BoneWeights* */ bwPtr,
+                                int indexCount,                     uintptr_t /* uint16_t  *  */ iPtr)->sk_sp<SkVertices> {
+        // See comment above for uintptr_t explanation
+        const SkPoint* positions       = reinterpret_cast<const SkPoint*>(pPtr);
+        const SkPoint* texs            = reinterpret_cast<const SkPoint*>(tPtr);
+        const SkColor* colors          = reinterpret_cast<const SkColor*>(cPtr);
+        const BoneIndices* boneIndices = reinterpret_cast<const BoneIndices*>(biPtr);
+        const BoneWeights* boneWeights = reinterpret_cast<const BoneWeights*>(bwPtr);
+        const uint16_t* indices        = reinterpret_cast<const uint16_t*>(iPtr);
+
+        return SkVertices::MakeCopy(mode, vertexCount, positions, texs, colors,
+                                    boneIndices, boneWeights, indexCount, indices);
+    }), allow_raw_pointers());
 
     class_<SkCanvas>("SkCanvas")
         .constructor<>()
         .function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
             // JS side gives us a signed int instead of an unsigned int for color
-            // Add a lambda to change it out.
+            // Add a optional_override to change it out.
             self.clear(SkColor(color));
         }))
         .function("drawPaint", &SkCanvas::drawPaint)
@@ -248,8 +437,9 @@
             // Otherwise, go with std::wstring and set UTF-32 encoding.
             self.drawText(text.c_str(), text.length(), x, y, p);
         }))
+        .function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
         .function("flush", &SkCanvas::flush)
-        .function("rotate", select_overload<void (SkScalar degrees, SkScalar px, SkScalar py)>(&SkCanvas::rotate))
+        .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
         .function("save", &SkCanvas::save)
         .function("scale", &SkCanvas::scale)
         .function("setMatrix", &SkCanvas::setMatrix)
@@ -279,7 +469,7 @@
         .function("setAntiAlias", &SkPaint::setAntiAlias)
         .function("setColor", optional_override([](SkPaint& self, JSColor color)->void {
             // JS side gives us a signed int instead of an unsigned int for color
-            // Add a lambda to change it out.
+            // Add a optional_override to change it out.
             self.setColor(SkColor(color));
         }))
         .function("setPathEffect", &SkPaint::setPathEffect)
@@ -305,6 +495,18 @@
         .function("_quadTo", &ApplyQuadTo)
         .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
 
+        // PathEffects
+        .function("_dash", &ApplyDash)
+        .function("_trim", &ApplyTrim)
+        .function("_stroke", &ApplyStroke)
+
+        // PathOps
+        .function("_simplify", &ApplySimplify)
+        .function("_op", &ApplyPathOp)
+
+        // Exporting
+        .function("toSVGString", &ToSVGString)
+
         .function("setFillType", &SkPath::setFillType)
         .function("getFillType", &SkPath::getFillType)
         .function("getBounds", &SkPath::getBounds)
@@ -312,6 +514,9 @@
         .function("equals", &Equals)
         .function("copy", &CopyPath);
 
+    class_<SkShader>("SkShader")
+        .smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>");
+
     class_<SkSurface>("SkSurface")
         .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
         .function("width", &SkSurface::width)
@@ -319,23 +524,90 @@
         .function("_flush", &SkSurface::flush)
         .function("makeImageSnapshot", &SkSurface::makeImageSnapshot)
         .function("_readPixels", optional_override([](SkSurface& self, int width, int height, uintptr_t /* uint8_t* */ cptr)->bool {
-            auto* dst = reinterpret_cast<uint8_t*>(cptr);
+            uint8_t* dst = reinterpret_cast<uint8_t*>(cptr);
             auto dstInfo = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
             return self.readPixels(dstInfo, dst, width*4, 0, 0);
         }))
         .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
 
+    class_<SkVertices>("SkVertices")
+        .smart_ptr<sk_sp<SkVertices>>("sk_sp<SkVertices>")
+        .function("_applyBones", optional_override([](SkVertices& self, uintptr_t /* Bone* */ bptr, int boneCount)->sk_sp<SkVertices> {
+            // See comment above for uintptr_t explanation
+            const Bone* bones = reinterpret_cast<const Bone*>(bptr);
+            return self.applyBones(bones, boneCount);
+        }))
+        .function("bounds", &SkVertices::bounds)
+        .function("mode", &SkVertices::mode)
+        .function("uniqueID", &SkVertices::uniqueID)
+        .function("dumpPositions", optional_override([](SkVertices& self)->void {
+            auto pos = self.positions();
+            for(int i = 0; i< self.vertexCount(); i++) {
+                SkDebugf("position[%d] = (%f, %f)\n", i, pos[i].x(), pos[i].y());
+            }
+        }))
+        .function("vertexCount", &SkVertices::vertexCount);
+
+
+    enum_<SkBlendMode>("BlendMode")
+        .value("Clear",      SkBlendMode::kClear)
+        .value("Src",        SkBlendMode::kSrc)
+        .value("Dst",        SkBlendMode::kDst)
+        .value("SrcOver",    SkBlendMode::kSrcOver)
+        .value("DstOver",    SkBlendMode::kDstOver)
+        .value("SrcIn",      SkBlendMode::kSrcIn)
+        .value("DstIn",      SkBlendMode::kDstIn)
+        .value("SrcOut",     SkBlendMode::kSrcOut)
+        .value("DstOut",     SkBlendMode::kDstOut)
+        .value("SrcATop",    SkBlendMode::kSrcATop)
+        .value("DstATop",    SkBlendMode::kDstATop)
+        .value("Xor",        SkBlendMode::kXor)
+        .value("Plus",       SkBlendMode::kPlus)
+        .value("Modulate",   SkBlendMode::kModulate)
+        .value("Screen",     SkBlendMode::kScreen)
+        .value("Overlay",    SkBlendMode::kOverlay)
+        .value("Darken",     SkBlendMode::kDarken)
+        .value("Lighten",    SkBlendMode::kLighten)
+        .value("ColorDodge", SkBlendMode::kColorDodge)
+        .value("ColorBurn",  SkBlendMode::kColorBurn)
+        .value("HardLight",  SkBlendMode::kHardLight)
+        .value("SoftLight",  SkBlendMode::kSoftLight)
+        .value("Difference", SkBlendMode::kDifference)
+        .value("Exclusion",  SkBlendMode::kExclusion)
+        .value("Multiply",   SkBlendMode::kMultiply)
+        .value("Hue",        SkBlendMode::kHue)
+        .value("Saturation", SkBlendMode::kSaturation)
+        .value("Color",      SkBlendMode::kColor)
+        .value("Luminosity", SkBlendMode::kLuminosity);
 
     enum_<SkPaint::Style>("PaintStyle")
-        .value("FILL",              SkPaint::Style::kFill_Style)
-        .value("STROKE",            SkPaint::Style::kStroke_Style)
-        .value("STROKE_AND_FILL",   SkPaint::Style::kStrokeAndFill_Style);
+        .value("Fill",            SkPaint::Style::kFill_Style)
+        .value("Stroke",          SkPaint::Style::kStroke_Style)
+        .value("StrokeAndFill",   SkPaint::Style::kStrokeAndFill_Style);
 
     enum_<SkPath::FillType>("FillType")
-        .value("WINDING",            SkPath::FillType::kWinding_FillType)
-        .value("EVENODD",            SkPath::FillType::kEvenOdd_FillType)
-        .value("INVERSE_WINDING",    SkPath::FillType::kInverseWinding_FillType)
-        .value("INVERSE_EVENODD",    SkPath::FillType::kInverseEvenOdd_FillType);
+        .value("Winding",           SkPath::FillType::kWinding_FillType)
+        .value("EvenOdd",           SkPath::FillType::kEvenOdd_FillType)
+        .value("InverseWinding",    SkPath::FillType::kInverseWinding_FillType)
+        .value("InverseEvenOdd",    SkPath::FillType::kInverseEvenOdd_FillType);
+
+    enum_<SkPathOp>("PathOp")
+        .value("Difference",         SkPathOp::kDifference_SkPathOp)
+        .value("Intersect",          SkPathOp::kIntersect_SkPathOp)
+        .value("Union",              SkPathOp::kUnion_SkPathOp)
+        .value("XOR",                SkPathOp::kXOR_SkPathOp)
+        .value("ReverseDifference",  SkPathOp::kReverseDifference_SkPathOp);
+
+    enum_<SkShader::TileMode>("TileMode")
+        .value("Clamp",    SkShader::TileMode::kClamp_TileMode)
+        .value("Repeat",   SkShader::TileMode::kRepeat_TileMode)
+        .value("Mirror",   SkShader::TileMode::kMirror_TileMode);
+
+    enum_<SkVertices::VertexMode>("VertexMode")
+        .value("Triangles",       SkVertices::VertexMode::kTriangles_VertexMode)
+        .value("TrianglesStrip",  SkVertices::VertexMode::kTriangleStrip_VertexMode)
+        .value("TriangleFan",    SkVertices::VertexMode::kTriangleFan_VertexMode);
+
 
     // A value object is much simpler than a class - it is returned as a JS
     // object and does not require delete().
@@ -360,6 +632,31 @@
         .field("w",   &SkISize::fWidth)
         .field("h",   &SkISize::fHeight);
 
+    // Allows clients to supply a 1D array of 9 elements and the bindings
+    // will automatically turn it into a 3x3 2D matrix.
+    // e.g. path.transform([0,1,2,3,4,5,6,7,8])
+    // This is likely simpler for the client than exposing SkMatrix
+    // directly and requiring them to do a lot of .delete().
+    value_array<SimpleMatrix>("SkMatrix")
+        .element(&SimpleMatrix::scaleX)
+        .element(&SimpleMatrix::skewX)
+        .element(&SimpleMatrix::transX)
+
+        .element(&SimpleMatrix::skewY)
+        .element(&SimpleMatrix::scaleY)
+        .element(&SimpleMatrix::transY)
+
+        .element(&SimpleMatrix::pers0)
+        .element(&SimpleMatrix::pers1)
+        .element(&SimpleMatrix::pers2);
+
+    constant("TRANSPARENT", (JSColor) SK_ColorTRANSPARENT);
+    constant("RED",         (JSColor) SK_ColorRED);
+    constant("BLUE",        (JSColor) SK_ColorBLUE);
+    constant("YELLOW",      (JSColor) SK_ColorYELLOW);
+    constant("CYAN",        (JSColor) SK_ColorCYAN);
+    // TODO(?)
+
 #if SK_INCLUDE_SKOTTIE
     // Animation things (may eventually go in own library)
     class_<skottie::Animation>("Animation")
@@ -377,7 +674,9 @@
             self.render(canvas, &r);
         }), allow_raw_pointers());
 
-    function("MakeAnimation", &MakeAnimation);
+    function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
+        return skottie::Animation::Make(json.c_str(), json.length());
+    }));
     constant("skottie", true);
 #endif
 
@@ -405,8 +704,8 @@
         const uint8_t* nimaBytes = reinterpret_cast<const uint8_t*>(nptr);
         const uint8_t* textureBytes = reinterpret_cast<const uint8_t*>(tptr);
 
-        auto nima = SkData::MakeWithoutCopy(nimaBytes, nlen);
-        auto texture = SkData::MakeWithoutCopy(textureBytes, tlen);
+        auto nima = SkData::MakeFromMalloc(nimaBytes, nlen);
+        auto texture = SkData::MakeFromMalloc(textureBytes, tlen);
         return new NimaActor(nima, texture);
     }), allow_raw_pointers());
     constant("nima", true);
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index 026ca32..b7a8653 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -117,7 +117,8 @@
   skia_use_freetype=true \
   skia_use_icu=false \
   skia_use_libheif=false \
-  skia_use_libjpeg_turbo=false \
+  skia_use_system_libjpeg_turbo = false \
+  skia_use_libjpeg_turbo=true \
   skia_use_libpng=true \
   skia_use_libwebp=false \
   skia_use_lua=false \
@@ -161,7 +162,9 @@
     -Imodules/skottie/include \
     -Imodules/sksg/include \
     -Isrc/core/ \
+    -Isrc/gpu/ \
     -Isrc/sfnt/ \
+    -Isrc/shaders/ \
     -Isrc/utils/ \
     -Itools \
     -Itools/fonts \
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index c11b4ca..4ced26d 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -29,8 +29,12 @@
 	LTRBRect: function() {},
 	MakeCanvas: function() {},
 	MakeCanvasSurface: function() {},
+	MakeImageShader: function() {},
+	MakeLinearGradientShader: function() {},
+	MakeRadialGradientShader: function() {},
 	MakeNimaActor: function() {},
 	MakeSkDashPathEffect: function() {},
+	MakeSkVertices: function() {},
 	MakeSurface: function() {},
 	currentContext: function() {},
 	getSkDataBytes: function() {},
@@ -39,8 +43,12 @@
 
 	// private API (i.e. things declared in the bindings that we use
 	// in the pre-js file)
+	_MakeImageShader: function() {},
+	_MakeLinearGradientShader: function() {},
 	_MakeNimaActor: function() {},
+	_MakeRadialGradientShader: function() {},
 	_MakeSkDashPathEffect: function() {},
+	_MakeSkVertices: function() {},
 	_getRasterN32PremulSurface: function() {},
 	_getWebGLSurface: function() {},
 
@@ -89,13 +97,16 @@
 		_close: function() {},
 		_conicTo: function() {},
 		_cubicTo: function() {},
+		_dash: function() {},
 		_lineTo: function() {},
 		_moveTo: function() {},
 		_op: function() {},
 		_quadTo: function() {},
 		_rect: function() {},
 		_simplify: function() {},
+		_stroke: function() {},
 		_transform: function() {},
+		_trim: function() {},
 		delete: function() {},
 	},
 
@@ -137,6 +148,16 @@
 		delete: function() {},
 	},
 
+	SkVertices: {
+		// public API (from C++ bindings)
+		/** @return {CanvasKit.SkVertices} */
+		applyBones: function() {},
+
+		// private API
+		/** @return {CanvasKit.SkVertices} */
+		_applyBones: function() {},
+	},
+
 	// Constants and Enums
 	gpu: {},
 	skottie: {},
@@ -162,11 +183,19 @@
 	/**
 	 * @type {Float32Array}
 	 */
-	HEAPF32: {}, // only needed for TypedArray mallocs
+	HEAPF32: {},
 	/**
 	 * @type {Uint8Array}
 	 */
 	HEAPU8: {},
+	/**
+	 * @type {Uint16Array}
+	 */
+	HEAPU16: {},
+	/**
+	 * @type {Int32Array}
+	 */
+	HEAP32: {},
 
 	_malloc: function() {},
 	_free: function() {},
@@ -181,16 +210,28 @@
 CanvasKit.SkPath.prototype.close = function() {};
 CanvasKit.SkPath.prototype.conicTo = function() {};
 CanvasKit.SkPath.prototype.cubicTo = function() {};
+CanvasKit.SkPath.prototype.dash = function() {};
 CanvasKit.SkPath.prototype.lineTo = function() {};
 CanvasKit.SkPath.prototype.moveTo = function() {};
 CanvasKit.SkPath.prototype.op = function() {};
 CanvasKit.SkPath.prototype.quadTo = function() {};
 CanvasKit.SkPath.prototype.rect = function() {};
 CanvasKit.SkPath.prototype.simplify = function() {};
+CanvasKit.SkPath.prototype.stroke = function() {};
 CanvasKit.SkPath.prototype.transform = function() {};
+CanvasKit.SkPath.prototype.trim = function() {};
 
 CanvasKit.SkSurface.prototype.flush = function() {};
 CanvasKit.SkSurface.prototype.dispose = function() {};
 
+CanvasKit.SkVertices.prototype.applyBones = function() {};
+
+// Define StrokeOpts object
+var StrokeOpts = {};
+StrokeOpts.prototype.width;
+StrokeOpts.prototype.miter_limit;
+StrokeOpts.prototype.cap;
+StrokeOpts.prototype.join;
+
 // Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
 function loadWebAssemblyModule() {}
diff --git a/experimental/canvaskit/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js
index 4289d87..0d13fb0 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2d.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2d.js
@@ -141,14 +141,14 @@
 
     this.stroke = function() {
       if (this._currentPath) {
-        this._paint.setStyle(CanvasKit.PaintStyle.STROKE);
+        this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
         this._canvas.drawPath(this._currentPath, this._paint);
       }
     }
 
     this.strokeText = function(text, x, y, maxWidth) {
       // TODO do something with maxWidth, probably involving measure
-      this._paint.setStyle(CanvasKit.PaintStyle.STROKE);
+      this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
       this._canvas.drawText(text, x, y, this._paint);
     }
 
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index d74f380..be9cab1 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -1,7 +1,7 @@
 // Adds JS functions to augment the CanvasKit interface.
 // For example, if there is a wrapper around the C++ call or logic to allow
 // chaining, it should go here.
-(function(CanvasKit){
+(function(CanvasKit) {
   // CanvasKit.onRuntimeInitialized is called after the WASM library has loaded.
   // Anything that modifies an exposed class (e.g. SkPath) should be set
   // after onRuntimeInitialized, otherwise, it can happen outside of that scope.
@@ -11,7 +11,9 @@
       // Takes 1, 2, or 10 args, where the first arg is always the path.
       // The options for the remaining args are:
       //   - an array of 9 parameters
-      //   - the 9 parameters of a full Matrix
+      //   - the 9 parameters of a full matrix
+      //     an array of 6 parameters (omitting perspective)
+      //     the 6 non-perspective params of a matrix.
       if (arguments.length === 1) {
         // Add path, unchanged.  Use identify matrix
         this._addPath(arguments[0], 1, 0, 0,
@@ -22,16 +24,17 @@
         var sm = arguments[1];
         this._addPath(arguments[0], a[1], a[2], a[3],
                                     a[4], a[5], a[6],
-                                    a[7], a[8], a[9]);
-      } else if (arguments.length === 10) {
+                                    a[7] || 0, a[8] || 0, a[9] || 1);
+      } else if (arguments.length === 7 || arguments.length === 10) {
         // User provided the 9 params of a (full) matrix directly.
+        // (or just the 6 non perspective ones)
         // These are in the same order as what Skia expects.
         var a = arguments;
         this._addPath(arguments[0], a[1], a[2], a[3],
                                     a[4], a[5], a[6],
-                                    a[7], a[8], a[9]);
+                                    a[7] || 0, a[8] || 0, a[9] || 1);
       } else {
-        console.err('addPath expected to take 1, 2, or 10 args. Got ' + arguments.length);
+        console.err('addPath expected to take 1, 2, 7, or 10 args. Got ' + arguments.length);
         return null;
       }
       return this;
@@ -57,6 +60,13 @@
       return this;
     };
 
+    CanvasKit.SkPath.prototype.dash = function(on, off, phase) {
+      if (this._dash(on, off, phase)) {
+        return this;
+      }
+      return null;
+    };
+
     CanvasKit.SkPath.prototype.lineTo = function(x, y) {
       this._lineTo(x, y);
       return this;
@@ -86,26 +96,62 @@
       return null;
     };
 
+    CanvasKit.SkPath.prototype.stroke = function(opts) {
+      // Fill out any missing values with the default values.
+      /**
+       * See externs.js for this definition
+       * @type {StrokeOpts}
+       */
+      opts = opts || {};
+      opts.width = opts.width || 1;
+      opts.miter_limit = opts.miter_limit || 4;
+      opts.cap = opts.cap || CanvasKit.StrokeCap.BUTT;
+      opts.join = opts.join || CanvasKit.StrokeJoin.MITER;
+      if (this._stroke(opts)) {
+        return this;
+      }
+      return null;
+    };
+
     CanvasKit.SkPath.prototype.transform = function() {
       // Takes 1 or 9 args
       if (arguments.length === 1) {
-        // argument 1 should be a 9 element array.
+        // argument 1 should be a 6 or 9 element array.
         var a = arguments[0];
         this._transform(a[0], a[1], a[2],
                         a[3], a[4], a[5],
-                        a[6], a[7], a[8]);
-      } else if (arguments.length === 9) {
-        // these arguments are the 9 members of the matrix
+                        a[6] || 0, a[7] || 0, a[8] || 1);
+      } else if (arguments.length === 6 || arguments.length === 9) {
+        // these arguments are the 6 or 9 members of the matrix
         var a = arguments;
         this._transform(a[0], a[1], a[2],
                         a[3], a[4], a[5],
-                        a[6], a[7], a[8]);
+                        a[6] || 0, a[7] || 0, a[8] || 1);
       } else {
-        console.err('transform expected to take 1 or 9 arguments. Got ' + arguments.length);
-        return null;
+        throw 'transform expected to take 1 or 9 arguments. Got ' + arguments.length;
       }
       return this;
     };
+    // isComplement is optional, defaults to false
+    CanvasKit.SkPath.prototype.trim = function(startT, stopT, isComplement) {
+      if (this._trim(startT, stopT, !!isComplement)) {
+        return this;
+      }
+      return null;
+    };
+
+    // bones should be a 3d array.
+    // Each bone is a 3x2 transformation matrix in column major order:
+    // | scaleX   skewX transX |
+    // |  skewY  scaleY transY |
+    // and bones is an array of those matrices.
+    // Returns a copy of this (SkVertices) with the bones applied.
+    CanvasKit.SkVertices.prototype.applyBones = function(bones) {
+      var bPtr = copy3dArray(bones, CanvasKit.HEAPF32);
+      var vert = this._applyBones(bPtr, bones.length);
+      CanvasKit._free(bPtr);
+      return vert;
+    }
 
     // Run through the JS files that are added at compile time.
     if (CanvasKit._extraInitializations) {
@@ -125,6 +171,68 @@
     };
   }
 
+  var nullptr = 0; // emscripten doesn't like to take null as uintptr_t
+
+  // arr can be a normal JS array or a TypedArray
+  // dest is something like CanvasKit.HEAPF32
+  function copy1dArray(arr, dest) {
+    if (!arr || !arr.length) {
+      return nullptr;
+    }
+    var ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
+    // In c++ terms, the WASM heap is a uint8_t*, a long buffer/array of single
+    // byte elements. When we run _malloc, we always get an offset/pointer into
+    // that block of memory.
+    // CanvasKit exposes some different views to make it easier to work with
+    // different types. HEAPF32 for example, exposes it as a float*
+    // However, to make the ptr line up, we have to do some pointer arithmetic.
+    // Concretely, we need to convert ptr to go from an index into a 1-byte-wide
+    // buffer to an index into a 4-byte-wide buffer (in the case of HEAPF32)
+    // and thus we divide ptr by 4.
+    dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);
+    return ptr;
+  }
+
+  // arr should be a non-jagged 2d JS array (TypeyArrays can't be nested
+  //     inside themselves.)
+  // dest is something like CanvasKit.HEAPF32
+  function copy2dArray(arr, dest) {
+    if (!arr || !arr.length) {
+      return nullptr;
+    }
+    var ptr = CanvasKit._malloc(arr.length * arr[0].length * dest.BYTES_PER_ELEMENT);
+    var idx = 0;
+    var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
+    for (var r = 0; r < arr.length; r++) {
+      for (var c = 0; c < arr[0].length; c++) {
+        dest[adjustedPtr + idx] = arr[r][c];
+        idx++;
+      }
+    }
+    return ptr;
+  }
+
+  // arr should be a non-jagged 3d JS array (TypeyArrays can't be nested
+  //     inside themselves.)
+  // dest is something like CanvasKit.HEAPF32
+  function copy3dArray(arr, dest) {
+    if (!arr || !arr.length || !arr[0].length) {
+      return nullptr;
+    }
+    var ptr = CanvasKit._malloc(arr.length * arr[0].length * arr[0][0].length * dest.BYTES_PER_ELEMENT);
+    var idx = 0;
+    var adjustedPtr = ptr / dest.BYTES_PER_ELEMENT;
+    for (var x = 0; x < arr.length; x++) {
+      for (var y = 0; y < arr[0].length; y++) {
+        for (var z = 0; z < arr[0][0].length; z++) {
+          dest[adjustedPtr + idx] = arr[x][y][z];
+          idx++;
+        }
+      }
+    }
+    return ptr;
+  }
+
   CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
     if (!phase) {
       phase = 0;
@@ -132,13 +240,91 @@
     if (!intervals.length || intervals.length % 2 === 1) {
       throw 'Intervals array must have even length';
     }
-    if (!(intervals instanceof Float32Array)) {
-      intervals = Float32Array.from(intervals);
+    var ptr = copy1dArray(intervals, CanvasKit.HEAPF32);
+    var dpe = CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase);
+    CanvasKit._free(ptr);
+    return dpe;
+  }
+
+  CanvasKit.MakeImageShader = function(imgData, xTileMode, yTileMode) {
+    var iptr = CanvasKit._malloc(imgData.byteLength);
+    CanvasKit.HEAPU8.set(new Uint8Array(imgData), iptr);
+    // No need to _free iptr, ImageShader takes it with SkData::MakeFromMalloc
+
+    return CanvasKit._MakeImageShader(iptr, imgData.byteLength, xTileMode, yTileMode);
+  }
+
+  CanvasKit.MakeLinearGradientShader = function(start, end, colors, pos, mode, localMatrix, flags) {
+    var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
+    var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
+    flags = flags || 0;
+
+    if (localMatrix) {
+      // Add perspective args if not provided.
+      if (localMatrix.length === 6) {
+        localMatrix.push(0, 0, 1);
+      }
+      var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
+                                                    colors.length, mode, flags, localMatrix);
+    } else {
+      var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr,
+                                                    colors.length, mode, flags);
     }
-    var BYTES_PER_ELEMENT = 4; // Float32Array always has 4 bytes per element
-    var ptr = CanvasKit._malloc(intervals.length * BYTES_PER_ELEMENT);
-    CanvasKit.HEAPF32.set(intervals, ptr / BYTES_PER_ELEMENT);
-    return CanvasKit._MakeSkDashPathEffect(ptr, intervals.length, phase);
+
+    CanvasKit._free(colorPtr);
+    CanvasKit._free(posPtr);
+    return lgs;
+  }
+
+  CanvasKit.MakeRadialGradientShader = function(center, radius, colors, pos, mode, localMatrix, flags) {
+    // TODO: matrix and flags
+    var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
+    var posPtr =   copy1dArray(pos,    CanvasKit.HEAPF32);
+    flags = flags || 0;
+
+    if (localMatrix) {
+      // Add perspective args if not provided.
+      if (localMatrix.length === 6) {
+        localMatrix.push(0, 0, 1);
+      }
+      var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
+                                                    colors.length, mode, flags, localMatrix);
+    } else {
+      var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr,
+                                                    colors.length, mode, flags);
+    }
+
+    CanvasKit._free(colorPtr);
+    CanvasKit._free(posPtr);
+    return rgs;
+  }
+
+  CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
+                                      boneIndices, boneWeights, indices) {
+    var positionPtr = copy2dArray(positions,          CanvasKit.HEAPF32);
+    var texPtr =      copy2dArray(textureCoordinates, CanvasKit.HEAPF32);
+    // Since we write the colors to memory as signed integers (JSColor), we can
+    // read them out on the other side as unsigned ints (SkColor) just fine
+    // - it's effectively casting.
+    var colorPtr =    copy1dArray(colors,             CanvasKit.HEAP32);
+
+    var boneIdxPtr =  copy2dArray(boneIndices,        CanvasKit.HEAP32);
+    var boneWtPtr  =  copy2dArray(boneWeights,        CanvasKit.HEAPF32);
+    var idxPtr =      copy1dArray(indices,            CanvasKit.HEAPU16);
+
+    var idxCount = (indices && indices.length) || 0;
+    // _MakeVertices will copy all the values in, so we are free to release
+    // the memory after.
+    var vertices = CanvasKit._MakeSkVertices(mode, positions.length, positionPtr,
+                                             texPtr, colorPtr, boneIdxPtr, boneWtPtr,
+                                             idxCount, idxPtr);
+    positionPtr && CanvasKit._free(positionPtr);
+    texPtr && CanvasKit._free(texPtr);
+    colorPtr && CanvasKit._free(colorPtr);
+    idxPtr && CanvasKit._free(idxPtr);
+    boneIdxPtr && CanvasKit._free(boneIdxPtr);
+    boneWtPtr && CanvasKit._free(boneWtPtr);
+    return vertices;
   }
 
   CanvasKit.MakeNimaActor = function(nimaFile, nimaTexture) {
@@ -146,6 +332,7 @@
     CanvasKit.HEAPU8.set(new Uint8Array(nimaFile), nptr);
     var tptr = CanvasKit._malloc(nimaTexture.byteLength);
     CanvasKit.HEAPU8.set(new Uint8Array(nimaTexture), tptr);
+    // No need to _free these ptrs, NimaActor takes them with SkData::MakeFromMalloc
 
     return CanvasKit._MakeNimaActor(nptr, nimaFile.byteLength, tptr, nimaTexture.byteLength);
   }
diff --git a/include/effects/SkDashPathEffect.h b/include/effects/SkDashPathEffect.h
index f742956..78ddb16 100644
--- a/include/effects/SkDashPathEffect.h
+++ b/include/effects/SkDashPathEffect.h
@@ -14,7 +14,8 @@
 public:
     /** intervals: array containing an even number of entries (>=2), with
          the even indices specifying the length of "on" intervals, and the odd
-         indices specifying the length of "off" intervals.
+         indices specifying the length of "off" intervals. This array will be
+         copied in Make, and can be disposed of freely after.
         count: number of elements in the intervals array
         phase: offset into the intervals array (mod the sum of all of the
          intervals).
diff --git a/modules/pathkit/pathkit_wasm_bindings.cpp b/modules/pathkit/pathkit_wasm_bindings.cpp
index d69e6dd..c8d821f 100644
--- a/modules/pathkit/pathkit_wasm_bindings.cpp
+++ b/modules/pathkit/pathkit_wasm_bindings.cpp
@@ -38,6 +38,7 @@
 using SkPathOrNull = emscripten::val;
 // Self-documenting for when we return a string
 using JSString = emscripten::val;
+using JSArray = emscripten::val;
 
 // =================================================================================
 // Creating/Exporting Paths with cmd arrays
@@ -53,11 +54,11 @@
     }
 }
 
-emscripten::val EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
-    emscripten::val cmds = emscripten::val::array();
+JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
+    JSArray cmds = emscripten::val::array();
 
     VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
-        emscripten::val cmd = emscripten::val::array();
+        JSArray cmd = emscripten::val::array();
         switch (verb) {
         case SkPath::kMove_Verb:
             cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());