From 44661cd332bfd4210c7ee4f9545a1ceb72e2f96b Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 16:40:06 -0700 Subject: [PATCH 1/7] Started buffer streaming --- src/canvas.cc | 42 ++++++++++++++++++++++++++++++++++++++++++ src/canvas.h | 1 + 2 files changed, 43 insertions(+) diff --git a/src/canvas.cc b/src/canvas.cc index ea7bd48..c457e16 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -5,6 +5,7 @@ // Copyright (c) 2010 LearnBoost // +#include #include "canvas.h" using namespace v8; @@ -20,6 +21,7 @@ Canvas::Initialize(Handle target) { t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(String::NewSymbol("Canvas")); + NODE_SET_PROTOTYPE_METHOD(t, "streamPNG", StreamPNG); NODE_SET_PROTOTYPE_METHOD(t, "savePNG", SavePNG); target->Set(String::NewSymbol("Canvas"), t->GetFunction()); } @@ -42,6 +44,46 @@ Canvas::New(const Arguments &args) { return args.This(); } +typedef struct { + unsigned char *pos; + unsigned char *end; + Handle fn; +} closure_t; + +static cairo_status_t +writeToBuffer(void *c, const uint8_t *data, unsigned len) { + printf("bytes %d\n", len); + closure_t *closure = (closure_t *) c; + if (closure->pos + len > closure->end) + return CAIRO_STATUS_WRITE_ERROR; + memcpy(closure->pos, data, len); + Handle argv[1]; + argv[0] = String::New("test"); + closure->fn.Call(Context::GetCurrent()->Global(), 1, argv); + closure->pos += len; + return CAIRO_STATUS_SUCCESS; +} + +/* + * Return a node Buffer containing PNG data. + */ + +Handle +Canvas::StreamPNG(const Arguments &args) { + HandleScope scope; + // TODO: error handling + if (!args[0]->IsFunction()) + return ThrowException(Exception::TypeError(String::New("function required"))); + Canvas *canvas = ObjectWrap::Unwrap(args.This()); + closure_t closure; + uint8_t data[64 * 1024]; + closure.pos = data; + closure.end = data + sizeof(data); + closure.fn = Handle::Cast(args[0]); // TODO: leakage? + cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure); + return Undefined(); +} + /* * Initialize cairo surface. */ diff --git a/src/canvas.h b/src/canvas.h index f359be5..f933648 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -38,6 +38,7 @@ class Canvas: public node::ObjectWrap { static void Initialize(Handle target); static Handle New(const Arguments &args); static Handle SavePNG(const Arguments &args); + static Handle StreamPNG(const Arguments &args); inline cairo_surface_t *getSurface(){ return _surface; } Canvas(int width, int height); From 0ceeaf477e0ae9c1498fc2b3c51078cf51d71f42 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 17:08:14 -0700 Subject: [PATCH 2/7] Typos --- src/canvas.cc | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/canvas.cc b/src/canvas.cc index c457e16..74aa5eb 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -5,10 +5,12 @@ // Copyright (c) 2010 LearnBoost // -#include #include "canvas.h" +#include +#include using namespace v8; +using namespace node; /* * Initialize Canvas. @@ -45,22 +47,19 @@ Canvas::New(const Arguments &args) { } typedef struct { - unsigned char *pos; - unsigned char *end; Handle fn; } closure_t; static cairo_status_t writeToBuffer(void *c, const uint8_t *data, unsigned len) { - printf("bytes %d\n", len); closure_t *closure = (closure_t *) c; - if (closure->pos + len > closure->end) - return CAIRO_STATUS_WRITE_ERROR; - memcpy(closure->pos, data, len); Handle argv[1]; - argv[0] = String::New("test"); - closure->fn.Call(Context::GetCurrent()->Global(), 1, argv); - closure->pos += len; + Buffer *buf = Buffer::New(len); + memcpy(buf->data(), data, len); + argv[0] = buf->handle_; + closure->fn->Call(Context::GetCurrent()->Global(), 1, argv); + // TODO: CAIRO_STATUS_NO_MEMORY + // TODO: pass len return CAIRO_STATUS_SUCCESS; } @@ -76,9 +75,6 @@ Canvas::StreamPNG(const Arguments &args) { return ThrowException(Exception::TypeError(String::New("function required"))); Canvas *canvas = ObjectWrap::Unwrap(args.This()); closure_t closure; - uint8_t data[64 * 1024]; - closure.pos = data; - closure.end = data + sizeof(data); closure.fn = Handle::Cast(args[0]); // TODO: leakage? cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure); return Undefined(); From 1c5d3346e72ae0a60e59af6565516ab0d4545bc2 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 17:12:52 -0700 Subject: [PATCH 3/7] Working --- src/canvas.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/canvas.cc b/src/canvas.cc index 74aa5eb..c390640 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -60,6 +60,7 @@ writeToBuffer(void *c, const uint8_t *data, unsigned len) { closure->fn->Call(Context::GetCurrent()->Global(), 1, argv); // TODO: CAIRO_STATUS_NO_MEMORY // TODO: pass len + // TODO: event emitter return CAIRO_STATUS_SUCCESS; } From 6af6212b3cad970f4de26827fa6fd66d222424cf Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 18:02:19 -0700 Subject: [PATCH 4/7] Fixed stupid segfault --- src/canvas.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/canvas.cc b/src/canvas.cc index c390640..86f942b 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -53,14 +53,14 @@ typedef struct { static cairo_status_t writeToBuffer(void *c, const uint8_t *data, unsigned len) { closure_t *closure = (closure_t *) c; - Handle argv[1]; + Handle argv[2]; Buffer *buf = Buffer::New(len); memcpy(buf->data(), data, len); argv[0] = buf->handle_; - closure->fn->Call(Context::GetCurrent()->Global(), 1, argv); + argv[1] = Integer::New(len); + closure->fn->Call(Context::GetCurrent()->Global(), 2, argv); // TODO: CAIRO_STATUS_NO_MEMORY - // TODO: pass len - // TODO: event emitter + // TODO: leak return CAIRO_STATUS_SUCCESS; } @@ -73,10 +73,10 @@ Canvas::StreamPNG(const Arguments &args) { HandleScope scope; // TODO: error handling if (!args[0]->IsFunction()) - return ThrowException(Exception::TypeError(String::New("function required"))); + return ThrowException(Exception::TypeError(String::New("callback function required"))); Canvas *canvas = ObjectWrap::Unwrap(args.This()); closure_t closure; - closure.fn = Handle::Cast(args[0]); // TODO: leakage? + closure.fn = Handle::Cast(args[0]); cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure); return Undefined(); } From 63b38e25d796ee94e046616d2c7e90214cacc8d7 Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 18:05:19 -0700 Subject: [PATCH 5/7] Passing null, 0 when done --- src/canvas.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/canvas.cc b/src/canvas.cc index 86f942b..05c6091 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -78,6 +78,10 @@ Canvas::StreamPNG(const Arguments &args) { closure_t closure; closure.fn = Handle::Cast(args[0]); cairo_surface_write_to_png_stream(canvas->getSurface(), writeToBuffer, &closure); + Handle argv[2]; + argv[0] = Null(); + argv[1] = Integer::New(0); + closure.fn->Call(Context::GetCurrent()->Global(), 2, argv); return Undefined(); } From 1e19b7ec10d78ab03ef42fc5c29d3e9a8c1b2f6f Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 18:42:29 -0700 Subject: [PATCH 6/7] Added PNGStream test --- lib/canvas.js | 14 +++++++++++++- src/canvas.cc | 1 + test/canvas.test.js | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/canvas.js b/lib/canvas.js index fa11b17..29f8d3e 100644 --- a/lib/canvas.js +++ b/lib/canvas.js @@ -14,7 +14,8 @@ var canvas = require('../build/default/canvas') , Canvas = canvas.Canvas , Context2d = canvas.CanvasRenderingContext2d , CanvasGradient = canvas.CanvasGradient - , cairoVersion = canvas.cairoVersion; + , cairoVersion = canvas.cairoVersion + , PNGStream = require('./pngstream'); /** * Export `Canvas` as the module. @@ -126,6 +127,17 @@ Canvas.prototype.getContext = function(contextId){ } }; +/** + * Create a `PNGStream` for `this` canvas. + * + * @return {PNGStream} + * @api public + */ + +Canvas.prototype.createPNGStream = function(){ + return new PNGStream(this); +}; + /** * Add `color` stop at the given `offset`. * diff --git a/src/canvas.cc b/src/canvas.cc index 05c6091..3d7d8b8 100644 --- a/src/canvas.cc +++ b/src/canvas.cc @@ -72,6 +72,7 @@ Handle Canvas::StreamPNG(const Arguments &args) { HandleScope scope; // TODO: error handling + // TODO: nonblocking if (!args[0]->IsFunction()) return ThrowException(Exception::TypeError(String::New("callback function required"))); Canvas *canvas = ObjectWrap::Unwrap(args.This()); diff --git a/test/canvas.test.js b/test/canvas.test.js index 4a73f17..e768177 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -14,6 +14,10 @@ function hash(val) { function assertChecksum(canvas, path, checksum, msg) { canvas.savePNG(path); + assertChecksumOf(canvas, path, checksum, msg); +} + +function assertChecksumOf(canvas, path, checksum, msg) { fs.readFile(path, function(err, buf){ assert.equal(hash(buf), checksum, msg); }); @@ -499,5 +503,47 @@ module.exports = { assert.ok(ctx.isPointInPath(60,110)); assert.ok(!ctx.isPointInPath(70,110)); assert.ok(!ctx.isPointInPath(50,120)); + }, + + 'test PNGStream': function(assert, beforeExit){ + var canvas = new Canvas(320, 320) + , ctx = canvas.getContext('2d') + , path = __dirname + '/images/pngstream.png' + , called = 0; + + ctx.strokeStyle = 'rgba(0,0,0,0.5)'; + ctx.strokeRect(0,0,320,320); + + ctx.fillStyle = 'rgba(0,0,0,0.02)'; + var steps = 200; + while (steps--) { + ctx.fillRect( + 160 - (steps / 2) + , 160 - (steps / 2) + , steps + , steps + ); + } + + var out = fs.createWriteStream(path) + , stream = canvas.createPNGStream(); + + out.on('close', function(){ + assertChecksumOf( + canvas + , path + , '04f2e1b4338de2d7451194cba7d29970' + , 'PNGStream failed'); + }); + + stream.on('data', function(chunk){ out.write(chunk); }); + stream.on('end', function(){ + ++called; + out.end(); + }); + + beforeExit(function(){ + assert.equal(1, called); + }); } } \ No newline at end of file From e967a5e39ee8e1b8de5c770a40e0d529d2a1cccc Mon Sep 17 00:00:00 2001 From: Tj Holowaychuk Date: Thu, 7 Oct 2010 18:43:56 -0700 Subject: [PATCH 7/7] Forgot to include pngstream.js :) --- lib/pngstream.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/pngstream.js diff --git a/lib/pngstream.js b/lib/pngstream.js new file mode 100644 index 0000000..831609f --- /dev/null +++ b/lib/pngstream.js @@ -0,0 +1,39 @@ + +/*! + * Canvas - PNGStream + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var EventEmitter = require('events').EventEmitter; + +/** + * Initialize a `PNGStream` with the given `canvas`. + * + * @param {Canvas} canvas + * @api public + */ + +var PNGStream = module.exports = function PNGStream(canvas) { + var self = this; + this.canvas = canvas; + process.nextTick(function(){ + canvas.streamPNG(function(chunk, len){ + if (len) { + self.emit('data', chunk, len); + } else { + self.emit('end'); + } + }); + }); +}; + +/** + * Inherit from `EventEmitter`. + */ + +PNGStream.prototype.__proto__ = EventEmitter.prototype; \ No newline at end of file