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) (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 bool
sharp_image_has_alpha(VipsImage *image); 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 #endif

View File

@ -159,6 +159,9 @@ class ResizeWorker : public NanAsyncWorker {
baton->flip = TRUE; 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 // Scaling calculations
double factor; double factor;
if (baton->width > 0 && baton->height > 0) { if (baton->width > 0 && baton->height > 0) {
@ -188,10 +191,20 @@ class ResizeWorker : public NanAsyncWorker {
baton->width = inputWidth; baton->width = inputWidth;
baton->height = inputHeight; 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) { if (shrink < 1) {
shrink = 1; shrink = 1;
} }
// Calculate residual float affine transformation
double residual = static_cast<double>(shrink) / factor; 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 // 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 // Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1; 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) { if (shrink >= 8) {
factor = factor / 8; factor = factor / 8;
shrink_on_load = 8; shrink_on_load = 8;
@ -222,7 +235,11 @@ class ResizeWorker : public NanAsyncWorker {
if (shrink_on_load > 1) { if (shrink_on_load > 1) {
// Recalculate integral shrink and double residual // Recalculate integral shrink and double residual
factor = std::max(factor, 1.0); 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; residual = static_cast<double>(shrink) / factor;
// Reload input using shrink-on-load // Reload input using shrink-on-load
g_object_unref(image); g_object_unref(image);
@ -340,11 +357,21 @@ class ResizeWorker : public NanAsyncWorker {
// Use vips_affine with the remaining float part // Use vips_affine with the remaining float part
if (residual != 0) { if (residual != 0) {
VipsImage *affined = vips_image_new(); // Apply variable blur radius of floor(residual) before large affine reductions
vips_object_local(hook, affined); 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" // Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str()); VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
// Perform affine transformation // 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)) { if (vips_affine(image, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) {
g_object_unref(interpolator); g_object_unref(interpolator);
return Error(baton, hook); return Error(baton, hook);

View File

@ -17,7 +17,8 @@ describe('Sharpen', function() {
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(240, info.height);
sharp(notSharpened) sharp(fixtures.inputJpg)
.resize(320, 240)
.sharpen() .sharpen()
.toBuffer(function(err, sharpened, info) { .toBuffer(function(err, sharpened, info) {
if (err) throw err; if (err) throw err;