skia2/modules/canvaskit/npm_build/example.html

1210 lines
37 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<title>CanvasKit (Skia via Web Assembly)</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
canvas, img {
border: 1px dashed #AAA;
}
</style>
<h2>Drop in replacement for HTML Canvas (e.g. node.js)</h2>
<img id=api1 width=300 height=300>
<canvas id=api1_c width=300 height=300></canvas>
<img id=api2 width=300 height=300>
<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>
<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>
<img id=api7 width=300 height=300>
<canvas id=api7_c width=300 height=300></canvas>
<img id=api8 width=300 height=300>
<canvas id=api8_c width=300 height=300></canvas>
<h2> CanvasKit expands the functionality of a stock HTML canvas</h2>
<canvas id=vertex1 width=300 height=300></canvas>
<canvas id=gradient1 width=300 height=300></canvas>
<canvas id=patheffect width=300 height=300></canvas>
<canvas id=paths width=200 height=200></canvas>
<canvas id=pathperson width=300 height=300></canvas>
<canvas id=ink width=300 height=300></canvas>
<canvas id=surfaces width=300 height=300></canvas>
<canvas id=atlas width=300 height=300></canvas>
Added CanvasKit.MakeImageFromCanvasImageSource which is useful as an alternative to CanvasKit.MakeImageFromEncoded, when used with Browser APIs for loading/decoding images. - `CanvasKit.MakeImageFromCanvasImageSource` takes either an HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap, or OffscreenCanvas and returns an SkImage. This function is an alternative to `CanvasKit.MakeImageFromEncoded` for creating SkImages when loading and decoding images. In the future, codesize of CanvasKit may be able to be reduced by removing image codecs in wasm, if browser APIs for decoding images are used along with `CanvasKit.MakeImageFromCanvasImageSource` instead of `CanvasKit.MakeImageFromEncoded`. - Three usage examples of `CanvasKit.MakeImageFromCanvasImageSource` in core.spec.ts. These examples use browser APIs to decode images including 2d canvas, bitmaprenderer canvas, HTMLImageElement and Blob. - Added support for asynchronous callbacks in perfs and tests. Here are notes on the image decoding approaches we tested and perfed in the process of finding ways to use Browser APIs to decode images: 1. pipeline: ArrayBuffer → ImageData → ctx.putImageData → context.getImageData → Uint8Array → CanvasKit.MakeImage ❌ Problem: ImageData constructor expects decoded bytes already. 2. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded (async function) pipeline: ArrayBuffer → Blob -> HTMLImageElement -> draw on Canvas2d -> context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 3rd place (in my testing locally) 3. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2 (async function) ArrayBuffer → Blob → ImageBitmap → draw on Canvas2d → context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 2nd place (in my testing locally) 4. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded3 (async function) ArrayBuffer → Blob → ImageBitmap → draw on canvas 1 using bitmaprenderer context → draw canvas 1 on canvas 2 using drawImage → context2d.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 1st place (in my testing locally) - quite surprising, this in some ways seems to be a more roundabout way of CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2, but it seems bitmaprenderer context is fairly fast. Bug: skia:10360 Change-Id: I6fe94b8196dfd1ad0d8929f04bb1697da537ca18 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/295390 Reviewed-by: Kevin Lubick <kjlubick@google.com>
2020-06-15 18:53:27 +00:00
<canvas id=decode width=300 height=300></canvas>
<h2> CanvasKit can allow for text measurement/placement (e.g. breaking, kerning)</h2>
<canvas id=textonpath width=300 height=300></canvas>
<h2> Interactive drawPatch</h2>
<canvas id=interdrawpatch width=512 height=512></canvas>
<script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
<script type="text/javascript" charset="utf-8">
var CanvasKit = null;
var cdn = 'https://storage.googleapis.com/skia-cdn/misc/';
const ckLoaded = CanvasKitInit({locateFile: (file) => '/node_modules/canvaskit/bin/'+file});
const loadRoboto = fetch(cdn + 'Roboto-Regular.ttf').then((response) => response.arrayBuffer());
const loadNotoSerif = fetch(cdn + 'NotoSerif-Regular.ttf').then((response) => response.arrayBuffer());
const loadTestImage = fetch(cdn + 'test.png').then((response) => response.arrayBuffer());
// Examples which only require canvaskit
ckLoaded.then((CK) => {
CanvasKit = CK;
PathExample(CanvasKit);
InkExample(CanvasKit);
PathPersonExample(CanvasKit);
VertexAPI1(CanvasKit);
GradiantAPI1(CanvasKit);
TextOnPathAPI1(CanvasKit);
SurfaceAPI1(CanvasKit);
CanvasAPI1(CanvasKit);
CanvasAPI2(CanvasKit);
CanvasAPI3(CanvasKit);
CanvasAPI4(CanvasKit);
CanvasAPI5(CanvasKit);
CanvasAPI6(CanvasKit);
CanvasAPI7(CanvasKit);
CanvasAPI8(CanvasKit);
InteractivePatch(CanvasKit);
});
// Examples requiring external resources
Promise.all([ckLoaded, loadRoboto]).then((results) => {DrawingExample(...results)});
Promise.all([ckLoaded, loadTestImage]).then((results) => {AtlasAPI1(...results)});
Added CanvasKit.MakeImageFromCanvasImageSource which is useful as an alternative to CanvasKit.MakeImageFromEncoded, when used with Browser APIs for loading/decoding images. - `CanvasKit.MakeImageFromCanvasImageSource` takes either an HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap, or OffscreenCanvas and returns an SkImage. This function is an alternative to `CanvasKit.MakeImageFromEncoded` for creating SkImages when loading and decoding images. In the future, codesize of CanvasKit may be able to be reduced by removing image codecs in wasm, if browser APIs for decoding images are used along with `CanvasKit.MakeImageFromCanvasImageSource` instead of `CanvasKit.MakeImageFromEncoded`. - Three usage examples of `CanvasKit.MakeImageFromCanvasImageSource` in core.spec.ts. These examples use browser APIs to decode images including 2d canvas, bitmaprenderer canvas, HTMLImageElement and Blob. - Added support for asynchronous callbacks in perfs and tests. Here are notes on the image decoding approaches we tested and perfed in the process of finding ways to use Browser APIs to decode images: 1. pipeline: ArrayBuffer → ImageData → ctx.putImageData → context.getImageData → Uint8Array → CanvasKit.MakeImage ❌ Problem: ImageData constructor expects decoded bytes already. 2. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded (async function) pipeline: ArrayBuffer → Blob -> HTMLImageElement -> draw on Canvas2d -> context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 3rd place (in my testing locally) 3. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2 (async function) ArrayBuffer → Blob → ImageBitmap → draw on Canvas2d → context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 2nd place (in my testing locally) 4. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded3 (async function) ArrayBuffer → Blob → ImageBitmap → draw on canvas 1 using bitmaprenderer context → draw canvas 1 on canvas 2 using drawImage → context2d.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 1st place (in my testing locally) - quite surprising, this in some ways seems to be a more roundabout way of CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2, but it seems bitmaprenderer context is fairly fast. Bug: skia:10360 Change-Id: I6fe94b8196dfd1ad0d8929f04bb1697da537ca18 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/295390 Reviewed-by: Kevin Lubick <kjlubick@google.com>
2020-06-15 18:53:27 +00:00
Promise.all([ckLoaded, loadTestImage]).then((results) => {DecodeAPI(...results)});
function DrawingExample(CanvasKit, robotoData) {
if (!robotoData || !CanvasKit) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('patheffect');
if (!surface) {
console.error('Could not make surface');
return;
}
const paint = new CanvasKit.Paint();
const fontMgr = CanvasKit.FontMgr.RefDefault();
const roboto = fontMgr.MakeTypefaceFromData(robotoData);
const textPaint = new CanvasKit.Paint();
textPaint.setColor(CanvasKit.RED);
textPaint.setAntiAlias(true);
const textFont = new CanvasKit.Font(roboto, 30);
let i = 0;
let X = 128;
let Y = 128;
function drawFrame(canvas) {
const path = starPath(CanvasKit, X, Y);
// Some animations see performance improvements by marking their
// paths as volatile.
path.setIsVolatile(true);
const dpe = CanvasKit.PathEffect.MakeDash([15, 5, 5, 10], i/5);
i++;
paint.setPathEffect(dpe);
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(5.0 + -3 * Math.cos(i/30));
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
canvas.clear(CanvasKit.TRANSPARENT);
canvas.drawPath(path, paint);
canvas.drawText('Try Clicking!', 10, 280, textPaint, textFont);
dpe.delete();
path.delete();
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
// Make animation interactive
let interact = (e) => {
if (!e.pressure) {
return;
}
X = e.offsetX;
Y = e.offsetY;
};
document.getElementById('patheffect').addEventListener('pointermove', interact);
document.getElementById('patheffect').addEventListener('pointerdown', interact);
preventScrolling(document.getElementById('patheffect'));
// A client would need to delete this if it didn't go on for ever.
// paint.delete();
// textPaint.delete();
// textFont.delete();
}
function InteractivePatch(CanvasKit) {
const ELEM = 'interdrawpatch';
const surface = CanvasKit.MakeCanvasSurface(ELEM);
if (!surface) {
console.error('Could not make surface');
return;
}
let live_corner, live_index;
const paint = new CanvasKit.Paint();
const pts_paint = new CanvasKit.Paint();
pts_paint.setStyle(CanvasKit.PaintStyle.Stroke);
pts_paint.setStrokeWidth(9);
pts_paint.setStrokeCap(CanvasKit.StrokeCap.Round);
const line_paint = new CanvasKit.Paint();
line_paint.setStyle(CanvasKit.PaintStyle.Stroke);
line_paint.setStrokeWidth(2);
const colors = [CanvasKit.RED, CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.CYAN];
const patch = [
[ 10,170, 10, 10, 170, 10], // prev_vector, point, next_vector
[340, 10, 500, 10, 500,170],
[500,340, 500,500, 340,500],
[170,500, 10,500, 10,340],
];
function get_corner(corner, index) {
return [corner[index*2+0], corner[index*2+1]];
}
function push_xy(array, xy) {
array.push(xy[0], xy[1]);
}
function patch_to_cubics(patch) {
const array = [];
push_xy(array, get_corner(patch[0],1));
push_xy(array, get_corner(patch[0],2));
for (let i = 1; i < 4; ++i) {
push_xy(array, get_corner(patch[i],0));
push_xy(array, get_corner(patch[i],1));
push_xy(array, get_corner(patch[i],2));
}
push_xy(array, get_corner(patch[0],0));
return array;
}
function drawFrame(canvas) {
const cubics = patch_to_cubics(patch);
canvas.drawColor(CanvasKit.WHITE);
canvas.drawPatch(cubics, colors, null, null, paint);
if (live_corner) {
canvas.drawPoints(CanvasKit.PointMode.Polygon, live_corner, line_paint);
}
canvas.drawPoints(CanvasKit.PointMode.Points, cubics, pts_paint);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
function length2(x, y) {
return x*x + y*y;
}
function hit_test(x,y, x1,y1) {
return length2(x-x1, y-y1) <= 10*10;
}
function pointer_up(e) {
live_corner = null;
live_index = null;
}
function pointer_down(e) {
live_corner = null;
live_index = null;
for (p of patch) {
for (let i = 0; i < 6; i += 2) {
if (hit_test(p[i], p[i+1], e.offsetX, e.offsetY)) {
live_corner = p;
live_index = i;
}
}
}
}
function pointer_move(e) {
if (e.pressure && live_corner) {
if (live_index == 2) {
// corner
const dx = e.offsetX - live_corner[2];
const dy = e.offsetY - live_corner[3];
for (let i = 0; i < 3; ++i) {
live_corner[i*2+0] += dx;
live_corner[i*2+1] += dy;
}
} else {
// control-point
live_corner[live_index+0] = e.offsetX;
live_corner[live_index+1] = e.offsetY;
}
}
}
document.getElementById(ELEM).addEventListener('pointermove', pointer_move);
document.getElementById(ELEM).addEventListener('pointerdown', pointer_down);
document.getElementById(ELEM).addEventListener('pointerup', pointer_up);
preventScrolling(document.getElementById(ELEM));
}
function PathPersonExample(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('pathperson');
if (!surface) {
console.error('Could not make surface');
return;
}
function drawFrame(canvas) {
const paint = new CanvasKit.Paint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const path = new CanvasKit.Path();
path.moveTo(10, 10);
path.lineTo(100, 10);
path.moveTo(10, 10);
path.lineTo(10, 200);
path.moveTo(10, 100);
path.lineTo(100,100);
path.moveTo(10, 200);
path.lineTo(100, 200);
canvas.drawPath(path, paint);
path.delete();
paint.delete();
}
// Intentionally just draw frame once
surface.drawOnce(drawFrame);
}
function PathExample(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('paths');
if (!surface) {
console.error('Could not make surface');
return;
}
function drawFrame(canvas) {
const paint = new CanvasKit.Paint();
paint.setStrokeWidth(1.0);
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
const path = new CanvasKit.Path();
path.moveTo(20, 5);
path.lineTo(30, 20);
path.lineTo(40, 10);
path.lineTo(50, 20);
path.lineTo(60, 0);
path.lineTo(20, 5);
path.moveTo(20, 80);
path.cubicTo(90, 10, 160, 150, 190, 10);
path.moveTo(36, 148);
path.quadTo(66, 188, 120, 136);
path.lineTo(36, 148);
path.moveTo(150, 180);
path.arcToTangent(150, 100, 50, 200, 20);
path.lineTo(160, 160);
path.moveTo(20, 120);
path.lineTo(20, 120);
canvas.drawPath(path, paint);
const rrect = CanvasKit.RRectXY([100, 10, 140, 62], 10, 4);
const rrectPath = new CanvasKit.Path().addRRect(rrect, true);
canvas.drawPath(rrectPath, paint);
rrectPath.delete();
path.delete();
paint.delete();
}
// Intentionally just draw frame once
surface.drawOnce(drawFrame);
}
function preventScrolling(canvas) {
canvas.addEventListener('touchmove', (e) => {
// Prevents touch events in the canvas from scrolling the canvas.
e.preventDefault();
e.stopPropagation();
});
}
function InkExample(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('ink');
if (!surface) {
console.error('Could not make surface');
return;
}
let paint = new CanvasKit.Paint();
paint.setAntiAlias(true);
paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setStrokeWidth(4.0);
paint.setPathEffect(CanvasKit.PathEffect.MakeCorner(50));
// Draw I N K
let path = new CanvasKit.Path();
path.moveTo(80, 30);
path.lineTo(80, 80);
path.moveTo(100, 80);
path.lineTo(100, 15);
path.lineTo(130, 95);
path.lineTo(130, 30);
path.moveTo(150, 30);
path.lineTo(150, 80);
path.moveTo(170, 30);
path.lineTo(150, 55);
path.lineTo(170, 80);
let paths = [path];
let paints = [paint];
function drawFrame(canvas) {
canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
for (let i = 0; i < paints.length && i < paths.length; i++) {
canvas.drawPath(paths[i], paints[i]);
}
surface.requestAnimationFrame(drawFrame);
}
let hold = false;
let interact = (e) => {
let type = e.type;
if (type === 'lostpointercapture' || type === 'pointerup' || !e.pressure ) {
hold = false;
return;
}
if (hold) {
path.lineTo(e.offsetX, e.offsetY);
} else {
paint = paint.copy();
paint.setColor(CanvasKit.Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() + .2));
paints.push(paint);
path = new CanvasKit.Path();
paths.push(path);
path.moveTo(e.offsetX, e.offsetY);
}
hold = true;
};
document.getElementById('ink').addEventListener('pointermove', interact);
document.getElementById('ink').addEventListener('pointerdown', interact);
document.getElementById('ink').addEventListener('lostpointercapture', interact);
document.getElementById('ink').addEventListener('pointerup', interact);
preventScrolling(document.getElementById('ink'));
surface.requestAnimationFrame(drawFrame);
}
function starPath(CanvasKit, X=128, Y=128, R=116) {
let p = new CanvasKit.Path();
p.moveTo(X + R, Y);
for (let i = 1; i < 8; i++) {
let a = 2.6927937 * i;
p.lineTo(X + R * Math.cos(a), Y + R * Math.sin(a));
}
return p;
}
function CanvasAPI1(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api1_c');
let skPromise = fetch(cdn + 'test.png')
// if clients want to use a Blob, they are responsible
// for reading it themselves.
.then((response) => response.arrayBuffer())
.then((buffer) => {
skcanvas._img = skcanvas.decodeImage(buffer);
});
let realPromise = fetch(cdn + 'test.png')
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((bitmap) => {
realCanvas._img = bitmap;
});
let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
'family': 'Bungee',
'style': 'normal',
'weight': '400',
}).load().then((font) => {
document.fonts.add(font);
});
let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
(response) => response.arrayBuffer()).then(
(buffer) => {
// loadFont is synchronous
skcanvas.loadFont(buffer, {
'family': 'Bungee',
'style': 'normal',
'weight': '400',
});
});
Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, 300, 300);
ctx.fillStyle = 'black';
ctx.font = '26px Bungee';
ctx.rotate(.1);
ctx.fillText('Awesome ', 25, 100);
ctx.strokeText('Groovy!', 200, 100);
// Draw line under Awesome
ctx.strokeStyle = 'rgba(125,0,0,0.5)';
ctx.beginPath();
ctx.lineWidth = 6;
ctx.moveTo(25, 105);
ctx.lineTo(200, 105);
ctx.stroke();
// squished vertically
ctx.globalAlpha = 0.7;
ctx.imageSmoothingQuality = 'medium';
ctx.drawImage(canvas._img, 150, 150, 150, 100);
ctx.rotate(-.2);
ctx.imageSmoothingEnabled = false;
ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
let idata = ctx.getImageData(80, 220, 40, 45);
ctx.putImageData(idata, 250, 10);
ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
ctx.resetTransform();
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
ctx.strokeRect(200, 10, 40, 45);
idata = ctx.createImageData(10, 20);
ctx.putImageData(idata, 10, 10);
}
document.getElementById('api1').src = skcanvas.toDataURL();
skcanvas.dispose();
});
}
function CanvasAPI2(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api2_c');
realCanvas.width = 300;
realCanvas.height = 300;
// svg data for a clock
skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.scale(1.5, 1.5);
ctx.moveTo(20, 5);
ctx.lineTo(30, 20);
ctx.lineTo(40, 10);
ctx.lineTo(50, 20);
ctx.lineTo(60, 0);
ctx.lineTo(20, 5);
ctx.moveTo(20, 80);
ctx.bezierCurveTo(90, 10, 160, 150, 190, 10);
ctx.moveTo(36, 148);
ctx.quadraticCurveTo(66, 188, 120, 136);
ctx.lineTo(36, 148);
ctx.rect(5, 170, 20, 25);
ctx.moveTo(150, 180);
ctx.arcTo(150, 100, 50, 200, 20);
ctx.lineTo(160, 160);
ctx.moveTo(20, 120);
ctx.arc(20, 120, 18, 0, 1.75 * Math.PI);
ctx.lineTo(20, 120);
ctx.moveTo(150, 5);
ctx.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI);
ctx.lineWidth = 4/3;
ctx.stroke();
// make a clock
ctx.stroke(canvas._path);
// Test edgecases and draw direction
ctx.beginPath();
ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
ctx.stroke();
ctx.beginPath();
ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
ctx.stroke();
ctx.beginPath();
ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
ctx.stroke();
}
document.getElementById('api2').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function CanvasAPI3(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api3_c');
realCanvas.width = 300;
realCanvas.height = 300;
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.rect(10, 10, 20, 20);
ctx.scale(2.0, 4.0);
ctx.rect(30, 10, 20, 20);
ctx.resetTransform();
ctx.rotate(Math.PI / 3);
ctx.rect(50, 10, 20, 20);
ctx.resetTransform();
ctx.translate(30, -2);
ctx.rect(70, 10, 20, 20);
ctx.resetTransform();
ctx.translate(60, 0);
ctx.rotate(Math.PI / 6);
ctx.transform(1.5, 0, 0, 0.5, 0, 0, 0); // effectively scale
ctx.rect(90, 10, 20, 20);
ctx.resetTransform();
ctx.save();
ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
ctx.rect(110, 10, 20, 20);
ctx.lineTo(110, 0);
ctx.restore();
ctx.lineTo(220, 120);
ctx.scale(3.0, 3.0);
ctx.font = '6pt Noto Mono';
ctx.fillText('This text should be huge', 10, 80);
ctx.resetTransform();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(250, 30);
ctx.lineTo(250, 80);
ctx.scale(3.0, 3.0);
ctx.lineTo(280/3, 90/3);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 5;
ctx.stroke();
}
document.getElementById('api3').src = skcanvas.toDataURL();
skcanvas.dispose();
}
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 Noto Mono';
ctx.fillText('This should be shadowed', 20, 80);
ctx.beginPath();
ctx.lineWidth = 6;
ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
ctx.scale(2, 1);
ctx.moveTo(10, 290);
ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
ctx.resetTransform();
ctx.scale(3, 1);
ctx.moveTo(10, 290);
ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
ctx.stroke();
}
document.getElementById('api4').src = skcanvas.toDataURL();
skcanvas.dispose();
}
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 Noto Mono';
ctx.strokeText('Dashed', 20, 350);
ctx.fillText('Not Dashed', 20, 400);
}
document.getElementById('api5').src = skcanvas.toDataURL();
skcanvas.dispose();
}
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');
let 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();
let 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();
skcanvas.dispose();
}
function CanvasAPI7(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api7_c');
let skPromise = fetch(cdn + 'test.png')
// if clients want to use a Blob, they are responsible
// for reading it themselves.
.then((response) => response.arrayBuffer())
.then((buffer) => {
skcanvas._img = skcanvas.decodeImage(buffer);
});
let realPromise = fetch(cdn + 'test.png')
.then((response) => response.blob())
.then((blob) => createImageBitmap(blob))
.then((bitmap) => {
realCanvas._img = bitmap;
});
Promise.all([realPromise, skPromise]).then(() => {
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.fillStyle = '#EEE';
ctx.fillRect(0, 0, 300, 300);
ctx.lineWidth = 20;
ctx.scale(0.1, 0.2);
let pattern = ctx.createPattern(canvas._img, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 1500, 750);
pattern = ctx.createPattern(canvas._img, 'repeat-x');
ctx.fillStyle = pattern;
ctx.fillRect(1500, 0, 3000, 750);
ctx.globalAlpha = 0.7;
pattern = ctx.createPattern(canvas._img, 'repeat-y');
ctx.fillStyle = pattern;
ctx.fillRect(0, 750, 1500, 1500);
ctx.strokeRect(0, 750, 1500, 1500);
pattern = ctx.createPattern(canvas._img, 'no-repeat');
ctx.fillStyle = pattern;
pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
ctx.fillRect(0, 0, 3000, 1500);
}
document.getElementById('api7').src = skcanvas.toDataURL();
skcanvas.dispose();
});
}
function CanvasAPI8(CanvasKit) {
let skcanvas = CanvasKit.MakeCanvas(300, 300);
let realCanvas = document.getElementById('api8_c');
function drawPoint(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
const IN = 'purple';
const OUT = 'orange';
const SCALE = 4;
const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
[6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
[25, 25], [26, 26], [27, 27]];
const tests = [
{
xOffset: 0,
yOffset: 0,
fillType: 'nonzero',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
},
{
xOffset: 30,
yOffset: 0,
fillType: 'evenodd',
strokeWidth: 0,
testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
},
{
xOffset: 0,
yOffset: 30,
fillType: null,
strokeWidth: 1,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
{
xOffset: 30,
yOffset: 30,
fillType: null,
strokeWidth: 2,
testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
},
];
for (let canvas of [skcanvas, realCanvas]) {
let ctx = canvas.getContext('2d');
ctx.font = '11px Noto Mono';
// Draw some visual aids
ctx.fillText('path-nonzero', 30, 15);
ctx.fillText('path-evenodd', 150, 15);
ctx.fillText('stroke-1px-wide', 30, 130);
ctx.fillText('stroke-2px-wide', 150, 130);
ctx.fillText('purple is IN, orange is OUT', 10, 280);
// Scale up to make single pixels easier to see
ctx.scale(SCALE, SCALE);
for (let test of tests) {
ctx.beginPath();
let xOffset = test.xOffset;
let yOffset = test.yOffset;
ctx.fillStyle = '#AAA';
ctx.lineWidth = test.strokeWidth;
ctx.rect(5+xOffset, 5+yOffset, 20, 20);
ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
if (test.fillType) {
ctx.fill(test.fillType);
} else {
ctx.stroke();
}
for (let pt of pts) {
let [x, y] = pt;
x += xOffset;
y += yOffset;
// naively apply transform when querying because the points queried
// ignore the CTM.
if (test.testFn(ctx, x, y)) {
drawPoint(ctx, x, y, IN);
} else {
drawPoint(ctx, x, y, OUT);
}
}
}
}
document.getElementById('api8').src = skcanvas.toDataURL();
skcanvas.dispose();
}
function VertexAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('vertex1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.Paint();
// See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
// for original c++ version.
let points = [0, 0, 250, 0, 100, 100, 0, 250];
let colors = [CanvasKit.RED, CanvasKit.BLUE,
CanvasKit.YELLOW, CanvasKit.CYAN];
let vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
points, null, colors,
false /*isVolatile*/);
canvas.drawVertices(vertices, CanvasKit.BlendMode.Src, paint);
vertices.delete();
// See https://fiddle.skia.org/c/e8bdae9bea3227758989028424fcac3d
// for original c++ version.
points = [300, 300, 50, 300, 200, 200, 300, 50 ];
let texs = [ 0, 0, 0, 250, 250, 250, 250, 0 ];
vertices = CanvasKit.MakeVertices(CanvasKit.VertexMode.TriangleFan,
points, texs, colors);
let shader = CanvasKit.Shader.MakeLinearGradient([0, 0], [250, 0],
colors, null, CanvasKit.TileMode.Clamp);
paint.setShader(shader);
canvas.drawVertices(vertices, CanvasKit.BlendMode.Darken, paint);
surface.flush();
shader.delete();
paint.delete();
surface.delete();
}
function GradiantAPI1(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('gradient1');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
let paint = new CanvasKit.Paint();
// See https://fiddle.skia.org/c/f48b22eaad1bb7adcc3faaa321754af6
// for original c++ version.
let colors = [CanvasKit.BLUE, CanvasKit.YELLOW, CanvasKit.RED];
let pos = [0, .7, 1.0];
let transform = [2, 0, 0,
0, 2, 0,
0, 0, 1];
let shader = CanvasKit.Shader.MakeRadialGradient([150, 150], 130, colors,
pos, CanvasKit.TileMode.Mirror, transform);
paint.setShader(shader);
const textFont = new CanvasKit.Font(null, 75);
const textBlob = CanvasKit.TextBlob.MakeFromText('Radial', textFont);
canvas.drawTextBlob(textBlob, 10, 200, paint);
paint.delete();
textFont.delete();
textBlob.delete();
surface.flush();
}
function TextOnPathAPI1(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('textonpath');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
paint.setStyle(CanvasKit.PaintStyle.Stroke);
paint.setAntiAlias(true);
const font = new CanvasKit.Font(null, 24);
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
const arc = new CanvasKit.Path();
arc.arcToOval(CanvasKit.LTRBRect(20, 40, 280, 300), -160, 140, true);
arc.lineTo(210, 140);
arc.arcToOval(CanvasKit.LTRBRect(20, 0, 280, 260), 160, -140, true);
const str = 'This téxt should follow the curve across contours...';
const textBlob = CanvasKit.TextBlob.MakeOnPath(str, arc, font);
canvas.drawPath(arc, paint);
canvas.drawTextBlob(textBlob, 0, 0, fontPaint);
surface.flush();
textBlob.delete();
arc.delete();
paint.delete();
font.delete();
fontPaint.delete();
}
function SurfaceAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('surfaces');
if (!surface) {
console.error('Could not make surface');
return;
}
const grContext = surface.grContext;
// create a subsurface as a temporary workspace.
const subSurface = surface.makeSurface({
width: 50,
height: 50,
alphaType: CanvasKit.AlphaType.Premul,
colorType: CanvasKit.ColorType.RGBA_8888,
colorSpace: CanvasKit.ColorSpace.SRGB,
});
if (!subSurface) {
console.error('Could not make subsurface');
return;
}
// draw a small "scene"
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.Color(139, 228, 135, 0.95)); // greenish
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setAntiAlias(true);
const subCanvas = subSurface.getCanvas();
subCanvas.clear(CanvasKit.BLACK);
subCanvas.drawRect(CanvasKit.LTRBRect(5, 15, 45, 40), paint);
paint.setColor(CanvasKit.Color(214, 93, 244)); // purplish
for (let i = 0; i < 10; i++) {
const x = Math.random() * 50;
const y = Math.random() * 50;
subCanvas.drawOval(CanvasKit.XYWHRect(x, y, 6, 6), paint);
}
// Snap it off as an Image - this image will be in the form the
// parent surface prefers (e.g. Texture for GPU / Raster for CPU).
const img = subSurface.makeImageSnapshot();
// clean up the temporary surface (which also cleans up subCanvas)
subSurface.delete();
paint.delete();
// Make it repeat a bunch with a shader
const pattern = img.makeShaderCubic(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Mirror,
1/3, 1/3);
const patternPaint = new CanvasKit.Paint();
patternPaint.setShader(pattern);
let i = 0;
function drawFrame(canvas) {
i++;
canvas.clear(CanvasKit.WHITE);
canvas.drawOval(CanvasKit.LTRBRect(i % 60, i % 60, 300 - (i% 60), 300 - (i % 60)), patternPaint);
surface.requestAnimationFrame(drawFrame);
// if (grContext) {
// console.log(grContext.getResourceCacheUsageBytes() + ' bytes used in the GPU');
// }
}
surface.requestAnimationFrame(drawFrame);
}
function AtlasAPI1(CanvasKit, imgData) {
if (!CanvasKit || !imgData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('atlas');
if (!surface) {
console.error('Could not make surface');
return;
}
const img = CanvasKit.MakeImageFromEncoded(imgData);
const paint = new CanvasKit.Paint();
paint.setColor(CanvasKit.Color(0, 0, 0, 0.8));
const srcs = new CanvasKit.RectBuilder();
srcs.push(0, 0, 250, 250 ); // LTRB
srcs.push(250, 0, 500, 250 );
const dsts = new CanvasKit.RSXFormBuilder();
dsts.push(.5, 0, 0, 0); // scos, ssin, tx, ty
dsts.push(0, .8, 200, 100);
const colors = new CanvasKit.ColorBuilder();
colors.push(CanvasKit.ColorAsInt(85, 170, 10, 128)); // light green
colors.push(CanvasKit.ColorAsInt(51, 51, 191, 128)); // light blue
let i = 0;
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
i++;
let scale = 0.5 + Math.sin(i/40)/4;
// update the coordinates of existing sprites - note that this
// does not require a full re-copy of the full array; they are
// updated in-place.
dsts.set(0, 0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200);
dsts.set(1, scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100);
canvas.drawAtlas(img, srcs, dsts, paint, CanvasKit.BlendMode.Plus, colors,
{filter: CanvasKit.FilterMode.Nearest});
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
}
Added CanvasKit.MakeImageFromCanvasImageSource which is useful as an alternative to CanvasKit.MakeImageFromEncoded, when used with Browser APIs for loading/decoding images. - `CanvasKit.MakeImageFromCanvasImageSource` takes either an HTMLImageElement, SVGImageElement, HTMLVideoElement, HTMLCanvasElement, ImageBitmap, or OffscreenCanvas and returns an SkImage. This function is an alternative to `CanvasKit.MakeImageFromEncoded` for creating SkImages when loading and decoding images. In the future, codesize of CanvasKit may be able to be reduced by removing image codecs in wasm, if browser APIs for decoding images are used along with `CanvasKit.MakeImageFromCanvasImageSource` instead of `CanvasKit.MakeImageFromEncoded`. - Three usage examples of `CanvasKit.MakeImageFromCanvasImageSource` in core.spec.ts. These examples use browser APIs to decode images including 2d canvas, bitmaprenderer canvas, HTMLImageElement and Blob. - Added support for asynchronous callbacks in perfs and tests. Here are notes on the image decoding approaches we tested and perfed in the process of finding ways to use Browser APIs to decode images: 1. pipeline: ArrayBuffer → ImageData → ctx.putImageData → context.getImageData → Uint8Array → CanvasKit.MakeImage ❌ Problem: ImageData constructor expects decoded bytes already. 2. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded (async function) pipeline: ArrayBuffer → Blob -> HTMLImageElement -> draw on Canvas2d -> context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 3rd place (in my testing locally) 3. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2 (async function) ArrayBuffer → Blob → ImageBitmap → draw on Canvas2d → context.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 2nd place (in my testing locally) 4. interface.js - CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded3 (async function) ArrayBuffer → Blob → ImageBitmap → draw on canvas 1 using bitmaprenderer context → draw canvas 1 on canvas 2 using drawImage → context2d.getImageData → Uint8Array → CanvasKit.MakeImage ✅ Works ⏱ Performance: 1st place (in my testing locally) - quite surprising, this in some ways seems to be a more roundabout way of CanvasKit.ExperimentalCanvas2DMakeImageFromEncoded2, but it seems bitmaprenderer context is fairly fast. Bug: skia:10360 Change-Id: I6fe94b8196dfd1ad0d8929f04bb1697da537ca18 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/295390 Reviewed-by: Kevin Lubick <kjlubick@google.com>
2020-06-15 18:53:27 +00:00
async function DecodeAPI(CanvasKit, imgData) {
if (!CanvasKit || !imgData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('decode');
if (!surface) {
console.error('Could not make surface');
return;
}
const blob = new Blob([ imgData ]);
// ImageBitmap is not supported in Safari
const imageBitmap = await createImageBitmap(blob);
const img = await CanvasKit.MakeImageFromCanvasImageSource(imageBitmap);
surface.drawOnce((canvas) => {
canvas.drawImage(img, 0, 0, null);
});
}
</script>