diff --git a/README.md b/README.md index 5e757883..c5676446 100755 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Compiling from source is recommended: cd libvips git checkout 7.38 ./bootstrap.sh - ./configure --enable-debug=no --enable-cxx=no --without-python --without-orc + ./configure --enable-debug=no --enable-cxx=yes --without-python --without-orc make sudo make install sudo ldconfig @@ -100,8 +100,8 @@ sharp('input.png').rotate(180).resize(300).sharpen().quality(90).webp().then(fun ``` ```javascript -sharp(inputBuffer).resize(200, 300).embedWhite().toFile('output.tiff').then(function() { - // output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled +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 }); ``` @@ -176,6 +176,18 @@ 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() + +Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation) for image resizing, the default (and fastest) interpolation if none is specified. + +### bicubicInterpolation() + +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. + ### progressive() Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed. diff --git a/index.js b/index.js index 89d1e6db..bf25bf03 100755 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ var Sharp = function(input) { angle: 0, withoutEnlargement: false, sharpen: false, + interpolator: 'bilinear', progressive: false, sequentialRead: false, quality: 80, @@ -87,6 +88,30 @@ Sharp.prototype.sharpen = function(sharpen) { return this; }; +/* + Use bilinear interpolation for the affine transformation (fastest, default) +*/ +Sharp.prototype.bilinearInterpolation = function() { + this.options.interpolator = 'bilinear'; + return this; +}; + +/* + Use bicubic interpolation for the affine transformation +*/ +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.progressive = function(progressive) { this.options.progressive = (typeof progressive === 'boolean') ? progressive : true; return this; diff --git a/src/sharp.cc b/src/sharp.cc index 3ef87108..06ebf1c9 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -23,6 +23,7 @@ struct resize_baton { bool max; VipsExtend extend; bool sharpen; + std::string interpolator; bool progressive; bool without_enlargement; VipsAccess access_method; @@ -294,12 +295,17 @@ class ResizeWorker : public NanAsyncWorker { } g_object_unref(shrunk_on_load); - // Use vips_affine with the remaining float part using bilinear interpolation + // Use vips_affine with the remaining float part VipsImage *affined = vips_image_new(); if (residual != 0) { - if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) { + // Create interpolator - "bilinear" (default), "bicubic" or "nohalo" + VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str()); + // Perform affine transformation + if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) { + g_object_unref(interpolator); return resize_error(baton, shrunk); } + g_object_unref(interpolator); } else { vips_copy(shrunk, &affined, NULL); } @@ -461,6 +467,7 @@ NAN_METHOD(resize) { } // Other options baton->sharpen = options->Get(NanNew("sharpen"))->BooleanValue(); + baton->interpolator = *String::Utf8Value(options->Get(NanNew("interpolator"))->ToString()); baton->progressive = options->Get(NanNew("progressive"))->BooleanValue(); baton->without_enlargement = options->Get(NanNew("withoutEnlargement"))->BooleanValue(); baton->access_method = options->Get(NanNew("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; diff --git a/tests/perf.js b/tests/perf.js index 0f3cd72a..10351c7a 100755 --- a/tests/perf.js +++ b/tests/perf.js @@ -166,6 +166,30 @@ async.series({ } }); } + }).add("sharp-file-buffer-bicubic", { + defer: true, + fn: function(deferred) { + sharp(inputJpg).resize(width, height).bicubicInterpolation().toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } + }).add("sharp-file-buffer-nohalo", { + defer: true, + fn: function(deferred) { + sharp(inputJpg).resize(width, height).nohaloInterpolation().toBuffer(function(err, buffer) { + if (err) { + throw err; + } else { + assert.notStrictEqual(null, buffer); + deferred.resolve(); + } + }); + } }).add("sharp-file-buffer-progressive", { defer: true, fn: function(deferred) { diff --git a/tests/unit.js b/tests/unit.js index 1f7cdfa5..7ff9e414 100755 --- a/tests/unit.js +++ b/tests/unit.js @@ -268,5 +268,41 @@ async.series([ sharp(inputTiff).webp().then(function() { done(); }); - } + }, + // Interpolation: bilinear + function(done) { + sharp(inputJpg).resize(320, 240).bilinearInterpolation().toFile(outputJpg, function(err) { + if (err) throw err; + imagemagick.identify(outputJpg, function(err, features) { + if (err) throw err; + assert.strictEqual(320, features.width); + assert.strictEqual(240, features.height); + done(); + }); + }); + }, + // Interpolation: bicubic + function(done) { + sharp(inputJpg).resize(320, 240).bicubicInterpolation().toFile(outputJpg, function(err) { + if (err) throw err; + imagemagick.identify(outputJpg, function(err, features) { + if (err) throw err; + assert.strictEqual(320, features.width); + assert.strictEqual(240, features.height); + done(); + }); + }); + }, + // Interpolation: nohalo + function(done) { + sharp(inputJpg).resize(320, 240).nohaloInterpolation().toFile(outputJpg, function(err) { + if (err) throw err; + imagemagick.identify(outputJpg, function(err, features) { + if (err) throw err; + assert.strictEqual(320, features.width); + assert.strictEqual(240, features.height); + done(); + }); + }); + }, ]);