[canvaskit] support globalAlpha and dashed strokes
This involves a refactor of how we deal with paint. Now, we have a source-of-truth paint that is copied and modified for each of fill/stroke/shadow. Adds preliminary support for blend modes. Most of what remains is text, images, and gradients. Bug: skia: Change-Id: I41806adeb7de4faa6c98a580c4f1de4e4a34a37d Reviewed-on: https://skia-review.googlesource.com/c/173223 Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
572fdcf08a
commit
12c0e50fda
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
#patheffect,#paths,#sk_drinks,#sk_party, #sk_legos, #sk_onboarding,
|
||||
#api1_c, #api2_c {
|
||||
img, canvas {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
@ -26,6 +26,8 @@
|
||||
<canvas id=api3_c width=300 height=300></canvas>
|
||||
<img id=api4 width=300 height=300>
|
||||
<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>
|
||||
|
||||
<h2> CanvasKit draws Paths to the browser</h2>
|
||||
<canvas id=vertex1 width=300 height=300></canvas>
|
||||
@ -48,8 +50,6 @@
|
||||
|
||||
<canvas id=nima_example width=300 height=300></canvas>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
@ -94,6 +94,7 @@
|
||||
CanvasAPI2(CanvasKit);
|
||||
CanvasAPI3(CanvasKit);
|
||||
CanvasAPI4(CanvasKit);
|
||||
CanvasAPI5(CanvasKit);
|
||||
|
||||
VertexAPI1(CanvasKit);
|
||||
VertexAPI2(CanvasKit, bonesImage);
|
||||
@ -607,11 +608,66 @@
|
||||
ctx.fillStyle = 'green';
|
||||
ctx.font = '16pt Arial';
|
||||
ctx.fillText('This should be shadowed', 20, 80);
|
||||
|
||||
}
|
||||
document.getElementById('api4').src = skcanvas.toDataURL();
|
||||
}
|
||||
|
||||
function CanvasAPI5(CanvasKit) {
|
||||
let skcanvas = CanvasKit.MakeCanvas(600, 600);
|
||||
let realCanvas = document.getElementById('api5_c');
|
||||
realCanvas.width = 600;
|
||||
realCanvas.height = 600;
|
||||
|
||||
for (let canvas of [skcanvas, realCanvas]) {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.scale(1.1, 1.1);
|
||||
ctx.translate(10, 10);
|
||||
// Shouldn't impact the fillRect calls
|
||||
ctx.setLineDash([5, 3]);
|
||||
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
|
||||
ctx.fillRect(20, 30, 100, 100);
|
||||
|
||||
ctx.globalAlpha = 0.81;
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
|
||||
ctx.fillRect(120, 30, 100, 100);
|
||||
// This shouldn't do anything
|
||||
ctx.globalAlpha = 0.1;
|
||||
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
|
||||
ctx.globalAlpha = 0.9;
|
||||
// Intentional no-op to check ordering
|
||||
ctx.clearRect(220, 30, 100, 100);
|
||||
ctx.fillRect(220, 30, 100, 100);
|
||||
|
||||
ctx.fillRect(320, 30, 100, 100);
|
||||
ctx.clearRect(330, 40, 80, 80);
|
||||
|
||||
ctx.strokeStyle = 'blue';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.setLineDash([5, 3]);
|
||||
ctx.strokeRect(20, 150, 100, 100);
|
||||
ctx.setLineDash([50, 30]);
|
||||
ctx.strokeRect(125, 150, 100, 100);
|
||||
ctx.lineDashOffset = 25;
|
||||
ctx.strokeRect(230, 150, 100, 100);
|
||||
ctx.setLineDash([2, 5, 9]);
|
||||
ctx.strokeRect(335, 150, 100, 100);
|
||||
|
||||
ctx.setLineDash([5, 2]);
|
||||
ctx.moveTo(336, 400);
|
||||
ctx.quadraticCurveTo(366, 488, 120, 450);
|
||||
ctx.lineTo(300, 400);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = '36pt Arial';
|
||||
ctx.strokeText('Dashed', 20, 350);
|
||||
ctx.fillText('Not Dashed', 20, 400);
|
||||
|
||||
}
|
||||
document.getElementById('api5').src = skcanvas.toDataURL();
|
||||
}
|
||||
|
||||
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
|
||||
if (!CanvasKit || !nimaFile || !nimaTexture) {
|
||||
return;
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include "SkColor.h"
|
||||
#include "SkCornerPathEffect.h"
|
||||
#include "SkDashPathEffect.h"
|
||||
#include "SkDashPathEffect.h"
|
||||
#include "SkData.h"
|
||||
#include "SkDiscretePathEffect.h"
|
||||
#include "SkEncodedImageFormat.h"
|
||||
@ -496,6 +495,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
SkPaint p(self);
|
||||
return p;
|
||||
}))
|
||||
.function("getBlendMode", &SkPaint::getBlendMode)
|
||||
.function("getColor", optional_override([](SkPaint& self)->JSColor {
|
||||
// JS side gives us a signed int instead of an unsigned int for color
|
||||
// Add a optional_override to change it out.
|
||||
@ -513,6 +513,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
return self.measureText(text.c_str(), text.length());
|
||||
}))
|
||||
.function("setAntiAlias", &SkPaint::setAntiAlias)
|
||||
.function("setBlendMode", &SkPaint::setBlendMode)
|
||||
.function("setColor", optional_override([](SkPaint& self, JSColor color)->void {
|
||||
// JS side gives us a signed int instead of an unsigned int for color
|
||||
// Add a optional_override to change it out.
|
||||
@ -601,12 +602,14 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.function("bounds", &SkVertices::bounds)
|
||||
.function("mode", &SkVertices::mode)
|
||||
.function("uniqueID", &SkVertices::uniqueID)
|
||||
#ifdef SK_DEBUG
|
||||
.function("dumpPositions", optional_override([](SkVertices& self)->void {
|
||||
auto pos = self.positions();
|
||||
for(int i = 0; i< self.vertexCount(); i++) {
|
||||
SkDebugf("position[%d] = (%f, %f)\n", i, pos[i].x(), pos[i].y());
|
||||
}
|
||||
}))
|
||||
#endif
|
||||
.function("vertexCount", &SkVertices::vertexCount);
|
||||
|
||||
|
||||
|
@ -40,9 +40,10 @@ var CanvasKit = {
|
||||
MakeSurface: function() {},
|
||||
MakeWebGLCanvasSurface: function() {},
|
||||
currentContext: function() {},
|
||||
getSkDataBytes: function() {},
|
||||
getColorComponents: function() {},
|
||||
getSkDataBytes: function() {},
|
||||
initFonts: function() {},
|
||||
multiplyByAlpha: function() {},
|
||||
setCurrentContext: function() {},
|
||||
|
||||
// private API (i.e. things declared in the bindings that we use
|
||||
@ -79,8 +80,10 @@ var CanvasKit = {
|
||||
clear: function() {},
|
||||
drawPaint: function() {},
|
||||
drawPath: function() {},
|
||||
drawText: function() {},
|
||||
drawRect: function() {},
|
||||
drawShadow: function() {},
|
||||
drawText: function() {},
|
||||
drawVertices: function() {},
|
||||
flush: function() {},
|
||||
rotate: function() {},
|
||||
save: function() {},
|
||||
@ -113,6 +116,7 @@ var CanvasKit = {
|
||||
// public API (from C++ bindings)
|
||||
/** @return {CanvasKit.SkPaint} */
|
||||
copy: function() {},
|
||||
getBlendMode: function() {},
|
||||
getColor: function() {},
|
||||
getStrokeCap: function() {},
|
||||
getStrokeJoin: function() {},
|
||||
@ -121,6 +125,7 @@ var CanvasKit = {
|
||||
getTextSize: function() {},
|
||||
measureText: function() {},
|
||||
setAntiAlias: function() {},
|
||||
setBlendMode: function() {},
|
||||
setColor: function() {},
|
||||
setMaskFilter: function() {},
|
||||
setPathEffect: function() {},
|
||||
@ -196,8 +201,10 @@ var CanvasKit = {
|
||||
|
||||
SkVertices: {
|
||||
// public API (from C++ bindings)
|
||||
/** @return {CanvasKit.SkVertices} */
|
||||
applyBones: function() {},
|
||||
bounds: function() {},
|
||||
mode: function() {},
|
||||
uniqueID: function() {},
|
||||
vertexCount: function() {},
|
||||
|
||||
// private API
|
||||
/** @return {CanvasKit.SkVertices} */
|
||||
@ -337,6 +344,7 @@ CanvasKit.SkPath.prototype.trim = function() {};
|
||||
CanvasKit.SkSurface.prototype.flush = function() {};
|
||||
CanvasKit.SkSurface.prototype.dispose = function() {};
|
||||
|
||||
/** @return {CanvasKit.SkVertices} */
|
||||
CanvasKit.SkVertices.prototype.applyBones = function() {};
|
||||
|
||||
CanvasKit.SkImage.prototype.encodeToData = function() {};
|
||||
@ -360,11 +368,14 @@ CanvasRenderingContext2D.prototype.arcTo = function() {};
|
||||
CanvasRenderingContext2D.prototype.beginPath = function() {};
|
||||
CanvasRenderingContext2D.prototype.bezierCurveTo = function() {};
|
||||
CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
|
||||
CanvasRenderingContext2D.prototype.clearRect = function() {};
|
||||
CanvasRenderingContext2D.prototype.closePath = function() {};
|
||||
CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
|
||||
CanvasRenderingContext2D.prototype.ellipse = function() {};
|
||||
CanvasRenderingContext2D.prototype.fill = function() {};
|
||||
CanvasRenderingContext2D.prototype.fillRect = function() {};
|
||||
CanvasRenderingContext2D.prototype.fillText = function() {};
|
||||
CanvasRenderingContext2D.prototype.getLineDash = function() {};
|
||||
CanvasRenderingContext2D.prototype.lineTo = function() {};
|
||||
CanvasRenderingContext2D.prototype.measureText = function() {};
|
||||
CanvasRenderingContext2D.prototype.moveTo = function() {};
|
||||
@ -377,12 +388,15 @@ CanvasRenderingContext2D.prototype.rotate = function() {};
|
||||
CanvasRenderingContext2D.prototype.save = function() {};
|
||||
CanvasRenderingContext2D.prototype.scale = function() {};
|
||||
CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
|
||||
CanvasRenderingContext2D.prototype.setLineDash = function() {};
|
||||
CanvasRenderingContext2D.prototype.setTransform = function() {};
|
||||
CanvasRenderingContext2D.prototype.stroke = function() {};
|
||||
CanvasRenderingContext2D.prototype.strokeRect = 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() {};
|
||||
|
||||
var DOMMatrix = {};
|
@ -28,4 +28,23 @@
|
||||
((color >> 24) & 0xFF) / 255,
|
||||
]
|
||||
}
|
||||
|
||||
CanvasKit.multiplyByAlpha = function(color, alpha) {
|
||||
if (alpha === 1) {
|
||||
return color;
|
||||
}
|
||||
// extract as int from 0 to 255
|
||||
var a = (color >> 24) & 0xFF;
|
||||
a *= alpha;
|
||||
// mask off the old alpha
|
||||
color &= 0xFFFFFF;
|
||||
return clamp(a) << 24 | color;
|
||||
|
||||
}
|
||||
}(Module)); // When this file is loaded in, the high level object is "Module";
|
||||
|
||||
// See https://stackoverflow.com/a/31090240
|
||||
// This contraption keeps closure from minifying away the check
|
||||
// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
|
||||
// Defined outside any scopes to make it available in all files.
|
||||
var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
|
||||
|
@ -4,14 +4,9 @@
|
||||
// SkCanvas).
|
||||
(function(CanvasKit) {
|
||||
|
||||
// See https://stackoverflow.com/a/31090240
|
||||
// This contraption keeps closure from minifying away the check
|
||||
// if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
|
||||
var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
|
||||
|
||||
function argsAreFinite(args) {
|
||||
function allAreFinite(args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (!Number.isFinite(args[0])) {
|
||||
if (!Number.isFinite(args[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -75,17 +70,26 @@
|
||||
this._canvas = skcanvas;
|
||||
this._paint = new CanvasKit.SkPaint();
|
||||
this._paint.setAntiAlias(true);
|
||||
this._paint.setStrokeWidth(1);
|
||||
|
||||
this._paint.setStrokeMiter(10);
|
||||
this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
|
||||
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
|
||||
|
||||
this._strokeColor = CanvasKit.BLACK;
|
||||
this._fillColor = CanvasKit.BLACK;
|
||||
this._shadowBlur = 0;
|
||||
this._shadowColor = CanvasKit.TRANSPARENT;
|
||||
this._shadowOffsetX = 0;
|
||||
this._shadowOffsetY = 0;
|
||||
this._strokeColor = CanvasKit.BLACK;
|
||||
this._fillColor = CanvasKit.BLACK;
|
||||
this._shadowBlur = 0;
|
||||
this._shadowColor = CanvasKit.TRANSPARENT;
|
||||
this._shadowOffsetX = 0;
|
||||
this._shadowOffsetY = 0;
|
||||
this._globalAlpha = 1;
|
||||
this._strokeWidth = 1;
|
||||
this._lineDashOffset = 0;
|
||||
this._lineDashList = [];
|
||||
// aka SkBlendMode
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
|
||||
|
||||
this._paint.setStrokeWidth(this._strokeWidth);
|
||||
this._paint.setBlendMode(this._globalCompositeOperation);
|
||||
|
||||
this._currentPath = new CanvasKit.SkPath();
|
||||
this._currentSubpath = null;
|
||||
@ -101,6 +105,44 @@
|
||||
// by the surface of which it is based.
|
||||
}
|
||||
|
||||
// This always accepts DOMMatrix/SVGMatrix or any other
|
||||
// object that has properties a,b,c,d,e,f defined.
|
||||
// It will return DOMMatrix if the constructor is defined
|
||||
// (e.g. we are in a browser), otherwise it will return a
|
||||
// flattened 9-element matrix. That 9 element matrix
|
||||
// can also be accepted on all platforms (somewhat
|
||||
// contrary to the specification, but at least keeps it usable
|
||||
// on Node)
|
||||
Object.defineProperty(this, 'currentTransform', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
if (isNode) {
|
||||
return this._currentTransform.slice();
|
||||
} else {
|
||||
// a-f aren't in the order as we have them. a-f are in "SVG order".
|
||||
var m = new DOMMatrix();
|
||||
m.a = this._currentTransform[0];
|
||||
m.c = this._currentTransform[1];
|
||||
m.e = this._currentTransform[2];
|
||||
m.b = this._currentTransform[3];
|
||||
m.d = this._currentTransform[4];
|
||||
m.f = this._currentTransform[5];
|
||||
return m;
|
||||
}
|
||||
},
|
||||
set: function(matrix) {
|
||||
if (matrix.a) {
|
||||
// if we see a property named 'a', guess that b-f will
|
||||
// also be there.
|
||||
this._currentTransform = [matrix.a, matrix.c, matrix.e,
|
||||
matrix.b, matrix.d, matrix.f,
|
||||
0, 0, 1];
|
||||
} else if (matrix.length === 9) {
|
||||
this._currentTransform = matrix;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'fillStyle', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
@ -124,6 +166,185 @@
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'globalAlpha', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._globalAlpha;
|
||||
},
|
||||
set: function(newAlpha) {
|
||||
// ignore invalid values, as per the spec
|
||||
if (!isFinite(newAlpha) || newAlpha < 0 || newAlpha > 1) {
|
||||
return;
|
||||
}
|
||||
this._globalAlpha = newAlpha;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'globalCompositeOperation', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
switch (this._globalCompositeOperation) {
|
||||
// composite-mode
|
||||
case CanvasKit.BlendMode.SrcOver:
|
||||
return 'source-over';
|
||||
case CanvasKit.BlendMode.DstOver:
|
||||
return 'destination-over';
|
||||
case CanvasKit.BlendMode.Src:
|
||||
return 'copy';
|
||||
case CanvasKit.BlendMode.Dst:
|
||||
return 'destination';
|
||||
case CanvasKit.BlendMode.Clear:
|
||||
return 'clear';
|
||||
case CanvasKit.BlendMode.SrcIn:
|
||||
return 'source-in';
|
||||
case CanvasKit.BlendMode.DstIn:
|
||||
return 'destination-in';
|
||||
case CanvasKit.BlendMode.SrcOut:
|
||||
return 'source-out';
|
||||
case CanvasKit.BlendMode.DstOut:
|
||||
return 'destination-out';
|
||||
case CanvasKit.BlendMode.SrcATop:
|
||||
return 'source-atop';
|
||||
case CanvasKit.BlendMode.DstATop:
|
||||
return 'destination-atop';
|
||||
case CanvasKit.BlendMode.Xor:
|
||||
return 'xor';
|
||||
case CanvasKit.BlendMode.Plus:
|
||||
return 'lighter';
|
||||
|
||||
case CanvasKit.BlendMode.Multiply:
|
||||
return 'multiply';
|
||||
case CanvasKit.BlendMode.Screen:
|
||||
return 'screen';
|
||||
case CanvasKit.BlendMode.Overlay:
|
||||
return 'overlay';
|
||||
case CanvasKit.BlendMode.Darken:
|
||||
return 'darken';
|
||||
case CanvasKit.BlendMode.Lighten:
|
||||
return 'lighten';
|
||||
case CanvasKit.BlendMode.ColorDodge:
|
||||
return 'color-dodge';
|
||||
case CanvasKit.BlendMode.ColorBurn:
|
||||
return 'color-burn';
|
||||
case CanvasKit.BlendMode.HardLight:
|
||||
return 'hard-light';
|
||||
case CanvasKit.BlendMode.SoftLight:
|
||||
return 'soft-light';
|
||||
case CanvasKit.BlendMode.Difference:
|
||||
return 'difference';
|
||||
case CanvasKit.BlendMode.Exclusion:
|
||||
return 'exclusion';
|
||||
case CanvasKit.BlendMode.Hue:
|
||||
return 'hue';
|
||||
case CanvasKit.BlendMode.Saturation:
|
||||
return 'saturation';
|
||||
case CanvasKit.BlendMode.Color:
|
||||
return 'color';
|
||||
case CanvasKit.BlendMode.Luminosity:
|
||||
return 'luminosity';
|
||||
}
|
||||
},
|
||||
set: function(newMode) {
|
||||
switch (newMode) {
|
||||
// composite-mode
|
||||
case 'source-over':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
|
||||
break;
|
||||
case 'destination-over':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.DstOver;
|
||||
break;
|
||||
case 'copy':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Src;
|
||||
break;
|
||||
case 'destination':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Dst;
|
||||
break;
|
||||
case 'clear':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Clear;
|
||||
break;
|
||||
case 'source-in':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SrcIn;
|
||||
break;
|
||||
case 'destination-in':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.DstIn;
|
||||
break;
|
||||
case 'source-out':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SrcOut;
|
||||
break;
|
||||
case 'destination-out':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.DstOut;
|
||||
break;
|
||||
case 'source-atop':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SrcATop;
|
||||
break;
|
||||
case 'destination-atop':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.DstATop;
|
||||
break;
|
||||
case 'xor':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Xor;
|
||||
break;
|
||||
case 'lighter':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
|
||||
break;
|
||||
case 'plus-lighter':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
|
||||
break;
|
||||
case 'plus-darker':
|
||||
throw 'plus-darker is not supported';
|
||||
|
||||
// blend-mode
|
||||
case 'multiply':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Multiply;
|
||||
break;
|
||||
case 'screen':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Screen;
|
||||
break;
|
||||
case 'overlay':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Overlay;
|
||||
break;
|
||||
case 'darken':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Darken;
|
||||
break;
|
||||
case 'lighten':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Lighten;
|
||||
break;
|
||||
case 'color-dodge':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.ColorDodge;
|
||||
break;
|
||||
case 'color-burn':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.ColorBurn;
|
||||
break;
|
||||
case 'hard-light':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.HardLight;
|
||||
break;
|
||||
case 'soft-light':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.SoftLight;
|
||||
break;
|
||||
case 'difference':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Difference;
|
||||
break;
|
||||
case 'exclusion':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Exclusion;
|
||||
break;
|
||||
case 'hue':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Hue;
|
||||
break;
|
||||
case 'saturation':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Saturation;
|
||||
break;
|
||||
case 'color':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Color;
|
||||
break;
|
||||
case 'luminosity':
|
||||
this._globalCompositeOperation = CanvasKit.BlendMode.Luminosity;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this._paint.setBlendMode(this._globalCompositeOperation);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineCap', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
@ -151,6 +372,19 @@
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineDashOffset', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._lineDashOffset;
|
||||
},
|
||||
set: function(newOffset) {
|
||||
if (!isFinite(newOffset)) {
|
||||
return;
|
||||
}
|
||||
this._lineDashOffset = newOffset;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineJoin', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
@ -188,6 +422,7 @@
|
||||
// Spec says to ignore NaN/Inf/0/negative values
|
||||
return;
|
||||
}
|
||||
this._strokeWidth = newWidth;
|
||||
this._paint.setStrokeWidth(newWidth);
|
||||
}
|
||||
});
|
||||
@ -273,7 +508,7 @@
|
||||
}
|
||||
|
||||
this.arcTo = function(x1, y1, x2, y2, radius) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
if (radius < 0) {
|
||||
@ -301,7 +536,7 @@
|
||||
}
|
||||
|
||||
this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
var pts = [cp1x, cp1y, cp2x, cp2y, x, y];
|
||||
@ -318,6 +553,15 @@
|
||||
this._currentSubpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
}
|
||||
|
||||
this.clearRect = function(x, y, width, height) {
|
||||
this._canvas.setMatrix(this._currentTransform);
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
this._paint.setBlendMode(CanvasKit.BlendMode.Clear);
|
||||
this._canvas.drawRect(CanvasKit.LTRBRect(x, y, x+width, y+height), this._paint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
this._paint.setBlendMode(this._globalCompositeOperation);
|
||||
}
|
||||
|
||||
this.closePath = function() {
|
||||
if (this._currentSubpath) {
|
||||
this._currentSubpath.close();
|
||||
@ -336,7 +580,7 @@
|
||||
|
||||
this.ellipse = function(x, y, radiusX, radiusY, rotation,
|
||||
startAngle, endAngle, ccw) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
if (radiusX < 0 || radiusY < 0) {
|
||||
@ -362,17 +606,29 @@
|
||||
temp.delete();
|
||||
}
|
||||
|
||||
// A helper to copy the current paint, ready for filling
|
||||
// This applies the global alpha.
|
||||
// Call dispose() after to clean up.
|
||||
this._fillPaint = function() {
|
||||
var paint = this._paint.copy();
|
||||
paint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
var alphaColor = CanvasKit.multiplyByAlpha(this._fillColor, this._globalAlpha);
|
||||
paint.setColor(alphaColor);
|
||||
|
||||
paint.dispose = function() {
|
||||
// If there are some helper effects in the future, clean them up
|
||||
// here. In any case, we have .dispose() to make _fillPaint behave
|
||||
// like _strokePaint and _shadowPaint.
|
||||
this.delete();
|
||||
}
|
||||
return paint;
|
||||
}
|
||||
|
||||
this.fill = function() {
|
||||
this._commitSubpath();
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
this._paint.setColor(this._fillColor);
|
||||
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);
|
||||
var fillPaint = this._fillPaint();
|
||||
|
||||
var shadowPaint = this._shadowPaint();
|
||||
var shadowPaint = this._shadowPaint(fillPaint);
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
@ -384,16 +640,22 @@
|
||||
shadowPaint.dispose();
|
||||
}
|
||||
|
||||
this._canvas.drawPath(this._currentPath, this._paint);
|
||||
// set stroke width back to original size:
|
||||
this._paint.setStrokeWidth(orig);
|
||||
this._canvas.drawPath(this._currentPath, fillPaint);
|
||||
fillPaint.dispose();
|
||||
}
|
||||
|
||||
this.fillRect = function(x, y, width, height) {
|
||||
var fillPaint = this._fillPaint();
|
||||
this._canvas.setMatrix(this._currentTransform);
|
||||
this._canvas.drawRect(CanvasKit.LTRBRect(x, y, x+width, y+height), fillPaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
fillPaint.dispose();
|
||||
}
|
||||
|
||||
this.fillText = function(text, x, y, maxWidth) {
|
||||
// TODO do something with maxWidth, probably involving measure
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Fill);
|
||||
this._paint.setColor(this._fillColor);
|
||||
var shadowPaint = this._shadowPaint();
|
||||
var fillPaint = this._fillPaint()
|
||||
var shadowPaint = this._shadowPaint(fillPaint);
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
@ -405,12 +667,17 @@
|
||||
// Don't need to setMatrix back, it will be handled by the next few lines.
|
||||
}
|
||||
this._canvas.setMatrix(this._currentTransform);
|
||||
this._canvas.drawText(text, x, y, this._paint);
|
||||
this._canvas.drawText(text, x, y, fillPaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
fillPaint.dispose();
|
||||
}
|
||||
|
||||
this.getLineDash = function() {
|
||||
return this._lineDashList.slice();
|
||||
}
|
||||
|
||||
this.lineTo = function(x, y) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
var pts = [x, y];
|
||||
@ -433,7 +700,7 @@
|
||||
}
|
||||
|
||||
this.moveTo = function(x, y) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
var pts = [x, y];
|
||||
@ -450,7 +717,7 @@
|
||||
}
|
||||
|
||||
this.quadraticCurveTo = function(cpx, cpy, x, y) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
var pts = [cpx, cpy, x, y];
|
||||
@ -466,7 +733,7 @@
|
||||
}
|
||||
|
||||
this.rect = function(x, y, width, height) {
|
||||
if (!argsAreFinite(arguments)) {
|
||||
if (!allAreFinite(arguments)) {
|
||||
return;
|
||||
}
|
||||
var pts = [x, y];
|
||||
@ -490,8 +757,9 @@
|
||||
}
|
||||
this._currentTransform = newState.ctm;
|
||||
// TODO(kjlubick): clipping region
|
||||
// TODO(kjlubick): dash list
|
||||
this._paint.setStrokeWidth(newState.sw);
|
||||
this._lineDashList = newState.ldl;
|
||||
this._strokeWidth = newState.sw;
|
||||
this._paint.setStrokeWidth(this._strokeWidth);
|
||||
this._strokeColor = newState.sc;
|
||||
this._fillColor = newState.fc;
|
||||
this._paint.setStrokeCap(newState.cap);
|
||||
@ -501,7 +769,11 @@
|
||||
this._shadowOffsetY = newState.soy;
|
||||
this._shadowBlur = newState.sb;
|
||||
this._shadowColor = newState.shc;
|
||||
//TODO: globalAlpha, lineDashOffset, filter, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
this._globalAlpha = newState.ga;
|
||||
this._globalCompositeOperation = newState.gco;
|
||||
this._paint.setBlendMode(this._globalCompositeOperation);
|
||||
this._lineDashOffset = newState.ldo;
|
||||
//TODO: filter, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
}
|
||||
|
||||
this.rotate = function(radians, px, py) {
|
||||
@ -514,8 +786,8 @@
|
||||
this._canvasStateStack.push({
|
||||
ctm: this._currentTransform.slice(),
|
||||
// TODO(kjlubick): clipping region
|
||||
// TODO(kjlubick): dash list
|
||||
sw: this._paint.getStrokeWidth(),
|
||||
ldl: this._lineDashList.slice(),
|
||||
sw: this._strokeWidth,
|
||||
sc: this._strokeColor,
|
||||
fc: this._fillColor,
|
||||
cap: this._paint.getStrokeCap(),
|
||||
@ -525,7 +797,10 @@
|
||||
soy: this._shadowOffsetY,
|
||||
sb: this._shadowBlur,
|
||||
shc: this._shadowColor,
|
||||
//TODO: globalAlpha, lineDashOffset, filter, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
ga: this._globalAlpha,
|
||||
ldo: this._lineDashOffset,
|
||||
gco: this._globalCompositeOperation,
|
||||
//TODO: filter, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
});
|
||||
}
|
||||
|
||||
@ -544,6 +819,21 @@
|
||||
return (Math.abs(sx) + Math.abs(sy))/2;
|
||||
}
|
||||
|
||||
this.setLineDash = function(dashes) {
|
||||
for (var i = 0; i < dashes.length; i++) {
|
||||
if (!Number.isFinite(dashes[i]) || dashes[i] < 0) {
|
||||
SkDebug('dash list must have positive, finite values');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (dashes.length % 2 === 1) {
|
||||
// as per the spec, concatenate 2 copies of dashes
|
||||
// to give it an even number of elements.
|
||||
Array.prototype.push.apply(dashes, dashes);
|
||||
}
|
||||
this._lineDashList = dashes;
|
||||
}
|
||||
|
||||
this.setTransform = function(a, b, c, d, e, f) {
|
||||
this._currentTransform = [a, c, e,
|
||||
b, d, f,
|
||||
@ -551,11 +841,13 @@
|
||||
}
|
||||
|
||||
// Returns the shadow paint for the current settings or null if there
|
||||
// should be no shadow. This ends up being a copy of the current
|
||||
// should be no shadow. This ends up being a copy of the given
|
||||
// paint with a blur maskfilter and the correct color.
|
||||
this._shadowPaint = function() {
|
||||
this._shadowPaint = function(basePaint) {
|
||||
// multiply first to see if the alpha channel goes to 0 after multiplication.
|
||||
var alphaColor = CanvasKit.multiplyByAlpha(this._shadowColor, this._globalAlpha);
|
||||
// if alpha is zero, no shadows
|
||||
if (!CanvasKit.getColorComponents(this._shadowColor)[3]) {
|
||||
if (!CanvasKit.getColorComponents(alphaColor)[3]) {
|
||||
return null;
|
||||
}
|
||||
// one of these must also be non-zero (otherwise the shadow is
|
||||
@ -563,8 +855,8 @@
|
||||
if (!(this._shadowBlur || this._shadowOffsetY || this._shadowOffsetX)) {
|
||||
return null;
|
||||
}
|
||||
var shadowPaint = this._paint.copy();
|
||||
shadowPaint.setColor(this._shadowColor);
|
||||
var shadowPaint = basePaint.copy();
|
||||
shadowPaint.setColor(alphaColor);
|
||||
var blurEffect = CanvasKit.MakeBlurMaskFilter(CanvasKit.BlurStyle.Normal,
|
||||
Math.max(1, this._shadowBlur/2), // very little blur when < 1
|
||||
false);
|
||||
@ -579,18 +871,36 @@
|
||||
return shadowPaint;
|
||||
}
|
||||
|
||||
this.stroke = function() {
|
||||
this._commitSubpath();
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
this._paint.setColor(this._strokeColor);
|
||||
var orig = this._paint.getStrokeWidth();
|
||||
// A helper to get a copy of the current paint, ready for stroking.
|
||||
// This applies the global alpha and the dashedness.
|
||||
// Call dispose() after to clean up.
|
||||
this._strokePaint = function() {
|
||||
var paint = this._paint.copy();
|
||||
paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
var alphaColor = CanvasKit.multiplyByAlpha(this._strokeColor, this._globalAlpha);
|
||||
paint.setColor(alphaColor);
|
||||
// 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);
|
||||
var scaledWidth = this._strokeWidth * this._scalefactor();
|
||||
paint.setStrokeWidth(scaledWidth);
|
||||
|
||||
var shadowPaint = this._shadowPaint();
|
||||
if (this._lineDashList.length) {
|
||||
var dashedEffect = CanvasKit.MakeSkDashPathEffect(this._lineDashList, this._lineDashOffset);
|
||||
paint.setPathEffect(dashedEffect);
|
||||
}
|
||||
|
||||
paint.dispose = function() {
|
||||
dashedEffect && dashedEffect.delete();
|
||||
this.delete();
|
||||
}
|
||||
return paint;
|
||||
}
|
||||
|
||||
this.stroke = function() {
|
||||
this._commitSubpath();
|
||||
var strokePaint = this._strokePaint();
|
||||
|
||||
var shadowPaint = this._shadowPaint(strokePaint);
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
@ -602,16 +912,23 @@
|
||||
shadowPaint.dispose();
|
||||
}
|
||||
|
||||
this._canvas.drawPath(this._currentPath, this._paint);
|
||||
// set stroke width back to original size:
|
||||
this._paint.setStrokeWidth(orig);
|
||||
this._canvas.drawPath(this._currentPath, strokePaint);
|
||||
strokePaint.dispose();
|
||||
}
|
||||
|
||||
this.strokeRect = function(x, y, width, height) {
|
||||
var strokePaint = this._strokePaint();
|
||||
this._canvas.setMatrix(this._currentTransform);
|
||||
this._canvas.drawRect(CanvasKit.LTRBRect(x, y, x+width, y+height), strokePaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
strokePaint.dispose();
|
||||
}
|
||||
|
||||
this.strokeText = function(text, x, y, maxWidth) {
|
||||
// TODO do something with maxWidth, probably involving measure
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
this._paint.setColor(this._strokeColor);
|
||||
var shadowPaint = this._shadowPaint();
|
||||
var strokePaint = this._strokePaint();
|
||||
|
||||
var shadowPaint = this._shadowPaint(strokePaint);
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
@ -623,8 +940,9 @@
|
||||
// Don't need to setMatrix back, it will be handled by the next few lines.
|
||||
}
|
||||
this._canvas.setMatrix(this._currentTransform);
|
||||
this._canvas.drawText(text, x, y, this._paint);
|
||||
this._canvas.drawText(text, x, y, strokePaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
strokePaint.dispose();
|
||||
}
|
||||
|
||||
this.translate = function(dx, dy) {
|
||||
|
@ -91,6 +91,43 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('can multiply colors by alpha', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const multiplyByAlpha = CanvasKit.multiplyByAlpha;
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
inColor: CanvasKit.Color(102, 51, 153, 1.0),
|
||||
inAlpha: 1.0,
|
||||
outColor: CanvasKit.Color(102, 51, 153, 1.0),
|
||||
},
|
||||
{
|
||||
inColor: CanvasKit.Color(102, 51, 153, 1.0),
|
||||
inAlpha: 0.8,
|
||||
outColor: CanvasKit.Color(102, 51, 153, 0.8),
|
||||
},
|
||||
{
|
||||
inColor: CanvasKit.Color(102, 51, 153, 0.8),
|
||||
inAlpha: 0.7,
|
||||
outColor: CanvasKit.Color(102, 51, 153, 0.56),
|
||||
},
|
||||
{
|
||||
inColor: CanvasKit.Color(102, 51, 153, 0.8),
|
||||
inAlpha: 1000,
|
||||
outColor: CanvasKit.Color(102, 51, 153, 1.0),
|
||||
},
|
||||
];
|
||||
|
||||
for (let tc of testCases) {
|
||||
// Print out the test case if the two don't match.
|
||||
expect(multiplyByAlpha(tc.inColor, tc.inAlpha))
|
||||
.toBe(tc.outColor, JSON.stringify(tc));
|
||||
}
|
||||
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}); // end describe('color string parsing')
|
||||
|
||||
function multipleCanvasTest(testname, done, test) {
|
||||
@ -253,6 +290,57 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
}));
|
||||
});
|
||||
|
||||
it('fills/strokes rects and supports some global settings', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
multipleCanvasTest('global_dashed_rects', done, (canvas) => {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.scale(1.1, 1.1);
|
||||
ctx.translate(10, 10);
|
||||
// Shouldn't impact the fillRect calls
|
||||
ctx.setLineDash([5, 3]);
|
||||
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 0.81)';
|
||||
ctx.fillRect(20, 30, 100, 100);
|
||||
|
||||
ctx.globalAlpha = 0.81;
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 1.0)';
|
||||
ctx.fillRect(120, 30, 100, 100);
|
||||
// This shouldn't do anything
|
||||
ctx.globalAlpha = 0.1;
|
||||
|
||||
ctx.fillStyle = 'rgba(200, 0, 100, 0.9)';
|
||||
ctx.globalAlpha = 0.9;
|
||||
// Intentional no-op to check ordering
|
||||
ctx.clearRect(220, 30, 100, 100);
|
||||
ctx.fillRect(220, 30, 100, 100);
|
||||
|
||||
ctx.fillRect(320, 30, 100, 100);
|
||||
ctx.clearRect(330, 40, 80, 80);
|
||||
|
||||
ctx.strokeStyle = 'blue';
|
||||
ctx.lineWidth = 3;
|
||||
ctx.setLineDash([5, 3]);
|
||||
ctx.strokeRect(20, 150, 100, 100);
|
||||
ctx.setLineDash([50, 30]);
|
||||
ctx.strokeRect(125, 150, 100, 100);
|
||||
ctx.lineDashOffset = 25;
|
||||
ctx.strokeRect(230, 150, 100, 100);
|
||||
ctx.setLineDash([2, 5, 9]);
|
||||
ctx.strokeRect(335, 150, 100, 100);
|
||||
|
||||
ctx.setLineDash([5, 2]);
|
||||
ctx.moveTo(336, 400);
|
||||
ctx.quadraticCurveTo(366, 488, 120, 450);
|
||||
ctx.lineTo(300, 400);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = '36pt Arial';
|
||||
ctx.strokeText('Dashed', 20, 350);
|
||||
ctx.fillText('Not Dashed', 20, 400);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('can read default properties', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
@ -265,8 +353,12 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
|
||||
const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
|
||||
'lineJoin', 'miterLimit', 'shadowOffsetY',
|
||||
'shadowBlur', 'shadowColor', 'shadowOffsetX'];
|
||||
'shadowBlur', 'shadowColor', 'shadowOffsetX',
|
||||
'globalAlpha', 'globalCompositeOperation',
|
||||
'lineDashOffset'];
|
||||
|
||||
// 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) {
|
||||
expect(skcontext[attr]).toBe(realContext[attr], attr);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user