[canvaskit] Add sundry APIs and tests

To the reviewer: I've tried to make it so each PS adds one new API.

Change-Id: I81fc85c7a93a19ce4fd725a125e138d35471e693
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/237155
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Kevin Lubick 2019-08-26 15:48:09 -04:00
parent 6064ecf7e9
commit e384df4f5e
7 changed files with 327 additions and 93 deletions

View File

@ -6,9 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- `SkCanvas.drawCircle()`, `SkCanvas.getSaveCount()`
- `SkPath.offset()`, `SkPath.drawOval`
### Changed
- `MakeSkVertices` uses a builder to save a copy.
### Breaking
- When `SkPath.arcTo` is given seven arguments, it no longer turns the first four into
a `SkRect` automatically, and instead uses them as
`arcTo(rx, ry, xAxisRotate, useSmallArc, isCCW, x, y)` (see SkPath.h for more).
## [0.6.0] - 2019-05-06
### Added

View File

@ -175,6 +175,11 @@ void ApplyAddArc(SkPath& orig, const SkRect& oval, SkScalar startAngle, SkScalar
orig.addArc(oval, startAngle, sweepAngle);
}
void ApplyAddOval(SkPath& orig, const SkRect& oval, bool ccw, unsigned start) {
orig.addOval(oval, ccw ? SkPath::Direction::kCCW_Direction :
SkPath::Direction::kCW_Direction, start);
}
void ApplyAddPath(SkPath& orig, const SkPath& newPath,
SkScalar scaleX, SkScalar skewX, SkScalar transX,
SkScalar skewY, SkScalar scaleY, SkScalar transY,
@ -213,6 +218,13 @@ void ApplyArcToAngle(SkPath& p, SkRect& oval, SkScalar startAngle, SkScalar swee
p.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
}
void ApplyAddArcToArcSize(SkPath& orig, SkScalar rx, SkScalar ry, SkScalar xAxisRotate,
bool useSmallArc, bool ccw, SkScalar x, SkScalar y) {
auto arcSize = useSmallArc ? SkPath::ArcSize::kSmall_ArcSize : SkPath::ArcSize::kLarge_ArcSize;
auto sweep = ccw ? SkPath::Direction::kCCW_Direction : SkPath::Direction::kCW_Direction;
orig.arcTo(rx, ry, xAxisRotate, arcSize, sweep, x, y);
}
void ApplyClose(SkPath& p) {
p.close();
}
@ -754,6 +766,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
}
self.drawAtlas(atlas, dstXforms, srcRects, colors, count, mode, nullptr, paint);
}), allow_raw_pointers())
.function("drawCircle", select_overload<void (SkScalar, SkScalar, SkScalar, const SkPaint& paint)>(&SkCanvas::drawCircle))
.function("drawImage", select_overload<void (const sk_sp<SkImage>&, SkScalar, SkScalar, const SkPaint*)>(&SkCanvas::drawImage), allow_raw_pointers())
.function("drawImageRect", optional_override([](SkCanvas& self, const sk_sp<SkImage>& image,
SkRect src, SkRect dst,
@ -791,6 +804,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("drawTextBlob", select_overload<void (const sk_sp<SkTextBlob>&, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawTextBlob))
.function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
.function("flush", &SkCanvas::flush)
.function("getSaveCount", &SkCanvas::getSaveCount)
.function("getTotalMatrix", optional_override([](const SkCanvas& self)->SimpleMatrix {
SkMatrix m = self.getTotalMatrix();
return toSimpleSkMatrix(m);
@ -968,6 +982,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
.constructor<const SkPath&>()
.function("_addArc", &ApplyAddArc)
// interface.js has 3 overloads of addPath
.function("_addOval", &ApplyAddOval)
.function("_addPath", &ApplyAddPath)
// interface.js has 4 overloads of addRect
.function("_addRect", &ApplyAddRect)
@ -975,6 +990,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
.function("_addRoundRect", &ApplyAddRoundRect)
.function("_arcTo", &ApplyArcTo)
.function("_arcTo", &ApplyArcToAngle)
.function("_arcTo", &ApplyAddArcToArcSize)
.function("_close", &ApplyClose)
.function("_conicTo", &ApplyConicTo)
.function("countPoints", &SkPath::countPoints)

View File

@ -105,6 +105,7 @@ var CanvasKit = {
clipRect: function() {},
concat: function() {},
drawArc: function() {},
drawCircle: function() {},
drawImage: function() {},
drawImageRect: function() {},
drawLine: function() {},
@ -116,6 +117,7 @@ var CanvasKit = {
drawRoundRect: function() {},
drawShadow: function() {},
drawText: function() {},
getSaveCount: function() {},
drawTextBlob: function() {},
drawVertices: function() {},
flush: function() {},
@ -232,6 +234,7 @@ var CanvasKit = {
// private API
_addArc: function() {},
_addOval: function() {},
_addPath: function() {},
_addRect: function() {},
_addRoundRect: function() {},
@ -509,6 +512,7 @@ var CanvasKit = {
// It's not enough to declare them above, because closure can still erase them
// unless they go on the prototype.
CanvasKit.SkPath.prototype.addArc = function() {};
CanvasKit.SkPath.prototype.addOval = function() {};
CanvasKit.SkPath.prototype.addPath = function() {};
CanvasKit.SkPath.prototype.addRect = function() {};
CanvasKit.SkPath.prototype.addRoundRect = function() {};
@ -520,6 +524,7 @@ CanvasKit.SkPath.prototype.cubicTo = function() {};
CanvasKit.SkPath.prototype.dash = function() {};
CanvasKit.SkPath.prototype.lineTo = function() {};
CanvasKit.SkPath.prototype.moveTo = function() {};
CanvasKit.SkPath.prototype.offset = function() {};
CanvasKit.SkPath.prototype.op = function() {};
CanvasKit.SkPath.prototype.quadTo = function() {};
CanvasKit.SkPath.prototype.rect = function() {};

View File

@ -130,6 +130,14 @@ CanvasKit.onRuntimeInitialized = function() {
return this;
};
CanvasKit.SkPath.prototype.addOval = function(oval, isCCW, startIndex) {
if (startIndex === undefined) {
startIndex = 1;
}
this._addOval(oval, !!isCCW, startIndex);
return this;
};
CanvasKit.SkPath.prototype.addPath = function() {
// Takes 1, 2, 7, or 10 required args, where the first arg is always the path.
// The last arg is optional and chooses between add or extend mode.
@ -243,15 +251,14 @@ CanvasKit.onRuntimeInitialized = function() {
// takes 4, 5 or 7 args
// - 5 x1, y1, x2, y2, radius
// - 4 oval (as Rect), startAngle, sweepAngle, forceMoveTo
// - 7 x1, y1, x2, y2, startAngle, sweepAngle, forceMoveTo
// - 7 rx, ry, xAxisRotate, useSmallArc, isCCW, x, y
var args = arguments;
if (args.length === 5) {
this._arcTo(args[0], args[1], args[2], args[3], args[4]);
} else if (args.length === 4) {
this._arcTo(args[0], args[1], args[2], args[3]);
} else if (args.length === 7) {
this._arcTo(CanvasKit.LTRBRect(args[0], args[1], args[2], args[3]),
args[4], args[5], args[6]);
this._arcTo(args[0], args[1], args[2], !!args[3], !!args[4], args[5], args[6]);
} else {
throw 'Invalid args for arcTo. Expected 4, 5, or 7, got '+ args.length;
}
@ -291,6 +298,13 @@ CanvasKit.onRuntimeInitialized = function() {
return this;
};
CanvasKit.SkPath.prototype.offset = function(dx, dy) {
this._transform(1, 0, dx,
0, 1, dy,
0, 0, 1);
return this;
};
CanvasKit.SkPath.prototype.op = function(otherPath, op) {
if (this._op(otherPath, op)) {
return this;

View File

@ -0,0 +1,165 @@
describe('CanvasKit\'s Canvas Behavior', function() {
let container = document.createElement('div');
document.body.appendChild(container);
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 600;
beforeEach(function() {
container.innerHTML = `
<canvas width=600 height=600 id=test></canvas>
<canvas width=600 height=600 id=report></canvas>`;
});
afterEach(function() {
container.innerHTML = '';
});
it('can draw directly to a canvas', function(done) {
LoadCanvasKit.then(catchException(done, () => {
// This is taken from example.html
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setStrokeWidth(2.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
canvas.drawLine(3, 10, 30, 15, paint);
canvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
const font = new CanvasKit.SkFont(null, 20);
canvas.drawText('this is ascii text', 5, 100, paint, font);
const blob = CanvasKit.SkTextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
canvas.drawTextBlob(blob, 5, 130, paint);
surface.flush();
font.delete();
blob.delete();
paint.delete();
reportSurface(surface, 'canvas_api_example', done);
}));
// See canvas2d for more API tests
});
it('can apply an effect and draw text', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const path = starPath(CanvasKit);
const paint = new CanvasKit.SkPaint();
const textPaint = new CanvasKit.SkPaint();
textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
textPaint.setAntiAlias(true);
const textFont = new CanvasKit.SkFont(null, 30);
const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
paint.setPathEffect(dpe);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
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('This is text', 10, 280, textPaint, textFont);
surface.flush();
dpe.delete();
path.delete();
paint.delete();
textFont.delete();
textPaint.delete();
reportSurface(surface, 'effect_and_text_example', done);
}));
});
it('returns the depth of the save state stack', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
expect(canvas.getSaveCount()).toEqual(1);
canvas.save();
canvas.save();
canvas.restore();
canvas.save();
canvas.save();
expect(canvas.getSaveCount()).toEqual(4);
// does nothing, by the SkCanvas API
canvas.restoreToCount(500);
expect(canvas.getSaveCount()).toEqual(4);
canvas.restore();
expect(canvas.getSaveCount()).toEqual(3);
canvas.save();
canvas.restoreToCount(2);
expect(canvas.getSaveCount()).toEqual(2);
surface.delete();
done();
}));
});
it('draws circles', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const path = starPath(CanvasKit);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.CYAN);
canvas.clear(CanvasKit.WHITE);
canvas.drawCircle(30, 50, 15, paint);
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setColor(CanvasKit.RED);
canvas.drawCircle(130, 80, 60, paint);
canvas.drawCircle(20, 150, 60, paint);
surface.flush();
path.delete();
paint.delete();
reportSurface(surface, 'circle_canvas', done);
}));
});
});

View File

@ -75,94 +75,6 @@ describe('CanvasKit\'s Path Behavior', function() {
// See PathKit for more tests, since they share implementation
});
it('can draw directly to a canvas', function(done) {
LoadCanvasKit.then(catchException(done, () => {
// This is taken from example.html
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setStrokeWidth(2.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
canvas.drawLine(3, 10, 30, 15, paint);
canvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
const font = new CanvasKit.SkFont(null, 20);
canvas.drawText('this is ascii text', 5, 100, paint, font);
const blob = CanvasKit.SkTextBlob.MakeFromText('Unicode chars 💩 é É ص', font);
canvas.drawTextBlob(blob, 5, 130, paint);
surface.flush();
font.delete();
blob.delete();
paint.delete();
reportSurface(surface, 'canvas_api_example', done);
}));
// See canvas2d for more API tests
});
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;
}
it('can apply an effect and draw text', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const path = starPath(CanvasKit);
const paint = new CanvasKit.SkPaint();
const textPaint = new CanvasKit.SkPaint();
textPaint.setColor(CanvasKit.Color(40, 0, 0, 1.0));
textPaint.setAntiAlias(true);
const textFont = new CanvasKit.SkFont(null, 30);
const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
paint.setPathEffect(dpe);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
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('This is text', 10, 280, textPaint, textFont);
surface.flush();
dpe.delete();
path.delete();
reportSurface(surface, 'effect_and_text_example', done);
}));
});
it('can create a path from an SVG string', function(done) {
LoadCanvasKit.then(catchException(done, () => {
//.This is a parallelagram from
@ -185,7 +97,7 @@ describe('CanvasKit\'s Path Behavior', function() {
}));
});
it('can create an SVG string from a path', function(done) {
it('can create an SVG string from a path', function(done) {
LoadCanvasKit.then(catchException(done, () => {
let cmds = [[CanvasKit.MOVE_VERB, 205, 5],
[CanvasKit.LINE_VERB, 795, 5],
@ -202,4 +114,106 @@ describe('CanvasKit\'s Path Behavior', function() {
done();
}));
});
it('uses offset to transform the path with dx,dy', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const path = starPath(CanvasKit);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.BLACK);
canvas.clear(CanvasKit.WHITE);
canvas.drawPath(path, paint);
path.offset(80, 40);
canvas.drawPath(path, paint);
surface.flush();
path.delete();
paint.delete();
reportSurface(surface, 'offset_path', done);
}));
});
it('draws ovals', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.BLACK);
canvas.clear(CanvasKit.WHITE);
const path = new CanvasKit.SkPath();
path.moveTo(5, 5);
path.lineTo(10, 120);
path.addOval(CanvasKit.LTRBRect(10, 20, 100, 200), false, 3);
path.lineTo(300, 300);
canvas.drawPath(path, paint);
surface.flush();
path.delete();
paint.delete();
reportSurface(surface, 'oval_path', done);
}));
});
it('draws arcTo in a multitude of ways', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const surface = CanvasKit.MakeCanvasSurface('test');
expect(surface).toBeTruthy('Could not make surface')
if (!surface) {
done();
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.BLACK);
canvas.clear(CanvasKit.WHITE);
const path = new CanvasKit.SkPath();
//path.moveTo(5, 5);
// takes 4, 5 or 7 args
// - 5 x1, y1, x2, y2, radius
path.arcTo(40, 0, 40, 40, 40);
// - 4 oval (as Rect), startAngle, sweepAngle, forceMoveTo
path.arcTo(CanvasKit.LTRBRect(90, 10, 120, 200), 30, 300, true);
// - 7 rx, ry, xAxisRotate, useSmallArc, isCCW, x, y
path.moveTo(5, 105);
path.arcTo(24, 24, 45, true, false, 82, 156);
canvas.drawPath(path, paint);
surface.flush();
path.delete();
paint.delete();
reportSurface(surface, 'oval_path', done);
}));
});
});

View File

@ -16,4 +16,15 @@ function reportSurface(surface, testname, done) {
reportCanvas(reportingCanvas, testname).then(() => {
done();
}).catch(reportError(done));
}
}
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;
}