[PathKit] Write more complete docs and clean up API to be consistent
Breaking Changes (should be minor, as it's mostly just things
for testing):
- PathKit.ApplyPathOp should have returned a new SkPath, but didn't.
It now does and is named "MakeFromOp", which makes the convention of
"Have 'make' in name, needs delete" more consistent.
- PathKit.FromCmds(arr) now only needs to take the JS Array and
will handle the TypedArrays under the hood. If clients want to deal
with TypedArrays themselves, they can use _FromCmds(ptr, len) directly.
- PathKit.MakeLTRBRect is now just PathKit.LTRBRect. The thing
returned is a normal JS Object and doesn't need delete().
As per custom with v0 apps, we are updating the minor version
to v0.3.0 to account for breaking changes.
Docs-Preview: https://skia.org/?cl=147960
Bug: skia:8216
Change-Id: Ia3626e69f3e97698fc62a6aee876af005e29ffca
Reviewed-on: https://skia-review.googlesource.com/147960
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Heather Miller <hcm@google.com>
diff --git a/experimental/pathkit/Makefile b/experimental/pathkit/Makefile
index f0e7f45..0547877 100644
--- a/experimental/pathkit/Makefile
+++ b/experimental/pathkit/Makefile
@@ -13,7 +13,7 @@
publish:
cd npm-wasm; npm publish
- cd ../npm-asmjs; npm publish
+ cd npm-asmjs; npm publish
update-major:
cd npm-wasm; npm version major
@@ -22,12 +22,12 @@
update-minor:
cd npm-wasm; npm version minor
- cd ../npm-asmjs; npm version minor
+ cd npm-asmjs; npm version minor
echo "Don't forget to publish."
update-patch:
cd npm-wasm; npm version patch
- cd ../npm-asmjs; npm version patch
+ cd npm-asmjs; npm version patch
echo "Don't forget to publish."
# Build the library and run the tests. If developing locally, test-continuous is better
diff --git a/experimental/pathkit/chaining.js b/experimental/pathkit/chaining.js
index d4902c6..f901d38 100644
--- a/experimental/pathkit/chaining.js
+++ b/experimental/pathkit/chaining.js
@@ -115,13 +115,13 @@
return null;
};
- PathKit.SkPath.prototype.quadraticCurveTo = function(x1, y1, x2, y2) {
- this._quadTo(x1, y1, x2, y2);
+ PathKit.SkPath.prototype.quadraticCurveTo = function(cpx, cpy, x, y) {
+ this._quadTo(cpx, cpy, x, y);
return this;
};
- PathKit.SkPath.prototype.quadTo = function(x1, y1, x2, y2) {
- this._quadTo(x1, y1, x2, y2);
+ PathKit.SkPath.prototype.quadTo = function(cpx, cpy, x, y) {
+ this._quadTo(cpx, cpy, x, y);
return this;
};
diff --git a/experimental/pathkit/externs.js b/experimental/pathkit/externs.js
index b68a3a2..9116254 100644
--- a/experimental/pathkit/externs.js
+++ b/experimental/pathkit/externs.js
@@ -24,6 +24,9 @@
SkBits2FloatUnsigned: function(num) {},
_malloc: function(size) {},
onRuntimeInitialized: function() {},
+ _FromCmds: function(ptr, size) {},
+ loadCmdsTypedArray: function(arr) {},
+ FromCmds: function(arr) {},
HEAPF32: {},
@@ -40,7 +43,7 @@
_lineTo: function(x1, y1) {},
_moveTo: function(x1, y1) {},
_op: function(otherPath, op) {},
- _quadTo: function(x1, y1, x2, y2) {},
+ _quadTo: function(cpx, cpy, x, y) {},
_rect: function(x, y, w, h) {},
_simplify: function() {},
_stroke: function(opts) {},
diff --git a/experimental/pathkit/helper.js b/experimental/pathkit/helper.js
index 0d476ab..4f84782 100644
--- a/experimental/pathkit/helper.js
+++ b/experimental/pathkit/helper.js
@@ -7,9 +7,9 @@
var Float32ArrayCache = {};
// Takes a 2D array of commands and puts them into the WASM heap
- // as a 1D array. This allowing them to referenced from the C++ code.
+ // as a 1D array. This allows them to referenced from the C++ code.
// Returns a 2 element array, with the first item being essentially a
- // pointer to the array and the second item being the lengh of
+ // pointer to the array and the second item being the length of
// the new 1D array.
//
// Example usage:
@@ -26,7 +26,7 @@
//
// If arguments at index 1... in each cmd row are strings, they will be
// parsed as hex, and then converted to floats using SkBits2FloatUnsigned
- PathKit['loadCmdsTypedArray'] = function(arr) {
+ PathKit.loadCmdsTypedArray = function(arr) {
var len = 0;
for (var r = 0; r < arr.length; r++) {
len += arr[r].length;
@@ -57,5 +57,13 @@
PathKit.HEAPF32.set(ta, ptr / ta.BYTES_PER_ELEMENT);
return [ptr, len];
}
+
+ // Experimentation has shown that using TypedArrays to pass arrays from
+ // JS to C++ is faster than passing the JS Arrays across.
+ // See above for example of cmds.
+ PathKit.FromCmds = function(cmds) {
+ var ptrLen = PathKit.loadCmdsTypedArray(cmds);
+ return PathKit._FromCmds(ptrLen[0], ptrLen[1]);
+ }
}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/pathkit/npm-asmjs/example.html b/experimental/pathkit/npm-asmjs/example.html
index 3479b83..e5c85e7 100644
--- a/experimental/pathkit/npm-asmjs/example.html
+++ b/experimental/pathkit/npm-asmjs/example.html
@@ -37,6 +37,7 @@
<canvas class=big id=canvas8></canvas>
<canvas class=big id=canvas9></canvas>
<canvas class=big id=canvas10></canvas>
+<canvas class=big id=canvas11></canvas>
<canvas class=big id=canvasTransform></canvas>
<h2> Supports fill-rules of nonzero and evenodd </h2>
@@ -193,58 +194,74 @@
// no-op
(path) => path,
// dash
- (path) => path.dash(10, 3, 0),
+ (path, counter) => path.dash(10, 3, counter/5),
// trim (takes optional 3rd param for returning the trimmed part
// or the complement)
- (path) => path.trim(0.25, 0.8, false),
+ (path, counter) => path.trim((counter/100) % 1, 0.8, false),
// simplify
(path) => path.simplify(),
// stroke
- (path) => path.stroke({
- width: 15,
+ (path, counter) => path.stroke({
+ width: 10 * (Math.sin(counter/30) + 1),
join: PathKit.StrokeJoin.BEVEL,
cap: PathKit.StrokeCap.BUTT,
miter_limit: 1,
}),
// "offset effect", that is, making a border around the shape.
- (path) => {
+ (path, counter) => {
let orig = path.copy();
path.stroke({
- width: 10,
+ width: 10 + (counter / 4) % 50,
join: PathKit.StrokeJoin.ROUND,
cap: PathKit.StrokeCap.SQUARE,
})
.op(orig, PathKit.PathOp.DIFFERENCE);
orig.delete();
+ },
+ (path, counter) => {
+ let simplified = path.simplify().copy();
+ path.stroke({
+ width: 2 + (counter / 2) % 100,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ })
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ simplified.delete();
}
];
- let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Offset"];
+ let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
- for (let i = 0; i < effects.length; i++) {
- let path = PathKit.NewPath();
- drawStar(path);
+ let counter = 0;
+ function frame() {
+ counter++;
+ for (let i = 0; i < effects.length; i++) {
+ let path = PathKit.NewPath();
+ drawStar(path);
- // The transforms apply directly to the path.
- effects[i](path);
+ // The transforms apply directly to the path.
+ effects[i](path, counter);
- let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
- setCanvasSize(ctx, 300, 300);
- ctx.strokeStyle = '#3c597a';
- ctx.fillStyle = '#3c597a';
- if (i === 4 || i === 5) {
- ctx.fill(path.toPath2D(), path.getFillTypeString());
- } else {
- ctx.stroke(path.toPath2D());
+ let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+ ctx.fillStyle = '#3c597a';
+ if (i >=4 ) {
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+ } else {
+ ctx.stroke(path.toPath2D());
+ }
+
+ ctx.font = '42px monospace';
+
+ let x = 150-ctx.measureText(names[i]).width/2;
+ ctx.strokeText(names[i], x, 290);
+
+ path.delete();
}
-
- ctx.font = '42px monospace';
-
- let x = 150-ctx.measureText(names[i]).width/2;
- ctx.strokeText(names[i], x, 290);
-
- path.delete();
+ window.requestAnimationFrame(frame);
}
+ window.requestAnimationFrame(frame);
}
function MatrixTransformExample(PathKit) {
diff --git a/experimental/pathkit/npm-asmjs/package.json b/experimental/pathkit/npm-asmjs/package.json
index b938696..792d35a 100644
--- a/experimental/pathkit/npm-asmjs/package.json
+++ b/experimental/pathkit/npm-asmjs/package.json
@@ -1,6 +1,6 @@
{
"name": "experimental-pathkit-asmjs",
- "version": "0.2.0",
+ "version": "0.3.0",
"description": "A asm.js version of Skia's PathOps toolkit",
"main": "bin/pathkit.js",
"homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
diff --git a/experimental/pathkit/npm-wasm/example.html b/experimental/pathkit/npm-wasm/example.html
index 60d6981..a6c8cca 100644
--- a/experimental/pathkit/npm-wasm/example.html
+++ b/experimental/pathkit/npm-wasm/example.html
@@ -37,6 +37,7 @@
<canvas class=big id=canvas8></canvas>
<canvas class=big id=canvas9></canvas>
<canvas class=big id=canvas10></canvas>
+<canvas class=big id=canvas11></canvas>
<canvas class=big id=canvasTransform></canvas>
<h2> Supports fill-rules of nonzero and evenodd </h2>
@@ -193,58 +194,74 @@
// no-op
(path) => path,
// dash
- (path) => path.dash(10, 3, 0),
+ (path, counter) => path.dash(10, 3, counter/5),
// trim (takes optional 3rd param for returning the trimmed part
// or the complement)
- (path) => path.trim(0.25, 0.8, false),
+ (path, counter) => path.trim((counter/100) % 1, 0.8, false),
// simplify
(path) => path.simplify(),
// stroke
- (path) => path.stroke({
- width: 15,
+ (path, counter) => path.stroke({
+ width: 10 * (Math.sin(counter/30) + 1),
join: PathKit.StrokeJoin.BEVEL,
cap: PathKit.StrokeCap.BUTT,
miter_limit: 1,
}),
// "offset effect", that is, making a border around the shape.
- (path) => {
+ (path, counter) => {
let orig = path.copy();
path.stroke({
- width: 10,
+ width: 10 + (counter / 4) % 50,
join: PathKit.StrokeJoin.ROUND,
cap: PathKit.StrokeCap.SQUARE,
})
.op(orig, PathKit.PathOp.DIFFERENCE);
orig.delete();
+ },
+ (path, counter) => {
+ let simplified = path.simplify().copy();
+ path.stroke({
+ width: 2 + (counter / 2) % 100,
+ join: PathKit.StrokeJoin.BEVEL,
+ cap: PathKit.StrokeCap.BUTT,
+ })
+ .op(simplified, PathKit.PathOp.REVERSE_DIFFERENCE);
+ simplified.delete();
}
];
- let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Offset"];
+ let names = ["(plain)", "Dash", "Trim", "Simplify", "Stroke", "Grow", "Shrink"];
- for (let i = 0; i < effects.length; i++) {
- let path = PathKit.NewPath();
- drawStar(path);
+ let counter = 0;
+ function frame() {
+ counter++;
+ for (let i = 0; i < effects.length; i++) {
+ let path = PathKit.NewPath();
+ drawStar(path);
- // The transforms apply directly to the path.
- effects[i](path);
+ // The transforms apply directly to the path.
+ effects[i](path, counter);
- let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
- setCanvasSize(ctx, 300, 300);
- ctx.strokeStyle = '#3c597a';
- ctx.fillStyle = '#3c597a';
- if (i === 4 || i === 5) {
- ctx.fill(path.toPath2D(), path.getFillTypeString());
- } else {
- ctx.stroke(path.toPath2D());
+ let ctx = document.getElementById(`canvas${i+5}`).getContext('2d');
+ setCanvasSize(ctx, 300, 300);
+ ctx.strokeStyle = '#3c597a';
+ ctx.fillStyle = '#3c597a';
+ if (i >=4 ) {
+ ctx.fill(path.toPath2D(), path.getFillTypeString());
+ } else {
+ ctx.stroke(path.toPath2D());
+ }
+
+ ctx.font = '42px monospace';
+
+ let x = 150-ctx.measureText(names[i]).width/2;
+ ctx.strokeText(names[i], x, 290);
+
+ path.delete();
}
-
- ctx.font = '42px monospace';
-
- let x = 150-ctx.measureText(names[i]).width/2;
- ctx.strokeText(names[i], x, 290);
-
- path.delete();
+ window.requestAnimationFrame(frame);
}
+ window.requestAnimationFrame(frame);
}
function MatrixTransformExample(PathKit) {
diff --git a/experimental/pathkit/npm-wasm/package.json b/experimental/pathkit/npm-wasm/package.json
index c951625..f83ab63 100644
--- a/experimental/pathkit/npm-wasm/package.json
+++ b/experimental/pathkit/npm-wasm/package.json
@@ -1,6 +1,6 @@
{
"name": "experimental-pathkit-wasm",
- "version": "0.2.0",
+ "version": "0.3.0",
"description": "A WASM version of Skia's PathOps toolkit",
"main": "bin/pathkit.js",
"homepage": "https://github.com/google/skia/tree/master/experimental/pathkit",
diff --git a/experimental/pathkit/pathkit_wasm_bindings.cpp b/experimental/pathkit/pathkit_wasm_bindings.cpp
index c680db1..40772d8 100644
--- a/experimental/pathkit/pathkit_wasm_bindings.cpp
+++ b/experimental/pathkit/pathkit_wasm_bindings.cpp
@@ -134,7 +134,7 @@
CHECK_NUM_ARGS(5);
x1 = cmds[i++], y1 = cmds[i++];
x2 = cmds[i++], y2 = cmds[i++];
- x3 = cmds[i++]; // width
+ x3 = cmds[i++]; // weight
path.conicTo(x1, y1, x2, y2, x3);
break;
case CUBIC:
@@ -249,6 +249,14 @@
return Op(pathOne, pathTwo, op, &pathOne);
}
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakeFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+ SkPath out;
+ if (Op(pathOne, pathTwo, op, &out)) {
+ return emscripten::val(out);
+ }
+ return emscripten::val::null();
+}
+
SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
SkPath path;
if (builder.resolve(&path)) {
@@ -481,9 +489,9 @@
.function("_arcTo", &ApplyArcTo)
//"bezierCurveTo" alias handled in JS bindings
.function("_close", &ApplyClose)
+ //"closePath" alias handled in JS bindings
.function("_conicTo", &ApplyConicTo)
.function("_cubicTo", &ApplyCubicTo)
- //"closePath" alias handled in JS bindings
.function("_ellipse", &ApplyEllipse)
.function("_lineTo", &ApplyLineTo)
@@ -530,6 +538,7 @@
.constructor<>()
.function("add", &SkOpBuilder::add)
+ .function("make", &ResolveBuilder)
.function("resolve", &ResolveBuilder);
// Without these function() bindings, the function would be exposed but oblivious to
@@ -537,13 +546,14 @@
// Import
function("FromSVGString", &FromSVGString);
- function("FromCmds", &FromCmds);
function("NewPath", &NewPath);
function("NewPath", &CopyPath);
+ // FromCmds is defined in helper.js to make use of TypedArrays transparent.
+ function("_FromCmds", &FromCmds);
// Path2D is opaque, so we can't read in from it.
// PathOps
- function("ApplyPathOp", &ApplyPathOp);
+ function("MakeFromOp", &MakeFromOp);
enum_<SkPathOp>("PathOp")
.value("DIFFERENCE", SkPathOp::kDifference_SkPathOp)
@@ -574,7 +584,7 @@
.field("fRight", &SkRect::fRight)
.field("fBottom", &SkRect::fBottom);
- function("MakeLTRBRect", &SkRect::MakeLTRB);
+ function("LTRBRect", &SkRect::MakeLTRB);
// Stroke
enum_<SkPaint::Join>("StrokeJoin")
diff --git a/experimental/pathkit/tests/path.spec.js b/experimental/pathkit/tests/path.spec.js
index e77be63..852435b 100644
--- a/experimental/pathkit/tests/path.spec.js
+++ b/experimental/pathkit/tests/path.spec.js
@@ -112,13 +112,13 @@
LoadPathKit.then(() => {
// Based on test_bounds_crbug_513799
let path = PathKit.NewPath();
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(0, 0, 0, 0));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(0, 0, 0, 0));
path.moveTo(-5, -8);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, -5, -8));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, -5, -8));
path.rect(1, 2, 2, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, 3, 4));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, 3, 4));
path.moveTo(1, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(-5, -8, 3, 4));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(-5, -8, 3, 4));
path.delete();
done();
});
@@ -130,9 +130,9 @@
let path = PathKit.NewPath();
path.moveTo(1, 1);
path.quadraticCurveTo(4, 3, 2, 2);
- expect(path.getBounds()).toEqual(PathKit.MakeLTRBRect(1, 1, 4, 3));
+ expect(path.getBounds()).toEqual(PathKit.LTRBRect(1, 1, 4, 3));
ExpectRectsToBeEqual(path.computeTightBounds(),
- PathKit.MakeLTRBRect(1, 1,
+ PathKit.LTRBRect(1, 1,
bits2float("0x40333334"), // 2.8
bits2float("0x40155556"))); // 2.3333333
path.delete();
diff --git a/experimental/pathkit/tests/pathops.spec.js b/experimental/pathkit/tests/pathops.spec.js
index 39d879c..273a6a4 100644
--- a/experimental/pathkit/tests/pathops.spec.js
+++ b/experimental/pathkit/tests/pathops.spec.js
@@ -117,11 +117,6 @@
return e;
}
- function fromCmds(cmds) {
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- return PathKit.FromCmds(ptr, len);
- }
-
it('combines two paths with .op() and matches what we see from C++', function(done) {
LoadPathKit.then(() => {
// Test JSON created with:
@@ -135,11 +130,11 @@
for (testName of testNames) {
let test = json[testName];
- let path1 = fromCmds(test.p1);
+ let path1 = PathKit.FromCmds(test.p1);
expect(path1).not.toBeNull(`path1 error when loading cmds '${test.p1}'`);
path1.setFillType(getFillType(test.fillType1));
- let path2 = fromCmds(test.p2);
+ let path2 = PathKit.FromCmds(test.p2);
expect(path2).not.toBeNull(`path2 error when loading cmds '${test.p2}'`);
path2.setFillType(getFillType(test.fillType2));
@@ -149,7 +144,7 @@
expect(combined).toBeNull(`Test ${testName} should have not created output, but did`);
} else {
expect(combined).not.toBeNull();
- let expected = fromCmds(test.out);
+ let expected = PathKit.FromCmds(test.out);
// Do a tolerant match.
let diff = diffPaths(expected, combined);
if (test.expectMatch === 'yes'){
@@ -192,7 +187,7 @@
for (testName of testNames) {
let test = json[testName];
- let path = fromCmds(test.path);
+ let path = PathKit.FromCmds(test.path);
expect(path).not.toBeNull(`path1 error when loading cmds '${test.path}'`);
path.setFillType(getFillType(test.fillType));
@@ -202,7 +197,7 @@
expect(simplified).toBeNull(`Test ${testName} should have not created output, but did`);
} else {
expect(simplified).not.toBeNull();
- let expected = fromCmds(test.out);
+ let expected = PathKit.FromCmds(test.out);
// Do a tolerant match.
let diff = diffPaths(expected, simplified);
if (test.expectMatch === 'yes'){
diff --git a/experimental/pathkit/tests/svg.spec.js b/experimental/pathkit/tests/svg.spec.js
index c28618b..ca3afe2 100644
--- a/experimental/pathkit/tests/svg.spec.js
+++ b/experimental/pathkit/tests/svg.spec.js
@@ -45,8 +45,7 @@
[PathKit.LINE_VERB, 5, 295],
[PathKit.LINE_VERB, 205, 5],
[PathKit.CLOSE_VERB]];
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- let path = PathKit.FromCmds(ptr, len);
+ let path = PathKit.FromCmds(cmds);
let svgStr = path.toSVGString();
// We output it in terse form, which is different than Wikipedia's version
@@ -64,8 +63,7 @@
[PathKit.LINE_VERB, 5, 295],
[PathKit.LINE_VERB, "0x15e80300", "0x400004dc"], // 9.37088e-26f, 2.0003f
[PathKit.CLOSE_VERB]];
- let [ptr, len] = PathKit.loadCmdsTypedArray(cmds);
- let path = PathKit.FromCmds(ptr, len);
+ let path = PathKit.FromCmds(cmds);
let svgStr = path.toSVGString();
expect(svgStr).toEqual('M9.37088e-26 2.0003L795 5L595 295L5 295L9.37088e-26 2.0003Z');