Skip to content

Commit ea599ad

Browse files
committed
Allow sharpen options to be provided as an Object
Also exposes x1, y2, y3 parameters #2561 #2935
1 parent 1de49f3 commit ea599ad

File tree

10 files changed

+214
-46
lines changed

10 files changed

+214
-46
lines changed

docs/api-operation.md

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,41 @@ When used without parameters, performs a fast, mild sharpen of the output image.
129129
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
130130
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
131131

132+
See [libvips sharpen][8] operation.
133+
132134
### Parameters
133135

134-
* `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
135-
* `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
136-
* `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
136+
* `options` **[Object][2]?** if present, is an Object with optional attributes.
137137

138-
<!---->
138+
* `options.sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
139+
* `options.m1` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
140+
* `options.m2` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
141+
* `options.x1` **[number][1]** threshold between "flat" and "jagged" (optional, default `2.0`)
142+
* `options.y2` **[number][1]** maximum amount of brightening. (optional, default `10.0`)
143+
* `options.y3` **[number][1]** maximum amount of darkening. (optional, default `20.0`)
144+
145+
### Examples
146+
147+
```javascript
148+
const data = await sharp(input).sharpen().toBuffer();
149+
```
150+
151+
```javascript
152+
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
153+
```
154+
155+
```javascript
156+
const data = await sharp(input)
157+
.sharpen({
158+
sigma: 2,
159+
m1: 0
160+
m2: 3,
161+
x1: 3,
162+
y2: 15,
163+
y3: 15,
164+
})
165+
.toBuffer();
166+
```
139167

140168
* Throws **[Error][5]** Invalid parameters
141169

@@ -190,7 +218,7 @@ Returns **Sharp**
190218

191219
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
192220

193-
See also [removeAlpha][8].
221+
See also [removeAlpha][9].
194222

195223
### Parameters
196224

@@ -264,7 +292,7 @@ Returns **Sharp**
264292
## clahe
265293

266294
Perform contrast limiting adaptive histogram equalization
267-
[CLAHE][9].
295+
[CLAHE][10].
268296

269297
This will, in general, enhance the clarity of the image by bringing out darker details.
270298

@@ -349,7 +377,7 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
349377

350378
### Parameters
351379

