[PathKit] Add various path effects
API Changes (nothing should be breaking):
- Exposes conic, although all output formats (SVG, Canvas) need conics
to be approximated with quads. Tests have been added to verify this
happens.
- Add .dash(), .trim(), .stroke() and examples for them.
- Expose Stroke enums (StrokeJoin, StrokeCap)
Adds tests for the cubic part and clean up a few spacing things.
There are some changes to the C++ code to simplify the build -
otherwise, I need to appease the linker and add add in a bunch
of files that may or may not get optimized out. Best make them
not even be compiled, just to make sure.
Bug: skia:8216
Change-Id: I1da3aaab1891f14a5b3dc01bb6523b4fd9a87b04
Reviewed-on: https://skia-review.googlesource.com/146650
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/experimental/pathkit/pathkit_wasm_bindings.cpp b/experimental/pathkit/pathkit_wasm_bindings.cpp
index 5587cad..5909e39 100644
--- a/experimental/pathkit/pathkit_wasm_bindings.cpp
+++ b/experimental/pathkit/pathkit_wasm_bindings.cpp
@@ -5,14 +5,17 @@
* found in the LICENSE file.
*/
+#include "SkDashPathEffect.h"
#include "SkFloatBits.h"
#include "SkFloatingPoint.h"
#include "SkMatrix.h"
+#include "SkPaint.h"
#include "SkParsePath.h"
#include "SkPath.h"
#include "SkPathOps.h"
#include "SkRect.h"
#include "SkString.h"
+#include "SkTrimPathEffect.h"
#include <emscripten/emscripten.h>
#include <emscripten/bind.h>
@@ -22,13 +25,16 @@
static const int MOVE = 0;
static const int LINE = 1;
static const int QUAD = 2;
+static const int CONIC = 3;
static const int CUBIC = 4;
static const int CLOSE = 5;
// Just for self-documenting purposes where the main thing being returned is an
-// SkPath, but in an error case, something of type val (e.g. null) could also be
+// SkPath, but in an error case, something of type null (which is val) could also be
// returned;
-using SkPathOrVal = emscripten::val;
+using SkPathOrNull = emscripten::val;
+// Self-documenting for when we return a string
+using JSString = emscripten::val;
// =================================================================================
// Creating/Exporting Paths with cmd arrays
@@ -60,16 +66,9 @@
cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
break;
case SkPath::kConic_Verb:
- SkPoint quads[5];
- // approximate with 2^1=2 quads.
- SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
- cmd.call<void>("push", MOVE, quads[0].x(), quads[0].y());
- cmds.call<void>("push", cmd);
- cmd = emscripten::val::array();
- cmd.call<void>("push", QUAD, quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
- cmds.call<void>("push", cmd);
- cmd = emscripten::val::array();
- cmd.call<void>("push", QUAD, quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
+ cmd.call<void>("push", CONIC,
+ pts[1].x(), pts[1].y(),
+ pts[2].x(), pts[2].y(), iter.conicWeight());
break;
case SkPath::kCubic_Verb:
cmd.call<void>("push", CUBIC,
@@ -99,7 +98,7 @@
// 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.
-SkPathOrVal EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE FromCmds(uintptr_t /* float* */ cptr, int numCmds) {
const auto* cmds = reinterpret_cast<const float*>(cptr);
SkPath path;
float x1, y1, x2, y2, x3, y3;
@@ -158,7 +157,7 @@
// SVG things
//========================================================================================
-emscripten::val EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
+JSString EMSCRIPTEN_KEEPALIVE ToSVGString(const SkPath& path) {
SkString s;
SkParsePath::ToSVGString(path, &s);
// Wrapping it in val automatically turns it into a JS string.
@@ -169,7 +168,7 @@
}
-SkPathOrVal EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE FromSVGString(std::string str) {
SkPath path;
if (SkParsePath::FromSVGString(str.c_str(), &path)) {
return emscripten::val(path);
@@ -181,7 +180,7 @@
// PATHOP things
//========================================================================================
-SkPathOrVal EMSCRIPTEN_KEEPALIVE SimplifyPath(const SkPath& path) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE SimplifyPath(const SkPath& path) {
SkPath simple;
if (Simplify(path, &simple)) {
return emscripten::val(simple);
@@ -189,7 +188,7 @@
return emscripten::val::null();
}
-SkPathOrVal EMSCRIPTEN_KEEPALIVE ApplyPathOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE ApplyPathOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
SkPath path;
if (Op(pathOne, pathTwo, op, &path)) {
return emscripten::val(path);
@@ -197,7 +196,7 @@
return emscripten::val::null();
}
-SkPathOrVal EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
+SkPathOrNull EMSCRIPTEN_KEEPALIVE ResolveBuilder(SkOpBuilder& builder) {
SkPath path;
if (builder.resolve(&path)) {
return emscripten::val(path);
@@ -228,7 +227,6 @@
SkPoint quads[5];
// approximate with 2^1=2 quads.
SkPath::ConvertConicToQuads(pts[0], pts[1], pts[2], iter.conicWeight(), quads, 1);
- ctx.call<void>("moveTo", quads[0].x(), quads[0].y());
ctx.call<void>("quadraticCurveTo", quads[1].x(), quads[1].y(), quads[2].x(), quads[2].y());
ctx.call<void>("quadraticCurveTo", quads[3].x(), quads[3].y(), quads[4].x(), quads[4].y());
break;
@@ -326,6 +324,70 @@
orig.addPath(newPath, m);
}
+JSString GetCanvasFillType(const SkPath& path) {
+ if (path.getFillType() == SkPath::FillType::kWinding_FillType) {
+ return emscripten::val("nonzero");
+ } else if (path.getFillType() == SkPath::FillType::kEvenOdd_FillType) {
+ return emscripten::val("evenodd");
+ } else {
+ SkDebugf("warning: can't translate inverted filltype to HTML Canvas\n");
+ return emscripten::val("nonzero"); //Use default
+ }
+}
+
+//========================================================================================
+// Path Effects
+//========================================================================================
+
+SkPathOrNull PathEffectDash(const SkPath& path, SkScalar on, SkScalar off, SkScalar phase) {
+ SkPath output;
+ SkScalar intervals[] = { on, off };
+ auto pe = SkDashPathEffect::Make(intervals, 2, phase);
+ if (!pe) {
+ SkDebugf("Invalid args to dash()\n");
+ return emscripten::val::null();
+ }
+ if (pe->filterPath(&output, path, nullptr, nullptr)) {
+ return emscripten::val(output);
+ }
+ SkDebugf("Could not make dashed path\n");
+ return emscripten::val::null();
+}
+
+SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement) {
+ SkPath output;
+ 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 emscripten::val::null();
+ }
+ if (pe->filterPath(&output, path, nullptr, nullptr)) {
+ return emscripten::val(output);
+ }
+ SkDebugf("Could not trim path\n");
+ return emscripten::val::null();
+}
+
+SkPathOrNull PathEffectTrim(const SkPath& path, SkScalar startT, SkScalar stopT) {
+ return PathEffectTrim(path, startT, stopT, false);
+}
+
+SkPathOrNull PathEffectStroke(const SkPath& path, SkScalar width, SkPaint::Join join, SkPaint::Cap cap) {
+ SkPath output;
+ SkPaint p;
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeCap(cap);
+ p.setStrokeJoin(join);
+ p.setStrokeWidth(width);
+
+ if (p.getFillPath(path, &output)) {
+ return emscripten::val(output);
+ }
+ SkDebugf("Could not stroke path\n");
+ return emscripten::val::null();
+}
+
//========================================================================================
// Testing things
//========================================================================================
@@ -384,6 +446,8 @@
.function("addPath",
select_overload<void(SkPath&, const SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&Path2DAddPath))
.function("close", &SkPath::close)
+ .function("conicTo",
+ select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::conicTo))
.function("cubicTo",
select_overload<void(SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&SkPath::cubicTo))
.function("quadTo",
@@ -392,9 +456,16 @@
// Extra features
.function("setFillType", &SkPath::setFillType)
.function("getFillType", &SkPath::getFillType)
+ .function("getCanvasFillType", &GetCanvasFillType)
.function("getBounds", &SkPath::getBounds)
.function("computeTightBounds", &SkPath::computeTightBounds)
+ // PathEffects
+ .function("dash", &PathEffectDash)
+ .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar)>(&PathEffectTrim))
+ .function("trim", select_overload<SkPathOrNull(const SkPath&, SkScalar, SkScalar, bool)>(&PathEffectTrim))
+ .function("stroke", &PathEffectStroke)
+
// PathOps
.function("simplify", &SimplifyPath)
.function("op", &ApplyPathOp)
@@ -407,6 +478,7 @@
#ifdef PATHKIT_TESTING
.function("dump", select_overload<void() const>(&SkPath::dump))
+ .function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
#endif
;
@@ -444,6 +516,7 @@
constant("MOVE_VERB", MOVE);
constant("LINE_VERB", LINE);
constant("QUAD_VERB", QUAD);
+ constant("CONIC_VERB", CONIC);
constant("CUBIC_VERB", CUBIC);
constant("CLOSE_VERB", CLOSE);
@@ -458,12 +531,20 @@
function("MakeLTRBRect", &SkRect::MakeLTRB);
- // coming soon - Stroke
+ // Stroke
+ enum_<SkPaint::Join>("StrokeJoin")
+ .value("MITER", SkPaint::Join::kMiter_Join)
+ .value("ROUND", SkPaint::Join::kRound_Join)
+ .value("BEVEL", SkPaint::Join::kBevel_Join);
+
+ enum_<SkPaint::Cap>("StrokeCap")
+ .value("BUTT", SkPaint::Cap::kButt_Cap)
+ .value("ROUND", SkPaint::Cap::kRound_Cap)
+ .value("SQUARE", SkPaint::Cap::kSquare_Cap);
+
// coming soon - Matrix
- // coming soon - Trim
-
// Test Utils
function("SkBits2FloatUnsigned", &SkBits2FloatUnsigned);
}