diff --git a/lib/buffer.js b/lib/buffer.js deleted file mode 100644 index 6787881..0000000 --- a/lib/buffer.js +++ /dev/null @@ -1,32 +0,0 @@ - -/*! - * Canvas - Buffer - * Copyright (c) 2010 LearnBoost - * MIT Licensed - */ - -/** - * Concatenate `this` Buffer with `other`. - * - * @param {Buffer} other - * @return {Buffer} - * @api public - */ - -Buffer.prototype.concat = function(other) { - var len = this.length - , buf = new Buffer(len + other.length); - this.copy(buf, 0, 0); - other.copy(buf, len, 0); - return buf; -}; - -// patch if not v0.2 of node.js and Buffer.isBuffer fails for SlowBuffer -if (process.version.indexOf("v0.2") == -1) { - var SlowBuffer = process.binding('buffer').SlowBuffer; - if (!Buffer.isBuffer(new SlowBuffer(10))) { - Buffer.isBuffer = function (b) { - return (b instanceof Buffer) || (b instanceof SlowBuffer); - }; - } -} \ No newline at end of file diff --git a/lib/canvas.js b/lib/canvas.js index e363504..90c74e5 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -41,12 +41,6 @@ exports.cairoVersion = cairoVersion; exports.Context2d = Context2d; exports.PNGStream = PNGStream; -/** - * Buffer extensions. - */ - -require('./buffer'); - /** * Context2d implementation. */ @@ -104,37 +98,33 @@ Canvas.prototype.createSyncPNGStream = function(){ }; /** - * Return a `Buffer` instance consisting of the PNG image data. - * - * @return {Buffer} - * @api public - */ - -Canvas.prototype.toBuffer = function(){ - var buf; - this.streamPNGSync(function(err, chunk, len){ - if (err) throw err; - if (len) { - buf = buf - ? buf.concat(chunk) - : chunk; - } - }); - return buf; -}; - -/** - * Return a data url. + * Return a data url. Pass a function for async support. * - * @param {String} type + * @param {String|Function} type + * @param {Function} fn * @return {String} * @api public */ -Canvas.prototype.toDataURL = function(type){ - // TODO: jpeg / svg / pdf :) +Canvas.prototype.toDataURL = function(type, fn){ + // Default to png type = type || 'image/png'; + + // Allow callback as first arg + if ('function' == typeof type) fn = type, type = 'image/png'; + + // Throw on non-png if ('image/png' != type) throw new Error('currently only image/png is supported'); - return 'data:' + type - + ';base64,' + this.toBuffer().toString('base64'); + + var prefix = 'data:' + type + ';base64,'; + + if (fn) { + this.toBuffer(function(err, buf){ + if (err) return fn(err); + var str = 'data:' + type + fn(null, prefix + buf.toString('base64')); + }); + } else { + return prefix + this.toBuffer().toString('base64'); + } }; diff --git a/src/Canvas.cc b/src/Canvas.cc index f0c2e21..6536db6 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -18,7 +18,12 @@ using namespace node; */ typedef struct { + Persistent pfn; Handle fn; + unsigned len; + uint8_t *data; + Canvas *canvas; + cairo_status_t status; } closure_t; /* @@ -33,6 +38,7 @@ Canvas::Initialize(Handle target) { t->SetClassName(String::NewSymbol("Canvas")); Local proto = t->PrototypeTemplate(); + NODE_SET_PROTOTYPE_METHOD(t, "toBuffer", ToBuffer); NODE_SET_PROTOTYPE_METHOD(t, "streamPNGSync", StreamPNGSync); proto->SetAccessor(String::NewSymbol("width"), GetWidth, SetWidth); proto->SetAccessor(String::NewSymbol("height"), GetHeight, SetHeight); @@ -102,12 +108,113 @@ Canvas::SetHeight(Local prop, Local val, const AccessorInfo &info } } +/* + * Canvas::ToBuffer callback. + */ + +static cairo_status_t +toBuffer(void *c, const uint8_t *data, unsigned len) { + closure_t *closure = (closure_t *) c; + // TODO: mem handling + if (closure->len) { + closure->data = (uint8_t *) realloc(closure->data, closure->len + len); + memcpy(closure->data + closure->len, data, len); + closure->len += len; + } else { + closure->data = (uint8_t *) malloc(len); + memcpy(closure->data, data, len); + closure->len += len; + } + return CAIRO_STATUS_SUCCESS; +} + +/* + * EIO toBuffer callback. + */ + +static int +EIO_ToBuffer(eio_req *req) { + closure_t *closure = (closure_t *) req->data; + + closure->status = cairo_surface_write_to_png_stream( + closure->canvas->getSurface() + , toBuffer + , closure); + + return 0; +} + +/* + * EIO after toBuffer callback. + */ + +static int +EIO_AfterToBuffer(eio_req *req) { + HandleScope scope; + closure_t *closure = (closure_t *) req->data; + ev_unref(EV_DEFAULT_UC); + + if (closure->status) { + Handle argv[1] = { Canvas::Error(closure->status) }; + closure->pfn->Call(Context::GetCurrent()->Global(), 1, argv); + } else { + Buffer *buf = Buffer::New(closure->len); + memcpy(buf->data(), closure->data, closure->len); + Handle argv[2] = { Null(), buf->handle_ }; + closure->pfn->Call(Context::GetCurrent()->Global(), 2, argv); + } + + closure->pfn.Dispose(); + delete closure; + return 0; +} + +/* + * Convert PNG data to a node::Buffer, async when a + * callback function is passed. + */ + +Handle +Canvas::ToBuffer(const Arguments &args) { + HandleScope scope; + Canvas *canvas = ObjectWrap::Unwrap(args.This()); + + // Async + if (args[0]->IsFunction()) { + closure_t *closure = new closure_t; + closure->len = 0; + closure->canvas = canvas; + // TODO: only one callback fn in closure + canvas->Ref(); + closure->pfn = Persistent::New(Handle::Cast(args[0])); + eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure); + ev_ref(EV_DEFAULT_UC); + return Undefined(); + } else { + closure_t closure; + closure.len = 0; + + TryCatch try_catch; + cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), toBuffer, &closure); + + if (try_catch.HasCaught()) { + return try_catch.ReThrow(); + } else if (status) { + return ThrowException(Canvas::Error(status)); + } else { + Buffer *buf = Buffer::New(closure.len); + memcpy(buf->data(), closure.data, closure.len); + return buf->handle_; + } + } +} + /* * Canvas::StreamPNG callback. */ static cairo_status_t -writeToBuffer(void *c, const uint8_t *data, unsigned len) { +streamPNG(void *c, const uint8_t *data, unsigned len) { closure_t *closure = (closure_t *) c; Buffer *buf = Buffer::New(len); #if NODE_VERSION_AT_LEAST(0,3,0) @@ -136,7 +243,7 @@ Canvas::StreamPNGSync(const Arguments &args) { closure.fn = Handle::Cast(args[0]); TryCatch try_catch; - cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure); + cairo_status_t status = cairo_surface_write_to_png_stream(canvas->getSurface(), streamPNG, &closure); if (try_catch.HasCaught()) { return try_catch.ReThrow(); diff --git a/src/Canvas.h b/src/Canvas.h index 182a126..6902b26 100644 --- a/src/Canvas.h +++ b/src/Canvas.h @@ -52,6 +52,7 @@ class Canvas: public node::ObjectWrap { int height; static void Initialize(Handle target); static Handle New(const Arguments &args); + static Handle ToBuffer(const Arguments &args); static Handle GetWidth(Local prop, const AccessorInfo &info); static Handle GetHeight(Local prop, const AccessorInfo &info); static void SetWidth(Local prop, Local val, const AccessorInfo &info); diff --git a/test/canvas.test.js b/test/canvas.test.js index bb0e766..44a8a43 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -237,7 +237,17 @@ module.exports = { }, 'test Canvas#toBuffer()': function(assert){ - assert.ok(Buffer.isBuffer(new Canvas(200, 200).toBuffer()), 'Canvas#toBuffer() failed'); + var buf = new Canvas(200,200).toBuffer(); + assert.equal('PNG', buf.slice(1,4).toString()); + assert.length(buf, 252); + }, + + 'test Canvas#toBuffer() async': function(assert){ + new Canvas(200, 200).toBuffer(function(err, buf){ + assert.ok(!err); + assert.equal('PNG', buf.slice(1,4).toString()); + assert.length(buf, 252); + }); }, 'test Canvas#toDataURL()': function(assert){ @@ -261,7 +271,7 @@ module.exports = { assert.equal(str, canvas.toDataURL(), 'Canvas#toDataURL() failed'); assert.equal(str, canvas.toDataURL('image/png'), 'Canvas#toDataURL() failed'); - + var err; try { canvas.toDataURL('image/jpeg'); @@ -269,5 +279,19 @@ module.exports = { err = e; } assert.equal('currently only image/png is supported', err.message); + }, + + 'test Canvas#toDataURL() async': function(assert){ + new Canvas(200,200).toDataURL(function(err, str){ + assert.ok(!err); + assert.length(str, 358); + }); + }, + + 'test Canvas#toDataURL() async with type': function(assert){ + new Canvas(200,200).toDataURL('image/png', function(err, str){ + assert.ok(!err); + assert.length(str, 358); + }); } } \ No newline at end of file