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.
ArrayBuffer
s 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 ArrayBuffer
s.
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();