From 65b7f7d7d5f3662bfe50df4f1fdc3d0b6f4a9763 Mon Sep 17 00:00:00 2001 From: Matt Hirsch Date: Thu, 7 Jul 2016 16:03:49 -0400 Subject: [PATCH] Add bandbool feature for channel-wise boolean operations (#496) --- docs/api.md | 18 +++++++ index.js | 23 ++++++++ src/operations.cc | 7 +++ src/operations.h | 5 ++ src/pipeline.cc | 17 ++++++ src/pipeline.h | 2 + test/fixtures/bandbool.png | Bin 0 -> 10634 bytes .../fixtures/expected/bandbool_and_result.png | Bin 0 -> 4866 bytes .../fixtures/expected/bandbool_eor_result.png | Bin 0 -> 7775 bytes test/fixtures/expected/bandbool_or_result.png | Bin 0 -> 4789 bytes test/fixtures/index.js | 1 + test/unit/bandbool.js | 51 ++++++++++++++++++ 12 files changed, 124 insertions(+) create mode 100644 test/fixtures/bandbool.png create mode 100644 test/fixtures/expected/bandbool_and_result.png create mode 100644 test/fixtures/expected/bandbool_eor_result.png create mode 100644 test/fixtures/expected/bandbool_or_result.png create mode 100644 test/unit/bandbool.js diff --git a/docs/api.md b/docs/api.md index 139a4e38..564df25a 100644 --- a/docs/api.md +++ b/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]) diff --git a/index.js b/index.js index 93a0a37c..2bde9ce4 100644 --- a/index.js +++ b/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 diff --git a/src/operations.cc b/src/operations.cc index 106c5a04..e85f5730 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -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 diff --git a/src/operations.h b/src/operations.h index dee7dbe4..8d1f9122 100644 --- a/src/operations.h +++ b/src/operations.h @@ -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_ diff --git a/src/pipeline.cc b/src/pipeline.cc index d0b1b77c..f643190c 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -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(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( diff --git a/src/pipeline.h b/src/pipeline.h index 96a1705c..a1807837 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -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), diff --git a/test/fixtures/bandbool.png b/test/fixtures/bandbool.png new file mode 100644 index 0000000000000000000000000000000000000000..39023cdf7df20199a64d10b9463cabdd56a8c254 GIT binary patch literal 10634 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 F{|hApR=EHG literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/bandbool_and_result.png b/test/fixtures/expected/bandbool_and_result.png new file mode 100644 index 0000000000000000000000000000000000000000..722716be866841203933e24a323573fbfc39c2d1 GIT binary patch literal 4866 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 bV9%JeXls1m=)lSUd2Si$-G$ZYxJ3OI|8I-t literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/bandbool_eor_result.png b/test/fixtures/expected/bandbool_eor_result.png new file mode 100644 index 0000000000000000000000000000000000000000..35b2920ad03ed5834fc2d5c6f84db08a76024796 GIT binary patch literal 7775 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 Y`wFewy>k=!@8?)SM)iG_lv&9C09%u}xc~qF literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/bandbool_or_result.png b/test/fixtures/expected/bandbool_or_result.png new file mode 100644 index 0000000000000000000000000000000000000000..3973cc1a0919212b9b0adb130b9ada5693b18e66 GIT binary patch literal 4789 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 aR|?hCN-hT;CXfF6M?HC@uUVpDi~S!x8FzXB literal 0 HcmV?d00001 diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 231c8b3b..3555a93c 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -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 diff --git a/test/unit/bandbool.js b/test/unit/bandbool.js new file mode 100644 index 00000000..725e4542 --- /dev/null +++ b/test/unit/bandbool.js @@ -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'); + }); + }); +});