352-
* `operand` **([Buffer][10] | [string][3])** Buffer containing image data or string containing the path to an image file.
380+
* `operand` **([Buffer][11] | [string][3])** Buffer containing image data or string containing the path to an image file.
353381
* `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
354382
* `options` **[Object][2]?**
355383

@@ -474,8 +502,10 @@ Returns **Sharp**
474502

475503
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
476504

477-
[8]: /api-channel#removealpha
505+
[8]: https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen
506+
507+
[9]: /api-channel#removealpha
478508

479-
[9]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
509+
[10]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
480510

481-
[10]: https://nodejs.org/api/buffer.html
511+
[11]: https://nodejs.org/api/buffer.html

docs/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ Requires libvips v8.12.2
66

77
### v0.30.3 - TBD
88

9+
* Allow `sharpen` options to be provided more consistently as an Object.
10+
[#2561](https://github.com/lovell/sharp/issues/2561)
11+
12+
* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation.
13+
[#2935](https://github.com/lovell/sharp/issues/2935)
14+
915
* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2).
1016
[#3118](https://github.com/lovell/sharp/issues/3118)
1117

docs/search-index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

lib/constructor.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,11 @@ const Sharp = function (input, options) {
186186
medianSize: 0,
187187
blurSigma: 0,
188188
sharpenSigma: 0,
189-
sharpenFlat: 1,
190-
sharpenJagged: 2,
189+
sharpenM1: 1,
190+
sharpenM2: 2,
191+
sharpenX1: 2,
192+
sharpenY2: 10,
193+
sharpenY3: 20,
191194
threshold: 0,
192195
thresholdGrayscale: true,
193196
trimThreshold: 0,

lib/operation.js

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -185,40 +185,105 @@ function affine (matrix, options) {
185185
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
186186
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
187187
*
188-
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
189-
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
190-
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
188+
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
189+
*
190+
* @example
191+
* const data = await sharp(input).sharpen().toBuffer();
192+
*
193+
* @example
194+
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
195+
*
196+
* @example
197+
* const data = await sharp(input)
198+
* .sharpen({
199+
* sigma: 2,
200+
* m1: 0
201+
* m2: 3,
202+
* x1: 3,
203+
* y2: 15,
204+
* y3: 15,
205+
* })
206+
* .toBuffer();
207+
*
208+
* @param {Object} [options] - if present, is an Object with optional attributes.
209+
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
210+
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
211+
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
212+
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
213+
* @param {number} [options.y2=10.0] - maximum amount of brightening.
214+
* @param {number} [options.y3=20.0] - maximum amount of darkening.
191215
* @returns {Sharp}
192216
* @throws {Error} Invalid parameters
193217
*/
194-
function sharpen (sigma, flat, jagged) {
195-
if (!is.defined(sigma)) {
218+
function sharpen (options) {
219+
if (!is.defined(options)) {
196220
// No arguments: default to mild sharpen
197221
this.options.sharpenSigma = -1;
198-
} else if (is.bool(sigma)) {
199-
// Boolean argument: apply mild sharpen?
200-
this.options.sharpenSigma = sigma ? -1 : 0;
201-
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
202-
// Numeric argument: specific sigma
203-
this.options.sharpenSigma = sigma;
204-
// Control over flat areas
205-
if (is.defined(flat)) {
206-
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
207-
this.options.sharpenFlat = flat;
222+
} else if (is.bool(options)) {
223+
// Deprecated boolean argument: apply mild sharpen?
224+
this.options.sharpenSigma = options ? -1 : 0;
225+
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
226+
// Deprecated numeric argument: specific sigma
227+
this.options.sharpenSigma = options;
228+
// Deprecated control over flat areas
229+
if (is.defined(arguments[1])) {
230+
if (is.number(arguments[1]) && is.inRange(arguments[1], 0, 10000)) {
231+
this.options.sharpenM1 = arguments[1];
232+
} else {
233+
throw is.invalidParameterError('flat', 'number between 0 and 10000', arguments[1]);
234+
}
235+
}
236+
// Deprecated control over jagged areas
237+
if (is.defined(arguments[2])) {
238+
if (is.number(arguments[2]) && is.inRange(arguments[2], 0, 10000)) {
239+
this.options.sharpenM2 = arguments[2];
240+
} else {
241+
throw is.invalidParameterError('jagged', 'number between 0 and 10000', arguments[2]);
242+
}
243+
}
244+
} else if (is.plainObject(options)) {
245+
if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
246+
this.options.sharpenSigma = options.sigma;
247+
} else {
248+
throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
249+
}
250+
if (is.defined(options.m1)) {
251+
if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
252+
this.options.sharpenM1 = options.m1;
253+
} else {
254+
throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
255+
}
256+
}
257+
if (is.defined(options.m2)) {
258+
if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
259+
this.options.sharpenM2 = options.m2;
260+
} else {
261+
throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
262+
}
263+
}
264+
if (is.defined(options.x1)) {
265+
if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
266+
this.options.sharpenX1 = options.x1;
267+
} else {
268+
throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
269+
}
270+
}
271+
if (is.defined(options.y2)) {
272+
if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
273+
this.options.sharpenY2 = options.y2;
208274
} else {
209-
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
275+
throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
210276
}
211277
}
212-
// Control over jagged areas
213-
if (is.defined(jagged)) {
214-
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
215-
this.options.sharpenJagged = jagged;
278+
if (is.defined(options.y3)) {
279+
if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
280+
this.options.sharpenY3 = options.y3;
216281
} else {
217-
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
282+
throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
218283
}
219284
}
220285
} else {
221-
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
286+
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
222287
}
223288
return this;
224289
}

src/operations.cc

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ namespace sharp {
209209
/*
210210
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
211211
*/
212-
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
212+
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
213+
double const x1, double const y2, double const y3) {
213214
if (sigma == -1.0) {
214215
// Fast, mild sharpen
215216
VImage sharpen = VImage::new_matrixv(3, 3,
@@ -224,8 +225,14 @@ namespace sharp {
224225
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
225226
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
226227
}
227-
return image.sharpen(
228-
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
228+
return image
229+
.sharpen(VImage::option()
230+
->set("sigma", sigma)
231+
->set("m1", m1)
232+
->set("m2", m2)
233+
->set("x1", x1)
234+
->set("y2", y2)
235+
->set("y3", y3))
229236
.colourspace(colourspaceBeforeSharpen);
230237
}
231238
}

src/operations.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ namespace sharp {
6464
/*
6565
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
6666
*/
67-
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
67+
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
68+
double const x1, double const y2, double const y3);
6869

6970
/*
7071
Threshold an image

src/pipeline.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ class PipelineWorker : public Napi::AsyncWorker {
577577

578578
// Sharpen
579579
if (shouldSharpen) {
580-
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
580+
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
581+
baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
581582
}
582583

583584
// Composite
@@ -1400,8 +1401,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
14001401
baton->lightness = sharp::AttrAsDouble(options, "lightness");
14011402
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
14021403
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
1403-
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
1404-
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
1404+
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
1405+
baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
1406+
baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
1407+
baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
1408+
baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
14051409
baton->threshold = sharp::AttrAsInt32(options, "threshold");
14061410
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
14071411
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");

src/pipeline.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ struct PipelineBaton {
9090
double lightness;
9191
int medianSize;
9292
double sharpenSigma;
93-
double sharpenFlat;
94-
double sharpenJagged;
93+
double sharpenM1;
94+
double sharpenM2;
95+
double sharpenX1;
96+
double sharpenY2;
97+
double sharpenY3;
9598
int threshold;
9699
bool thresholdGrayscale;
97100
double trimThreshold;
@@ -234,8 +237,11 @@ struct PipelineBaton {
234237
lightness(0),
235238
medianSize(0),
236239
sharpenSigma(0.0),
237-
sharpenFlat(1.0),
238-
sharpenJagged(2.0),
240+
sharpenM1(1.0),
241+
sharpenM2(2.0),
242+
sharpenX1(2.0),
243+
sharpenY2(10.0),
244+
sharpenY3(20.0),
239245
threshold(0),
240246
thresholdGrayscale(true),
241247
trimThreshold(0.0),

test/unit/sharpen.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,22 @@ describe('Sharpen', function () {
4545
});
4646
});
4747

48+
it('sigma=3.5, m1=2, m2=4', (done) => {
49+
sharp(fixtures.inputJpg)
50+
.resize(320, 240)
51+
.sharpen({ sigma: 3.5, m1: 2, m2: 4 })
52+
.toBuffer()
53+
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
54+
});
55+
56+
it('sigma=3.5, m1=2, m2=4, x1=2, y2=5, y3=25', (done) => {
57+
sharp(fixtures.inputJpg)
58+
.resize(320, 240)
59+
.sharpen({ sigma: 3.5, m1: 2, m2: 4, x1: 2, y2: 5, y3: 25 })
60+
.toBuffer()
61+
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
62+
});
63+
4864
if (!process.env.SHARP_TEST_WITHOUT_CACHE) {
4965
it('specific radius/levels with alpha channel', function (done) {
5066
sharp(fixtures.inputPngWithTransparency)
@@ -92,6 +108,36 @@ describe('Sharpen', function () {
92108
});
93109
});
94110

111+
it('invalid options.sigma', () => assert.throws(
112+
() => sharp().sharpen({ sigma: -1 }),
113+
/Expected number between 0\.01 and 10000 for options\.sigma but received -1 of type number/
114+
));
115+
116+
it('invalid options.m1', () => assert.throws(
117+
() => sharp().sharpen({ sigma: 1, m1: -1 }),
118+
/Expected number between 0 and 10000 for options\.m1 but received -1 of type number/
119+
));
120+
121+
it('invalid options.m2', () => assert.throws(
122+
() => sharp().sharpen({ sigma: 1, m2: -1 }),
123+
/Expected number between 0 and 10000 for options\.m2 but received -1 of type number/
124+
));
125+
126+
it('invalid options.x1', () => assert.throws(
127+
() => sharp().sharpen({ sigma: 1, x1: -1 }),
128+
/Expected number between 0 and 10000 for options\.x1 but received -1 of type number/
129+
));
130+
131+
it('invalid options.y2', () => assert.throws(
132+
() => sharp().sharpen({ sigma: 1, y2: -1 }),
133+
/Expected number between 0 and 10000 for options\.y2 but received -1 of type number/
134+
));
135+
136+
it('invalid options.y3', () => assert.throws(
137+
() => sharp().sharpen({ sigma: 1, y3: -1 }),
138+
/Expected number between 0 and 10000 for options\.y3 but received -1 of type number/
139+
));
140+
95141
it('sharpened image is larger than non-sharpened', function (done) {
96142
sharp(fixtures.inputJpg)
97143
.resize(320, 240)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy