skia2/modules/canvaskit/perf/canvas.bench.js
Elliot Evans 2879619a29 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 19:35:09 +00:00

238 lines
8.3 KiB
JavaScript

describe('Basic Canvas ops', () => {
let container = document.createElement('div');
document.body.appendChild(container);
beforeEach(async () => {
container.innerHTML = `
<canvas width=600 height=600 id=test></canvas>`;
await LoadCanvasKit;
});
afterEach(function() {
container.innerHTML = '';
});
it('can draw 100 colored regions', () => {
function setup(ctx) {
ctx.surface = CanvasKit.MakeCanvasSurface('test');
ctx.canvas = ctx.surface.getCanvas();
};
function test(ctx) {
for (let i=0; i<100; i++) {
ctx.canvas.save();
ctx.canvas.clipRect(CanvasKit.LTRBRect(i, i, i+100, i+100),
CanvasKit.ClipOp.Intersect, false);
ctx.canvas.drawColor(CanvasKit.Color4f(1-i/100, 1.0, i/100, 1.0),
CanvasKit.BlendMode.SrcOver);
ctx.canvas.restore();
}
ctx.surface.flush();
};
function teardown(ctx) {
ctx.surface.delete();
};
benchmarkAndReport('canvas_drawColor', setup, test, teardown);
});
it('can compute tonal colors', () => {
function setup(ctx) {};
function test(ctx) {
for (let i = 0; i < 10; i++) {
const input = {
ambient: randomColor(),
spot: randomColor(),
};
const out = CanvasKit.computeTonalColors(input);
if (out.spot[2] > 10 || out.ambient[3] > 10) {
// Something to make sure v8 can't optimize away the return value
throw 'not possible';
}
}
};
function teardown(ctx) {};
benchmarkAndReport('computeTonalColors', setup, test, teardown);
});
function randomColor() {
return CanvasKit.Color4f(Math.random(), Math.random(), Math.random(), Math.random());
}
it('can get and set the color to a paint', () => {
function setup(ctx) {
ctx.paint = new CanvasKit.SkPaint();
};
function test(ctx) {
for (let i = 0; i < 10; i++) {
ctx.paint.setColor(randomColor());
const color = ctx.paint.getColor();
if (color[3] > 4) {
// Something to make sure v8 can't optimize away the return value
throw 'not possible';
}
}
};
function teardown(ctx) {
ctx.paint.delete();
};
benchmarkAndReport('paint_setColor_getColor', setup, test, teardown);
});
it('can set the color to a paint by components', () => {
function setup(ctx) {
ctx.paint = new CanvasKit.SkPaint();
};
function test(ctx) {
const r = Math.random();
const g = Math.random();
const b = Math.random();
const a = Math.random();
for (let i = 0; i < 10000; i++) {
ctx.paint.setColorComponents(r, g, b, a);
}
};
function teardown(ctx) {
ctx.paint.delete();
};
benchmarkAndReport('paint_setColorComponents', setup, test, teardown);
});
it('can draw a shadow with tonal colors', () => {
function setup(ctx) {
ctx.surface = CanvasKit.MakeCanvasSurface('test');
ctx.canvas = ctx.surface.getCanvas();
};
const input = {
ambient: CanvasKit.Color4f(0.2, 0.1, 0.3, 0.5),
spot: CanvasKit.Color4f(0.8, 0.8, 0.9, 0.9),
};
const lightRadius = 30;
const flags = 0;
const lightPos = [250,150,300];
const zPlaneParams = [0,0,1];
const path = starPath(CanvasKit);
function test(ctx) {
const out = CanvasKit.computeTonalColors(input);
ctx.canvas.drawShadow(path, zPlaneParams, lightPos, lightRadius,
out.ambient, out.spot, flags);
ctx.surface.flush();
};
function teardown(ctx) {
ctx.surface.delete();
};
benchmarkAndReport('canvas_drawShadow', setup, test, teardown);
});
it('can draw a gradient with an array of 1M colors', () => {
function setup(ctx) {
ctx.surface = CanvasKit.MakeCanvasSurface('test');
ctx.canvas = ctx.surface.getCanvas();
};
function test(ctx) {
ctx.canvas.clear(CanvasKit.WHITE);
const num = 1000000;
colors = Array(num);
positions = Array(num);
for (let i=0; i<num; i++) {
colors[i] = CanvasKit.Color((i*37)%255, 255, (1-i/num)*255);
positions[i] = i/num;
}
const shader = CanvasKit.SkShader.MakeRadialGradient(
[300, 300], 50, // center, radius
colors, positions,
CanvasKit.TileMode.Mirror,
);
const paint = new CanvasKit.SkPaint();
paint.setStyle(CanvasKit.PaintStyle.Fill);
paint.setShader(shader);
ctx.canvas.drawPaint(paint);
ctx.surface.flush();
paint.delete();
};
function teardown(ctx) {
ctx.surface.delete();
};
benchmarkAndReport('canvas_drawHugeGradient', setup, test, teardown);
});
function NO_OP() {}
function htmlImageElementToDataURL(htmlImageElement) {
const canvas = document.createElement('canvas');
canvas.height = htmlImageElement.height;
canvas.width = htmlImageElement.width;
const ctx = canvas.getContext('2d')
ctx.drawImage(htmlImageElement, 0, 0);
return canvas.toDataURL();
}
const TEST_IMAGE_FILENAMES = [ 'test_64x64.png', 'test_512x512.png', 'test_1500x959.jpg'];
// This for loop generates two perf cases for each test image. One uses browser APIs
// to decode an image, and the other uses codecs included in the CanvasKit wasm to decode an
// image. wasm codec Image decoding is faster (50 microseconds vs 20000 microseconds), but
// including codecs in wasm increases the size of the CanvasKit wasm binary.
for (const testImageFilename of TEST_IMAGE_FILENAMES) {
const imageResponsePromise = fetch(`/assets/${testImageFilename}`);
const imageArrayBufferPromise = imageResponsePromise.then((imageResponse) => imageResponse.arrayBuffer());
const htmlImageElement = new Image();
htmlImageElementLoadPromise = new Promise((resolve) => htmlImageElement.addEventListener('load', resolve));
// Create a data url of the image so that load and decode time can be measured
// while hopefully ignoring the time of getting the image from disk / the network.
imageDataURLPromise = htmlImageElementLoadPromise.then(() => htmlImageElementToDataURL(htmlImageElement));
htmlImageElement.src = `/assets/${testImageFilename}`;
it('can decode an image using HTMLImageElement and Canvas2D', async () => {
const imageDataURL = await imageDataURLPromise;
async function test(ctx) {
const image = new Image();
// Testing showed that waiting for the load event is faster than waiting for
// image.decode().
// HTMLImageElement.decode() reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode
const promise = new Promise((resolve) => image.addEventListener('load', resolve));
image.src = imageDataURL;
await promise;
const img = await CanvasKit.MakeImageFromCanvasImageSource(image);
img.delete();
}
await asyncBenchmarkAndReport(`canvas_${testImageFilename}_HTMLImageElementDecoding`, NO_OP, test, NO_OP);
});
it('can decode an image using codecs in wasm', async () => {
const encodedArrayBuffer = await imageArrayBufferPromise;
function test(ctx) {
const img = CanvasKit.MakeImageFromEncoded(encodedArrayBuffer);
img.delete();
}
benchmarkAndReport(`canvas_${testImageFilename}_wasmImageDecoding`, NO_OP, test, NO_OP);
});
}
});