mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Control level of sharpening via radius/flat/jagged #108
This commit is contained in:
parent
47927ef47d
commit
86681100b7
10
README.md
10
README.md
@ -319,9 +319,15 @@ 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".
|
||||||
|
|
||||||
#### sharpen()
|
#### sharpen([radius], [flat], [jagged])
|
||||||
|
|
||||||
Perform a mild sharpen of the output image. This typically reduces performance by 10%.
|
When used without parameters, perform 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%.
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
|
||||||
|
|
||||||
#### interpolateWith(interpolator)
|
#### interpolateWith(interpolator)
|
||||||
|
|
||||||
|
45
index.js
45
index.js
@ -43,7 +43,9 @@ var Sharp = function(input) {
|
|||||||
// operations
|
// operations
|
||||||
background: [0, 0, 0, 255],
|
background: [0, 0, 0, 255],
|
||||||
flatten: false,
|
flatten: false,
|
||||||
sharpen: false,
|
sharpenRadius: 0,
|
||||||
|
sharpenFlat: 1,
|
||||||
|
sharpenJagged: 2,
|
||||||
gamma: 0,
|
gamma: 0,
|
||||||
greyscale: false,
|
greyscale: false,
|
||||||
// output options
|
// output options
|
||||||
@ -137,16 +139,6 @@ Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Deprecated embed* methods, to be removed in v0.8.0
|
|
||||||
*/
|
|
||||||
Sharp.prototype.embedWhite = util.deprecate(function() {
|
|
||||||
return this.background('white').embed();
|
|
||||||
}, "embedWhite() is deprecated, use background('white').embed() instead");
|
|
||||||
Sharp.prototype.embedBlack = util.deprecate(function() {
|
|
||||||
return this.background('black').embed();
|
|
||||||
}, "embedBlack() is deprecated, use background('black').embed() instead");
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set the background colour for embed and flatten operations.
|
Set the background colour for embed and flatten operations.
|
||||||
Delegates to the 'Color' module, which can throw an Error
|
Delegates to the 'Color' module, which can throw an Error
|
||||||
@ -215,8 +207,35 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.sharpen = function(sharpen) {
|
/*
|
||||||
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
Sharpen the output image.
|
||||||
|
Call without a radius to use a fast, mild sharpen.
|
||||||
|
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
|
||||||
|
radius - size of mask in pixels, must be integer
|
||||||
|
flat - level of "flat" area sharpen, default 1
|
||||||
|
jagged - level of "jagged" area sharpen, default 2
|
||||||
|
*/
|
||||||
|
Sharp.prototype.sharpen = function(radius, flat, jagged) {
|
||||||
|
if (typeof radius === 'undefined') {
|
||||||
|
// No arguments: default to mild sharpen
|
||||||
|
this.options.sharpenRadius = -1;
|
||||||
|
} else if (typeof radius === 'boolean') {
|
||||||
|
// Boolean argument: apply mild sharpen?
|
||||||
|
this.options.sharpenRadius = radius ? -1 : 0;
|
||||||
|
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0)) {
|
||||||
|
// Numeric argument: specific radius
|
||||||
|
this.options.sharpenRadius = radius;
|
||||||
|
if (typeof flat === 'number' && !Number.isNaN(flat)) {
|
||||||
|
// Control over flat areas
|
||||||
|
this.options.sharpenFlat = flat;
|
||||||
|
}
|
||||||
|
if (typeof jagged === 'number' && !Number.isNaN(jagged)) {
|
||||||
|
// Control over jagged areas
|
||||||
|
this.options.sharpenJagged = jagged;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid integral sharpen radius ' + radius);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.7.2",
|
"version": "0.8.0",
|
||||||
"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>",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^2.3.11",
|
"bluebird": "^2.3.11",
|
||||||
"color": "^0.7.1",
|
"color": "^0.7.1",
|
||||||
"nan": "^1.4.0",
|
"nan": "^1.4.1",
|
||||||
"semver": "^4.1.0"
|
"semver": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -50,7 +50,9 @@ struct ResizeBaton {
|
|||||||
std::string interpolator;
|
std::string interpolator;
|
||||||
double background[4];
|
double background[4];
|
||||||
bool flatten;
|
bool flatten;
|
||||||
bool sharpen;
|
int sharpenRadius;
|
||||||
|
double sharpenFlat;
|
||||||
|
double sharpenJagged;
|
||||||
double gamma;
|
double gamma;
|
||||||
bool greyscale;
|
bool greyscale;
|
||||||
int angle;
|
int angle;
|
||||||
@ -74,7 +76,9 @@ struct ResizeBaton {
|
|||||||
canvas(CROP),
|
canvas(CROP),
|
||||||
gravity(0),
|
gravity(0),
|
||||||
flatten(false),
|
flatten(false),
|
||||||
sharpen(false),
|
sharpenRadius(0),
|
||||||
|
sharpenFlat(1.0),
|
||||||
|
sharpenJagged(2.0),
|
||||||
gamma(0.0),
|
gamma(0.0),
|
||||||
greyscale(false),
|
greyscale(false),
|
||||||
flip(false),
|
flip(false),
|
||||||
@ -506,18 +510,26 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
image = extractedPost;
|
image = extractedPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mild sharpen
|
// Sharpen
|
||||||
if (baton->sharpen) {
|
if (baton->sharpenRadius != 0) {
|
||||||
VipsImage *sharpened = vips_image_new();
|
VipsImage *sharpened = vips_image_new();
|
||||||
vips_object_local(hook, sharpened);
|
vips_object_local(hook, sharpened);
|
||||||
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
|
if (baton->sharpenRadius == -1) {
|
||||||
-1.0, -1.0, -1.0,
|
// Fast, mild sharpen
|
||||||
-1.0, 32.0, -1.0,
|
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
|
||||||
-1.0, -1.0, -1.0);
|
-1.0, -1.0, -1.0,
|
||||||
vips_image_set_double(sharpen, "scale", 24);
|
-1.0, 32.0, -1.0,
|
||||||
vips_object_local(hook, sharpen);
|
-1.0, -1.0, -1.0);
|
||||||
if (vips_conv(image, &sharpened, sharpen, NULL)) {
|
vips_image_set_double(sharpen, "scale", 24);
|
||||||
return Error(baton, hook);
|
vips_object_local(hook, sharpen);
|
||||||
|
if (vips_conv(image, &sharpened, sharpen, NULL)) {
|
||||||
|
return Error(baton, hook);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
|
||||||
|
if (vips_sharpen(image, &sharpened, "radius", baton->sharpenRadius, "m1", baton->sharpenFlat, "m2", baton->sharpenJagged, NULL)) {
|
||||||
|
return Error(baton, hook);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
g_object_unref(image);
|
g_object_unref(image);
|
||||||
image = sharpened;
|
image = sharpened;
|
||||||
@ -838,7 +850,9 @@ 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->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
|
||||||
|
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
|
||||||
|
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();
|
||||||
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
||||||
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
|
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
|
||||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
||||||
|
@ -163,7 +163,7 @@ async.series({
|
|||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add('sharp-sharpen', {
|
}).add('sharp-sharpen-mild', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||||
@ -175,6 +175,18 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).add('sharp-sharpen-radius', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).sharpen(3, 1, 3).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}).add('sharp-nearest-neighbour', {
|
}).add('sharp-nearest-neighbour', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
|
@ -7,7 +7,57 @@ var fixtures = require('../fixtures');
|
|||||||
|
|
||||||
describe('Sharpen', function() {
|
describe('Sharpen', function() {
|
||||||
|
|
||||||
it('sharpen image is larger than non-sharpen', function(done) {
|
it('specific radius and levels 0.5, 2.5', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(3, 0.5, 2.5)
|
||||||
|
.toFile(fixtures.path('output.sharpen-3-0.5-2.5.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('specific radius 3 and levels 2, 4', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(5, 2, 4)
|
||||||
|
.toFile(fixtures.path('output.sharpen-5-2-4.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 sharpen', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen()
|
||||||
|
.toFile(fixtures.path('output.sharpen-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).sharpen(1.5);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sharpened image is larger than non-sharpened', function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.sharpen(false)
|
.sharpen(false)
|
||||||
@ -19,7 +69,7 @@ describe('Sharpen', function() {
|
|||||||
assert.strictEqual(240, info.height);
|
assert.strictEqual(240, info.height);
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.sharpen()
|
.sharpen(true)
|
||||||
.toBuffer(function(err, sharpened, info) {
|
.toBuffer(function(err, sharpened, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(true, sharpened.length > 0);
|
assert.strictEqual(true, sharpened.length > 0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user