diff --git a/README.md b/README.md index 0a11d9b2..32b75ad3 100755 --- a/README.md +++ b/README.md @@ -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". -#### blur([radius]) +#### blur([sigma]) 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]) diff --git a/index.js b/index.js index 9f40367f..125c0ea9 100755 --- a/index.js +++ b/index.js @@ -43,7 +43,7 @@ var Sharp = function(input) { // operations background: [0, 0, 0, 255], flatten: false, - blurRadius: 0, + blurSigma: 0, sharpenRadius: 0, sharpenFlat: 1, sharpenJagged: 2, @@ -208,21 +208,21 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) { /* Blur the output image. - Call without a radius to use a fast, mild blur. - Call with a radius to use a slower, more accurate Gaussian blur. + Call without a sigma to use a fast, mild blur. + Call with a sigma to use a slower, more accurate Gaussian blur. */ -Sharp.prototype.blur = function(radius) { - if (typeof radius === 'undefined') { +Sharp.prototype.blur = function(sigma) { + if (typeof sigma === 'undefined') { // No arguments: default to mild blur - this.options.blurRadius = -1; - } else if (typeof radius === 'boolean') { + this.options.blurSigma = -1; + } else if (typeof sigma === 'boolean') { // Boolean argument: apply mild blur? - this.options.blurRadius = radius ? -1 : 0; - } else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) { - // Numeric argument: specific radius - this.options.blurRadius = radius; + this.options.blurSigma = sigma ? -1 : 0; + } else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) { + // Numeric argument: specific sigma + this.options.blurSigma = sigma; } 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; }; diff --git a/package.json b/package.json index cb57accc..739461bd 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharp", - "version": "0.8.0", + "version": "0.8.1", "author": "Lovell Fuller ", "contributors": [ "Pierre Inglebert ", diff --git a/src/resize.cc b/src/resize.cc index 26c6cdbf..4c829130 100755 --- a/src/resize.cc +++ b/src/resize.cc @@ -51,7 +51,7 @@ struct ResizeBaton { std::string interpolator; double background[4]; bool flatten; - int blurRadius; + double blurSigma; int sharpenRadius; double sharpenFlat; double sharpenJagged; @@ -78,7 +78,7 @@ struct ResizeBaton { canvas(Canvas::CROP), gravity(0), flatten(false), - blurRadius(0), + blurSigma(0.0), sharpenRadius(0), sharpenFlat(1.0), sharpenJagged(2.0), @@ -364,22 +364,33 @@ class ResizeWorker : public NanAsyncWorker { } // Use vips_affine with the remaining float part - if (residual != 0) { - // Apply variable blur radius of floor(residual) before large affine reductions - if (residual >= 1) { - VipsImage *blurred; - if (vips_gaussblur(image, &blurred, floor(residual), NULL)) { - return Error(baton, hook); + if (residual != 0.0) { + // Apply Gaussian blur before large affine reductions + 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; + if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) { + return Error(baton, hook); + } + vips_object_local(hook, blurred); + image = blurred; } - vips_object_local(hook, blurred); - image = blurred; } // Create interpolator - "bilinear" (default), "bicubic" or "nohalo" VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str()); vips_object_local(hook, interpolator); // Perform affine transformation 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); } vips_object_local(hook, affined); @@ -502,10 +513,10 @@ class ResizeWorker : public NanAsyncWorker { } // Blur - if (baton->blurRadius != 0) { + if (baton->blurSigma != 0.0) { VipsImage *blurred; - if (baton->blurRadius == -1) { - // Fast, mild blur + if (baton->blurSigma < 0.0) { + // Fast, mild blur - averages neighbouring pixels VipsImage *blur = vips_image_new_matrixv(3, 3, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, @@ -517,7 +528,15 @@ class ResizeWorker : public NanAsyncWorker { } } else { // 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); } } @@ -847,7 +866,7 @@ NAN_METHOD(resize) { baton->interpolator = *String::Utf8Value(options->Get(NanNew("interpolator"))->ToString()); // Operators baton->flatten = options->Get(NanNew("flatten"))->BooleanValue(); - baton->blurRadius = options->Get(NanNew("blurRadius"))->Int32Value(); + baton->blurSigma = options->Get(NanNew("blurSigma"))->NumberValue(); baton->sharpenRadius = options->Get(NanNew("sharpenRadius"))->Int32Value(); baton->sharpenFlat = options->Get(NanNew("sharpenFlat"))->NumberValue(); baton->sharpenJagged = options->Get(NanNew("sharpenJagged"))->NumberValue(); diff --git a/test/unit/blur.js b/test/unit/blur.js index b3f8e746..5c6c080f 100755 --- a/test/unit/blur.js +++ b/test/unit/blur.js @@ -35,11 +35,11 @@ describe('Blur', function() { }); }); - it('specific radius 100', function(done) { + it('specific radius 0.3', function(done) { sharp(fixtures.inputJpg) .resize(320, 240) - .blur(100) - .toFile(fixtures.path('output.blur-100.jpg'), function(err, info) { + .blur(0.3) + .toFile(fixtures.path('output.blur-0.3.jpg'), function(err, info) { if (err) throw err; assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); @@ -64,7 +64,7 @@ describe('Blur', function() { it('invalid radius', function(done) { var isValid = true; try { - sharp(fixtures.inputJpg).blur(1.5); + sharp(fixtures.inputJpg).blur(0.1); } catch (err) { isValid = false; } diff --git a/test/unit/io.js b/test/unit/io.js index aed88e28..23d5e057 100755 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -370,7 +370,7 @@ describe('Input/output', function() { }); 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 sharp(fixtures.inputPng) .resize(320, 240) @@ -401,7 +401,7 @@ describe('Input/output', function() { }); 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); sharp(inputTiffBuffer) .resize(320, 240)