Add 'fast' blur and Gaussian blur feature #108

This commit is contained in:
Lovell Fuller 2014-11-10 22:38:13 +00:00
parent df5cf402e3
commit 32d9bc204a
5 changed files with 156 additions and 2 deletions

View File

@ -319,11 +319,19 @@ 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])
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%.
* `radius`, if present, is an integral Number representing the approximate blur mask radius in pixels.
#### sharpen([radius], [flat], [jagged])
When used without parameters, perform a fast, mild sharpen of the output image. This typically reduces performance by 10%.
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
When a `radius` is provided, perform a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
When a `radius` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
* `radius`, if present, is an integral Number representing the sharpen mask radius in pixels.
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.

View File

@ -43,6 +43,7 @@ var Sharp = function(input) {
// operations
background: [0, 0, 0, 255],
flatten: false,
blurRadius: 0,
sharpenRadius: 0,
sharpenFlat: 1,
sharpenJagged: 2,
@ -207,6 +208,27 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
return this;
};
/*
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.
*/
Sharp.prototype.blur = function(radius) {
if (typeof radius === 'undefined') {
// No arguments: default to mild blur
this.options.blurRadius = -1;
} else if (typeof radius === 'boolean') {
// Boolean argument: apply mild blur?
this.options.blurRadius = radius ? -1 : 0;
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0)) {
// Numeric argument: specific radius
this.options.blurRadius = radius;
} else {
throw new Error('Invalid integral blur radius ' + radius);
}
return this;
};
/*
Sharpen the output image.
Call without a radius to use a fast, mild sharpen.

View File

@ -50,6 +50,7 @@ struct ResizeBaton {
std::string interpolator;
double background[4];
bool flatten;
int blurRadius;
int sharpenRadius;
double sharpenFlat;
double sharpenJagged;
@ -76,6 +77,7 @@ struct ResizeBaton {
canvas(CROP),
gravity(0),
flatten(false),
blurRadius(0),
sharpenRadius(0),
sharpenFlat(1.0),
sharpenJagged(2.0),
@ -510,6 +512,31 @@ class ResizeWorker : public NanAsyncWorker {
image = extractedPost;
}
// Blur
if (baton->blurRadius != 0) {
VipsImage *blurred = vips_image_new();
vips_object_local(hook, blurred);
if (baton->blurRadius == -1) {
// Fast, mild blur
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);
vips_image_set_double(blur, "scale", 9);
vips_object_local(hook, blur);
if (vips_conv(image, &blurred, blur, NULL)) {
return Error(baton, hook);
}
} else {
// Slower, accurate Gaussian blur
if (vips_gaussblur(image, &blurred, baton->blurRadius, NULL)) {
return Error(baton, hook);
}
}
g_object_unref(image);
image = blurred;
}
// Sharpen
if (baton->sharpenRadius != 0) {
VipsImage *sharpened = vips_image_new();
@ -850,6 +877,7 @@ NAN_METHOD(resize) {
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
// Operators
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
baton->blurRadius = options->Get(NanNew<String>("blurRadius"))->Int32Value();
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();

View File

@ -187,6 +187,30 @@ async.series({
}
});
}
}).add('sharp-blur-mild', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).blur().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-blur-radius', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).blur(3).toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-nearest-neighbour', {
defer: true,
fn: function(deferred) {

72
test/unit/blur.js Executable file
View File

@ -0,0 +1,72 @@
'use strict';
var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
describe('Blur', function() {
it('specific radius', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(1)
.toFile(fixtures.path('output.blur-1.jpg'), function(err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('mild blur', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur()
.toFile(fixtures.path('output.blur-mild.jpg'), function(err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('invalid radius', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).blur(1.5);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('blurred image is smaller than non-blurred', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(false)
.toBuffer(function(err, notBlurred, info) {
if (err) throw err;
assert.strictEqual(true, notBlurred.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(true)
.toBuffer(function(err, blurred, info) {
if (err) throw err;
assert.strictEqual(true, blurred.length > 0);
assert.strictEqual(true, blurred.length < notBlurred.length);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
});
});