2

I'm rewritng a small javascript for being able to put it in a worker.js like it is documented here:

Mozilla - Web_Workers_API

The worker.js shall display an image on an OffscreenCanvas like it is documented here:

Mozilla - OfscreenCanvas documentation

The initial script is using the following statement that obviously cannot be used in a worker.js file, because there is no "document":

    var imgElement = document.createElement("img");
    imgElement.src = canvas.toDataURL("image/png");

But how can I substitue the

document.createElement("img");

statement in the worker.js for still being able to use the second statement:

imgElement.src = canvas.toDataURL("image/png");

If anyone has any idea, it would be really appreciated. :)

tonispark
  • 133
  • 2
  • 7

1 Answers1

6

Just don't.

Instead of exporting the canvas content and make the browser decode that image only to display it, simply display the HTMLCanvasElement directly.

This advice already stood for before you switched to an OffscreenCanvas, but it still does.

Then how to draw on an OffscreenCanvas in a Worker and still display it? I hear you ask.

Well, you can request an OffscreenCanvas from an HTMLCanvasElement through its transferControlToOffscreen() method.

So the way to go is, in the UI thread, you genereate the <canvas> element that will be used for displaying the image, and you generate an OffscreenCanvas from it. Then you start your Worker to which you'll transfer the OffscreenCanvas.
In the Worker you'll wait for the OffscreenCanvas in the onmessage event and grab the context and draw on it.

UI thread

const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker(url);
worker.postMessage(offscreen, [offscreen]);
container.append(canvas);

Worker thread

onmessage = (evt) => {
  const canvas = evt.data;
  const ctx = canvas.getContext(ctx_type);
  //...

All the drawings made from the Worker will get painted on the visible canvas, without blocking the UI thread at all.

const canvas = document.querySelector("canvas");
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker(getWorkerURL());
worker.postMessage(offscreen, [offscreen]);


function getWorkerURL() {
  const worker_script = `
  onmessage = (evt) => {
    const canvas = evt.data;
    const w = canvas.width = 500;
    const h = canvas.height = 500;
    const ctx = canvas.getContext("2d");
    // draw some noise
    const img = new ImageData(w,h);
    const arr = new Uint32Array(img.data.buffer);
    for( let i=0; i<arr.length; i++ ) {
      arr[i] = Math.random() * 0xFFFFFFFF;
    }
    ctx.putImageData(img, 0, 0);
    for( let i = 0; i < 500; i++ ) {
      ctx.arc( Math.random() * w, Math.random() * h, Math.random() * 20, 0, Math.PI*2 );
      ctx.closePath();
    }
    ctx.globalCompositeOperation = "xor";
    ctx.fill();
  };
  `;

  const blob = new Blob( [ worker_script ] );
  return URL.createObjectURL( blob );
}
canvas { border: 1px solid; }
<canvas></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks for the clarification. Quote: "In the Worker you'll wait for the OffscreenCanvas in the onmessage event and grab the context and draw on it." Perhaps it is pretty obvious and I just don't get it, but looking at my initial question, how can I substitute the command: var imgElement = document.createElement("img"); ...because I still don't have a "document". What do I miss here? :/ – tonispark Apr 12 '21 at 20:15
  • You don't substitute this line. You don't need an at all. I provided a live snippet at the bottom with all you need. – Kaiido Apr 12 '21 at 22:37