Web Workers and Canvas

Using Web Workers for drawing using native canvas functions

[community edit: This answer was written and accepted in 2011. Other technologies have emerged (or are emerging) which may allow Web Workers and Canvas to co-exist better; the reader should be aware of all the answers on this page besides this answer.]

You cannot pass a canvas object or canvas context to a worker thread because the canvas is part of the DOM.

Web Workers and Canvas

Small update, as the question is now more than half a year old:

In Chrome/Chromium 6 you can now send a canvas' ImageData object to a web worker, let the web worker make changes to the object and then write it back to the canvas using putImageData(..).

Google's Chromabrush does it this way, the source-code can be found here:

  • Main thread
  • Web worker

Update:

The latest development snapshots of Opera (10.70) and Firefox (4.0b1) also support passing ImageData objects to a web worker.

Update 2017:

Actual links from Github (easier to find needed files from Chromabrush):

  • Sending imageData to worker
  • Receiving data

Asynchronous canvas drawing with Web Workers

If you want to use a WebWorker (for example for more complex drawing algorithms), I could think of the following setup:

  • onmousedown, spawn a new worker and register a handler on it that paints objects on the canvas
  • onmouseup (and -leave etc), terminate the worker
  • onmousemove, if a worker exists determine mouse coordinates and send them into the worker

In the worker

  • listen to new mouse coordinates
  • start an timeout interval which constantly fires draw-events (depending on current coordinates and a clever algorithm)

Yet I think that a Worker is too much overhead for a simple graffiti tool. Use the simple solution without a Worker like @Esailija demonstrated.

If you had a more complex application which could make good use of Workers, you wouldn't really spawn them onmousedown and terminate them. Instead, you maybe instantiated single Workers for single kinds of tools, and fired start-processing and end-processing events to them.

Web workers and Canvas data

DOM can't be accessed from a WebWorker.

The best way I can see is to re-define this object in the webworker, and define a protocol to transmit each call to a method. But it won't work when you need other objects like images.

Worker-side :

var CanvasRenderingContext2D = function() {
this.fillStyle = "black";
this.strokeStyle = "black";
...
this.lineWidth = 1.0;
};

["fillRect", "strokeRect", "beginPath", ... "rotate", "stroke"].forEach(function(methodName) {
CanvasRenderingContext2D.prototype[methodName] = function() {
postMessage({called: methodName, args: arguments, currentObjectAttributes: this});
};
});

...

var myCanvas = new CanvasRenderingContext2D();
myCanvas.fillStyle = "rgb(200,0,0)";
myCanvas.fillRect(10, 10, 55, 50);

Main-page side :

var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var worker = new Worker( ... );
worker.onMessage = function(event) {
var data = event.data;

// Refreshing context attributes
var attributes = data.currentObjectAttributes;
for(var k in attributes) {
context[k] = attributes[k];
}

// Calling method
var method = context[data.called];
method.apply(context, data.args);
};

EDIT :

I tried to integrate it with your code (not tested). To integrate it, I had to change the structure of the messages sent by the worker.

// get code from user and prepend helper "functions"
var userCode = editor.getValue();
var codeWithMessages = '\n\
function print (data) { postMessage(["print", data.toString()]); } \n\
function println(data) { postMessage(["print", data.toString() + "\\n"]); } \n\
function alert (data) { postMessage(["alert", data.toString()]); } \n\
var CanvasRenderingContext2D = function() { \n\
this.fillStyle = "black"; \n\
this.strokeStyle = "black"; \n\
/* ... */ \n\
this.lineWidth = 1.0; \n\
}; \n\
["fillRect", "strokeRect", "beginPath", /* ... */ "rotate", "stroke"].forEach(function(methodName) { \n\
CanvasRenderingContext2D.prototype[methodName] = function() { \n\
postMessage(["canvas", {called: methodName, args: Array.prototype.slice.call(arguments), currentObjectAttributes: this}]); \n\
}; \n\
});\n' + userCode;
var codeWithMessagesAndExit = codeWithMessages + "\npostMessage(['exit']);";
var blob = new Blob([codeWithMessagesAndExit], {type: 'application/javascript'});
// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);
outer.worker = new Worker(blobURL);

...

// Define your canvas ...
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");

// handle messages by "printing" to the run-output element, or display
// a success message when finishing
outer.worker.addEventListener('message', function (event) {
var method = event.data[0] || null;
var data = event.data[1] || null;

if(method == "canvas") {
// Refreshing context attributes
var attributes = data.currentObjectAttributes;
for(var k in attributes) {
context[k] = attributes[k];
}

// Calling method
var method = context[data.called];
method.apply(context, data.args);
} else if(method == "exit") {
outer.worker.terminate();
outer.worker = null;

outer.codeRunStatus = outer.CODE_RUN_SUCCESS;
outer.errorMessage = null;
outer.outputFromLatestRun = $("#run-output").text();
$("#run-output").append("<span class='code-run-success'>\nKörning klar</span>");
enableButtons();
} else if(method == "alert") {
alert(data);
} else if(method == "print") {
$("#run-output").append(data);
} else {
$("#run-output").append(event.data);
}
}, false);
// start the worker
outer.worker.postMessage();

canvas data to web worker

Everything is copied to a webworker, so unless your computation is very intensive, I doubt you'll see much gain there.

WebWorkers are meant for long-running, computationally intensive algorithms. The obvious use cases are:

  • AI in web games
  • Raytracers
  • Compression/decompression on large data sets
  • AJAX requests that need a lot of processing before it's displayed
  • Other complex algorithms

Since data is copied both ways, you have to be careful about what you're doing. WebWorkers don't have access to the DOM, so they're likely useless for what you're trying to do. I don't know what your app does, but it sounds like it isn't very intense.

There are also Sharedworkers, which can be shared by multiple tabs/windows, which is a really nice way to pass data between tabs.

Edit:

Also look into the structured clone algorithm. It seems to be more efficient that JSON for many things, and can even duplicate ImageData (so it doesn't just support strings anymore).

For browsers that don't support the clone algorithm, I would urge you to consider base64. It's decent at storing binary data, and I think it's faster than JSON.stringify. You may have to write some code to handle it though.



Related Topics



Leave a reply



Submit