Skip to content

Commit 61ef7b2

Browse files
committed
[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>
1 parent 2a1848d commit 61ef7b2

File tree

6 files changed

+575
-29
lines changed

6 files changed

+575
-29
lines changed

experimental/canvaskit/canvaskit/example.html

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ <h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
2424
<canvas id=api2_c width=300 height=300></canvas>
2525
<img id=api3 width=300 height=300>
2626
<canvas id=api3_c width=300 height=300></canvas>
27+
<img id=api4 width=300 height=300>
28+
<canvas id=api4_c width=300 height=300></canvas>
2729

2830
<h2> CanvasKit draws Paths to the browser</h2>
2931
<canvas id=vertex1 width=300 height=300></canvas>
@@ -91,6 +93,7 @@ <h2> Nima </h2>
9193
CanvasAPI1(CanvasKit);
9294
CanvasAPI2(CanvasKit);
9395
CanvasAPI3(CanvasKit);
96+
CanvasAPI4(CanvasKit);
9497

9598
VertexAPI1(CanvasKit);
9699
VertexAPI2(CanvasKit, bonesImage);
@@ -556,6 +559,59 @@ <h2> Nima </h2>
556559
document.getElementById('api3').src = skcanvas.toDataURL();
557560
}
558561

562+
function CanvasAPI4(CanvasKit) {
563+
let skcanvas = CanvasKit.MakeCanvas(300, 300);
564+
let realCanvas = document.getElementById('api4_c');
565+
realCanvas.width = 300;
566+
realCanvas.height = 300;
567+
568+
for (let canvas of [skcanvas, realCanvas]) {
569+
let ctx = canvas.getContext('2d');
570+
571+
ctx.strokeStyle = '#000';
572+
ctx.fillStyle = '#CCC';
573+
ctx.shadowColor = 'rebeccapurple';
574+
ctx.shadowBlur = 1;
575+
ctx.shadowOffsetX = 3;
576+
ctx.shadowOffsetY = -8;
577+
ctx.rect(10, 10, 30, 30);
578+
579+
ctx.save();
580+
ctx.strokeStyle = '#C00';
581+
ctx.fillStyle = '#00C';
582+
ctx.shadowBlur = 0;
583+
ctx.shadowColor = 'transparent';
584+
585+
ctx.stroke();
586+
587+
ctx.restore();
588+
ctx.fill();
589+
590+
ctx.beginPath();
591+
ctx.moveTo(36, 148);
592+
ctx.quadraticCurveTo(66, 188, 120, 136);
593+
ctx.closePath();
594+
ctx.stroke();
595+
596+
ctx.beginPath();
597+
ctx.shadowColor = '#993366AA';
598+
ctx.shadowOffsetX = 8;
599+
ctx.shadowBlur = 5;
600+
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
601+
ctx.rect(110, 10, 20, 20);
602+
ctx.lineTo(110, 0);
603+
ctx.resetTransform();
604+
ctx.lineTo(220, 120);
605+
ctx.stroke();
606+
607+
ctx.fillStyle = 'green';
608+
ctx.font = '16pt Arial';
609+
ctx.fillText('This should be shadowed', 20, 80);
610+
611+
}
612+
document.getElementById('api4').src = skcanvas.toDataURL();
613+
}
614+
559615
function NimaExample(CanvasKit, nimaFile, nimaTexture) {
560616
if (!CanvasKit || !nimaFile || !nimaTexture) {
561617
return;

experimental/canvaskit/canvaskit_bindings.cpp

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@
2222
#include "SkDiscretePathEffect.h"
2323
#include "SkEncodedImageFormat.h"
2424
#include "SkFontMgr.h"
25+
#include "SkBlurTypes.h"
2526
#include "SkFontMgrPriv.h"
2627
#include "SkGradientShader.h"
2728
#include "SkImageShader.h"
29+
#include "SkMaskFilter.h"
2830
#include "SkPaint.h"
2931
#include "SkParsePath.h"
3032
#include "SkPath.h"
3133
#include "SkPathEffect.h"
3234
#include "SkPathOps.h"
3335
#include "SkScalar.h"
3436
#include "SkShader.h"
37+
#include "SkShadowUtils.h"
3538
#include "SkString.h"
3639
#include "SkStrokeRec.h"
3740
#include "SkSurface.h"
@@ -349,6 +352,10 @@ EMSCRIPTEN_BINDINGS(Skia) {
349352
function("getSkDataBytes", &getSkDataBytes, allow_raw_pointers());
350353
function("MakeSkCornerPathEffect", &SkCornerPathEffect::Make, allow_raw_pointers());
351354
function("MakeSkDiscretePathEffect", &SkDiscretePathEffect::Make, allow_raw_pointers());
355+
function("MakeBlurMaskFilter", optional_override([](SkBlurStyle style, SkScalar sigma, bool respectCTM)->sk_sp<SkMaskFilter> {
356+
// Adds a little helper because emscripten doesn't expose default params.
357+
return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
358+
}), allow_raw_pointers());
352359
function("MakePathFromOp", &MakePathFromOp);
353360

354361
// These won't be called directly, there's a JS helper to deal with typed arrays.
@@ -445,7 +452,16 @@ EMSCRIPTEN_BINDINGS(Skia) {
445452
.function("drawPaint", &SkCanvas::drawPaint)
446453
.function("drawPath", &SkCanvas::drawPath)
447454
.function("drawRect", &SkCanvas::drawRect)
448-
.function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x, SkScalar y, const SkPaint& p) {
455+
.function("drawShadow", optional_override([](SkCanvas& self, const SkPath& path,
456+
const SkPoint3& zPlaneParams,
457+
const SkPoint3& lightPos, SkScalar lightRadius,
458+
JSColor ambientColor, JSColor spotColor,
459+
uint32_t flags) {
460+
SkShadowUtils::DrawShadow(&self, path, zPlaneParams, lightPos, lightRadius,
461+
SkColor(ambientColor), SkColor(spotColor), flags);
462+
}))
463+
.function("drawText", optional_override([](SkCanvas& self, std::string text, SkScalar x,
464+
SkScalar y, const SkPaint& p) {
449465
// TODO(kjlubick): This does not work well for non-ascii
450466
// Need to maybe add a helper in interface.js that supports UTF-8
451467
// Otherwise, go with std::wstring and set UTF-32 encoding.
@@ -471,12 +487,20 @@ EMSCRIPTEN_BINDINGS(Skia) {
471487
.function("_encodeToData", select_overload<sk_sp<SkData>()const>(&SkImage::encodeToData))
472488
.function("_encodeToDataWithFormat", select_overload<sk_sp<SkData>(SkEncodedImageFormat encodedImageFormat, int quality)const>(&SkImage::encodeToData));
473489

490+
class_<SkMaskFilter>("SkMaskFilter")
491+
.smart_ptr<sk_sp<SkMaskFilter>>("sk_sp<SkMaskFilter>");
492+
474493
class_<SkPaint>("SkPaint")
475494
.constructor<>()
476495
.function("copy", optional_override([](const SkPaint& self)->SkPaint {
477496
SkPaint p(self);
478497
return p;
479498
}))
499+
.function("getColor", optional_override([](SkPaint& self)->JSColor {
500+
// JS side gives us a signed int instead of an unsigned int for color
501+
// Add a optional_override to change it out.
502+
return JSColor(self.getColor());
503+
}))
480504
.function("getStrokeWidth", &SkPaint::getStrokeWidth)
481505
.function("getStrokeMiter", &SkPaint::getStrokeMiter)
482506
.function("getStrokeCap", &SkPaint::getStrokeCap)
@@ -494,6 +518,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
494518
// Add a optional_override to change it out.
495519
self.setColor(SkColor(color));
496520
}))
521+
.function("setMaskFilter", &SkPaint::setMaskFilter)
497522
.function("setPathEffect", &SkPaint::setPathEffect)
498523
.function("setShader", &SkPaint::setShader)
499524
.function("setStrokeWidth", &SkPaint::setStrokeWidth)
@@ -616,17 +641,27 @@ EMSCRIPTEN_BINDINGS(Skia) {
616641
.value("Color", SkBlendMode::kColor)
617642
.value("Luminosity", SkBlendMode::kLuminosity);
618643

619-
enum_<SkPaint::Style>("PaintStyle")
620-
.value("Fill", SkPaint::Style::kFill_Style)
621-
.value("Stroke", SkPaint::Style::kStroke_Style)
622-
.value("StrokeAndFill", SkPaint::Style::kStrokeAndFill_Style);
644+
enum_<SkBlurStyle>("BlurStyle")
645+
.value("Normal", SkBlurStyle::kNormal_SkBlurStyle)
646+
.value("Solid", SkBlurStyle::kSolid_SkBlurStyle)
647+
.value("Outer", SkBlurStyle::kOuter_SkBlurStyle)
648+
.value("Inner", SkBlurStyle::kInner_SkBlurStyle);
623649

624650
enum_<SkPath::FillType>("FillType")
625651
.value("Winding", SkPath::FillType::kWinding_FillType)
626652
.value("EvenOdd", SkPath::FillType::kEvenOdd_FillType)
627653
.value("InverseWinding", SkPath::FillType::kInverseWinding_FillType)
628654
.value("InverseEvenOdd", SkPath::FillType::kInverseEvenOdd_FillType);
629655

656+
enum_<SkEncodedImageFormat>("ImageFormat")
657+
.value("PNG", SkEncodedImageFormat::kPNG)
658+
.value("JPEG", SkEncodedImageFormat::kJPEG);
659+
660+
enum_<SkPaint::Style>("PaintStyle")
661+
.value("Fill", SkPaint::Style::kFill_Style)
662+
.value("Stroke", SkPaint::Style::kStroke_Style)
663+
.value("StrokeAndFill", SkPaint::Style::kStrokeAndFill_Style);
664+
630665
enum_<SkPathOp>("PathOp")
631666
.value("Difference", SkPathOp::kDifference_SkPathOp)
632667
.value("Intersect", SkPathOp::kIntersect_SkPathOp)
@@ -660,10 +695,6 @@ EMSCRIPTEN_BINDINGS(Skia) {
660695
.value("TrianglesStrip", SkVertices::VertexMode::kTriangleStrip_VertexMode)
661696
.value("TriangleFan", SkVertices::VertexMode::kTriangleFan_VertexMode);
662697

663-
enum_<SkEncodedImageFormat>("ImageFormat")
664-
.value("PNG", SkEncodedImageFormat::kPNG)
665-
.value("JPEG", SkEncodedImageFormat::kJPEG);
666-
667698

668699
// A value object is much simpler than a class - it is returned as a JS
669700
// object and does not require delete().
@@ -685,6 +716,12 @@ EMSCRIPTEN_BINDINGS(Skia) {
685716
.element(&SkPoint::fX)
686717
.element(&SkPoint::fY);
687718

719+
// SkPoint3s can be represented by [x, y, z]
720+
value_array<SkPoint3>("SkPoint3")
721+
.element(&SkPoint3::fX)
722+
.element(&SkPoint3::fY)
723+
.element(&SkPoint3::fZ);
724+
688725
// {"w": Number, "h", Number}
689726
value_object<SkSize>("SkSize")
690727
.field("w", &SkSize::fWidth)
@@ -717,6 +754,7 @@ EMSCRIPTEN_BINDINGS(Skia) {
717754
constant("BLUE", (JSColor) SK_ColorBLUE);
718755
constant("YELLOW", (JSColor) SK_ColorYELLOW);
719756
constant("CYAN", (JSColor) SK_ColorCYAN);
757+
constant("BLACK", (JSColor) SK_ColorBLACK);
720758
// TODO(?)
721759

722760
#if SK_INCLUDE_SKOTTIE

experimental/canvaskit/externs.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ var CanvasKit = {
2727
Color: function() {},
2828
/** @return {CanvasKit.SkRect} */
2929
LTRBRect: function() {},
30+
MakeBlurMaskFilter: function() {},
3031
MakeCanvas: function() {},
3132
MakeCanvasSurface: function() {},
32-
MakeSWCanvasSurface: function() {},
33-
MakeWebGLCanvasSurface: function() {},
3433
MakeImageShader: function() {},
3534
MakeLinearGradientShader: function() {},
36-
MakeRadialGradientShader: function() {},
3735
MakeNimaActor: function() {},
36+
MakeRadialGradientShader: function() {},
37+
MakeSWCanvasSurface: function() {},
3838
MakeSkDashPathEffect: function() {},
3939
MakeSkVertices: function() {},
4040
MakeSurface: function() {},
41+
MakeWebGLCanvasSurface: function() {},
4142
currentContext: function() {},
4243
getSkDataBytes: function() {},
44+
getColorComponents: function() {},
4345
initFonts: function() {},
4446
setCurrentContext: function() {},
4547

@@ -78,6 +80,7 @@ var CanvasKit = {
7880
drawPaint: function() {},
7981
drawPath: function() {},
8082
drawText: function() {},
83+
drawShadow: function() {},
8184
flush: function() {},
8285
rotate: function() {},
8386
save: function() {},
@@ -110,6 +113,7 @@ var CanvasKit = {
110113
// public API (from C++ bindings)
111114
/** @return {CanvasKit.SkPaint} */
112115
copy: function() {},
116+
getColor: function() {},
113117
getStrokeCap: function() {},
114118
getStrokeJoin: function() {},
115119
getStrokeMiter: function() {},
@@ -118,6 +122,7 @@ var CanvasKit = {
118122
measureText: function() {},
119123
setAntiAlias: function() {},
120124
setColor: function() {},
125+
setMaskFilter: function() {},
121126
setPathEffect: function() {},
122127
setShader: function() {},
123128
setStrokeCap: function() {},
@@ -203,6 +208,52 @@ var CanvasKit = {
203208
gpu: {},
204209
skottie: {},
205210

211+
TRANSPARENT: {},
212+
RED: {},
213+
BLUE: {},
214+
YELLOW: {},
215+
CYAN: {},
216+
BLACK: {},
217+
218+
BlendMode: {
219+
Clear: {},
220+
Src: {},
221+
Dst: {},
222+
SrcOver: {},
223+
DstOver: {},
224+
SrcIn: {},
225+
DstIn: {},
226+
SrcOut: {},
227+
DstOut: {},
228+
SrcATop: {},
229+
DstATop: {},
230+
Xor: {},
231+
Plus: {},
232+
Modulate: {},
233+
Screen: {},
234+
Overlay: {},
235+
Darken: {},
236+
Lighten: {},
237+
ColorDodge: {},
238+
ColorBurn: {},
239+
HardLight: {},
240+
SoftLight: {},
241+
Difference: {},
242+
Exclusion: {},
243+
Multiply: {},
244+
Hue: {},
245+
Saturation: {},
246+
Color: {},
247+
Luminosity: {},
248+
},
249+
250+
BlurStyle: {
251+
Normal: {},
252+
Solid: {},
253+
Outer: {},
254+
Inner: {},
255+
},
256+
206257
FillType: {
207258
Winding: {},
208259
EvenOdd: {},
@@ -312,6 +363,7 @@ CanvasRenderingContext2D.prototype.clearHitRegions = function() {};
312363
CanvasRenderingContext2D.prototype.closePath = function() {};
313364
CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
314365
CanvasRenderingContext2D.prototype.ellipse = function() {};
366+
CanvasRenderingContext2D.prototype.fill = function() {};
315367
CanvasRenderingContext2D.prototype.fillText = function() {};
316368
CanvasRenderingContext2D.prototype.lineTo = function() {};
317369
CanvasRenderingContext2D.prototype.measureText = function() {};
@@ -320,7 +372,9 @@ CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
320372
CanvasRenderingContext2D.prototype.rect = function() {};
321373
CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
322374
CanvasRenderingContext2D.prototype.resetTransform = function() {};
375+
CanvasRenderingContext2D.prototype.restore = function() {};
323376
CanvasRenderingContext2D.prototype.rotate = function() {};
377+
CanvasRenderingContext2D.prototype.save = function() {};
324378
CanvasRenderingContext2D.prototype.scale = function() {};
325379
CanvasRenderingContext2D.prototype.scrollPathIntoView = function() {};
326380
CanvasRenderingContext2D.prototype.setTransform = function() {};

experimental/canvaskit/helper.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,15 @@
1717
}
1818
return (clamp(a*255) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0);
1919
}
20+
21+
// returns [r, g, b, a] from a color
22+
// where a is scaled between 0 and 1.0
23+
CanvasKit.getColorComponents = function(color) {
24+
return [
25+
(color >> 16) & 0xFF,
26+
(color >> 8) & 0xFF,
27+
(color >> 0) & 0xFF,
28+
((color >> 24) & 0xFF) / 255,
29+
]
30+
}
2031
}(Module)); // When this file is loaded in, the high level object is "Module";

0 commit comments

Comments
 (0)