I'm writing a node-webkit addon in C++ that decodes image data and returns an array of pixel data to the caller. The following code (abridged for brevity) works just fine:
caller.js
var image_data = canvas_context.createImageData(w, h);
var addon = require('decoder.node');
var decoder = new addon.Decoder();
var pixel_array = decoder.getPixelArray();
image_data.data.set(pixel_array);
canvas_context.putImageData(image_data);
decoder.cpp
Handle<Value> Decoder::getPixelArray(const Arguments &args) {
HandleScope scope;
// unwrap self
DecoderObj *obj = ObjectWrap::Unwrap<DecoderObj>(args.This());
if (obj == NULL) return scope.Close(Number::New(1));
// get pointer to pixel data
PixelPtr *ptr = NULL;
uint64_t len = 0;
obj->GetPixelsPtr((PixelPtr*)&ptr, &len);
// create javascript native array
// from http://luismreis.github.io/node-bindings-guide/docs/returning.html
v8::Handle<Value> fun_val = Context::GetCurrent()->Global()->Get(String::New("Uint8ClampedArray"));
v8::Handle<Function> fun = v8::Handle<Function>::Cast(fun_val);
const unsigned argc = 1;
Local<Value> argv[argc] = { Local<Value>::New(Uint32::New(len)) };
Local<v8::Object> array = fun->NewInstance(argc, argv);
// populate js native array with pixel ptr data
array->SetIndexedPropertiesToExternalArrayData(ptr, v8::kExternalUnsignedByteArray, len);
// return native array to javascript caller
return scope.Close(array);
}
The only problem is when the caller invokes the set() method on the ImageData object. This has proven to be unacceptably slow (i.e. >= 20ms). I assume the slowness is due to a large amount of data being copied from one memory location to another. So I thought I'd try to fix this by passing a reference to the ImageData object into the addon, and then have the addon write directly to the object, thereby eliminating the need for a memory copy. Unfortunately I haven't been able to get this to work. Here's the modified code that doesn't work:
caller.js
var image_data = canvas_context.createImageData(w, h);
var addon = require('decoder.node');
var decoder = new addon.Decoder();
decoder.getPixelArray(image_data.data);
canvas_context.putImageData(image_data);
decoder.cpp
Handle<Value> Decoder::getPixelArray(const Arguments &args) {
HandleScope scope;
// unwrap self
DecoderObj *obj = ObjectWrap::Unwrap<DecoderObj>(args.This());
if (obj == NULL) return scope.Close(Number::New(1));
// convert image_data arg into local array
Local<Array> array = Array::Cast(*args[0]);
// get pointer to pixel data
PixelPtr *ptr = NULL;
uint64_t len = 0;
obj->GetPixelsPtr((PixelPtr*)&ptr, &len);
// populate image_data array with pixel ptr data
array->SetIndexedPropertiesToExternalArrayData(ptr, v8::kExternalUnsignedByteArray, len);
}
I know this doesn't work because putImageData() results in a black frame, whereas the original code successfully draws the actual frame as expected.
Here is my question: is it possible to create an ImageData object and/or Uint8ClampedArray in JavaScript, pass it by reference into a Node.js addon, have the addon write to the object's memory location, and then be able to use the object in the JavaScript context without performing a memory copy??
I don't know if I'm doing it wrong, or it's impossible, or both. :)