Add bandbool feature for channel-wise boolean operations (#496)

This commit is contained in:
Matt Hirsch 2016-07-07 16:03:49 -04:00 committed by Lovell Fuller
parent a982cfdb20
commit 65b7f7d7d5
12 changed files with 124 additions and 0 deletions

View File

@ -483,6 +483,24 @@ sharp('input.png')
}); });
``` ```
#### bandbool(operation)
Perform a bitwise boolean operation on image color channels (bands in vips terminology). The result is a single channel grayscale image. Bandbool is performed at the end of the image processing pipeline, after gamma correction, colorspace conversion, normalization, and other operations. This makes it possible to create an image that contains the unaltered result of the boolean operation. Note that the alpha channel of the image is included in `bandbool` operations. All channels are cast to an integer type before the operation. `bandbool` takes no effect on single channel images.
`operation` is a string containing the name of the bitwise operator to be appled to image color channels, which can be one of:
* `and` performs a bitwise and operation, like the c-operator `&`
* `or` performs a bitwise or operation, like the c-operator `|`
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`
```javascript
sharp('input.png')
.bandbool(sharp.bool.and)
.toFile('output.png')
```
In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`. For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
### Output ### Output
#### toFile(path, [callback]) #### toFile(path, [callback])

View File

@ -89,6 +89,7 @@ var Sharp = function(input, options) {
gamma: 0, gamma: 0,
greyscale: false, greyscale: false,
normalize: 0, normalize: 0,
bandBoolOp: null,
// overlay // overlay
overlayFileIn: '', overlayFileIn: '',
overlayBufferIn: null, overlayBufferIn: null,
@ -588,6 +589,22 @@ Sharp.prototype.normalize = function(normalize) {
}; };
Sharp.prototype.normalise = Sharp.prototype.normalize; Sharp.prototype.normalise = Sharp.prototype.normalize;
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
Sharp.prototype.bandbool = function(boolOp) {
if(typeof boolOp !== 'string') {
throw new Error('Invalid bandbool operation');
}
boolOp = boolOp.toLowerCase();
var ops = ['and', 'or', 'eor'];
if(ops.indexOf(boolOp) == -1) {
throw new Error('Invalid bandbool operation');
}
this.options.bandBoolOp = boolOp;
return this;
};
/* /*
Convert to greyscale Convert to greyscale
*/ */
@ -785,6 +802,12 @@ module.exports.interpolator = {
vsqbs: 'vsqbs', vsqbs: 'vsqbs',
vertexSplitQuadraticBasisSpline: 'vsqbs' vertexSplitQuadraticBasisSpline: 'vsqbs'
}; };
// Boolean operations for bandbool
module.exports.bool = {
and: 'and',
or: 'or',
eor: 'eor'
};
/* /*
Resize image to width x height pixels Resize image to width x height pixels

View File

@ -392,4 +392,11 @@ namespace sharp {
return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold; return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
} }
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
return image.bandbool(boolean);
}
} // namespace sharp } // namespace sharp

View File

@ -82,6 +82,11 @@ namespace sharp {
*/ */
VImage Threshold(VImage image, double const threshold, bool const thresholdColor); VImage Threshold(VImage image, double const threshold, bool const thresholdColor);
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
} // namespace sharp } // namespace sharp
#endif // SRC_OPERATIONS_H_ #endif // SRC_OPERATIONS_H_

View File

