[canvaskit] Add gradients and clips

Refactors things inside to support both color
and gradient as _fillStyle/_strokeStyle.

Bug: skia:
Change-Id: I364ceb7d55c41e11161d5577dcd1611a592bbc29
Reviewed-on: https://skia-review.googlesource.com/c/173421
Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
Kevin Lubick 2018-11-29 15:07:02 -05:00
parent 62f64b5a05
commit eb2f6b0adb
6 changed files with 440 additions and 20 deletions

View File

@ -28,6 +28,8 @@
<canvas id=api4_c width=300 height=300></canvas>
<img id=api5 width=300 height=300>
<canvas id=api5_c width=300 height=300></canvas>
<img id=api6 width=300 height=300>
<canvas id=api6_c width=300 height=300></canvas>
<h2> CanvasKit draws Paths to the browser</h2>
<canvas id=vertex1 width=300 height=300></canvas>
@ -95,6 +97,7 @@
CanvasAPI3(CanvasKit);
CanvasAPI4(CanvasKit);
CanvasAPI5(CanvasKit);
CanvasAPI6(CanvasKit);
VertexAPI1(CanvasKit);
VertexAPI2(CanvasKit, bonesImage);
@ -673,6 +676,54 @@
document.getElementById('api5').src = skcanvas.toDataURL();
}
function CanvasAPI6(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(600, 600);
let realCanvas = document.getElementById('api6_c');
realCanvas.width = 600;
realCanvas.height = 600;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
var rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
// Add three color stops
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(0.7, 'white');
rgradient.addColorStop(1, 'blue');
ctx.fillStyle = rgradient;
ctx.globalAlpha = 0.7;
ctx.fillRect(0, 0, 600, 600);
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.arc(300, 100, 90, 0, Math.PI*1.66);
ctx.closePath();
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 5;
ctx.stroke();
ctx.save();
ctx.clip();
var lgradient = ctx.createLinearGradient(200, 20, 420, 40);
// Add three color stops
lgradient.addColorStop(0, 'green');
lgradient.addColorStop(0.5, 'cyan');
lgradient.addColorStop(1, 'orange');
ctx.fillStyle = lgradient;
ctx.fillRect(200, 30, 200, 300);
ctx.restore();
ctx.fillRect(550, 550, 40, 40);
}
document.getElementById('api6').src = skcanvas.toDataURL();
}
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
if (!CanvasKit || !nimaFile || !nimaTexture) {
return;

View File

@ -455,6 +455,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
return SkImageShader::Make(img, tx, ty, nullptr);
}), allow_raw_pointers());
// Allow localMatrix to be optional, so we have 2 declarations of these gradients
function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
@ -502,6 +503,35 @@ EMSCRIPTEN_BINDINGS(Skia) {
return SkGradientShader::MakeRadial(center, radius, colors, positions, count,
mode, flags, &localMatrix);
}), allow_raw_pointers());
function("_MakeTwoPointConicalGradientShader", optional_override([](
SkPoint start, SkScalar startRadius,
SkPoint end, SkScalar endRadius,
uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
colors, positions, count, mode,
flags, nullptr);
}), allow_raw_pointers());
function("_MakeTwoPointConicalGradientShader", optional_override([](
SkPoint start, SkScalar startRadius,
SkPoint end, SkScalar endRadius,
uintptr_t /* SkColor* */ cPtr, uintptr_t /* SkScalar* */ pPtr,
int count, SkShader::TileMode mode, uint32_t flags,
const SimpleMatrix& lm)->sk_sp<SkShader> {
// See comment above for uintptr_t explanation
const SkColor* colors = reinterpret_cast<const SkColor*> (cPtr);
const SkScalar* positions = reinterpret_cast<const SkScalar*>(pPtr);
SkMatrix localMatrix = toSkMatrix(lm);
return SkGradientShader::MakeTwoPointConical(start, startRadius, end, endRadius,
colors, positions, count, mode,
flags, &localMatrix);
}), allow_raw_pointers());
function("_MakeSkVertices", optional_override([](SkVertices::VertexMode mode, int vertexCount,
uintptr_t /* SkPoint* */ pPtr, uintptr_t /* SkPoint* */ tPtr,
uintptr_t /* SkColor* */ cPtr,
@ -526,6 +556,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
// Add a optional_override to change it out.
self.clear(SkColor(color));
}))
.function("clipPath", select_overload<void (const SkPath&, SkClipOp, bool)>(&SkCanvas::clipPath))
.function("drawPaint", &SkCanvas::drawPaint)
.function("drawPath", &SkCanvas::drawPath)
.function("drawRect", &SkCanvas::drawRect)
@ -546,6 +577,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
}))
.function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
.function("flush", &SkCanvas::flush)
.function("restore", &SkCanvas::restore)
.function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
.function("save", &SkCanvas::save)
.function("scale", &SkCanvas::scale)
@ -728,6 +760,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
.value("Outer", SkBlurStyle::kOuter_SkBlurStyle)
.value("Inner", SkBlurStyle::kInner_SkBlurStyle);
enum_<SkClipOp>("ClipOp")
.value("Difference", SkClipOp::kDifference)
.value("Intersect", SkClipOp::kIntersect);
enum_<SkPath::FillType>("FillType")
.value("Winding", SkPath::FillType::kWinding_FillType)
.value("EvenOdd", SkPath::FillType::kEvenOdd_FillType)

