Search code examples
javascriptarraybufferargb

Converting 32BitPackedARGB array with DataView and ArrayBuffer to solve Endiness


I have this code here:

var can = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
can.width = '16';
can.height = '16';
var ctx = can.getContext('2d');
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillRect(0, 0, 16, 16);

var ARGBpixmap = [16, 16];
var image_data = ctx.getImageData(0, 0, can.width, can.height);
for (var x = 0; x < image_data.width; x++) {
    for (var y = 0; y < image_data.height; y++) {
        var i = x * 4 + y * 4 * image_data.width;
        var r = image_data.data[i];
        var g = image_data.data[i + 1];
        var b = image_data.data[i + 2];
        ARGBpixmap.push(r + g + b);
    }
}

console.log(ARGBpixmap);

However it is endiness. So I heard I have to use DataView and ArrayBuffer. This was my attempt:

var can = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
can.width = '16';
can.height = '16';
var ctx = can.getContext('2d');
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillRect(0, 0, 16, 16);

// Getting pixels as a byte (uint8) array
var imageData = ctx.getImageData(0, 0, can.width, can.height);
var pixels8BitsRGBA = imageData.data;
console.log(pixels8BitsRGBA.length)

var sizeOfEachStoredNumber = 4; //bytes
var numberOfStoresPerPixel = 4; //we have a r g and b
var numberOfPixels = can.width * can.height;
var buffer = new ArrayBuffer((numberOfPixels * (sizeOfEachStoredNumber *  numberOfStoresPerPixel))  + 8); // +8 bytes for the two leading 32 bytes integers
console.log((numberOfPixels * (sizeOfEachStoredNumber *  numberOfStoresPerPixel))  + 8);

//buffer size is: 256 repetitions of 4 numbers. each number is 4bytes. so 16 bytes per repetition. 256 reps. so 4096 total bytes is size
// Reverting bytes from RGBA to ARGB
//var pixels8BitsARGB = 
var view = new DataView(buffer, 0);
view.setUint8(0, can.width, true);
view.setUint8(4, can.height, true);
for(var i = 0 ; i < pixels8BitsRGBA.length ; i += 4) {
    var bytePos = i/4*16+8; //starting byte position of this group of 4 numbers // +8 bytes for the two leading 32 bytes integers
    //console.log(bytePos, h)
    var r = pixels8BitsRGBA[i  ];
    var g = pixels8BitsRGBA[i+1];
    var b = pixels8BitsRGBA[i+2];
    var a = pixels8BitsRGBA[i+3];

    view.setUint8(bytePos, a, true);
    view.setUint8(bytePos+4, r, true);
    view.setUint8(bytePos+8, g, true);
    view.setUint8(bytePos+12, b, true);
}

// Converting array buffer to a uint32 one, and adding leading width and height
var pixelsAs32Bits = new Uint32Array(buffer);

console.log(pixelsAs32Bits);

Both are copy pasteable code, the first one gets very different numbers. How can I fix my code? I'm not that great with DataView and ArrayBuffer.


Solution

  • You don't really need typed arrays, but I guess it doesn't hurt to use them.

    Without typed arrays, in the first snippet,

    ARGBpixmap.push(r + g + b);
    

    just needs to be

    ARGBpixmap.push((r << 16) + (g << 8) + b);
    

    This converts 3 bytes into a single 32 bit integer of the form

    00000000 RRRRRRRR GGGGGGGG BBBBBBBBB

    You may want 1s or the alpha value instead of the 0s, depending on what you are going to do with the data. To get the alpha value, use

    var r = image_data.data[i];
    var g = image_data.data[i + 1];
    var b = image_data.data[i + 2];
    var a = image_data.data[i + 3];
    ARGBpixmap.push((a << 24) + (r << 16) + (g << 8) + b);
    

    You may be able to get an unsigned number using this line, but it should not really matter, as the low 32 bits are exactly the same, see http://www.codeonastick.com/2013/06/javascript-convert-signed-integer-to.html

    ARGBpixmap.push(((a << 24) + (r << 16) + (g << 8) + b) >>> 0);
    

    In the second snippet

    view.setUint8(bytePos, a, true);
    view.setUint8(bytePos+4, r, true);
    view.setUint8(bytePos+8, g, true);
    view.setUint8(bytePos+12, b, true);
    

    should probably be

    view.setUint8(i + 3, a, true);
    view.setUint8(i + 2, r, true);
    view.setUint8(i + 1, g, true);
    view.setUint8(i + 0, b, true);
    

    The reason is that now you are dealing with the ARGB data byte by byte at the source and target. i is a byte position already, you don't need to calculate another. The relevant differences are only within each 4-byte group.

    EDIT: Account for endianess.