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
This commit is contained in:
Lovell Fuller 2014-11-07 20:04:07 +00:00
parent 7537adf399
commit 47927ef47d
4 changed files with 53 additions and 6 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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<double>(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<double>(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);

View File

@ -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;