From 512dffaad0a8a666b9ec2a7d3eb3260b5cfa0e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20Pin=C3=A7on?= Date: Tue, 22 Oct 2024 18:11:16 +0200 Subject: [PATCH] WIP --- docs/api-operation.md | 24 +++++++++++++++++ lib/constructor.js | 2 ++ lib/operation.js | 40 ++++++++++++++++++++++++++++ src/operations.cc | 24 +++++++++++++++++ src/operations.h | 9 +++++++ src/pipeline.cc | 12 +++++++++ src/pipeline.h | 4 +++ test/fixtures/dot-and-lines.png | Bin 0 -> 463 bytes test/fixtures/expected/dilate-1.png | Bin 0 -> 540 bytes test/fixtures/expected/erode-1.png | Bin 0 -> 530 bytes test/fixtures/index.js | 2 ++ test/unit/dilate.js | 38 ++++++++++++++++++++++++++ test/unit/erode.js | 38 ++++++++++++++++++++++++++ 13 files changed, 193 insertions(+) create mode 100644 test/fixtures/dot-and-lines.png create mode 100644 test/fixtures/expected/dilate-1.png create mode 100644 test/fixtures/expected/erode-1.png create mode 100644 test/unit/dilate.js create mode 100644 test/unit/erode.js diff --git a/docs/api-operation.md b/docs/api-operation.md index d38e209ff..05851895d 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -265,6 +265,30 @@ const gaussianBlurred = await sharp(input) .toBuffer(); ``` +## dilate + +Dilate the image. + +**Throws**: + +- Error Invalid parameters + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [width] | number | 1 | a value between 1 and XX representing the mask size | + +## erode + +Erodes the image. + +**Throws**: + +- Error Invalid parameters + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [width] | number | 1 | a value between 1 and XX representing the mask size | + ## flatten > flatten([options]) ⇒ Sharp diff --git a/lib/constructor.js b/lib/constructor.js index 0ff85c667..b66bff2e6 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -240,6 +240,8 @@ const Sharp = function (input, options) { trimBackground: [], trimThreshold: -1, trimLineArt: false, + dilateWidth: 0, + erodeWidth: 0, gamma: 0, gammaOut: 0, greyscale: false, diff --git a/lib/operation.js b/lib/operation.js index 538286491..936ab289f 100644 --- a/lib/operation.js +++ b/lib/operation.js @@ -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. * @@ -940,6 +978,8 @@ module.exports = function (Sharp) { flop, affine, sharpen, + erode, + dilate, median, blur, flatten, diff --git a/src/operations.cc b/src/operations.cc index 775f6134e..071ee6592 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -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 diff --git a/src/operations.h b/src/operations.h index b2881d65d..22ff46fcf 100644 --- a/src/operations.h +++ b/src/operations.h @@ -120,6 +120,15 @@ namespace sharp { VImage EmbedMultiPage(VImage image, int left, int top, int width, int height, VipsExtend extendWith, std::vector 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_ diff --git a/src/pipeline.cc b/src/pipeline.cc index ac5ead01e..cfe72937a 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -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); @@ -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"); diff --git a/src/pipeline.h b/src/pipeline.h index 777a4c543..f50ea2165 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -100,6 +100,8 @@ struct PipelineBaton { int trimOffsetTop; std::vector linearA; std::vector linearB; + int dilateWidth; + int erodeWidth; double gamma; double gammaOut; bool greyscale; @@ -273,6 +275,8 @@ struct PipelineBaton { trimOffsetTop(0), linearA{}, linearB{}, + dilateWidth(0), + erodeWidth(0), gamma(0.0), greyscale(false), normalise(false), diff --git a/test/fixtures/dot-and-lines.png b/test/fixtures/dot-and-lines.png new file mode 100644 index 0000000000000000000000000000000000000000..5c50d2456fbdd9083a058a16392a2213131d91db GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^DImFdh=n2n7~lk=F;=}e%IWQl7;iF1B#Zfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pEv^p zqo=2fV@SoVx3>)W4jJ&U1|)s`7p^BQ+{AY2R_45Lu|FwmzRlkD{$8G;<9mks5LB>O@NYISf zaBk(=E88oVdYf*Z@TcQh_Mb3zJ>k{bOV>Dv1$)?LvD`6i4G~;F!*x|txR1)zccR6f zPpc|5bURJUWP;jGrvNSDx+bap?AO-RO$olerEX~jwS0-25)MXjWWVY1G4DI-{stQz3S=df3Fq)GS$gF_`NLdVcrWRThVLL7XM2>%whEg ndqgLK3mhN^gEl0@M)+Tvkhy;EgS``gAgTe~DWM4f8lAZa literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/dilate-1.png b/test/fixtures/expected/dilate-1.png new file mode 100644 index 0000000000000000000000000000000000000000..947eb4b0ce1037e4e8f51e7bd5fc052099e95e0a GIT binary patch literal 540 zcmeAS@N?(olHy`uVBq!ia0vp^DImN7?1y#Al(oAg3_cUHS!&PtnQ(|W(kPu<9n%y^8chgE{xKrBJ$KtuyvAUk+XVee0) zYdg-=$YjT4N@dT5ORcuQ*1Go@)BmP;-KXi!$B;BZP5GVF%Y8<7<1y1^8?Q-fOTV5J zS3Gk+FC%uVzRuow?DF~ThVACPcjI}sSZzx#shfUj>(4V+m-nq%dz0O7-(>li)0e-O zapbT2crp36O8WJGcb48wI{lh+r}yro)2<6UrB|gKyRMCu5;C`9{dV0MAB9yPRIq4z zS-0+b^(T67#iHZ=I*yD#{C{8yK?;p00i_>zopr08#ATT>t<8 literal 0 HcmV?d00001 diff --git a/test/fixtures/expected/erode-1.png b/test/fixtures/expected/erode-1.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6d627442e0d2c611cb44f5ef5042b39cbd17be GIT binary patch literal 530 zcmeAS@N?(olHy`uVBq!ia0vp^DImgssEq`bJW%k$@K+m-A4_K(;0leX4b|HIG9zEbx3d&>Xq>C4~m3CG<& z?%wa~!T0WW@$_d$%)YBX(L39j`_7+Z?#yX3@5T#S8S5JF-mg@W7L`_3@A~4*mNReu zbsg!oWjg)aS;+hwht7L`&pS7iH|()jK~-~MX6DSBe?=D>UpCtOTe}Fw@D91z6J+ju zU;cit`krn5`yW18{VDkOr|NY@uNnT`71;56`GNXto_tZk?rgta-$`XRLJ2>N00jrl bu6OKTOXeRcXcnpi#wUZPtDnm{r-UW|emUR? literal 0 HcmV?d00001 diff --git a/test/fixtures/index.js b/test/fixtures/index.js index e4b8e266e..0546a3be2 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -126,6 +126,8 @@ module.exports = { inputJPGBig: getPath('flowers.jpeg'), + inputPngDotAndLines: getPath('dot-and-lines.png'), + inputPngStripesV: getPath('stripesV.png'), inputPngStripesH: getPath('stripesH.png'), diff --git a/test/unit/dilate.js b/test/unit/dilate.js new file mode 100644 index 000000000..94588a285 --- /dev/null +++ b/test/unit/dilate.js @@ -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); + }); + }); +}); diff --git a/test/unit/erode.js b/test/unit/erode.js new file mode 100644 index 000000000..4d2da81f0 --- /dev/null +++ b/test/unit/erode.js @@ -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); + }); + }); +});