diff --git a/docs/changelog.md b/docs/changelog.md index 09a9c651..4ab48f45 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,6 +14,10 @@ Requires libvips v8.5.2. [#607](https://github.com/lovell/sharp/issues/607) [@puzrin](https://github.com/puzrin) +* Improve performance and accuracy of nearest neighbour integral upsampling. + [#752](https://github.com/lovell/sharp/issues/752) + [@MrIbby](https://github.com/MrIbby) + * Ensure ARM64 pre-built binaries use correct C++11 ABI version. [#772](https://github.com/lovell/sharp/issues/772) [@ajiratech2](https://github.com/ajiratech2) diff --git a/src/pipeline.cc b/src/pipeline.cc index 3224bdd1..fd800058 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -417,16 +417,24 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("centre", baton->centreSampling)); } } - // Perform affine enlargement + // Perform enlargement if (yresidual > 1.0 || xresidual > 1.0) { - vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data()); - if (yresidual > 1.0) { - image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option() - ->set("interpolate", interpolator)); - } - if (xresidual > 1.0) { - image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option() - ->set("interpolate", interpolator)); + if (trunc(xresidual) == xresidual && trunc(yresidual) == yresidual && baton->interpolator == "nearest") { + // Fast, integral nearest neighbour enlargement + image = image.zoom(xresidual, yresidual); + } else { + // Floating point affine transformation + vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data()); + if (yresidual > 1.0 && xresidual > 1.0) { + image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option() + ->set("interpolate", interpolator)); + } else if (yresidual > 1.0) { + image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option() + ->set("interpolate", interpolator)); + } else if (xresidual > 1.0) { + image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option() + ->set("interpolate", interpolator)); + } } } } diff --git a/test/unit/interpolation.js b/test/unit/interpolation.js index ab964168..85372f6b 100644 --- a/test/unit/interpolation.js +++ b/test/unit/interpolation.js @@ -35,17 +35,54 @@ describe('Interpolators and kernels', function () { sharp.interpolator.locallyBoundedBicubic, sharp.interpolator.vertexSplitQuadraticBasisSpline ].forEach(function (interpolator) { - it(interpolator, function (done) { - sharp(fixtures.inputJpg) - .resize(320, null, { interpolator: interpolator }) - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - fixtures.assertSimilar(fixtures.inputJpg, data, done); - }); + describe(interpolator, function () { + it('x and y', function (done) { + sharp(fixtures.inputTiff8BitDepth) + .resize(200, 200, { interpolator: interpolator }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + done(); + }); + }); + it('x only', function (done) { + sharp(fixtures.inputTiff8BitDepth) + .resize(200, 21, { interpolator: interpolator }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(200, info.width); + assert.strictEqual(21, info.height); + done(); + }); + }); + it('y only', function (done) { + sharp(fixtures.inputTiff8BitDepth) + .resize(21, 200, { interpolator: interpolator }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(21, info.width); + assert.strictEqual(200, info.height); + done(); + }); + }); }); }); + + it('nearest with integral factor', function (done) { + sharp(fixtures.inputTiff8BitDepth) + .resize(210, 210, { interpolator: 'nearest' }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(210, info.width); + assert.strictEqual(210, info.height); + done(); + }); + }); }); it('unknown kernel throws', function () {