JavaScript Canvas - OffscreenCanvas
Written by Ian Elliot   
Monday, 16 September 2019
Article Index
JavaScript Canvas - OffscreenCanvas
Using A Worker
transferControlToOffScreen

The UI thread is now very simple:

var worker = new Worker("animate.js");
var ctx = document.body.appendChild(
createCanvas(600, 600)).getContext("2d"); worker.addEventListener("message", function (e) { ctx.drawImage(e.data.frame,0,0); fps.value =e.data.fps; });

You can see that all the UI thread has to do is respond to the event sent by the worker. It draws the image to the canvas and updates the text box.

If you try this out you will discover that it doesn’t quite work. The reason is that when you clear the OffscreenCanvas all of the pixels are set to transparent black, i.e. background pixels. When you draw the bitmap onto the canvas only the foreground pixels change the canvas. What this means is that you don’t get a clearing of the canvas at each frame. There are a number of different ways around the problem. If you are using drawImage then the simplest is to change the composition rule to:

ctx.globalCompositeOperation="copy";

With this change the OffscreenCanvas replaces all of the pixels in the DOM canvas.

You can see the complete modified program at www.iopress.info.

This raises the question of why the OffscreenCanvas cannot be used to replace the current bitmap displayed by the canvas without the need to use compositing. The latest part of the standard provides a new graphics context for the canvas and a new method that allows the bitmap being displayed to be swapped to another. To use this you have to change the UI graphics context to:

var ctx = document.body.appendChild(
createCanvas(600, 600)).getContext("bitmaprenderer");

Currently only Chrome supports the “bitmaprenderer” context. With this change we can now transfer the bitmap without compositing and at maximum speed:

worker.addEventListener("message",
           function (e) {
             ctx.transferFromImageBitmap(e.data.frame);
             fps.value =e.data.fps;
           });

largecover360

TransferControlToOffScreen

There is another way to organize things that makes animation very simple. As the use of an OffscreenCanvas to update a DOM canvas is so common, there is a way to link an OffscreenCanvas and a DOM canvas so that updates to the OffscreenCanvas are automatically transferred to the DOM canvas. Simply create a DOM canvas and use the transferControlToOffScreen method to return an OffscreenCanvas that is linked to it. You can pass this OffscreenCanvas to a web worker and it can draw on it. Every time the browser does a repaint the OffscreenCanvas is automatically used to update the DOM canvas. The only downside is that you cannot make use of the DOM canvas directly – it is essentially just a “front” for the object doing all the work, the OffscreenCanvas. This is potentially the fastest way to use an OffscreenCanvas to display changes at the highest frame rate.

So how would we change the previous bouncing ball program to make use of this?

You can see the complete modified program at www.iopress.info.

The main program in the UI thread is just:

var worker = new Worker("animate.js");
var offCanvas = document.body.appendChild(
createCanvas(600, 600)).transferControlToOffscreen(); worker.postMessage({canvas: offCanvas}, [offCanvas]);

That is all you need as the worker thread does everything else. It needs an event handler to accept the message that gives it the OffscreenCanvas and make use of it:

var ctx;
var ballImage;
this.addEventListener("message", function (e) {
 ctx = e.data.canvas.getContext("2d");
 var ctx2 = new OffscreenCanvas(40, 40).getContext("2d");
 var path = new Path2D();
 var r = 20;
 path.arc(20, 20, r, 0, 2 * Math.PI);
 ctx2.fill(path);
 ballImage = ctx2.canvas.transferToImageBitmap();
 var noBalls = 80;
 var balls = [];
 for (i = 0; i < noBalls; i++) {
   balls[i] = new Ball(
new Pos(Math.floor(Math.random()*250), Math.floor(Math.random()*250)), new Vel(Math.floor(Math.random()*10)-5, Math.floor(Math.random()*10)-5), new Acc(0, .2), 20); } Animation.spriteList = balls; Animation.run(); });

Notice that this means the whole of the web worker’s main program is now in the event handler. The worker can’t start doing anything until it has the OffscreenCanvas provided by the GUI thread and, once it has it, it can get on with the animation as before. The only changes needed are to keep ctx and ballImage global so that other methods can access them.

