[canvaskit] Make transforms work properly
This re-works the subpath model and does transforming of ports in JS rather than in C++; the latter was not easy to conform to the Canvas Spec, especially for changing transforms in the middle of a path. Additionally adds jpeg to the available images it can produce. Bug: skia: Change-Id: I5a52ec341d4060198c8680aa4d3b85a26f77b6b9 Reviewed-on: https://skia-review.googlesource.com/c/172500 Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
3387f2b6ac
commit
b9db3906d5
@ -17,6 +17,14 @@
|
||||
|
||||
</style>
|
||||
|
||||
<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
|
||||
<img id=api1 width=300 height=300>
|
||||
<canvas id=api1_c width=300 height=300></canvas>
|
||||
<img id=api2 width=300 height=300>
|
||||
<canvas id=api2_c width=300 height=300></canvas>
|
||||
<img id=api3 width=300 height=300>
|
||||
<canvas id=api3_c width=300 height=300></canvas>
|
||||
|
||||
<h2> CanvasKit draws Paths to the browser</h2>
|
||||
<canvas id=vertex1 width=300 height=300></canvas>
|
||||
<canvas id=vertex2 width=300 height=300></canvas>
|
||||
@ -38,11 +46,7 @@
|
||||
|
||||
<canvas id=nima_example width=300 height=300></canvas>
|
||||
|
||||
<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
|
||||
<img id=api1 width=300 height=300>
|
||||
<canvas id=api1_c width=300 height=300></canvas>
|
||||
<img id=api2 width=300 height=300>
|
||||
<canvas id=api2_c width=300 height=300></canvas>
|
||||
|
||||
|
||||
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
|
||||
|
||||
@ -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;
|
||||
|
@ -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<void (SkScalar, SkScalar, SkScalar)>(&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<void() const>(&SkPath::dump))
|
||||
.function("dumpHex", select_overload<void() const>(&SkPath::dumpHex))
|
||||
#endif
|
||||
;
|
||||
|
||||
class_<SkShader>("SkShader")
|
||||
.smart_ptr<sk_sp<SkShader>>("sk_sp<SkShader>");
|
||||
@ -617,6 +634,22 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.value("XOR", SkPathOp::kXOR_SkPathOp)
|
||||
.value("ReverseDifference", SkPathOp::kReverseDifference_SkPathOp);
|
||||
|
||||
enum_<SkPaint::Cap>("StrokeCap")
|
||||
.value("Butt", SkPaint::Cap::kButt_Cap)
|
||||
.value("Round", SkPaint::Cap::kRound_Cap)
|
||||
.value("Square", SkPaint::Cap::kSquare_Cap);
|
||||
|
||||
enum_<SkPaint::Join>("StrokeJoin")
|
||||
.value("Miter", SkPaint::Join::kMiter_Join)
|
||||
.value("Round", SkPaint::Join::kRound_Join)
|
||||
.value("Bevel", SkPaint::Join::kBevel_Join);
|
||||
|
||||
value_object<StrokeOpts>("StrokeOpts")
|
||||
.field("width", &StrokeOpts::width)
|
||||
.field("miter_limit", &StrokeOpts::miter_limit)
|
||||
.field("join", &StrokeOpts::join)
|
||||
.field("cap", &StrokeOpts::cap);
|
||||
|
||||
enum_<SkShader::TileMode>("TileMode")
|
||||
.value("Clamp", SkShader::TileMode::kClamp_TileMode)
|
||||
.value("Repeat", SkShader::TileMode::kRepeat_TileMode)
|
||||
|
@ -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() {};
|
||||
|
@ -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,
|
||||
this._currentTransform = [a, c, e,
|
||||
b, d, f,
|
||||
0, 0, 1]);
|
||||
}
|
||||
|
||||
this.skew = function(sx, sy) {
|
||||
this._canvas.skew(sx, sy);
|
||||
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':
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
1
experimental/canvaskit/tests/matrix.spec.js
Normal file
1
experimental/canvaskit/tests/matrix.spec.js
Normal file
@ -0,0 +1 @@
|
||||
//TODO test mapPoints and such
|
Loading…
Reference in New Issue
Block a user