Working with ArrayBuffers

This article needs a technical review. How you can help.

This article needs an editorial review. How you can help.

Introductory Reading

The PointerType's section of the TypeConversion page explains the fundementals and that this is possible (specifically the second example).

Summary

ArrayBuffers are simply byte arrays. In js-ctypes this is a ctypes.uint8_t.array(###) (ctypes.unsigned_char are also ctypes.uint8_t).

A feature discussed in bug 732936 shows the possibility to use.

Based on work: https://dxr.mozilla.org/mozilla-central/source/js/src/ctypes/CTypes.cpp#3080

Based on work: https://dxr.mozilla.org/mozilla-central/source/js/src/vm/ArrayBufferObject.cpp#1301

Demo 1 - Image Data

This demo describes how to transfer a byte array pointed by ctypes.uint8_t.ptr to ImageData of canvas. This demo is based on the fact that the ImageData returned from CanvasRenderingContext2D.getImageData is a Uint8ClampedArray view for an ArrayBuffer.

Here is a basic example of getting and setting Uint8ClampedArray and ArrayBuffer of ImageData.

// context is a CanvasRenderingContext2D of some canvas
var imageData = context.getImageData(x, y, w, h);
var array = imageData.data; // array is a Uint8ClampedArray
var buffer = imageData.data.buffer; // buffer is a ArrayBuffer

// incomingBuffer is a TypedArray
var imageData2 = context.createImageData(w, h);
imageData2.data.set(incomingBuffer);

Say we have a byte array pixelBuffer, you needed to put create ImageData out of it, the data property of this holds an array of byte's.

We start with this:

// pixelBuffer is a pointer to a RGBA pixel buffer of 400x400 image.
pixelBuffer.toString(); // "ctypes.uint8_t.ptr(ctypes.UInt64("0x352e0000"))"

var imgWidth = 400;
var imgHeight = 400;
var myImgDat = new ImageData(imgWidth, imgHeight);

Method 1: Passing ArrayType CData to Uint8ClampedArray.prototype.set

Now one method is to get into a js-ctypes array and then set it into the ImageData like this:

// Cast pointer to array, to pass to Uint8ClampedArray.prototype.set.
var casted = ctypes.cast(pixelBuffer.address(), ctypes.uint8_t.array(myImgData.data.length).ptr).contents; // myImgDat.data.length is imgWidth * imgHeight * 4 because per pixel there is r, g, b, a numbers
casted.toString(); // "ctypes.uint8_t.array(640000)([45, 66, 135, 255, 99, 86, 55, 255, .......... ])"
myImgDat.data.set(casted);

The ctypes.cast takes a couple of milliseconds, however, the myImgDat.data.set takes up to 800ms for a size of 52,428,800 (which is image size of 1280 x 1024 pixels) so for the size of 640,000 it takes about 98ms.

Method 2: Manually Handled

Another method is to handle it manually:

var casted = ctypes.cast(pixelBuffer.address(), ctypes.uint8_t.array(myImgData.data.length).ptr).contents; // myImgDat.data.length is imgWidth * imgHeight *4 because per pixel there is r, g, b, a numbers

/** METHOD A **/
for (var nIndex = 0; nIndex < casted.length; nIndex = nIndex + 4) { // casted.length is same as myImgDat.data.length
    var r = casted[nIndex];
    var g = casted[nIndex + 1];
    var b = casted[nIndex + 2];
    var a = casted[nIndex + 3];

    myImgDat.data[nIndex] = r;
    myImgDat.data[nIndex + 1] = g;
    myImgDat.data[nIndex + 2] = b;
    myImgDat.data[nIndex + 3] = a;
}


/***** OR DO THE BELOW WHICH USES THE .set METHOD *****/

/** METHOD B **/
var normalArr = [];
for (var nIndex = 0; nIndex < cast.length; nIndex = nIndex + 4) { // casted.length is same as myImgDat.data.length
    var r = casted[nIndex];
    var g = casted[nIndex + 1];
    var b = casted[nIndex + 2];
    var a = casted[nIndex + 3];

    normalArr.push(r);
    normalArr.push(g);
    normalArr.push(b);
    normalArr.push(a);
}
myImgDat.data.set(normalArr);

This, however, does not take advantage of the method 1, this manually goes through the array sets the ImageData array. The cast is the same as in the previous method, it takes a couple of milliseconds. However, the manual methods here of A takes ~1300 ms and B ~1400 ms for an array length of 52,428,800 (which is image size of 1280 x 1024 pixels).

Method 3: Transfer byte array by calling memcpy

This method is the recommended way. It takes only a couple of milliseconds for large arrays. Notice how we do not cast the pixelBuffer. Based on https://dxr.mozilla.org/mozilla-central/source/js/src/ctypes/CTypes.cpp#3080 passing an ArrayBuffer object to pointer type will pass the pointer to buffer. Based on https://dxr.mozilla.org/mozilla-central/source/js/src/vm/ArrayBufferObject.cpp#1301 it returns dataPointer, there is no extra allocation.

var lib;
switch (OS.Constants.Sys.Name.toLowerCase()) {
    case 'winnt':
    case 'winmo':
    case 'winnt': //windows
        lib = ctypes.open('msvcrt');
        break;
    case 'darwin': // mac
        lib = ctypes.open('libc.dylib');
        break;
    case 'freebsd':
        lib = ctypes.open('libc.so.7');
        break;
    case 'openbsd':
        lib = ctypes.open('libc.so.61.0');
        break;
    case 'android':
    case 'sunos':
    case 'netbsd':
    case 'dragonfly':
        lib = ctypes.open('libc.so');
        break;
    case 'linux':
        lib = ctypes.open('libc.so.6');
        break;
    case 'gnu/kfreebsd':
        lib = ctypes.open('libc.so.0.1');
        break;
    default:
        //assume unix
        try {
            lib = ctypes.open(ctypes.libraryName('c'));
        } catch (ex) {
            throw new Error('I dont know where to memcpy is defined on your operating system, "' + OS.Constants.Sys.Name + '"');
            lib.close();
        }
}

try {
    var memcpy = lib.declare('memcpy', OS.Constants.Sys.Name.toLowerCase().indexOf('win') == 0 ? ctypes.winapi_abi : ctypes.default_abi,
        ctypes.void_t, // return
        ctypes.void_t.ptr, // *dest
        ctypes.void_t.ptr, // *src
        ctypes.size_t // count
    );
} catch (ex) {
    throw new Error('I dont know where to memcpy is defined on your operating system, "' + OS.Constants.Sys.Name + '"');
    lib.close()
}

memcpy(myImgDat.data, pixelBuffer, myImgDat.data.length); // myImgDat.data.length is imgWidth * imgHeight *4 because per pixel there is r, g, b, a numbers

lib.close();

See Also

Document Tags and Contributors

 Contributors to this page: Sheppy, rolfedh, Noitidart, arai
 Last updated by: Sheppy,