diff --git a/docs/changelog.md b/docs/changelog.md index 0a45c9e5..c996f174 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,12 @@ Requires libvips v8.3.1 +#### v0.15.1 - TBD + +* Concat Stream-based input in single operation for ~+3% perf and less GC. + [#429](https://github.com/lovell/sharp/issues/429) + [@papandreou](https://github.com/papandreou) + #### v0.15.0 - 21st May 2016 * Use libvips' new Lanczos 3 kernel as default for image reduction. diff --git a/index.js b/index.js index b0d40f47..c8e5ad48 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,7 @@ var Sharp = function(input, options) { stream.Duplex.call(this); this.options = { // input options - bufferIn: null, + bufferIn: [], streamIn: false, sequentialRead: false, limitInputPixels: maximum.pixels, @@ -217,18 +217,8 @@ Sharp.prototype._inputOptions = function(options) { Sharp.prototype._write = function(chunk, encoding, callback) { /*jslint unused: false */ if (this.options.streamIn) { - if (typeof chunk === 'object' && chunk instanceof Buffer) { - if (this.options.bufferIn instanceof Buffer) { - // Append to existing Buffer - this.options.bufferIn = Buffer.concat( - [this.options.bufferIn, chunk], - this.options.bufferIn.length + chunk.length - ); - } else { - // Create new Buffer - this.options.bufferIn = new Buffer(chunk.length); - chunk.copy(this.options.bufferIn); - } + if (isBuffer(chunk)) { + this.options.bufferIn.push(chunk); callback(); } else { callback(new Error('Non-Buffer data on Writable Stream')); @@ -238,6 +228,15 @@ Sharp.prototype._write = function(chunk, encoding, callback) { } }; +/* + Flattens the array of chunks in bufferIn +*/ +Sharp.prototype._flattenBufferIn = function() { + if (Array.isArray(this.options.bufferIn)) { + this.options.bufferIn = Buffer.concat(this.options.bufferIn); + } +}; + // Weighting to apply to image crop module.exports.gravity = { center: 0, @@ -882,6 +881,7 @@ Sharp.prototype._pipeline = function(callback) { if (this.options.streamIn) { // output=file/buffer, input=stream this.on('finish', function() { + that._flattenBufferIn(); sharp.pipeline(that.options, callback); }); } else { @@ -894,6 +894,7 @@ Sharp.prototype._pipeline = function(callback) { if (this.options.streamIn) { // output=stream, input=stream this.on('finish', function() { + that._flattenBufferIn(); sharp.pipeline(that.options, function(err, data, info) { if (err) { that.emit('error', err); @@ -923,6 +924,7 @@ Sharp.prototype._pipeline = function(callback) { // output=promise, input=stream return new BluebirdPromise(function(resolve, reject) { that.on('finish', function() { + that._flattenBufferIn(); sharp.pipeline(that.options, function(err, data) { if (err) { reject(err); @@ -956,6 +958,7 @@ Sharp.prototype.metadata = function(callback) { if (typeof callback === 'function') { if (this.options.streamIn) { this.on('finish', function() { + that._flattenBufferIn(); sharp.metadata(that.options, callback); }); } else { @@ -966,6 +969,7 @@ Sharp.prototype.metadata = function(callback) { if (this.options.streamIn) { return new BluebirdPromise(function(resolve, reject) { that.on('finish', function() { + that._flattenBufferIn(); sharp.metadata(that.options, function(err, metadata) { if (err) { reject(err); @@ -994,13 +998,15 @@ Sharp.prototype.metadata = function(callback) { Cloned instances share the same input. */ Sharp.prototype.clone = function() { + var that = this; // Clone existing options var clone = new Sharp(); util._extend(clone.options, this.options); // Pass 'finish' event to clone for Stream-based input this.on('finish', function() { // Clone inherits input data - clone.options.bufferIn = this.options.bufferIn; + that._flattenBufferIn(); + clone.options.bufferIn = that.options.bufferIn; clone.emit('finish'); }); return clone;