mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add bandbool feature for channel-wise boolean operations (#496)
This commit is contained in:
parent
a982cfdb20
commit
65b7f7d7d5
18
docs/api.md
18
docs/api.md
@ -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
|
||||
|
||||
#### toFile(path, [callback])
|
||||
|
23
index.js
23
index.js
@ -89,6 +89,7 @@ var Sharp = function(input, options) {
|
||||
gamma: 0,
|
||||
greyscale: false,
|
||||
normalize: 0,
|
||||
bandBoolOp: null,
|
||||
// overlay
|
||||
overlayFileIn: '',
|
||||
overlayBufferIn: null,
|
||||
@ -588,6 +589,22 @@ Sharp.prototype.normalize = function(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
|
||||
*/
|
||||
@ -785,6 +802,12 @@ module.exports.interpolator = {
|
||||
vsqbs: 'vsqbs',
|
||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||
};
|
||||
// Boolean operations for bandbool
|
||||
module.exports.bool = {
|
||||
and: 'and',
|
||||
or: 'or',
|
||||
eor: 'eor'
|
||||
};
|
||||
|
||||
/*
|
||||
Resize image to width x height pixels
|
||||
|
@ -392,4 +392,11 @@ namespace sharp {
|
||||
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
|
||||
|
@ -82,6 +82,11 @@ namespace sharp {
|
||||
*/
|
||||
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
|
||||
|
||||
#endif // SRC_OPERATIONS_H_
|
||||
|
@ -55,6 +55,7 @@ using sharp::Sharpen;
|
||||
using sharp::EntropyCrop;
|
||||
using sharp::TileCache;
|
||||
using sharp::Threshold;
|
||||
using sharp::Bandbool;
|
||||
|
||||
using sharp::ImageType;
|
||||
using sharp::ImageTypeId;
|
||||
@ -470,6 +471,8 @@ class PipelineWorker : public AsyncWorker {
|
||||
bool shouldSharpen = baton->sharpenSigma != 0.0;
|
||||
bool shouldThreshold = baton->threshold != 0;
|
||||
bool shouldCutout = baton->overlayCutout;
|
||||
bool shouldBandbool = baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST &&
|
||||
baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND;
|
||||
bool shouldPremultiplyAlpha = HasAlpha(image) &&
|
||||
(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
|
||||
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
||||
SetExifOrientation(image, baton->withMetadataOrientation);
|
||||
@ -1194,6 +1202,15 @@ NAN_METHOD(pipeline) {
|
||||
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
|
||||
Callback *queueListener = new Callback(
|
||||
|
@ -92,6 +92,7 @@ struct PipelineBaton {
|
||||
int convKernelHeight;
|
||||
double convKernelScale;
|
||||
double convKernelOffset;
|
||||
VipsOperationBoolean bandBoolOp;
|
||||
int tileSize;
|
||||
int tileOverlap;
|
||||
VipsForeignDzContainer tileContainer;
|
||||
@ -151,6 +152,7 @@ struct PipelineBaton {
|
||||
convKernelHeight(0),
|
||||
convKernelScale(0.0),
|
||||
convKernelOffset(0.0),
|
||||
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||
tileSize(256),
|
||||
tileOverlap(0),
|
||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||
|
BIN
test/fixtures/bandbool.png
vendored
Normal file
BIN
test/fixtures/bandbool.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
test/fixtures/expected/bandbool_and_result.png
vendored
Normal file
BIN
test/fixtures/expected/bandbool_and_result.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
test/fixtures/expected/bandbool_eor_result.png
vendored
Normal file
BIN
test/fixtures/expected/bandbool_eor_result.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
test/fixtures/expected/bandbool_or_result.png
vendored
Normal file
BIN
test/fixtures/expected/bandbool_or_result.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
1
test/fixtures/index.js
vendored
1
test/fixtures/index.js
vendored
@ -79,6 +79,7 @@ module.exports = {
|
||||
inputPngOverlayLayer2LowAlpha: getPath('alpha-layer-2-ink-low-alpha.png'),
|
||||
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-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
|
||||
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
|
||||
|
51
test/unit/bandbool.js
Normal file
51
test/unit/bandbool.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user