diff --git a/docs/api.md b/docs/api.md index f2f9573e..dacbc1e2 100644 --- a/docs/api.md +++ b/docs/api.md @@ -298,6 +298,25 @@ sharp(input) }); ``` +#### extractChannel(channel) + +Extract a channel from the image. The following channel names are equivalent: + + * Red: `0, 'red'` + * Green: `1, 'green'` + * Blue: `2, 'blue'` + +The result will be a single-channel grayscale image. + +```javascript +sharp(input) + .extractChannel('green') + .toFile('input_green.jpg',function(err, info) { + // info.channels === 1 + // input_green.jpg contains the green channel of the input image + }); +``` + #### background(rgba) Set the background for the `embed`, `flatten` and `extend` operations. diff --git a/index.js b/index.js index 414fe206..937d3ba3 100644 --- a/index.js +++ b/index.js @@ -115,6 +115,7 @@ var Sharp = function(input, options) { withMetadataOrientation: -1, tileSize: 256, tileOverlap: 0, + extractChannel: -1, // Function to notify of queue length changes queueListener: function(queueLength) { module.exports.queue.emit('change', queueLength); @@ -303,6 +304,21 @@ Sharp.prototype.extract = function(options) { return this; }; +Sharp.prototype.extractChannel = function(channel) { + if (channel === 'red') + channel = 0; + else if (channel === 'green') + channel = 1; + else if (channel === 'blue') + channel = 2; + if(isInteger(channel) && inRange(channel,0,4)) { + this.options.extractChannel = channel; + } else { + throw new Error('Cannot extract invalid channel ' + channel); + } + return this; +}; + /* Set the background colour for embed and flatten operations. Delegates to the 'Color' module, which can throw an Error diff --git a/src/pipeline.cc b/src/pipeline.cc index c3ac736d..99ef7a10 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -789,6 +789,15 @@ class PipelineWorker : public AsyncWorker { image = Bandbool(image, baton->bandBoolOp); } + // Extract an image channel (aka vips band) + if(baton->extractChannel > -1) { + if(baton->extractChannel >= image.bands()) { + (baton->err).append("Cannot extract channel from image. Too few channels in image."); + return Error(); + } + image = image.extract_band(baton->extractChannel); + } + // Override EXIF Orientation tag if (baton->withMetadata && baton->withMetadataOrientation != -1) { SetExifOrientation(image, baton->withMetadataOrientation); @@ -1175,6 +1184,7 @@ NAN_METHOD(pipeline) { baton->extendBottom = attrAs(options, "extendBottom"); baton->extendLeft = attrAs(options, "extendLeft"); baton->extendRight = attrAs(options, "extendRight"); + baton->extractChannel = attrAs(options, "extractChannel"); // Output options baton->progressive = attrAs(options, "progressive"); baton->quality = attrAs(options, "quality"); diff --git a/src/pipeline.h b/src/pipeline.h index 906ee3fb..c6a64f5a 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -94,6 +94,7 @@ struct PipelineBaton { double convKernelScale; double convKernelOffset; VipsOperationBoolean bandBoolOp; + int extractChannel; int tileSize; int tileOverlap; VipsForeignDzContainer tileContainer; @@ -155,6 +156,7 @@ struct PipelineBaton { convKernelScale(0.0), convKernelOffset(0.0), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), + extractChannel(-1), tileSize(256), tileOverlap(0), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), diff --git a/test/fixtures/expected/extract-blue.jpg b/test/fixtures/expected/extract-blue.jpg new file mode 100644 index 00000000..c2a332f9 Binary files /dev/null and b/test/fixtures/expected/extract-blue.jpg differ diff --git a/test/fixtures/expected/extract-green.jpg b/test/fixtures/expected/extract-green.jpg new file mode 100644 index 00000000..2de81c25 Binary files /dev/null and b/test/fixtures/expected/extract-green.jpg differ diff --git a/test/fixtures/expected/extract-red.jpg b/test/fixtures/expected/extract-red.jpg new file mode 100644 index 00000000..f7276f07 Binary files /dev/null and b/test/fixtures/expected/extract-red.jpg differ diff --git a/test/unit/extractChannel.js b/test/unit/extractChannel.js new file mode 100644 index 00000000..843b8567 --- /dev/null +++ b/test/unit/extractChannel.js @@ -0,0 +1,72 @@ +'use strict'; + +var assert = require('assert'); + +var sharp = require('../../index'); +var fixtures = require('../fixtures'); + +describe('Image channel extraction', function() { + + it('Red channel', function(done) { + sharp(fixtures.inputJpg) + .extractChannel('red') + .resize(320,240) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fixtures.assertSimilar(fixtures.expected('extract-red.jpg'), data, done); + }); + }); + + it('Green channel', function(done) { + sharp(fixtures.inputJpg) + .extractChannel('green') + .resize(320,240) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fixtures.assertSimilar(fixtures.expected('extract-green.jpg'), data, done); + }); + }); + + it('Blue channel', function(done) { + sharp(fixtures.inputJpg) + .extractChannel('blue') + .resize(320,240) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fixtures.assertSimilar(fixtures.expected('extract-blue.jpg'), data, done); + }); + }); + + it('Blue channel by number', function(done) { + sharp(fixtures.inputJpg) + .extractChannel(2) + .resize(320,240) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fixtures.assertSimilar(fixtures.expected('extract-blue.jpg'), data, done); + }); + }); + + it('Invalid channel number', function() { + assert.throws(function() { + sharp(fixtures.inputJpg) + .extractChannel(-1); + }); + }); + + it('No arguments', function() { + assert.throws(function() { + sharp(fixtures.inputJpg) + .extractChannel(); + }); + }); + +});