mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Ensure correct Gaussian blur before affine #121
Use double sigma instead of int radius for blur
This commit is contained in:
parent
177a4f574c
commit
b7c7fc22f3
@ -319,13 +319,13 @@ Do not enlarge the output image if the input image width *or* height are already
|
|||||||
|
|
||||||
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
||||||
|
|
||||||
#### blur([radius])
|
#### blur([sigma])
|
||||||
|
|
||||||
When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%.
|
When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%.
|
||||||
|
|
||||||
When a `radius` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 30%.
|
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
|
||||||
|
|
||||||
* `radius`, if present, is an integral Number representing the approximate blur mask radius in pixels.
|
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels.
|
||||||
|
|
||||||
#### sharpen([radius], [flat], [jagged])
|
#### sharpen([radius], [flat], [jagged])
|
||||||
|
|
||||||
|
24
index.js
24
index.js
@ -43,7 +43,7 @@ var Sharp = function(input) {
|
|||||||
// operations
|
// operations
|
||||||
background: [0, 0, 0, 255],
|
background: [0, 0, 0, 255],
|
||||||
flatten: false,
|
flatten: false,
|
||||||
blurRadius: 0,
|
blurSigma: 0,
|
||||||
sharpenRadius: 0,
|
sharpenRadius: 0,
|
||||||
sharpenFlat: 1,
|
sharpenFlat: 1,
|
||||||
sharpenJagged: 2,
|
sharpenJagged: 2,
|
||||||
@ -208,21 +208,21 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Blur the output image.
|
Blur the output image.
|
||||||
Call without a radius to use a fast, mild blur.
|
Call without a sigma to use a fast, mild blur.
|
||||||
Call with a radius to use a slower, more accurate Gaussian blur.
|
Call with a sigma to use a slower, more accurate Gaussian blur.
|
||||||
*/
|
*/
|
||||||
Sharp.prototype.blur = function(radius) {
|
Sharp.prototype.blur = function(sigma) {
|
||||||
if (typeof radius === 'undefined') {
|
if (typeof sigma === 'undefined') {
|
||||||
// No arguments: default to mild blur
|
// No arguments: default to mild blur
|
||||||
this.options.blurRadius = -1;
|
this.options.blurSigma = -1;
|
||||||
} else if (typeof radius === 'boolean') {
|
} else if (typeof sigma === 'boolean') {
|
||||||
// Boolean argument: apply mild blur?
|
// Boolean argument: apply mild blur?
|
||||||
this.options.blurRadius = radius ? -1 : 0;
|
this.options.blurSigma = sigma ? -1 : 0;
|
||||||
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
|
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
|
||||||
// Numeric argument: specific radius
|
// Numeric argument: specific sigma
|
||||||
this.options.blurRadius = radius;
|
this.options.blurSigma = sigma;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid blur radius ' + radius + ' (expected integer >= 1)');
|
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.8.0",
|
"version": "0.8.1",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||||
|
@ -51,7 +51,7 @@ struct ResizeBaton {
|
|||||||
std::string interpolator;
|
std::string interpolator;
|
||||||
double background[4];
|
double background[4];
|
||||||
bool flatten;
|
bool flatten;
|
||||||
int blurRadius;
|
double blurSigma;
|
||||||
int sharpenRadius;
|
int sharpenRadius;
|
||||||
double sharpenFlat;
|
double sharpenFlat;
|
||||||
double sharpenJagged;
|
double sharpenJagged;
|
||||||
@ -78,7 +78,7 @@ struct ResizeBaton {
|
|||||||
canvas(Canvas::CROP),
|
canvas(Canvas::CROP),
|
||||||
gravity(0),
|
gravity(0),
|
||||||
flatten(false),
|
flatten(false),
|
||||||
blurRadius(0),
|
blurSigma(0.0),
|
||||||
sharpenRadius(0),
|
sharpenRadius(0),
|
||||||
sharpenFlat(1.0),
|
sharpenFlat(1.0),
|
||||||
sharpenJagged(2.0),
|
sharpenJagged(2.0),
|
||||||
@ -364,22 +364,33 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use vips_affine with the remaining float part
|
// Use vips_affine with the remaining float part
|
||||||
if (residual != 0) {
|
if (residual != 0.0) {
|
||||||
// Apply variable blur radius of floor(residual) before large affine reductions
|
// Apply Gaussian blur before large affine reductions
|
||||||
if (residual >= 1) {
|
if (residual < 1.0) {
|
||||||
|
// Calculate standard deviation
|
||||||
|
double sigma = ((1.0 / residual) - 0.5) / 1.5;
|
||||||
|
if (sigma >= 0.3) {
|
||||||
|
// Create Gaussian function for standard deviation
|
||||||
|
VipsImage *gaussian;
|
||||||
|
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
||||||
|
return Error(baton, hook);
|
||||||
|
}
|
||||||
|
vips_object_local(hook, gaussian);
|
||||||
|
// Apply Gaussian function
|
||||||
VipsImage *blurred;
|
VipsImage *blurred;
|
||||||
if (vips_gaussblur(image, &blurred, floor(residual), NULL)) {
|
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
||||||
return Error(baton, hook);
|
return Error(baton, hook);
|
||||||
}
|
}
|
||||||
vips_object_local(hook, blurred);
|
vips_object_local(hook, blurred);
|
||||||
image = blurred;
|
image = blurred;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
|
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
|
||||||
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
|
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
|
||||||
vips_object_local(hook, interpolator);
|
vips_object_local(hook, interpolator);
|
||||||
// Perform affine transformation
|
// Perform affine transformation
|
||||||
VipsImage *affined;
|
VipsImage *affined;
|
||||||
if (vips_affine(image, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) {
|
if (vips_affine(image, &affined, residual, 0.0, 0.0, residual, "interpolate", interpolator, NULL)) {
|
||||||
return Error(baton, hook);
|
return Error(baton, hook);
|
||||||
}
|
}
|
||||||
vips_object_local(hook, affined);
|
vips_object_local(hook, affined);
|
||||||
@ -502,10 +513,10 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Blur
|
// Blur
|
||||||
if (baton->blurRadius != 0) {
|
if (baton->blurSigma != 0.0) {
|
||||||
VipsImage *blurred;
|
VipsImage *blurred;
|
||||||
if (baton->blurRadius == -1) {
|
if (baton->blurSigma < 0.0) {
|
||||||
// Fast, mild blur
|
// Fast, mild blur - averages neighbouring pixels
|
||||||
VipsImage *blur = vips_image_new_matrixv(3, 3,
|
VipsImage *blur = vips_image_new_matrixv(3, 3,
|
||||||
1.0, 1.0, 1.0,
|
1.0, 1.0, 1.0,
|
||||||
1.0, 1.0, 1.0,
|
1.0, 1.0, 1.0,
|
||||||
@ -517,7 +528,15 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Slower, accurate Gaussian blur
|
// Slower, accurate Gaussian blur
|
||||||
if (vips_gaussblur(image, &blurred, baton->blurRadius, NULL)) {
|
// Create Gaussian function for standard deviation
|
||||||
|
VipsImage *gaussian;
|
||||||
|
if (vips_gaussmat(&gaussian, baton->blurSigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
||||||
|
return Error(baton, hook);
|
||||||
|
}
|
||||||
|
vips_object_local(hook, gaussian);
|
||||||
|
// Apply Gaussian function
|
||||||
|
VipsImage *blurred;
|
||||||
|
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
||||||
return Error(baton, hook);
|
return Error(baton, hook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -847,7 +866,7 @@ NAN_METHOD(resize) {
|
|||||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
||||||
// Operators
|
// Operators
|
||||||
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
|
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
|
||||||
baton->blurRadius = options->Get(NanNew<String>("blurRadius"))->Int32Value();
|
baton->blurSigma = options->Get(NanNew<String>("blurSigma"))->NumberValue();
|
||||||
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
|
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
|
||||||
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
|
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
|
||||||
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();
|
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();
|
||||||
|
@ -35,11 +35,11 @@ describe('Blur', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('specific radius 100', function(done) {
|
it('specific radius 0.3', function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.blur(100)
|
.blur(0.3)
|
||||||
.toFile(fixtures.path('output.blur-100.jpg'), function(err, info) {
|
.toFile(fixtures.path('output.blur-0.3.jpg'), function(err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('jpeg', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(320, info.width);
|
assert.strictEqual(320, info.width);
|
||||||
@ -64,7 +64,7 @@ describe('Blur', function() {
|
|||||||
it('invalid radius', function(done) {
|
it('invalid radius', function(done) {
|
||||||
var isValid = true;
|
var isValid = true;
|
||||||
try {
|
try {
|
||||||
sharp(fixtures.inputJpg).blur(1.5);
|
sharp(fixtures.inputJpg).blur(0.1);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
@ -370,7 +370,7 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||||
it('withoutAdaptiveFiltering generates smaller file [libvips 7.41.0+]', function(done) {
|
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.41.0]', function(done) {
|
||||||
// First generate with adaptive filtering
|
// First generate with adaptive filtering
|
||||||
sharp(fixtures.inputPng)
|
sharp(fixtures.inputPng)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
@ -401,7 +401,7 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
|
if (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
|
||||||
it('Load TIFF from Buffer [libvips 7.40.0+]', function(done) {
|
it('Load TIFF from Buffer [libvips ' + sharp.libvipsVersion() + '>=7.40.0]', function(done) {
|
||||||
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
||||||
sharp(inputTiffBuffer)
|
sharp(inputTiffBuffer)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user