Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Erode / dilate preliminary implementation #4243

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/api-operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,30 @@ const gaussianBlurred = await sharp(input)
.toBuffer();
```

## dilate

Dilate the image.

**Throws**:

- <code>Error</code> Invalid parameters

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [width] | <code>number</code> | <code>1</code> | a value between 1 and XX representing the mask size |

## erode

Erodes the image.

**Throws**:

- <code>Error</code> Invalid parameters

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [width] | <code>number</code> | <code>1</code> | a value between 1 and XX representing the mask size |


## flatten
> flatten([options]) ⇒ <code>Sharp</code>
Expand Down
2 changes: 2 additions & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ const Sharp = function (input, options) {
trimBackground: [],
trimThreshold: -1,
trimLineArt: false,
dilateWidth: 0,
erodeWidth: 0,
gamma: 0,
gammaOut: 0,
greyscale: false,
Expand Down
40 changes: 40 additions & 0 deletions lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,44 @@ function blur (options) {
return this;
}

/**
* Dilate the image.
* @param {Number} [width] dilate width.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function dilate (width) {
if (!is.defined(width)) {
// No arguments: default to 1px dilation (3x3 mask)
this.options.dilateWidth = 1;
} else if (is.integer(width) && width > 0) {
// Numeric argument: specific width
this.options.dilateWidth = width;
} else {
throw is.invalidParameterError('dilate', 'positive integer', dilate);
}
return this;
}

/**
* Erode the image.
* @param {Number} [width] erode width.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function erode (width) {
if (!is.defined(width)) {
// No arguments: default to 1px erosion (3x3 mask)
this.options.erodeWidth = 1;
} else if (is.integer(width) && width > 0) {
// Numeric argument: specific width
this.options.erodeWidth = width;
} else {
throw is.invalidParameterError('erode', 'positive integer', erode);
}
return this;
}

/**
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
*
Expand Down Expand Up @@ -940,6 +978,8 @@ module.exports = function (Sharp) {
flop,
affine,
sharpen,
erode,
dilate,
median,
blur,
flatten,
Expand Down
24 changes: 24 additions & 0 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -472,4 +472,28 @@ namespace sharp {
}
}

/*
* Dilate an image
*/
VImage Dilate(VImage image, int const width) {
const int maskWidth = 2*width + 1;
VImage mask = VImage::new_matrix(maskWidth, maskWidth);

return image.morph(
mask,
VIPS_OPERATION_MORPHOLOGY_DILATE).invert();
}

/*
* Erode an image
*/
VImage Erode(VImage image, int const width) {
const int maskWidth = 2*width + 1;
VImage mask = VImage::new_matrix(maskWidth, maskWidth);

return image.morph(
mask,
VIPS_OPERATION_MORPHOLOGY_ERODE).invert();
}

} // namespace sharp
9 changes: 9 additions & 0 deletions src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ namespace sharp {
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
VipsExtend extendWith, std::vector<double> background, int nPages, int *pageHeight);

/*
* Dilate an image
*/
VImage Dilate(VImage image, int const maskWidth);

/*
* Erode an image
*/
VImage Erode(VImage image, int const maskWidth);
} // namespace sharp

#endif // SRC_OPERATIONS_H_
12 changes: 12 additions & 0 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,16 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
}

// Dilate - must happen before blurring, due to the utility of dilating after thresholding
if (baton->dilateWidth != 0) {
image = sharp::Dilate(image, baton->dilateWidth);
}

// Erode - must happen before blurring, due to the utility of eroding after thresholding
if (baton->erodeWidth != 0) {
image = sharp::Erode(image, baton->erodeWidth);
}

// Blur
if (shouldBlur) {
image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
Expand Down Expand Up @@ -1564,6 +1574,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth");
baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth");
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
Expand Down
4 changes: 4 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ struct PipelineBaton {
int trimOffsetTop;
std::vector<double> linearA;
std::vector<double> linearB;
int dilateWidth;
int erodeWidth;
double gamma;
double gammaOut;
bool greyscale;
Expand Down Expand Up @@ -273,6 +275,8 @@ struct PipelineBaton {
trimOffsetTop(0),
linearA{},
linearB{},
dilateWidth(0),
erodeWidth(0),
gamma(0.0),
greyscale(false),
normalise(false),
Expand Down
Binary file added test/fixtures/dot-and-lines.png
Loading
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/dilate-1.png
Loading
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/erode-1.png
Loading
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 @@ -126,6 +126,8 @@ module.exports = {

inputJPGBig: getPath('flowers.jpeg'),

inputPngDotAndLines: getPath('dot-and-lines.png'),

inputPngStripesV: getPath('stripesV.png'),
inputPngStripesH: getPath('stripesH.png'),

Expand Down
38 changes: 38 additions & 0 deletions test/unit/dilate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const assert = require('assert');

const sharp = require('../../');
const fixtures = require('../fixtures');

describe('Dilate', function () {
it('dilate 1 png', function (done) {
sharp(fixtures.inputPngDotAndLines)
.dilate(1)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('dilate-1.png'), data, done);
});
});

it('dilate 1 png - default width', function (done) {
sharp(fixtures.inputPngDotAndLines)
.dilate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('dilate-1.png'), data, done);
});
});

it('invalid dilation width', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).dilate(-1);
});
});
});
38 changes: 38 additions & 0 deletions test/unit/erode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict';

const assert = require('assert');

const sharp = require('../../');
const fixtures = require('../fixtures');

describe('Erode', function () {
it('erode 1 png', function (done) {
sharp(fixtures.inputPngDotAndLines)
.erode(1)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('erode-1.png'), data, done);
});
});

it('erode 1 png - default width', function (done) {
sharp(fixtures.inputPngDotAndLines)
.erode()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('erode-1.png'), data, done);
});
});

it('invalid erosion width', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).erode(-1);
});
});
});