Skip to content

Better streams that support being returned asynchronously. #765

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions lib/imagestream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

/*!
* Canvas - ImageStream
* Copyright (c) 2010 LearnBoost <[email protected]>
* 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;
38 changes: 13 additions & 25 deletions lib/jpegstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Module dependencies.
*/

var Readable = require('stream').Readable;
var ImageStream = require('./imagestream');
var util = require('util');

/**
Expand All @@ -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
*/
Expand All @@ -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
Expand All @@ -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);
}
};
34 changes: 13 additions & 21 deletions lib/pdfstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Module dependencies.
*/

var Readable = require('stream').Readable;
var ImageStream = require('./imagestream');
var util = require('util');

/**
Expand All @@ -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
Expand All @@ -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);
}
};
49 changes: 12 additions & 37 deletions lib/pngstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Module dependencies.
*/

var Readable = require('stream').Readable;
var ImageStream = require('./imagestream');
var util = require('util');

/**
Expand All @@ -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
Expand All @@ -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);
}
};
90 changes: 90 additions & 0 deletions test/canvas.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down