gct
gct

Reputation: 14563

What's the fastest way to draw an image to the screen in JavaScript from array of RGB pixels?

I'm working on a graphics frontend that renders server-side and pushes screen updates to a browser by sending compressed images to the client (think VNC). I've decided the overhead of encoding PNGs is too high, so currently I'm sending raw blobs of 8-bit RGB pixel values through a websocket (with compression enabled). This is actually very fast and I'm seeing great compression gains (75K -> 2.7k for example).

On the client however, I have to take the raw pixels and then draw them onto a canvas. This is my current best code performance wise:

// receive message from server
self.ws.onmessage = function (evt) {
    // get image offset
    var dv = new DataView(evt.data);
    var dx = dv.getInt16(0);
    var dy = dv.getInt16(2);
    var ww = dv.getInt16(4);
    var hh = dv.getInt16(6);
    var offset = 8;

    // get context to canvas and create image
    var ctx = self.canvas.getContext("2d");
    var img = ctx.createImageData(ww, hh);

    // unpack image data
    var start = performance.now();
    var dst = 0, src = offset;
    for (var ii=0; ii < ww*hh; ii++) {
        img.data[dst++] = dv.getUint8(src++);
        img.data[dst++] = dv.getUint8(src++);
        img.data[dst++] = dv.getUint8(src++);
        img.data[dst++] = 255;
    }

    // draw image
    ctx.putImageData(img, dx, dy, 0, 0, ww, hh);
    var end = performance.now();

    console.log("draw took " + (end-start) + " milliseconds");

The aforementioned 75K image (which is 1000x500 pixels) takes ~53ms to render in this way though, which is a long time. What's the fastest way to do this drawing operation? I can output RGBA pixels instead of that makes life easier.

Edit: Seems like this might be more of a Firefox issue, Chrome runs this same decode loop in ~2.5ms on average.

Upvotes: 6

Views: 1440

Answers (1)

gct
gct

Reputation: 14563

Switching to full RGBA output (which doesn't add much overhead thanks to the compression), and using this code to directly wrap the websocket buffer is significantly faster:

// receive message from server
self.ws.onmessage = function (evt) {
    // get image offset
    var dv = new DataView(evt.data);
    var dx = dv.getInt16(0);
    var dy = dv.getInt16(2);
    var ww = dv.getInt16(4);
    var hh = dv.getInt16(6);

    // get context to canvas and create image
    var ctx = self.canvas.getContext("2d");

    // draw image data
    var start = performance.now();
    ctx.putImageData(
        new ImageData(new Uint8ClampedArray(evt.data, 8), ww, hh),
        dx, dy,
        0,  0,
        ww, hh
    );
    var end = performance.now();

    console.log("draw took " + (end-start) + " milliseconds");
}

Now the large image size takes ~1ms to render in Firefox and 350us in Chrome. Going to call that good enough.

Upvotes: 2

Related Questions