From 47927ef47d65e89f7ce99b9b2e7c34c6e38968cb Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Fri, 7 Nov 2014 20:04:07 +0000 Subject: [PATCH] Shrink less, affine more, maintain performance #75 Affects interpolators with 4x4+ window size e.g. Bicubic, LBB, Nohalo Introduces blur before large affine to improve large PNG reductions --- src/common.cc | 12 ++++++++++++ src/common.h | 7 +++++++ src/resize.cc | 37 ++++++++++++++++++++++++++++++++----- test/unit/sharpen.js | 3 ++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/common.cc b/src/common.cc index 8c8a776d..3399b7bd 100755 --- a/src/common.cc +++ b/src/common.cc @@ -116,3 +116,15 @@ sharp_image_has_alpha(VipsImage *image) { (image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK) ); } + +/* + Returns the window size for the named interpolator. For example, + a window size of 3 means a 3x3 pixel grid is used for the calculation. +*/ +int +sharp_interpolator_window_size(char const *name) { + VipsInterpolate *interpolator = vips_interpolate_new(name); + int window_size = vips_interpolate_get_window_size(interpolator); + g_object_unref(interpolator); + return window_size; +} diff --git a/src/common.h b/src/common.h index fde61778..f79e2302 100755 --- a/src/common.h +++ b/src/common.h @@ -43,4 +43,11 @@ sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const bool sharp_image_has_alpha(VipsImage *image); +/* + Returns the window size for the named interpolator. For example, + a window size of 3 means a 3x3 pixel grid is used for the calculation. +*/ +int +sharp_interpolator_window_size(char const *name); + #endif diff --git a/src/resize.cc b/src/resize.cc index adeb6422..8f872b3d 100755 --- a/src/resize.cc +++ b/src/resize.cc @@ -159,6 +159,9 @@ class ResizeWorker : public NanAsyncWorker { baton->flip = TRUE; } + // Get window size of interpolator, used for determining shrink vs affine + int interpolatorWindowSize = sharp_interpolator_window_size(baton->interpolator.c_str()); + // Scaling calculations double factor; if (baton->width > 0 && baton->height > 0) { @@ -188,10 +191,20 @@ class ResizeWorker : public NanAsyncWorker { baton->width = inputWidth; baton->height = inputHeight; } - int shrink = floor(factor); + + // Calculate integral box shrink + int shrink = 1; + if (factor >= 2 && interpolatorWindowSize > 3) { + // Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic + shrink = floor(factor * 3.0 / interpolatorWindowSize); + } else { + shrink = floor(factor); + } if (shrink < 1) { shrink = 1; } + + // Calculate residual float affine transformation double residual = static_cast(shrink) / factor; // Do not enlarge the output if the input width *or* height are already less than the required dimensions @@ -207,7 +220,7 @@ class ResizeWorker : public NanAsyncWorker { // Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract int shrink_on_load = 1; - if (inputImageType == JPEG && baton->gamma == 0 && baton->topOffsetPre == -1) { + if (inputImageType == JPEG && shrink >= 2 && baton->gamma == 0 && baton->topOffsetPre == -1) { if (shrink >= 8) { factor = factor / 8; shrink_on_load = 8; @@ -222,7 +235,11 @@ class ResizeWorker : public NanAsyncWorker { if (shrink_on_load > 1) { // Recalculate integral shrink and double residual factor = std::max(factor, 1.0); - shrink = floor(factor); + if (factor >= 2 && interpolatorWindowSize > 3) { + shrink = floor(factor * 3.0 / interpolatorWindowSize); + } else { + shrink = floor(factor); + } residual = static_cast(shrink) / factor; // Reload input using shrink-on-load g_object_unref(image); @@ -340,11 +357,21 @@ class ResizeWorker : public NanAsyncWorker { // Use vips_affine with the remaining float part if (residual != 0) { - VipsImage *affined = vips_image_new(); - vips_object_local(hook, affined); + // Apply variable blur radius of floor(residual) before large affine reductions + if (residual >= 1) { + VipsImage *blurred = vips_image_new(); + vips_object_local(hook, blurred); + if (vips_gaussblur(image, &blurred, floor(residual), NULL)) { + return Error(baton, hook); + } + g_object_unref(image); + image = blurred; + } // Create interpolator - "bilinear" (default), "bicubic" or "nohalo" VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str()); // Perform affine transformation + VipsImage *affined = vips_image_new(); + vips_object_local(hook, affined); if (vips_affine(image, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) { g_object_unref(interpolator); return Error(baton, hook); diff --git a/test/unit/sharpen.js b/test/unit/sharpen.js index 1d09fd04..c2e6c931 100755 --- a/test/unit/sharpen.js +++ b/test/unit/sharpen.js @@ -17,7 +17,8 @@ describe('Sharpen', function() { assert.strictEqual('jpeg', info.format); assert.strictEqual(320, info.width); assert.strictEqual(240, info.height); - sharp(notSharpened) + sharp(fixtures.inputJpg) + .resize(320, 240) .sharpen() .toBuffer(function(err, sharpened, info) { if (err) throw err;