diff --git a/lib/imagestream.js b/lib/imagestream.js new file mode 100644 index 000000000..faf97211e --- /dev/null +++ b/lib/imagestream.js @@ -0,0 +1,50 @@ +'use strict'; + +/*! + * Canvas - ImageStream + * Copyright (c) 2010 LearnBoost + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Readable = require('stream').Readable, + util = require('util'); + +if (typeof Readable === 'undefined') { + throw new Error('Node versions <0.10 are no longer supported.'); +} + +/** + * A base class for a stream of compressed image data. + * + * @api public + */ + +var ImageStream = module.exports = function ImageStream() { + + Readable.call(this, {}); + + this._started = false; +}; + +util.inherits(ImageStream, Readable); + +function noop() {} + +ImageStream.prototype._read = function () { + // For now we're not controlling the c++ code's data emission, so we only + // call canvas.streamPNGSync once and let it emit data at will. + this._read = noop; + var self = this; + + process.nextTick(function () { + self._stream_method(self._handle_chunk.bind(self)); + }); +}; + +ImageStream.prototype._stream_method = noop; + +ImageStream.prototype._handle_chunk = noop; diff --git a/lib/jpegstream.js b/lib/jpegstream.js index 6c8f0e8cb..2a030ddd8 100644 --- a/lib/jpegstream.js +++ b/lib/jpegstream.js @@ -10,7 +10,7 @@ * Module dependencies. */ -var Readable = require('stream').Readable; +var ImageStream = require('./imagestream'); var util = require('util'); /** @@ -26,6 +26,7 @@ var util = require('util'); * stream.pipe(out); * * @param {Canvas} canvas + * @param {Object} options options passed to the JPEG encoder. * @param {Boolean} sync * @api public */ @@ -35,7 +36,7 @@ var JPEGStream = module.exports = function JPEGStream(canvas, options, sync) { throw new TypeError("Class constructors cannot be invoked without 'new'"); } - Readable.call(this); + ImageStream.call(this); var self = this; var method = sync @@ -48,30 +49,17 @@ var JPEGStream = module.exports = function JPEGStream(canvas, options, sync) { // TODO: implement async if ('streamJPEG' == method) method = 'streamJPEGSync'; this.method = method; + this._stream_method = canvas[method].bind(canvas, options.bufsize, options.quality, options.progressive); }; -util.inherits(JPEGStream, Readable); +util.inherits(JPEGStream, ImageStream); -function noop() {} - -JPEGStream.prototype._read = function _read() { - // For now we're not controlling the c++ code's data emission, so we only - // call canvas.streamJPEGSync once and let it emit data at will. - this._read = noop; - var self = this; - var method = this.method; - var bufsize = this.options.bufsize; - var quality = this.options.quality; - var progressive = this.options.progressive; - process.nextTick(function(){ - self.canvas[method](bufsize, quality, progressive, function(err, chunk){ - if (err) { - self.emit('error', err); - } else if (chunk) { - self.push(chunk); - } else { - self.push(null); - } - }); - }); +JPEGStream.prototype._handle_chunk = function (err, chunk) { + if (err) { + this.emit('error', err); + } else if(chunk) { + this.push(chunk); + } else { + this.push(null); + } }; diff --git a/lib/pdfstream.js b/lib/pdfstream.js index 5f96ea702..1bd4be21b 100644 --- a/lib/pdfstream.js +++ b/lib/pdfstream.js @@ -8,7 +8,7 @@ * Module dependencies. */ -var Readable = require('stream').Readable; +var ImageStream = require('./imagestream'); var util = require('util'); /** @@ -33,7 +33,7 @@ var PDFStream = module.exports = function PDFStream(canvas, sync) { throw new TypeError("Class constructors cannot be invoked without 'new'"); } - Readable.call(this); + ImageStream.call(this); var self = this , method = sync @@ -45,26 +45,18 @@ var PDFStream = module.exports = function PDFStream(canvas, sync) { // TODO: implement async if ('streamPDF' == method) method = 'streamPDFSync'; this.method = method; -}; -util.inherits(PDFStream, Readable); + this._stream_method = canvas[method].bind(canvas); +}; -function noop() {} +util.inherits(PDFStream, ImageStream); -PDFStream.prototype._read = function _read() { - // For now we're not controlling the c++ code's data emission, so we only - // call canvas.streamPDFSync once and let it emit data at will. - this._read = noop; - var self = this; - process.nextTick(function(){ - self.canvas[self.method](function(err, chunk, len){ - if (err) { - self.emit('error', err); - } else if (len) { - self.push(chunk); - } else { - self.push(null); - } - }); - }); +PDFStream.prototype._handle_chunk = function (err, chunk, len) { + if (err) { + this.emit('error', err); + } else if(len) { + this.push(chunk); + } else { + this.push(null); + } }; diff --git a/lib/pngstream.js b/lib/pngstream.js index b0a68f04f..539f552e7 100644 --- a/lib/pngstream.js +++ b/lib/pngstream.js @@ -10,7 +10,7 @@ * Module dependencies. */ -var Readable = require('stream').Readable; +var ImageStream = require('./imagestream'); var util = require('util'); /** @@ -35,7 +35,7 @@ var PNGStream = module.exports = function PNGStream(canvas, sync) { throw new TypeError("Class constructors cannot be invoked without 'new'"); } - Readable.call(this); + ImageStream.call(this); var self = this; var method = sync @@ -47,43 +47,18 @@ var PNGStream = module.exports = function PNGStream(canvas, sync) { // TODO: implement async if ('streamPNG' === method) method = 'streamPNGSync'; this.method = method; -}; - -util.inherits(PNGStream, Readable); -var PNGStream = module.exports = function PNGStream(canvas, sync) { - Readable.call(this); - - var self = this; - var method = sync - ? 'streamPNGSync' - : 'streamPNG'; - this.sync = sync; - this.canvas = canvas; - - // TODO: implement async - if ('streamPNG' === method) method = 'streamPNGSync'; - this.method = method; + this._stream_method = canvas[method].bind(canvas); }; -util.inherits(PNGStream, Readable); - -function noop() {} +util.inherits(PNGStream, ImageStream); -PNGStream.prototype._read = function _read() { - // For now we're not controlling the c++ code's data emission, so we only - // call canvas.streamPNGSync once and let it emit data at will. - this._read = noop; - var self = this; - process.nextTick(function(){ - self.canvas[self.method](function(err, chunk, len){ - if (err) { - self.emit('error', err); - } else if (len) { - self.push(chunk); - } else { - self.push(null); - } - }); - }); +PNGStream.prototype._handle_chunk = function (err, chunk, len) { + if (err) { + this.emit('error', err); + } else if(len) { + this.push(chunk); + } else { + this.push(null); + } }; diff --git a/test/canvas.test.js b/test/canvas.test.js index c4e66d299..06daabf36 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -956,6 +956,96 @@ describe('Canvas', function () { s.on('end', done); }); + it('should handle PNG streams returned asynchronously, (AKA in a Promise)', function (done) { + + function build_stream(funk) { + var canvas = new Canvas(20, 20) + , stream = canvas.createSyncPNGStream(); + + process.nextTick(function () { + funk(stream); + }); + } + + build_stream(function (stream) { + var firstChunk = true; + stream.on('data', function(chunk){ + if (firstChunk) { + firstChunk = false; + assert.equal('PNG', chunk.slice(1,4).toString()); + } + }); + stream.on('end', function(){ + done(); + }); + stream.on('error', function(err) { + done(err); + }); + }) + }); + + it('should handle JPEG streams returned asynchronously, (AKA in a Promise)', function (done) { + + function build_stream(funk) { + var canvas = new Canvas(640, 480) + , stream = canvas.jpegStream(); + + process.nextTick(function () { + funk(stream); + }); + } + + build_stream(function (stream) { + var firstChunk = true; + var bytes = 0; + stream.on('data', function(chunk){ + if (firstChunk) { + firstChunk = false; + assert.equal(0xFF, chunk[0]); + assert.equal(0xD8, chunk[1]); + assert.equal(0xFF, chunk[2]); + } + bytes += chunk.length; + }); + stream.on('end', function(){ + assert.equal(bytes, 5427); + done(); + }); + stream.on('error', function(err) { + done(err); + }); + }) + }); + + it('should handle PDF streams returned asynchronously, (AKA in a Promise)', function (done) { + + function build_stream(funk) { + var canvas = new Canvas(640, 480, 'pdf') + , stream = canvas.createSyncPDFStream(); + + process.nextTick(function () { + funk(stream); + }); + } + + build_stream(function (stream) { + var firstChunk = true; + var bytes = 0; + stream.on('data', function (chunk) { + if (firstChunk) { + firstChunk = false; + assert.equal(chunk.slice(1, 4).toString(), 'PDF'); + } + }); + stream.on('end', function () { + done(); + }); + stream.on('error', function (err) { + done(err); + }); + }) + }); + it('Context2d#fill()', function() { var canvas = new Canvas(2, 2); var ctx = canvas.getContext('2d');