[canvaskit] Fix shadow bugs

This also adds a helper option to writing canvas2d spec tests
to see the CanvasKit option side by side with the real canvas
version.

Bug: skia:9940, skia:9947
Change-Id: Ia8fc4e1332d3896933b86291181bc3ba890d26ed
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/275618
Reviewed-by: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Kevin Lubick 2020-03-06 10:47:05 -05:00
parent 54de2fa48d
commit a093cffed2
3 changed files with 73 additions and 10 deletions

View File

@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `MakeTwoPointConicalGradientShader` will be renamed soon. Calls can be replaced with
`SkShader.MakeTwoPointConicalGradient`.
### Fixed
- Shadows are properly draw on fillRect and strokeRect in the canvas2d emulation layer.
- Shadow offsets properly ignore the CTM in the canvas2d emulation layer.
## [0.13.0] - 2020-02-28
### Deprecated

View File

@ -674,7 +674,7 @@ function CanvasRenderingContext2D(skcanvas) {
var shadowPaint = this._shadowPaint(fillPaint);
if (shadowPaint) {
this._canvas.save();
this._canvas.concat(this._shadowOffsetMatrix());
this._applyShadowOffsetMatrix();
this._canvas.drawPath(path, shadowPaint);
this._canvas.restore();
shadowPaint.dispose();
@ -685,6 +685,16 @@ function CanvasRenderingContext2D(skcanvas) {
this.fillRect = function(x, y, width, height) {
var fillPaint = this._fillPaint();
var shadowPaint = this._shadowPaint(fillPaint);
if (shadowPaint) {
this._canvas.save();
this._applyShadowOffsetMatrix();
this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), shadowPaint);
this._canvas.restore();
shadowPaint.dispose();
}
this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), fillPaint);
fillPaint.dispose();
}
@ -697,7 +707,7 @@ function CanvasRenderingContext2D(skcanvas) {
var shadowPaint = this._shadowPaint(fillPaint);
if (shadowPaint) {
this._canvas.save();
this._canvas.concat(this._shadowOffsetMatrix());
this._applyShadowOffsetMatrix();
this._canvas.drawTextBlob(blob, x, y, shadowPaint);
this._canvas.restore();
shadowPaint.dispose();
@ -991,12 +1001,13 @@ function CanvasRenderingContext2D(skcanvas) {
this.transform(a, b, c, d, e, f);
}
// Returns the matrix representing the offset of the shadows. This unapplies
// the effects of the scale, which should not affect the shadow offsets.
this._shadowOffsetMatrix = function() {
var sx = this._currentTransform[0];
var sy = this._currentTransform[4];
return CanvasKit.SkMatrix.translated(this._shadowOffsetX/sx, this._shadowOffsetY/sy);
// We need to apply the shadowOffsets on the device coordinates, so we undo
// the CTM, apply the offsets, then re-apply the CTM.
this._applyShadowOffsetMatrix = function() {
var inverted = CanvasKit.SkMatrix.invert(this._currentTransform);
this._canvas.concat(inverted);
this._canvas.concat(CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY));
this._canvas.concat(this._currentTransform);
}
// Returns the shadow paint for the current settings or null if there
@ -1066,7 +1077,7 @@ function CanvasRenderingContext2D(skcanvas) {
var shadowPaint = this._shadowPaint(strokePaint);
if (shadowPaint) {
this._canvas.save();
this._canvas.concat(this._shadowOffsetMatrix());
this._applyShadowOffsetMatrix();
this._canvas.drawPath(path, shadowPaint);
this._canvas.restore();
shadowPaint.dispose();
@ -1078,6 +1089,15 @@ function CanvasRenderingContext2D(skcanvas) {
this.strokeRect = function(x, y, width, height) {
var strokePaint = this._strokePaint();
var shadowPaint = this._shadowPaint(strokePaint);
if (shadowPaint) {
this._canvas.save();
this._applyShadowOffsetMatrix();
this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), shadowPaint);
this._canvas.restore();
shadowPaint.dispose();
}
this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), strokePaint);
strokePaint.dispose();
}
@ -1090,7 +1110,7 @@ function CanvasRenderingContext2D(skcanvas) {
var shadowPaint = this._shadowPaint(strokePaint);
if (shadowPaint) {
this._canvas.save();
this._canvas.concat(this._shadowOffsetMatrix());
this._applyShadowOffsetMatrix();
this._canvas.drawTextBlob(blob, x, y, shadowPaint);
this._canvas.restore();
shadowPaint.dispose();

View File

@ -267,6 +267,18 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
realCanvas.width = CANVAS_WIDTH;
realCanvas.height = CANVAS_HEIGHT;
if (!done) {
console.log('debugging canvaskit');
test(realCanvas);
test(skcanvas);
const png = skcanvas.toDataURL();
const img = document.createElement('img');
document.body.appendChild(img);
img.src = png;
debugger;
return;
}
let promises = [];
for (let canvas of [skcanvas, realCanvas]) {
@ -611,6 +623,33 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
}));
});
it('apply shadows correctly when rotated', function(done) {
LoadCanvasKit.then(catchException(done, () => {
multipleCanvasTest('shadows_with_rotate_skbug_9947', done, (canvas) => {
const ctx = canvas.getContext('2d');
const angle = 240;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.save();
ctx.translate(80, 80);
ctx.rotate((angle * Math.PI) / 180);
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowColor = 'rgba(100,100,100,0.5)';
ctx.shadowBlur = 1;
ctx.fillStyle = 'black';
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.rect(-20, -20, 40, 40);
ctx.fill();
ctx.fillRect(30, 30, 40, 40);
ctx.strokeRect(30, -20, 40, 40);
ctx.fillText('text', -20, -30);
ctx.restore();
});
}));
});
it('can make patterns', function(done) {
let skImageData = null;
let htmlImage = null;