The Animation.run method is now reduced to:

Animation.run = function (t) {
   Animation.frameRate(t);
   Animation.clearCanvas();
   for (var i = 0; i < Animation.spriteList.length; i++){
        var ball1 = Animation.spriteList[i];
        ball1.update();
        ball1.render();
   }
   requestAnimationFrame(Animation.run);
 };

Notice that now there is no need to post any data back to the UI. All that has to be done is to wait for the next animation frame. The update to the UI thread is performed at the next page render.

If you try this out you will find that there is no longer any frame rate feedback because the web worker is no longer sending any data to the UI thread. This is easy to fix by making the frameRate method post the data back to the UI thread:

Animation.frameRate = function (t) {
    if (typeof t !== "undefined") {
       Animation.frameRate.temp = 
0.8 * Animation.frameRate.temp + 0.2 * (t - Animation.frameRate.tp); Animation.frameRate.tp = t; } Animation.frameRate.count++; if (Animation.frameRate.count === 120) { Animation.fps = (
1000 /Animation.frameRate.temp).toFixed(2); Animation.frameRate.temp = 0; Animation.frameRate.count = 0; self.postMessage({fps: Animation.fps}); } }; Animation.frameRate.count = 0; Animation.frameRate.tp = 0; Animation.frameRate.temp = 0; Animation.fps = 0;

The UI thread simply needs an event handler to show the frame rate data:

worker.addEventListener("message",
                        function (e) {
                           fps.value = e.data.fps;
                        });

With this change it all works as before. The advantage is that the browser is responsible for optimizing the update of the display and this should minimize any inefficient moving of data between bitmaps.

You can see the complete modified program at www.iopress.info.

Summary

  • A web worker can run JavaScript code using a separate thread from the UI thread.

  • The web worker’s code is isolated from any code running in the UI thread and in any other thread. It is also unable to access the DOM and other objects that are freely available to the UI thread.

  • Communication between the UI thread and the worker thread is via events and event handlers.

  • Data cannot be simply shared between the threads. Instead you can send a copy of the data as part of the event or you can transfer ownership of a limited number of objects using the transferable object protocol.

  • The OffscreenCanvas object has all of the functionality of a canvas object but it is not part of the DOM and can be used by the UI thread or a Worker thread.

  • OffscreenCanvas is a transferable object and this allows the Worker thread to update a canvas object in the UI thread.

  • This connection between a canvas in the UI thread and an OffscreenCanvas in the Worker thread is so common that the transferControlToOffscreen method will connect the UI thread to an OffscreenCanvas. The UI thread will be automatically updated to show the current content of the OffscreenCanvas.

Now available as a paperback or ebook from Amazon.

JavaScript Bitmap Graphics
With Canvas

largecover360

 

Contents

  1. JavaScript Graphics
  2. Getting Started With Canvas
  3. Drawing Paths
      Extract:  Basic Paths 
  4. Stroke and Fill
      Extract: Stroke Properties 
  5. Transformations
      Extract: Transformations 
  6. Text
      Extract: Text, Typography & SVG 
  7. Clipping, Compositing and Effects
      Extract: Clipping & Basic Compositing 
  8. Generating Bitmaps
      Extract:  Introduction To Bitmaps **NEW!
  9. WebWorkers & OffscreenCanvas
      Extract: OffscreenCanvas
  10. Bit Manipulation In JavaScript
  11. Typed Arrays
  12. Files, blobs, URLs & Fetch
  13. Image Processing
      Extract: ImageData 
  14. 3D WebGL
  15. 2D WebGL
    Extract: WebGL Convolutions 

Related Articles

Reading A BMP File In JavaScript

Getting Started With SVG

SVG, JavaScript and the DOM

Getting Started with Box2D in JavaScript

<ASIN:1871962625>

<ASIN:1871962579>

<ASIN:1871962560>

<ASIN:1871962501>

<ASIN:1871962528>

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on, Twitter, Facebook or Linkedin.

square

 



 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Monday, 16 September 2019 )