diff --git a/README.md b/README.md index 47ed9bb8..665733a3 100755 --- a/README.md +++ b/README.md @@ -110,21 +110,33 @@ image.metadata(function(err, metadata) { ``` ```javascript -var pipeline = sharp().rotate().resize(null, 200).progressive().toBuffer(function(err, outputBuffer, info) { - if (err) { - throw err; - } - // outputBuffer contains 200px high progressive JPEG image data, auto-rotated using EXIF Orientation tag - // info.width and info.height contain the final pixel dimensions of the resized image -}); +var pipeline = sharp() + .rotate() + .resize(null, 200) + .progressive() + .toBuffer(function(err, outputBuffer, info) { + if (err) { + throw err; + } + // outputBuffer contains 200px high progressive JPEG image data, + // auto-rotated using EXIF Orientation tag + // info.width and info.height contain the dimensions of the resized image + }); readableStream.pipe(pipeline); ``` ```javascript -sharp('input.png').rotate(180).resize(300).sharpen().quality(90).webp().toBuffer().then(function(outputBuffer, info) { - // outputBuffer contains 300px wide, upside down, sharpened, 90% quality WebP image data - // info.width and info.height contain the final pixel dimensions of the resized image -}); +sharp('input.png') + .rotate(180) + .resize(300) + .sharpen() + .quality(90) + .webp() + .toBuffer() + .then(function(outputBuffer) { + // outputBuffer contains 300px wide, upside down, sharpened, + // 90% quality WebP image data + }); ``` ```javascript @@ -137,10 +149,16 @@ http.createServer(function(request, response) { ``` ```javascript -sharp(inputBuffer).resize(200, 300).bicubicInterpolation().embedWhite().toFile('output.tiff').then(function() { - // output.tiff is a 200 pixels wide and 300 pixels high image containing a bicubic scaled - // version, embedded on a white canvas, of the image data in buffer -}); +sharp(inputBuffer) + .resize(200, 300) + .interpolateWith(sharp.interpolator.nohalo) + .embedWhite() + .toFile('output.tiff') + .then(function() { + // output.tiff is a 200 pixels wide and 300 pixels high image + // containing a bicubic scaled version, embedded on a white canvas, + // of the image data in inputBuffer + }); ``` ```javascript @@ -247,17 +265,17 @@ This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensio Perform a mild sharpen of the resultant image. This typically reduces performance by 30%. -#### bilinearInterpolation() +#### interpolateWith(interpolator) -Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) for image resizing, the default (and fastest) interpolation if none is specified. +Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`. -#### bicubicInterpolation() +Possible interpolators, in order of performance, are: -Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) for image resizing. This typically reduces performance by 5%. - -#### nohaloInterpolation() - -Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/) for image resizing. This typically reduces performance by a factor of 2. +* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default (and fastest) interpolation. +* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%. +* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" when enlarging and typically reduces performance by 5%. +* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2. +* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3. ### Output options diff --git a/index.js b/index.js index 74674444..0281e08a 100755 --- a/index.js +++ b/index.js @@ -135,28 +135,32 @@ Sharp.prototype.sharpen = function(sharpen) { }; /* - Use bilinear interpolation for the affine transformation (fastest, default) + Set the interpolator to use for the affine transformation */ -Sharp.prototype.bilinearInterpolation = function() { - this.options.interpolator = 'bilinear'; +module.exports.interpolator = { + bilinear: 'bilinear', + bicubic: 'bicubic', + nohalo: 'nohalo', + locallyBoundedBicubic: 'lbb', + vertexSplitQuadraticBasisSpline: 'vsqbs' +}; +Sharp.prototype.interpolateWith = function(interpolator) { + this.options.interpolator = interpolator; return this; }; /* - Use bicubic interpolation for the affine transformation + Deprecated interpolation methods, to be removed in v0.7.0 */ -Sharp.prototype.bicubicInterpolation = function() { - this.options.interpolator = 'bicubic'; - return this; -}; - -/* - Use Nohalo interpolation for the affine transformation -*/ -Sharp.prototype.nohaloInterpolation = function() { - this.options.interpolator = 'nohalo'; - return this; -}; +Sharp.prototype.bilinearInterpolation = util.deprecate(function() { + return this.interpolateWith(module.exports.interpolator.bilinear); +}, 'bilinearInterpolation() is deprecated, use interpolateWith(sharp.interpolator.bilinear) instead'); +Sharp.prototype.bicubicInterpolation = util.deprecate(function() { + return this.interpolateWith(module.exports.interpolator.bicubic); +}, 'bicubicInterpolation() is deprecated, use interpolateWith(sharp.interpolator.bicubic) instead'); +Sharp.prototype.nohaloInterpolation = util.deprecate(function() { + return this.interpolateWith(module.exports.interpolator.nohalo); +}, 'nohaloInterpolation() is deprecated, use interpolateWith(sharp.interpolator.nohalo) instead'); Sharp.prototype.progressive = function(progressive) { this.options.progressive = (typeof progressive === 'boolean') ? progressive : true; diff --git a/tests/perf.js b/tests/perf.js index c53c9e70..1abd5fe1 100755 --- a/tests/perf.js +++ b/tests/perf.js @@ -188,7 +188,7 @@ async.series({ }).add("sharp-file-buffer-bicubic", { defer: true, fn: function(deferred) { - sharp(inputJpg).resize(width, height).bicubicInterpolation().toBuffer(function(err, buffer) { + sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) { if (err) { throw err; } else { @@ -200,7 +200,31 @@ async.series({ }).add("sharp-file-buffer-nohalo", { defer: true, fn: function(deferred) { - sharp(inputJpg).resize(width, height).nohaloInterpolation().toBuffer(function(err, buffer) { + sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add("sharp-file-buffer-locallyBoundedBicubic", { + defer: true, + fn: function(deferred) { + sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add("sharp-file-buffer-vertexSplitQuadraticBasisSpline", { + defer: true, + fn: function(deferred) { + sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) { if (err) { throw err; } else { diff --git a/tests/unit.js b/tests/unit.js index 6ccc8331..ba9a79a8 100755 --- a/tests/unit.js +++ b/tests/unit.js @@ -249,7 +249,7 @@ async.series([ }, // Interpolation: bilinear function(done) { - sharp(inputJpg).resize(320, 240).bilinearInterpolation().toBuffer(function(err, data, info) { + sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bilinear).toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(320, info.width); @@ -259,7 +259,7 @@ async.series([ }, // Interpolation: bicubic function(done) { - sharp(inputJpg).resize(320, 240).bicubicInterpolation().toBuffer(function(err, data, info) { + sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(320, info.width); @@ -269,7 +269,27 @@ async.series([ }, // Interpolation: nohalo function(done) { - sharp(inputJpg).resize(320, 240).nohaloInterpolation().toBuffer(function(err, data, info) { + sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }, + // Interpolation: locally bounded bicubic (LBB) + function(done) { + sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }, + // Interpolation: vertex split quadratic basis spline (VSQBS) + function(done) { + sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual(320, info.width); @@ -346,7 +366,7 @@ async.series([ }); }); var pipeline = sharp().resize(320, 240); - readable.pipe(pipeline).pipe(writable) + readable.pipe(pipeline).pipe(writable); }, // Crop, gravity=north function(done) { @@ -516,7 +536,7 @@ async.series([ assert.strictEqual(1362, info.width); assert.strictEqual(1112, info.height); done(); - }) + }); }); }, // Verify internal counters