Add extractChannel operation to extract a channel from an image (#497)

This commit is contained in:
Matt Hirsch 2016-07-09 11:48:30 -04:00 committed by Lovell Fuller
parent f672f86b53
commit 83d8847f57
8 changed files with 119 additions and 0 deletions

View File

@ -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) #### background(rgba)
Set the background for the `embed`, `flatten` and `extend` operations. Set the background for the `embed`, `flatten` and `extend` operations.

View File

@ -115,6 +115,7 @@ var Sharp = function(input, options) {
withMetadataOrientation: -1, withMetadataOrientation: -1,
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
extractChannel: -1,
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function(queueLength) { queueListener: function(queueLength) {
module.exports.queue.emit('change', queueLength); module.exports.queue.emit('change', queueLength);
@ -303,6 +304,21 @@ Sharp.prototype.extract = function(options) {
return this; 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. Set the background colour for embed and flatten operations.
Delegates to the 'Color' module, which can throw an Error Delegates to the 'Color' module, which can throw an Error

View File

@ -789,6 +789,15 @@ class PipelineWorker : public AsyncWorker {
image = Bandbool(image, baton->bandBoolOp); 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 // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
SetExifOrientation(image, baton->withMetadataOrientation); SetExifOrientation(image, baton->withMetadataOrientation);
@ -1175,6 +1184,7 @@ NAN_METHOD(pipeline) {
baton->extendBottom = attrAs<int32_t>(options, "extendBottom"); baton->extendBottom = attrAs<int32_t>(options, "extendBottom");
baton->extendLeft = attrAs<int32_t>(options, "extendLeft"); baton->extendLeft = attrAs<int32_t>(options, "extendLeft");
baton->extendRight = attrAs<int32_t>(options, "extendRight"); baton->extendRight = attrAs<int32_t>(options, "extendRight");
baton->extractChannel = attrAs<int32_t>(options, "extractChannel");
// Output options // Output options
baton->progressive = attrAs<bool>(options, "progressive"); baton->progressive = attrAs<bool>(options, "progressive");
baton->quality = attrAs<int32_t>(options, "quality"); baton->quality = attrAs<int32_t>(options, "quality");

View File

@ -94,6 +94,7 @@ struct PipelineBaton {
double convKernelScale; double convKernelScale;
double convKernelOffset; double convKernelOffset;
VipsOperationBoolean bandBoolOp; VipsOperationBoolean bandBoolOp;
int extractChannel;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
@ -155,6 +156,7 @@ struct PipelineBaton {
convKernelScale(0.0), convKernelScale(0.0),
convKernelOffset(0.0), convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST), bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

BIN
test/fixtures/expected/extract-blue.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
test/fixtures/expected/extract-green.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
test/fixtures/expected/extract-red.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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();
});
});
});