diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index 8b22bf4178..84bde8a3e2 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -17,6 +17,14 @@
+
Drop in replacement for HTML Canvas (e.g. node.js)
+
+
+
+
+
+
+
CanvasKit draws Paths to the browser
@@ -38,11 +46,7 @@
-Drop in replacement for HTML Canvas (e.g. node.js)
-
-
-
-
+
@@ -86,6 +90,7 @@
CanvasAPI1(CanvasKit);
CanvasAPI2(CanvasKit);
+ CanvasAPI3(CanvasKit);
VertexAPI1(CanvasKit);
VertexAPI2(CanvasKit, bonesImage);
@@ -453,13 +458,14 @@
}
function CanvasAPI2(CanvasKit) {
- let skcanvas = CanvasKit.MakeCanvas(200, 200);
+ let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api2_c');
- realCanvas.width = 200;
- realCanvas.height = 200;
+ realCanvas.width = 300;
+ realCanvas.height = 300;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
+ ctx.scale(1.5, 1.5);
ctx.moveTo(20, 5);
ctx.lineTo(30, 20);
ctx.lineTo(40, 10);
@@ -488,12 +494,68 @@
ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
ctx.lineWidth = 4/3;
-
ctx.stroke();
}
document.getElementById('api2').src = skcanvas.toDataURL();
}
+ function CanvasAPI3(CanvasKit) {
+ let skcanvas = CanvasKit.MakeCanvas(300, 300);
+ let realCanvas = document.getElementById('api3_c');
+ realCanvas.width = 300;
+ realCanvas.height = 300;
+
+ for (let canvas of [skcanvas, realCanvas]) {
+ let ctx = canvas.getContext('2d');
+ ctx.rect(10, 10, 20, 20);
+
+ ctx.scale(2.0, 4.0);
+ ctx.rect(30, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.rotate(Math.PI / 3);
+ ctx.rect(50, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.translate(30, -2);
+ ctx.rect(70, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.translate(60, 0);
+ ctx.rotate(Math.PI / 6);
+ ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
+ ctx.rect(90, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+ ctx.rect(110, 10, 20, 20);
+ ctx.lineTo(110, 0);
+ ctx.resetTransform();
+ ctx.lineTo(220, 120);
+
+ ctx.scale(3.0, 3.0);
+ ctx.font = '6pt Arial';
+ ctx.fillText('This text should be huge', 10, 80);
+ ctx.resetTransform();
+
+ ctx.strokeStyle = 'black';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(250, 30);
+ ctx.lineTo(250, 80);
+ ctx.scale(3.0, 3.0);
+ ctx.lineTo(280/3, 90/3);
+ ctx.closePath();
+ ctx.strokeStyle = 'black';
+ ctx.lineWidth = 5;
+ ctx.stroke();
+
+ }
+ document.getElementById('api3').src = skcanvas.toDataURL();
+ }
+
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
if (!CanvasKit || !nimaFile || !nimaTexture) {
return;
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 5ddefeb809..04432f8051 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -281,7 +281,7 @@ bool ApplyTrim(SkPath& path, SkScalar startT, SkScalar stopT, bool isComplement)
}
struct StrokeOpts {
- // Default values are set in chaining.js which allows clients
+ // Default values are set in interface.js which allows clients
// to set any number of them. Otherwise, the binding code complains if
// any are omitted.
SkScalar width;
@@ -456,7 +456,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("rotate", select_overload(&SkCanvas::rotate))
.function("save", &SkCanvas::save)
.function("scale", &SkCanvas::scale)
- .function("setMatrix", &SkCanvas::setMatrix)
+ .function("setMatrix", optional_override([](SkCanvas& self, const SimpleMatrix& m) {
+ self.setMatrix(toSkMatrix(m));
+ }))
.function("skew", &SkCanvas::skew)
.function("translate", &SkCanvas::translate);
@@ -475,6 +477,11 @@ EMSCRIPTEN_BINDINGS(Skia) {
SkPaint p(self);
return p;
}))
+ .function("getStrokeWidth", &SkPaint::getStrokeWidth)
+ .function("getStrokeMiter", &SkPaint::getStrokeMiter)
+ .function("getStrokeCap", &SkPaint::getStrokeCap)
+ .function("getStrokeJoin", &SkPaint::getStrokeJoin)
+ .function("getTextSize", &SkPaint::getTextSize)
.function("measureText", optional_override([](SkPaint& self, std::string text) {
// TODO(kjlubick): This does not work well for non-ascii
// Need to maybe add a helper in interface.js that supports UTF-8
@@ -490,6 +497,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("setPathEffect", &SkPaint::setPathEffect)
.function("setShader", &SkPaint::setShader)
.function("setStrokeWidth", &SkPaint::setStrokeWidth)
+ .function("setStrokeMiter", &SkPaint::setStrokeMiter)
+ .function("setStrokeCap", &SkPaint::setStrokeCap)
+ .function("setStrokeJoin", &SkPaint::setStrokeJoin)
.function("setStyle", &SkPaint::setStyle)
.function("setTextSize", &SkPaint::setTextSize);
@@ -507,7 +517,9 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("_arcTo", &ApplyArcTo)
.function("_close", &ApplyClose)
.function("_conicTo", &ApplyConicTo)
+ .function("countPoints", &SkPath::countPoints)
.function("_cubicTo", &ApplyCubicTo)
+ .function("getPoint", &SkPath::getPoint)
.function("_lineTo", &ApplyLineTo)
.function("_moveTo", &ApplyMoveTo)
.function("_quadTo", &ApplyQuadTo)
@@ -530,7 +542,12 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("getBounds", &SkPath::getBounds)
.function("computeTightBounds", &SkPath::computeTightBounds)
.function("equals", &Equals)
- .function("copy", &CopyPath);
+ .function("copy", &CopyPath)
+#ifdef SK_DEBUG
+ .function("dump", select_overload(&SkPath::dump))
+ .function("dumpHex", select_overload(&SkPath::dumpHex))
+#endif
+ ;
class_("SkShader")
.smart_ptr>("sk_sp");
@@ -617,6 +634,22 @@ EMSCRIPTEN_BINDINGS(Skia) {
.value("XOR", SkPathOp::kXOR_SkPathOp)
.value("ReverseDifference", SkPathOp::kReverseDifference_SkPathOp);
+ enum_("StrokeCap")
+ .value("Butt", SkPaint::Cap::kButt_Cap)
+ .value("Round", SkPaint::Cap::kRound_Cap)
+ .value("Square", SkPaint::Cap::kSquare_Cap);
+
+ enum_("StrokeJoin")
+ .value("Miter", SkPaint::Join::kMiter_Join)
+ .value("Round", SkPaint::Join::kRound_Join)
+ .value("Bevel", SkPaint::Join::kBevel_Join);
+
+ value_object("StrokeOpts")
+ .field("width", &StrokeOpts::width)
+ .field("miter_limit", &StrokeOpts::miter_limit)
+ .field("join", &StrokeOpts::join)
+ .field("cap", &StrokeOpts::cap);
+
enum_("TileMode")
.value("Clamp", SkShader::TileMode::kClamp_TileMode)
.value("Repeat", SkShader::TileMode::kRepeat_TileMode)
@@ -625,10 +658,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
enum_("VertexMode")
.value("Triangles", SkVertices::VertexMode::kTriangles_VertexMode)
.value("TrianglesStrip", SkVertices::VertexMode::kTriangleStrip_VertexMode)
- .value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);
+ .value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);
enum_("ImageFormat")
- .value("PNG", SkEncodedImageFormat::kPNG)
+ .value("PNG", SkEncodedImageFormat::kPNG)
.value("JPEG", SkEncodedImageFormat::kJPEG);
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index 2c60c37dc3..c2758e4a62 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -97,11 +97,52 @@ var CanvasKit = {
},
SkMatrix: {
+ identity: function() {},
+ mapPoints: function() {},
+ multiply: function() {},
rotated: function() {},
+ scaled: function() {},
+ skewed: function() {},
+ translated: function() {},
+ },
+
+ SkPaint: {
+ // public API (from C++ bindings)
+ /** @return {CanvasKit.SkPaint} */
+ copy: function() {},
+ getStrokeCap: function() {},
+ getStrokeJoin: function() {},
+ getStrokeMiter: function() {},
+ getStrokeWidth: function() {},
+ getTextSize: function() {},
+ measureText: function() {},
+ setAntiAlias: function() {},
+ setColor: function() {},
+ setPathEffect: function() {},
+ setShader: function() {},
+ setStrokeCap: function() {},
+ setStrokeJoin: function() {},
+ setStrokeMiter: function() {},
+ setStrokeWidth: function() {},
+ setStyle: function() {},
+ setTextSize: function() {},
+
+ //private API
+ delete: function() {},
},
SkPath: {
// public API (from C++ bindings)
+ computeTightBounds: function() {},
+ /** @return {CanvasKit.SkPath} */
+ copy: function() {},
+ countPoints: function() {},
+ equals: function() {},
+ getBounds: function() {},
+ getFillType: function() {},
+ getPoint: function() {},
+ setFillType: function() {},
+ toSVGString: function() {},
// private API
_addArc: function() {},
@@ -123,23 +164,8 @@ var CanvasKit = {
_transform: function() {},
_trim: function() {},
delete: function() {},
- },
-
- SkPaint: {
- // public API (from C++ bindings)
- /** @return {CanvasKit.SkPaint} */
- copy: function() {},
- measureText: function() {},
- setAntiAlias: function() {},
- setColor: function() {},
- setPathEffect: function() {},
- setShader: function() {},
- setStrokeWidth: function() {},
- setStyle: function() {},
- setTextSize: function() {},
-
- //private API
- delete: function() {},
+ dump: function() {},
+ dumpHex: function() {},
},
SkRect: {
@@ -176,11 +202,6 @@ var CanvasKit = {
// Constants and Enums
gpu: {},
skottie: {},
- PaintStyle: {
- Fill: {},
- Stroke: {},
- StrokeAndFill: {},
- },
FillType: {
Winding: {},
@@ -194,6 +215,24 @@ var CanvasKit = {
JPEG: {},
},
+ PaintStyle: {
+ Fill: {},
+ Stroke: {},
+ StrokeAndFill: {},
+ },
+
+ StrokeCap: {
+ Butt: {},
+ Round: {},
+ Square: {},
+ },
+
+ StrokeJoin: {
+ Miter: {},
+ Round: {},
+ Bevel: {},
+ },
+
// Things Enscriptem adds for us
/** Represents the heap of the WASM code
@@ -258,5 +297,38 @@ StrokeOpts.prototype.miter_limit;
StrokeOpts.prototype.cap;
StrokeOpts.prototype.join;
+var HTMLCanvas = {};
+HTMLCanvas.prototype.getContext = function() {};
+HTMLCanvas.prototype.toDataURL = function() {};
+HTMLCanvas.prototype.dispose = function() {};
+
+var CanvasRenderingContext2D = {};
+CanvasRenderingContext2D.prototype.addHitRegion = function() {};
+CanvasRenderingContext2D.prototype.arc = function() {};
+CanvasRenderingContext2D.prototype.arcTo = function() {};
+CanvasRenderingContext2D.prototype.beginPath = function() {};
+CanvasRenderingContext2D.prototype.bezierCurveTo = function() {};
+CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
+CanvasRenderingContext2D.prototype.closePath = function() {};
+CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
+CanvasRenderingContext2D.prototype.ellipse = function() {};
+CanvasRenderingContext2D.prototype.fillText = function() {};
+CanvasRenderingContext2D.prototype.lineTo = function() {};
+CanvasRenderingContext2D.prototype.measureText = function() {};
+CanvasRenderingContext2D.prototype.moveTo = function() {};
+CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
+CanvasRenderingContext2D.prototype.rect = function() {};
+CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
+CanvasRenderingContext2D.prototype.resetTransform = function() {};
+CanvasRenderingContext2D.prototype.rotate = function() {};
+CanvasRenderingContext2D.prototype.scale = function() {};
+CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
+CanvasRenderingContext2D.prototype.setTransform = function() {};
+CanvasRenderingContext2D.prototype.stroke = function() {};
+CanvasRenderingContext2D.prototype.strokeText = function() {};
+CanvasRenderingContext2D.prototype.transform = function() {};
+CanvasRenderingContext2D.prototype.translate = 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/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js
index 7b7e1a5f7c..0325b34e37 100644
--- a/experimental/canvaskit/htmlcanvas/canvas2d.js
+++ b/experimental/canvaskit/htmlcanvas/canvas2d.js
@@ -40,9 +40,9 @@
return null;
}
- this.toDataURL = function(codec) {
- // TODO(kjlubick): maybe support other codecs?
- // For now, just to png
+ this.toDataURL = function(codec, quality) {
+ // TODO(kjlubick): maybe support other codecs (webp?)
+ // For now, just to png and jpeg
this._surface.flush();
var img = this._surface.makeImageSnapshot();
@@ -50,14 +50,19 @@
SkDebug('no snapshot');
return;
}
- var png = img.encodeToData();
- if (!png) {
+ var codec = codec || 'image/png';
+ var format = CanvasKit.ImageFormat.PNG;
+ if (codec === 'image/jpeg') {
+ format = CanvasKit.ImageFormat.JPEG;
+ }
+ var quality = quality || 0.92;
+ var skimg = img.encodeToData(format, quality);
+ if (!skimg) {
SkDebug('encoding failure');
return
}
- // TODO(kjlubick): clean this up a bit - maybe better naming?
- var pngBytes = CanvasKit.getSkDataBytes(png);
- return 'data:image/png;base64,' + toBase64String(pngBytes);
+ var imgBytes = CanvasKit.getSkDataBytes(skimg);
+ return 'data:' + codec + ';base64,' + toBase64String(imgBytes);
}
this.dispose = function() {
@@ -70,15 +75,18 @@
this._canvas = skcanvas;
this._paint = new CanvasKit.SkPaint();
this._paint.setAntiAlias(true);
- this._paint.setStrokeWidth(2);
- this._currentSubPath = null;
- this._paths = [];
- this._pathStarted = false;
+ this._paint.setStrokeWidth(1);
+ this._paint.setStrokeMiter(10);
+ this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
+ this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
+
+ this._currentPath = new CanvasKit.SkPath();
+ this._currentSubpath = null;
+ this._currentTransform = CanvasKit.SkMatrix.identity();
this._dispose = function() {
- this._paths.forEach(function(path) {
- path.delete();
- });
+ this._currentPath.delete();
+ this._currentSubpath && this._currentSubpath.delete();
this._paint.delete();
// Don't delete this._canvas as it will be disposed
// by the surface of which it is based.
@@ -121,32 +129,61 @@
if (!argsAreFinite(arguments)) {
return;
}
- this._ensureSubpath(x1, y1);
if (radius < 0) {
throw 'radii cannot be negative';
}
- this._currentSubPath.arcTo(x1, y1, x2, y2, radius);
+ var pts = [x1, y1, x2, y2];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
+ x1 = pts[0];
+ y1 = pts[1];
+ x2 = pts[2];
+ y2 = pts[3];
+ if (!this._currentSubpath) {
+ this._newSubpath(x1, y1);
+ }
+ this._currentSubpath.arcTo(x1, y1, x2, y2, radius * this._scalefactor());
}
+ // As per the spec this doesn't begin any paths, it only
+ // clears out any previous subpaths.
this.beginPath = function() {
- this._currentSubPath = new CanvasKit.SkPath();
- this._paths.push(this._currentSubPath);
- this._pathStarted = false;
+ this._currentPath.delete();
+ this._currentPath = new CanvasKit.SkPath();
+ this._currentSubpath && this._currentSubpath.delete();
+ this._currentSubpath = null;
}
this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
if (!argsAreFinite(arguments)) {
return;
}
- this._ensureSubpath(cp1x, cp1y);
- this._currentSubPath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+ var pts = [cp1x, cp1y, cp2x, cp2y, x, y];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
+ cp1x = pts[0];
+ cp1y = pts[1];
+ cp2x = pts[2];
+ cp2y = pts[3];
+ x = pts[4];
+ y = pts[5];
+ if (!this._currentSubpath) {
+ this._newSubpath(cp1x, cp1y);
+ }
+ this._currentSubpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
this.closePath = function() {
- if (this._currentSubPath) {
- this._currentSubPath.close();
- this._currentSubPath = null;
- this._pathStarted = false;
+ if (this._currentSubpath) {
+ this._currentSubpath.close();
+ var lastPt = this._currentSubpath.getPoint(0);
+ this._newSubpath(lastPt[0], lastPt[1]);
+ }
+ }
+
+ this._commitSubpath = function () {
+ if (this._currentSubpath) {
+ this._currentPath.addPath(this._currentSubpath, false);
+ this._currentSubpath.delete();
+ this._currentSubpath = null;
}
}
@@ -158,49 +195,46 @@
if (radiusX < 0 || radiusY < 0) {
throw 'radii cannot be negative';
}
- this._pathStarted = true;
+ if (!this._currentSubpath) {
+ // Don't use newSubpath here because calculating the starting
+ // point in the arc is non-trivial. Just make a new, empty
+ // subpath to append to.
+ this._currentSubpath = new CanvasKit.SkPath();
+ }
var bounds = CanvasKit.LTRBRect(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
var temp = new CanvasKit.SkPath();
+ // Skia takes degrees. JS tends to be radians.
temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
- var m = CanvasKit.SkMatrix.rotated(radiansToDegrees(rotation), x, y);
- this._currentSubPath.addPath(temp, m, true);
- temp.delete();
- }
+ var m = CanvasKit.SkMatrix.multiply(
+ this._currentTransform,
+ CanvasKit.SkMatrix.rotated(rotation, x, y));
- // ensureSubpath makes SkPath behave like the browser's path object
- // in that the first lineTo really acts like a moveTo.
- // ensureSubpath is the term used in the canvas spec:
- // https://html.spec.whatwg.org/multipage/canvas.html#ensure-there-is-a-subpath
- // ensureSubpath returns true if the drawing command can proceed,
- // false otherwise (i.e. it was the first command and may be replaced
- // with a moveTo).
- this._ensureSubpath = function(x, y) {
- if (!this._currentSubPath) {
- this.beginPath();
- }
- if (!this._pathStarted) {
- this._pathStarted = true;
- this.moveTo(x, y);
- return false;
- }
- return true;
+ this._currentSubpath.addPath(temp, m, true);
+ temp.delete();
}
this.fillText = function(text, x, y, maxWidth) {
// TODO do something with maxWidth, probably involving measure
+ this._canvas.setMatrix(this._currentTransform);
this._canvas.drawText(text, x, y, this._paint);
+ this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
}
this.lineTo = function(x, y) {
if (!argsAreFinite(arguments)) {
return;
}
- // lineTo is the odd-ball in the sense that a line-to without
- // a previous subpath is the same as a moveTo.
- if (this._ensureSubpath(x, y)) {
- this._currentSubPath.lineTo(x, y);
+ var pts = [x, y];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
+ x = pts[0];
+ y = pts[1];
+ // A lineTo without a previous subpath is turned into a moveTo
+ if (!this._currentSubpath) {
+ this._newSubpath(x, y);
+ } else {
+ this._currentSubpath.lineTo(x, y);
}
}
@@ -215,77 +249,116 @@
if (!argsAreFinite(arguments)) {
return;
}
- if (this._ensureSubpath(x, y)) {
- this._currentSubPath.moveTo(x, y);
- }
+ var pts = [x, y];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
+ x = pts[0];
+ y = pts[1];
+ this._newSubpath(x, y);
+ }
+
+ this._newSubpath = function(x, y) {
+ this._commitSubpath();
+ this._currentSubpath = new CanvasKit.SkPath();
+ this._currentSubpath.moveTo(x, y);
}
this.quadraticCurveTo = function(cpx, cpy, x, y) {
if (!argsAreFinite(arguments)) {
return;
}
- this._ensureSubpath(cpx, cpy);
- this._currentSubPath.quadTo(cpx, cpy, x, y);
+ var pts = [cpx, cpy, x, y];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
+ cpx = pts[0];
+ cpy = pts[1];
+ x = pts[2];
+ y = pts[3];
+ if (!this._currentSubpath) {
+ this._newSubpath(cpx, cpy);
+ }
+ this._currentSubpath.quadTo(cpx, cpy, x, y);
}
this.rect = function(x, y, width, height) {
if (!argsAreFinite(arguments)) {
return;
}
+ var pts = [x, y];
+ CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
// https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
- this.beginPath();
- this._currentSubPath.addRect(x, y, x+width, y+height);
- this.beginPath();
- this._currentSubPath.moveTo(x, y);
+ this._newSubpath(x, y);
+ var scale = this._scalefactor();
+ this._currentSubpath.addRect(x, y, x+width, y+height);
+ this._currentSubpath.transform(this._currentTransform);
+ this._newSubpath(pts[0], pts[1]);
}
this.resetTransform = function() {
- this.setTransform(1, 0, 0, 1, 0, 0);
+ this._currentTransform = CanvasKit.SkMatrix.identity();
}
this.rotate = function(radians, px, py) {
- // bindings can't turn undefined into floats
- this._canvas.rotate(radians * 180/Math.PI, px || 0, py || 0);
+ this._currentTransform = CanvasKit.SkMatrix.multiply(
+ this._currentTransform,
+ CanvasKit.SkMatrix.rotated(radians, px, py));
}
this.scale = function(sx, sy) {
- this._canvas.scale(sx, sy);
+ this._currentTransform = CanvasKit.SkMatrix.multiply(
+ this._currentTransform,
+ CanvasKit.SkMatrix.scaled(sx, sy));
}
- this.scale = function(sx, sy) {
- this._canvas.scale(sx, sy);
+ this._scalefactor = function() {
+ // This is an approximation of what Chrome does when scaling up
+ // line width.
+ var m = this._currentTransform;
+ var sx = m[0];
+ var sy = m[4];
+ return (Math.abs(sx) + Math.abs(sy))/2;
}
this.setTransform = function(a, b, c, d, e, f) {
- this._canvas.setMatrix([a, c, e,
- b, d, f,
- 0, 0, 1]);
- }
-
- this.skew = function(sx, sy) {
- this._canvas.skew(sx, sy);
+ this._currentTransform = [a, c, e,
+ b, d, f,
+ 0, 0, 1];
}
this.stroke = function() {
- if (this._currentSubPath) {
+ if (this._currentSubpath) {
+ this._commitSubpath();
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
- this._paint.setLine
- for (var i = 0; i < this._paths.length; i++) {
- this._canvas.drawPath(this._paths[i], this._paint);
- }
+ var orig = this._paint.getStrokeWidth();
+ // This is not in the spec, but it appears Chrome scales up
+ // the line width by some amount when stroking (and filling?).
+ var scaledWidth = orig * this._scalefactor();
+ this._paint.setStrokeWidth(scaledWidth);
+ this._canvas.drawPath(this._currentPath, this._paint);
+ // set stroke width back to original size:
+ this._paint.setStrokeWidth(orig);
}
}
this.strokeText = function(text, x, y, maxWidth) {
// TODO do something with maxWidth, probably involving measure
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
+ this._canvas.setMatrix(this._currentTransform);
this._canvas.drawText(text, x, y, this._paint);
+ this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
}
this.translate = function(dx, dy) {
- this._canvas.translate(dx, dy);
+ this._currentTransform = CanvasKit.SkMatrix.multiply(
+ this._currentTransform,
+ CanvasKit.SkMatrix.translated(dx, dy));
}
+ this.transform = function(a, b, c, d, e, f) {
+ this._currentTransform = CanvasKit.SkMatrix.multiply(
+ this._currentTransform,
+ [a, c, e,
+ b, d, f,
+ 0, 0, 1]);
+ }
// Not supported operations (e.g. for Web only)
this.addHitRegion = function() {};
@@ -323,7 +396,7 @@
SkDebug('Could not parse font size' + fontStr);
return 16;
}
- var size = fontSize[1];
+ var size = parseFloat(fontSize[1]);
var unit = fontSize[2];
switch (unit) {
case 'pt':
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index febdffceb5..248189650c 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -19,23 +19,90 @@
return a * b + c * d + e * f;
}
- // Return a matrix representing a rotation by n degrees.
+ CanvasKit.SkMatrix.identity = function() {
+ return [
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1,
+ ];
+ };
+
+ // Maps the given points according to the passed in matrix.
+ // Results are done in place.
+ // See SkMatrix.h::mapPoints for the docs on the math.
+ CanvasKit.SkMatrix.mapPoints = function(matrix, ptArr) {
+ if (ptArr.length % 2) {
+ throw 'mapPoints requires an even length arr';
+ }
+ for (var i = 0; i < ptArr.length; i+=2) {
+ var x = ptArr[i], y = ptArr[i+1];
+ // Gx+Hy+I
+ var denom = matrix[6]*x + matrix[7]*y + matrix[8];
+ // Ax+By+C
+ var xTrans = matrix[0]*x + matrix[1]*y + matrix[2];
+ // Dx+Ey+F
+ var yTrans = matrix[3]*x + matrix[4]*y + matrix[5];
+ ptArr[i] = xTrans/denom;
+ ptArr[i+1] = yTrans/denom;
+ }
+ return ptArr;
+ };
+
+ CanvasKit.SkMatrix.multiply = function(m1, m2) {
+ var result = [0,0,0, 0,0,0, 0,0,0];
+ for (var r = 0; r < 3; r++) {
+ for (var c = 0; c < 3; c++) {
+ // m1 and m2 are 1D arrays pretending to be 2D arrays
+ result[3*r + c] = sdot(m1[3*r + 0], m2[3*0 + c],
+ m1[3*r + 1], m2[3*1 + c],
+ m1[3*r + 2], m2[3*2 + c]);
+ }
+ }
+ return result;
+ }
+
+ // Return a matrix representing a rotation by n radians.
// px, py optionally say which point the rotation should be around
// with the default being (0, 0);
- CanvasKit.SkMatrix.rotated = function(degrees, px, py) {
+ CanvasKit.SkMatrix.rotated = function(radians, px, py) {
px = px || 0;
py = py || 0;
- var rad = degreesToRadians(degrees);
- var sinV = Math.sin(rad);
- var cosV = Math.cos(rad);
+ var sinV = Math.sin(radians);
+ var cosV = Math.cos(radians);
return [
cosV, -sinV, sdot( sinV, py, 1 - cosV, px),
sinV, cosV, sdot(-sinV, px, 1 - cosV, py),
0, 0, 1,
];
};
- // TODO(kjlubick): translated, scaled
+ CanvasKit.SkMatrix.scaled = function(sx, sy, px, py) {
+ px = px || 0;
+ py = py || 0;
+ return [
+ sx, 0, px - sx * px,
+ 0, sy, py - sy * py,
+ 0, 0, 1,
+ ];
+ };
+
+ CanvasKit.SkMatrix.skewed = function(kx, ky, px, py) {
+ px = px || 0;
+ py = py || 0;
+ return [
+ 1, kx, -kx * px,
+ ky, 1, -ky * py,
+ 0, 0, 1,
+ ];
+ };
+
+ CanvasKit.SkMatrix.translated = function(dx, dy) {
+ return [
+ 1, 0, dx,
+ 0, 1, dy,
+ 0, 0, 1,
+ ];
+ };
CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
// see arc() for the HTMLCanvas version
@@ -183,8 +250,8 @@
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;
+ opts.cap = opts.cap || CanvasKit.StrokeCap.Butt;
+ opts.join = opts.join || CanvasKit.StrokeJoin.Miter;
if (this._stroke(opts)) {
return this;
}
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
index d2b3f6601b..03e744b275 100644
--- a/experimental/canvaskit/tests/canvas2d.spec.js
+++ b/experimental/canvaskit/tests/canvas2d.spec.js
@@ -94,7 +94,7 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
test(canvas);
// canvas has .toDataURL (even though skcanvas is not a real Canvas)
// so this will work.
- promises.push(reportCanvas(canvas, 'all_path_operations', canvas._config));
+ promises.push(reportCanvas(canvas, testname, canvas._config));
}
Promise.all(promises).then(() => {
skcanvas.dispose();
@@ -107,8 +107,6 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
LoadCanvasKit.then(catchException(done, () => {
multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
let ctx = canvas.getContext('2d');
- // TODO(kjlubick): scale doesn't work quite right yet, but putting
- // it before all draws makes it consistent for now.
ctx.scale(3.0, 3.0);
ctx.moveTo(20, 5);
ctx.lineTo(30, 20);
@@ -142,6 +140,58 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
});
}));
});
+
+ it('handles all the transforms as specified', function(done) {
+ LoadCanvasKit.then(catchException(done, () => {
+ multipleCanvasTest('all_matrix_operations', done, (canvas) => {
+ let ctx = canvas.getContext('2d');
+ ctx.rect(10, 10, 20, 20);
+
+ ctx.scale(2.0, 4.0);
+ ctx.rect(30, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.rotate(Math.PI / 3);
+ ctx.rect(50, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.translate(30, -2);
+ ctx.rect(70, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.translate(60, 0);
+ ctx.rotate(Math.PI / 6);
+ ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
+ ctx.rect(90, 10, 20, 20);
+ ctx.resetTransform();
+
+ ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+ ctx.rect(110, 10, 20, 20);
+ ctx.lineTo(110, 0);
+ ctx.resetTransform();
+ ctx.lineTo(220, 120);
+
+ ctx.scale(3.0, 3.0);
+ ctx.font = '6pt Arial';
+ ctx.fillText('This text should be huge', 10, 80);
+ ctx.resetTransform();
+
+ ctx.strokeStyle = 'black';
+ ctx.lineWidth = 2;
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.moveTo(250, 30);
+ ctx.lineTo(250, 80);
+ ctx.scale(3.0, 3.0);
+ ctx.lineTo(280/3, 90/3);
+ ctx.closePath();
+ ctx.strokeStyle = 'black';
+ ctx.lineWidth = 5;
+ ctx.stroke();
+ });
+ }));
+ });
}); // end describe('Path drawing API')
diff --git a/experimental/canvaskit/tests/matrix.spec.js b/experimental/canvaskit/tests/matrix.spec.js
new file mode 100644
index 0000000000..eff2d4ee6c
--- /dev/null
+++ b/experimental/canvaskit/tests/matrix.spec.js
@@ -0,0 +1 @@
+//TODO test mapPoints and such
\ No newline at end of file