Skip to content

Commit c21dc07

Browse files
committed
[canvaskit] Add demo on how to decode images in web worker
Change-Id: I3c9107799c5df2c39a8ea8ddf106978786de79f7 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/317389 Reviewed-by: Yegor Jbanov <[email protected]> Reviewed-by: Nathaniel Nifong <[email protected]>
1 parent 952f8f1 commit c21dc07

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<title>Image Decoding Demo</title>
3+
<meta charset="utf-8" />
4+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
7+
<style>
8+
canvas {
9+
border: 1px dashed grey;
10+
}
11+
12+
.canvas-container {
13+
float: left;
14+
}
15+
</style>
16+
17+
<body>
18+
<h1>CanvasKit loading images in a webworker (using browser-based decoders)</h1>
19+
<p>NOTE: this demo currently only works in chromium-based browsers, where
20+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas#Browser_compatibility">
21+
Offscreen Canvas
22+
</a>
23+
is supported.
24+
</p>
25+
26+
<div class="canvas-container">
27+
<h2>Decoding on main thread</h2>
28+
<canvas id="main-thread-canvas" width=500 height=500></canvas>
29+
<div>
30+
<button id="load-button-main">Decode Image on Main Thread</button>
31+
<button id="load-button-web">Decode Image with Web Worker</button>
32+
<button id="clear-button">Clear Image</button>
33+
</div>
34+
<p>
35+
Notice that decoding the image on the main thread pauses the circle animation until the
36+
image is ready to be drawn, where as decoding it in a webworker does not have this pause
37+
(or at least not as drastic a pause). You may want to reload the page, as browsers are
38+
smart enough to not have to re-decode the image on subsequent requests.
39+
</p>
40+
</div>
41+
42+
</body>
43+
<script type="text/javascript" src="https://particles.skia.org/static/canvaskit.js"></script>
44+
<script type="text/javascript" src="main.js"></script>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Inspired by https://gist.github.com/ahem/d19ee198565e20c6f5e1bcd8f87b3408
2+
const worker = new Worker('worker.js');
3+
4+
const canvasKitInitPromise =
5+
CanvasKitInit({locateFile: (file) => 'https://particles.skia.org/static/'+file});
6+
7+
const bigImagePromise =
8+
fetch('https://upload.wikimedia.org/wikipedia/commons/3/30/Large_Gautama_Buddha_statue_in_Buddha_Park_of_Ravangla%2C_Sikkim.jpg')
9+
.then((response) => response.blob());
10+
11+
Promise.all([
12+
canvasKitInitPromise,
13+
bigImagePromise
14+
]).then(([
15+
CanvasKit,
16+
imageBlob
17+
]) => {
18+
const surface = CanvasKit.MakeWebGLCanvasSurface('main-thread-canvas', null);
19+
if (!surface) {
20+
throw 'Could not make main thread canvas surface';
21+
}
22+
23+
const paint = new CanvasKit.SkPaint();
24+
paint.setColor(CanvasKit.RED);
25+
26+
let decodedImage;
27+
// This animation draws a red circle oscillating from the center of the canvas.
28+
// It is there to show the lag introduced by decoding the image on the main
29+
// thread.
30+
const drawFrame = (canvas) => {
31+
canvas.clear(CanvasKit.WHITE);
32+
33+
if (decodedImage) {
34+
canvas.drawImageRect(decodedImage,
35+
CanvasKit.LTRBRect(0, 0, 3764, 5706), // original size of the image
36+
CanvasKit.LTRBRect(0, 0, 500, 800), // scaled down
37+
null); // no paint needed
38+
}
39+
canvas.drawCircle(250, 250, 200 * Math.abs(Math.sin(Date.now() / 1000)), paint);
40+
surface.requestAnimationFrame(drawFrame);
41+
};
42+
surface.requestAnimationFrame(drawFrame);
43+
44+
45+
document.getElementById('load-button-main').addEventListener('click', () => {
46+
if (decodedImage) {
47+
decodedImage.delete();
48+
decodedImage = null;
49+
}
50+
const imgBitmapPromise = createImageBitmap(imageBlob);
51+
imgBitmapPromise.then((imgBitmap) => {
52+
decodedImage = CanvasKit.MakeImageFromCanvasImageSource(imgBitmap);
53+
});
54+
});
55+
56+
document.getElementById('load-button-web').addEventListener('click', () => {
57+
if (decodedImage) {
58+
decodedImage.delete();
59+
decodedImage = null;
60+
}
61+
worker.postMessage(imageBlob);
62+
});
63+
worker.addEventListener('message', (e) => {
64+
const decodedBuffer = e.data.decodedArrayBuffer;
65+
const pixels = new Uint8Array(decodedBuffer);
66+
decodedImage = CanvasKit.MakeImage(pixels, e.data.width, e.data.height,
67+
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888, CanvasKit.SkColorSpace.SRGB);
68+
});
69+
document.getElementById('clear-button').addEventListener('click', () => {
70+
if (decodedImage) {
71+
decodedImage.delete();
72+
decodedImage = null;
73+
}
74+
});
75+
});
76+
77+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// This worker listens for a message that is the blob of data that is an encoded image.
2+
// In principle, this worker could also load the image, but I didn't want to introduce
3+
// network lag in this comparison. When it has decoded the image and converted it into
4+
// unpremul bytes, it returns the width, height, and the pixels in an array buffer via
5+
// a worker message.
6+
self.addEventListener('message', (e) => {
7+
const blob = e.data;
8+
createImageBitmap(blob).then((bitmap) => {
9+
const oCanvas = new OffscreenCanvas(bitmap.width, bitmap.height);
10+
const ctx2d = oCanvas.getContext('2d');
11+
ctx2d.drawImage(bitmap, 0, 0);
12+
13+
const imageData = ctx2d.getImageData(0, 0, bitmap.width, bitmap.height);
14+
const arrayBuffer = imageData.data.buffer;
15+
self.postMessage({
16+
width: bitmap.width,
17+
height: bitmap.height,
18+
decodedArrayBuffer: arrayBuffer
19+
}, [
20+
arrayBuffer // give up ownership of this object
21+
]);
22+
});
23+
});

0 commit comments

Comments
 (0)