Trey Keown
Trey Keown

Reputation: 1355

Fast way to make RGB array into RGBA array in Javascript

An emulator I am working with internally stores a 1-dimensional framebuffer of RGB values. However, HTML5 canvas uses RGBA values when calling putImageData. In order to display the framebuffer, I currently loop through the RGB array and create a new RGBA array, in a manner similar to this.

This seems suboptimal. There has been much written on performing canvas draws quickly, but I'm still lost on how to improve my application performance. Is there any way to more quickly translate this RGB array to an RGBA array? The alpha channel will always be fully opaque. Also, is there any way to interface with a canvas so that it takes an array of RGB, not RGBA, values?

Upvotes: 3

Views: 3827

Answers (2)

GameAlchemist
GameAlchemist

Reputation: 19294

Both ctx.createImageData and ctx.getImageData will create a buffer, the later (get) will be slower since it has also to copy the buffer. This jsperf : http://jsperf.com/drawing-pixels-to-data
confirms that we have a like 33% slowdown on Chrome, and 16 times slower on Firefox (FFF seems to byte-copy when Chrome copy with 32 or 64 bits move).

i'll just recall that you can handle typed array of different types, and even create a view on the buffer (image.data.buffer).
So this may allow you to write the bytes 4 by 4.

var dest = ctx.createImageData(width, height);
var dest32 = new Int32Array(dest.data.buffer);
var i = 0, j=0, last = 3*width*height;
while (i<last) {
      dest32[j] = src[i]<<24 + src[i+1] << 16 
                   + src[i+2] << 8 + 255;
      i+=3; 
      j++;
}

You will see in this jsperf test i made that it is faster to write using 32 bits integers : http://jsperf.com/rgb-to-rgba-conversion-with-typed-arrays notice that there is a big issue in those tests : since this test is awfull in terms of garbage creation, accuracy is so-so. Still after many launch, we see that we have around 50% gain on write 4 vs write 1.

Edit : it might be worth to see if reading the source with a DataView wouldn't speed things up. but the input array has to be a buffer (or have a buffer property like a Uint8Array). (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/DataView)

do not hesitate to update the fiddle with such a try.

Edit 2 : I don't understand i re-ran the test and now write 4 is slower : ??? and after, faster again : -------

Anyway you have great interest in keeping the dest32 buffer under your hand and not create a new one each time anyway, so since this test measure the Int32Array creation, it does not correspond to your use case.

Upvotes: 2

Alnitak
Alnitak

Reputation: 339786

There's no way to use plain RGB, but the loop in that code could be optimised somewhat by removing repeated calculations, array deferences, etc.

In general you shouldn't use ctx.getImageData to obtain the destination buffer - you don't normally care what values are already there and should use ctx.createImageData instead. If at all possible, re-use the same raw buffer for every frame.

However, since you want to preset the alpha values to 0xff (they default to 0x00) and only need to do so once, it seems to be much most efficient to just fill the canvas and then fetch the raw values with getImageData.

ctx.fillStyle = '#ffffff'; // implicit alpha of 1
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
dest = ctx.getImageData(0, 0).data

and then for each frame for can just leave the alpha byte untouched:

var n = 4 * w * h;
var s = 0, d = 0;
while (d < n) {
    dest[d++] = src[s++];
    dest[d++] = src[s++];
    dest[d++] = src[s++];
    d++;    // skip the alpha byte
}

You could also experiment with "loop unrolling" (i.e. repeating that four line block multiple times within the while loop) although results will vary across browsers.

Since it's very likely that your total number of pixels will be a multiple of four, just repeat the block another three times and then the while will only be evaluated for every four pixel copies.

Upvotes: 4

Related Questions