diff --git a/README.md b/README.md index a9c4b766..86b98d1b 100755 --- a/README.md +++ b/README.md @@ -163,6 +163,15 @@ var pipeline = sharp() readableStream.pipe(pipeline); ``` +```javascript +var pipeline = sharp().rotate(); +pipeline.clone().resize(800, 600).pipe(firstWritableStream); +pipeline.clone().extract(20, 20, 100, 100).pipe(secondWritableStream); +readableStream.pipe(pipeline); +// firstWritableStream receives auto-rotated, resized readableStream +// secondWritableStream receives auto-rotated, extracted region of readableStream +``` + ```javascript sharp('input.png') .rotate(180) @@ -347,6 +356,14 @@ Do not process input images where the number of pixels (width * height) exceeds `pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF). +#### clone() + +Takes a "snapshot" of the instance, returning a new instance. +Cloned instances inherit the input of their parent instance. +This allows multiple output Streams +and therefore multiple processing pipelines +to share a single input Stream. + ### Image transformation options #### resize(width, [height]) diff --git a/index.js b/index.js index 2f07f4b5..e7532eec 100755 --- a/index.js +++ b/index.js @@ -760,6 +760,24 @@ Sharp.prototype.metadata = function(callback) { } }; +/* + Clone new instance using existing options. + Cloned instances share the same input. +*/ +Sharp.prototype.clone = function() { + // Clone existing options + var clone = new Sharp(); + util._extend(clone.options, this.options); + clone.streamIn = false; + // Pass 'finish' event to clone for Stream-based input + this.on('finish', function() { + // Clone inherits input data + clone.options.bufferIn = this.options.bufferIn; + clone.emit('finish'); + }); + return clone; +}; + /* Get and set cache memory and item limits */ diff --git a/src/pipeline.cc b/src/pipeline.cc index 5a6dbb35..99940a6f 100755 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -1222,7 +1222,6 @@ NAN_METHOD(pipeline) { baton->bufferInLength = node::Buffer::Length(buffer); baton->bufferIn = new char[baton->bufferInLength]; memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength); - options->Set(NanNew("bufferIn"), NanNull()); } // ICC profile to use when input CMYK image has no embedded profile baton->iccProfilePath = *String::Utf8Value(options->Get(NanNew("iccProfilePath"))->ToString()); diff --git a/test/unit/clone.js b/test/unit/clone.js new file mode 100755 index 00000000..6e826827 --- /dev/null +++ b/test/unit/clone.js @@ -0,0 +1,58 @@ +'use strict'; + +var fs = require('fs'); +var assert = require('assert'); + +var sharp = require('../../index'); +var fixtures = require('../fixtures'); + +sharp.cache(0); + +describe('Clone', function() { + it('Read from Stream and write to multiple Streams', function(done) { + var finishEventsExpected = 2; + // Output stream 1 + var output1 = fixtures.path('output.multi-stream.1.jpg'); + var writable1 = fs.createWriteStream(output1); + writable1.on('finish', function() { + sharp(output1).toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fs.unlinkSync(output1); + finishEventsExpected--; + if (finishEventsExpected === 0) { + done(); + } + }); + }); + // Output stream 2 + var output2 = fixtures.path('output.multi-stream.2.jpg'); + var writable2 = fs.createWriteStream(output2); + writable2.on('finish', function() { + sharp(output2).toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(data.length, info.size); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(100, info.width); + assert.strictEqual(122, info.height); + fs.unlinkSync(output2); + finishEventsExpected--; + if (finishEventsExpected === 0) { + done(); + } + }); + }); + // Create parent instance + var rotator = sharp().rotate(90); + // Cloned instances with differing dimensions + rotator.clone().resize(320, 240).pipe(writable1); + rotator.clone().resize(100).pipe(writable2); + // Go + fs.createReadStream(fixtures.inputJpg).pipe(rotator); + }); +});