[CanvasKit] Implement some basic Canvas/Surface things.
drawText is having issues in a release build. Skottie sometimes
asserts in debug mode. This possibly has something to do with
memory alignment - like https://skia-review.googlesource.com/c/skia/+/155980
helped fix.
Patchset 9 shows off integrating Skia drawing to
an HTML canvas using Ganesh.
To see it locally, set up https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
and then set $EMSDK to be that directory. Then run
make clean
make local-example
and navigate to http://localhost:8000/skia-wasm/example.html
Patchset 20 shows off Skottie animating directly to a Canvas.
Docs-Preview: https://skia.org/?cl=153882
Bug: skia:
Change-Id: I2ad2f4ffac00925ee901982ccbaeb7aa63b1ea23
Reviewed-on: https://skia-review.googlesource.com/153882
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 67195fc..fe37aed 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -399,7 +399,8 @@
}
optional("fontmgr_custom") {
- enabled = is_linux && skia_use_freetype && !skia_use_fontconfig
+ enabled = (is_linux || target_cpu == "wasm") && skia_use_freetype &&
+ !skia_use_fontconfig
deps = [
":typeface_freetype",
@@ -929,7 +930,7 @@
]
}
- if (is_linux) {
+ if (is_linux || target_cpu == "wasm") {
sources += [ "src/ports/SkDebug_stdio.cpp" ]
if (skia_use_egl) {
libs += [ "GLESv2" ]
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
new file mode 100644
index 0000000..dc76224
--- /dev/null
+++ b/experimental/canvaskit/Makefile
@@ -0,0 +1,25 @@
+clean:
+ rm -rf ../../out/canvaskit_wasm
+ rm -rf ./canvas-kit/bin
+ $(MAKE) release
+
+release:
+ # Does an incremental build where possible.
+ ./compile.sh
+ mkdir -p ./canvas-kit/bin
+ cp ../../out/canvaskit_wasm/skia.js ./canvas-kit/bin
+ cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+
+debug:
+ # Does an incremental build where possible.
+ ./compile.sh debug
+ mkdir -p ./canvas-kit/bin
+ cp ../../out/canvaskit_wasm/skia.js ./canvas-kit/bin
+ cp ../../out/canvaskit_wasm/skia.wasm ./canvas-kit/bin
+
+local-example:
+ rm -rf node_modules/canvas-kit
+ mkdir -p node_modules
+ ln -s -T ../canvas-kit node_modules/canvas-kit
+ echo "Go check out http://localhost:8000/canvas-kit/example.html"
+ python serve.py
diff --git a/experimental/canvaskit/canvas-kit/.gitignore b/experimental/canvaskit/canvas-kit/.gitignore
new file mode 100644
index 0000000..6dd29b7
--- /dev/null
+++ b/experimental/canvaskit/canvas-kit/.gitignore
@@ -0,0 +1 @@
+bin/
\ No newline at end of file
diff --git a/experimental/canvaskit/canvas-kit/example.html b/experimental/canvaskit/canvas-kit/example.html
new file mode 100644
index 0000000..6b92186
--- /dev/null
+++ b/experimental/canvaskit/canvas-kit/example.html
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<title>CanvasKit (Skia via Web Assembly)</title>
+<meta charset="utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<style>
+ svg, canvas {
+ border: 1px dashed #AAA;
+ }
+
+ #patheffect,#paths,#sk_drinks,#sk_party, #sk_legos {
+ width: 300px;
+ height: 300px;
+ }
+
+</style>
+
+<h2> CanvasKit draws Paths to WebGL</h2>
+<canvas id=patheffect width=300 height=300></canvas>
+<canvas id=paths width=200 height=200></canvas>
+<canvas id=ink width=300 height=300></canvas>
+
+<h2> Skottie </h2>
+<canvas id=sk_legos width=300 height=300></canvas>
+<canvas id=sk_drinks width=500 height=500></canvas>
+<canvas id=sk_party width=800 height=800></canvas>
+
+<!-- Doesn't work yet. -->
+<button id=lego_btn>Take a picture of the legos</button>
+
+<script type="text/javascript" src="/node_modules/canvas-kit/bin/skia.js"></script>
+
+<script type="text/javascript" charset="utf-8">
+
+ var CanvasKit = null;
+ var legoJSON = null;
+ var drinksJSON = null;
+ var confettiJSON = null;
+ CanvasKitInit({
+ locateFile: (file) => '/node_modules/canvas-kit/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
+ SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+ SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
+ SkottieExample(CanvasKit, 'sk_party', confettiJSON);
+ });
+
+ fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
+ resp.text().then((str) => {
+ legoJSON = str;
+ SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+ });
+ });
+
+ fetch('https://storage.googleapis.com/skia-cdn/misc/drinks.json').then((resp) => {
+ resp.text().then((str) => {
+ drinksJSON = str;
+ SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
+ });
+ });
+
+ fetch('https://storage.googleapis.com/skia-cdn/misc/confetti.json').then((resp) => {
+ resp.text().then((str) => {
+ confettiJSON = str;
+ SkottieExample(CanvasKit, 'sk_party', confettiJSON);
+ });
+ });
+
+ // crashes the lego drawing
+ const btn = document.getElementById('lego_btn');
+ btn.addEventListener('click', () => {
+ const surface = CanvasKit.getWebGLSurface('sk_legos');
+ if (!surface) {
+ console.log('Could not get lego surface');
+ }
+
+ const img = surface.makeImageSnapshot()
+ if (!img) { return }
+ const png = img.encodeToData()
+ if (!png) { return }
+ const pngBytes = CanvasKit.getSkDataBytes(png);
+ // See https://stackoverflow.com/a/12713326
+ let b64encoded = btoa(String.fromCharCode.apply(null, pngBytes));
+ console.log("base64 encoded image", b64encoded);
+ });
+
+ function DrawingExample(CanvasKit) {
+ const surface = CanvasKit.getWebGLSurface('patheffect');
+ if (!surface) {
+ console.log('Could not make surface');
+ }
+ const context = CanvasKit.currentContext();
+
+ const canvas = surface.getCanvas();
+
+ const paint = new CanvasKit.SkPaint();
+
+ const textPaint = new CanvasKit.SkPaint();
+ textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
+ textPaint.setTextSize(30);
+ textPaint.setAntiAlias(true);
+
+ let i = 0;
+
+ let X = 128;
+ let Y = 128;
+
+ function drawFrame() {
+ const path = starPath(CanvasKit, X, Y);
+ CanvasKit.setCurrentContext(context);
+ const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
+ i++;
+
+ paint.setPathEffect(dpe);
+ 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.drawPath(path, paint);
+ canvas.drawText('Try mousing over!', 10, 280, textPaint);
+ canvas.flush();
+ dpe.delete();
+ path.delete();
+ window.requestAnimationFrame(drawFrame);
+ }
+ window.requestAnimationFrame(drawFrame);
+
+ // Make
+ document.getElementById('patheffect').addEventListener('mousemove', (e) => {
+ X = e.offsetX;
+ Y = e.offsetY;
+ });
+
+ // A client would need to delete this if it didn't go on for ever.
+ //paint.delete();
+ }
+
+ function PathExample(CanvasKit) {
+ const surface = CanvasKit.getWebGLSurface('paths');
+ if (!surface) {
+ console.log('Could not make surface');
+ }
+ const context = CanvasKit.currentContext();
+
+ const canvas = surface.getCanvas();
+
+ function drawFrame() {
+ CanvasKit.setCurrentContext(context);
+ const paint = new CanvasKit.SkPaint();
+ paint.setStrokeWidth(1.0);
+ paint.setAntiAlias(true);
+ paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+ paint.setStyle(CanvasKit.PaintStyle.STROKE);
+
+ const path = new CanvasKit.SkPath();
+ path.moveTo(20, 5);
+ path.lineTo(30, 20);
+ path.lineTo(40, 10);
+ path.lineTo(50, 20);
+ path.lineTo(60, 0);
+ path.lineTo(20, 5);
+
+ path.moveTo(20, 80);
+ path.cubicTo(90, 10, 160, 150, 190, 10);
+
+ path.moveTo(36, 148);
+ path.quadTo(66, 188, 120, 136);
+ path.lineTo(36, 148);
+
+ path.moveTo(150, 180);
+ path.arcTo(150, 100, 50, 200, 20);
+ path.lineTo(160, 160);
+
+ path.moveTo(20, 120);
+ path.lineTo(20, 120);
+
+ canvas.drawPath(path, paint);
+
+ canvas.flush();
+
+ path.delete();
+ paint.delete();
+ // Intentionally just draw frame once
+ }
+ window.requestAnimationFrame(drawFrame);
+ }
+
+
+ function InkExample(CanvasKit) {
+ const surface = CanvasKit.getWebGLSurface('ink');
+ if (!surface) {
+ console.log('Could not make surface');
+ }
+ const context = CanvasKit.currentContext();
+
+ const canvas = surface.getCanvas();
+
+ let paint = new CanvasKit.SkPaint();
+ paint.setAntiAlias(true);
+ paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+ paint.setStyle(CanvasKit.PaintStyle.STROKE);
+ paint.setStrokeWidth(4.0);
+ paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
+
+ let path = new CanvasKit.SkPath();
+ path.moveTo(30, 30);
+ path.lineTo(60, 80);
+
+ let paths = [path];
+ let paints = [paint];
+
+ function drawFrame() {
+ CanvasKit.setCurrentContext(context);
+
+ for (let i = 0; i < paints.length && i < paths.length; i++) {
+ canvas.drawPath(paths[i], paints[i]);
+ }
+ canvas.flush();
+
+ window.requestAnimationFrame(drawFrame);
+ }
+
+ let hold = false;
+ document.getElementById('ink').addEventListener('mousemove', (e) => {
+ if (!e.buttons) {
+ hold = false;
+ return;
+ }
+ if (hold) {
+ path.lineTo(e.offsetX, e.offsetY);
+ } else {
+ paint = paint.copy();
+ paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
+ paints.push(paint);
+ path = new CanvasKit.SkPath();
+ paths.push(path);
+ path.moveTo(e.offsetX, e.offsetY);
+ }
+ hold = true;
+ });
+ window.requestAnimationFrame(drawFrame);
+ }
+
+ function starPath(CanvasKit, X=128, Y=128, R=116) {
+ let p = new CanvasKit.SkPath();
+ p.moveTo(X + R, Y);
+ for (let i = 1; i < 8; i++) {
+ let a = 2.6927937 * i;
+ p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
+ }
+ return p;
+ }
+
+ function fps(frameTimes) {
+ let total = 0;
+ for (let ft of frameTimes) {
+ total += ft;
+ }
+ return frameTimes.length / total;
+ }
+
+ function SkottieExample(CanvasKit, id, jsonStr, bounds) {
+ if (!CanvasKit || !jsonStr) {
+ return;
+ }
+ const animation = CanvasKit.MakeAnimation(jsonStr);
+ const duration = animation.duration() * 1000;
+ const size = animation.size();
+ let c = document.getElementById(id);
+ bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
+
+ const surface = CanvasKit.getWebGLSurface(id);
+ if (!surface) {
+ console.log('Could not make surface');
+ }
+ const context = CanvasKit.currentContext();
+ const canvas = surface.getCanvas();
+
+ let firstFrame = new Date().getTime();
+
+ function drawFrame() {
+ let now = new Date().getTime();
+ let seek = ((now - firstFrame) / duration) % 1.0;
+ CanvasKit.setCurrentContext(context);
+ animation.seek(seek);
+
+ animation.render(canvas, bounds);
+ canvas.flush();
+ window.requestAnimationFrame(drawFrame);
+ }
+ window.requestAnimationFrame(drawFrame);
+
+ //animation.delete();
+ }
+
+</script>
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
new file mode 100644
index 0000000..203d41c
--- /dev/null
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GrGLInterface.h"
+#include "GrGLTypes.h"
+#include "SkCanvas.h"
+#include "SkCanvas.h"
+#include "SkDashPathEffect.h"
+#include "SkCornerPathEffect.h"
+#include "SkDiscretePathEffect.h"
+#include "SkFontMgr.h"
+#include "SkFontMgrPriv.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPathEffect.h"
+#include "SkScalar.h"
+#include "SkSurface.h"
+#include "SkSurfaceProps.h"
+#include "SkTestFontMgr.h"
+#include "Skottie.h"
+
+#include <iostream>
+#include <string>
+#include <GL/gl.h>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/html5.h>
+
+using namespace emscripten;
+
+using JSColor = int32_t;
+
+
+void EMSCRIPTEN_KEEPALIVE initFonts() {
+ gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
+}
+
+// Wraps the WebGL context in an SkSurface and returns it.
+sk_sp<SkSurface> getWebGLSurface(std::string id, int width, int height) {
+ // Context configurations
+ EmscriptenWebGLContextAttributes attrs;
+ emscripten_webgl_init_context_attributes(&attrs);
+ attrs.alpha = true;
+ attrs.premultipliedAlpha = true;
+ attrs.majorVersion = 1;
+ attrs.enableExtensionsByDefault = true;
+
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(id.c_str(), &attrs);
+ if (context < 0) {
+ printf("failed to create webgl context %d\n", context);
+ return nullptr;
+ }
+ EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
+ if (r < 0) {
+ printf("failed to make webgl current %d\n", r);
+ return nullptr;
+ }
+
+ glClearColor(0, 0, 0, 0);
+ glClearStencil(0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ // setup GrContext
+ auto interface = GrGLMakeNativeInterface();
+
+ // setup contexts
+ sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
+
+ // Wrap the frame buffer object attached to the screen in a Skia render target so Skia can
+ // render to it
+ GrGLint buffer;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
+ GrGLFramebufferInfo info;
+ info.fFBOID = (GrGLuint) buffer;
+ SkColorType colorType;
+
+ info.fFormat = GL_RGBA8;
+ colorType = kRGBA_8888_SkColorType;
+
+ GrBackendRenderTarget target(width, height, 0, 8, info);
+
+ sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
+ kBottomLeft_GrSurfaceOrigin,
+ colorType, nullptr, nullptr));
+ return surface;
+}
+
+sk_sp<skottie::Animation> MakeAnimation(std::string json) {
+ return skottie::Animation::Make(json.c_str(), json.length());
+}
+
+//========================================================================================
+// Path things
+//========================================================================================
+
+// All these Apply* methods are simple wrappers to avoid returning an object.
+// The default WASM bindings produce code that will leak if a return value
+// isn't assigned to a JS variable and has delete() called on it.
+// These Apply methods, combined with the smarter binding code allow for chainable
+// commands that don't leak if the return value is ignored (i.e. when used intuitively).
+
+void ApplyAddPath(SkPath& orig, const SkPath& newPath,
+ SkScalar scaleX, SkScalar skewX, SkScalar transX,
+ SkScalar skewY, SkScalar scaleY, SkScalar transY,
+ SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+ SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+ skewY , scaleY, transY,
+ pers0 , pers1 , pers2);
+ orig.addPath(newPath, m);
+}
+
+void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar radius) {
+ p.arcTo(x1, y1, x2, y2, radius);
+}
+
+void ApplyClose(SkPath& p) {
+ p.close();
+}
+
+void ApplyConicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar w) {
+ p.conicTo(x1, y1, x2, y2, w);
+}
+
+void ApplyCubicTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
+ SkScalar x3, SkScalar y3) {
+ p.cubicTo(x1, y1, x2, y2, x3, y3);
+}
+
+void ApplyLineTo(SkPath& p, SkScalar x, SkScalar y) {
+ p.lineTo(x, y);
+}
+
+void ApplyMoveTo(SkPath& p, SkScalar x, SkScalar y) {
+ p.moveTo(x, y);
+}
+
+void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
+ p.quadTo(x1, y1, x2, y2);
+}
+
+void ApplyTransform(SkPath& orig,
+ SkScalar scaleX, SkScalar skewX, SkScalar transX,
+ SkScalar skewY, SkScalar scaleY, SkScalar transY,
+ SkScalar pers0, SkScalar pers1, SkScalar pers2) {
+ SkMatrix m = SkMatrix::MakeAll(scaleX, skewX , transX,
+ skewY , scaleY, transY,
+ pers0 , pers1 , pers2);
+ orig.transform(m);
+}
+
+SkPath EMSCRIPTEN_KEEPALIVE CopyPath(const SkPath& a) {
+ SkPath copy(a);
+ return copy;
+}
+
+bool EMSCRIPTEN_KEEPALIVE Equals(const SkPath& a, const SkPath& b) {
+ 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()));
+}
+
+// Hack to avoid embind creating a binding for SkData destructor
+namespace emscripten {
+ namespace internal {
+ template<typename ClassType>
+ void raw_destructor(ClassType *);
+
+ template<>
+ void raw_destructor<SkData>(SkData *ptr) {
+ }
+ }
+}
+
+// Some timesignatures below have uintptr_t instead of a pointer to a primative
+// type (e.g. SkScalar). This is necessary because we can't use "bind" (EMSCRIPTEN_BINDINGS)
+// and pointers to primitive types (Only bound types like SkPoint). We could if we used
+// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
+// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
+// SkPath or SkCanvas.
+//
+// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
+// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
+// types Pi, Pf"). But, we can just pretend they are numbers and cast them to be pointers and
+// the compiler is happy.
+EMSCRIPTEN_BINDINGS(Skia) {
+ function("initFonts", &initFonts);
+ function("_getWebGLSurface", &getWebGLSurface, 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("_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());
+
+ 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.
+ self.clear(SkColor(color));
+ }))
+ .function("drawPaint", &SkCanvas::drawPaint)
+ .function("drawPath", &SkCanvas::drawPath)
+ .function("drawRect", &SkCanvas::drawRect)
+ .function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
+ return; // Currently broken, some memory things seem off.
+ //self.drawText(text.c_str(), text.length(), x, y, p);
+ }))
+ .function("flush", &SkCanvas::flush)
+ .function("save", &SkCanvas::save)
+ .function("translate", &SkCanvas::translate);
+
+ class_<SkData>("SkData")
+ .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
+ .function("size", &SkData::size);
+
+ class_<SkImage>("SkImage")
+ .smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
+ .function("encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData));
+
+ class_<SkPaint>("SkPaint")
+ .constructor<>()
+ .function("copy", optional_override([](const SkPaint& self)->SkPaint {
+ SkPaint p(self);
+ return p;
+ }))
+ .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.
+ self.setColor(SkColor(color));
+ }))
+ .function("setPathEffect", &SkPaint::setPathEffect)
+ .function("setShader", &SkPaint::setShader)
+ .function("setStrokeWidth", &SkPaint::setStrokeWidth)
+ .function("setStyle", &SkPaint::setStyle)
+ .function("setTextSize", &SkPaint::setTextSize);
+
+ class_<SkPathEffect>("SkPathEffect")
+ .smart_ptr<sk_sp<SkPathEffect>>("sk_sp<SkPathEffect>");
+
+ //TODO make these chainable like PathKit
+ class_<SkPath>("SkPath")
+ .constructor<>()
+ .constructor<const SkPath&>()
+ // interface.js has 3 overloads of addPath
+ .function("_addPath", &ApplyAddPath)
+ .function("_arcTo", &ApplyArcTo)
+ .function("_close", &ApplyClose)
+ .function("_conicTo", &ApplyConicTo)
+ .function("_cubicTo", &ApplyCubicTo)
+ .function("_lineTo", &ApplyLineTo)
+ .function("_moveTo", &ApplyMoveTo)
+ .function("_quadTo", &ApplyQuadTo)
+ .function("_transform", select_overload<void(SkPath& orig, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
+
+ .function("setFillType", &SkPath::setFillType)
+ .function("getFillType", &SkPath::getFillType)
+ .function("getBounds", &SkPath::getBounds)
+ .function("computeTightBounds", &SkPath::computeTightBounds)
+ .function("equals", &Equals)
+ .function("copy", &CopyPath);
+
+ class_<SkSurface>("SkSurface")
+ .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
+ .function("width", &SkSurface::width)
+ .function("height", &SkSurface::height)
+ .function("makeImageSnapshot", &SkSurface::makeImageSnapshot)
+ .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
+
+
+ enum_<SkPaint::Style>("PaintStyle")
+ .value("FILL", SkPaint::Style::kFill_Style)
+ .value("STROKE", SkPaint::Style::kStroke_Style)
+ .value("STROKE_AND_FILL", 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);
+
+ // A value object is much simpler than a class - it is returned as a JS
+ // object and does not require delete().
+ // https://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#value-types
+ value_object<SkRect>("SkRect")
+ .field("fLeft", &SkRect::fLeft)
+ .field("fTop", &SkRect::fTop)
+ .field("fRight", &SkRect::fRight)
+ .field("fBottom", &SkRect::fBottom);
+
+ // SkPoints can be represented by [x, y]
+ value_array<SkPoint>("SkPoint")
+ .element(&SkPoint::fX)
+ .element(&SkPoint::fY);
+
+ // {"w": Number, "h", Number}
+ value_object<SkSize>("SkSize")
+ .field("w", &SkSize::fWidth)
+ .field("h", &SkSize::fHeight);
+
+ value_object<SkISize>("SkISize")
+ .field("w", &SkISize::fWidth)
+ .field("h", &SkISize::fHeight);
+
+ // Animation things (may eventually go in own library)
+ class_<skottie::Animation>("Animation")
+ .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
+ .function("version", optional_override([](skottie::Animation& self)->std::string {
+ return std::string(self.version().c_str());
+ }))
+ .function("size", &skottie::Animation::size)
+ .function("duration", &skottie::Animation::duration)
+ .function("seek", &skottie::Animation::seek)
+ .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
+ self.render(canvas, nullptr);
+ }), allow_raw_pointers())
+ .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas, const SkRect r)->void {
+ self.render(canvas, &r);
+ }), allow_raw_pointers());
+
+ function("MakeAnimation", &MakeAnimation);
+
+ function("currentContext", &emscripten_webgl_get_current_context);
+ function("setCurrentContext", &emscripten_webgl_make_context_current);
+}
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
new file mode 100755
index 0000000..0c45186
--- /dev/null
+++ b/experimental/canvaskit/compile.sh
@@ -0,0 +1,154 @@
+#!/bin/bash
+# Copyright 2018 Google LLC
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex
+
+BASE_DIR=`cd $(dirname ${BASH_SOURCE[0]}) && pwd`
+# This expects the environment variable EMSDK to be set
+if [[ ! -d $EMSDK ]]; then
+ echo "Be sure to set the EMSDK environment variable."
+ exit 1
+fi
+
+BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
+
+# Navigate to SKIA_HOME from where this file is located.
+pushd $BASE_DIR/../..
+
+source $EMSDK/emsdk_env.sh
+
+RELEASE_CONF="-Oz --closure 1 --llvm-lto 3"
+
+if [[ $@ == *debug* ]]; then
+ echo "Building a Debug build"
+ RELEASE_CONF="-O0 --js-opts 0 -s SAFE_HEAP=1 -s ASSERTIONS=1 -g3 -DPATHKIT_TESTING -DSK_DEBUG"
+fi
+
+
+echo "Compiling bitcode"
+
+EMCC=`which emcc`
+EMCXX=`which em++`
+
+# Inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_bindings.sh
+./bin/gn gen ${BUILD_DIR} \
+ --args="cc=\"${EMCC}\" \
+ cxx=\"${EMCXX}\" \
+ extra_cflags_cc=[\"-frtti\",\"-s\",\"USE_FREETYPE=1\", \"-DIS_WEBGL=1\"] \
+ extra_cflags=[\"-s\",\"USE_FREETYPE=1\",\"-s\",\"USE_LIBPNG=1\", \"-DIS_WEBGL=1\"] \
+ is_debug=false \
+ is_official_build=true \
+ is_component_build=false \
+ target_cpu=\"wasm\" \
+ \
+ skia_use_egl=true \
+ skia_use_vulkan=false \
+ skia_use_libwebp=false \
+ skia_use_libpng=true \
+ skia_use_lua=false \
+ skia_use_dng_sdk=false \
+ skia_use_fontconfig=false \
+ skia_use_libjpeg_turbo=false \
+ skia_use_libheif=false \
+ skia_use_expat=false \
+ skia_use_vulkan=false \
+ skia_use_freetype=true \
+ skia_use_icu=false \
+ skia_use_expat=false \
+ skia_use_piex=false \
+ skia_use_zlib=true \
+ \
+ skia_enable_gpu=true \
+ skia_enable_fontmgr_empty=false \
+ skia_enable_pdf=false"
+
+ninja -C ${BUILD_DIR} libskia.a
+
+export EMCC_CLOSURE_ARGS="--externs $BASE_DIR/externs.js "
+
+# inspired by https://github.com/Zubnix/skia-wasm-port/blob/master/build_skia_wasm_bitcode.sh
+echo "Compiling bindings"
+${EMCC} \
+ $RELEASE_CONF \
+ -std=c++11 \
+ -Iinclude/c \
+ -Iinclude/codec \
+ -Iinclude/config \
+ -Iinclude/core \
+ -Iinclude/effects \
+ -Iinclude/gpu \
+ -Iinclude/gpu/gl \
+ -Iinclude/pathops \
+ -Iinclude/private \
+ -Iinclude/utils/ \
+ -Imodules/skottie/include \
+ -Isrc/core/ \
+ -Itools/fonts \
+ $BASE_DIR/canvaskit_bindings.cpp \
+ -o $BUILD_DIR/canvaskit_bindings.bc
+
+echo "Generating final wasm"
+
+# Skottie doesn't end up in libskia and is currently not its own library
+# so we just hack in the .cpp files we need for now.
+${EMCC} \
+ $RELEASE_CONF \
+ -Iinclude/c \
+ -Iinclude/codec \
+ -Iinclude/config \
+ -Iinclude/core \
+ -Iinclude/effects \
+ -Iinclude/gpu \
+ -Iinclude/gpu/gl \
+ -Iinclude/pathops \
+ -Iinclude/private \
+ -Iinclude/utils/ \
+ -Imodules/skottie/include \
+ -Imodules/sksg/include \
+ -Isrc/core/ \
+ -Isrc/utils/ \
+ -Isrc/sfnt/ \
+ -Itools/fonts \
+ -Itools \
+ -lEGL \
+ -lGLESv2 \
+ -std=c++11 \
+ --bind \
+ --pre-js $BASE_DIR/helper.js \
+ --pre-js $BASE_DIR/interface.js \
+ -DSKOTTIE_HACK \
+ $BUILD_DIR/canvaskit_bindings.bc \
+ $BUILD_DIR/libskia.a \
+ modules/skottie/src/Skottie.cpp \
+ modules/skottie/src/SkottieAdapter.cpp \
+ modules/skottie/src/SkottieAnimator.cpp \
+ modules/skottie/src/SkottieJson.cpp \
+ modules/skottie/src/SkottieLayer.cpp \
+ modules/skottie/src/SkottieLayerEffect.cpp \
+ modules/skottie/src/SkottiePrecompLayer.cpp \
+ modules/skottie/src/SkottieShapeLayer.cpp \
+ modules/skottie/src/SkottieTextLayer.cpp \
+ modules/skottie/src/SkottieValue.cpp \
+ modules/sksg/src/*.cpp \
+ src/core/SkCubicMap.cpp \
+ src/core/SkTime.cpp \
+ src/pathops/SkOpBuilder.cpp \
+ src/pathops/SkPathOpsTypes.cpp \
+ tools/fonts/SkTestFontMgr.cpp \
+ tools/fonts/SkTestTypeface.cpp \
+ src/utils/SkJSON.cpp \
+ src/utils/SkParse.cpp \
+ -s ALLOW_MEMORY_GROWTH=1 \
+ -s TOTAL_MEMORY=64MB \
+ -s EXPORT_NAME="CanvasKitInit" \
+ -s FORCE_FILESYSTEM=0 \
+ -s MODULARIZE=1 \
+ -s NO_EXIT_RUNTIME=1 \
+ -s STRICT=1 \
+ -s USE_FREETYPE=1 \
+ -s USE_LIBPNG=1 \
+ -s WASM=1 \
+ -o $BUILD_DIR/skia.js
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
new file mode 100644
index 0000000..5d07f0d
--- /dev/null
+++ b/experimental/canvaskit/externs.js
@@ -0,0 +1,80 @@
+/*
+ * This externs file prevents the Closure JS compiler from minifying away
+ * names of objects created by Emscripten.
+ * Basically, by defining empty objects and functions here, Closure will
+ * know not to rename them. This is needed because of our pre-js files,
+ * that is, the JS we hand-write to bundle into the output. That JS will be
+ * hit by the closure compiler and thus needs to know about what functions
+ * have special names and should not be minified.
+ *
+ * Emscripten does not support automatically generating an externs file, so we
+ * do it by hand. The general process is to write some JS code, and then put any
+ * calls to CanvasKit or related things in here. Running ./compile.sh and then
+ * looking at the minified results or running the Release trybot should
+ * verify nothing was missed. Optionally, looking directly at the minified
+ * pathkit.js can be useful when developing locally.
+ *
+ * Docs:
+ * https://github.com/cljsjs/packages/wiki/Creating-Externs
+ * https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
+ *
+ * Example externs:
+ * https://github.com/google/closure-compiler/tree/master/externs
+ */
+
+var CanvasKit = {
+ // public API (i.e. things we declare in the pre-js file)
+ Color: function(r, g, b, a) {},
+ currentContext: function() {},
+ getWebGLSurface: function(htmlID) {},
+ MakeSkDashPathEffect: function(intervals, phase) {},
+ setCurrentContext: function() {},
+ LTRBRect: function(l, t, r, b) {},
+
+ // private API (i.e. things declared in the bindings that we use
+ // in the pre-js file)
+ _getWebGLSurface: function(htmlID, w, h) {},
+ _malloc: function(size) {},
+ onRuntimeInitialized: function() {},
+ _MakeSkDashPathEffect: function(ptr, len, phase) {},
+
+ // Objects and properties on CanvasKit
+
+ HEAPF32: {}, // only needed for TypedArray mallocs
+
+ SkPath: {
+ // public API should go below because closure still will
+ // remove things declared here and not on the prototype.
+
+ // private API
+ _addPath: function(path, scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2) {},
+ _arcTo: function(x1, y1, x2, y2, radius) {},
+ _close: function() {},
+ _conicTo: function(x1, y1, x2, y2, w) {},
+ _cubicTo: function(cp1x, cp1y, cp2x, cp2y, x, y) {},
+ _lineTo: function(x1, y1) {},
+ _moveTo: function(x1, y1) {},
+ _op: function(otherPath, op) {},
+ _quadTo: function(cpx, cpy, x, y) {},
+ _rect: function(x, y, w, h) {},
+ _simplify: function() {},
+ _transform: function(scaleX, skewX, transX, skewY, scaleY, transY, pers0, pers1, pers2) {},
+ }
+}
+
+// Path public API
+CanvasKit.SkPath.prototype.addPath = function() {};
+CanvasKit.SkPath.prototype.arcTo = function(x1, y1, x2, y2, radius) {};
+CanvasKit.SkPath.prototype.close = function() {};
+CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {};
+CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {};
+CanvasKit.SkPath.prototype.lineTo = function(x, y) {};
+CanvasKit.SkPath.prototype.moveTo = function(x, y) {};
+CanvasKit.SkPath.prototype.op = function(otherPath, op) {};
+CanvasKit.SkPath.prototype.quadTo = function(x1, y1, x2, y2) {};
+CanvasKit.SkPath.prototype.rect = function(x, y, w, h) {};
+CanvasKit.SkPath.prototype.simplify = function() {};
+CanvasKit.SkPath.prototype.transform = function() {};
+
+// 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/helper.js b/experimental/canvaskit/helper.js
new file mode 100644
index 0000000..6cc1564
--- /dev/null
+++ b/experimental/canvaskit/helper.js
@@ -0,0 +1,16 @@
+
+// Adds any extra JS functions/helpers we want to CanvasKit.
+// Wrapped in a function to avoid leaking global variables.
+(function(CanvasKit){
+
+ function clamp(c) {
+ return Math.round(Math.max(0, Math.min(c || 0, 255)));
+ }
+
+ // Colors are just a 32 bit number with 8 bits each of a, r, g, b
+ // The API is the same as CSS's representation of color rgba(), that is
+ // r,g,b are 0-255, and a is 0.0 to 1.0.
+ CanvasKit.Color = function(r, g, b, a) {
+ return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
+ }
+}(Module)); // When this file is loaded in, the high level object is "Module";
\ No newline at end of file
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
new file mode 100644
index 0000000..f0e5b0c
--- /dev/null
+++ b/experimental/canvaskit/interface.js
@@ -0,0 +1,147 @@
+// 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){
+ // 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.
+ CanvasKit.onRuntimeInitialized = function() {
+ // All calls to 'this' need to go in externs.js so closure doesn't minify them away.
+ CanvasKit.SkPath.prototype.addPath = function() {
+ // 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
+ if (arguments.length === 1) {
+ // Add path, unchanged. Use identify matrix
+ this._addPath(arguments[0], 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1);
+ } else if (arguments.length === 2) {
+ // User provided the 9 params of a full matrix as an array.
+ 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) {
+ // User provided the 9 params of a (full) matrix directly.
+ // 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]);
+ } else {
+ console.err('addPath expected to take 1, 2, or 10 args. Got ' + arguments.length);
+ return null;
+ }
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.arcTo = function(x1, y1, x2, y2, radius) {
+ this._arcTo(x1, y1, x2, y2, radius);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.close = function() {
+ this._close();
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.conicTo = function(x1, y1, x2, y2, w) {
+ this._conicTo(x1, y1, x2, y2, w);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.cubicTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+ this._cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.lineTo = function(x, y) {
+ this._lineTo(x, y);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.moveTo = function(x, y) {
+ this._moveTo(x, y);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.op = function(otherPath, op) {
+ if (this._op(otherPath, op)) {
+ return this;
+ }
+ return null;
+ };
+
+ CanvasKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
+ this._quadTo(cpx, cpy, x, y);
+ return this;
+ };
+
+ CanvasKit.SkPath.prototype.simplify = function() {
+ if (this._simplify()) {
+ 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.
+ 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
+ var a = arguments;
+ this._transform(a[0], a[1], a[2],
+ a[3], a[4], a[5],
+ a[6], a[7], a[8]);
+ } else {
+ console.err('transform expected to take 1 or 9 arguments. Got ' + arguments.length);
+ return null;
+ }
+ return this;
+ };
+ }
+
+ CanvasKit.getWebGLSurface = function(htmlID) {
+ var canvas = document.getElementById(htmlID);
+ if (!canvas) {
+ throw 'Canvas with id ' + htmlID + ' was not found';
+ }
+ // Maybe better to use clientWidth/height. See:
+ // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
+ return this._getWebGLSurface(htmlID, canvas.width, canvas.height);
+ }
+
+ // Likely only used for tests.
+ CanvasKit.LTRBRect = function(l, t, r, b) {
+ return {
+ fLeft: l,
+ fTop: t,
+ fRight: r,
+ fBottom: b,
+ };
+ }
+
+ CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
+ if (!phase) {
+ phase = 0;
+ }
+ if (!intervals.length || intervals.length % 2 === 1) {
+ throw 'Intervals array must have even length';
+ }
+ if (!(intervals instanceof Float32Array)) {
+ intervals = Float32Array.from(intervals);
+ }
+ 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);
+ }
+
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/serve.py b/experimental/canvaskit/serve.py
new file mode 100644
index 0000000..8c6541f
--- /dev/null
+++ b/experimental/canvaskit/serve.py
@@ -0,0 +1,20 @@
+# Copyright 2018 Google LLC
+
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import SimpleHTTPServer
+import SocketServer
+
+PORT = 8000
+
+class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ pass
+
+Handler.extensions_map['.js'] = 'application/javascript'
+# Without the correct MIME type, async compilation doesn't work
+Handler.extensions_map['.wasm'] = 'application/wasm'
+
+httpd = SocketServer.TCPServer(("", PORT), Handler)
+
+httpd.serve_forever()
diff --git a/modules/pathkit/compile.sh b/modules/pathkit/compile.sh
index 25bc3d5..ea6f146 100755
--- a/modules/pathkit/compile.sh
+++ b/modules/pathkit/compile.sh
@@ -89,13 +89,13 @@
-DWEB_ASSEMBLY=1 \
-fno-rtti -fno-exceptions -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0 \
$WASM_CONF \
--s MODULARIZE=1 \
--s EXPORT_NAME="PathKitInit" \
--s NO_EXIT_RUNTIME=1 \
--s ERROR_ON_UNDEFINED_SYMBOLS=1 \
--s ERROR_ON_MISSING_LIBRARIES=1 \
--s NO_FILESYSTEM=1 \
-s BINARYEN_IGNORE_IMPLICIT_TRAPS=1 \
+-s ERROR_ON_MISSING_LIBRARIES=1 \
+-s ERROR_ON_UNDEFINED_SYMBOLS=1 \
+-s EXPORT_NAME="PathKitInit" \
+-s MODULARIZE=1 \
+-s NO_EXIT_RUNTIME=1 \
+-s NO_FILESYSTEM=1 \
-s STRICT=1 \
$OUTPUT \
$BASE_DIR/pathkit_wasm_bindings.cpp \
diff --git a/modules/pathkit/npm-asmjs/example.html b/modules/pathkit/npm-asmjs/example.html
index f51c628..c50c453 100644
--- a/modules/pathkit/npm-asmjs/example.html
+++ b/modules/pathkit/npm-asmjs/example.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<title>PathKit (Skia + asm.js)</title>
+<title>PathKit (Skia's Geometry + asm.js)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
diff --git a/modules/pathkit/npm-wasm/example.html b/modules/pathkit/npm-wasm/example.html
index 09af83b..488f6ec 100644
--- a/modules/pathkit/npm-wasm/example.html
+++ b/modules/pathkit/npm-wasm/example.html
@@ -1,5 +1,5 @@
<!DOCTYPE html>
-<title>PathKit (Skia + WASM)</title>
+<title>PathKit (Skia's Geometry + WASM)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
diff --git a/modules/pathkit/pathkit_wasm_bindings.cpp b/modules/pathkit/pathkit_wasm_bindings.cpp
index 38af2c6..d69e6dd 100644
--- a/modules/pathkit/pathkit_wasm_bindings.cpp
+++ b/modules/pathkit/pathkit_wasm_bindings.cpp
@@ -5,19 +5,19 @@
* found in the LICENSE file.
*/
+#include "SkCubicMap.h"
#include "SkDashPathEffect.h"
#include "SkFloatBits.h"
#include "SkFloatingPoint.h"
#include "SkMatrix.h"
#include "SkPaint.h"
+#include "SkPaintDefaults.h"
#include "SkParsePath.h"
-#include "SkStrokeRec.h"
#include "SkPath.h"
#include "SkPathOps.h"
-#include "SkCubicMap.h"
#include "SkRect.h"
-#include "SkPaintDefaults.h"
#include "SkString.h"
+#include "SkStrokeRec.h"
#include "SkTrimPathEffect.h"
#include <emscripten/emscripten.h>
diff --git a/site/user/modules/canvaskit.md b/site/user/modules/canvaskit.md
index 69658a6..60e53bf 100644
--- a/site/user/modules/canvaskit.md
+++ b/site/user/modules/canvaskit.md
@@ -8,7 +8,7 @@
enabling fast-paced development on the web platform.
It can also be used as a deployment mechanism for custom web apps requiring
cutting-edge features, like Skia's [Lottie
-animation](https://skia.org/user/modules/skottie) support.
+animation](https://skia.org/user/modules/skottie) support.
Features
@@ -26,30 +26,28 @@
<style>
#demo canvas {
border: 1px dashed #AAA;
+ margin: 2px;
}
- #patheffect {
- width: 400px
+ #patheffect, #ink {
+ width: 400px;
height: 400px;
}
- #sk_drinks,#sk_party {
+ #sk_legos, #sk_drinks,#sk_party {
width: 300px;
height: 300px;
}
- #sk_legos {
- width: 300px;
- height: 300px;
- }
</style>
<div id=demo>
<h3>An Interactive Path (try mousing over)</h3>
<canvas id=patheffect width=400 height=400></canvas>
+ <canvas id=ink width=400 height=400></canvas>
<h3>Skottie</h3>
- <canvas id=sk_legos width=600 height=600></canvas>
+ <canvas id=sk_legos width=300 height=300></canvas>
<canvas id=sk_drinks width=500 height=500></canvas>
<canvas id=sk_party width=800 height=800></canvas>
</div>
@@ -61,7 +59,7 @@
var locate_file = '';
if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
console.log('WebAssembly is supported!');
- locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.0.1/bin/';
+ locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.0.2/bin/';
} else {
console.log('WebAssembly is not supported (yet) on this browser.');
document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
@@ -78,7 +76,9 @@
}).then((CK) => {
CanvasKit = CK;
DrawingExample(CanvasKit);
- SkottieExample(CanvasKit, 'sk_legos', legoJSON);
+ InkExample(CanvasKit);
+ // Set bounds to fix the 4:3 resolution of the legos
+ SkottieExample(CanvasKit, 'sk_legos', legoJSON, {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
SkottieExample(CanvasKit, 'sk_drinks', drinksJSON);
SkottieExample(CanvasKit, 'sk_party', confettiJSON);
});
@@ -115,11 +115,6 @@
const paint = new CanvasKit.SkPaint();
- const textPaint = new CanvasKit.SkPaint();
- textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
- textPaint.setStrokeWidth(2);
- textPaint.setTextSize(60);
-
let i = 0;
let X = 128;
@@ -140,8 +135,6 @@
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
canvas.drawPath(path, paint);
- // Currently does not work.
- canvas.drawText('Some Text', 10, 10, textPaint);
canvas.flush();
dpe.delete();
path.delete();
@@ -159,6 +152,62 @@
//paint.delete();
}
+ function InkExample(CanvasKit) {
+ const surface = CanvasKit.getWebGLSurface('ink');
+ if (!surface) {
+ console.log('Could not make surface');
+ }
+ const context = CanvasKit.currentContext();
+
+ const canvas = surface.getCanvas();
+
+ let paint = new CanvasKit.SkPaint();
+ paint.setAntiAlias(true);
+ paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+ paint.setStyle(CanvasKit.PaintStyle.STROKE);
+ paint.setStrokeWidth(4.0);
+ // This effect smooths out the drawn lines a bit.
+ paint.setPathEffect(CanvasKit.MakeSkCornerPathEffect(50));
+
+ let path = new CanvasKit.SkPath();
+ path.moveTo(30, 30);
+ path.lineTo(60, 80);
+
+ let paths = [path];
+ let paints = [paint];
+
+ function drawFrame() {
+ CanvasKit.setCurrentContext(context);
+
+ for (let i = 0; i < paints.length && i < paths.length; i++) {
+ canvas.drawPath(paths[i], paints[i]);
+ }
+ canvas.flush();
+
+ window.requestAnimationFrame(drawFrame);
+ }
+
+ let hold = false;
+ document.getElementById('ink').addEventListener('mousemove', (e) => {
+ if (!e.buttons) {
+ hold = false;
+ return;
+ }
+ if (hold) {
+ path.lineTo(e.offsetX, e.offsetY);
+ } else {
+ paint = paint.copy();
+ paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
+ paints.push(paint);
+ path = new CanvasKit.SkPath();
+ paths.push(path);
+ path.moveTo(e.offsetX, e.offsetY);
+ }
+ hold = true;
+ });
+ window.requestAnimationFrame(drawFrame);
+ }
+
function starPath(CanvasKit, X=128, Y=128, R=116) {
let p = new CanvasKit.SkPath();
p.moveTo(X + R, Y);
@@ -169,32 +218,36 @@
return p;
}
- function SkottieExample(CanvasKit, id, jsonStr) {
+ function SkottieExample(CanvasKit, id, jsonStr, bounds) {
if (!CanvasKit || !jsonStr) {
return;
}
+ const animation = CanvasKit.MakeAnimation(jsonStr);
+ const duration = animation.duration() * 1000;
+ const size = animation.size();
+ let c = document.getElementById(id);
+ bounds = bounds || {fLeft: 0, fTop: 0, fRight: size.w, fBottom: size.h};
+
const surface = CanvasKit.getWebGLSurface(id);
if (!surface) {
console.log('Could not make surface');
}
const context = CanvasKit.currentContext();
const canvas = surface.getCanvas();
- const animation = CanvasKit.MakeAnimation(jsonStr);
- let i = 0;
+ let firstFrame = new Date().getTime();
+
function drawFrame() {
+ let now = new Date().getTime();
+ let seek = ((now - firstFrame) / duration) % 1.0;
CanvasKit.setCurrentContext(context);
- animation.seek(i);
- i += 1/300.0;
- if (i > 1.0) {
- i = 0;
- }
- animation.render(canvas);
+ animation.seek(seek);
+
+ animation.render(canvas, bounds);
canvas.flush();
window.requestAnimationFrame(drawFrame);
}
window.requestAnimationFrame(drawFrame);
-
//animation.delete();
}
}
diff --git a/src/effects/SkDashPathEffect.cpp b/src/effects/SkDashPathEffect.cpp
index cc414f7..8c4abdd 100644
--- a/src/effects/SkDashPathEffect.cpp
+++ b/src/effects/SkDashPathEffect.cpp
@@ -375,7 +375,7 @@
sk_sp<SkFlattenable> SkDashImpl::CreateProc(SkReadBuffer& buffer) {
#ifdef WEB_ASSEMBLY
- // Should not be reachable by WebAssembly Code.
+ // Should not be reachable by PathKit WebAssembly Code.
SkASSERT(false);
return nullptr;
#else
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 2fa332f..5bc07e4 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1407,7 +1407,12 @@
// We require some form of FBO support and all GLs with FBO support can render to RGBA8
fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
} else {
- if (version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
+ bool isWebGL = false;
+ // hack for skbug:8378
+ #if IS_WEBGL==1
+ isWebGL = true;
+ #endif
+ if (isWebGL || version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
ctxInfo.hasExtension("GL_ARM_rgba8")) {
fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
}
diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp
index df46c20..5ced583 100644
--- a/src/pathops/SkPathOpsTypes.cpp
+++ b/src/pathops/SkPathOpsTypes.cpp
@@ -103,6 +103,7 @@
return aBits < bBits + epsilon;
}
+#ifndef SKOTTIE_HACK
// equality using the same error term as between
bool AlmostBequalUlps(float a, float b) {
const int UlpsEpsilon = 2;
@@ -227,6 +228,7 @@
}
return result;
}
+#endif
SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head,
SkArenaAlloc* allocator