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