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

Add blur precision #4168

Merged
merged 1 commit into from
Jul 20, 2024
Merged
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
10 changes: 6 additions & 4 deletions docs/api-operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ const output = await sharp(input).median(5).toBuffer();


## blur
> blur([sigma]) ⇒ <code>Sharp</code>
> blur([options]) ⇒ <code>Sharp</code>

Blur the image.

Expand All @@ -245,9 +245,11 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
- <code>Error</code> Invalid parameters


| Param | Type | Description |
| --- | --- | --- |
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| [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**
```js
Expand Down
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ const Sharp = function (input, options) {
negateAlpha: true,
medianSize: 0,
blurSigma: 0,
precision: 'integer',
sharpenSigma: 0,
sharpenM1: 1,
sharpenM2: 2,
Expand Down
11 changes: 10 additions & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ declare namespace sharp {
* @throws {Error} Invalid parameters
* @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.
Expand Down Expand Up @@ -1342,6 +1342,15 @@ declare namespace sharp {
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 {
/** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */
background?: Color | undefined;
Expand Down
41 changes: 36 additions & 5 deletions lib/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@
const color = require('color');
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
* or auto-orient based on the EXIF `Orientation` tag.
Expand Down Expand Up @@ -367,23 +378,43 @@ function median (size) {
* .blur(5)
* .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}
* @throws {Error} Invalid parameters
*/
function blur (sigma) {
if (!is.defined(sigma)) {
function blur (options) {
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
this.options.blurSigma = -1;
} else if (is.bool(sigma)) {
} else if (is.bool(options)) {
// 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)) {
// Numeric argument: specific sigma
this.options.blurSigma = sigma;
} else {
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
}

return this;
}

Expand Down
5 changes: 3 additions & 2 deletions src/operations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ namespace sharp {
/*
* 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) {
// Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3,
Expand All @@ -155,7 +155,8 @@ namespace sharp {
return image.conv(blur);
} else {
// Slower, accurate Gaussian blur
return StaySequential(image).gaussblur(sigma);
return StaySequential(image).gaussblur(sigma, VImage::option()
->set("precision", precision));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/operations.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ namespace sharp {
/*
* 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.
Expand Down
3 changes: 2 additions & 1 deletion src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {

// Blur
if (shouldBlur) {
image = sharp::Blur(image, baton->blurSigma);
image = sharp::Blur(image, baton->blurSigma, baton->precision);
}

// Unflatten the image
Expand Down Expand Up @@ -1541,6 +1541,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->negate = sharp::AttrAsBool(options, "negate");
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION);
baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = sharp::AttrAsInt32(options, "hue");
Expand Down
1 change: 1 addition & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ struct PipelineBaton {
bool negate;
bool negateAlpha;
double blurSigma;
VipsPrecision precision;
double brightness;
double saturation;
int hue;
Expand Down
42 changes: 42 additions & 0 deletions test/unit/blur.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
sharp(fixtures.inputJpg)
.resize(320, 240)
Expand Down Expand Up @@ -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/);
});
});