@ -55,6 +55,7 @@ using sharp::Sharpen;
using sharp::EntropyCrop; using sharp::EntropyCrop;
using sharp::TileCache; using sharp::TileCache;
using sharp::Threshold; using sharp::Threshold;
using sharp::Bandbool;
using sharp::ImageType; using sharp::ImageType;
using sharp::ImageTypeId; using sharp::ImageTypeId;
@ -470,6 +471,8 @@ class PipelineWorker : public AsyncWorker {
bool shouldSharpen = baton->sharpenSigma != 0.0; bool shouldSharpen = baton->sharpenSigma != 0.0;
bool shouldThreshold = baton->threshold != 0; bool shouldThreshold = baton->threshold != 0;
bool shouldCutout = baton->overlayCutout; bool shouldCutout = baton->overlayCutout;
bool shouldBandbool = baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST &&
baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND;
bool shouldPremultiplyAlpha = HasAlpha(image) && bool shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldAffineTransform || shouldBlur || shouldConv || shouldSharpen || (hasOverlay && !shouldCutout)); (shouldAffineTransform || shouldBlur || shouldConv || shouldSharpen || (hasOverlay && !shouldCutout));
@ -774,6 +777,11 @@ class PipelineWorker : public AsyncWorker {
} }
} }
// Apply per-channel Bandbool bitwise operations after all other operations
if (shouldBandbool) {
image = Bandbool(image, baton->bandBoolOp);
}
// 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);
@ -1194,6 +1202,15 @@ NAN_METHOD(pipeline) {
baton->convKernel[i] = To<double>(Get(kdata, i).ToLocalChecked()).FromJust(); baton->convKernel[i] = To<double>(Get(kdata, i).ToLocalChecked()).FromJust();
} }
} }
// Bandbool operation
std::string opStr = attrAsStr(options, "bandBoolOp");
if(opStr == "and" ) {
baton->bandBoolOp = VIPS_OPERATION_BOOLEAN_AND;
} else if(opStr == "or") {
baton->bandBoolOp = VIPS_OPERATION_BOOLEAN_OR;
} else if(opStr == "eor") {
baton->bandBoolOp = VIPS_OPERATION_BOOLEAN_EOR;
}
// Function to notify of queue length changes // Function to notify of queue length changes
Callback *queueListener = new Callback( Callback *queueListener = new Callback(

View File

@ -92,6 +92,7 @@ struct PipelineBaton {
int convKernelHeight; int convKernelHeight;
double convKernelScale; double convKernelScale;
double convKernelOffset; double convKernelOffset;
VipsOperationBoolean bandBoolOp;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
@ -151,6 +152,7 @@ struct PipelineBaton {
convKernelHeight(0), convKernelHeight(0),
convKernelScale(0.0), convKernelScale(0.0),
convKernelOffset(0.0), convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

BIN
test/fixtures/bandbool.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -79,6 +79,7 @@ module.exports = {
inputPngOverlayLayer2LowAlpha: getPath('alpha-layer-2-ink-low-alpha.png'), inputPngOverlayLayer2LowAlpha: getPath('alpha-layer-2-ink-low-alpha.png'),
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'), inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'), inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),
inputPngBooleanNoAlpha: getPath('bandbool.png'),
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

51
test/unit/bandbool.js Normal file
View File

@ -0,0 +1,51 @@
'use strict';
var assert = require('assert');
var fixtures = require('../fixtures');
var sharp = require('../../index');
describe('Bandbool per-channel boolean operations', function() {
it('\'and\' Operation', function(done) {
sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool('and')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height);
assert.strictEqual(1, info.channels);
fixtures.assertSimilar(fixtures.expected('bandbool_and_result.png'), data, done);
});
});
it('\'or\' Operation', function(done) {
sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool(sharp.bool.or)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height);
assert.strictEqual(1, info.channels);
fixtures.assertSimilar(fixtures.expected('bandbool_or_result.png'), data, done);
});
});
it('\'eor\' Operation', function(done) {
sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool('eor')
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(200, info.width);
assert.strictEqual(200, info.height);
assert.strictEqual(1, info.channels);
fixtures.assertSimilar(fixtures.expected('bandbool_eor_result.png'), data, done);
});
});
it('Invalid operation', function() {
assert.throws(function() {
sharp(fixtures.inputPngBooleanNoAlpha)
.bandbool('fail');
});
});
});