skia2/modules/canvaskit/npm_build/example.html

1250 lines
38 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;
}
#api5_c, #api6_c {
width: 300px;
height: 300px;
}
</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>
<canvas id=drawGlyphs width=300 height=300></canvas>
<h2> Interactive drawPatch</h2>
<canvas id=interdrawpatch width=512 height=512></canvas>
<script type="text/javascript" src="/build/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) => '/build/'+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);
DrawGlyphsAPI1(CanvasKit);
SurfaceAPI1(CanvasKit);
[canvaskit] Add Freetype/Fonts to Bazel Build This re-works src/ports/BUILD.bazel to work like our other BUILD files, i.e. one rule "srcs" that brings in the necessary private filegroups. To work around an abort with LLVM [1], we have to go back to an earlier version of emscripten (temporarily?). Future work should look at using transitions [2] to allow various executables (e.g. CanvasKit, DM) to set their own set of Bazel flags, w/o the build invokers having to specify them. These transitions might be able to handle more complex cases that we currently use if statements in GN to deal with. The Freetype build rule was created by taking the BUILD.gn rule, adding in all the sources listed there and then playing compile-whack-a-mole to add in all the headers and included .c files. Suggested Review Order: - third_party/BUILD.bazel to see freetype build rules - bazel/common_config_settings/ to see treatment of fontmgr like codecs (many possible) and fontmgr_factory (only one). - src/ports/BUILD.bazel - BUILD.bazel - modules/canvaskit/BUILD.bazel. Take note of the gen_rule that calls tools/embed_resources.py to produce the .cpp file containing the embedded font data. - Everything else. [1] https://github.com/emscripten-core/emscripten/issues/15528 [2] https://github.com/bazelbuild/examples/tree/main/rules/starlark_configurations/cc_binary_selectable_copts Bug: skia:12541 Change-Id: I08dab82a901d80507007b354ca20cbfad2c2388f Reviewed-on: https://skia-review.googlesource.com/c/skia/+/471636 Reviewed-by: Ben Wagner <bungeman@google.com> Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
2021-11-16 19:09:44 +00:00
if (CanvasKit.MakeCanvas){
CanvasAPI1(CanvasKit);
CanvasAPI2(CanvasKit);
CanvasAPI3(CanvasKit);
CanvasAPI4(CanvasKit);
CanvasAPI5(CanvasKit);
CanvasAPI6(CanvasKit);
CanvasAPI7(CanvasKit);
CanvasAPI8(CanvasKit);
} else {
console.log("Skipping CanvasAPI1 because it's not compiled in");
}
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 roboto = CanvasKit.Typeface.MakeFreeTypeFaceFromData(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, CanvasKit.BlendMode.Dst, 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);
let text = ctx.measureText('Awesome');
ctx.fillText('Awesome ', 25, 100);
ctx.strokeText('Groovy!', 35 + text.width, 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); // 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.Dst, 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 DrawGlyphsAPI1(CanvasKit) {
const surface = CanvasKit.MakeSWCanvasSurface('drawGlyphs');
if (!surface) {
console.error('Could not make surface');
return;
}
const canvas = surface.getCanvas();
const paint = new CanvasKit.Paint();
const font = new CanvasKit.Font(null, 16);
paint.setAntiAlias(true);
let glyphs = [];
let positions = [];
for (let i = 0; i < 256; ++i) {
glyphs.push(i);
positions.push((i % 16) * 16);
positions.push(Math.round(i/16) * 16);
}
canvas.drawGlyphs(glyphs, positions, 16, 20, font, paint);
surface.flush();
paint.delete();
font.delete();
}
function SurfaceAPI1(CanvasKit) {
const surface = CanvasKit.MakeCanvasSurface('surfaces');
if (!surface) {
console.error('Could not make surface');
return;
}
// 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);
}
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));
// Allocate space for 2 rectangles.
const srcs = CanvasKit.Malloc(Float32Array, 8);
srcs.toTypedArray().set([
0, 0, 250, 250, // LTRB
250, 0, 500, 250
]);
// Allocate space for 2 RSXForms
const dsts = CanvasKit.Malloc(Float32Array, 8);
dsts.toTypedArray().set([
.5, 0, 0, 0, // scos, ssin, tx, ty
0, .8, 200, 100
]);
// Allocate space for 4 colors.
const colors = new CanvasKit.Malloc(Uint32Array, 2);
colors.toTypedArray().set([
CanvasKit.ColorAsInt( 85, 170, 10, 128), // light green
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.toTypedArray().set([0.5, 0, (2*i)%200, (5*Math.round(i/200)) % 200], 0);
dsts.toTypedArray().set([scale*Math.sin(i/20), scale*Math.cos(i/20), 200, 100], 4);
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>