From 843dab9134f55a3c10db78f716c4ab3e6846d346 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:04:53 -0800 Subject: [PATCH 1/9] Misc --- lib/buffer.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 6787881..6b5fb20 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -19,14 +19,4 @@ Buffer.prototype.concat = function(other) { 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 +}; \ No newline at end of file From 5ae2c57753c5e7bcc127b3eb3c1479095040b0e2 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:06:28 -0800 Subject: [PATCH 2/9] Removed buffer utils --- lib/buffer.js | 22 ---------------------- lib/canvas.js | 26 -------------------------- 2 files changed, 48 deletions(-) delete mode 100644 lib/buffer.js diff --git a/lib/buffer.js b/lib/buffer.js deleted file mode 100644 index 6b5fb20..0000000 --- a/lib/buffer.js +++ /dev/null @@ -1,22 +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; -}; \ No newline at end of file diff --git a/lib/canvas.js b/lib/canvas.js index e363504..87157ed 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. */ @@ -103,26 +97,6 @@ Canvas.prototype.createSyncPNGStream = function(){ return new PNGStream(this, true); }; -/** - * 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. * From 2955ae7bfa4d05290440ce559011d8be151262cc Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:13:16 -0800 Subject: [PATCH 3/9] Stubbed out Canvas::ToBuffer() --- src/Canvas.cc | 8 ++++++++ src/Canvas.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/Canvas.cc b/src/Canvas.cc index f0c2e21..88249ac 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -33,6 +33,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,6 +103,13 @@ Canvas::SetHeight(Local prop, Local val, const AccessorInfo &info } } +Handle +Canvas::ToBuffer(const Arguments &args) { + HandleScope scope; + + return Undefined(); +} + /* * Canvas::StreamPNG callback. */ 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); From 51328138af9b949cc67806dd2131d94f715ccfd5 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:32:16 -0800 Subject: [PATCH 4/9] Implemented sync toBuffer() --- src/Canvas.cc | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 88249ac..e9580af 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -19,6 +19,8 @@ using namespace node; typedef struct { Handle fn; + unsigned len; + uint8_t *data; } closure_t; /* @@ -103,9 +105,51 @@ 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; +} + Handle Canvas::ToBuffer(const Arguments &args) { HandleScope scope; + + // Async + if (args[0]->IsFunction()) { + + } else { + Canvas *canvas = ObjectWrap::Unwrap(args.This()); + 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_; + } + } return Undefined(); } @@ -115,7 +159,7 @@ Canvas::ToBuffer(const Arguments &args) { */ 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) @@ -144,7 +188,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(); From 9a1484de3c58279d7d2de0641c1a121b5db5cebb Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:40:31 -0800 Subject: [PATCH 5/9] Prep for async toBuffer() --- src/Canvas.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index e9580af..54f97e1 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -18,6 +18,7 @@ using namespace node; */ typedef struct { + Persistent pfn; Handle fn; unsigned len; uint8_t *data; @@ -128,12 +129,16 @@ toBuffer(void *c, const uint8_t *data, unsigned len) { 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; + // TODO: only one callback fn in closure + closure->fn = Persistent::New(Handle::Cast(args[0])); + return Undefined(); } else { - Canvas *canvas = ObjectWrap::Unwrap(args.This()); closure_t closure; closure.len = 0; @@ -150,8 +155,6 @@ Canvas::ToBuffer(const Arguments &args) { return buf->handle_; } } - - return Undefined(); } /* From 0025e5907c034421f6731b886c786d588119819a Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 16:58:58 -0800 Subject: [PATCH 6/9] Added async toBuffer() --- src/Canvas.cc | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 54f97e1..564c1f6 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -22,6 +22,8 @@ typedef struct { Handle fn; unsigned len; uint8_t *data; + Canvas *canvas; + cairo_status_t status; } closure_t; /* @@ -126,6 +128,53 @@ toBuffer(void *c, const uint8_t *data, unsigned 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); + // TODO: Unref(); + + 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; @@ -135,8 +184,12 @@ Canvas::ToBuffer(const Arguments &args) { if (args[0]->IsFunction()) { closure_t *closure = new closure_t; closure->len = 0; + closure->canvas = canvas; // TODO: only one callback fn in closure - closure->fn = Persistent::New(Handle::Cast(args[0])); + 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; From 37fc4a6398760103dd595a69fd2246082043b9c3 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 17:07:02 -0800 Subject: [PATCH 7/9] Misc --- src/Canvas.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Canvas.cc b/src/Canvas.cc index 564c1f6..6536db6 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -140,7 +140,7 @@ EIO_ToBuffer(eio_req *req) { closure->canvas->getSurface() , toBuffer , closure); - + return 0; } @@ -153,7 +153,6 @@ EIO_AfterToBuffer(eio_req *req) { HandleScope scope; closure_t *closure = (closure_t *) req->data; ev_unref(EV_DEFAULT_UC); - // TODO: Unref(); if (closure->status) { Handle argv[1] = { Canvas::Error(closure->status) }; From 8dcc42c5f73aebccecc43f4a96930ec4b6a3c268 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 17:10:14 -0800 Subject: [PATCH 8/9] Added new async toBuffer() tests --- test/canvas.test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/canvas.test.js b/test/canvas.test.js index bb0e766..498d505 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, done){ + 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){ From 5146c27886e8d2d6183e856167533316c0173a7a Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Mon, 8 Nov 2010 17:24:20 -0800 Subject: [PATCH 9/9] Added async toDataURL() support --- lib/canvas.js | 28 ++++++++++++++++++++++------ test/canvas.test.js | 18 ++++++++++++++++-- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/canvas.js b/lib/canvas.js index 87157ed..90c74e5 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -98,17 +98,33 @@ Canvas.prototype.createSyncPNGStream = function(){ }; /** - * 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/test/canvas.test.js b/test/canvas.test.js index 498d505..44a8a43 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -242,7 +242,7 @@ module.exports = { assert.length(buf, 252); }, - 'test Canvas#toBuffer() async': function(assert, done){ + '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()); @@ -271,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'); @@ -279,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