[canvaskit] Add shadow and save/restore support
This also exposes the ShadowUtils::drawShadow on Canvas, even though it wasn't what was needed to duplicate the Canvas effect. Bug: skia: Change-Id: I12276ef106244218e4827b7fcd7949c83cf13e5f Reviewed-on: https://skia-review.googlesource.com/c/172967 Reviewed-by: Kevin Lubick <kjlubick@google.com>
This commit is contained in:
parent
2a1848d2aa
commit
61ef7b2589
@ -24,6 +24,8 @@
|
||||
<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>
|
||||
<img id=api4 width=300 height=300>
|
||||
<canvas id=api4_c width=300 height=300></canvas>
|
||||
|
||||
<h2> CanvasKit draws Paths to the browser</h2>
|
||||
<canvas id=vertex1 width=300 height=300></canvas>
|
||||
@ -91,6 +93,7 @@
|
||||
CanvasAPI1(CanvasKit);
|
||||
CanvasAPI2(CanvasKit);
|
||||
CanvasAPI3(CanvasKit);
|
||||
CanvasAPI4(CanvasKit);
|
||||
|
||||
VertexAPI1(CanvasKit);
|
||||
VertexAPI2(CanvasKit, bonesImage);
|
||||
@ -556,6 +559,59 @@
|
||||
document.getElementById('api3').src = skcanvas.toDataURL();
|
||||
}
|
||||
|
||||
function CanvasAPI4(CanvasKit) {
|
||||
let skcanvas = CanvasKit.MakeCanvas(300, 300);
|
||||
let realCanvas = document.getElementById('api4_c');
|
||||
realCanvas.width = 300;
|
||||
realCanvas.height = 300;
|
||||
|
||||
for (let canvas of [skcanvas, realCanvas]) {
|
||||
let ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.fillStyle = '#CCC';
|
||||
ctx.shadowColor = 'rebeccapurple';
|
||||
ctx.shadowBlur = 1;
|
||||
ctx.shadowOffsetX = 3;
|
||||
ctx.shadowOffsetY = -8;
|
||||
ctx.rect(10, 10, 30, 30);
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#C00';
|
||||
ctx.fillStyle = '#00C';
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowColor = 'transparent';
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(36, 148);
|
||||
ctx.quadraticCurveTo(66, 188, 120, 136);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.shadowColor = '#993366AA';
|
||||
ctx.shadowOffsetX = 8;
|
||||
ctx.shadowBlur = 5;
|
||||
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.stroke();
|
||||
|
||||
ctx.fillStyle = 'green';
|
||||
ctx.font = '16pt Arial';
|
||||
ctx.fillText('This should be shadowed', 20, 80);
|
||||
|
||||
}
|
||||
document.getElementById('api4').src = skcanvas.toDataURL();
|
||||
}
|
||||
|
||||
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
|
||||
if (!CanvasKit || !nimaFile || !nimaTexture) {
|
||||
return;
|
||||
|
@ -22,9 +22,11 @@
|
||||
#include "SkDiscretePathEffect.h"
|
||||
#include "SkEncodedImageFormat.h"
|
||||
#include "SkFontMgr.h"
|
||||
#include "SkBlurTypes.h"
|
||||
#include "SkFontMgrPriv.h"
|
||||
#include "SkGradientShader.h"
|
||||
#include "SkImageShader.h"
|
||||
#include "SkMaskFilter.h"
|
||||
#include "SkPaint.h"
|
||||
#include "SkParsePath.h"
|
||||
#include "SkPath.h"
|
||||
@ -32,6 +34,7 @@
|
||||
#include "SkPathOps.h"
|
||||
#include "SkScalar.h"
|
||||
#include "SkShader.h"
|
||||
#include "SkShadowUtils.h"
|
||||
#include "SkString.h"
|
||||
#include "SkStrokeRec.h"
|
||||
#include "SkSurface.h"
|
||||
@ -349,6 +352,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
|
||||
function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
|
||||
function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
|
||||
function("MakeBlurMaskFilter", optional_override([](SkBlurStyle style, SkScalar sigma, bool respectCTM)->sk_sp<SkMaskFilter> {
|
||||
// Adds a little helper because emscripten doesn't expose default params.
|
||||
return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
|
||||
}), allow_raw_pointers());
|
||||
function("MakePathFromOp", &MakePathFromOp);
|
||||
|
||||
// These won't be called directly, there's a JS helper to deal with typed arrays.
|
||||
@ -445,7 +452,16 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.function("drawPaint", &SkCanvas::drawPaint)
|
||||
.function("drawPath", &SkCanvas::drawPath)
|
||||
.function("drawRect", &SkCanvas::drawRect)
|
||||
.function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
|
||||
.function("drawShadow", optional_override([](SkCanvas& self, const SkPath& path,
|
||||
const SkPoint3& zPlaneParams,
|
||||
const SkPoint3& lightPos, SkScalar lightRadius,
|
||||
JSColor ambientColor, JSColor spotColor,
|
||||
uint32_t flags) {
|
||||
SkShadowUtils::DrawShadow(&self, path, zPlaneParams, lightPos, lightRadius,
|
||||
SkColor(ambientColor), SkColor(spotColor), flags);
|
||||
}))
|
||||
.function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x,
|
||||
SkScalar y, const SkPaint& p) {
|
||||
// TODO(kjlubick): This does not work well for non-ascii
|
||||
// Need to maybe add a helper in interface.js that supports UTF-8
|
||||
// Otherwise, go with std::wstring and set UTF-32 encoding.
|
||||
@ -471,12 +487,20 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.function("_encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData))
|
||||
.function("_encodeToDataWithFormat", select_overload<sk_sp<SkData>(SkEncodedImageFormat encodedImageFormat, int quality)const>(&SkImage::encodeToData));
|
||||
|
||||
class_<SkMaskFilter>("SkMaskFilter")
|
||||
.smart_ptr<sk_sp<SkMaskFilter>>("sk_sp<SkMaskFilter>");
|
||||
|
||||
class_<SkPaint>("SkPaint")
|
||||
.constructor<>()
|
||||
.function("copy", optional_override([](const SkPaint& self)->SkPaint {
|
||||
SkPaint p(self);
|
||||
return p;
|
||||
}))
|
||||
.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.
|
||||
return JSColor(self.getColor());
|
||||
}))
|
||||
.function("getStrokeWidth", &SkPaint::getStrokeWidth)
|
||||
.function("getStrokeMiter", &SkPaint::getStrokeMiter)
|
||||
.function("getStrokeCap", &SkPaint::getStrokeCap)
|
||||
@ -494,6 +518,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
// Add a optional_override to change it out.
|
||||
self.setColor(SkColor(color));
|
||||
}))
|
||||
.function("setMaskFilter", &SkPaint::setMaskFilter)
|
||||
.function("setPathEffect", &SkPaint::setPathEffect)
|
||||
.function("setShader", &SkPaint::setShader)
|
||||
.function("setStrokeWidth", &SkPaint::setStrokeWidth)
|
||||
@ -616,10 +641,11 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.value("Color", SkBlendMode::kColor)
|
||||
.value("Luminosity", SkBlendMode::kLuminosity);
|
||||
|
||||
enum_<SkPaint::Style>("PaintStyle")
|
||||
.value("Fill", SkPaint::Style::kFill_Style)
|
||||
.value("Stroke", SkPaint::Style::kStroke_Style)
|
||||
.value("StrokeAndFill", SkPaint::Style::kStrokeAndFill_Style);
|
||||
enum_<SkBlurStyle>("BlurStyle")
|
||||
.value("Normal", SkBlurStyle::kNormal_SkBlurStyle)
|
||||
.value("Solid", SkBlurStyle::kSolid_SkBlurStyle)
|
||||
.value("Outer", SkBlurStyle::kOuter_SkBlurStyle)
|
||||
.value("Inner", SkBlurStyle::kInner_SkBlurStyle);
|
||||
|
||||
enum_<SkPath::FillType>("FillType")
|
||||
.value("Winding", SkPath::FillType::kWinding_FillType)
|
||||
@ -627,6 +653,15 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.value("InverseWinding", SkPath::FillType::kInverseWinding_FillType)
|
||||
.value("InverseEvenOdd", SkPath::FillType::kInverseEvenOdd_FillType);
|
||||
|
||||
enum_<SkEncodedImageFormat>("ImageFormat")
|
||||
.value("PNG", SkEncodedImageFormat::kPNG)
|
||||
.value("JPEG", SkEncodedImageFormat::kJPEG);
|
||||
|
||||
enum_<SkPaint::Style>("PaintStyle")
|
||||
.value("Fill", SkPaint::Style::kFill_Style)
|
||||
.value("Stroke", SkPaint::Style::kStroke_Style)
|
||||
.value("StrokeAndFill", SkPaint::Style::kStrokeAndFill_Style);
|
||||
|
||||
enum_<SkPathOp>("PathOp")
|
||||
.value("Difference", SkPathOp::kDifference_SkPathOp)
|
||||
.value("Intersect", SkPathOp::kIntersect_SkPathOp)
|
||||
@ -660,10 +695,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.value("TrianglesStrip", SkVertices::VertexMode::kTriangleStrip_VertexMode)
|
||||
.value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);
|
||||
|
||||
enum_<SkEncodedImageFormat>("ImageFormat")
|
||||
.value("PNG", SkEncodedImageFormat::kPNG)
|
||||
.value("JPEG", SkEncodedImageFormat::kJPEG);
|
||||
|
||||
|
||||
// A value object is much simpler than a class - it is returned as a JS
|
||||
// object and does not require delete().
|
||||
@ -685,6 +716,12 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
.element(&SkPoint::fX)
|
||||
.element(&SkPoint::fY);
|
||||
|
||||
// SkPoint3s can be represented by [x, y, z]
|
||||
value_array<SkPoint3>("SkPoint3")
|
||||
.element(&SkPoint3::fX)
|
||||
.element(&SkPoint3::fY)
|
||||
.element(&SkPoint3::fZ);
|
||||
|
||||
// {"w": Number, "h", Number}
|
||||
value_object<SkSize>("SkSize")
|
||||
.field("w", &SkSize::fWidth)
|
||||
@ -717,6 +754,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
|
||||
constant("BLUE", (JSColor) SK_ColorBLUE);
|
||||
constant("YELLOW", (JSColor) SK_ColorYELLOW);
|
||||
constant("CYAN", (JSColor) SK_ColorCYAN);
|
||||
constant("BLACK", (JSColor) SK_ColorBLACK);
|
||||
// TODO(?)
|
||||
|
||||
#if SK_INCLUDE_SKOTTIE
|
||||
|
@ -27,19 +27,21 @@ var CanvasKit = {
|
||||
Color: function() {},
|
||||
/** @return {CanvasKit.SkRect} */
|
||||
LTRBRect: function() {},
|
||||
MakeBlurMaskFilter: function() {},
|
||||
MakeCanvas: function() {},
|
||||
MakeCanvasSurface: function() {},
|
||||
MakeSWCanvasSurface: function() {},
|
||||
MakeWebGLCanvasSurface: function() {},
|
||||
MakeImageShader: function() {},
|
||||
MakeLinearGradientShader: function() {},
|
||||
MakeRadialGradientShader: function() {},
|
||||
MakeNimaActor: function() {},
|
||||
MakeRadialGradientShader: function() {},
|
||||
MakeSWCanvasSurface: function() {},
|
||||
MakeSkDashPathEffect: function() {},
|
||||
MakeSkVertices: function() {},
|
||||
MakeSurface: function() {},
|
||||
MakeWebGLCanvasSurface: function() {},
|
||||
currentContext: function() {},
|
||||
getSkDataBytes: function() {},
|
||||
getColorComponents: function() {},
|
||||
initFonts: function() {},
|
||||
setCurrentContext: function() {},
|
||||
|
||||
@ -78,6 +80,7 @@ var CanvasKit = {
|
||||
drawPaint: function() {},
|
||||
drawPath: function() {},
|
||||
drawText: function() {},
|
||||
drawShadow: function() {},
|
||||
flush: function() {},
|
||||
rotate: function() {},
|
||||
save: function() {},
|
||||
@ -110,6 +113,7 @@ var CanvasKit = {
|
||||
// public API (from C++ bindings)
|
||||
/** @return {CanvasKit.SkPaint} */
|
||||
copy: function() {},
|
||||
getColor: function() {},
|
||||
getStrokeCap: function() {},
|
||||
getStrokeJoin: function() {},
|
||||
getStrokeMiter: function() {},
|
||||
@ -118,6 +122,7 @@ var CanvasKit = {
|
||||
measureText: function() {},
|
||||
setAntiAlias: function() {},
|
||||
setColor: function() {},
|
||||
setMaskFilter: function() {},
|
||||
setPathEffect: function() {},
|
||||
setShader: function() {},
|
||||
setStrokeCap: function() {},
|
||||
@ -203,6 +208,52 @@ var CanvasKit = {
|
||||
gpu: {},
|
||||
skottie: {},
|
||||
|
||||
TRANSPARENT: {},
|
||||
RED: {},
|
||||
BLUE: {},
|
||||
YELLOW: {},
|
||||
CYAN: {},
|
||||
BLACK: {},
|
||||
|
||||
BlendMode: {
|
||||
Clear: {},
|
||||
Src: {},
|
||||
Dst: {},
|
||||
SrcOver: {},
|
||||
DstOver: {},
|
||||
SrcIn: {},
|
||||
DstIn: {},
|
||||
SrcOut: {},
|
||||
DstOut: {},
|
||||
SrcATop: {},
|
||||
DstATop: {},
|
||||
Xor: {},
|
||||
Plus: {},
|
||||
Modulate: {},
|
||||
Screen: {},
|
||||
Overlay: {},
|
||||
Darken: {},
|
||||
Lighten: {},
|
||||
ColorDodge: {},
|
||||
ColorBurn: {},
|
||||
HardLight: {},
|
||||
SoftLight: {},
|
||||
Difference: {},
|
||||
Exclusion: {},
|
||||
Multiply: {},
|
||||
Hue: {},
|
||||
Saturation: {},
|
||||
Color: {},
|
||||
Luminosity: {},
|
||||
},
|
||||
|
||||
BlurStyle: {
|
||||
Normal: {},
|
||||
Solid: {},
|
||||
Outer: {},
|
||||
Inner: {},
|
||||
},
|
||||
|
||||
FillType: {
|
||||
Winding: {},
|
||||
EvenOdd: {},
|
||||
@ -312,6 +363,7 @@ CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
|
||||
CanvasRenderingContext2D.prototype.closePath = function() {};
|
||||
CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
|
||||
CanvasRenderingContext2D.prototype.ellipse = function() {};
|
||||
CanvasRenderingContext2D.prototype.fill = function() {};
|
||||
CanvasRenderingContext2D.prototype.fillText = function() {};
|
||||
CanvasRenderingContext2D.prototype.lineTo = function() {};
|
||||
CanvasRenderingContext2D.prototype.measureText = function() {};
|
||||
@ -320,7 +372,9 @@ CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
|
||||
CanvasRenderingContext2D.prototype.rect = function() {};
|
||||
CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
|
||||
CanvasRenderingContext2D.prototype.resetTransform = function() {};
|
||||
CanvasRenderingContext2D.prototype.restore = function() {};
|
||||
CanvasRenderingContext2D.prototype.rotate = function() {};
|
||||
CanvasRenderingContext2D.prototype.save = function() {};
|
||||
CanvasRenderingContext2D.prototype.scale = function() {};
|
||||
CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
|
||||
CanvasRenderingContext2D.prototype.setTransform = function() {};
|
||||
|
@ -17,4 +17,15 @@
|
||||
}
|
||||
return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
|
||||
}
|
||||
|
||||
// returns [r, g, b, a] from a color
|
||||
// where a is scaled between 0 and 1.0
|
||||
CanvasKit.getColorComponents = function(color) {
|
||||
return [
|
||||
(color >> 16) & 0xFF,
|
||||
(color >> 8) & 0xFF,
|
||||
(color >> 0) & 0xFF,
|
||||
((color >> 24) & 0xFF) / 255,
|
||||
]
|
||||
}
|
||||
}(Module)); // When this file is loaded in, the high level object is "Module";
|
||||
|
@ -80,10 +80,19 @@
|
||||
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._currentPath = new CanvasKit.SkPath();
|
||||
this._currentSubpath = null;
|
||||
this._currentTransform = CanvasKit.SkMatrix.identity();
|
||||
|
||||
this._canvasStateStack = [];
|
||||
|
||||
this._dispose = function() {
|
||||
this._currentPath.delete();
|
||||
this._currentSubpath && this._currentSubpath.delete();
|
||||
@ -92,17 +101,88 @@
|
||||
// by the surface of which it is based.
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'fillStyle', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return colorToString(this._fillColor);
|
||||
},
|
||||
set: function(newStyle) {
|
||||
this._fillColor = parseColor(newStyle);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'font', {
|
||||
enumerable: true,
|
||||
get: function(newStyle) {
|
||||
// TODO generate this
|
||||
return '10px sans-serif';
|
||||
},
|
||||
set: function(newStyle) {
|
||||
var size = parseFontSize(newStyle);
|
||||
// TODO styles
|
||||
// TODO(kjlubick) styles, font name
|
||||
this._paint.setTextSize(size);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineCap', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
switch (this._paint.getStrokeCap()) {
|
||||
case CanvasKit.StrokeCap.Butt:
|
||||
return 'butt';
|
||||
case CanvasKit.StrokeCap.Round:
|
||||
return 'round';
|
||||
case CanvasKit.StrokeCap.Square:
|
||||
return 'square';
|
||||
}
|
||||
},
|
||||
set: function(newCap) {
|
||||
switch (newCap) {
|
||||
case 'butt':
|
||||
this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
|
||||
return;
|
||||
case 'round':
|
||||
this._paint.setStrokeCap(CanvasKit.StrokeCap.Round);
|
||||
return;
|
||||
case 'square':
|
||||
this._paint.setStrokeCap(CanvasKit.StrokeCap.Square);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineJoin', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
switch (this._paint.getStrokeJoin()) {
|
||||
case CanvasKit.StrokeJoin.Miter:
|
||||
return 'miter';
|
||||
case CanvasKit.StrokeJoin.Round:
|
||||
return 'round';
|
||||
case CanvasKit.StrokeJoin.Bevel:
|
||||
return 'bevel';
|
||||
}
|
||||
},
|
||||
set: function(newJoin) {
|
||||
switch (newJoin) {
|
||||
case 'miter':
|
||||
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
|
||||
return;
|
||||
case 'round':
|
||||
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Round);
|
||||
return;
|
||||
case 'bevel':
|
||||
this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Bevel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'lineWidth', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._paint.getStrokeWidth();
|
||||
},
|
||||
set: function(newWidth) {
|
||||
if (newWidth <= 0 || !newWidth) {
|
||||
// Spec says to ignore NaN/Inf/0/negative values
|
||||
@ -112,10 +192,77 @@
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'miterLimit', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._paint.getStrokeMiter();
|
||||
},
|
||||
set: function(newLimit) {
|
||||
if (newLimit <= 0 || !newLimit) {
|
||||
// Spec says to ignore NaN/Inf/0/negative values
|
||||
return;
|
||||
}
|
||||
this._paint.setStrokeMiter(newLimit);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'shadowBlur', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._shadowBlur;
|
||||
},
|
||||
set: function(newBlur) {
|
||||
// ignore negative, inf and NAN (but not 0) as per the spec.
|
||||
if (newBlur < 0 || !isFinite(newBlur)) {
|
||||
return;
|
||||
}
|
||||
this._shadowBlur = newBlur;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'shadowColor', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return colorToString(this._shadowColor);
|
||||
},
|
||||
set: function(newColor) {
|
||||
this._shadowColor = parseColor(newColor);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'shadowOffsetX', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._shadowOffsetX;
|
||||
},
|
||||
set: function(newOffset) {
|
||||
if (!isFinite(newOffset)) {
|
||||
return;
|
||||
}
|
||||
this._shadowOffsetX = newOffset;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'shadowOffsetY', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return this._shadowOffsetY;
|
||||
},
|
||||
set: function(newOffset) {
|
||||
if (!isFinite(newOffset)) {
|
||||
return;
|
||||
}
|
||||
this._shadowOffsetY = newOffset;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'strokeStyle', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return colorToString(this._strokeColor);
|
||||
},
|
||||
set: function(newStyle) {
|
||||
this._paint.setColor(parseColor(newStyle));
|
||||
this._strokeColor = parseColor(newStyle);
|
||||
}
|
||||
});
|
||||
|
||||
@ -215,8 +362,48 @@
|
||||
temp.delete();
|
||||
}
|
||||
|
||||
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 shadowPaint = this._shadowPaint();
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
|
||||
);
|
||||
this._canvas.setMatrix(offsetMatrix);
|
||||
this._canvas.drawPath(this._currentPath, shadowPaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
shadowPaint.dispose();
|
||||
}
|
||||
|
||||
this._canvas.drawPath(this._currentPath, this._paint);
|
||||
// set stroke width back to original size:
|
||||
this._paint.setStrokeWidth(orig);
|
||||
}
|
||||
|
||||
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();
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
|
||||
);
|
||||
this._canvas.setMatrix(offsetMatrix);
|
||||
this._canvas.drawText(text, x, y, shadowPaint);
|
||||
shadowPaint.dispose();
|
||||
// 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.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
@ -296,12 +483,52 @@
|
||||
this._currentTransform = CanvasKit.SkMatrix.identity();
|
||||
}
|
||||
|
||||
this.restore = function() {
|
||||
var newState = this._canvasStateStack.pop();
|
||||
if (!newState) {
|
||||
return;
|
||||
}
|
||||
this._currentTransform = newState.ctm;
|
||||
// TODO(kjlubick): clipping region
|
||||
// TODO(kjlubick): dash list
|
||||
this._paint.setStrokeWidth(newState.sw);
|
||||
this._strokeColor = newState.sc;
|
||||
this._fillColor = newState.fc;
|
||||
this._paint.setStrokeCap(newState.cap);
|
||||
this._paint.setStrokeJoin(newState.jn);
|
||||
this._paint.setStrokeMiter(newState.mtr);
|
||||
this._shadowOffsetX = newState.sox;
|
||||
this._shadowOffsetY = newState.soy;
|
||||
this._shadowBlur = newState.sb;
|
||||
this._shadowColor = newState.shc;
|
||||
//TODO: globalAlpha, lineDashOffset, filter, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
}
|
||||
|
||||
this.rotate = function(radians, px, py) {
|
||||
this._currentTransform = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
CanvasKit.SkMatrix.rotated(radians, px, py));
|
||||
}
|
||||
|
||||
this.save = function() {
|
||||
this._canvasStateStack.push({
|
||||
ctm: this._currentTransform.slice(),
|
||||
// TODO(kjlubick): clipping region
|
||||
// TODO(kjlubick): dash list
|
||||
sw: this._paint.getStrokeWidth(),
|
||||
sc: this._strokeColor,
|
||||
fc: this._fillColor,
|
||||
cap: this._paint.getStrokeCap(),
|
||||
jn: this._paint.getStrokeJoin(),
|
||||
mtr: this._paint.getStrokeMiter(),
|
||||
sox: this._shadowOffsetX,
|
||||
soy: this._shadowOffsetY,
|
||||
sb: this._shadowBlur,
|
||||
shc: this._shadowColor,
|
||||
//TODO: globalAlpha, lineDashOffset, filter, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled, imageSmoothingQuality.
|
||||
});
|
||||
}
|
||||
|
||||
this.scale = function(sx, sy) {
|
||||
this._currentTransform = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
@ -323,24 +550,78 @@
|
||||
0, 0, 1];
|
||||
}
|
||||
|
||||
this.stroke = function() {
|
||||
if (this._currentSubpath) {
|
||||
this._commitSubpath();
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
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);
|
||||
// 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
|
||||
// paint with a blur maskfilter and the correct color.
|
||||
this._shadowPaint = function() {
|
||||
// if alpha is zero, no shadows
|
||||
if (!CanvasKit.getColorComponents(this._shadowColor)[3]) {
|
||||
return null;
|
||||
}
|
||||
// one of these must also be non-zero (otherwise the shadow is
|
||||
// completely hidden. And the spec says so).
|
||||
if (!(this._shadowBlur || this._shadowOffsetY || this._shadowOffsetX)) {
|
||||
return null;
|
||||
}
|
||||
var shadowPaint = this._paint.copy();
|
||||
shadowPaint.setColor(this._shadowColor);
|
||||
var blurEffect = CanvasKit.MakeBlurMaskFilter(CanvasKit.BlurStyle.Normal,
|
||||
Math.max(1, this._shadowBlur/2), // very little blur when < 1
|
||||
false);
|
||||
shadowPaint.setMaskFilter(blurEffect);
|
||||
|
||||
// hack up a "destructor" which also cleans up the blurEffect. Otherwise,
|
||||
// we leak the blurEffect (since smart pointers don't help us in JS land).
|
||||
shadowPaint.dispose = function() {
|
||||
blurEffect.delete();
|
||||
this.delete();
|
||||
};
|
||||
return shadowPaint;
|
||||
}
|
||||
|
||||
this.stroke = function() {
|
||||
this._commitSubpath();
|
||||
this._paint.setStyle(CanvasKit.PaintStyle.Stroke);
|
||||
|
||||
this._paint.setColor(this._strokeColor);
|
||||
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 shadowPaint = this._shadowPaint();
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
|
||||
);
|
||||
this._canvas.setMatrix(offsetMatrix);
|
||||
this._canvas.drawPath(this._currentPath, shadowPaint);
|
||||
this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
shadowPaint.dispose();
|
||||
}
|
||||
|
||||
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._paint.setColor(this._strokeColor);
|
||||
var shadowPaint = this._shadowPaint();
|
||||
if (shadowPaint) {
|
||||
var offsetMatrix = CanvasKit.SkMatrix.multiply(
|
||||
this._currentTransform,
|
||||
CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
|
||||
);
|
||||
this._canvas.setMatrix(offsetMatrix);
|
||||
this._canvas.drawText(text, x, y, shadowPaint);
|
||||
shadowPaint.dispose();
|
||||
// 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.setMatrix(CanvasKit.SkMatrix.identity());
|
||||
@ -421,6 +702,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
function colorToString(skcolor) {
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
|
||||
var components = CanvasKit.getColorComponents(skcolor);
|
||||
var r = components[0];
|
||||
var g = components[1];
|
||||
var b = components[2];
|
||||
var a = components[3];
|
||||
if (a === 1.0) {
|
||||
// hex
|
||||
r = r.toString(16).toLowerCase();
|
||||
g = g.toString(16).toLowerCase();
|
||||
b = b.toString(16).toLowerCase();
|
||||
r = (r.length === 1 ? '0'+r: r);
|
||||
g = (g.length === 1 ? '0'+g: g);
|
||||
b = (b.length === 1 ? '0'+b: b);
|
||||
return '#'+r+g+b;
|
||||
} else {
|
||||
a = (a === 0 || a === 1) ? a : a.toFixed(8);
|
||||
return 'rgba('+r+', '+g+', '+b+', '+a+')';
|
||||
}
|
||||
}
|
||||
|
||||
function valueOrPercent(aStr) {
|
||||
var a = parseFloat(aStr) || 1;
|
||||
if (aStr && aStr.indexOf('%') !== -1) {
|
||||
@ -484,6 +787,7 @@
|
||||
}
|
||||
|
||||
CanvasKit._testing['parseColor'] = parseColor;
|
||||
CanvasKit._testing['colorToString'] = colorToString;
|
||||
|
||||
// Create the following with
|
||||
// node ./htmlcanvas/_namedcolors.js --expose-wasm
|
||||
|
@ -33,14 +33,14 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
container.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('color string parsing', function() {
|
||||
describe('color strings', function() {
|
||||
function hex(s) {
|
||||
return parseInt(s, 16);
|
||||
}
|
||||
|
||||
it('parses hex color strings', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
let parseColor = CanvasKit._testing.parseColor;
|
||||
const parseColor = CanvasKit._testing.parseColor;
|
||||
expect(parseColor('#FED')).toEqual(
|
||||
CanvasKit.Color(hex('FF'), hex('EE'), hex('DD'), 1));
|
||||
expect(parseColor('#FEDC')).toEqual(
|
||||
@ -54,7 +54,7 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
});
|
||||
it('parses rgba color strings', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
let parseColor = CanvasKit._testing.parseColor;
|
||||
const parseColor = CanvasKit._testing.parseColor;
|
||||
expect(parseColor('rgba(117, 33, 64, 0.75)')).toEqual(
|
||||
CanvasKit.Color(117, 33, 64, 0.75));
|
||||
expect(parseColor('rgb(117, 33, 64, 0.75)')).toEqual(
|
||||
@ -68,7 +68,7 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
});
|
||||
it('parses named color strings', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
let parseColor = CanvasKit._testing.parseColor;
|
||||
const parseColor = CanvasKit._testing.parseColor;
|
||||
expect(parseColor('grey')).toEqual(
|
||||
CanvasKit.Color(128, 128, 128, 1.0));
|
||||
expect(parseColor('blanchedalmond')).toEqual(
|
||||
@ -78,6 +78,19 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
it('properly produces color strings', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const colorToString = CanvasKit._testing.colorToString;
|
||||
|
||||
expect(colorToString(CanvasKit.Color(102, 51, 153, 1.0))).toEqual('#663399');
|
||||
|
||||
expect(colorToString(CanvasKit.Color(255, 235, 205, 0.5))).toEqual(
|
||||
'rgba(255, 235, 205, 0.50196078)');
|
||||
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}); // end describe('color string parsing')
|
||||
|
||||
function multipleCanvasTest(testname, done, test) {
|
||||
@ -192,6 +205,76 @@ describe('CanvasKit\'s Canvas 2d Behavior', function() {
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('properly saves and restores states, even when drawing shadows', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
multipleCanvasTest('shadows_and_save_restore', done, (canvas) => {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.fillStyle = '#CCC';
|
||||
ctx.shadowColor = 'rebeccapurple';
|
||||
ctx.shadowBlur = 1;
|
||||
ctx.shadowOffsetX = 3;
|
||||
ctx.shadowOffsetY = -8;
|
||||
ctx.rect(10, 10, 30, 30);
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#C00';
|
||||
ctx.fillStyle = '#00C';
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowColor = 'transparent';
|
||||
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(36, 148);
|
||||
ctx.quadraticCurveTo(66, 188, 120, 136);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.shadowColor = '#993366AA';
|
||||
ctx.shadowOffsetX = 8;
|
||||
ctx.shadowBlur = 5;
|
||||
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.stroke();
|
||||
|
||||
ctx.fillStyle = 'green';
|
||||
ctx.font = '16pt Arial';
|
||||
ctx.fillText('This should be shadowed', 20, 80);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('can read default properties', function(done) {
|
||||
LoadCanvasKit.then(catchException(done, () => {
|
||||
const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||
const realCanvas = document.getElementById('test');
|
||||
realCanvas.width = CANVAS_WIDTH;
|
||||
realCanvas.height = CANVAS_HEIGHT;
|
||||
|
||||
const skcontext = skcanvas.getContext('2d');
|
||||
const realContext = realCanvas.getContext('2d');
|
||||
|
||||
const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
|
||||
'lineJoin', 'miterLimit', 'shadowOffsetY',
|
||||
'shadowBlur', 'shadowColor', 'shadowOffsetX'];
|
||||
|
||||
for( let attr of toTest) {
|
||||
expect(skcontext[attr]).toBe(realContext[attr], attr);
|
||||
}
|
||||
|
||||
skcanvas.dispose();
|
||||
done();
|
||||
}));
|
||||
});
|
||||
}); // end describe('Path drawing API')
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user