View File

@ -31,6 +31,7 @@ var CanvasKit = {
MakeCanvas: function() {},
MakeCanvasSurface: function() {},
MakeImageShader: function() {},
/** @return {LinearCanvasGradient} */
MakeLinearGradientShader: function() {},
MakeNimaActor: function() {},
MakeRadialGradientShader: function() {},
@ -38,6 +39,8 @@ var CanvasKit = {
MakeSkDashPathEffect: function() {},
MakeSkVertices: function() {},
MakeSurface: function() {},
/** @return {RadialCanvasGradient} */
MakeTwoPointConicalGradientShader: function() {},
MakeWebGLCanvasSurface: function() {},
currentContext: function() {},
getColorComponents: function() {},
@ -54,6 +57,7 @@ var CanvasKit = {
_MakeRadialGradientShader: function() {},
_MakeSkDashPathEffect: function() {},
_MakeSkVertices: function() {},
_MakeTwoPointConicalGradientShader: function() {},
_getRasterN32PremulSurface: function() {},
_getWebGLSurface: function() {},
@ -78,6 +82,7 @@ var CanvasKit = {
SkCanvas: {
// public API (from C++ bindings)
clear: function() {},
clipPath: function() {},
drawPaint: function() {},
drawPath: function() {},
drawRect: function() {},
@ -85,6 +90,7 @@ var CanvasKit = {
drawText: function() {},
drawVertices: function() {},
flush: function() {},
restore: function() {},
rotate: function() {},
save: function() {},
scale: function() {},
@ -261,6 +267,11 @@ var CanvasKit = {
Inner: {},
},
ClipOp: {
Difference: {},
Intersect: {},
},
FillType: {
Winding: {},
EvenOdd: {},
@ -279,6 +290,14 @@ var CanvasKit = {
StrokeAndFill: {},
},
PathOp: {
Difference: {},
Intersect: {},
Union: {},
XOR: {},
ReverseDifference: {},
},
StrokeCap: {
Butt: {},
Round: {},
@ -291,6 +310,18 @@ var CanvasKit = {
Bevel: {},
},
TileMode: {
Clamp: {},
Repeat: {},
Mirror: {},
},
VertexMode: {
Triangles: {},
TrianglesStrip: {},
TriangleFan: {},
},
// Things Enscriptem adds for us
/** Represents the heap of the WASM code
@ -356,6 +387,7 @@ StrokeOpts.prototype.miter_limit;
StrokeOpts.prototype.cap;
StrokeOpts.prototype.join;
// Define everything created in the canvas2d spec here
var HTMLCanvas = {};
HTMLCanvas.prototype.getContext = function() {};
HTMLCanvas.prototype.toDataURL = function() {};
@ -369,7 +401,10 @@ CanvasRenderingContext2D.prototype.beginPath = function() {};
CanvasRenderingContext2D.prototype.bezierCurveTo = function() {};
CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
CanvasRenderingContext2D.prototype.clearRect = function() {};
CanvasRenderingContext2D.prototype.clip = function() {};
CanvasRenderingContext2D.prototype.closePath = function() {};
CanvasRenderingContext2D.prototype.createLinearGradient = function() {};
CanvasRenderingContext2D.prototype.createRadialGradient = function() {};
CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
CanvasRenderingContext2D.prototype.ellipse = function() {};
CanvasRenderingContext2D.prototype.fill = function() {};
@ -396,6 +431,11 @@ CanvasRenderingContext2D.prototype.strokeText = function() {};
CanvasRenderingContext2D.prototype.transform = function() {};
CanvasRenderingContext2D.prototype.translate = function() {};
var LinearCanvasGradient = {};
LinearCanvasGradient.prototype.addColorStop = function() {};
var RadialCanvasGradient = {};
RadialCanvasGradient.prototype.addColorStop = function() {};
// Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
function loadWebAssemblyModule() {};

View File

@ -66,6 +66,156 @@
}
}
function LinearCanvasGradient(x1, y1, x2, y2) {
this._shader = null;
this._colors = [];
this._pos = [];
this.addColorStop = function(offset, color) {
if (offset < 0 || offset > 1 || !isFinite(offset)) {
throw 'offset must be between 0 and 1 inclusively';
}
color = parseColor(color);
// From the spec: If multiple stops are added at the same offset on a
// gradient, then they must be placed in the order added, with the first
// one closest to the start of the gradient, and each subsequent one
// infinitesimally further along towards the end point (in effect
// causing all but the first and last stop added at each point to be
// ignored).
// To implement that, if an offset is already in the list,
// we just overwrite its color (since the user can't remove Color stops
// after the fact).
var idx = this._pos.indexOf(offset);
if (idx !== -1) {
this._colors[idx] = color;
} else {
// insert it in sorted order
for (idx = 0; idx < this._pos.length; idx++) {
if (this._pos[idx] > offset) {
break;
}
}
this._pos .splice(idx, 0, offset);
this._colors.splice(idx, 0, color);
}
}
this._copy = function() {
var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
lcg._colors = this._colors.slice();
lcg._pos = this._pos.slice();
return lcg;
}
this._dispose = function() {
if (this._shader) {
this._shader.delete();
this._shader = null;
}
}
this._getShader = function(currentTransform, globalAlpha) {
// From the spec: "The points in the linear gradient must be transformed
// as described by the current transformation matrix when rendering."
var pts = [x1, y1, x2, y2];
CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
var sx1 = pts[0];
var sy1 = pts[1];
var sx2 = pts[2];
var sy2 = pts[3];
this._dispose();
var colors = this._colors.map(function(c) {
return CanvasKit.multiplyByAlpha(c, globalAlpha);
});
this._shader = CanvasKit.MakeLinearGradientShader([sx1, sy1], [sx2, sy2],
colors, this._pos, CanvasKit.TileMode.Clamp);
return this._shader;
}
}
// Note, Skia has a different notion of a "radial" gradient.
// Skia has a twoPointConical gradient that is the same as the
// canvas's RadialGradient.
function RadialCanvasGradient(x1, y1, r1, x2, y2, r2) {
this._shader = null;
this._colors = [];
this._pos = [];
this.addColorStop = function(offset, color) {
if (offset < 0 || offset > 1 || !isFinite(offset)) {
throw 'offset must be between 0 and 1 inclusively';
}
color = parseColor(color);
// From the spec: If multiple stops are added at the same offset on a
// gradient, then they must be placed in the order added, with the first
// one closest to the start of the gradient, and each subsequent one
// infinitesimally further along towards the end point (in effect
// causing all but the first and last stop added at each point to be
// ignored).
// To implement that, if an offset is already in the list,
// we just overwrite its color (since the user can't remove Color stops
// after the fact).
var idx = this._pos.indexOf(offset);
if (idx !== -1) {
this._colors[idx] = color;
} else {
// insert it in sorted order
for (idx = 0; idx < this._pos.length; idx++) {
if (this._pos[idx] > offset) {
break;
}
}
this._pos .splice(idx, 0, offset);
this._colors.splice(idx, 0, color);
}
}
this._copy = function() {
var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
rcg._colors = this._colors.slice();
rcg._pos = this._pos.slice();
return rcg;
}
this._dispose = function() {
if (this._shader) {
this._shader.delete();
this._shader = null;
}
}
this._getShader = function(currentTransform, globalAlpha) {
// From the spec: "The points in the linear gradient must be transformed
// as described by the current transformation matrix when rendering."
var pts = [x1, y1, x2, y2];
CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
var sx1 = pts[0];
var sy1 = pts[1];
var sx2 = pts[2];
var sy2 = pts[3];
// Maybe refactor _scalefactor() on which this is taken?
var sx = currentTransform[0];
var sy = currentTransform[4];
var scaleFactor = (Math.abs(sx) + Math.abs(sy))/2;
var sr1 = r1 * scaleFactor;
var sr2 = r2 * scaleFactor;
this._dispose();
var colors = this._colors.map(function(c) {
return CanvasKit.multiplyByAlpha(c, globalAlpha);
});
this._shader = CanvasKit.MakeTwoPointConicalGradientShader(
[sx1, sy1], sr1, [sx2, sy2], sr2, colors, this._pos,
CanvasKit.TileMode.Clamp);
return this._shader;
}
}
function CanvasRenderingContext2D(skcanvas) {
this._canvas = skcanvas;
this._paint = new CanvasKit.SkPaint();
@ -75,8 +225,8 @@
this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
this._strokeColor = CanvasKit.BLACK;
this._fillColor = CanvasKit.BLACK;
this._strokeStyle = CanvasKit.BLACK;
this._fillStyle = CanvasKit.BLACK;
this._shadowBlur = 0;
this._shadowColor = CanvasKit.TRANSPARENT;
this._shadowOffsetX = 0;
@ -95,12 +245,19 @@
this._currentSubpath = null;
this._currentTransform = CanvasKit.SkMatrix.identity();
// Use this for save/restore
this._canvasStateStack = [];
// Keep a reference to all the gradients that were allocated
// for cleanup in _dispose;
this._gradients = [];
this._dispose = function() {
this._currentPath.delete();
this._currentSubpath && this._currentSubpath.delete();
this._paint.delete();
this._gradients.forEach(function(gradient) {
gradient._dispose();
});
// Don't delete this._canvas as it will be disposed
// by the surface of which it is based.
}
@ -146,10 +303,18 @@
Object.defineProperty(this, 'fillStyle', {
enumerable: true,
get: function() {
return colorToString(this._fillColor);
if (Number.isInteger(this._fillStyle)) {
return colorToString(this._fillStyle);
}
return this._fillStyle;
},
set: function(newStyle) {
this._fillColor = parseColor(newStyle);
if (typeof newStyle === 'string') {
this._fillStyle = parseColor(newStyle);
} else if (newStyle.addColorStop) {
// It's probably a gradient.
this._fillStyle = newStyle
}
}
});
@ -494,10 +659,16 @@
Object.defineProperty(this, 'strokeStyle', {
enumerable: true,
get: function() {
return colorToString(this._strokeColor);
return colorToString(this._strokeStyle);
},
set: function(newStyle) {
this._strokeColor = parseColor(newStyle);
if (typeof newStyle === 'string') {
this._strokeStyle = parseColor(newStyle);
} else if (newStyle.addColorStop) {
// It's probably a gradient.
this._strokeStyle = newStyle
}
}
});
@ -562,6 +733,17 @@
this._paint.setBlendMode(this._globalCompositeOperation);
}
this.clip = function(fillRule) {
this._commitSubpath();
var clip = this._currentPath.copy();
if (fillRule && fillRule.toLowerCase() === 'evenodd') {
clip.setFillType(CanvasKit.FillType.EvenOdd);
} else {
clip.setFillType(CanvasKit.FillType.Winding);
}
this._canvas.clipPath(clip, CanvasKit.ClipOp.Intersect, true);
}
this.closePath = function() {
if (this._currentSubpath) {
this._currentSubpath.close();
@ -570,6 +752,24 @@
}
}
this.createLinearGradient = function(x1, y1, x2, y2) {
if (!allAreFinite(arguments)) {
return;
}
var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
this._gradients.push(lcg);
return lcg;
}
this.createRadialGradient = function(x1, y1, r1, x2, y2, r2) {
if (!allAreFinite(arguments)) {
return;
}
var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
this._gradients.push(rcg);
return rcg;
}
this._commitSubpath = function () {
if (this._currentSubpath) {
this._currentPath.addPath(this._currentSubpath, false);
@ -612,8 +812,13 @@
this._fillPaint = function() {
var paint = this._paint.copy();
paint.setStyle(CanvasKit.PaintStyle.Fill);
var alphaColor = CanvasKit.multiplyByAlpha(this._fillColor, this._globalAlpha);
paint.setColor(alphaColor);
if (Number.isInteger(this._fillStyle)) {
var alphaColor = CanvasKit.multiplyByAlpha(this._fillStyle, this._globalAlpha);
paint.setColor(alphaColor);
} else {
var gradient = this._fillStyle._getShader(this._currentTransform, this._globalAlpha);
paint.setShader(gradient);
}
paint.dispose = function() {
// If there are some helper effects in the future, clean them up
@ -756,12 +961,11 @@
return;
}
this._currentTransform = newState.ctm;
// TODO(kjlubick): clipping region
this._lineDashList = newState.ldl;
this._strokeWidth = newState.sw;
this._paint.setStrokeWidth(this._strokeWidth);
this._strokeColor = newState.sc;
this._fillColor = newState.fc;
this._strokeStyle = newState.ss;
this._fillStyle = newState.fs;
this._paint.setStrokeCap(newState.cap);
this._paint.setStrokeJoin(newState.jn);
this._paint.setStrokeMiter(newState.mtr);
@ -773,7 +977,10 @@
this._globalCompositeOperation = newState.gco;
this._paint.setBlendMode(this._globalCompositeOperation);
this._lineDashOffset = newState.ldo;
//TODO: filter, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
//TODO: font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
// restores the clip
this._canvas.restore();
}
this.rotate = function(radians, px, py) {
@ -783,13 +990,26 @@
}
this.save = function() {
if (this._fillStyle._copy) {
var fs = this._fillStyle._copy();
this._gradients.push(fs);
} else {
var fs = this._fillStyle;
}
if (this._strokeStyle._copy) {
var ss = this._strokeStyle._copy();
this._gradients.push(ss);
} else {
var ss = this._strokeStyle;
}
this._canvasStateStack.push({
ctm: this._currentTransform.slice(),
// TODO(kjlubick): clipping region
ldl: this._lineDashList.slice(),
sw: this._strokeWidth,
sc: this._strokeColor,
fc: this._fillColor,
ss: ss,
fs: fs,
cap: this._paint.getStrokeCap(),
jn: this._paint.getStrokeJoin(),
mtr: this._paint.getStrokeMiter(),
@ -800,8 +1020,10 @@
ga: this._globalAlpha,
ldo: this._lineDashOffset,
gco: this._globalCompositeOperation,
//TODO: filter, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
//TODO: font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
});
// Saves the clip
this._canvas.save();
}
this.scale = function(sx, sy) {
@ -877,8 +1099,14 @@
this._strokePaint = function() {
var paint = this._paint.copy();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
var alphaColor = CanvasKit.multiplyByAlpha(this._strokeColor, this._globalAlpha);
paint.setColor(alphaColor);
if (Number.isInteger(this._strokeStyle)) {
var alphaColor = CanvasKit.multiplyByAlpha(this._strokeStyle, this._globalAlpha);
paint.setColor(alphaColor);
} else {
var gradient = this._strokeStyle._getShader(this._currentTransform, this._globalAlpha);
paint.setShader(gradient);
}
// This is not in the spec, but it appears Chrome scales up
// the line width by some amount when stroking (and filling?).
var scaledWidth = this._strokeWidth * this._scalefactor();

View File

@ -434,7 +434,6 @@
}
CanvasKit.MakeRadialGradientShader = function(center, radius, colors, pos, mode, localMatrix, flags) {
// TODO: matrix and flags
var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
@ -456,6 +455,31 @@
return rgs;
}
CanvasKit.MakeTwoPointConicalGradientShader = function(start, startRadius, end, endRadius,
colors, pos, mode, localMatrix, flags) {
var colorPtr = copy1dArray(colors, CanvasKit.HEAP32);
var posPtr = copy1dArray(pos, CanvasKit.HEAPF32);
flags = flags || 0;
if (localMatrix) {
// Add perspective args if not provided.
if (localMatrix.length === 6) {
localMatrix.push(0, 0, 1);
}
var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
start, startRadius, end, endRadius,
colorPtr, posPtr, colors.length, mode, flags, localMatrix);
} else {
var rgs = CanvasKit._MakeTwoPointConicalGradientShader(
start, startRadius, end, endRadius,
colorPtr, posPtr, colors.length, mode, flags);
}
CanvasKit._free(colorPtr);
CanvasKit._free(posPtr);
return rgs;
}
CanvasKit.MakeSkVertices = function(mode, positions, textureCoordinates, colors,
boneIndices, boneWeights, indices) {
var positionPtr = copy2dArray(positions, CanvasKit.HEAPF32);

View File

@ -341,6 +341,47 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
}));
});
it('supports gradients, which respect clip/save/restore', function(done) {
LoadCanvasKit.then(catchException(done, () => {
multipleCanvasTest('gradients_clip', done, (canvas) => {
let ctx = canvas.getContext('2d');
var rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
rgradient.addColorStop(0, 'red');
rgradient.addColorStop(.7, 'white');
rgradient.addColorStop(1, 'blue');
ctx.fillStyle = rgradient;
ctx.globalAlpha = 0.7;
ctx.fillRect(0,0,600,600);
ctx.globalAlpha = 0.95;
ctx.beginPath();
ctx.arc(300, 100, 90, 0, Math.PI*1.66);
ctx.closePath();
ctx.strokeStyle = 'yellow';
ctx.lineWidth = 5;
ctx.stroke();
ctx.save();
ctx.clip();
var lgradient = ctx.createLinearGradient(200, 20, 420, 40);
lgradient.addColorStop(0, 'green');
lgradient.addColorStop(.5, 'cyan');
lgradient.addColorStop(1, 'orange');
ctx.fillStyle = lgradient;
ctx.fillRect(200, 30, 200, 300);
ctx.restore();
ctx.fillRect(550, 550, 40, 40);
});
}));
});
it('can read default properties', function(done) {
LoadCanvasKit.then(catchException(done, () => {
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
@ -359,7 +400,7 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
// Compare all the default values of the properties of skcanvas
// to the default values on the properties of a real canvas.
for( let attr of toTest) {
for(let attr of toTest) {
expect(skcontext[attr]).toBe(realContext[attr], attr);
}