Search code examples
javascripthtmlcanvas32-bit

Canvas using Uint32Array: Wrong colors are being rendered


I'm currently creating a JS canvas where I want to display a box of different colors.

I'm using uint32 for extra speed, and my colors never display correctly! I've looked at the examples mainly over here: https://stackoverflow.com/a/19502117 where someone said in the comments:

(small I or JS will throw an error). Tip for OP: colors for Uint32 can also be given simply be using hex - no need to do shifting: 0xff00000 = black + alpha set to 255; for little-endian/LSB CPUs, opposite on big-endian/MSB CPUs."

I'm certain my laptop is little-endian.

I have a demo of my issue here: http://jsfiddle.net/GhwUC/357/

var canvas = document.getElementById('canvas');
var canvasWidth  = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);

var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);

for (var y = 0; y < canvasHeight; ++y) {
    for (var x = 0; x < canvasWidth; ++x) {
        data[y * canvasWidth + x] = 0xff80d7ff // Should be light blue (#80d7ff)
    }
}

imageData.data.set(buf8);

ctx.putImageData(imageData, 0, 0);

The color in question here is:

color

But the fiddle displays a yellow-ish color: color fail

It's the same on other colors, thanks a lot in advance!

EDIT: thanks @Oriol for quick answer! I used the following function to reverse my colors (in case anyone was interested):

function reverseUint32 (uint32) {
    var s32 = new Uint32Array(4);
    var s8 = new Uint8Array(s32.buffer);
    var t32 = new Uint32Array(4);
    var t8 = new Uint8Array(t32.buffer);        
    reverseUint32 = function (x) {
        s32[0] = x;
        t8[0] = s8[3];
        t8[1] = s8[2];
        t8[2] = s8[1];
        t8[3] = s8[0];
        return t32[0];
    }
    return reverseUint32(uint32);
};

Use it like: reverseUint32(0xfc66feff)


Solution

  • This happens when you treat a Uint8Array buffer as a Uint32 in little endian:

    var buf = new Uint8Array([0x12, 0x34, 0x56, 0x78]).buffer;
    console.log(new Uint32Array(buf)[0].toString(16));
    // "78563412" in little endian, "12345678" in big endian

    So in little endian, the order becomes AABBGGRR instead of AARRGGBB.

    You could reverse 0x80d7ffff to 0xffffd780, but then it wouldn't work on big endian machines.

    To avoid these problems you can use a DataView, which allows to specify the endianness, defaulting to big endian:

    view.setUint32(offset, 0xffffd780, true);  // #80d7ff, in little endian
    view.setUint32(offset, 0x80d7ffff, false); // #80d7ff, in big endian
    

    var canvas = document.getElementById('canvas'),
        canvasWidth  = canvas.width,
        canvasHeight = canvas.height,
        ctx = canvas.getContext('2d'),
        imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight),
        view = new DataView(imageData.data.buffer);
    for (var y = 0; y < canvasHeight; ++y) {
      for (var x = 0; x < canvasWidth; ++x) {
        var offset = 4 * (y * canvasWidth + x);
        view.setUint32(offset, 0x80d7ffff); // light blue (#80d7ff)
      }
    }
    ctx.putImageData(imageData, 0, 0);
    <canvas id="canvas" height="256" width="256"></canvas>

    But it seems that browsers haven't optimized much DataView, so it's slow. Then it might be better to set the color components separately in the Uint8ClampedArray:

    var canvas = document.getElementById('canvas'),
        canvasWidth  = canvas.width,
        canvasHeight = canvas.height,
        ctx = canvas.getContext('2d'),
        imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight),
        data = imageData.data;
    for (var y = 0; y < canvasHeight; ++y) {
      for (var x = 0; x < canvasWidth; ++x) {
        var offset = 4 * (y * canvasWidth + x);
        // light blue (#80d7ff)
        data[offset+0] = 0x80; // red
        data[offset+1] = 0xd7; // green
        data[offset+2] = 0xff; // blue
        data[offset+3] = 0xff; // alpha
      }
    }
    ctx.putImageData(imageData, 0, 0);
    <canvas id="canvas" height="256" width="256"></canvas>