Working with ArrayBuffers

Introductory Reading

The Pointer types section of the Type conversion page explains the fundamentals of this operation. The second code example provides a specific illustration of the operation.

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

bug 732936 includes a discussion of working with ArrayBuffers.

This feature is based on the following work:

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

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

Example 1 - Image Data

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

The following codeblock provides 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);

Further, if you have a byte array pixelBuffer, and you need to create ImageData from it. The data property holds an array of bytes.

We start with the following:

// 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

One method is to get into a js-ctypes array, and then set it into the ImageData, as illustrated by the following code example.

// 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 strategy is to handle it manually, as illustrated by the following code example:

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);

The preceding example, however, does not take advantage of Method 1, but instead manually goes through the array and sets the ImageData array. The cast takes a couple of milliseconds, roughly the same as Method 1. However, the manual Method A, in the preceding example, takes ~1300 ms. Method B takes ~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 is the recommended method, as it only takes a couple of milliseconds, even for large arrays. Notice that the following example does not cast the pixelBuffer. Passing an ArrayBuffer object to pointer type will pass the pointer to buffer (Based on https://dxr.mozilla.org/mozilla-central/source/js/src/ctypes/CTypes.cpp#3080). Further, it returns dataPointer, and there is no extra allocation (Based on https://dxr.mozilla.org/mozilla-central/source/js/src/vm/ArrayBufferObject.cpp#1301).

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: bunnybooboo, jeremy-french, Sheppy, rolfedh, Noitidart, arai
 Last updated by: bunnybooboo,