Expose optional precision parameter of blur operation (#4168)

This commit is contained in:
Marcos Casagrande 2024-07-20 14:53:23 +02:00 committed by GitHub
parent 10c6f474d9
commit 67a4592756
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 103 additions and 15 deletions

View File

@ -231,7 +231,7 @@ const output = await sharp(input).median(5).toBuffer();
## blur ## blur
> blur([sigma]) ⇒ <code>Sharp</code> > blur([options]) ⇒ <code>Sharp</code>
Blur the image. Blur the image.
@ -245,9 +245,11 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
- <code>Error</code> Invalid parameters - <code>Error</code> Invalid parameters
| Param | Type | Description | | Param | Type | Default | Description |
| --- | --- | --- | | --- | --- | --- | --- |
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. | | [options] | <code>Object</code> \| <code>number</code> \| <code>Boolean</code> | | |
| [options.sigma] | <code>number</code> | | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
| [options.precision] | <code>string</code> | <code>&quot;&#x27;integer&#x27;&quot;</code> | How accurate the operation should be, one of: integer, float, approximate. |
**Example** **Example**
```js ```js

File diff suppressed because one or more lines are too long

View File

@ -226,6 +226,7 @@ const Sharp = function (input, options) {
negateAlpha: true, negateAlpha: true,
medianSize: 0, medianSize: 0,
blurSigma: 0, blurSigma: 0,
precision: 'integer',
sharpenSigma: 0, sharpenSigma: 0,
sharpenM1: 1, sharpenM1: 1,
sharpenM2: 2, sharpenM2: 2,

11
lib/index.d.ts vendored
View File

@ -464,7 +464,7 @@ declare namespace sharp {
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations * @returns A sharp instance that can be used to chain operations
*/ */
blur(sigma?: number | boolean): Sharp; blur(sigma?: number | boolean | BlurOptions): Sharp;
/** /**
* Merge alpha transparency channel, if any, with background. * Merge alpha transparency channel, if any, with background.
@ -1342,6 +1342,15 @@ declare namespace sharp {
background?: Color | undefined; background?: Color | undefined;
} }
type Precision = 'integer' | 'float' | 'approximate';
interface BlurOptions {
/** A value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2` */
sigma: number;
/** How accurate the operation should be, one of: integer, float, approximate. (optional, default "integer") */
precision?: Precision | undefined;
}
interface FlattenOptions { interface FlattenOptions {
/** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */ /** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */
background?: Color | undefined; background?: Color | undefined;

View File

@ -6,6 +6,17 @@
const color = require('color'); const color = require('color');
const is = require('./is'); const is = require('./is');
/**
* How accurate an operation should be.
* @member
* @private
*/
const vipsPrecision = {
integer: 'integer',
float: 'float',
approximate: 'approximate'
};
/** /**
* Rotate the output image by either an explicit angle * Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag. * or auto-orient based on the EXIF `Orientation` tag.
@ -367,23 +378,43 @@ function median (size) {
* .blur(5) * .blur(5)
* .toBuffer(); * .toBuffer();
* *
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. * @param {Object|number|Boolean} [options]
* @param {number} [options.sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {string} [options.precision='integer'] How accurate the operation should be, one of: integer, float, approximate.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function blur (sigma) { function blur (options) {
if (!is.defined(sigma)) { let sigma;
if (is.number(options)) {
sigma = options;
} else if (is.plainObject(options)) {
if (!is.number(options.sigma)) {
throw is.invalidParameterError('options.sigma', 'number between 0.3 and 1000', sigma);
}
sigma = options.sigma;
if ('precision' in options) {
if (is.string(vipsPrecision[options.precision])) {
this.options.precision = vipsPrecision[options.precision];
} else {
throw is.invalidParameterError('precision', 'one of: integer, float, approximate', options.precision);
}
}
}
if (!is.defined(options)) {
// No arguments: default to mild blur // No arguments: default to mild blur
this.options.blurSigma = -1; this.options.blurSigma = -1;
} else if (is.bool(sigma)) { } else if (is.bool(options)) {
// Boolean argument: apply mild blur? // Boolean argument: apply mild blur?
this.options.blurSigma = sigma ? -1 : 0; this.options.blurSigma = options ? -1 : 0;
} else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) { } else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
// Numeric argument: specific sigma // Numeric argument: specific sigma
this.options.blurSigma = sigma; this.options.blurSigma = sigma;
} else { } else {
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma); throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
} }
return this; return this;
} }

View File

@ -144,7 +144,7 @@ namespace sharp {
/* /*
* Gaussian blur. Use sigma of -1.0 for fast blur. * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma) { VImage Blur(VImage image, double const sigma, VipsPrecision precision) {
if (sigma == -1.0) { if (sigma == -1.0) {
// Fast, mild blur - averages neighbouring pixels // Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3, VImage blur = VImage::new_matrixv(3, 3,
@ -155,7 +155,8 @@ namespace sharp {
return image.conv(blur); return image.conv(blur);
} else { } else {
// Slower, accurate Gaussian blur // Slower, accurate Gaussian blur
return StaySequential(image).gaussblur(sigma); return StaySequential(image).gaussblur(sigma, VImage::option()
->set("precision", precision));
} }
} }

View File

@ -47,7 +47,7 @@ namespace sharp {
/* /*
* Gaussian blur. Use sigma of -1.0 for fast blur. * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma); VImage Blur(VImage image, double const sigma, VipsPrecision precision);
/* /*
* Convolution with a kernel. * Convolution with a kernel.

View File

@ -592,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Blur // Blur
if (shouldBlur) { if (shouldBlur) {
image = sharp::Blur(image, baton->blurSigma); image = sharp::Blur(image, baton->blurSigma, baton->precision);
} }
// Unflatten the image // Unflatten the image
@ -1541,6 +1541,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->negate = sharp::AttrAsBool(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha"); baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION);
baton->brightness = sharp::AttrAsDouble(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = sharp::AttrAsDouble(options, "saturation"); baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = sharp::AttrAsInt32(options, "hue"); baton->hue = sharp::AttrAsInt32(options, "hue");

View File

@ -78,6 +78,7 @@ struct PipelineBaton {
bool negate; bool negate;
bool negateAlpha; bool negateAlpha;
double blurSigma; double blurSigma;
VipsPrecision precision;
double brightness; double brightness;
double saturation; double saturation;
int hue; int hue;

View File

@ -35,6 +35,19 @@ describe('Blur', function () {
}); });
}); });
it('specific options.sigma 10', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur({ sigma: 10 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), data, done);
});
});
it('specific radius 0.3', function (done) { it('specific radius 0.3', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
@ -91,4 +104,33 @@ describe('Blur', function () {
}); });
}); });
}); });
it('invalid precision', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' });
}, /Expected one of: integer, float, approximate for precision but received invalid of type string/);
});
it('specific radius 10 and precision approximate', async () => {
const approximate = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur({ sigma: 10, precision: 'approximate' })
.toBuffer();
const integer = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(10)
.toBuffer();
assert.notDeepEqual(approximate, integer);
await new Promise(resolve => {
fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), approximate, resolve);
});
});
it('options.sigma is required if options object is passed', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ precision: 'invalid' });
}, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/);
});
}); });