Skip to content

Commit

Permalink
Merge c4f42f0 into 83d8847
Browse files Browse the repository at this point in the history
  • Loading branch information
mhirsch committed Jul 11, 2016
2 parents 83d8847 + c4f42f0 commit e0ba4d1
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 22 deletions.
15 changes: 15 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,21 @@ sharp('input.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`.

#### boolean(image, operation)

Perform a bitwise boolean operation with `image`.

`image` is one of the following.

* Buffer contianing PNG, WebP, GIF or SVG image data, or
* String containing the path to an image file

This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation`, between the corresponding pixels of the input images. The boolean operation can be one of the following:

* `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 `^`

### Output

Expand Down
27 changes: 27 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ var Sharp = function(input, options) {
greyscale: false,
normalize: 0,
bandBoolOp: null,
booleanOp: null,
booleanBufferIn: null,
booleanFileIn: '',
// overlay
overlayFileIn: '',
overlayBufferIn: null,
Expand Down Expand Up @@ -365,6 +368,30 @@ Sharp.prototype.negate = function(negate) {
return this;
};

/*
Bitwise boolean operations between images
*/
Sharp.prototype.boolean = function(operand, operator) {
if (isString(operand)) {
this.options.booleanFileIn = operand;
} else if (isBuffer(operand)) {
this.options.booleanBufferIn = operand;
} else {
throw new Error('Unsupported boolean operand ' + typeof operand);
}
if (!isString(operator)) {
throw new Error('Invalid boolean operation ' + operator);
}
operator = operator.toLowerCase();
var ops = ['and', 'or', 'eor'];
if(ops.indexOf(operator) == -1) {
throw new Error('Invalid boolean operation ' + operator);
}
this.options.booleanOp = operator;

return this;
};

/*
Overlay with another image, using an optional gravity
*/
Expand Down
9 changes: 9 additions & 0 deletions src/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,13 @@ namespace sharp {
}
}

/*
Get VIPS Boolean operatoin type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string opStr) {
return static_cast<VipsOperationBoolean>(
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data())
);
}

} // namespace sharp
5 changes: 5 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ namespace sharp {
*/
int MaximumImageAlpha(VipsInterpretation interpretation);

/*
Get VIPS Boolean operatoin type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string opStr);


} // namespace sharp

Expand Down
7 changes: 7 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@ namespace sharp {
return image.bandbool(boolean);
}

/*
Perform bitwise boolean operation between images
*/
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
return image.boolean(imageR, boolean);
}

VImage Trim(VImage image, int const tolerance) {
using sharp::MaximumImageAlpha;
// An equivalent of ImageMagick's -trim in C++ ... automatically remove
Expand Down
5 changes: 5 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ namespace sharp {
*/
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);

/*
Perform bitwise boolean operation between images
*/
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean);

/*
Trim an image
*/
Expand Down
99 changes: 77 additions & 22 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#include <node.h>
#include <node_buffer.h>

#include <sstream>
#include <iomanip>

#include "nan.h"

#include "common.h"
Expand Down Expand Up @@ -56,6 +59,7 @@ using sharp::EntropyCrop;
using sharp::TileCache;
using sharp::Threshold;
using sharp::Bandbool;
using sharp::Boolean;
using sharp::Trim;

using sharp::ImageType;
Expand All @@ -78,19 +82,22 @@ using sharp::FreeCallback;
using sharp::CalculateCrop;
using sharp::counterProcess;
using sharp::counterQueue;
using sharp::GetBooleanOperation;

typedef struct BufferContainer_t {
std::string name;
Local<Object> nodeBuf;
} BufferContainer;

class PipelineWorker : public AsyncWorker {
public:
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener,
const Local<Object> &bufferIn, const Local<Object> &overlayBufferIn) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
if (baton->overlayBufferInLength > 0) {
SaveToPersistent("overlayBufferIn", overlayBufferIn);
}
const std::vector<BufferContainer> saveBuffers) :
AsyncWorker(callback), baton(baton), queueListener(queueListener), saveBuffers(saveBuffers) {
for (const BufferContainer buf : saveBuffers) {
SaveToPersistent(buf.name.c_str(), buf.nodeBuf);
}
}
~PipelineWorker() {}

/*
Expand Down Expand Up @@ -784,6 +791,44 @@ class PipelineWorker : public AsyncWorker {
}
}

// Apply bitwise boolean operation between images
if (baton->booleanOp != VIPS_OPERATION_BOOLEAN_LAST &&
(baton->booleanBufferInLength > 0 || !baton->booleanFileIn.empty())) {
VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN;
if (baton->booleanBufferInLength > 0) {
// Buffer input for boolean operation
booleanImageType = DetermineImageType(baton->booleanBufferIn, baton->booleanBufferInLength);
if (booleanImageType != ImageType::UNKNOWN) {
try {
booleanImage = VImage::new_from_buffer(baton->booleanBufferIn, baton->booleanBufferInLength,
nullptr, VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Boolean operation buffer has corrupt header");
booleanImageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Boolean operation buffer contains unsupported image format");
}
} else if (!baton->booleanFileIn.empty()) {
// File input for boolean operation
booleanImageType = DetermineImageType(baton->booleanFileIn.data());
if (booleanImageType != ImageType::UNKNOWN) {
try {
booleanImage = VImage::new_from_file(baton->booleanFileIn.data(),
VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Boolean operation file has corrupt header");
}
}
}
if (booleanImageType == ImageType::UNKNOWN) {
return Error();
}
// Apply the boolean operation
image = Boolean(image, booleanImage, baton->booleanOp);
}

// Apply per-channel Bandbool bitwise operations after all other operations
if (shouldBandbool) {
image = Bandbool(image, baton->bandBoolOp);
Expand Down Expand Up @@ -1007,11 +1052,8 @@ class PipelineWorker : public AsyncWorker {
}

// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
if (baton->overlayBufferInLength > 0) {
GetFromPersistent("overlayBufferIn");
for (const BufferContainer buf : saveBuffers) {
GetFromPersistent(buf.name.c_str());
}
delete baton;

Expand All @@ -1028,6 +1070,7 @@ class PipelineWorker : public AsyncWorker {
private:
PipelineBaton *baton;
Callback *queueListener;
std::vector<BufferContainer> saveBuffers;

/*
Calculate the angle of rotation and need-to-flip for the output image.
Expand Down Expand Up @@ -1155,6 +1198,14 @@ NAN_METHOD(pipeline) {
baton->overlayYOffset = attrAs<int32_t>(options, "overlayYOffset");
baton->overlayTile = attrAs<bool>(options, "overlayTile");
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
// Boolean options
baton->booleanFileIn = attrAsStr(options, "booleanFileIn");
Local<Object> booleanBufferIn;
if (node::Buffer::HasInstance(Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked())) {
booleanBufferIn = Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->booleanBufferInLength = node::Buffer::Length(booleanBufferIn);
baton->booleanBufferIn = node::Buffer::Data(booleanBufferIn);
}
// Resize options
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
baton->crop = attrAs<int32_t>(options, "crop");
Expand Down Expand Up @@ -1232,14 +1283,10 @@ NAN_METHOD(pipeline) {
}
}
// 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;
}
baton->bandBoolOp = GetBooleanOperation(attrAsStr(options, "bandBoolOp"));

// Boolean operation
baton->booleanOp = GetBooleanOperation(attrAsStr(options, "booleanOp"));

// Function to notify of queue length changes
Callback *queueListener = new Callback(
Expand All @@ -1248,7 +1295,15 @@ NAN_METHOD(pipeline) {

// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn, overlayBufferIn));

std::vector<BufferContainer> saveBuffers;
if (baton->bufferInLength)
saveBuffers.push_back({"bufferIn", bufferIn});
if (baton->overlayBufferInLength)
saveBuffers.push_back({"overlayBufferIn", overlayBufferIn});
if (baton->booleanBufferInLength)
saveBuffers.push_back({"booleanBufferIn", booleanBufferIn});
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, saveBuffers));

// Increment queued task counter
g_atomic_int_inc(&counterQueue);
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct PipelineBaton {
int overlayYOffset;
bool overlayTile;
bool overlayCutout;
std::string booleanFileIn;
char *booleanBufferIn;
size_t booleanBufferInLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
Expand Down Expand Up @@ -94,6 +97,7 @@ struct PipelineBaton {
double convKernelScale;
double convKernelOffset;
VipsOperationBoolean bandBoolOp;
VipsOperationBoolean booleanOp;
int extractChannel;
int tileSize;
int tileOverlap;
Expand All @@ -116,6 +120,7 @@ struct PipelineBaton {
overlayYOffset(-1),
overlayTile(false),
overlayCutout(false),
booleanBufferInLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
Expand Down Expand Up @@ -156,6 +161,7 @@ struct PipelineBaton {
convKernelScale(0.0),
convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
tileSize(256),
tileOverlap(0),
Expand Down
Binary file added test/fixtures/booleanTest.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/boolean_and_result.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/boolean_eor_result.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/boolean_or_result.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ module.exports = {
inputPngStripesV: getPath('stripesV.png'),
inputPngStripesH: getPath('stripesH.png'),

inputJpgBooleanTest: getPath('booleanTest.jpg'),

inputV: getPath('vfile.v'),

outputJpg: getPath('output.jpg'),
Expand Down

0 comments on commit e0ba4d1

Please sign in to comment.