3

I have a web-worker that decodes a jpeg image with javascript.

It then passes the pixel data of that image to the main thread in the same format canvas image data accepts.

The goal is to draw the decoded array of pixels onto a canvas element using the standard:

context.putImageData(imgData, 0, 0);

This seems simple enough, however the only way I can figure out how to do this is to make a loop the length of the data received from the worker, and copy the pixel value 1 at a time into the imgData.data array like this:

var workerData = //Data received from the worker;

for(i=0;i<workerData.length;i++){
    imgData.data[i] = workerData[i];
}

I have tried to just copy the array like this:

imgData.data = workerData.slice();

However it doesn't change the value of the imageData when it comes time to draw it to the canvas. Is there any faster or at least a more sane way to do this?

Edit: I found from this post that you can use set like this:

var canvas = document.createElement('canvas');
var imageData = canvas.getContext('2d').createImageData(width, height);
imageData.data.set(myData);

And it works perfect!

YAHsaves
  • 1,697
  • 12
  • 33

2 Answers2

0
 context.putImageData(new ImageData(workerData, 100, 100), 0, 0);

Recreating an ImageData object shouldnt be a problem as its just a small wrapper.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Oh I see what you're implying. I tried it, however I get this error: TypeError: Not enough arguments to ImageData – YAHsaves Dec 28 '17 at 19:50
  • @YAHsaves oh right, you need to pass a width and a height – Jonas Wilms Dec 28 '17 at 19:53
  • That still didn't seem to work, it said invalid or negative length not matter what dimensions I tried. Perhaps I just had the wrong settings, however I did find an answer after some more searching. You can use set like this: imageData.data.set(myData); – YAHsaves Dec 28 '17 at 19:58
0

What ImageData expects is an Uint8ClampedArray which is a TypedArray, but it sounds that what your worker sends is an Array.

So you will probably get faster results by working directly in you worker from such a TypedArray. Beside possibly making the worker part a bit faster, it will also allow you to transfer the underlying ArrayBuffer from the worker thread to the main thread, without having to copy the data, never.

Once the ArrayBuffer has been received on the main thread, you will just have to create an UInt8ClampedArray view from it, and create an ImageData wrapper from this TypedArray.

At this point, all the main thread would have done is to create a view on the buffer (minimal memory waste) and an ImageData wrapper over this view (once again, a simple object creation). It won't have created nor even read the data your worker produced. This will be done by putImageData.

worker.js

data = new UInt32Array(width * height); //can even be an other type of TypedArray
... // fill 'data'
self.postMessage({
  data: data,
  width: width,
  height: height
},
[data.buffer] // tranfer the ArrayBuffer
);

main.js

worker.onmessage = e => {
  const msg = e.data;
  const view = new Uint8ClampedArray(msg.data.buffer);
  const imageData = new ImageData(view, msg.width, msg.height);
  ctx.putImageData(imageData, 0,0);
};

const ctx = c.getContext('2d');
const worker = new Worker(generateWorkerURL());
worker.onmessage = e => {
  const msg = e.data;
  const view = new Uint8ClampedArray(msg.data.buffer);
  const imageData = new ImageData(view, msg.width, msg.height);
  ctx.putImageData(imageData, 0,0);
};
worker.postMessage('go');



// stacksnippet only
function generateWorkerURL() {
  const workerScript = document.querySelector('script[type="worker-script"]');
  const blob = new Blob([workerScript.textContent], {type: 'application/javascript'});
  return URL.createObjectURL(blob);
}
<script type="worker-script">
  onmessage = e => {
    const canvasWidth = 500,
      canvasHeight = 500,
      // can be any TypedArray
      data = new Uint32Array(canvasWidth * canvasHeight);

    // fill 'data'
    // taken from https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipulation-with-typed-arrays/
    for (let y = 0; y < canvasHeight; ++y) {
        for (let x = 0; x < canvasWidth; ++x) {
            let value = x * y & 0xff;
            data[y * canvasWidth + x] =
                (255   << 24) |    // alpha
                (value << 16) |    // blue
                (value <<  8) |    // green
                value;             // red
        }
    }
    self.postMessage({
      data: data,
      width: canvasWidth,
      height: canvasHeight
    },
    [data.buffer] // tranfer the ArrayBuffer
    );
    console.log('transfered', !data.length);
}
</script>
<canvas id=c width=500 height=500></canvas>

Also note that it's not clear as to why you want to go through an ImageData, when you could simply use drawImage, either from an HTMLImageElement directly, or even from an ImageBitmap if you really want to work from workers.

Kaiido
  • 123,334
  • 13
  • 219
  • 285